Skip to content

Commit 717a2d2

Browse files
fix(attachments): address review feedback + fix pyrefly CI failure
- chat.py:2107 — type the rehydrate-attachment callable so the list comprehension narrows to list[Attachment]. Unblocks CI. - _coerce_attachments: replace `or` fallbacks with `is not None` (Port Rule #1 truthiness trap) - google_chat rehydrate_attachment: preserve resolved URL when reconstructing, drop truthiness fallback on meta["url"] - Harden telegram and whatsapp rehydrate tests to execute the async callback and verify download-method wiring (AsyncMock). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a4e9235 commit 717a2d2

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

src/chat_sdk/adapters/google_chat/adapter.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,12 +2684,13 @@ def rehydrate_attachment(self, attachment: Attachment) -> Attachment:
26842684
"""
26852685
meta = attachment.fetch_metadata or {}
26862686
resource_name = meta.get("resourceName")
2687-
url = meta.get("url") or attachment.url
2688-
if not (resource_name or url):
2687+
meta_url = meta.get("url")
2688+
url = meta_url if meta_url is not None else attachment.url
2689+
if resource_name is None and url is None:
26892690
return attachment
26902691
return Attachment(
26912692
type=attachment.type,
2692-
url=attachment.url,
2693+
url=url,
26932694
name=attachment.name,
26942695
mime_type=attachment.mime_type,
26952696
size=attachment.size,

src/chat_sdk/chat.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,9 +2102,11 @@ def _rehydrate_message(self, raw: Any, adapter: Adapter | None = None) -> Messag
21022102
# Matches TS: `adapter?.rehydrateAttachment?.(att)` — duck-typed so
21032103
# adapters that do not declare the hook (e.g. bare MockAdapter) are
21042104
# treated as no-ops and the attachment is left untouched.
2105-
rehydrate = getattr(adapter, "rehydrate_attachment", None) if adapter else None
2106-
if callable(rehydrate) and msg.attachments:
2107-
msg.attachments = [att if att.fetch_data is not None else rehydrate(att) for att in msg.attachments]
2105+
rehydrate_fn: Callable[[Attachment], Attachment] | None = (
2106+
getattr(adapter, "rehydrate_attachment", None) if adapter else None
2107+
)
2108+
if rehydrate_fn is not None and msg.attachments:
2109+
msg.attachments = [att if att.fetch_data is not None else rehydrate_fn(att) for att in msg.attachments]
21082110

21092111
return msg
21102112

@@ -2164,16 +2166,22 @@ def _coerce_attachments(raw: Any) -> list[Attachment]:
21642166
if isinstance(att, Attachment):
21652167
out.append(att)
21662168
elif isinstance(att, dict):
2169+
mime_type = att.get("mimeType")
2170+
if mime_type is None:
2171+
mime_type = att.get("mime_type")
2172+
fetch_metadata = att.get("fetchMetadata")
2173+
if fetch_metadata is None:
2174+
fetch_metadata = att.get("fetch_metadata")
21672175
out.append(
21682176
Attachment(
21692177
type=att.get("type", "file"),
21702178
url=att.get("url"),
21712179
name=att.get("name"),
2172-
mime_type=att.get("mimeType") or att.get("mime_type"),
2180+
mime_type=mime_type,
21732181
size=att.get("size"),
21742182
width=att.get("width"),
21752183
height=att.get("height"),
2176-
fetch_metadata=att.get("fetchMetadata") or att.get("fetch_metadata"),
2184+
fetch_metadata=fetch_metadata,
21772185
)
21782186
)
21792187
return out

tests/test_telegram_adapter.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,16 +378,24 @@ def test_adapter_class_properties(self):
378378
class TestRehydrateAttachment:
379379
"""Port of the Telegram adapter's ``rehydrateAttachment`` behavior."""
380380

381-
def test_rehydrate_attachment_uses_file_id_from_fetch_metadata(self):
381+
@pytest.mark.asyncio
382+
async def test_rehydrate_attachment_uses_file_id_from_fetch_metadata(self):
383+
from unittest.mock import AsyncMock
384+
382385
from chat_sdk.types import Attachment
383386

384387
adapter = _make_adapter()
388+
adapter.download_file = AsyncMock(return_value=b"ok")
385389
attachment = Attachment(
386390
type="image",
387391
fetch_metadata={"fileId": "AgACAgIAAxkB"},
388392
)
389393
rehydrated = adapter.rehydrate_attachment(attachment)
390394
assert rehydrated.fetch_data is not None
395+
# Execute the rehydrated closure to verify it wires file_id correctly.
396+
data = await rehydrated.fetch_data()
397+
assert data == b"ok"
398+
adapter.download_file.assert_awaited_once_with("AgACAgIAAxkB")
391399
# fetch_metadata is preserved so the attachment stays serializable/rehydratable again.
392400
assert rehydrated.fetch_metadata == {"fileId": "AgACAgIAAxkB"}
393401

tests/test_whatsapp_webhook.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,16 +697,24 @@ async def test_delete_message_raises(self):
697697
class TestRehydrateAttachment:
698698
"""Cover ``WhatsAppAdapter.rehydrate_attachment``."""
699699

700-
def test_rehydrates_fetch_data_from_media_id(self):
700+
@pytest.mark.asyncio
701+
async def test_rehydrates_fetch_data_from_media_id(self):
702+
from unittest.mock import AsyncMock
703+
701704
from chat_sdk.types import Attachment
702705

703706
adapter = _make_adapter()
707+
adapter.download_media = AsyncMock(return_value=b"ok")
704708
attachment = Attachment(
705709
type="image",
706710
fetch_metadata={"mediaId": "media-42"},
707711
)
708712
rehydrated = adapter.rehydrate_attachment(attachment)
709713
assert rehydrated.fetch_data is not None
714+
# Execute the rehydrated closure to verify it wires media_id correctly.
715+
data = await rehydrated.fetch_data()
716+
assert data == b"ok"
717+
adapter.download_media.assert_awaited_once_with("media-42")
710718
assert rehydrated.fetch_metadata == {"mediaId": "media-42"}
711719

712720
def test_returns_unchanged_when_no_media_id(self):

0 commit comments

Comments
 (0)