Skip to content

Commit 2e51b5f

Browse files
committed
fix: review comments by ai
1 parent cf0321a commit 2e51b5f

4 files changed

Lines changed: 54 additions & 30 deletions

File tree

models/conversation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from dataclasses import dataclass, field
55
from typing import Any
66

7-
_logger = logging.getLogger(__name__)
8-
97
from models.errors import SchemaError
108
from models.from_dict_validation import (
119
require_dict,
@@ -15,6 +13,8 @@
1513
require_type,
1614
)
1715

16+
_logger = logging.getLogger(__name__)
17+
1818

1919
@dataclass(frozen=True)
2020
class Composer:

models/raw_access.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,19 @@ def composer_headers(
163163

164164
if isinstance(data, Composer):
165165
return data.full_conversation_headers_only
166-
headers = optional_raw_list(
167-
data,
168-
"fullConversationHeadersOnly",
169-
model="Composer",
170-
entity_id=composer_id,
171-
)
172-
return headers if headers is not None else []
166+
if not isinstance(data, dict):
167+
return []
168+
value = data.get("fullConversationHeadersOnly")
169+
if value is None:
170+
return []
171+
if not isinstance(value, list):
172+
_logger.warning(
173+
"Schema drift in Composer %s: invalid type for fullConversationHeadersOnly (expected list, got %s)",
174+
composer_id,
175+
type(value).__name__,
176+
)
177+
return []
178+
return value
173179

174180

175181
def composer_newly_created_files(data: Any, composer_id: str) -> list[Any]:
@@ -206,11 +212,13 @@ def bubble_relevant_files(bubble: Any, bubble_id: str = "") -> list[Any]:
206212
if isinstance(bubble, Bubble):
207213
return bubble.relevant_files
208214
if isinstance(bubble, dict):
209-
value = bubble.get("relevantFiles")
210-
if value is None:
211-
return []
212-
if isinstance(value, list):
213-
return value
215+
files = optional_raw_list(
216+
bubble,
217+
"relevantFiles",
218+
model="Bubble",
219+
entity_id=bubble_id,
220+
)
221+
return files if files is not None else []
214222
return []
215223

216224

@@ -220,23 +228,27 @@ def bubble_attached_file_uris(bubble: Any, bubble_id: str = "") -> list[Any]:
220228
if isinstance(bubble, Bubble):
221229
return bubble.attached_file_code_chunks_uris
222230
if isinstance(bubble, dict):
223-
value = bubble.get("attachedFileCodeChunksUris")
224-
if value is None:
225-
return []
226-
if isinstance(value, list):
227-
return value
231+
uris = optional_raw_list(
232+
bubble,
233+
"attachedFileCodeChunksUris",
234+
model="Bubble",
235+
entity_id=bubble_id,
236+
)
237+
return uris if uris is not None else []
228238
return []
229239

230240

231241
def bubble_context(bubble: Any, bubble_id: str = "") -> dict[str, Any]:
232242
from models.conversation import Bubble
233243

234244
if isinstance(bubble, Bubble):
235-
return bubble.context or {}
245+
return bubble.context
236246
if isinstance(bubble, dict):
237-
ctx = bubble.get("context")
238-
if ctx is None:
239-
return {}
240-
if isinstance(ctx, dict):
241-
return ctx
247+
ctx = optional_raw_dict(
248+
bubble,
249+
"context",
250+
model="Bubble",
251+
entity_id=bubble_id,
252+
)
253+
return ctx if ctx is not None else {}
242254
return {}

tests/test_raw_accessors.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
class TestRawAccessorDriftLogging(unittest.TestCase):
2626
def test_composer_newly_created_files_empty_when_key_missing(self) -> None:
27-
bare = Composer.from_dict(GOOD_COMPOSER_RAW, composer_id="cid-2")
27+
raw = dict(GOOD_COMPOSER_RAW)
28+
raw.pop("newlyCreatedFiles", None)
29+
bare = Composer.from_dict(raw, composer_id="cid-2")
2830
with self.assertNoLogs("models.conversation", level="WARNING"):
2931
self.assertEqual(bare.newly_created_files, [])
3032

utils/text_extract.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
"""Text extraction helpers mirroring the bubble/richText parsing in the Node.js codebase."""
22

3+
from __future__ import annotations
4+
35
import json
46
import re
7+
from typing import Any, Protocol
8+
9+
10+
class HasBubbleRaw(Protocol):
11+
"""Bubble model or any object exposing a Cursor JSON ``raw`` dict."""
12+
13+
@property
14+
def raw(self) -> dict[str, Any]: ...
515

616

717
def extract_text_from_rich_text(children: list) -> str:
@@ -21,12 +31,12 @@ def extract_text_from_rich_text(children: list) -> str:
2131
return text
2232

2333

24-
def extract_text_from_bubble(bubble: dict | object) -> str:
34+
def extract_text_from_bubble(bubble: HasBubbleRaw | dict[str, Any]) -> str:
2535
"""Extract displayable text from a bubble object (text, richText, codeBlocks)."""
26-
if hasattr(bubble, "raw"):
27-
bubble = bubble.raw # type: ignore[union-attr]
28-
if not bubble or not isinstance(bubble, dict):
36+
raw: dict[str, Any] = bubble if isinstance(bubble, dict) else bubble.raw
37+
if not raw:
2938
return ""
39+
bubble = raw
3040

3141
text = ""
3242

0 commit comments

Comments
 (0)