Skip to content

Commit b1b04be

Browse files
docs: add Google-style docstrings to public api/services/utils/models surface
1 parent ae468e9 commit b1b04be

13 files changed

Lines changed: 240 additions & 0 deletions

api/composers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def _read_json_file(path: str) -> Any:
3030

3131
@bp.route("/api/composers")
3232
def list_composers() -> tuple[Response, int] | Response:
33+
"""List all composers across workspace databases (GET /api/composers).
34+
35+
Returns:
36+
JSON array of composer dicts sorted by ``lastUpdatedAt`` descending.
37+
500 on failure.
38+
"""
3339
try:
3440
workspace_path = resolve_workspace_path()
3541
composers = []
@@ -122,6 +128,15 @@ def list_composers() -> tuple[Response, int] | Response:
122128
return json_response({"error": "Failed to get composers"}, 500)
123129
@bp.route("/api/composers/<composer_id>")
124130
def get_composer(composer_id: str) -> tuple[Response, int] | Response:
131+
"""Fetch one composer by ID (GET /api/composers/<composer_id>).
132+
133+
Args:
134+
composer_id: Composer UUID.
135+
136+
Returns:
137+
Composer JSON from per-workspace storage or global fallback. 404 when not
138+
found or schema drift blocks serving; 500 on unexpected failure.
139+
"""
125140
try:
126141
workspace_path = resolve_workspace_path()
127142

api/config_api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424

