Repository navigation
feat(typing)!: make the spec type parameters covariant - #191
Merged
d-v-b merged 3 commits intoOct 28, 2026
Merged
Conversation
TAttr and TItem are covariant: the specs are frozen, so both parameters appear only in read positions on instances, and a narrowed spec is substitutable for a wider one. TAnyItem dissolves (TBaseItem's bare, recursive spelling is again both bound and default), and the Any* aliases become ordinary wide types (ArraySpec[TBaseAttr] and friends), so attributes and members read through them stay checked instead of going gradual. The constructing classmethods (from_array, v2 and v3) take TBaseAttr instead of TAttr, since a covariant parameter may not appear in argument positions; the class's attribute type is still enforced, by validation. tests/typing_conformance.py gains widening functions that pyright and ty verify (7 diagnostics each without covariance, 0 with). Assisted-by: ClaudeCode:claude-fable-5 Co-Authored-By: Claude Fable 5 <[email protected]>
The from_flat accumulators use the Any* aliases instead of literal Any parameterizations; the conformance module docstring describes the covariant design and its checkers; a conformance function records that from_array keeps Self returns on narrowed classes while its attributes parameter is TBaseAttr (enforced by validation); and the zarr-developers#190 fragment describes the merged end state instead of the superseded TAnyItem design. Assisted-by: ClaudeCode:claude-fable-5 Co-Authored-By: Claude Fable 5 <[email protected]>
Assisted-by: ClaudeCode:claude-fable-5 Co-Authored-By: Claude Fable 5 <[email protected]>
d-v-b
added a commit
to d-v-b/data-model
that referenced
this pull request
Oct 28, 2026
…bers override The pyz ArraySpec type parameter was phantom (both wrappers subclassed ArraySpec[Any]), so every declared attributes model on the Sentinel array classes was decorative: Sentinel2DataArray accepted arbitrary attributes and held plain dicts. The parameter is now passed through, so attributes validate against the declared model and read back typed. The GroupSpec wrappers stop binding the members mapping type into the parent's member-item slot (the old type-var hack and both its ignores): with pydantic-zarr's covariant parameters, parameterizing the parent at its widest member type and narrowing the members field is a legal override. s3_olci spells unconstrained attributes Mapping[str, object] (object no longer satisfies the enforced bound), and the GeoZarr v3 group classes use AnyGroupSpec, now an ordinary wide type, so member reads stay checked. pydantic-zarr is bumped to main with covariant spec parameters (zarr-developers/pydantic-zarr#191). Assisted-by: ClaudeCode:claude-fable-5 Co-Authored-By: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI text below 🤖
The spec type parameters (
TAttr,TItem) are now covariant, in v2 and v3: the specs are frozen, so both parameters appear only in read positions on instances, and a narrowed spec is substitutable for a wider one. TheAny*aliases (AnyArraySpec,AnyGroupSpec,AnyBaseGroupSpec) become ordinary wide types (ArraySpec[TBaseAttr]and friends) instead of gradual-typing escapes, so attributes and members read through them — including the results ofresolve_path,from_flatand theis_*_specguards — keep checked types instead ofAny. The constructing classmethods (from_array) takeattributes: TBaseAttrinstead ofTAttr, since a covariant parameter may not appear in argument positions: the argument is checked against the class's attribute type by validation rather than statically, which is the trade that keepsSelfreturn types for subclasses.Design notes
TBaseItem's bare, recursive spelling serves as bothTItem's bound and its default again, andTAnyItemis deleted. The default's bare spelling remains load-bearing at runtime — it is the pydantic schema for a bareGroupSpec's members, validating nested members as specs all the way down.changes/190.bugfix.mdis rewritten to describe the merged end state (both fragments render in the same release notes).from_arraysignature, the alternative — a method-scoped TypeVar (cls: type[ArraySpec[A]]) — would keep static checking of theattributesargument but loseSelfreturns, which downstream subclasses rely on (DataArray.from_arrayreturningDataArray). The conformance file records the chosen trade-off.Testing
tests/typing_conformance.pygains covariant-widening functions (narrowed array/group →Any*alias) and a checked-read assertion; ty and pyright each report 7 diagnostics ifcovariant=Trueis removed, 0 with it.eopf-geozarr(the pyright-checked downstream from fix(typing): accept narrowed member specs in the TItem bound #190): whole-repo pyright 0 errors and itsdata_apitest suite passes with this branch installed.from_arraytrade-off, leftover literalAnyparameterizations infrom_flat); all are addressed in the follow-up commit.Stack bookkeeping
Builds on #190 (merged); rebased onto
mainat 5c3475f. Lands before the next release so the release notes describe the covariant design once.🤖 Generated with Claude Code