fix(types): P2 — wire _MixinBase + col() across sqlmodel_repo; suppress pydantic/SQLModel column typing false positives

- Add _MixinBase abstract class to _helpers.py: declares _session(),
  _deserialize_attacker(), _assert_pending(), _check_and_bump_version(),
  and list_running_topology_deckies() so mypy can see cross-mixin contracts
- Add _require(val, msg) helper for narrowing T | None → T
- Inherit _MixinBase in all 26 leaf mixin classes
- Wrap SQLAlchemy column method calls (.is_(), .like(), .notin_(), .in_(),
  .contains()) with col() from sqlmodel — fixes attr-defined false positives
  caused by pydantic plugin typing class-level fields as Python value types
- Wrap select(Model.field) with select(col(Model.field)) for column projections
- Add pyproject.toml [[tool.mypy.overrides]] to disable arg-type in
  sqlmodel_repo.*: pydantic plugin resolves .where(Model.field == v) as
  where(bool), a false positive; call-arg still catches real argument errors
- Remove 9 stale # type: ignore comments (logging, helpers, credentials)
- Fix telemetry.py traced() overload no-redef + misc
- Fix logs.py datetime/str operator and nullable PK comparison with col()
- sqlmodel_repo/ now has 0 mypy errors
This commit is contained in:
2026-05-01 00:49:18 -04:00
parent d777a1c4e0
commit 614780f144
30 changed files with 221 additions and 100 deletions

View File

@@ -12,14 +12,60 @@ from __future__ import annotations
import asyncio
import json
from abc import abstractmethod
from contextlib import asynccontextmanager
from typing import Any
from typing import Any, Optional, TypeVar
import orjson
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from decnet.logging import get_logger
T = TypeVar("T")
def _require(val: T | None, msg: str) -> T:
"""Narrow ``X | None`` to ``X``, raising ``ValueError`` if None."""
if val is None:
raise ValueError(msg)
return val
class _MixinBase:
"""Typing base for all repo mixins.
Declares the contract that ``SQLModelRepository`` satisfies at runtime
via MRO composition. Without this, mypy checks each mixin in isolation
and cannot see ``_session`` or cross-mixin helpers.
"""
@abstractmethod
def _session(self):
"""Return a cancellation-safe async session context manager."""
raise NotImplementedError
@staticmethod
def _deserialize_attacker(d: dict[str, Any]) -> dict[str, Any]:
"""Stub — concrete impl on AttackersCoreMixin via MRO."""
return d
async def _assert_pending(self, session: AsyncSession, topology_id: str) -> None:
"""Stub — concrete impl on TopologyCoreMixin via MRO."""
raise NotImplementedError
async def _check_and_bump_version(
self,
session: AsyncSession,
topology_id: str,
expected_version: Optional[int],
) -> None:
"""Stub — concrete impl on TopologyCoreMixin via MRO."""
raise NotImplementedError
async def list_running_topology_deckies(self) -> list[dict[str, Any]]:
"""Stub — concrete impl on TopologyDeckiesMixin via MRO."""
raise NotImplementedError
_log = get_logger("db.pool")
# Hold strong refs to in-flight cleanup tasks so they aren't GC'd mid-run.
@@ -66,7 +112,7 @@ def _detach_close(session: AsyncSession) -> None:
task = loop.create_task(_cleanup())
_cleanup_tasks.add(task)
# Consume any exception to silence "Task exception was never retrieved".
task.add_done_callback(lambda t: (_cleanup_tasks.discard(t), t.exception()))
task.add_done_callback(lambda t: (_cleanup_tasks.discard(t), t.exception())) # type: ignore[func-returns-value]
@asynccontextmanager