Bookmark Entities
The Bookmark entity is the central domain object in the kaisen99-etchblok-test-api-f5f8018 codebase. Defined in app/models/bookmark.py, it encapsulates a saved URL along with its metadata, organizational tags, and lifecycle state.
Lifecycle States and Visibility
The visibility and lifecycle of a bookmark are managed through the BookmarkStatus enumeration. This status determines how a bookmark is treated by the repository and search services.
class BookmarkStatus(Enum):
"""Visibility status of a bookmark."""
ACTIVE = "active"
ARCHIVED = "archived"
TRASHED = "trashed"
The Bookmark class provides explicit methods to transition between these states. Every state transition automatically triggers an update to the updated_at timestamp via the internal _touch() helper.
archive(): Moves the bookmark to theARCHIVEDstate.trash(): Performs a "soft-delete" by moving the bookmark to theTRASHEDstate.restore(): Returns a trashed or archived bookmark to theACTIVEstate.
Identity and Timestamps
Each Bookmark instance is uniquely identified by a 12-character hexadecimal string. This ID is generated automatically upon instantiation using a slice of a UUID4.
id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
The updated_at field is maintained through the _touch() method, which is called by any method that modifies the bookmark's state or tags:
def _touch(self) -> None:
"""Update the modification timestamp."""
self.updated_at = datetime.utcnow()
Metadata and Tag Management
Bookmarks support two primary forms of additional data:
- Tags: A list of string identifiers stored in the
tagsattribute. - Metadata: An arbitrary dictionary (
metadata) for extensibility, allowing for the storage of key/value pairs that don't fit into the standard schema.
Tag management is handled through dedicated methods that ensure the updated_at timestamp is refreshed:
def add_tag(self, tag_id: str) -> bool:
"""Attach a tag. Returns False if already present."""
if tag_id in self.tags:
return False
self.tags.append(tag_id)
self._touch()
return True
Serialization Patterns
The Bookmark class implements a specific serialization pattern for interacting with the API and persistence layers.
Deserialization for Creation
The from_dict class method is designed specifically for creating new bookmarks from user input (e.g., a JSON request body). It only extracts core fields: url, title, description, and tags.
Note: from_dict does not restore system-managed fields like id, status, or timestamps. These are always generated fresh for new instances.
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Bookmark":
return cls(
url=data["url"],
title=data["title"],
description=data.get("description", ""),
tags=data.get("tags", []),
)
Serialization for Responses
The to_dict method provides a complete dictionary representation of the bookmark, including its current status and ISO-formatted timestamps, suitable for JSON API responses.
def to_dict(self) -> Dict[str, Any]:
return {
"id": self.id,
"url": self.url,
"title": self.title,
"description": self.description,
"tags": self.tags,
"status": self.status.value,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
"metadata": self.metadata,
}
Integration with Service Layer
While the Bookmark class contains a name-mangled __validate_url method, the actual validation logic is decoupled and resides in app/models/_validators.py. The BookmarkService in app/services/bookmark_service.py orchestrates the creation of bookmarks by combining these validators with the Bookmark model.
# Example of Bookmark creation in BookmarkService
def create_bookmark(self, data: Dict[str, Any]) -> Tuple[Optional[Bookmark], Optional[str]]:
# Validation is performed before instantiation
error = _validate_url(data.get("url", "")) or _validate_title(data.get("title", ""))
if error:
return None, error
# The model is instantiated only after validation passes
bookmark = Bookmark.from_dict(data)
self._repo.save_bookmark(bookmark)
return bookmark, None
This separation ensures that the Bookmark entity remains a clean data container while the service layer handles the business rules and persistence requirements.