Skip to content

feat(typing)!: make the spec type parameters covariant - #191

Merged
d-v-b merged 3 commits into
zarr-developers:mainfrom
d-v-b:feat/covariant-spec-parameters
Oct 28, 2026
Merged

d-v-b merged 3 commits into
zarr-developers:mainfrom
d-v-b:feat/covariant-spec-parameters

Conversation

@d-v-b

@d-v-b d-v-b commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

🤖 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. The Any* 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 of resolve_path, from_flat and the is_*_spec guards — keep checked types instead of Any. The constructing classmethods (from_array) take attributes: TBaseAttr instead of TAttr, 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 keeps Self return types for subclasses.

Design notes

  • This supersedes the bound/default split fix(typing): accept narrowed member specs in the TItem bound #190 introduced: with covariance, TBaseItem's bare, recursive spelling serves as both TItem's bound and its default again, and TAnyItem is deleted. The default's bare spelling remains load-bearing at runtime — it is the pydantic schema for a bare GroupSpec's members, validating nested members as specs all the way down.
  • changes/190.bugfix.md is rewritten to describe the merged end state (both fragments render in the same release notes).
  • On the from_array signature, the alternative — a method-scoped TypeVar (cls: type[ArraySpec[A]]) — would keep static checking of the attributes argument but lose Self returns, which downstream subclasses rely on (DataArray.from_array returning DataArray). The conformance file records the chosen trade-off.

Testing

  • Full suite green across the zarr-python matrix (19,819); pyrefly, ty, pyright and ruff clean; pydantic's runtime is indifferent to variance declarations.
  • tests/typing_conformance.py gains covariant-widening functions (narrowed array/group → Any* alias) and a checked-read assertion; ty and pyright each report 7 diagnostics if covariant=True is removed, 0 with it.
  • Verified against eopf-geozarr (the pyright-checked downstream from fix(typing): accept narrowed member specs in the TItem bound #190): whole-repo pyright 0 errors and its data_api test suite passes with this branch installed.
  • The roborev review of the covariance commit produced three findings (stale fix(typing): accept narrowed member specs in the TItem bound #190 fragment, undocumented from_array trade-off, leftover literal Any parameterizations in from_flat); all are addressed in the follow-up commit.
Stack bookkeeping

Builds on #190 (merged); rebased onto main at 5c3475f. Lands before the next release so the release notes describe the covariant design once.

🤖 Generated with Claude Code

d-v-b and others added 3 commits October 28, 2026 13:15
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
d-v-b merged commit 66cf239 into zarr-developers:main Oct 28, 2026
25 checks passed
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]>
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