Search and Pagination
To retrieve bookmarks in this system, you can either fetch a paginated list with optional status filters or perform a full-text search across titles and descriptions. Both operations are managed by the BookmarkService.
Paginated Listing with Filters
To retrieve a specific page of bookmarks, use the list_bookmarks method. This method returns a tuple containing the list of bookmarks for the requested page and the total count of matching items.
from app.services.bookmark_service import BookmarkService
service = BookmarkService()
# Retrieve the first page of active bookmarks
bookmarks, total = service.list_bookmarks(
page=1,
per_page=25,
status="active"
)
for b in bookmarks:
print(f"{b.title}: {b.url}")
print(f"Total matching bookmarks: {total}")
Key Parameters
page: The 1-based page number (default is1).per_page: The number of items to return per page (default is25).status: An optional filter string. Valid values are defined inBookmarkStatus:"active","archived", or"trashed".
Implementation in Routes
In the API layer (app/routes/bookmarks.py), this is typically exposed via query parameters:
@bookmarks_bp.route("/", methods=["GET"])
def list_bookmarks():
page = request.args.get("page", 1, type=int)
per_page = request.args.get("per_page", 25, type=int)
status = request.args.get("status", None)
bookmarks, total = _service.list_bookmarks(page=page, per_page=per_page, status=status)
return jsonify({
"bookmarks": [b.to_dict() for b in bookmarks],
"total": total
})
Full-Text Search
The search method provides a full-text search capability across bookmark titles and descriptions. It uses an internal SearchIndex that ranks results based on relevance.
from app.services.bookmark_service import BookmarkService
service = BookmarkService()
# Search for bookmarks containing "python" and "tutorial"
results = service.search(query="python tutorial", limit=10)
for bookmark in results:
print(f"Found: {bookmark.title}")
Search Logic
The underlying SearchIndex (found in app/services/search_service.py) implements the following logic:
- Tokenization: The query is split into lowercase tokens, and common stop words (e.g., "the", "and", "for") are removed.
- AND-based Matching: A bookmark must contain all tokens from the query to be included in the results.
- Ranking: Results are ranked by the frequency of the query tokens appearing in the bookmark's title and description.
Implementation in Routes
The search endpoint in app/routes/bookmarks.py demonstrates how to handle search requests:
@bookmarks_bp.route("/search", methods=["GET"])
def search_bookmarks():
query = request.args.get("q", "")
limit = request.args.get("limit", 20, type=int)
results = _service.search(query, limit=limit)
return jsonify({
"results": [b.to_dict() for b in results],
"count": len(results)
})
Troubleshooting and Constraints
Search Index Persistence
The SearchIndex is an in-memory inverted index. It is rebuilt from the BookmarkRepository every time the BookmarkService (a singleton) is initialized. While it updates incrementally when you call create_bookmark or update_bookmark, any data not persisted in the repository will be lost on restart.
Status Filter Fail-soft
If an invalid status string is passed to list_bookmarks, the BookmarkRepository catches the ValueError and returns all bookmarks regardless of status, rather than raising an error.
Search Limits
The SearchIndex has a hard-coded MAX_SEARCH_RESULTS of 100 (defined in app/services/search_service.py). Even if you request a higher limit in the search() method, the internal index will not return more than this maximum.
Pagination Indexing
Pagination is 1-based. Passing page=0 will result in an empty list or incorrect slicing because the internal calculation used in BookmarkRepository.list_bookmarks is start = (page - 1) * per_page.