Skip to content

xarray_annotated

xarray_annotated

xarray-annotated: validate DataArray properties declared via typing.Annotated.

Annotation is the unifying technology: a declared property is read off an Annotated[DataArray, ...] hint and validated at runtime. Each kind of property is a domain subpackage, imported explicitly:

  • xarray_annotated.units — physical units (pint / CF), the mature domain.
  • xarray_annotated.schema — structural properties (dims, coords, dtype); validate-only (never mutates).
  • xarray_annotated.temporal — the frequency (and phase) of a time axis; validate-only.

The top level is deliberately thin — nothing domain-specific is re-exported here, so the domains never collide in a shared namespace::

from xarray_annotated import schema, temporal, units
from xarray_annotated.units import declare_units, check_units
from xarray_annotated.schema import declare_schema, Dims, Dtype
from xarray_annotated.temporal import declare_freq, Freq

The only names surfaced at the top level are domain-agnostic helpers that belong to no single domain: the shared declaration writer annotate and its inverse reader declarations_from_signature (which reads every facet declared on a signature into one uniform Declared value), plus the Annotated introspection kernel (unwrap_annotated, walk_signature). walk_signature is the shared driver behind every domain’s reader, so a third-party facet author can use it to build their own *_from_signature reader. No domain-specific name is re-exported here.

Declared dataclass

Declared(
    unit: Unit | None = None,
    dims: Dims | None = None,
    dtype: Dtype | None = None,
    coords: Coords | None = None,
    freq: Freq | None = None,
)

Every facet declared on one DataArray hint, one marker (or None) per slot.

The uniform value produced by declarations_from_signature: a homogeneous bag of markers, so facet-generic code can treat each slot identically. Each slot holds the corresponding marker or None when that facet was not declared; the unit is always a Unit (its bare-string shorthand is normalised on read), so .unit.unit recovers the string.

The field order (unit, dims, dtype, coords, freq) matches annotate’s keyword arguments, so annotate(unit=d.unit, dims=d.dims, dtype=d.dtype, coords=d.coords, freq=d.freq) rebuilds the hint this was read from.

annotate

annotate(
    base: Any = DataArray,
    *,
    unit: str | Unit | None = None,
    dims: Iterable[str] | Dims | None = None,
    dtype: str | Dtype | None = None,
    coords: Iterable[str] | Coords | None = None,
    freq: str | Freq | None = None,
) -> Any

Build an Annotated[base, <markers>] hint from declared facet values.

The inverse of the *_from_signature readers: given facet values it returns a real Annotated object carrying the corresponding markers, in a fixed order (unit, dims, dtype, coords, freq). Assign it to a function’s return/parameter annotation and the declare_units / declare_schema / declare_freq decorators read it back exactly as they would a hand-written one.

Each facet accepts either a raw value or an already-built marker, so a caller holding a mix (e.g. a Unit object but bare dim-name tuples) can pass both without unwrapping:

* `unit`   — a unit string (`"Pa"`) or a `Unit`.
* `dims`   — an iterable of dim names (`("time", "x")`) or a `Dims`.
* `dtype`  — a dtype string (`"float64"`) or a `Dtype`.
* `coords` — an iterable of coord names or a `Coords`.
* `freq`   — an offset string (`"7D"`) or a `Freq`.

A facet left as None contributes no marker. When no facet is given, base is returned unchanged (no Annotated wrapper), so annotate() is a safe no-op default.

Note the freq string is a writer convenience only: there is no bare-string shorthand on the read side, so a frequency must be spelled as a Freq marker in a hand-written annotation.

Parameters:

  • base (Any, default: DataArray ) –

    The base type to annotate (default xarray.DataArray).

  • unit (str | Unit | None, default: None ) –

    Declared unit, or None.

  • dims (Iterable[str] | Dims | None, default: None ) –

    Declared dimensions, or None.

  • dtype (str | Dtype | None, default: None ) –

    Declared dtype, or None.

  • coords (Iterable[str] | Coords | None, default: None ) –

    Declared coordinates, or None.

  • freq (str | Freq | None, default: None ) –

    Declared temporal frequency, or None.

Returns:

  • Any

    Annotated[base, <markers>] if any facet was given; otherwise base.

Examples:

>>> from typing import Annotated, get_args, get_origin
>>> import xarray as xr
>>> from xarray_annotated import annotate
>>> from xarray_annotated.units import Unit
>>> hint = annotate(unit="Pa", dims=("time", "x"), dtype="float64")
>>> get_origin(hint) is Annotated
True
>>> Unit("Pa") in get_args(hint)
True
>>> annotate() is xr.DataArray
True

declarations_from_signature

declarations_from_signature(
    func: object,
) -> tuple[dict[str, Declared], dict[str, Declared] | Declared | None]

Read every declared facet off a function’s signature in one uniform shape.

The all-facets counterpart to units_from_signature / schema_from_signature: a single reader whose payload is a Declared carrying the unit, dims, dtype, and coords declared on a DataArray hint at once. A consumer that would otherwise call both per-domain readers and merge their differently-shaped results can call this once and route a uniform value instead.

Parameters:

  • func (object) –

    A callable whose parameters/return may carry Annotated[DataArray, ...] metadata.

Returns:

unwrap_annotated

unwrap_annotated(hint: Any) -> Any

Return the underlying type of an Annotated hint, else the hint itself.

Annotated[DataArray, ...]DataArray; a non-Annotated hint is returned unchanged. Lets type comparisons (e.g. t is DataArray) see through the metadata that declarations attach to parameters.

Parameters:

  • hint (Any) –

    A type hint, possibly Annotated.

Returns:

  • Any

    The base type if hint is Annotated; otherwise hint unchanged.

walk_signature

walk_signature(
    func: object, extract: Callable[[Any], T | None]
) -> tuple[dict[str, T], dict[str, T] | T | None]

Extract per-parameter and return declarations from a function’s hints.

Reads get_type_hints(func, include_extras=True) and maps each hint through extract, which returns a domain’s declaration payload for that hint or None when the hint carries no declaration. The is not None filter is the single “no declaration” rule shared by every domain.

Parameters:

  • func (object) –

    A callable whose parameters/return may carry Annotated metadata.

  • extract (Callable[[Any], T | None]) –

    Turns one hint into a declaration payload, or None. For units this is annotated_unit (payload str); for schema annotated_schema (payload list[marker]).

Returns:

  • dict[str, T]

    An (inputs, output) pair:

  • dict[str, T] | T | None
    • inputsdict[str, T] mapping each declared parameter to its payload.
  • tuple[dict[str, T], dict[str, T] | T | None]
    • output — a dict[str, T] (per-field) if the return hint is a TypedDict or dataclass; a single T if the return is one declared Annotated[DataArray, ...]; None otherwise.