Skip to content

Enable strict mypy across eucare package - #4

Open
imagirom wants to merge 4 commits into
mainfrom
worktree-strict-typing
Open

Enable strict mypy across eucare package#4
imagirom wants to merge 4 commits into
mainfrom
worktree-strict-typing

Conversation

@imagirom

Copy link
Copy Markdown
Owner

Fixes imagirom/eucare#34.

Summary

  • Annotates ~40 modules in eucare/ and switches mypy to strict defaults (disallow_untyped_defs = true, check_untyped_defs = true, no_implicit_optional = true, ignore_missing_imports = true) — the transitional per-module override mechanism in pyproject.toml is gone.
  • HalfEdge / Vertex / Face now declare structural links (rev/nex/pre/orig/dest, any_outgoing, any_side) as non-Optional with a cast(...) in __init__, matching the data-structure invariant that they are linked before being read. This cascade removed hundreds of downstream errors at read sites.
  • Added @overload to HalfEdgeGraph.copy and EHEG_from_nx so callers get a precise return type instead of unpacking a union.
  • Added a single mypy override for tifffile (transitively pulled in by skimage; uses py3.12 type syntax that mypy parses under py3.10).
  • Added stub deps to the dev extra: types-PyYAML, types-networkx, types-tqdm, scipy-stubs.

Bugs fixed along the way

  • eucare.base.nearest_neighbor had a precedence bug — the conditional return mixed , and if/else so the return_index=False branch could not actually be taken. Rewritten as two distinct return statements.
  • eucare.example_graphs referenced example_graphs.curved_platonic where example_tilesets.curved_platonic was intended; would have raised at runtime if the fallback path was taken.

Test plan

  • mypy eucareSuccess: no issues found in 42 source files
  • pytest -q --ignore=docs392 passed, 1 skipped

🤖 Generated with Claude Code

imagirom and others added 4 commits May 26, 2026 18:43
All eucare modules now pass mypy with strict settings
(`disallow_untyped_defs = true`, `check_untyped_defs = true`,
`no_implicit_optional = true`), so the transitional per-module override
mechanism in pyproject.toml can go away.

Key changes:
- Type annotations added across ~40 modules. Foundational DCEL classes
  (HalfEdge / Vertex / Face) declare structural links (rev/nex/pre/orig/
  dest, any_outgoing, any_side) as non-Optional with a `cast(...)` in
  __init__, matching the invariant that they are always linked before
  being read — this cascade fixed hundreds of downstream errors.
- Added `@overload` to HalfEdgeGraph.copy and EHEG_from_nx so callers
  get precise return types instead of tuple-or-not unions.
- pyproject.toml: replace permissive transitional block with strict
  defaults, exclude tests/docs, plus a small follow_imports=skip
  override for tifffile (uses py3.12 `type` syntax that mypy parses
  under py3.10).
- Added types-PyYAML, types-networkx, types-tqdm, scipy-stubs to dev
  extras so CI installs the needed stubs.
- Fixed a latent bug in eucare.base.nearest_neighbor: the conditional
  return mixed operator precedence with the tuple so the
  `return_index=False` branch could not actually be taken; rewrote as
  two distinct return statements.
- Fixed a latent docs example bug in eucare.example_graphs (referenced
  example_graphs.curved_platonic where example_tilesets.curved_platonic
  was intended).

All 392 existing tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`tests/test_image_to_graph.py` and the intersecting-cylinders tests need
`mahotas`/`scikit-image`/`rdp`/`plotly` to import. Previously CI only
installed `--extra dev`, so collection failed at import time. Add the
necessary extras so the test suite can run.

Note: `--extra torch` is intentionally not added; the torch-dependent
tests in `test_alternating_flagstones.py` already gate themselves with
`pytest.mark.skipif(not HAS_TORCH, ...)`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cuts every cast() in the eucare package without loosening mypy strictness.
Three mechanical patterns did most of the work:

* HalfEdgeGraph.copy() and Conway operators now use TypeVar so the
  return type matches the input. ~17 cast() call sites drop away —
  callers get EuclideanPositionHEG / GeometricHEG back directly.
* HalfEdge / Vertex / Face structural links (rev, nex, pre, orig,
  dest, any_outgoing, any_side) are typed as non-Optional at class
  level, with __init__ doing `if x is not None: self.x = x`. The
  attribute literally doesn't exist until linked, so reads before
  set fail fast at AttributeError instead of silently lying.
* CairoRenderer's surface/dc move to the same "set on first use"
  pattern; width/height fallback rewritten as straight `if None`
  branches that mypy can narrow on its own.

Remaining one-offs:
* subdivide_face's `f: Face` was wrong — its body handles `f is None`
  for border edges; signature now reflects that.
* apply_mobius / MobiusTransform.__call__ overloaded on
  complex vs NDArray so scalar callers don't need to widen-and-cast.
* example_graphs.from_tiles overloaded on `add_positions` so callers
  get GeometricHEG (the True case) without casting.
* io.py drops the cast(Any, ...) around float() by switching to a
  precise isinstance(int|float|np.floating|np.integer) gate.

Removed two structurally-unnecessary `assert isinstance(...)`s inside
the Conway operator __call__s: they narrowed the TypeVar away and
forced callers to re-cast. The runtime invariant they enforced was
already implied by the input type.

Mypy: clean under the existing strict settings (no rollback).
Tests: 388 passed, 1 skipped, 4 deselected (slow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges the Conway shorthand methods from #28/PR#3. Conflict in
conway/operators.py was on the __call__ signature: kept our TypeVar G
return type, picked up the `set[Face] | Callable[[Face], bool] | None`
filter from main.

Typed the new conway/methods.py: `_shorthand` returns `Callable[...,
GeometricHEG]`; the inner `method` annotated as
`(self: GeometricHEG, *args: Any, **kwargs: Any) -> GeometricHEG`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant