Skip to main content

Smart vs. Manual Collections

In etchblok-test-api, collections provide a way to group bookmarks either through explicit user action or automated rules. This distinction is managed by the CollectionType enumeration and implemented within the Collection class.

Collection Types

The CollectionType enum in app/models/collection.py defines the two operational modes for any collection:

class CollectionType(Enum):
"""The kind of collection."""

MANUAL = "manual"
SMART = "smart"
  • Manual Collections: Act as standard folders or lists where the user explicitly adds or removes bookmarks.
  • Smart Collections: Act as dynamic views that conceptually include bookmarks based on a defined filter_rule.

Manual Collections

Manual collections rely on the bookmark_ids attribute, which is an ordered list of strings. Membership is managed via the add_bookmark and remove_bookmark methods.

When adding a bookmark, etchblok-test-api ensures that the collection is not a smart collection and that the bookmark is not already present:

def add_bookmark(self, bookmark_id: str) -> bool:
"""Add a bookmark to a manual collection."""
if self.is_smart or bookmark_id in self.bookmark_ids:
return False
self.bookmark_ids.append(bookmark_id)
return True

Manual collections also support explicit reordering of their contents via the reorder method, provided the new list contains the exact same set of IDs currently in the collection.

Smart Collections and Filter Rules

Smart collections are defined by a filter_rule string. This rule is used to automatically determine which bookmarks belong to the collection based on their content.

Filtering Logic

The filtering logic is implemented in the internal _apply_filter method. It performs a case-insensitive keyword match against both the title and description of a bookmark:

def _apply_filter(self, bookmarks: list) -> List[str]:
"""Evaluate the filter_rule against a list of bookmarks."""
if not self.filter_rule:
return []
keyword = self.filter_rule.lower()
return [b.id for b in bookmarks if keyword in b.title.lower() or keyword in b.description.lower()]

Design Constraints

A key design choice in etchblok-test-api is the strict separation of membership logic:

  1. No Manual Overrides: The add_bookmark method explicitly rejects additions to smart collections. If is_smart is true, the method returns False immediately.
  2. Internal Automation: While the Collection model defines how to filter bookmarks, the _apply_filter method is currently marked as internal. In the current version of the BookmarkService, smart collections are created and persisted, but the service layer does not yet automatically invoke _apply_filter to populate bookmark_ids dynamically during retrieval.

API Integration

Collections are created via the BookmarkService.create_collection method, which accepts a dictionary typically sourced from a JSON request body in app/routes/collections.py.

Creating a Collection

To create a collection, the API expects a name and an optional type. If the type is smart, a filter_rule should also be provided.

# Example of creating a smart collection via the service layer
data = {
"name": "Python Resources",
"type": "smart",
"filter_rule": "python"
}
collection, error = bookmark_service.create_collection(data)

Adding Bookmarks

For manual collections, the BookmarkService.add_to_collection method facilitates membership:

def add_to_collection(self, collection_id: str, bookmark_id: str) -> bool:
"""Add a bookmark to a collection."""
collection = self._repo.get_collection(collection_id)
if not collection:
return False
if not collection.add_bookmark(bookmark_id):
return False
self._repo.save_collection(collection)
return True

This service method respects the underlying model's restriction: if a developer attempts to call add_to_collection on a smart collection, the collection.add_bookmark call will return False, and the operation will fail, maintaining the integrity of the rule-based system.