Skip to content

Replace mypy with ty for type checking#400

Merged
aldenks merged 11 commits intomainfrom
claude/migrate-mypy-to-ty-FfWTU
Feb 3, 2026
Merged

Replace mypy with ty for type checking#400
aldenks merged 11 commits intomainfrom
claude/migrate-mypy-to-ty-FfWTU

Conversation

@aldenks
Copy link
Member

@aldenks aldenks commented Jan 30, 2026

Summary

Migrate from mypy to ty as the primary type checker for the project. This includes updating all configuration files, CI/CD workflows, documentation, and code to use ty instead of mypy.

Key Changes

Configuration & Setup

  • Updated pyproject.toml to replace mypy~=1.18 dependency with ty~=0.0.14
  • Replaced mypy configuration section with ty configuration including Python 3.13 target and custom rule settings
  • Updated .gitignore and .dockerignore to ignore .ty/ cache directory instead of mypy caches
  • Updated .pre-commit-config.yaml to run ty check instead of mypy
  • Removed mypy VSCode extension recommendation from .vscode/extensions.json

CI/CD & Documentation

  • Updated GitHub Actions workflow to run uv run ty check instead of uv run mypy
  • Updated AGENTS.md and README.md with ty commands and references
  • Updated code style guidelines to reference ty type checking instead of mypy strict mode

Code Changes

  • Removed # type: ignore[import-untyped] comments from untyped library imports (numba, kubernetes, rasterio, etc.) as ty handles these differently
  • Replaced mypy-specific # type: ignore[...] comments with ty equivalents where needed (e.g., # ty: ignore[not-iterable])
  • Updated pydantic-related type ignores on @computed_field decorators to work with ty
  • Improved type annotations in config_models.py for better type safety with codecs handling
  • Added explicit imports for submodules (e.g., import zarr.abc.store, import sentry_sdk.crons) to satisfy ty's stricter module checking
  • Updated ruff ignore comments to remove mypy-specific references (UP046, UP047)
  • Refactored complex type-ignored expressions to be more explicit (e.g., in datasets_cf_compliance_test.py)

Type Checking Configuration

  • Configured ty to warn on possibly-missing-attribute (for submodule access like zarr.abc)
  • Configured ty to warn on no-matching-overload (for incomplete xarray stubs)
  • Configured ty to warn on invalid-method-override (for valid Python covariance patterns)

Notable Details

  • ty provides better handling of untyped library imports without requiring explicit ignore comments
  • The migration maintains strict type checking while being more pragmatic about third-party library limitations
  • All existing type annotations and patterns remain compatible with ty

https://claude.ai/code/session_01RTS6T1SJHLNr4u6vinbqVg

lead_time=lead_time,
domain="conus",
file_type=file_type,
file_type=file_type, # ty: ignore[invalid-argument-type]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems typed correctly. maybe our item utility isn't right?

lead_time=lead_time,
domain="conus",
file_type=file_type,
file_type=file_type, # ty: ignore[invalid-argument-type]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems typed correctly. maybe our item utility isn't right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't able to get around this

Comment on lines 133 to 134
return gefs_file_type
# ty can't narrow through if/elif chain; remaining values are "a" | "b"
return gefs_file_type # ty: ignore[invalid-return-type]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can probably help type checker narrow and remove the ignore with an elif gefs_file_type == "a" or gefs_file_type == "b", and then an else: assert_never(gefs_file_type)

Comment on lines 616 to 618
# xarray's __getitem__ with a list returns Dataset, but ty stubs say DataArray
ds: xr.Dataset = self.template_ds[[v.name for v in self.data_vars]] # type: ignore[assignment]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# xarray's __getitem__ with a list returns Dataset, but ty stubs say DataArray
ds: xr.Dataset = self.template_ds[[v.name for v in self.data_vars]] # type: ignore[assignment]
ds: xr.Dataset = self.template_ds[[v.name for v in self.data_vars]] # type: ignore[assignment] stubs are wrong

Comment on lines 142 to 143
case _ as unreachable:
assert_never(unreachable)
assert_never(unreachable) # ty: ignore[type-assertion-failure]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should work without an ignore

case _:
    assert_never(gefs_file_type)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't, but im going to live with that until ty implements this and then it should start telling us the type ignore is unused

