xarray-annotated 0.5.2__tar.gz → 0.7.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.7.0}/PKG-INFO +47 -37
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/README.md +45 -35
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/pyproject.toml +42 -4
- xarray_annotated-0.7.0/pyproject.toml.orig +144 -0
- xarray_annotated-0.7.0/src/xarray_annotated/__init__.py +60 -0
- xarray_annotated-0.7.0/src/xarray_annotated/_annotations.py +179 -0
- xarray_annotated-0.7.0/src/xarray_annotated/_config.py +100 -0
- xarray_annotated-0.7.0/src/xarray_annotated/_enforce.py +379 -0
- xarray_annotated-0.7.0/src/xarray_annotated/_reader.py +230 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/_writer.py +16 -17
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/schema/__init__.py +8 -25
- xarray_annotated-0.7.0/src/xarray_annotated/schema/_annotations.py +127 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/schema/_check.py +57 -55
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/temporal/__init__.py +8 -25
- xarray_annotated-0.7.0/src/xarray_annotated/temporal/_annotations.py +68 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/temporal/_check.py +154 -99
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/units/__init__.py +3 -23
- xarray_annotated-0.7.0/src/xarray_annotated/units/_annotations.py +57 -0
- xarray_annotated-0.7.0/src/xarray_annotated/units/_check.py +490 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/units/_registry.py +9 -1
- xarray_annotated-0.5.2/pyproject.toml.orig +0 -81
- xarray_annotated-0.5.2/src/xarray_annotated/__init__.py +0 -50
- xarray_annotated-0.5.2/src/xarray_annotated/_annotations.py +0 -119
- xarray_annotated-0.5.2/src/xarray_annotated/_config.py +0 -101
- xarray_annotated-0.5.2/src/xarray_annotated/_reader.py +0 -110
- xarray_annotated-0.5.2/src/xarray_annotated/schema/_annotations.py +0 -254
- xarray_annotated-0.5.2/src/xarray_annotated/schema/_config.py +0 -147
- xarray_annotated-0.5.2/src/xarray_annotated/schema/_decorator.py +0 -124
- xarray_annotated-0.5.2/src/xarray_annotated/temporal/_annotations.py +0 -154
- xarray_annotated-0.5.2/src/xarray_annotated/temporal/_config.py +0 -182
- xarray_annotated-0.5.2/src/xarray_annotated/temporal/_decorator.py +0 -133
- xarray_annotated-0.5.2/src/xarray_annotated/units/_annotations.py +0 -169
- xarray_annotated-0.5.2/src/xarray_annotated/units/_check.py +0 -592
- xarray_annotated-0.5.2/src/xarray_annotated/units/_config.py +0 -237
- xarray_annotated-0.5.2/src/xarray_annotated/units/_decorator.py +0 -203
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/LICENSE +0 -0
- {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/py.typed +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xarray-annotated
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Run-time validation of xarray DataArray
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Run-time validation of xarray DataArray structure, units and frequency, declared via typing.Annotated
|
|
5
5
|
Keywords: xarray,pint,units,validation,annotations
|
|
6
6
|
Author: jmarshrossney
|
|
7
7
|
Author-email: jmarshrossney <17361029+jmarshrossney@users.noreply.github.com>
|
|
@@ -33,21 +33,19 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
[](https://jmarshrossney.github.io/xarray-annotated)
|
|
34
34
|
[](https://github.com/jmarshrossney/xarray-annotated/blob/main/LICENSE)
|
|
35
35
|
|
|
36
|
-
`xarray-annotated`
|
|
36
|
+
`xarray-annotated` checks `xarray.DataArray` arguments and return values against properties declared in the function signature with [`typing.Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated):
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
- **structure**: dimensions, coordinates and dtype;
|
|
39
|
+
- **physical units**;
|
|
40
|
+
- **time frequency** and phase.
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
- physical units,
|
|
42
|
-
- frequency (and phase) of a time axis,
|
|
42
|
+
Making signatures the single source of truth has several advantages: the same declaration
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
- can be used by automated checks and validation, and hence enforced at run time;
|
|
45
|
+
- appears in generated documentation without needing to be hand-written;
|
|
46
|
+
- provides rich context for AI coding agents.
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
- documentation can be generated automatically,
|
|
48
|
-
- automated tools (coding agents) receive steering.
|
|
49
|
-
|
|
50
|
-
For validating and automatically converting physical units we lean on the excellent [pint](https://pint.readthedocs.io/en/stable/) and [pint-xarray](https://pint-xarray.readthedocs.io).
|
|
48
|
+
For physical units we lean on the excellent [pint](https://pint.readthedocs.io/en/stable/) and [pint-xarray](https://pint-xarray.readthedocs.io).
|
|
51
49
|
([cf-xarray](https://cf-xarray.readthedocs.io) is an optional dependency for CF/UDUNITS unit strings.)
|
|
52
50
|
|
|
53
51
|
For full user documentation please visit **[https://jmarshrossney.github.io/xarray-annotated/](https://jmarshrossney.github.io/xarray-annotated/)**.
|
|
@@ -78,10 +76,12 @@ It assumes a unit (pascals) and says so in the docstring but doesn't check or en
|
|
|
78
76
|
```python
|
|
79
77
|
import xarray as xr
|
|
80
78
|
|
|
79
|
+
|
|
81
80
|
def altitude(p: xr.DataArray) -> xr.DataArray:
|
|
82
81
|
"""Barometric altitude. Assumes p is in Pa."""
|
|
83
82
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
84
83
|
|
|
84
|
+
|
|
85
85
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
86
86
|
altitude(p)
|
|
87
87
|
```
|
|
@@ -96,24 +96,37 @@ Attributes:
|
|
|
96
96
|
The input units are wrong (hPa vs Pa) but the onus is on the user to check, and in reality we all know that such bugs can go unnoticed for a terrifyingly long time.
|
|
97
97
|
The returned altitude is numerically wrong and carries a completely bogus hPa units in `attrs`.
|
|
98
98
|
|
|
99
|
-
Here we use `xarray-annotated` to declare expected properties in the signature and enforce them at run-time with minimal boilerplate (
|
|
99
|
+
Here we use `xarray-annotated` to declare expected properties in the signature and enforce them at run-time with minimal boilerplate (one decorator).
|
|
100
100
|
|
|
101
|
+
<!-- docs-test: raises ValueError -->
|
|
101
102
|
```python
|
|
102
103
|
from typing import Annotated
|
|
103
104
|
import xarray as xr
|
|
104
|
-
|
|
105
|
-
from xarray_annotated
|
|
105
|
+
import xarray_annotated as xra
|
|
106
|
+
from xarray_annotated import Dims, Unit
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
@
|
|
108
|
+
|
|
109
|
+
@xra.enforce
|
|
109
110
|
def altitude(
|
|
110
111
|
p: Annotated[xr.DataArray, Dims("time"), Unit("Pa")],
|
|
111
112
|
) -> Annotated[xr.DataArray, Dims("time"), Unit("m")]:
|
|
112
113
|
"""Barometric altitude."""
|
|
113
|
-
|
|
114
|
+
alt = 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
115
|
+
return alt.assign_attrs(units="m")
|
|
116
|
+
|
|
114
117
|
|
|
115
118
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
116
119
|
altitude(p)
|
|
120
|
+
# ValueError: [altitude] input 'p': unit 'hPa' differs from declared 'Pa'.
|
|
121
|
+
# Converting would change the values; if that is correct for this quantity,
|
|
122
|
+
# pass xra.units.convert(da, 'Pa').
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The wrong unit is now an error rather than a wrong answer.
|
|
126
|
+
Pressure is safe to convert, so we can do that explicitly:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
altitude(xra.units.convert(p, "Pa"))
|
|
117
130
|
```
|
|
118
131
|
```
|
|
119
132
|
<xarray.DataArray (time: 2)> Size: 16B
|
|
@@ -123,34 +136,31 @@ Attributes:
|
|
|
123
136
|
units: m
|
|
124
137
|
```
|
|
125
138
|
|
|
126
|
-
Now the outputs are physical and carry the
|
|
127
|
-
The
|
|
139
|
+
Now the outputs are physical and carry the unit the signature promised.
|
|
140
|
+
The body has to state that unit, because xarray would otherwise leave the `Pa` label on the result, and a label that disagrees with the declaration raises: it could just as easily mean a forgotten conversion.
|
|
141
|
+
Nothing is [converted automatically](https://jmarshrossney.github.io/xarray-annotated/guides/units.html#converting-an-input), because whether a conversion is correct depends on the quantity, and a compatible unit is not always the right one.
|
|
128
142
|
The docstring no longer needs to say "assumes Pa", because the signature does --- and unlike the docstring, it is actually enforced.
|
|
129
143
|
|
|
130
|
-
|
|
144
|
+
An array whose units were _dimensionally_ wrong would also have raised:
|
|
131
145
|
|
|
146
|
+
<!-- docs-test: raises DimensionalityError -->
|
|
132
147
|
```python
|
|
133
148
|
altitude(xr.DataArray([1.0, 2.0], dims=["time"], attrs={"units": "kg"}))
|
|
134
149
|
# DimensionalityError: Cannot convert from 'kilogram' ([mass])
|
|
135
150
|
# to 'pascal' ([mass] / [length] / [time] ** 2)
|
|
136
|
-
|
|
137
|
-
altitude(xr.DataArray([[1013.0, 1000.0]], dims=["run", "time"], attrs={"units": "hPa"}))
|
|
138
|
-
# SchemaError: [altitude] 'p' dims mismatch: expected ('time',) in any order, got ('run', 'time')
|
|
139
151
|
```
|
|
140
152
|
|
|
141
|
-
|
|
153
|
+
And so would one with an extra dimension:
|
|
142
154
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
<!-- docs-test: raises SchemaError -->
|
|
156
|
+
```python
|
|
157
|
+
altitude(
|
|
158
|
+
xr.DataArray([[101300.0, 100000.0]], dims=["run", "time"], attrs={"units": "Pa"})
|
|
159
|
+
)
|
|
160
|
+
# SchemaError: [altitude] 'p' dims mismatch: expected ('time',) in any order, got ('run', 'time')
|
|
146
161
|
```
|
|
147
162
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
A `.pre-commit-config.yaml` is included to run the same linting (ruff) and type-checking (pyright) steps on every commit. Install the hooks with:
|
|
151
|
-
|
|
152
|
-
```sh
|
|
153
|
-
uv run pre-commit install
|
|
154
|
-
```
|
|
163
|
+
## Contributing
|
|
155
164
|
|
|
156
|
-
|
|
165
|
+
Issues and pull requests are welcome.
|
|
166
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the package's scope and how to set up a development environment.
|
|
@@ -6,21 +6,19 @@
|
|
|
6
6
|
[](https://jmarshrossney.github.io/xarray-annotated)
|
|
7
7
|
[](https://github.com/jmarshrossney/xarray-annotated/blob/main/LICENSE)
|
|
8
8
|
|
|
9
|
-
`xarray-annotated`
|
|
9
|
+
`xarray-annotated` checks `xarray.DataArray` arguments and return values against properties declared in the function signature with [`typing.Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated):
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- **structure**: dimensions, coordinates and dtype;
|
|
12
|
+
- **physical units**;
|
|
13
|
+
- **time frequency** and phase.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
- physical units,
|
|
15
|
-
- frequency (and phase) of a time axis,
|
|
15
|
+
Making signatures the single source of truth has several advantages: the same declaration
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- can be used by automated checks and validation, and hence enforced at run time;
|
|
18
|
+
- appears in generated documentation without needing to be hand-written;
|
|
19
|
+
- provides rich context for AI coding agents.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
- documentation can be generated automatically,
|
|
21
|
-
- automated tools (coding agents) receive steering.
|
|
22
|
-
|
|
23
|
-
For validating and automatically converting physical units we lean on the excellent [pint](https://pint.readthedocs.io/en/stable/) and [pint-xarray](https://pint-xarray.readthedocs.io).
|
|
21
|
+
For physical units we lean on the excellent [pint](https://pint.readthedocs.io/en/stable/) and [pint-xarray](https://pint-xarray.readthedocs.io).
|
|
24
22
|
([cf-xarray](https://cf-xarray.readthedocs.io) is an optional dependency for CF/UDUNITS unit strings.)
|
|
25
23
|
|
|
26
24
|
For full user documentation please visit **[https://jmarshrossney.github.io/xarray-annotated/](https://jmarshrossney.github.io/xarray-annotated/)**.
|
|
@@ -51,10 +49,12 @@ It assumes a unit (pascals) and says so in the docstring but doesn't check or en
|
|
|
51
49
|
```python
|
|
52
50
|
import xarray as xr
|
|
53
51
|
|
|
52
|
+
|
|
54
53
|
def altitude(p: xr.DataArray) -> xr.DataArray:
|
|
55
54
|
"""Barometric altitude. Assumes p is in Pa."""
|
|
56
55
|
return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
57
56
|
|
|
57
|
+
|
|
58
58
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
59
59
|
altitude(p)
|
|
60
60
|
```
|
|
@@ -69,24 +69,37 @@ Attributes:
|
|
|
69
69
|
The input units are wrong (hPa vs Pa) but the onus is on the user to check, and in reality we all know that such bugs can go unnoticed for a terrifyingly long time.
|
|
70
70
|
The returned altitude is numerically wrong and carries a completely bogus hPa units in `attrs`.
|
|
71
71
|
|
|
72
|
-
Here we use `xarray-annotated` to declare expected properties in the signature and enforce them at run-time with minimal boilerplate (
|
|
72
|
+
Here we use `xarray-annotated` to declare expected properties in the signature and enforce them at run-time with minimal boilerplate (one decorator).
|
|
73
73
|
|
|
74
|
+
<!-- docs-test: raises ValueError -->
|
|
74
75
|
```python
|
|
75
76
|
from typing import Annotated
|
|
76
77
|
import xarray as xr
|
|
77
|
-
|
|
78
|
-
from xarray_annotated
|
|
78
|
+
import xarray_annotated as xra
|
|
79
|
+
from xarray_annotated import Dims, Unit
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
@
|
|
81
|
+
|
|
82
|
+
@xra.enforce
|
|
82
83
|
def altitude(
|
|
83
84
|
p: Annotated[xr.DataArray, Dims("time"), Unit("Pa")],
|
|
84
85
|
) -> Annotated[xr.DataArray, Dims("time"), Unit("m")]:
|
|
85
86
|
"""Barometric altitude."""
|
|
86
|
-
|
|
87
|
+
alt = 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
|
|
88
|
+
return alt.assign_attrs(units="m")
|
|
89
|
+
|
|
87
90
|
|
|
88
91
|
p = xr.DataArray([1013.0, 1000.0], dims=["time"], attrs={"units": "hPa"})
|
|
89
92
|
altitude(p)
|
|
93
|
+
# ValueError: [altitude] input 'p': unit 'hPa' differs from declared 'Pa'.
|
|
94
|
+
# Converting would change the values; if that is correct for this quantity,
|
|
95
|
+
# pass xra.units.convert(da, 'Pa').
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The wrong unit is now an error rather than a wrong answer.
|
|
99
|
+
Pressure is safe to convert, so we can do that explicitly:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
altitude(xra.units.convert(p, "Pa"))
|
|
90
103
|
```
|
|
91
104
|
```
|
|
92
105
|
<xarray.DataArray (time: 2)> Size: 16B
|
|
@@ -96,34 +109,31 @@ Attributes:
|
|
|
96
109
|
units: m
|
|
97
110
|
```
|
|
98
111
|
|
|
99
|
-
Now the outputs are physical and carry the
|
|
100
|
-
The
|
|
112
|
+
Now the outputs are physical and carry the unit the signature promised.
|
|
113
|
+
The body has to state that unit, because xarray would otherwise leave the `Pa` label on the result, and a label that disagrees with the declaration raises: it could just as easily mean a forgotten conversion.
|
|
114
|
+
Nothing is [converted automatically](https://jmarshrossney.github.io/xarray-annotated/guides/units.html#converting-an-input), because whether a conversion is correct depends on the quantity, and a compatible unit is not always the right one.
|
|
101
115
|
The docstring no longer needs to say "assumes Pa", because the signature does --- and unlike the docstring, it is actually enforced.
|
|
102
116
|
|
|
103
|
-
|
|
117
|
+
An array whose units were _dimensionally_ wrong would also have raised:
|
|
104
118
|
|
|
119
|
+
<!-- docs-test: raises DimensionalityError -->
|
|
105
120
|
```python
|
|
106
121
|
altitude(xr.DataArray([1.0, 2.0], dims=["time"], attrs={"units": "kg"}))
|
|
107
122
|
# DimensionalityError: Cannot convert from 'kilogram' ([mass])
|
|
108
123
|
# to 'pascal' ([mass] / [length] / [time] ** 2)
|
|
109
|
-
|
|
110
|
-
altitude(xr.DataArray([[1013.0, 1000.0]], dims=["run", "time"], attrs={"units": "hPa"}))
|
|
111
|
-
# SchemaError: [altitude] 'p' dims mismatch: expected ('time',) in any order, got ('run', 'time')
|
|
112
124
|
```
|
|
113
125
|
|
|
114
|
-
|
|
126
|
+
And so would one with an extra dimension:
|
|
115
127
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
<!-- docs-test: raises SchemaError -->
|
|
129
|
+
```python
|
|
130
|
+
altitude(
|
|
131
|
+
xr.DataArray([[101300.0, 100000.0]], dims=["run", "time"], attrs={"units": "Pa"})
|
|
132
|
+
)
|
|
133
|
+
# SchemaError: [altitude] 'p' dims mismatch: expected ('time',) in any order, got ('run', 'time')
|
|
119
134
|
```
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
A `.pre-commit-config.yaml` is included to run the same linting (ruff) and type-checking (pyright) steps on every commit. Install the hooks with:
|
|
124
|
-
|
|
125
|
-
```sh
|
|
126
|
-
uv run pre-commit install
|
|
127
|
-
```
|
|
136
|
+
## Contributing
|
|
128
137
|
|
|
129
|
-
|
|
138
|
+
Issues and pull requests are welcome.
|
|
139
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the package's scope and how to set up a development environment.
|
|
@@ -4,8 +4,8 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xarray-annotated"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Run-time validation of xarray DataArray
|
|
7
|
+
version = "0.7.0"
|
|
8
|
+
description = "Run-time validation of xarray DataArray structure, units and frequency, declared via typing.Annotated"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
@@ -56,6 +56,13 @@ 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
|
+
"dask>=2026.8.0",
|
|
64
|
+
"jaxtyping>=0.3.11",
|
|
65
|
+
"netcdf4>=1.7.5.1.post1",
|
|
59
66
|
]
|
|
60
67
|
docs = [
|
|
61
68
|
"marimo>=0.23.11",
|
|
@@ -80,12 +87,43 @@ line-length = 88
|
|
|
80
87
|
indent-width = 4
|
|
81
88
|
target-version = "py312"
|
|
82
89
|
|
|
90
|
+
[tool.ruff.lint]
|
|
91
|
+
select = [
|
|
92
|
+
"E",
|
|
93
|
+
"D",
|
|
94
|
+
"W",
|
|
95
|
+
"F",
|
|
96
|
+
"UP",
|
|
97
|
+
"B",
|
|
98
|
+
"SIM",
|
|
99
|
+
"RUF",
|
|
100
|
+
"PT",
|
|
101
|
+
"I",
|
|
102
|
+
]
|
|
103
|
+
|
|
83
104
|
[tool.ruff.lint.per-file-ignores]
|
|
84
|
-
"
|
|
85
|
-
"
|
|
105
|
+
"tests/**" = [
|
|
106
|
+
"B008",
|
|
107
|
+
"D",
|
|
108
|
+
"E501",
|
|
109
|
+
]
|
|
110
|
+
"examples/**" = [
|
|
111
|
+
"B018",
|
|
112
|
+
"D100",
|
|
113
|
+
"D103",
|
|
114
|
+
"E501",
|
|
86
115
|
"F722",
|
|
116
|
+
"F821",
|
|
117
|
+
"F841",
|
|
118
|
+
"RUF001",
|
|
119
|
+
"RUF003",
|
|
120
|
+
"PLR1711",
|
|
121
|
+
"PLC0415",
|
|
87
122
|
]
|
|
88
123
|
|
|
124
|
+
[tool.ruff.lint.pydocstyle]
|
|
125
|
+
convention = "google"
|
|
126
|
+
|
|
89
127
|
[tool.uv]
|
|
90
128
|
exclude-newer = "1 week"
|
|
91
129
|
|
|
@@ -0,0 +1,144 @@
|
|
|
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.7.0"
|
|
8
|
+
description = "Run-time validation of xarray DataArray structure, units and frequency, 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`, `test_dask.py` and `test_netcdf.py` skip themselves when
|
|
59
|
+
# they are absent.
|
|
60
|
+
integrations = [
|
|
61
|
+
"beartype>=0.22.9",
|
|
62
|
+
"dask>=2026.8.0",
|
|
63
|
+
"jaxtyping>=0.3.11",
|
|
64
|
+
"netcdf4>=1.7.5.1.post1",
|
|
65
|
+
]
|
|
66
|
+
docs = [
|
|
67
|
+
"marimo>=0.23.11",
|
|
68
|
+
"marimo-md-export>=0.7.0",
|
|
69
|
+
"mkdocstrings-python>=2.0.5",
|
|
70
|
+
"zensical>=0.0.46",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.pytest]
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
[tool.pyright]
|
|
77
|
+
venvPath = "."
|
|
78
|
+
venv = ".venv"
|
|
79
|
+
include = ["src/", "tests/"]
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 88
|
|
83
|
+
indent-width = 4
|
|
84
|
+
target-version = "py312"
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
# Ruff broadened its default rule set in 0.16, so always select explicitly:
|
|
88
|
+
# that way a version bump cannot change which rules apply.
|
|
89
|
+
select = [
|
|
90
|
+
"E", # pycodestyle errors
|
|
91
|
+
"D", # pydocstyle
|
|
92
|
+
"W", # pycodestyle warnings
|
|
93
|
+
"F", # pyflakes
|
|
94
|
+
"UP", # pyupgrade
|
|
95
|
+
"B", # flake8-bugbear
|
|
96
|
+
"SIM", # flake8-simplify
|
|
97
|
+
"RUF", # ruff-specific rules
|
|
98
|
+
"PT", # flake8-pytest-style
|
|
99
|
+
"I", # isort
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.lint.per-file-ignores]
|
|
103
|
+
# Exempt tests from docstring requirements. B008 (function call in an argument
|
|
104
|
+
# default) is exempted because several tests exist precisely to check that a
|
|
105
|
+
# parameter left at its default is still converted/validated -- the call in the
|
|
106
|
+
# default is the fixture under test, not an oversight.
|
|
107
|
+
"tests/**" = ["B008", "D", "E501"]
|
|
108
|
+
# marimo threads `Annotated` in as a cell argument, so ruff can't resolve it to
|
|
109
|
+
# `typing.Annotated` and misreads the unit strings in the metadata as forward
|
|
110
|
+
# references (F821 "undefined name" / F722 "syntax error in forward annotation").
|
|
111
|
+
# The rest are generic to marimo notebooks, which are generated dataflow
|
|
112
|
+
# descriptions rather than hand-written modules:
|
|
113
|
+
# B018 a bare trailing expression IS the cell's output mechanism.
|
|
114
|
+
# E501 long prose/URLs inside mo.md() cannot be rewrapped by ruff format,
|
|
115
|
+
# and a `# noqa` after the closing quotes flip-flops forever
|
|
116
|
+
# (marimo strips the two spaces, ruff format restores them).
|
|
117
|
+
# D100 notebooks are not modules and have no module docstring.
|
|
118
|
+
# D103 cell functions are machine-generated and undocumented.
|
|
119
|
+
# F841 exploratory cells leave unused variables around.
|
|
120
|
+
# RUF001 prose contains en dashes; RUF003 contains Greek letters.
|
|
121
|
+
"examples/**" = [
|
|
122
|
+
"B018",
|
|
123
|
+
"D100",
|
|
124
|
+
"D103",
|
|
125
|
+
"E501",
|
|
126
|
+
"F722",
|
|
127
|
+
"F821",
|
|
128
|
+
"F841",
|
|
129
|
+
"RUF001",
|
|
130
|
+
"RUF003",
|
|
131
|
+
# Pre-emptive, and only active if "PL" is ever added to select above:
|
|
132
|
+
# PLR1711 ruff deletes the bare `return` that ends every cell and
|
|
133
|
+
# `marimo check --fix` re-adds it -- an infinite flip-flop.
|
|
134
|
+
# PLC0415 notebook imports live inside cells by design.
|
|
135
|
+
"PLR1711",
|
|
136
|
+
"PLC0415",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
[tool.ruff.lint.pydocstyle]
|
|
140
|
+
convention = "google"
|
|
141
|
+
|
|
142
|
+
[tool.uv]
|
|
143
|
+
exclude-newer = "1 week"
|
|
144
|
+
exclude-newer-package = { marimo-md-export = false }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""xarray-annotated: validate DataArray properties declared via ``typing.Annotated``.
|
|
2
|
+
|
|
3
|
+
Annotation is the unifying technology: a declared property is read off an
|
|
4
|
+
``Annotated[DataArray, ...]`` hint and enforced at runtime. Each kind of
|
|
5
|
+
property is a domain subpackage, imported explicitly:
|
|
6
|
+
|
|
7
|
+
* ``xarray_annotated.units`` — physical units (pint / CF).
|
|
8
|
+
* ``xarray_annotated.schema`` — structural properties (dims, coords, dtype);
|
|
9
|
+
validate-only (never mutates).
|
|
10
|
+
* ``xarray_annotated.temporal`` — the frequency (and phase) of a time axis;
|
|
11
|
+
validate-only.
|
|
12
|
+
|
|
13
|
+
One decorator, ``enforce``, applies every facet declared on a signature, units first:
|
|
14
|
+
|
|
15
|
+
import xarray_annotated as xra
|
|
16
|
+
from xarray_annotated import Dims, Freq, Unit
|
|
17
|
+
|
|
18
|
+
@xra.enforce
|
|
19
|
+
def f(p: Annotated[xr.DataArray, Unit("Pa"), Dims("time"), Freq("D")]): ...
|
|
20
|
+
|
|
21
|
+
The top level is deliberately thin — the domains' checker names stay in their
|
|
22
|
+
subpackages. The exceptions are the markers (``Coords``, ``Dims``, ``Dtype``,
|
|
23
|
+
``Freq``, ``Unit``), which are unique across domains and are combined freely in one
|
|
24
|
+
hint, and the domain-*agnostic*
|
|
25
|
+
names: ``enforce``; the ``enabled`` switch; the shared
|
|
26
|
+
declaration *writer* ``annotate`` and its inverse *readers*
|
|
27
|
+
``declaration_from_hint`` and ``declarations_from_signature`` (which read every facet
|
|
28
|
+
declared on a hint or a signature into one uniform ``Declared`` value).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from importlib.metadata import version as _version
|
|
32
|
+
|
|
33
|
+
from . import schema, temporal, units
|
|
34
|
+
from ._config import enabled
|
|
35
|
+
from ._enforce import enforce
|
|
36
|
+
from ._reader import Declared, declaration_from_hint, declarations_from_signature
|
|
37
|
+
from ._writer import annotate
|
|
38
|
+
from .schema import Coords, Dims, Dtype
|
|
39
|
+
from .temporal import Freq
|
|
40
|
+
from .units import Unit
|
|
41
|
+
|
|
42
|
+
__version__ = _version("xarray-annotated")
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"Coords",
|
|
46
|
+
"Declared",
|
|
47
|
+
"Dims",
|
|
48
|
+
"Dtype",
|
|
49
|
+
"Freq",
|
|
50
|
+
"Unit",
|
|
51
|
+
"__version__",
|
|
52
|
+
"annotate",
|
|
53
|
+
"declaration_from_hint",
|
|
54
|
+
"declarations_from_signature",
|
|
55
|
+
"enabled",
|
|
56
|
+
"enforce",
|
|
57
|
+
"schema",
|
|
58
|
+
"temporal",
|
|
59
|
+
"units",
|
|
60
|
+
]
|