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.
Files changed (37) hide show
  1. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/PKG-INFO +47 -37
  2. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/README.md +45 -35
  3. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/pyproject.toml +42 -4
  4. xarray_annotated-0.7.0/pyproject.toml.orig +144 -0
  5. xarray_annotated-0.7.0/src/xarray_annotated/__init__.py +60 -0
  6. xarray_annotated-0.7.0/src/xarray_annotated/_annotations.py +179 -0
  7. xarray_annotated-0.7.0/src/xarray_annotated/_config.py +100 -0
  8. xarray_annotated-0.7.0/src/xarray_annotated/_enforce.py +379 -0
  9. xarray_annotated-0.7.0/src/xarray_annotated/_reader.py +230 -0
  10. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/_writer.py +16 -17
  11. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/schema/__init__.py +8 -25
  12. xarray_annotated-0.7.0/src/xarray_annotated/schema/_annotations.py +127 -0
  13. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/schema/_check.py +57 -55
  14. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/temporal/__init__.py +8 -25
  15. xarray_annotated-0.7.0/src/xarray_annotated/temporal/_annotations.py +68 -0
  16. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/temporal/_check.py +154 -99
  17. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/units/__init__.py +3 -23
  18. xarray_annotated-0.7.0/src/xarray_annotated/units/_annotations.py +57 -0
  19. xarray_annotated-0.7.0/src/xarray_annotated/units/_check.py +490 -0
  20. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/src/xarray_annotated/units/_registry.py +9 -1
  21. xarray_annotated-0.5.2/pyproject.toml.orig +0 -81
  22. xarray_annotated-0.5.2/src/xarray_annotated/__init__.py +0 -50
  23. xarray_annotated-0.5.2/src/xarray_annotated/_annotations.py +0 -119
  24. xarray_annotated-0.5.2/src/xarray_annotated/_config.py +0 -101
  25. xarray_annotated-0.5.2/src/xarray_annotated/_reader.py +0 -110
  26. xarray_annotated-0.5.2/src/xarray_annotated/schema/_annotations.py +0 -254
  27. xarray_annotated-0.5.2/src/xarray_annotated/schema/_config.py +0 -147
  28. xarray_annotated-0.5.2/src/xarray_annotated/schema/_decorator.py +0 -124
  29. xarray_annotated-0.5.2/src/xarray_annotated/temporal/_annotations.py +0 -154
  30. xarray_annotated-0.5.2/src/xarray_annotated/temporal/_config.py +0 -182
  31. xarray_annotated-0.5.2/src/xarray_annotated/temporal/_decorator.py +0 -133
  32. xarray_annotated-0.5.2/src/xarray_annotated/units/_annotations.py +0 -169
  33. xarray_annotated-0.5.2/src/xarray_annotated/units/_check.py +0 -592
  34. xarray_annotated-0.5.2/src/xarray_annotated/units/_config.py +0 -237
  35. xarray_annotated-0.5.2/src/xarray_annotated/units/_decorator.py +0 -203
  36. {xarray_annotated-0.5.2 → xarray_annotated-0.7.0}/LICENSE +0 -0
  37. {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.5.2
4
- Summary: Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated
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
  [![Docs](https://github.com/jmarshrossney/xarray-annotated/actions/workflows/docs.yml/badge.svg)](https://jmarshrossney.github.io/xarray-annotated)
34
34
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/jmarshrossney/xarray-annotated/blob/main/LICENSE)
35
35
 
36
- `xarray-annotated` enables run-time validation of `xarray.DataArray` properties declared in function signatures via [`typing.Annotated`](https://docs.python.org/3/library/typing.html#typing.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
- The main idea is that annotations serve as a single source of truth for
38
+ - **structure**: dimensions, coordinates and dtype;
39
+ - **physical units**;
40
+ - **time frequency** and phase.
39
41
 
40
- - structural schema (dims, coords, dtype),
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
- from which
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
- - static checkers and run-time validation can derive expected properties,
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 (just decorators).
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
- from xarray_annotated.schema import declare_schema, Dims
105
- from xarray_annotated.units import declare_units, Unit
105
+ import xarray_annotated as xra
106
+ from xarray_annotated import Dims, Unit
106
107
 
107
- @declare_units
108
- @declare_schema
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
- return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
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 correct units attribute.
127
- The `hPa` input was converted to `Pa` before the body ran, and the result was stamped with the unit the signature promised.
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
- If we had handed the function an array whose units were _dimensionally_ wrong, or with an extra dimension, this would have failed gracefully:
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
- ## Contributing
153
+ And so would one with an extra dimension:
142
154
 
143
- ```sh
144
- uv sync # install the package + dev dependencies into .venv
145
- just # lint, typecheck, test, build docs
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
- See the `justfile` for individual targets (`just lint`, `just test`, `just test-cov`, ...).
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
- Contributions are welcome --- open an issue or pull request on GitHub.
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
  [![Docs](https://github.com/jmarshrossney/xarray-annotated/actions/workflows/docs.yml/badge.svg)](https://jmarshrossney.github.io/xarray-annotated)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/jmarshrossney/xarray-annotated/blob/main/LICENSE)
8
8
 
9
- `xarray-annotated` enables run-time validation of `xarray.DataArray` properties declared in function signatures via [`typing.Annotated`](https://docs.python.org/3/library/typing.html#typing.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
- The main idea is that annotations serve as a single source of truth for
11
+ - **structure**: dimensions, coordinates and dtype;
12
+ - **physical units**;
13
+ - **time frequency** and phase.
12
14
 
13
- - structural schema (dims, coords, dtype),
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
- from which
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
- - static checkers and run-time validation can derive expected properties,
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 (just decorators).
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
- from xarray_annotated.schema import declare_schema, Dims
78
- from xarray_annotated.units import declare_units, Unit
78
+ import xarray_annotated as xra
79
+ from xarray_annotated import Dims, Unit
79
80
 
80
- @declare_units
81
- @declare_schema
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
- return 44330.0 * (1.0 - (p / 101325.0) ** 0.1903)
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 correct units attribute.
100
- The `hPa` input was converted to `Pa` before the body ran, and the result was stamped with the unit the signature promised.
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
- If we had handed the function an array whose units were _dimensionally_ wrong, or with an extra dimension, this would have failed gracefully:
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
- ## Contributing
126
+ And so would one with an extra dimension:
115
127
 
116
- ```sh
117
- uv sync # install the package + dev dependencies into .venv
118
- just # lint, typecheck, test, build docs
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
- See the `justfile` for individual targets (`just lint`, `just test`, `just test-cov`, ...).
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
- Contributions are welcome --- open an issue or pull request on GitHub.
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.5.2"
8
- description = "Run-time validation of xarray DataArray properties (units, dims/coords/dtype) declared via typing.Annotated"
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
- "examples/notebook.py" = [
85
- "F821",
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
+ ]