@aldenks aldenks force-pushed the claude/migrate-mypy-to-ty-FfWTU branch from 86c253d to 2ad3ff0 Compare January 30, 2026 15:13
claude and others added 3 commits January 30, 2026 16:27
Replace mypy with Astral's ty type checker throughout the project:

- Replace mypy dependency with ty in pyproject.toml
- Update ty configuration in pyproject.toml
- Update pre-commit hook to use ty
- Update GitHub Actions workflow to use ty
- Update documentation (README.md, AGENTS.md/CLAUDE.md)
- Update .vscode/extensions.json to remove mypy extension
- Update .gitignore and .dockerignore for ty cache

Remove unnecessary type: ignore comments that ty no longer needs:
- ty has better type stubs for numba, numcodecs, fsspec, rasterio, yaml, kubernetes
- ty handles pydantic's @computed_field properly

Add explicit submodule imports for zarr.abc, zarr.storage, sentry_sdk.crons,
numcodecs.abc to satisfy ty's stricter submodule attribute checking.

Add targeted ty: ignore comments for:
- numba's prange (not-iterable - prange is only iterable at runtime in @njit)
- Complex type narrowing that ty can't verify through control flow
- xarray's ds[[list]] returning DataArray instead of Dataset (stub limitation)

Configure ty rules to treat some checks as warnings instead of errors:
- invalid-method-override: Subclass return type covariance is valid Python
- no-matching-overload: xarray stubs are incomplete
- possibly-missing-attribute: For submodule access patterns

ty finds issues mypy missed due to better type stubs, while still being fast.

https://claude.ai/code/session_01RTS6T1SJHLNr4u6vinbqVg
@aldenks aldenks force-pushed the claude/migrate-mypy-to-ty-FfWTU branch from b949cb8 to 7cc985b Compare January 30, 2026 21:47
aldenks and others added 4 commits January 30, 2026 21:23
Remove ABC and @AbstractMethod decorators from TemplateConfig, RegionJob,
and DynamicalDataset base classes. The methods still raise NotImplementedError
to enforce implementation at runtime, but ABC is no longer needed since:

1. Parameter names between base and subclass methods now match
2. ty doesn't require ABC for proper type checking of method overrides
3. This allows tests to instantiate base/intermediate classes directly

https://claude.ai/code/session_01RTS6T1SJHLNr4u6vinbqVg
Remove typing.override decorators since they're not used consistently
throughout the codebase. Add noqa: ARG002 comments for unused method
arguments that are required by the parent class interface.

https://claude.ai/code/session_01RTS6T1SJHLNr4u6vinbqVg
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the project’s static type checking from mypy to ty, updating configuration, CI workflows, docs, and code to align with ty’s rules and diagnostics. It also makes small refactors and import adjustments to satisfy ty’s stricter module and attribute checking while keeping runtime behavior unchanged.

Changes:

  • Replace mypy with ty across tooling: dev dependencies, pyproject.toml config, pre-commit hooks, GitHub Actions workflow, editor recommendations, and ignore patterns.
  • Update code and tests to remove mypy-specific # type: ignore[...] comments, add ty-specific ignores, and introduce explicit imports / annotations (e.g., for numba, zarr, sentry, rasterio, Timedelta math) to satisfy ty while preserving semantics.
  • Refactor a few helper functions and expressions (e.g., XML parsing for CF standard names, Timedelta division, band-selection logic for rasterio) for clearer typing and better alignment with ty’s analysis.

Reviewed changes