2525
@bp.route("/api/detect-environment")
2626
def detect_environment() -> Response:
27+
"""Detect runtime OS, WSL, and SSH-remote context (GET /api/detect-environment).
28+
29+
Returns:
30+
JSON with ``os``, ``isWSL``, and ``isRemote``. Falls back to safe defaults
31+
on detection errors.
32+
"""
2733
try:
2834
is_wsl = False
2935
is_remote = bool(
@@ -98,6 +104,16 @@ def validate_path() -> tuple[Response, int] | Response:
98104
return json_response({"valid": False, "error": "Failed to validate path"}, 500)
99105
@bp.route("/api/set-workspace", methods=["POST"])
100106
def set_workspace() -> tuple[Response, int] | Response:
107+
"""Persist a validated workspace storage path (POST /api/set-workspace).
108+
109+
Body: ``{"path": "<workspaceStorage root>"}``. Path is canonicalized via
110+
:func:`utils.path_validation.validate_workspace_path` before storing the
111+
thread-safe module override.
112+
113+
Returns:
114+
``{"success": true, "path": "..."}`` on success. 400 for invalid path or
115+
body; 500 when override storage fails.
116+
"""
101117
# Reject non-dict JSON bodies (array / string / number / null). Without
102118
# this, get_json returns the value directly, the truthy fallback `or {}`
103119
# is bypassed, and `body.get("path", "")` raises AttributeError — which
@@ -127,6 +143,12 @@ def set_workspace() -> tuple[Response, int] | Response:
127143

128144
@bp.route("/api/get-username")
129145
def get_username() -> Response:
146+
"""Return the detected Windows/WSL username (GET /api/get-username).
147+
148+
Returns:
149+
JSON ``{"username": "..."}``. Falls back to ``YOUR_USERNAME`` when
150+
detection fails.
151+
"""
130152
try:
131153
username = "YOUR_USERNAME"
132154

api/logs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def _extract_chat_id_from_bubble_key(key: str) -> str | None:
3030

3131
@bp.route("/api/logs")
3232
def get_logs() -> tuple[Response, int] | Response:
33+
"""List chat logs from global and per-workspace storage (GET /api/logs).
34+
35+
Returns:
36+
JSON array of log summary objects (id, title, timestamp, etc.). 500 on
37+
unexpected failure.
38+
"""
3339
try:
3440
workspace_path = resolve_workspace_path()
3541
logs = []

api/pdf.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ def _safe_text(text: str) -> str:
4747

4848
@bp.route("/api/generate-pdf", methods=["POST"])
4949
def generate_pdf() -> tuple[Response, int] | Response:
50+
"""Render markdown chat content as a PDF download (POST /api/generate-pdf).
51+
52+
Body: ``{"markdown": "...", "title": "..."}``.
53+
54+
Returns:
55+
``application/pdf`` attachment on success. 400/500 JSON errors on failure.
56+
"""
5057
try:
5158
body = request.get_json(silent=True) or {}
5259
markdown_text = body.get("markdown", "")
@@ -55,10 +62,14 @@ def generate_pdf() -> tuple[Response, int] | Response:
5562
from fpdf import FPDF
5663

5764
class PDFDoc(FPDF):
65+
"""Minimal fpdf2 document with page numbers in the footer."""
66+
5867
def header(self) -> None:
68+
"""No running header (title is rendered in body)."""
5969
pass
6070

6171
def footer(self) -> None:
72+
"""Render centered page ``n/total`` at the bottom."""
6273
self.set_y(-15)
6374
self.set_font("Helvetica", "I", 8)
6475
self.cell(0, 10, f"Page {self.page_no()}/{{nb}}", align="C")

api/search.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525

2626
@bp.route("/api/search")
2727
def search() -> tuple[Response, int] | Response:
28+
"""Search chats, composers, and CLI sessions across Cursor storage.
29+
30+
Query params: ``q`` (required), ``type`` (``all`` | ``chat`` | ``composer``).
31+
32+
Returns:
33+
JSON ``{"results": [...]}`` with optional ``warnings``. 400 when ``q`` is
34+
empty; 500 on unexpected failure.
35+
"""
2836
try:
2937
query = request.args.get("q", "").strip()
3038
search_type = request.args.get("type", "all")

api/workspaces.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ def _request_nocache() -> bool:
5757

5858
@bp.route("/api/workspaces")
5959
def list_workspaces() -> tuple[Response, int] | Response:
60+
"""List workspace projects for the sidebar (GET /api/workspaces).
61+
62+
Honors ``?nocache=1`` to bypass the summary disk cache.
63+
64+
Returns:
65+
JSON with ``projects`` and optional ``warnings``. 500 on failure.
66+
"""
6067
try:
6168
workspace_path = resolve_workspace_path()
6269
rules = exclusion_rules()
@@ -76,6 +83,15 @@ def list_workspaces() -> tuple[Response, int] | Response:
7683

7784
@bp.route("/api/workspaces/<workspace_id>")
7885
def get_workspace(workspace_id: str) -> tuple[Response, int] | Response:
86+
"""Return metadata for one workspace, global bucket, or CLI project.
87+
88+
Args:
89+
workspace_id: Storage folder name, ``global``, or ``cli:<project_id>``.
90+
91+
Returns:
92+
Workspace JSON (id, name, path, folder, lastModified). 404 when not found;
93+
500 on unexpected failure.
94+
"""
7995
try:
8096
if workspace_id == "global":
8197
return json_response({
@@ -150,6 +166,17 @@ def get_workspace(workspace_id: str) -> tuple[Response, int] | Response:
150166

151167
@bp.route("/api/workspaces/<workspace_id>/tabs")
152168
def get_workspace_tabs(workspace_id: str) -> tuple[Response, int] | Response:
169+
"""List conversation tabs for a workspace (GET /api/workspaces/<id>/tabs).
170+
171+
Args:
172+
workspace_id: Storage folder name or ``cli:<project_id>``.
173+
174+
Query params: ``summary=1`` for lightweight tab headers only; ``nocache=1`` to
175+
bypass cache on summary requests.
176+
177+
Returns:
178+
Tabs payload from :func:`services.workspace_tabs` helpers. 500 on failure.
179+
"""
153180
if workspace_id.startswith("cli:"):
154181
try:
155182
return get_cli_workspace_tabs(workspace_id, exclusion_rules())
@@ -176,6 +203,16 @@ def get_workspace_tabs(workspace_id: str) -> tuple[Response, int] | Response:
176203

177204
@bp.route("/api/workspaces/<workspace_id>/tabs/<composer_id>")
178205
def get_workspace_tab(workspace_id: str, composer_id: str) -> tuple[Response, int] | Response:
206+
"""Lazy-load one conversation tab (GET /api/workspaces/<id>/tabs/<composer_id>).
207+
208+
Args:
209+
workspace_id: IDE workspace folder name (CLI workspaces return 400).
210+
composer_id: Composer UUID to load.
211+
212+
Returns:
213+
Single-tab JSON from :func:`services.workspace_tabs.assemble_single_tab`.
214+
400 for CLI workspaces; 500 on unexpected failure.
215+
"""
179216
if workspace_id.startswith("cli:"):
180217
return json_response({"error": "Per-tab lazy load is not supported for CLI workspaces"}, 400)
181218
try:

models/cli_session.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ class CliSessionMeta:
1717

1818
@classmethod
1919
def from_dict(cls, raw: dict[str, Any]) -> "CliSessionMeta":
20+
"""Parse CLI session ``meta`` JSON into a validated descriptor.
21+
22+
Args:
23+
raw: Decoded meta object from a CLI chat session.
24+
25+
Returns:
26+
Validated :class:`CliSessionMeta`.
27+
28+
Raises:
29+
SchemaError: When ``latestRootBlobId`` is missing or not a string.
30+
"""
2031
raw = require_dict(raw, model="CliSessionMeta", field="meta")
2132
latest = require_truthy(
2233
raw.get("latestRootBlobId"),

models/conversation.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ class Composer:
3030

3131
@classmethod
3232
def from_dict(cls, raw: dict[str, Any], *, composer_id: str) -> "Composer":
33+
"""Parse a global ``composerData`` row into a validated composer.
34+
35+
Args:
36+
raw: Decoded JSON object from cursorDiskKV.
37+
composer_id: Composer UUID from the storage key.
38+
39+
Returns:
40+
Validated :class:`Composer` with required headers and timestamps.
41+
42+
Raises:
43+
SchemaError: When required fields are missing or malformed.
44+
"""
3345
raw = require_dict(raw, model="Composer", field="composerData")
3446
require_non_empty_str(composer_id, model="Composer", field="composerId")
3547
require_key(raw, "fullConversationHeadersOnly", model="Composer")
@@ -164,6 +176,17 @@ class WorkspaceLocalComposer:
164176

165177
@classmethod
166178
def from_dict(cls, raw: dict[str, Any]) -> "WorkspaceLocalComposer":
179+
"""Parse one ``allComposers`` entry from per-workspace state.
180+
181+
Args:
182+
raw: Composer summary dict from ``composer.composerData``.
183+
184+
Returns:
185+
Validated local composer row.
186+
187+
Raises:
188+
SchemaError: When ``composerId`` is missing or invalid.
189+
"""
167190
raw = require_dict(raw, model="WorkspaceLocalComposer", field="composer")
168191
composer_id = require_non_empty_str_field(
169192
raw, "composerId", model="WorkspaceLocalComposer"
@@ -187,6 +210,18 @@ class Bubble:
187210

188211
@classmethod
189212
def from_dict(cls, raw: dict[str, Any], *, bubble_id: str) -> "Bubble":
213+
"""Parse one ``bubbleId:*`` KV value into a validated bubble.
214+
215+
Args:
216+
raw: Decoded bubble JSON (``bubble_id`` comes from the key, not value).
217+
bubble_id: Bubble UUID from the storage key suffix.
218+
219+
Returns:
220+
Validated :class:`Bubble`.
221+
222+
Raises:
223+
SchemaError: When the payload or *bubble_id* is invalid.
224+
"""
190225
raw = require_dict(raw, model="Bubble", field="bubble")
191226
require_non_empty_str(bubble_id, model="Bubble", field="bubbleId")
192227
return cls(bubble_id=bubble_id, raw=raw)

models/export.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ class ExportEntry:
3131

3232
@classmethod
3333
def from_dict(cls, raw: dict[str, Any]) -> "ExportEntry":
34+
"""Parse one manifest.jsonl row into a validated export entry.
35+
36+
Args:
37+
raw: Decoded JSON object for a single manifest line.
38+
39+
Returns:
40+
Validated :class:`ExportEntry`.
41+
42+
Raises:
43+
SchemaError: When ``log_id``, ``title``, or ``workspace`` are missing.
44+
"""
3445
raw = require_dict(raw, model="ExportEntry", field="entry")
3546
require_non_empty_str_fields(
3647
raw,

models/workspace.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ class Workspace:
1616

1717
@classmethod
1818
def from_dict(cls, raw: dict[str, Any], *, workspace_id: str) -> "Workspace":
19+
"""Parse ``workspace.json`` into a validated workspace descriptor.
20+
21+
Args:
22+
raw: Decoded workspace.json object.
23+
workspace_id: Workspace storage folder name.
24+
25+
Returns:
26+
Validated :class:`Workspace` (``folder`` may be ``None`` for CLI-only).
27+
28+
Raises:
29+
SchemaError: When required fields are missing or malformed.
30+
"""
1931
raw = require_dict(raw, model="Workspace", field="workspace.json")
2032
require_non_empty_str(workspace_id, model="Workspace", field="workspaceId")
2133
folder = require_optional_str(raw.get("folder"), model="Workspace", field="folder")

0 commit comments

Comments
 (0)