xarray-annotated 0.5.2__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/PKG-INFO +5 -1
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/README.md +4 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/pyproject.toml +39 -3
- xarray_annotated-0.6.0/pyproject.toml.orig +141 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/__init__.py +1 -1
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/_annotations.py +16 -9
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/_config.py +5 -8
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/schema/__init__.py +3 -2
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/schema/_annotations.py +1 -1
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/schema/_config.py +5 -5
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/schema/_decorator.py +4 -3
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/__init__.py +3 -1
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/_annotations.py +1 -1
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/_config.py +7 -7
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_check.py +7 -7
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_config.py +9 -9
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_decorator.py +2 -1
- xarray_annotated-0.5.2/pyproject.toml.orig +0 -81
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/LICENSE +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/_reader.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/_writer.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/py.typed +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/schema/_check.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/_check.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/_decorator.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/__init__.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_annotations.py +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_registry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xarray-annotated
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated
|
|
5
5
|
Keywords: xarray,pint,units,validation,annotations
|
|
6
6
|
Author: jmarshrossney
|
|
@@ -78,10 +78,12 @@ It assumes a unit (pascals) and says so in the docstring but doesn't check or en
|
|
|
78
78
|
```python
|
|
79
79
|
import xarray as xr
|
|
80
80
|
|
|
81
|
+
|
|
81
82
|
def altitude(p: xr.DataArray) -> xr.DataArray:
|
|
82
83
|
"""Barometric altitude. Assumes p is in Pa."""
|
|
83
84
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
84
85
|
|
|
86
|
+
|
|
85
87
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
86
88
|
altitude(p)
|
|
87
89
|
```
|
|
@@ -104,6 +106,7 @@ import xarray as xr
|
|
|
104
106
|
from xarray_annotated.schema import declare_schema, Dims
|
|
105
107
|
from xarray_annotated.units import declare_units, Unit
|
|
106
108
|
|
|
109
|
+
|
|
107
110
|
@declare_units
|
|
108
111
|
@declare_schema
|
|
109
112
|
def altitude(
|
|
@@ -112,6 +115,7 @@ def altitude(
|
|
|
112
115
|
"""Barometric altitude."""
|
|
113
116
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
114
117
|
|
|
118
|
+
|
|
115
119
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
116
120
|
altitude(p)
|
|
117
121
|
```
|
|
@@ -51,10 +51,12 @@ It assumes a unit (pascals) and says so in the docstring but doesn't check or en
|
|
|
51
51
|
```python
|
|
52
52
|
import xarray as xr
|
|
53
53
|
|
|
54
|
+
|
|
54
55
|
def altitude(p: xr.DataArray) -> xr.DataArray:
|
|
55
56
|
"""Barometric altitude. Assumes p is in Pa."""
|
|
56
57
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
57
58
|
|
|
59
|
+
|
|
58
60
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
59
61
|
altitude(p)
|
|
60
62
|
```
|
|
@@ -77,6 +79,7 @@ import xarray as xr
|
|
|
77
79
|
from xarray_annotated.schema import declare_schema, Dims
|
|
78
80
|
from xarray_annotated.units import declare_units, Unit
|
|
79
81
|
|
|
82
|
+
|
|
80
83
|
@declare_units
|
|
81
84
|
@declare_schema
|
|
82
85
|
def altitude(
|
|
@@ -85,6 +88,7 @@ def altitude(
|
|
|
85
88
|
"""Barometric altitude."""
|
|
86
89
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
87
90
|
|
|
91
|
+
|
|
88
92
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
89
93
|
altitude(p)
|
|
90
94
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xarray-annotated"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -56,6 +56,11 @@ dev = [
|
|
|
56
56
|
"rust-just>=1.46.0",
|
|
57
57
|
"pre-commit>=4.6.0",
|
|
58
58
|
{ include-group = "docs" },
|
|
59
|
+
{ include-group = "integrations" },
|
|
60
|
+
]
|
|
61
|
+
integrations = [
|
|
62
|
+
"beartype>=0.22.9",
|
|
63
|
+
"jaxtyping>=0.3.11",
|
|
59
64
|
]
|
|
60
65
|
docs = [
|
|
61
66
|
"marimo>=0.23.11",
|
|
@@ -80,12 +85,43 @@ line-length = 88
|
|
|
80
85
|
indent-width = 4
|
|
81
86
|
target-version = "py312"
|
|
82
87
|
|
|
88
|
+
[tool.ruff.lint]
|
|
89
|
+
select = [
|
|
90
|
+
"E",
|
|
91
|
+
"D",
|
|
92
|
+
"W",
|
|
93
|
+
"F",
|
|
94
|
+
"UP",
|
|
95
|
+
"B",
|
|
96
|
+
"SIM",
|
|
97
|
+
"RUF",
|
|
98
|
+
"PT",
|
|
99
|
+
"I",
|
|
100
|
+
]
|
|
101
|
+
|
|
83
102
|
[tool.ruff.lint.per-file-ignores]
|
|
84
|
-
"
|
|
85
|
-
"
|
|
103
|
+
"tests/**" = [
|
|
104
|
+
"B008",
|
|
105
|
+
"D",
|
|
106
|
+
"E501",
|
|
107
|
+
]
|
|
108
|
+
"examples/**" = [
|
|
109
|
+
"B018",
|
|
110
|
+
"D100",
|
|
111
|
+
"D103",
|
|
112
|
+
"E501",
|
|
86
113
|
"F722",
|
|
114
|
+
"F821",
|
|
115
|
+
"F841",
|
|
116
|
+
"RUF001",
|
|
117
|
+
"RUF003",
|
|
118
|
+
"PLR1711",
|
|
119
|
+
"PLC0415",
|
|
87
120
|
]
|
|
88
121
|
|
|
122
|
+
[tool.ruff.lint.pydocstyle]
|
|
123
|
+
convention = "google"
|
|
124
|
+
|
|
89
125
|
[tool.uv]
|
|
90
126
|
exclude-newer = "1 week"
|
|
91
127
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.11.17,<0.12.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xarray-annotated"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "jmarshrossney", email = "17361029+jmarshrossney@users.noreply.github.com" }
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">=3.12"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"pandas>=2.2",
|
|
18
|
+
"pint>=0.25.3",
|
|
19
|
+
"pint-xarray>=0.6.1",
|
|
20
|
+
"xarray>=2026.2.0",
|
|
21
|
+
]
|
|
22
|
+
keywords = ["xarray", "pint", "units", "validation", "annotations"]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Scientific/Engineering",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
cf = ["cf-xarray>=0.11.3"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://jmarshrossney.github.io/xarray-annotated"
|
|
37
|
+
Repository = "https://github.com/jmarshrossney/xarray-annotated"
|
|
38
|
+
Documentation = "https://jmarshrossney.github.io/xarray-annotated"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"xarray-annotated[cf]",
|
|
43
|
+
"cftime>=1.6",
|
|
44
|
+
"coverage>=7.13.5",
|
|
45
|
+
"pyright>=1.1.409",
|
|
46
|
+
"pytest>=9.0.2",
|
|
47
|
+
"pytest-cov>=7.1.0",
|
|
48
|
+
"ruff>=0.15.4",
|
|
49
|
+
"rust-just>=1.46.0",
|
|
50
|
+
"pre-commit>=4.6.0",
|
|
51
|
+
{include-group = "docs"},
|
|
52
|
+
{include-group = "integrations"},
|
|
53
|
+
]
|
|
54
|
+
# Third-party libraries xarray-annotated *composes with* but never imports. These are
|
|
55
|
+
# test-only: there is nothing for a user to install to "enable" the integration, so this
|
|
56
|
+
# is deliberately a dependency group and not an optional-dependency extra (contrast
|
|
57
|
+
# `cf`, which is an extra because `units._registry` really does import cf_xarray).
|
|
58
|
+
# `tests/test_jaxtyping.py` skips itself when they are absent.
|
|
59
|
+
integrations = [
|
|
60
|
+
"beartype>=0.22.9",
|
|
61
|
+
"jaxtyping>=0.3.11",
|
|
62
|
+
]
|
|
63
|
+
docs = [
|
|
64
|
+
"marimo>=0.23.11",
|
|
65
|
+
"marimo-md-export>=0.7.0",
|
|
66
|
+
"mkdocstrings-python>=2.0.5",
|
|
67
|
+
"zensical>=0.0.46",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[tool.pytest]
|
|
71
|
+
testpaths = ["tests"]
|
|
72
|
+
|
|
73
|
+
[tool.pyright]
|
|
74
|
+
venvPath = "."
|
|
75
|
+
venv = ".venv"
|
|
76
|
+
include = ["src/", "tests/"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
line-length = 88
|
|
80
|
+
indent-width = 4
|
|
81
|
+
target-version = "py312"
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
# Ruff broadened its default rule set in 0.16, so always select explicitly:
|
|
85
|
+
# that way a version bump cannot change which rules apply.
|
|
86
|
+
select = [
|
|
87
|
+
"E", # pycodestyle errors
|
|
88
|
+
"D", # pydocstyle
|
|
89
|
+
"W", # pycodestyle warnings
|
|
90
|
+
"F", # pyflakes
|
|
91
|
+
"UP", # pyupgrade
|
|
92
|
+
"B", # flake8-bugbear
|
|
93
|
+
"SIM", # flake8-simplify
|
|
94
|
+
"RUF", # ruff-specific rules
|
|
95
|
+
"PT", # flake8-pytest-style
|
|
96
|
+
"I", # isort
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.per-file-ignores]
|
|
100
|
+
# Exempt tests from docstring requirements. B008 (function call in an argument
|
|
101
|
+
# default) is exempted because several tests exist precisely to check that a
|
|
102
|
+
# parameter left at its default is still converted/validated -- the call in the
|
|
103
|
+
# default is the fixture under test, not an oversight.
|
|
104
|
+
"tests/**" = ["B008", "D", "E501"]
|
|
105
|
+
# marimo threads `Annotated` in as a cell argument, so ruff can't resolve it to
|
|
106
|
+
# `typing.Annotated` and misreads the unit strings in the metadata as forward
|
|
107
|
+
# references (F821 "undefined name" / F722 "syntax error in forward annotation").
|
|
108
|
+
# The rest are generic to marimo notebooks, which are generated dataflow
|
|
109
|
+
# descriptions rather than hand-written modules:
|
|
110
|
+
# B018 a bare trailing expression IS the cell's output mechanism.
|
|
111
|
+
# E501 long prose/URLs inside mo.md() cannot be rewrapped by ruff format,
|
|
112
|
+
# and a `# noqa` after the closing quotes flip-flops forever
|
|
113
|
+
# (marimo strips the two spaces, ruff format restores them).
|
|
114
|
+
# D100 notebooks are not modules and have no module docstring.
|
|
115
|
+
# D103 cell functions are machine-generated and undocumented.
|
|
116
|
+
# F841 exploratory cells leave unused variables around.
|
|
117
|
+
# RUF001 prose contains en dashes; RUF003 contains Greek letters.
|
|
118
|
+
"examples/**" = [
|
|
119
|
+
"B018",
|
|
120
|
+
"D100",
|
|
121
|
+
"D103",
|
|
122
|
+
"E501",
|
|
123
|
+
"F722",
|
|
124
|
+
"F821",
|
|
125
|
+
"F841",
|
|
126
|
+
"RUF001",
|
|
127
|
+
"RUF003",
|
|
128
|
+
# Pre-emptive, and only active if "PL" is ever added to select above:
|
|
129
|
+
# PLR1711 ruff deletes the bare `return` that ends every cell and
|
|
130
|
+
# `marimo check --fix` re-adds it -- an infinite flip-flop.
|
|
131
|
+
# PLC0415 notebook imports live inside cells by design.
|
|
132
|
+
"PLR1711",
|
|
133
|
+
"PLC0415",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
[tool.ruff.lint.pydocstyle]
|
|
137
|
+
convention = "google"
|
|
138
|
+
|
|
139
|
+
[tool.uv]
|
|
140
|
+
exclude-newer = "1 week"
|
|
141
|
+
exclude-newer-package = { marimo-md-export = false }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""xarray-annotated: validate
|
|
1
|
+
"""xarray-annotated: validate DataArray properties declared via ``typing.Annotated``.
|
|
2
2
|
|
|
3
3
|
Annotation is the unifying technology: a declared property is read off an
|
|
4
4
|
``Annotated[DataArray, ...]`` hint and validated at runtime. Each kind of
|
|
@@ -14,7 +14,6 @@ from collections.abc import Callable
|
|
|
14
14
|
from typing import (
|
|
15
15
|
Annotated,
|
|
16
16
|
Any,
|
|
17
|
-
TypeVar,
|
|
18
17
|
Union,
|
|
19
18
|
get_args,
|
|
20
19
|
get_origin,
|
|
@@ -24,8 +23,6 @@ from typing import (
|
|
|
24
23
|
|
|
25
24
|
import xarray as xr
|
|
26
25
|
|
|
27
|
-
_T = TypeVar("_T")
|
|
28
|
-
|
|
29
26
|
|
|
30
27
|
def unwrap_annotated(hint: Any) -> Any:
|
|
31
28
|
"""Return the underlying type of an `Annotated` hint, else the hint itself.
|
|
@@ -44,30 +41,40 @@ def unwrap_annotated(hint: Any) -> Any:
|
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
def _is_dataarray_type(tp: Any) -> bool:
|
|
47
|
-
"""Return whether `tp` is `DataArray` (possibly wrapped in a Union).
|
|
44
|
+
"""Return whether `tp` is `DataArray` (possibly wrapped or in a Union).
|
|
48
45
|
|
|
49
46
|
Accepts a bare `DataArray` as well as `DataArray | None` /
|
|
50
47
|
`Optional[DataArray]` (and any other union that includes `DataArray`), so an
|
|
51
48
|
optional DataArray parameter can still carry a declaration. Anything else
|
|
52
49
|
(scalars, `str`, `bool`, `Dataset`, …) is not a declaration-bearing type.
|
|
53
50
|
|
|
51
|
+
Also accepts any *array-type wrapper* that exposes the underlying array class
|
|
52
|
+
as an `array_type` attribute — notably jaxtyping's `Float[DataArray, "t x"]`,
|
|
53
|
+
which occupies the base-type slot of an `Annotated` hint rather than competing
|
|
54
|
+
with it. This is a duck-typed check: jaxtyping is never imported, and any
|
|
55
|
+
other library following the same convention composes for free. See
|
|
56
|
+
`docs/guides/jaxtyping.md`.
|
|
57
|
+
|
|
54
58
|
Args:
|
|
55
59
|
tp: A type annotation to inspect.
|
|
56
60
|
|
|
57
61
|
Returns:
|
|
58
|
-
`True` if `tp` is or contains `DataArray`.
|
|
62
|
+
`True` if `tp` is, wraps, or contains `DataArray`.
|
|
59
63
|
"""
|
|
60
64
|
if tp is xr.DataArray:
|
|
61
65
|
return True
|
|
66
|
+
inner = getattr(tp, "array_type", None)
|
|
67
|
+
if isinstance(inner, type) and issubclass(inner, xr.DataArray):
|
|
68
|
+
return True
|
|
62
69
|
if get_origin(tp) in (Union, types.UnionType):
|
|
63
70
|
return any(_is_dataarray_type(arg) for arg in get_args(tp))
|
|
64
71
|
return False
|
|
65
72
|
|
|
66
73
|
|
|
67
|
-
def walk_signature(
|
|
74
|
+
def walk_signature[T](
|
|
68
75
|
func: object,
|
|
69
|
-
extract: Callable[[Any],
|
|
70
|
-
) -> tuple[dict[str,
|
|
76
|
+
extract: Callable[[Any], T | None],
|
|
77
|
+
) -> tuple[dict[str, T], dict[str, T] | T | None]:
|
|
71
78
|
"""Extract per-parameter and return declarations from a function's hints.
|
|
72
79
|
|
|
73
80
|
Reads `get_type_hints(func, include_extras=True)` and maps each hint through
|
|
@@ -98,7 +105,7 @@ def walk_signature(
|
|
|
98
105
|
if (payload := extract(hint)) is not None
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
output: dict[str,
|
|
108
|
+
output: dict[str, T] | T | None
|
|
102
109
|
if is_typeddict(ret):
|
|
103
110
|
ret_hints = get_type_hints(ret, include_extras=True)
|
|
104
111
|
output = {
|
|
@@ -19,7 +19,6 @@ live here.
|
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
21
|
from collections.abc import Callable
|
|
22
|
-
from typing import TypeVar
|
|
23
22
|
|
|
24
23
|
#: Master switch default. Validation is on unless explicitly disabled.
|
|
25
24
|
DEFAULT_ENABLED: bool = True
|
|
@@ -35,8 +34,6 @@ _FALSEY: frozenset[str] = frozenset({"0", "false", "no", "off"})
|
|
|
35
34
|
#: Process-wide override for the master switch, shared across all domains.
|
|
36
35
|
_process_enabled: bool | None = None
|
|
37
36
|
|
|
38
|
-
_T = TypeVar("_T")
|
|
39
|
-
|
|
40
37
|
|
|
41
38
|
class _Unset:
|
|
42
39
|
"""Sentinel distinguishing 'argument omitted' from 'explicitly set to None'."""
|
|
@@ -48,12 +45,12 @@ class _Unset:
|
|
|
48
45
|
_UNSET = _Unset()
|
|
49
46
|
|
|
50
47
|
|
|
51
|
-
def _resolve(
|
|
48
|
+
def _resolve[T](
|
|
52
49
|
env_var: str,
|
|
53
|
-
process_value:
|
|
54
|
-
default:
|
|
55
|
-
parse: Callable[[str],
|
|
56
|
-
) ->
|
|
50
|
+
process_value: T | None,
|
|
51
|
+
default: T,
|
|
52
|
+
parse: Callable[[str], T],
|
|
53
|
+
) -> T:
|
|
57
54
|
"""Resolve one axis: env var (parsed) → process override → default."""
|
|
58
55
|
env = os.environ.get(env_var)
|
|
59
56
|
if env:
|
|
@@ -13,8 +13,9 @@ Declare several at once inside ``Annotated`` and apply them with ``declare_schem
|
|
|
13
13
|
from xarray_annotated.schema import declare_schema, Dims, Dtype
|
|
14
14
|
|
|
15
15
|
@declare_schema
|
|
16
|
-
def f(
|
|
17
|
-
|
|
16
|
+
def f(
|
|
17
|
+
x: Annotated[xr.DataArray, Dims("time", "x"), Dtype("float64")],
|
|
18
|
+
) -> xr.DataArray: ...
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
21
|
from ._annotations import (
|
|
@@ -20,11 +20,11 @@ from dataclasses import dataclass
|
|
|
20
20
|
from typing import Literal
|
|
21
21
|
|
|
22
22
|
from .._config import (
|
|
23
|
+
_UNSET,
|
|
23
24
|
DEFAULT_ENABLED,
|
|
24
25
|
ENABLED_ENV_VAR,
|
|
25
26
|
_resolve,
|
|
26
27
|
_Unset,
|
|
27
|
-
_UNSET,
|
|
28
28
|
get_enabled_override,
|
|
29
29
|
resolve_enabled,
|
|
30
30
|
set_enabled_override,
|
|
@@ -100,8 +100,8 @@ def get_policy() -> Policy:
|
|
|
100
100
|
|
|
101
101
|
def set_policy(
|
|
102
102
|
*,
|
|
103
|
-
enabled: bool |
|
|
104
|
-
on_mismatch: OnMismatch |
|
|
103
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
104
|
+
on_mismatch: OnMismatch | _Unset | None = _UNSET,
|
|
105
105
|
) -> None:
|
|
106
106
|
"""Set process-wide schema-policy overrides for one or more axes.
|
|
107
107
|
|
|
@@ -125,8 +125,8 @@ def set_policy(
|
|
|
125
125
|
@contextmanager
|
|
126
126
|
def policy(
|
|
127
127
|
*,
|
|
128
|
-
enabled: bool |
|
|
129
|
-
on_mismatch: OnMismatch |
|
|
128
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
129
|
+
on_mismatch: OnMismatch | _Unset | None = _UNSET,
|
|
130
130
|
):
|
|
131
131
|
"""Temporarily override schema-policy axes, restoring them on exit.
|
|
132
132
|
|
|
@@ -44,8 +44,8 @@ def declare_schema(
|
|
|
44
44
|
default). When the policy is **disabled** the wrapper is a total no-op.
|
|
45
45
|
|
|
46
46
|
Usable bare (`@declare_schema`) or called (`@declare_schema(on_mismatch=...)`).
|
|
47
|
-
Every marker declaration is validated **at decoration time**
|
|
48
|
-
so a malformed declaration fails fast at import.
|
|
47
|
+
Every marker declaration is validated **at decoration time**
|
|
48
|
+
(`assert_valid_schema`), so a malformed declaration fails fast at import.
|
|
49
49
|
|
|
50
50
|
Args:
|
|
51
51
|
func: The function to decorate (when used bare); `None` when parametrised.
|
|
@@ -91,7 +91,8 @@ def declare_schema(
|
|
|
91
91
|
if not pol.enabled:
|
|
92
92
|
# Master switch off: a total no-op (no validation at all).
|
|
93
93
|
return fn(*args, **kwargs)
|
|
94
|
-
# Per-call default severity (a marker's own override still wins
|
|
94
|
+
# Per-call default severity (a marker's own override still wins
|
|
95
|
+
# inside check_schema).
|
|
95
96
|
eff = on_mismatch if on_mismatch is not None else pol.on_mismatch
|
|
96
97
|
if input_markers:
|
|
97
98
|
bound = sig.bind_partial(*args, **kwargs)
|
|
@@ -16,7 +16,9 @@ the classic resample footgun of a series landing on Wednesdays::
|
|
|
16
16
|
from xarray_annotated.temporal import declare_freq, Freq
|
|
17
17
|
|
|
18
18
|
@declare_freq
|
|
19
|
-
def weekly_mean(
|
|
19
|
+
def weekly_mean(
|
|
20
|
+
x: Annotated[xr.DataArray, Freq("D")],
|
|
21
|
+
) -> Annotated[xr.DataArray, Freq("W-SUN")]:
|
|
20
22
|
return x.resample(time="W-SUN").mean()
|
|
21
23
|
|
|
22
24
|
``freq_compatible`` is the same comparison with no array in hand, for a build-time
|
|
@@ -24,11 +24,11 @@ from dataclasses import dataclass
|
|
|
24
24
|
from typing import Literal
|
|
25
25
|
|
|
26
26
|
from .._config import (
|
|
27
|
+
_UNSET,
|
|
27
28
|
DEFAULT_ENABLED,
|
|
28
29
|
ENABLED_ENV_VAR,
|
|
29
30
|
_resolve,
|
|
30
31
|
_Unset,
|
|
31
|
-
_UNSET,
|
|
32
32
|
get_enabled_override,
|
|
33
33
|
resolve_enabled,
|
|
34
34
|
set_enabled_override,
|
|
@@ -127,9 +127,9 @@ def get_policy() -> Policy:
|
|
|
127
127
|
|
|
128
128
|
def set_policy(
|
|
129
129
|
*,
|
|
130
|
-
enabled: bool |
|
|
131
|
-
on_mismatch: OnMismatch |
|
|
132
|
-
on_uninferable: OnUninferable |
|
|
130
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
131
|
+
on_mismatch: OnMismatch | _Unset | None = _UNSET,
|
|
132
|
+
on_uninferable: OnUninferable | _Unset | None = _UNSET,
|
|
133
133
|
) -> None:
|
|
134
134
|
"""Set process-wide temporal-policy overrides for one or more axes.
|
|
135
135
|
|
|
@@ -158,9 +158,9 @@ def set_policy(
|
|
|
158
158
|
@contextmanager
|
|
159
159
|
def policy(
|
|
160
160
|
*,
|
|
161
|
-
enabled: bool |
|
|
162
|
-
on_mismatch: OnMismatch |
|
|
163
|
-
on_uninferable: OnUninferable |
|
|
161
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
162
|
+
on_mismatch: OnMismatch | _Unset | None = _UNSET,
|
|
163
|
+
on_uninferable: OnUninferable | _Unset | None = _UNSET,
|
|
164
164
|
):
|
|
165
165
|
"""Temporarily override temporal-policy axes, restoring them on exit.
|
|
166
166
|
|
|
@@ -425,13 +425,13 @@ def _apply_output_quantified(
|
|
|
425
425
|
return da
|
|
426
426
|
if equal:
|
|
427
427
|
return da
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
428
|
+
# Only *compatible* units raise here; an incompatible label falls through to the
|
|
429
|
+
# conversion below, which raises DimensionalityError for us.
|
|
430
|
+
if on_output == "strict" and units_compatible(label, declared):
|
|
431
|
+
raise ValueError(
|
|
432
|
+
f"{prefix}output {name!r}: unit {label!r} differs from declared "
|
|
433
|
+
f"{declared!r} and on_output='strict' requires them to match"
|
|
434
|
+
)
|
|
435
435
|
try:
|
|
436
436
|
return da.pint.to(declared)
|
|
437
437
|
except PintExceptionGroup as group:
|
|
@@ -31,11 +31,11 @@ from dataclasses import dataclass
|
|
|
31
31
|
from typing import Literal
|
|
32
32
|
|
|
33
33
|
from .._config import (
|
|
34
|
+
_UNSET,
|
|
34
35
|
DEFAULT_ENABLED,
|
|
35
36
|
ENABLED_ENV_VAR,
|
|
36
37
|
_resolve,
|
|
37
38
|
_Unset,
|
|
38
|
-
_UNSET,
|
|
39
39
|
get_enabled_override,
|
|
40
40
|
resolve_enabled,
|
|
41
41
|
set_enabled_override,
|
|
@@ -152,10 +152,10 @@ def get_policy() -> Policy:
|
|
|
152
152
|
|
|
153
153
|
def set_policy(
|
|
154
154
|
*,
|
|
155
|
-
enabled: bool |
|
|
156
|
-
on_missing: OnMissing |
|
|
157
|
-
on_inexact: OnInexact |
|
|
158
|
-
on_output: OnOutput |
|
|
155
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
156
|
+
on_missing: OnMissing | _Unset | None = _UNSET,
|
|
157
|
+
on_inexact: OnInexact | _Unset | None = _UNSET,
|
|
158
|
+
on_output: OnOutput | _Unset | None = _UNSET,
|
|
159
159
|
) -> None:
|
|
160
160
|
"""Set process-wide policy overrides for one or more axes.
|
|
161
161
|
|
|
@@ -189,10 +189,10 @@ def set_policy(
|
|
|
189
189
|
@contextmanager
|
|
190
190
|
def policy(
|
|
191
191
|
*,
|
|
192
|
-
enabled: bool |
|
|
193
|
-
on_missing: OnMissing |
|
|
194
|
-
on_inexact: OnInexact |
|
|
195
|
-
on_output: OnOutput |
|
|
192
|
+
enabled: bool | _Unset | None = _UNSET,
|
|
193
|
+
on_missing: OnMissing | _Unset | None = _UNSET,
|
|
194
|
+
on_inexact: OnInexact | _Unset | None = _UNSET,
|
|
195
|
+
on_output: OnOutput | _Unset | None = _UNSET,
|
|
196
196
|
):
|
|
197
197
|
"""Temporarily override policy axes, restoring all of them on exit.
|
|
198
198
|
|
|
@@ -199,5 +199,6 @@ def declare_units(
|
|
|
199
199
|
return wrapper
|
|
200
200
|
|
|
201
201
|
# @declare_units → func is the decorated function;
|
|
202
|
-
# @declare_units(on_missing=...) → func is None, return the parametrised
|
|
202
|
+
# @declare_units(on_missing=...) → func is None, return the parametrised
|
|
203
|
+
# decorator.
|
|
203
204
|
return decorate if func is None else decorate(func)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.11.17,<0.12.0"]
|
|
3
|
-
build-backend = "uv_build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "xarray-annotated"
|
|
7
|
-
version = "0.5.2"
|
|
8
|
-
description = "Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated"
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "jmarshrossney", email = "17361029+jmarshrossney@users.noreply.github.com" }
|
|
11
|
-
]
|
|
12
|
-
readme = "README.md"
|
|
13
|
-
license = "MIT"
|
|
14
|
-
license-files = ["LICENSE"]
|
|
15
|
-
requires-python = ">=3.12"
|
|
16
|
-
dependencies = [
|
|
17
|
-
"pandas>=2.2",
|
|
18
|
-
"pint>=0.25.3",
|
|
19
|
-
"pint-xarray>=0.6.1",
|
|
20
|
-
"xarray>=2026.2.0",
|
|
21
|
-
]
|
|
22
|
-
keywords = ["xarray", "pint", "units", "validation", "annotations"]
|
|
23
|
-
classifiers = [
|
|
24
|
-
"Development Status :: 3 - Alpha",
|
|
25
|
-
"Intended Audience :: Science/Research",
|
|
26
|
-
"Programming Language :: Python :: 3.12",
|
|
27
|
-
"Programming Language :: Python :: 3.13",
|
|
28
|
-
"Programming Language :: Python :: 3.14",
|
|
29
|
-
"Topic :: Scientific/Engineering",
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
[project.optional-dependencies]
|
|
33
|
-
cf = ["cf-xarray>=0.11.3"]
|
|
34
|
-
|
|
35
|
-
[project.urls]
|
|
36
|
-
Homepage = "https://jmarshrossney.github.io/xarray-annotated"
|
|
37
|
-
Repository = "https://github.com/jmarshrossney/xarray-annotated"
|
|
38
|
-
Documentation = "https://jmarshrossney.github.io/xarray-annotated"
|
|
39
|
-
|
|
40
|
-
[dependency-groups]
|
|
41
|
-
dev = [
|
|
42
|
-
"xarray-annotated[cf]",
|
|
43
|
-
"cftime>=1.6",
|
|
44
|
-
"coverage>=7.13.5",
|
|
45
|
-
"pyright>=1.1.409",
|
|
46
|
-
"pytest>=9.0.2",
|
|
47
|
-
"pytest-cov>=7.1.0",
|
|
48
|
-
"ruff>=0.15.4",
|
|
49
|
-
"rust-just>=1.46.0",
|
|
50
|
-
"pre-commit>=4.6.0",
|
|
51
|
-
{include-group = "docs"},
|
|
52
|
-
]
|
|
53
|
-
docs = [
|
|
54
|
-
"marimo>=0.23.11",
|
|
55
|
-
"marimo-md-export>=0.7.0",
|
|
56
|
-
"mkdocstrings-python>=2.0.5",
|
|
57
|
-
"zensical>=0.0.46",
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
[tool.pytest]
|
|
61
|
-
testpaths = ["tests"]
|
|
62
|
-
|
|
63
|
-
[tool.pyright]
|
|
64
|
-
venvPath = "."
|
|
65
|
-
venv = ".venv"
|
|
66
|
-
include = ["src/", "tests/"]
|
|
67
|
-
|
|
68
|
-
[tool.ruff]
|
|
69
|
-
line-length = 88
|
|
70
|
-
indent-width = 4
|
|
71
|
-
target-version = "py312"
|
|
72
|
-
|
|
73
|
-
[tool.ruff.lint.per-file-ignores]
|
|
74
|
-
# marimo threads `Annotated` in as a cell argument, so ruff can't resolve it to
|
|
75
|
-
# `typing.Annotated` and misreads the unit strings in the metadata as forward
|
|
76
|
-
# references (F821 "undefined name" / F722 "syntax error in forward annotation").
|
|
77
|
-
"examples/notebook.py" = ["F821", "F722"]
|
|
78
|
-
|
|
79
|
-
[tool.uv]
|
|
80
|
-
exclude-newer = "1 week"
|
|
81
|
-
exclude-newer-package = { marimo-md-export = false }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/temporal/_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|
{xarray_annotated-0.5.2 → xarray_annotated-0.6.0}/src/xarray_annotated/units/_annotations.py
RENAMED
|
File without changes
|
|
File without changes
|