Copilot reviewed 59 out of 61 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
uv.lock Drops mypy and related deps, adds ty and updates dev extras to reflect the new type checker.
pyproject.toml Removes mypy and pydantic-mypy configuration, adds ty rules and dev dependency, and adjusts Ruff comments about PEP 695 generics.
.pre-commit-config.yaml Swaps the local mypy hook for a ty hook running uv run ty check.
.github/workflows/code-quality.yml Updates CI to run uv run ty check in place of uv run mypy.
.gitignore / .dockerignore Generalizes the “mypy” ignore section to “type checkers” while retaining mypy cache patterns.
.vscode/extensions.json Removes the mypy VS Code extension recommendation, leaving only Ruff.
README.md Updates development tooling docs to mention ty instead of mypy and adjusts recommended commands.
AGENTS.md Switches tool listing and example commands from mypy to ty and updates the code-style guideline to refer to ty ignores.
tests/noaa/gfs/template_config_test.py Removes an obsolete mypy attr-defined ignore on coords.fget in tests.
tests/noaa/gfs/forecast/template_config_test.py Drops mypy import-untyped ignore on rasterio in GFS forecast template tests.
tests/noaa/gefs/forecast_35_day/template_config_test.py Same as above for GEFS 35-day forecast template tests.
tests/example/template_config_test.py Updates a commented-out rasterio import to remove the mypy-specific ignore.
tests/common/validation_test.py Replaces a broad zarr import with more targeted zarr.core.sync / zarr.storage imports to satisfy ty’s module/attribute checking.
tests/common/test_storage.py Adds zarr.storage import to make types like LocalStore explicit for ty.
tests/common/template_config_test.py Splits a Timedelta division into a temporary float result to align with ty’s assignment rules, then casts to int.
tests/common/region_job_test.py Imports dask.array explicitly and removes unnecessary type ignores around dask.array.full; renames unused parameters with # noqa: ARG002.
tests/common/dynamical_dataset_test.py Adds explicit imports for sentry_sdk.crons and zarr.errors; removes pydantic computed_field ignore annotations on test config properties.
tests/common/datasets_cf_compliance_test.py Refactors XML parsing into an explicit loop that checks elements and text for None instead of relying on union-attr ignores.
tests/common/common_template_config_subclasses_test.py Removes mypy-specific ignores on template_path reassignment and fixture return type.
src/scripts/validation/compare_spatial.py Adds targeted # ty: ignore[...] comments for attributes on ref_data where ty’s analysis is too conservative.
src/scripts/generate_manual_workflows.py Removes mypy import-untyped ignore from the yaml import.
src/reformatters/noaa/hrrr/template_config.py Cleans up pydantic computed_field annotations by dropping mypy-specific prop-decorator ignores.
src/reformatters/noaa/hrrr/region_job.py Removes mypy import-untyped on rasterio and rewrites GRIB band matching from a walrus-comprehension into a clearer loop with explicit list accumulation.
src/reformatters/noaa/hrrr/forecast_48_hour/template_config.py Same computed_field cleanup; no runtime behavior change.
src/reformatters/noaa/hrrr/forecast_48_hour/region_job.py Adds a # ty: ignore[invalid-argument-type] where ty can’t see a domain argument’s compatibility, without changing behavior.
src/reformatters/noaa/hrrr/analysis/template_config.py Removes mypy ignores around computed fields on analysis templates.
src/reformatters/noaa/hrrr/analysis/region_job.py Same domain file_type argument ty-ignore adjustment as the 48-hour forecast job.
src/reformatters/noaa/gfs/region_job.py Drops mypy import ignore and expands GRIB band selection into an explicit loop mirroring the HRRR change.
src/reformatters/noaa/gfs/forecast/template_config.py Cleans up computed_field decorators for dataset attributes, coords, and data_vars.
src/reformatters/noaa/gfs/analysis/template_config.py Same computed_field cleanup and introduces a named-but-unused ds parameter with # noqa: ARG002 for ty.
src/reformatters/noaa/gefs/read_data.py Removes mypy import ignore, explicitly imports rasterio.warp, and rewrites band matching to a loop consistent with other GRIB readers.
src/reformatters/noaa/gefs/gefs_config_models.py Makes gefs_file_type handling more precise and type-safe by enumerating simple file types and using assert_never for impossible branches to satisfy ty.
src/reformatters/noaa/gefs/forecast_35_day/template_config.py Adjusts append-dim chunk-size computation to go through a float variable (per ty) and removes computed_field mypy ignores.
src/reformatters/noaa/gefs/analysis/template_config.py Same pattern as GEFS 35-day: chunk-size calculation via an intermediate float and computed_field cleanup.
src/reformatters/example/template_config.py Updates example TemplateConfig to the new computed_field style without mypy-specific ignores.
src/reformatters/example/region_job.py Removes an old mypy type-var ignore in commented example logic.
src/reformatters/ecmwf/ifs_ens/forecast_15_day_0_25_degree/template_config.py Cleans computed_field annotations and keeps TemplateConfig integration compatible with ty.
src/reformatters/ecmwf/ifs_ens/forecast_15_day_0_25_degree/region_job.py Removes mypy import-untyped ignore for rasterio.
src/reformatters/dwd/icon_eu/forecast/template_config.py Uses computed_field without mypy ignores for the new DWD ICON-EU forecast template.
src/reformatters/dwd/icon_eu/forecast/region_job.py Drops mypy ignore for rasterio and cleans up a commented example type-var ignore in update_template_with_results.
src/reformatters/contrib/uarizona/swann/analysis/template_config.py Cleans computed_fields for the SWANN analysis TemplateConfig.
src/reformatters/contrib/uarizona/swann/analysis/region_job.py Removes mypy import ignore on rasterio and updates an unused-parameter name to comply with linting; note: still uses from rasterio import rasterio, which is likely an incorrect import form and should be import rasterio.
src/reformatters/contrib/noaa/ndvi_cdr/analysis/template_config.py Removes mypy-specific computed_field ignores for the NDVI CDR analysis template.
src/reformatters/contrib/noaa/ndvi_cdr/analysis/region_job.py Drops mypy import-untyped on rasterio and renames an unused parameter with an ARG002 suppression.
src/reformatters/contrib/noaa/ndvi_cdr/analysis/quality_flags.py Removes a no-any-return ignore now that return types are precise.
src/reformatters/contrib/nasa/smap/level3_36km_v9/template_config.py Applies the same computed_field cleanup and uses ty-friendly typing for coords and data_vars.
src/reformatters/contrib/nasa/smap/level3_36km_v9/region_job.py Drops mypy import-untyped from rasterio and marks an unused parameter appropriately.
src/reformatters/common/zarr.py Adds zarr.core.sync import explicitly for sync_to_store, aligning with ty’s module-resolution expectations.
src/reformatters/common/validation.py Adds explicit zarr.core.sync / zarr.storage imports and keeps validation helpers typed in a way that ty can understand.
src/reformatters/common/update_progress_tracker.py Removes the mypy ignore on fsspec, imports fsspec.implementations.local, and uses it for a precise LocalFileSystem type check.
src/reformatters/common/template_utils.py Drops mypy no-untyped-call ignores for dask.array.full and adds explicit zarr submodule imports for ty.
src/reformatters/common/template_config.py Cleans all TemplateConfig computed_fields of mypy ignores, updates derive_coordinates signatures for ty, and makes append-dim chunk-size computation explicitly go through a float to satisfy ty’s Timedelta-division typing.
src/reformatters/common/storage.py Removes mypy import ignore on fsspec, imports zarr submodules explicitly, and keeps StorageConfig.version as a computed_field without mypy-specific suppression.
src/reformatters/common/region_job.py Removes a type-var ignore in update_template_with_results, adds a ty-ignore for the max(..., default=None) call, and explicitly annotates an xarray Dataset slice to work around stub issues.
src/reformatters/common/kubernetes.py Drops the mypy import-untyped ignore on the Kubernetes client/config import.
src/reformatters/common/interpolation.py Removes numba decorator type-ignore and introduces a ty-ignore on prange iteration, keeping the numba-accelerated interpolation logic intact.
src/reformatters/common/dynamical_dataset.py Adds an explicit sentry_sdk.crons import and cleans computed_fields for ty compatibility.
src/reformatters/common/deaccumulation.py Similar to interpolation: cleans numba decorator ignore and adds a ty-ignore on prange, preserving deaccumulation behavior.
src/reformatters/common/config_models.py Reworks codecs_to_dicts to return only dicts (instead of sometimes codecs) and makes its input optional, aligning Encoding.filters with ty’s expectations.
src/reformatters/common/binary_rounding.py Removes numba decorator type-ignore and adds a prange ty-ignore while keeping rounding logic unchanged.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@JackKelly
Copy link
Contributor

Love this 🙂

@aldenks
Copy link
Member Author

aldenks commented Feb 2, 2026

@JackKelly it checks the whole repo in 1.9s! (mypy is 18s). Im going to wait to update and merge it until #406 is on main to not create merge conflicts for you

@aldenks aldenks marked this pull request as ready for review February 3, 2026 20:37
@aldenks aldenks enabled auto-merge (squash) February 3, 2026 20:51
@aldenks aldenks merged commit 4697ffa into main Feb 3, 2026
5 checks passed
@aldenks aldenks deleted the claude/migrate-mypy-to-ty-FfWTU branch February 3, 2026 20:54
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.

3 participants