Todo Serialization Format and Mapping Logic in Lifetrace: A Complete Guide
The Lifetrace backend converts SQLAlchemy Todo models into flat Python dictionaries using TodoIcalMixin._todo_to_dict, applying field-specific fallback logic and normalization helpers to match the TodoResponse schema before JSON serialization.
The freeu-group/lifetrace repository provides a robust task management backend that bridges database storage with API consumption and iCalendar export. Understanding the todo serialization format is essential for developers integrating with the Lifetrace API or extending its calendar functionality. When a Todo entity is retrieved from the database, it undergoes a precise transformation process defined in the storage layer that maps SQLAlchemy model attributes to a standardized dictionary structure.
Core Serialization Architecture
The _todo_to_dict Method
The conversion logic resides in lifetrace/storage/todo_manager_ical.py within the TodoIcalMixin class. The _todo_to_dict method accepts an active SQLAlchemy session and a Todo model instance, returning a plain Python dictionary that aligns exactly with the TodoResponse schema defined in lifetrace/schemas/todo.py.
This flat dictionary serves as the intermediate format for both JSON API responses and iCalendar component generation, ensuring consistent data representation across all output channels.
Field Mapping Logic and Fallbacks
The mapping logic handles over 30 fields with intelligent fallbacks to ensure data integrity and backward compatibility:
- id: Required field mapping directly from
todo.id; raisesValueErrorwhen missing. - uid: Maps from
todo.uid; may beNone. - name: Direct mapping from
todo.name. - summary: Uses
getattr(todo, "summary", None) or todo.nameto guarantee a non-empty string by falling back to thenamefield. - due: Maps from
todo.duewith fallback to the legacytodo.deadlinefield. - dtstart/dtend: Maps from
todo.dtstartandtodo.dtend, falling back tostart_timeandend_timerespectively. - time_zone: Maps from
todo.time_zone, withtzidas an alternative source. - is_all_day: Boolean field that defaults to
Falsewhen the database column isNone. - percent_complete: Defaults to
0when the database value isNone. - order: Defaults to
0when not explicitly set. - item_type: Stored as an upper-case iCalendar component (e.g.,
VTODO). - ical_status: Maps from
todo.ical_statusor derives fromtodo.statusvia_to_ical_status. - dtstamp, created, last_modified: Various timestamp columns with fallbacks to
updated_at. - source_type, source_key, source_date: Direct attribute mappings for optional metadata.
Relational data is hydrated separately via abstract methods:
- tags: Retrieved via
_get_todo_tags(session, todo_id), returning a list of strings. - attachments: Retrieved via
_get_todo_attachments(session, todo_id), returning a list of dictionaries matchingTodoAttachmentResponse. - related_activities: Processed through
_safe_int_listto guarantee a list of integers.
Data Normalization Helpers
Specialized utilities in lifetrace/storage/todo_manager_utils.py ensure consistent data formats before serialization.
Status Mapping with _to_ical_status
The _to_ical_status function maps internal status values to iCalendar-compliant strings. According to the source code, active maps to NEEDS-ACTION, completed maps to COMPLETED, and canceled maps to CANCELLED.
Reminder Offset Normalization
The _normalize_reminder_offsets helper parses raw JSON strings into clean, sorted lists of positive integers. For example, the input '[30, "15", -5, "bad"]' is sanitized to [15, 30], with negative values, duplicates, and invalid entries removed. The test suite in tests/test_todo_serialization.py confirms that None values become empty lists.
Safe Integer List Conversion
The _safe_int_list utility safely converts potentially null or JSON values into lists of integers, used specifically for the related_activities field to ensure type safety.
Working with Todo Serialization
Converting a Todo Instance
# Assuming manager is a concrete subclass of TodoIcalMixin
# and session is an active SQLAlchemy session
from lifetrace.storage.models import Todo
todo = session.query(Todo).get(42)
data = manager._todo_to_dict(session, todo)
# data is now a dict matching TodoResponse schema
Expected JSON Output Structure
{
"id": 42,
"uid": "2023-10-12T09:00:00Z",
"name": "Buy groceries",
"summary": "Buy groceries",
"description": "Milk, eggs, bread",
"is_all_day": false,
"status": "active",
"priority": "none",
"reminder_offsets": [10, 30],
"tags": ["shopping", "errands"],
"attachments": [],
"related_activities": [7, 12],
"created_at": "2023-10-10T12:00:00Z",
"updated_at": "2023-10-11T08:45:00Z"
}
Using Normalization Helpers Directly
from lifetrace.storage.todo_manager_utils import _normalize_reminder_offsets
from lifetrace.storage.todo_manager_ical import _to_ical_status
# Clean malformed reminder offsets
raw_offsets = '[30, "15", -5, "bad"]'
clean_offsets = _normalize_reminder_offsets(raw_offsets)
# Result: [15, 30]
# Map status to iCalendar format
ical_status = _to_ical_status("completed")
# Result: "COMPLETED"
Summary
- The todo serialization format in Lifetrace is a flat Python dictionary matching the
TodoResponsePydantic schema defined inlifetrace/schemas/todo.py. - Core logic resides in
lifetrace/storage/todo_manager_ical.py, specifically withinTodoIcalMixin._todo_to_dict. - Field fallbacks ensure data completeness, such as
summaryfalling back toname, andduefalling back todeadline. - Normalization helpers enforce data quality by parsing reminder offsets, mapping status values to iCalendar format, and safely converting integer lists.
- Relational data (tags and attachments) is loaded via abstract methods that must be implemented by concrete manager subclasses.
Frequently Asked Questions
Where is the main todo serialization logic implemented?
The primary serialization logic is implemented in lifetrace/storage/todo_manager_ical.py within the TodoIcalMixin class. The _todo_to_dict method performs the conversion from SQLAlchemy model to dictionary format that matches the TodoResponse schema.
How does Lifetrace handle missing summary fields?
When the summary attribute is null or missing, the serializer falls back to the name field using the expression getattr(todo, "summary", None) or todo.name. This ensures the API always returns a non-empty string for the summary field.
What format are reminder offsets stored in after serialization?
Reminder offsets are normalized into a sorted list of positive integers. The _normalize_reminder_offsets helper parses JSON strings, filters out negative values and duplicates, and converts valid entries to integers, returning an empty list if the input is None.
How is the Todo status mapped to iCalendar format?
The _to_ical_status function maps internal status values to iCalendar-compliant strings: active becomes NEEDS-ACTION, completed becomes COMPLETED, and canceled becomes CANCELLED. This mapping ensures compatibility with standard calendar applications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →