Split adapter.py monolith into focused modules; restore Ruff complexity defaults (issue #12)
adapter.py was a 3,493-line monolith. Split it into focused modules with clear separation of responsibilities, bringing it down to ~857 lines: - Module-level helpers: hooks, classify, pickers, commands, setup, defaults, secrets - Frame-handler mixins: inbound, tool_frames, push_frames, media_frames, picker_frames, channel_frames, query_frames - mixin_base: IrisAdapterBase (declaration-only base for shared attrs) - adapter.py now holds only IrisAdapter (the composition of the 7 mixins + BasePlatformAdapter), register(), and test-facing re-exports The mixins come before BasePlatformAdapter in the MRO so their methods override the base; super() calls (e.g. send_image) still resolve to BasePlatformAdapter. No circular imports; dispatch.py and http_server.py (instance-method callers) are unaffected. Ruff complexity ceilings (PLR0911/0912/0913/0915) restored to Ruff's built-in defaults (12/50/6/5) instead of "just above the current maxima", which ratchets the bar down as code grows. The existing genuinely-complex functions (frame builders mirroring the wire schema, the QR matrix builder, the dispatch table) carry an explicit `# noqa: PLR09xx` marking them as reviewed, frozen exceptions; new code is held to the default ceilings. All 125 tests green (94 test_android + 31 test_android_http); no new ruff errors introduced.
This commit is contained in:
1 parent
7faaf2aa1c
commit
b8e756c3dd
26 files changed
+3101
-2775
No files matched your search
@@ -0,0 +1,127 @@
|
||||
"""M4: outbound media (agent -> app): ``media.offer`` emission.
|
||||
|
||||
Mixin for ``adapter.IrisAdapter``. The gateway's dispatch partition
|
||||
(gateway/run.py) extracts MEDIA: tags / image URLs from the final response,
|
||||
filters them through ``filter_media_delivery_paths``, then calls the
|
||||
``send_*`` overrides with local file paths. We re-validate each path
|
||||
(defense in depth), register it in the media registry, mint a ``media_id``,
|
||||
and emit ``media.offer``; the app fetches the bytes via ``media.pull``.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from gateway.platforms.base import SendResult, validate_media_delivery_path
|
||||
|
||||
from . import media as media_bridge
|
||||
from . import protocol
|
||||
from .classify import _thread_id_from_metadata
|
||||
from .mixin_base import IrisAdapterBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MediaHandlers(IrisAdapterBase):
|
||||
"""Outbound media (see module docstring)."""
|
||||
|
||||
async def _offer_media(
|
||||
self,
|
||||
chat_id: str,
|
||||
path: str,
|
||||
kind: str,
|
||||
filename: str | None,
|
||||
metadata: dict[str, Any] | None,
|
||||
) -> SendResult:
|
||||
safe = validate_media_delivery_path(path)
|
||||
if safe is None:
|
||||
logger.warning("iris: media path failed delivery validation: %s", path)
|
||||
return SendResult(success=False, error="iris: media path not deliverable")
|
||||
try:
|
||||
size = os.path.getsize(safe)
|
||||
except OSError as e:
|
||||
logger.warning("iris: media file unreadable %s: %s", safe, e)
|
||||
return SendResult(success=False, error="iris: media file unreadable")
|
||||
entry = self._media.register_outbound(
|
||||
safe, kind, media_bridge.mime_for_path(safe), filename or os.path.basename(safe), size
|
||||
)
|
||||
thread_id = _thread_id_from_metadata(metadata)
|
||||
frame = protocol.media_offer(
|
||||
entry.media_id,
|
||||
entry.kind,
|
||||
entry.mime,
|
||||
entry.size,
|
||||
entry.filename,
|
||||
chat_id=chat_id,
|
||||
thread_id=thread_id,
|
||||
message_id=self._last_message_id.get(chat_id),
|
||||
)
|
||||
await self._broadcast_or_log(chat_id, frame)
|
||||
return SendResult(success=True, message_id=entry.media_id)
|
||||
|
||||
async def send_image(
|
||||
self,
|
||||
chat_id: str,
|
||||
image_url: str,
|
||||
caption: str | None = None,
|
||||
reply_to: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> SendResult:
|
||||
"""Send an image (M4: local files offered over WS; remote URLs fall
|
||||
back to the base text rendering)."""
|
||||
if image_url.startswith("file://"):
|
||||
from urllib.parse import unquote
|
||||
|
||||
return await self._offer_media(chat_id, unquote(image_url[7:]), "image", None, metadata)
|
||||
return await super().send_image(
|
||||
chat_id, image_url, caption=caption, reply_to=reply_to, metadata=metadata
|
||||
)
|
||||
|
||||
async def send_image_file(
|
||||
self,
|
||||
chat_id: str,
|
||||
image_path: str,
|
||||
caption: str | None = None,
|
||||
reply_to: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> SendResult:
|
||||
"""Send a local image file (M4)."""
|
||||
return await self._offer_media(chat_id, image_path, "image", None, metadata)
|
||||
|
||||
async def send_video(
|
||||
self,
|
||||
chat_id: str,
|
||||
video_path: str,
|
||||
caption: str | None = None,
|
||||
reply_to: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> SendResult:
|
||||
"""Send a video (M4)."""
|
||||
return await self._offer_media(chat_id, video_path, "video", None, metadata)
|
||||
|
||||
async def send_voice(
|
||||
self,
|
||||
chat_id: str,
|
||||
audio_path: str,
|
||||
caption: str | None = None,
|
||||
reply_to: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> SendResult:
|
||||
"""Send a voice note / audio file (M4)."""
|
||||
return await self._offer_media(chat_id, audio_path, "voice", None, metadata)
|
||||
|
||||
async def send_document( # noqa: PLR0913
|
||||
self,
|
||||
chat_id: str,
|
||||
file_path: str,
|
||||
caption: str | None = None,
|
||||
file_name: str | None = None,
|
||||
reply_to: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> SendResult:
|
||||
"""Send a document (M4)."""
|
||||
return await self._offer_media(chat_id, file_path, "document", file_name, metadata)
|
||||
Reference in new issue
Block a user