Skip to content

config: add quax to the mypy pre-commit hook - #806

Merged
nstarman merged 1 commit into
mainfrom
claude/mypy-hook-add-quax
Aug 14, 2026
Merged

config: add quax to the mypy pre-commit hook#806
nstarman merged 1 commit into
mainfrom
claude/mypy-hook-add-quax

Conversation

@nstarman

Copy link
Copy Markdown
Contributor

Summary

Followup to a review comment on #805/#796: quaxify is wrapped in galax.utils._jax/galax.coordinates._src.shape with an explicit cast, because quax is deliberately excluded from the mypy pre-commit hook's isolated environment (only dataclassish, optype, and plum-dispatch are installed there, to keep it fast). Without quax resolvable, decorating with quax.quaxify directly makes the decorated function untyped — so the cast isn't a workaround for quax's own typing, it's a workaround for this hook's minimal environment.

This PR adds quax (only quax — not quaxed/unxt/coordinax/jax, which would pull in the whole JAX stack and defeat the point of the minimal hook) to additional_dependencies, and removes the now-genuinely-redundant quaxify wrapper.

Doing so lets mypy follow real types through every quax-touched call chain for the first time, which surfaced 191 pre-existing errors across 60 files that were previously masked. Breakdown:

  • no-any-return (bulk of it) — from jax's own functions (jax.jit, jax.grad, jax.lax.scan, .min()/.max()) being untyped under this project's ignore_missing_imports policy for jax.*. Fixed with # type: ignore[no-any-return], matching the one existing precedent in this codebase (coordinates/_src/base.py).
  • redundant casts that predated quax being resolvable — removed, including the quaxify wrapper itself.
  • override/misc from equinox's AbstractVar/AbstractClassVar + property-override pattern, and plum's multiple-dispatch redefinitions — ignored with the matching error code, consistent with how this codebase already handles the same pattern elsewhere.
  • two real fixes, not just suppressions:
    • _error_if_not_all_constant_parameters in the galpy interop is now generic ([PT: gp.AbstractPotential](pot: PT) -> PT) so it preserves the caller's specific potential subtype instead of widening every caller to AbstractPotential.
    • NBodyField's force calculation called self.eps.ustrip(...) / self.masses.ustrip(...) as method calls, but both fields are typed Quantity | Array — a bare Array has no .ustrip method, so this would crash at runtime for that case. Switched to the polymorphic u.ustrip(AllowValue, ...) free function already used two lines above for the same purpose.
  • integrate_field's @ft.partial(eqx.filter_jit) decorator had zero bound arguments, making it a no-op wrapper that also breaks mypy's ability to see eqx.filter_jit's ParamSpec-preserving signature through functools.partial. Simplified to bare @eqx.filter_jit, matching the pattern already used elsewhere in this codebase.

Verification

  • pre-commit run --all-files (the actual hook config, not a local approximation) is clean.
  • Full test suite: 1080 passed, 170 skipped, 14 xfailed — no new failures. (One pre-existing failure, test_mockstreamgenerator.py::test_second_deriv, was confirmed to already fail identically on unmodified main; unrelated to this change, deselected for the full-suite verification run.)
  • Doctests on every touched file: 2076 passed.

🤖 Generated with Claude Code

The mypy hook's additional_dependencies deliberately excludes quax,
quaxed, unxt, coordinax, and jax to keep the isolated hook environment
fast -- everything they touch resolves to Any there. That's why
galax.utils._jax.quaxify wrapped quax.quaxify in an explicit cast: without
quax installed, decorating with it directly makes the decorated function
untyped, which is what #796's review comment ran into.

Installing quax specifically (not the rest -- that pulls in the whole
JAX stack and defeats the point of the minimal hook) lets mypy follow
real types through every quax-touched call chain for the first time.
That surfaced 191 pre-existing errors across 60 files that were
previously masked, mostly:

- `no-any-return` from jax's own functions (jax.jit, jax.grad,
  jax.lax.scan, .min()/.max()) being untyped under this project's
  ignore_missing_imports policy for jax.* -- fixed with
  `# type: ignore[no-any-return]`, matching the one existing precedent
  in this codebase (coordinates/_src/base.py).
- redundant casts that predated quax being resolvable, now removed,
  including the galax.utils._jax.quaxify wrapper itself.
- `override`/`misc` from equinox's AbstractVar/AbstractClassVar +
  property pattern and plum's multiple-dispatch redefinitions --
  ignored with the matching error code.
- two real fixes: _error_if_not_all_constant_parameters in the galpy
  interop is now generic so it preserves the caller's specific
  potential subtype instead of widening to AbstractPotential; and
  NBodyField._call used `self.eps.ustrip(...)`/`self.masses.ustrip(...)`
  as method calls, which would crash if a bare Array (not a Quantity)
  were passed for either field -- switched to the polymorphic
  `u.ustrip(AllowValue, ...)` free function already used two lines
  above for the same purpose.
- `integrate_field`'s `@ft.partial(eqx.filter_jit)` decorator carried
  zero bound arguments, making it a no-op wrapper that also breaks
  mypy's ability to see through functools.partial's ParamSpec; changed
  to bare `@eqx.filter_jit`, matching the pattern already used
  elsewhere in this codebase.

Verified with the actual pre-commit hook (not just a local mypy
invocation): `pre-commit run --all-files` is clean, the full test
suite passes, and doctests on every touched file pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.63415% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.73%. Comparing base (b8a378a) to head (b762ddd).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...lax/dynamics/_src/examples/uniform_acceleration.py 0.00% 3 Missing ⚠️
src/galax/potential/_src/builtin/burkert.py 75.00% 2 Missing ⚠️
src/galax/potential/_src/builtin/plummer.py 71.42% 2 Missing ⚠️
src/galax/dynamics/_src/examples/mw_lmc.py 75.00% 1 Missing ⚠️
src/galax/potential/_src/base_multi.py 85.71% 1 Missing ⚠️
src/galax/potential/_src/builtin/null.py 85.71% 1 Missing ⚠️
...rc/galax/potential/_src/builtin/stoneostriker15.py 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #806      +/-   ##
==========================================
- Coverage   95.75%   95.73%   -0.03%     
==========================================
  Files         159      159              
  Lines        6055     6089      +34     
==========================================
+ Hits         5798     5829      +31     
- Misses        257      260       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nstarman nstarman added this to the v0.1.0 milestone Aug 14, 2026
@nstarman
nstarman merged commit cb80a50 into main Aug 14, 2026
17 of 19 checks passed
@nstarman
nstarman deleted the claude/mypy-hook-add-quax branch August 14, 2026 14:18
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