Skip to content

chore(types): mypy strict for 5 modules (batch 1 of #34)#42

Merged
fede-kamel merged 1 commit into
mainfrom
feat/mypy-migrate-batch1
May 2, 2026
Merged

chore(types): mypy strict for 5 modules (batch 1 of #34)#42
fede-kamel merged 1 commit into
mainfrom
feat/mypy-migrate-batch1

Conversation

@fede-kamel
Copy link
Copy Markdown
Contributor

First batch of issue #34 — migrate mypy `ignore_errors` blanket to per-line.

Lifted modules (1066 LOC, all in strict mode now)

  • `locus.core.reducers` (445 LOC)
  • `locus.core.send` (322 LOC)
  • `locus.models.registry` (150 LOC)
  • `locus.tools.context` (67 LOC)
  • `locus.tools.registry` (82 LOC)

Real fixes (not type-ignore covers)

  • `core/reducers.py` — `AddMessages.call` now narrows the str sentinel up front, accepts `list[Message] | Message | str`, and raises on unrecognized strings (replaces the buggy `[update]` wrap that was producing `list[str]` for non-list inputs).
  • `core/protocols.py` + `models/base.py` — `ModelProtocol.stream()` declared as `def` (not `async def`) so the Protocol matches concrete async-generator implementations. `async def` with `yield` returns `AsyncIterator[X]` directly, not `Coroutine[..., AsyncIterator[X]]`; the Protocol now types correctly.
  • `tools/registry.py` — `iter` return type annotated as `Iterator[Tool]` (was missing).
  • `tools/context.py` — `confidence` property cast to `float` at the boundary so the `Any`-typed `self.state` doesn't poison the return.

Targeted `# type: ignore[code]` annotations (with justification comments)

  • `MaxValue` / `MinValue`: unbounded `T` can't prove `SupportsRichComparison`.
  • `get_args(annotation)[1]`: `typing.get_args` returns `tuple[Any, ...]`.
  • `is_send_list()`: custom TypeGuard-style narrowing mypy can't propagate.
  • `ToolRegistry.iter`: intentional Liskov override of Pydantic's field iterator.
  • `models/registry` lambdas + `_make_oci`: `cast("ModelProtocol", ...)` at the registration boundary because mypy's `Callable`-variance check doesn't propagate structural-Protocol narrowing on Pydantic model classes.

Test plan

  • `hatch run typecheck` — "Success: no issues found in 150 source files"
  • `hatch run test` — 3,246 unit tests pass + 1 skip
  • `pre-commit run --all-files` clean (ruff lint, ruff format, mypy, codespell, markdownlint, gitleaks)
  • `hatch -e docs run mkdocs build --strict` clean
  • DCO sign-off

What remains for issue #34

12 more module groups still under `ignore_errors`. Will land in subsequent batches:

```
locus.rag.* locus.memory.* locus.integrations.*
locus.streaming.* locus.loop.* locus.playbooks.*
locus.server.* locus.multiagent.graph locus.hooks.builtin.*
locus.reasoning.* locus.skills.* locus.a2a.*
```

…ry, tools.context, tools.registry (#34)

First batch of the mypy ignore_errors → per-line migration.

Five modules (1066 LOC total) lifted from the blanket ignore_errors
override and back into strict checking:

- locus.core.reducers   (445 LOC)
- locus.core.send       (322 LOC)
- locus.models.registry (150 LOC)
- locus.tools.context    (67 LOC)
- locus.tools.registry   (82 LOC)

Real fixes (not type-ignore covers):
- core/reducers.py — AddMessages.__call__ now narrows the str sentinel
  branch up front, accepts list[Message] | Message | str, and raises
  on unrecognized strings (replaces the buggy [update] wrap that was
  producing list[str] for non-list inputs).
- core/protocols.py — ModelProtocol.stream() declared as def (not
  async def) so the Protocol matches concrete async-generator
  implementations. async def with yield returns AsyncIterator[X]
  directly, not Coroutine[..., AsyncIterator[X]]; the Protocol now
  types correctly.
- models/base.py — same change to the duplicate ModelProtocol there.
- tools/registry.py — __iter__ return type annotated as Iterator[Tool]
  (was missing); collections.abc.Iterator import added.
- tools/context.py — confidence property cast to float at the
  boundary so the Any-typed self.state doesn't poison the return.

Targeted # type: ignore[code] annotations with justification:
- core/reducers.py — MaxValue/MinValue use unbounded T; mypy can't
  prove SupportsRichComparison on max()/min() so two
  type:ignore[call-overload,no-any-return] lines.
- core/reducers.py — get_args(annotation)[1] returns Any from
  typing.get_args so the callable-narrowed return needs
  type:ignore[no-any-return].
- core/send.py — is_send_list() is a custom TypeGuard-style check
  whose narrowing mypy can't propagate; one type:ignore[no-any-return].
- tools/registry.py — __iter__ override Liskov mismatch with
  Pydantic's BaseModel.__iter__ is intentional and predates strict
  typing; type:ignore[override] with justification.
- models/registry.py — three lambdas + the _make_oci function cast
  concrete Pydantic model classes to ModelProtocol at the
  registration boundary because mypy's Callable-variance check
  doesn't propagate structural-Protocol narrowing.

All 3,246 unit tests pass; pre-commit clean (ruff lint, ruff format,
mypy, codespell, markdownlint, gitleaks); mkdocs --strict clean.
EOF

Signed-off-by: Federico Kamelhar <federico.kamelhar@oracle.com>
@oracle-contributor-agreement oracle-contributor-agreement Bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label May 2, 2026
@fede-kamel fede-kamel merged commit ac41245 into main May 2, 2026
10 checks passed
@fede-kamel fede-kamel deleted the feat/mypy-migrate-batch1 branch May 2, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant