Skip to main content

Data Serialization and Persistence

In etchblok-test-api, domain models use a consistent pattern of to_dict and from_dict methods to bridge the gap between Python objects and JSON-serializable dictionaries. This approach ensures that complex types like datetime and Enum are correctly formatted for API responses and that incoming request data is properly mapped to model attributes.

Serializing Models for API Responses

When you need to return a model instance in a Flask response, use the to_dict() method. This method converts the model's internal state into a plain dictionary where all values are JSON-compatible (e.g., datetime objects become ISO 8601 strings and Enum members become their underlying values).

from flask import jsonify
from app.models.bookmark import Bookmark

def get_bookmark_response(bookmark: Bookmark):
# to_dict() handles ISO format dates and Enum values automatically
return jsonify(bookmark.to_dict())

Implementation Details

Each model implements to_dict() to include both stored attributes and calculated properties:

  • Bookmark: Includes timestamps (created_at, updated_at) as ISO strings and the status Enum as a string.
  • Tag: Includes the color Enum value and the current usage_count.
  • Collection: Includes a calculated size property and maps the internal collection_type attribute to a type key in the dictionary.

Example from app/models/collection.py:

def to_dict(self) -> Dict[str, Any]:
"""Serialise to JSON-safe dictionary."""
return {
"id": self.id,
"name": self.name,
"type": self.collection_type.value, # Enum to string
"bookmark_ids": self.bookmark_ids,
"filter_rule": self.filter_rule,
"is_pinned": self.is_pinned,
"size": self.size, # Calculated property
"created_at": self.created_at.isoformat(), # Datetime to ISO string
}

Creating Models from Request Data

To instantiate a model from a JSON request body, use the @classmethod from_dict(). In etchblok-test-api, this method is primarily used in the service layer to create new instances from validated user input.

from app.models.tag import Tag

# Example request data
data = {"name": "Research", "color": "blue", "description": "Work related"}

# Create instance
new_tag = Tag.from_dict(data)

Usage in Service Layer

The BookmarkService uses from_dict after performing initial validation on the raw request data.

# app/services/bookmark_service.py

def create_bookmark(self, data: Dict[str, Any]) -> Tuple[Optional[Bookmark], Optional[str]]:
# 1. Validate raw data
error = _validate_url(data.get("url", "")) or _validate_title(data.get("title", ""))
if error:
return None, error

# 2. Instantiate model
bookmark = Bookmark.from_dict(data)

# 3. Persist
self._repo.save_bookmark(bookmark)
return bookmark, None

Handling Complex Types

The serialization methods in etchblok-test-api specifically manage the conversion of Enums and Datetimes to ensure consistency across the API.

Enums

Models like Tag and Collection use Enums for attributes like color and type. The from_dict methods handle the lookup of these Enums from string values.

# app/models/tag.py

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Tag":
"""Construct a Tag from a dictionary."""
# Map string color to TagColor Enum, defaulting to GRAY
color = TagColor(data["color"]) if "color" in data else TagColor.GRAY
return cls(
name=data["name"],
color=color,
description=data.get("description", "")
)

Timestamps

The Bookmark and Collection models automatically generate created_at timestamps upon instantiation. While to_dict exports these as strings, from_dict typically ignores them during creation to allow the model to set its own initial state.

Troubleshooting and Gotchas

Partial State Restoration

The from_dict() methods in etchblok-test-api are designed for creation, not full state restoration. They typically only extract a subset of fields (like url and title) and do not restore internal fields like id, created_at, or updated_at from the input dictionary. If you need to restore a full object from a database, you must manually assign these fields or use a repository-specific loading mechanism.

Missing Required Fields

Bookmark.from_dict() and Tag.from_dict() will raise a KeyError if mandatory fields (like url or name) are missing from the input dictionary. Always validate the presence of required keys in the service layer before calling from_dict.

Field Name Mismatches

In some cases, the JSON key differs from the model attribute name. For example, Collection.from_dict() expects a type key in the dictionary but maps it to the collection_type attribute on the Collection instance.

# app/models/collection.py

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Collection":
# Maps 'type' key to CollectionType enum
ctype = CollectionType(data.get("type", "manual"))
return cls(
name=data["name"],
collection_type=ctype,
filter_rule=data.get("filter_rule", ""),
)