# Coding Conventions **Analysis Date:** 2026-04-01 ## Linting and Formatting **Tool:** ruff (configured in `pyproject.toml`) **Settings:** - Line length: 100 characters - Target: Python 3.11 - Active rule sets: `E` (pycodestyle errors), `F` (pyflakes), `I` (isort), `UP` (pyupgrade), `B` (bugbear) **Type checking:** mypy (available as dev dependency; not enforced in CI at this time) Run linting: ```bash ruff check . ruff format . ``` ## File Naming - Module files: `snake_case.py` (e.g., `room_router.py`, `test_dispatcher.py`) - Each module starts with a comment declaring its path: `# core/handler.py` - Test files: `test_.py` (e.g., `test_store.py`, `test_converter.py`) - No index/barrel files except `__init__.py` for package registration ## Class Naming - `PascalCase` for all classes (e.g., `EventDispatcher`, `MockPlatformClient`, `MatrixBot`) - Protocol/interface classes named after the capability: `StateStore`, `PlatformClient`, `WebhookReceiver` - Manager classes suffixed with `Manager`: `ChatManager`, `AuthManager`, `SettingsManager` - Dataclasses follow the same `PascalCase` rule: `IncomingMessage`, `OutgoingUI`, `MatrixRuntime` ## Function and Method Naming - `snake_case` for all functions and methods - Private helpers prefixed with single underscore: `_to_dict`, `_from_dict`, `_routing_key`, `_latency` - Handler functions named `handle_`: `handle_start`, `handle_message`, `handle_new_chat` - Builder functions named `build_`: `build_runtime`, `build_event_dispatcher`, `build_skills_text` - Converter functions named `from_`: `from_room_event`, `from_command`, `from_reaction` - Predicate functions named `is_`: `is_authenticated`, `is_new` ## Variable Naming - `snake_case` for all variables and parameters - Internal state attributes prefixed with `_`: `self._store`, `self._platform`, `self._handlers` - Store key prefixes are module-level constants in `UPPER_SNAKE_CASE`: ```python ROOM_META_PREFIX = "matrix_room:" USER_META_PREFIX = "matrix_user:" ``` - Constants for reaction strings are module-level: `CONFIRM_REACTION = "👍"`, `PLATFORM = "matrix"` ## Type Annotations All files use `from __future__ import annotations` at the top for deferred evaluation. **Annotation style:** - Use built-in generics (`list[str]`, `dict[str, Any]`) — not `List`, `Dict` from `typing` - Union types written with `|`: `str | None`, `IncomingCallback | None` - Type aliases at module level: `IncomingEvent = IncomingMessage | IncomingCommand | IncomingCallback` - Callable types use `typing.Callable` and `typing.Awaitable`: ```python HandlerFn = Callable[..., Awaitable[list[OutgoingEvent]]] ``` - Handler functions use loose `list` return type without generics (consistent across `core/handlers/`) - Protocol classes use `...` as body for abstract methods: ```python async def get(self, key: str) -> dict | None: ... ``` **Pydantic vs dataclasses:** - `core/protocol.py` — plain `@dataclass` with `field(default_factory=...)` for mutable defaults - `sdk/interface.py` — Pydantic `BaseModel` for all SDK-facing models (`User`, `MessageResponse`, `UserSettings`) - Choose `@dataclass` for internal protocol structs, `BaseModel` for SDK boundary models ## Import Organization Order (enforced by ruff `I` rules): 1. `from __future__ import annotations` 2. Standard library imports (grouped) 3. Third-party imports (grouped) 4. Local imports from project packages (grouped) Example from `adapter/matrix/bot.py`: ```python from __future__ import annotations import asyncio import os from dataclasses import dataclass from pathlib import Path import structlog from nio import AsyncClient, ... from dotenv import load_dotenv from adapter.matrix.converter import from_reaction, from_room_event from core.auth import AuthManager from core.protocol import OutgoingEvent, ... from sdk.mock import MockPlatformClient ``` No relative imports; all imports use absolute package paths from the project root. ## Async Patterns All I/O methods are `async def`. There are no sync wrappers around async code. **Handler signature pattern** (used uniformly across `core/handlers/`): ```python async def handle_(event: IncomingEvent, auth_mgr, platform, chat_mgr, settings_mgr) -> list: ``` Note: manager parameters are untyped in handler signatures (accepted as `**kwargs` at call site in `EventDispatcher.dispatch`). **Awaiting store calls:** ```python stored = await self._store.get(f"auth:{user_id}") await self._store.set(f"auth:{user_id}", _to_dict(flow)) ``` **SQLiteStore uses sync sqlite3** inside `async def` methods — blocking I/O is not off-loaded to a thread executor. This is a known limitation (see CONCERNS.md). **Mock latency simulation:** ```python await self._latency(200, 600) # min_ms, max_ms ``` ## Logging **Library:** `structlog` **Pattern:** ```python import structlog logger = structlog.get_logger(__name__) logger.info("Chat created", chat_id=chat_id, user_id=user_id) logger.warning("No handler registered", event_type=event_type.__name__, key=key) ``` - Always pass structured keyword arguments — never use f-strings in log calls - Logger created at module level with `structlog.get_logger(__name__)` ## Error Handling - Raise `ValueError` for invalid domain state (e.g., chat not found in `ChatManager.rename`) - `sdk/interface.py` defines `PlatformError(Exception)` with a `code` field for SDK-level errors - Handler functions never raise — they return `[]` or a fallback `OutgoingMessage` - No `try/except` blocks in core handlers; errors from the platform are expected to propagate ## Comments - Module-level comment declaring file path at top: `# core/handler.py` - Docstrings for classes with non-obvious behavior: ```python class MockPlatformClient: """ Заглушка SDK платформы Lambda. ... """ ``` - Inline comments for non-obvious blocks: ```python # Scan by chat_id suffix when user_id unknown (slower) ``` - Comments in Russian are normal and acceptable throughout the codebase ## Serialization Pattern Dataclasses are serialized/deserialized via private module-level functions, not class methods: ```python def _to_dict(ctx: ChatContext) -> dict: return { "chat_id": ctx.chat_id, ... } def _from_dict(d: dict) -> ChatContext: return ChatContext(chat_id=d["chat_id"], ...) ``` This pattern is used in `core/auth.py` and `core/chat.py`. Follow this pattern for any new manager that persists to `StateStore`. ## Module Design - No barrel `__init__.py` exports except `core/handlers/__init__.py` which exposes `register_all` - Manager classes take `(platform, store)` as constructor args; `platform` is often stored as `object` or not stored at all if unused - `@dataclass` is preferred for plain data containers, not NamedTuple or TypedDict - Store key namespacing follows `::` pattern: `"chat:u1:C1"`, `"auth:u1"`, `"matrix_room:!r:m.org"` --- *Convention analysis: 2026-04-01*