mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
4 KiB
4 KiB
Annotation Service API Design
Python service layer for annotation operations.
Module: paperforge/annotation/__init__.py
"""
PaperForge Annotation Layer.
Read-only Zotero SQLite parsing → annotations.db cache → CLI commands → Obsidian overlay.
"""
Module: paperforge/annotation/probe.py
class ZoteroAnnotationProbe:
"""Read-only probe into Zotero SQLite for annotations."""
def __init__(self, zotero_db_path: Path):
self.zotero_db_path = zotero_db_path
def copy_to_temp(self) -> Path:
"""Copy zotero.sqlite to temp file. Returns temp path."""
def open_readonly(self, db_path: Path) -> sqlite3.Connection:
"""Open SQLite in read-only mode (URI mode=ro)."""
def fetch_annotations(self, conn, paper_key: str = "", limit: int = 100) -> list[dict]:
"""Fetch annotations with parent attachment info."""
def fetch_tags_for_annotations(self, conn, item_ids: list[int]) -> dict[int, list[str]]:
"""Fetch tags for annotation items."""
def probe(self, limit: int = 20) -> dict:
"""Full probe: schema check → fetch → return unified JSON."""
Module: paperforge/annotation/db.py
class AnnotationDB:
"""Management of annotations.db."""
def __init__(self, vault: Path):
self.vault = vault
self.db_path = vault / "System" / "PaperForge" / "indexes" / "annotations.db"
def get_connection(self, read_only: bool = False) -> sqlite3.Connection:
"""Open connection with WAL mode."""
def ensure_schema(self):
"""Create tables if not exist, migrate if needed."""
def integrity_check(self) -> bool:
"""PRAGMA integrity_check."""
def get_stats(self) -> dict:
"""Return count by type, source, sync_state."""
Module: paperforge/annotation/import_annotations.py
def import_from_zotero(
vault: Path,
zotero_db_path: Path | None = None,
paper_key: str = "",
dry_run: bool = False,
) -> dict:
"""Import annotations from Zotero SQLite into annotations.db.
Steps:
1. Detect/copy Zotero SQLite
2. Open read-only connection
3. Fetch annotations (optionally filtered by paper_key)
4. For each annotation:
a. Check if already imported (by zotero_key + source_version)
b. If new: INSERT with sync_state='zotero_synced', is_readonly=1
c. If updated: UPDATE with new position/comment/text, bump version
d. If deleted: soft delete (set deleted_at)
5. Commit
6. Return import stats
"""
Module: paperforge/annotation/export.py
def export_json(ann_conn, paper_key: str) -> str:
"""Export annotations for a paper as pretty JSON."""
def export_markdown(ann_conn, paper_key: str, paper_title: str) -> str:
"""Export annotations as formatted Markdown."""
CLI: paperforge/commands/annotation.py
def run(args) -> int:
"""Dispatch annotation subcommands."""
if args.annotation_action == "import":
return _cmd_import(args)
elif args.annotation_action == "list":
return _cmd_list(args)
elif args.annotation_action == "create":
return _cmd_create(args)
elif args.annotation_action == "patch":
return _cmd_patch(args)
elif args.annotation_action == "delete":
return _cmd_delete(args)
elif args.annotation_action == "export":
return _cmd_export(args)
elif args.annotation_action == "status":
return _cmd_status(args)
JSON Output Contract
All commands support --json flag. Output envelope:
{
"ok": true,
"command": "annotation <subcommand>",
"data": {
"paper_id": "ABCD1234",
"annotations": [...],
"count": 15
},
"meta": {
"db_path": "/path/to/annotations.db",
"elapsed_ms": 123
}
}
Error envelope:
{
"ok": false,
"command": "annotation <subcommand>",
"error": {
"code": "ZOTERO_DB_NOT_FOUND",
"message": "Zotero SQLite not found at /path/to/zotero.sqlite"
}
}