xarray-annotated 0.5.2__py3-none-any.whl → 0.7.0__py3-none-any.whl
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/__init__.py +33 -23
- xarray_annotated/_annotations.py +104 -44
- xarray_annotated/_config.py +71 -72
- xarray_annotated/_enforce.py +379 -0
- xarray_annotated/_reader.py +173 -53
- xarray_annotated/_writer.py +16 -17
- xarray_annotated/schema/__init__.py +8 -25
- xarray_annotated/schema/_annotations.py +30 -157
- xarray_annotated/schema/_check.py +57 -55
- xarray_annotated/temporal/__init__.py +8 -25
- xarray_annotated/temporal/_annotations.py +11 -97
- xarray_annotated/temporal/_check.py +154 -99
- xarray_annotated/units/__init__.py +3 -23
- xarray_annotated/units/_annotations.py +16 -128
- xarray_annotated/units/_check.py +279 -381
- xarray_annotated/units/_registry.py +9 -1
- {xarray_annotated-0.5.2.dist-info → xarray_annotated-0.7.0.dist-info}/METADATA +47 -37
- xarray_annotated-0.7.0.dist-info/RECORD +21 -0
- {xarray_annotated-0.5.2.dist-info → xarray_annotated-0.7.0.dist-info}/WHEEL +1 -1
- xarray_annotated/schema/_config.py +0 -147
- xarray_annotated/schema/_decorator.py +0 -124
- xarray_annotated/temporal/_config.py +0 -182
- xarray_annotated/temporal/_decorator.py +0 -133
- xarray_annotated/units/_config.py +0 -237
- xarray_annotated/units/_decorator.py +0 -203
- xarray_annotated-0.5.2.dist-info/RECORD +0 -26
- {xarray_annotated-0.5.2.dist-info → xarray_annotated-0.7.0.dist-info}/licenses/LICENSE +0 -0
xarray_annotated/__init__.py
CHANGED
|
@@ -1,50 +1,60 @@
|
|
|
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
|
-
``Annotated[DataArray, ...]`` hint and
|
|
4
|
+
``Annotated[DataArray, ...]`` hint and enforced at runtime. Each kind of
|
|
5
5
|
property is a domain subpackage, imported explicitly:
|
|
6
6
|
|
|
7
|
-
* ``xarray_annotated.units`` — physical units (pint / CF)
|
|
7
|
+
* ``xarray_annotated.units`` — physical units (pint / CF).
|
|
8
8
|
* ``xarray_annotated.schema`` — structural properties (dims, coords, dtype);
|
|
9
9
|
validate-only (never mutates).
|
|
10
10
|
* ``xarray_annotated.temporal`` — the frequency (and phase) of a time axis;
|
|
11
11
|
validate-only.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from xarray_annotated import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
The
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
``
|
|
27
|
-
|
|
28
|
-
|
|
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
29
|
"""
|
|
30
30
|
|
|
31
31
|
from importlib.metadata import version as _version
|
|
32
32
|
|
|
33
33
|
from . import schema, temporal, units
|
|
34
|
-
from .
|
|
35
|
-
from .
|
|
34
|
+
from ._config import enabled
|
|
35
|
+
from ._enforce import enforce
|
|
36
|
+
from ._reader import Declared, declaration_from_hint, declarations_from_signature
|
|
36
37
|
from ._writer import annotate
|
|
38
|
+
from .schema import Coords, Dims, Dtype
|
|
39
|
+
from .temporal import Freq
|
|
40
|
+
from .units import Unit
|
|
37
41
|
|
|
38
42
|
__version__ = _version("xarray-annotated")
|
|
39
43
|
|
|
40
44
|
__all__ = [
|
|
45
|
+
"Coords",
|
|
41
46
|
"Declared",
|
|
47
|
+
"Dims",
|
|
48
|
+
"Dtype",
|
|
49
|
+
"Freq",
|
|
50
|
+
"Unit",
|
|
42
51
|
"__version__",
|
|
43
52
|
"annotate",
|
|
53
|
+
"declaration_from_hint",
|
|
44
54
|
"declarations_from_signature",
|
|
55
|
+
"enabled",
|
|
56
|
+
"enforce",
|
|
45
57
|
"schema",
|
|
46
58
|
"temporal",
|
|
47
59
|
"units",
|
|
48
|
-
"unwrap_annotated",
|
|
49
|
-
"walk_signature",
|
|
50
60
|
]
|
xarray_annotated/_annotations.py
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
"""Shared `typing.Annotated` introspection for xarray-annotated.
|
|
2
2
|
|
|
3
3
|
Pure `typing` machinery with no dependency on any domain's registry or config —
|
|
4
|
-
the "declaration side" common to every domain.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return shapes once, for everyone.
|
|
4
|
+
the "declaration side" common to every domain. An *extractor* turns one
|
|
5
|
+
`Annotated[...]` hint into a declaration payload;
|
|
6
|
+
`_walk_signature` drives that extractor over a whole function signature, handling
|
|
7
|
+
the `TypedDict` / dataclass / single-value return shapes once, for everyone.
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
10
|
import dataclasses
|
|
12
11
|
import types
|
|
13
|
-
from collections.abc import Callable
|
|
12
|
+
from collections.abc import Callable, Iterable
|
|
14
13
|
from typing import (
|
|
15
14
|
Annotated,
|
|
16
15
|
Any,
|
|
17
|
-
|
|
16
|
+
TypeAliasType,
|
|
18
17
|
Union,
|
|
19
18
|
get_args,
|
|
20
19
|
get_origin,
|
|
@@ -24,96 +23,157 @@ from typing import (
|
|
|
24
23
|
|
|
25
24
|
import xarray as xr
|
|
26
25
|
|
|
27
|
-
_T = TypeVar("_T")
|
|
28
26
|
|
|
27
|
+
def _single_marker[M](metadata: Iterable[Any], marker_type: type[M]) -> M | None:
|
|
28
|
+
"""Return the one `marker_type` marker in `metadata`, or `None` if absent.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
""
|
|
30
|
+
A hint declares each facet at most once. Two markers of one type are either
|
|
31
|
+
contradictory (`Dims("x")` with `Dims("y")`) or redundant, so a second one is a
|
|
32
|
+
malformed declaration rather than something to resolve by position.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
metadata that declarations attach to parameters.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
hint: A type hint, possibly `Annotated`.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
The base type if `hint` is `Annotated`; otherwise `hint` unchanged.
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: If `metadata` holds more than one `marker_type` marker.
|
|
42
36
|
"""
|
|
43
|
-
|
|
37
|
+
found = [m for m in metadata if isinstance(m, marker_type)]
|
|
38
|
+
if len(found) > 1:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"one Annotated hint declares {len(found)} {marker_type.__name__} "
|
|
41
|
+
f"markers ({', '.join(map(repr, found))}); declare at most one"
|
|
42
|
+
)
|
|
43
|
+
return found[0] if found else None
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _is_dataarray_type(tp: Any) -> bool:
|
|
47
|
-
"""Return whether `tp` is `DataArray` (possibly wrapped in a Union).
|
|
47
|
+
"""Return whether `tp` is `DataArray` (possibly wrapped or in a Union).
|
|
48
48
|
|
|
49
49
|
Accepts a bare `DataArray` as well as `DataArray | None` /
|
|
50
50
|
`Optional[DataArray]` (and any other union that includes `DataArray`), so an
|
|
51
51
|
optional DataArray parameter can still carry a declaration. Anything else
|
|
52
52
|
(scalars, `str`, `bool`, `Dataset`, …) is not a declaration-bearing type.
|
|
53
53
|
|
|
54
|
+
Also accepts any *array-type wrapper* that exposes the underlying array class
|
|
55
|
+
as an `array_type` attribute — notably jaxtyping's `Float[DataArray, "t x"]`,
|
|
56
|
+
which occupies the base-type slot of an `Annotated` hint rather than competing
|
|
57
|
+
with it. This is a duck-typed check: jaxtyping is never imported, and any
|
|
58
|
+
other library following the same convention composes for free. See
|
|
59
|
+
`docs/guides/jaxtyping.md`.
|
|
60
|
+
|
|
54
61
|
Args:
|
|
55
62
|
tp: A type annotation to inspect.
|
|
56
63
|
|
|
57
64
|
Returns:
|
|
58
|
-
`True` if `tp` is or contains `DataArray`.
|
|
65
|
+
`True` if `tp` is, wraps, or contains `DataArray`.
|
|
59
66
|
"""
|
|
60
67
|
if tp is xr.DataArray:
|
|
61
68
|
return True
|
|
69
|
+
inner = getattr(tp, "array_type", None)
|
|
70
|
+
if isinstance(inner, type) and issubclass(inner, xr.DataArray):
|
|
71
|
+
return True
|
|
62
72
|
if get_origin(tp) in (Union, types.UnionType):
|
|
63
73
|
return any(_is_dataarray_type(arg) for arg in get_args(tp))
|
|
64
74
|
return False
|
|
65
75
|
|
|
66
76
|
|
|
67
|
-
def
|
|
77
|
+
def _strip_return_hint(
|
|
78
|
+
hint: Any, path: frozenset[Any] = frozenset()
|
|
79
|
+
) -> tuple[Any, bool]:
|
|
80
|
+
"""Strip `type` aliases, `Annotated` and `| None` off a return hint.
|
|
81
|
+
|
|
82
|
+
An alias that refers to itself is left in place: it resolves to nothing. The reader
|
|
83
|
+
raises on one, except a generic one, as in `type G[T] = G[T] | None`, whose `G[int]`
|
|
84
|
+
it does not enter.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
What remains, and whether a `| None` was stripped on the way to it.
|
|
88
|
+
"""
|
|
89
|
+
origin = get_origin(hint)
|
|
90
|
+
alias = next((a for a in (hint, origin) if isinstance(a, TypeAliasType)), None)
|
|
91
|
+
if alias is not None:
|
|
92
|
+
if alias in path:
|
|
93
|
+
return hint, False
|
|
94
|
+
return _strip_return_hint(alias.__value__, path | {alias})
|
|
95
|
+
if origin is Annotated:
|
|
96
|
+
return _strip_return_hint(get_args(hint)[0], path)
|
|
97
|
+
if origin in (Union, types.UnionType):
|
|
98
|
+
members = [arg for arg in get_args(hint) if arg is not types.NoneType]
|
|
99
|
+
if len(members) == 1:
|
|
100
|
+
return _strip_return_hint(members[0], path)[0], True
|
|
101
|
+
return hint, False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _record_return(hint: Any) -> tuple[Any, bool]:
|
|
105
|
+
"""Read a return hint's `TypedDict` or dataclass, and whether it may be `None`.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
The class, or `None` if the hint resolves to no such class, and whether the
|
|
109
|
+
hint allows `None`.
|
|
110
|
+
"""
|
|
111
|
+
base, optional = _strip_return_hint(hint)
|
|
112
|
+
record = get_origin(base) or base
|
|
113
|
+
is_record = is_typeddict(record) or dataclasses.is_dataclass(record)
|
|
114
|
+
return (record if isinstance(record, type) and is_record else None), optional
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _walk_signature[T](
|
|
68
118
|
func: object,
|
|
69
|
-
extract: Callable[[Any],
|
|
70
|
-
) -> tuple[dict[str,
|
|
119
|
+
extract: Callable[[Any], T | None],
|
|
120
|
+
) -> tuple[dict[str, T], dict[str, T] | T | None]:
|
|
71
121
|
"""Extract per-parameter and return declarations from a function's hints.
|
|
72
122
|
|
|
73
123
|
Reads `get_type_hints(func, include_extras=True)` and maps each hint through
|
|
74
|
-
`extract`, which returns a
|
|
75
|
-
|
|
76
|
-
|
|
124
|
+
`extract`, which returns a declaration payload for that hint or `None` when
|
|
125
|
+
the hint carries no declaration. `extract` also sees a whole return hint before
|
|
126
|
+
any of its fields, so it can refuse what it finds there.
|
|
77
127
|
|
|
78
128
|
Args:
|
|
79
129
|
func: A callable whose parameters/return may carry `Annotated` metadata.
|
|
80
|
-
extract: Turns one hint into a declaration payload, or `None`. For
|
|
81
|
-
this is `
|
|
82
|
-
|
|
130
|
+
extract: Turns one hint into a declaration payload, or `None`. For every
|
|
131
|
+
built-in facet at once this is `declaration_from_hint` (payload
|
|
132
|
+
`Declared`).
|
|
83
133
|
|
|
84
134
|
Returns:
|
|
85
135
|
An `(inputs, output)` pair:
|
|
86
136
|
|
|
87
137
|
* `inputs` — `dict[str, T]` mapping each declared parameter to its payload.
|
|
88
138
|
* `output` — a `dict[str, T]` (per-field) if the return hint is a
|
|
89
|
-
`TypedDict` or dataclass
|
|
90
|
-
`
|
|
139
|
+
`TypedDict` or dataclass, read through a `type` alias, `Annotated`,
|
|
140
|
+
`| None` and generic parameters; a single `T` if the return is one
|
|
141
|
+
declared `Annotated[DataArray, ...]`; `None` otherwise.
|
|
91
142
|
"""
|
|
143
|
+
qualname = getattr(func, "__qualname__", repr(func))
|
|
144
|
+
|
|
145
|
+
def _extract(hint: Any, where: str) -> T | None:
|
|
146
|
+
try:
|
|
147
|
+
return extract(hint)
|
|
148
|
+
except ValueError as exc:
|
|
149
|
+
exc.add_note(f"in {qualname} {where}")
|
|
150
|
+
raise
|
|
151
|
+
|
|
92
152
|
hints = get_type_hints(func, include_extras=True)
|
|
93
153
|
ret = hints.pop("return", None)
|
|
94
154
|
|
|
95
155
|
inputs = {
|
|
96
156
|
name: payload
|
|
97
157
|
for name, hint in hints.items()
|
|
98
|
-
if (payload :=
|
|
158
|
+
if (payload := _extract(hint, f"input {name!r}")) is not None
|
|
99
159
|
}
|
|
100
160
|
|
|
101
|
-
output: dict[str,
|
|
102
|
-
|
|
103
|
-
|
|
161
|
+
output: dict[str, T] | T | None = _extract(ret, "output")
|
|
162
|
+
record, _ = _record_return(ret)
|
|
163
|
+
if is_typeddict(record):
|
|
164
|
+
ret_hints = get_type_hints(record, include_extras=True)
|
|
104
165
|
output = {
|
|
105
166
|
name: payload
|
|
106
167
|
for name, hint in ret_hints.items()
|
|
107
|
-
if (payload :=
|
|
168
|
+
if (payload := _extract(hint, f"output {name!r}")) is not None
|
|
108
169
|
}
|
|
109
|
-
elif
|
|
110
|
-
ret_hints = get_type_hints(
|
|
170
|
+
elif record is not None:
|
|
171
|
+
ret_hints = get_type_hints(record, include_extras=True)
|
|
111
172
|
output = {
|
|
112
173
|
f.name: payload
|
|
113
|
-
for f in dataclasses.fields(
|
|
114
|
-
if (payload :=
|
|
174
|
+
for f in dataclasses.fields(record)
|
|
175
|
+
if (payload := _extract(ret_hints.get(f.name), f"output {f.name!r}"))
|
|
176
|
+
is not None
|
|
115
177
|
}
|
|
116
|
-
else:
|
|
117
|
-
output = extract(ret)
|
|
118
178
|
|
|
119
179
|
return inputs, output
|
xarray_annotated/_config.py
CHANGED
|
@@ -1,66 +1,33 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- `_Unset` / `_UNSET` — the sentinel that lets a `set_policy` keyword distinguish
|
|
13
|
-
"argument omitted" from "explicitly set to `None`" (which *clears* an override).
|
|
14
|
-
|
|
15
|
-
Domain-specific axes (units' `on_missing` / `on_inexact`, schema's `on_mismatch`)
|
|
16
|
-
stay in the respective domain's `_config.py`; only the genuinely shared pieces
|
|
17
|
-
live here.
|
|
1
|
+
"""The package-wide master switch, the only global that changes how checks behave.
|
|
2
|
+
|
|
3
|
+
Behaviour is never configured globally, so that how a function validates can be read
|
|
4
|
+
off its signature. What remains global is whether xarray-annotated does anything at
|
|
5
|
+
all. The unit registry is process-wide too, but it decides which units parse, rather
|
|
6
|
+
than what a check does with them.
|
|
7
|
+
|
|
8
|
+
The switch resolves on every call as: an `enabled(...)` block, then the
|
|
9
|
+
`XARRAY_ANNOTATED_ENABLED` environment variable, then on. Code beats the
|
|
10
|
+
environment in both directions, so `enabled(True)` restores checking in a process
|
|
11
|
+
started with the variable set to false.
|
|
18
12
|
"""
|
|
19
13
|
|
|
20
14
|
import os
|
|
21
|
-
from collections.abc import
|
|
22
|
-
from
|
|
15
|
+
from collections.abc import Generator
|
|
16
|
+
from contextlib import contextmanager
|
|
17
|
+
from contextvars import ContextVar
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
DEFAULT_ENABLED: bool = True
|
|
21
|
+
__all__ = ["ENABLED_ENV_VAR", "enabled", "resolve_enabled"]
|
|
26
22
|
|
|
27
|
-
#: Master switch is package-wide (gates every annotation-validation domain), so it
|
|
28
|
-
#: is deliberately un-namespaced; each domain's behavioural knobs are namespaced.
|
|
29
23
|
ENABLED_ENV_VAR = "XARRAY_ANNOTATED_ENABLED"
|
|
30
24
|
|
|
31
|
-
#: String values (lower-cased) accepted for the boolean `enabled` env var.
|
|
32
25
|
_TRUTHY: frozenset[str] = frozenset({"1", "true", "yes", "on"})
|
|
33
26
|
_FALSEY: frozenset[str] = frozenset({"0", "false", "no", "off"})
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_T = TypeVar("_T")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class _Unset:
|
|
42
|
-
"""Sentinel distinguishing 'argument omitted' from 'explicitly set to None'."""
|
|
43
|
-
|
|
44
|
-
def __repr__(self) -> str:
|
|
45
|
-
return "<unset>"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
_UNSET = _Unset()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _resolve(
|
|
52
|
-
env_var: str,
|
|
53
|
-
process_value: _T | None,
|
|
54
|
-
default: _T,
|
|
55
|
-
parse: Callable[[str], _T],
|
|
56
|
-
) -> _T:
|
|
57
|
-
"""Resolve one axis: env var (parsed) → process override → default."""
|
|
58
|
-
env = os.environ.get(env_var)
|
|
59
|
-
if env:
|
|
60
|
-
return parse(env)
|
|
61
|
-
if process_value is not None:
|
|
62
|
-
return process_value
|
|
63
|
-
return default
|
|
28
|
+
_override: ContextVar[bool | None] = ContextVar(
|
|
29
|
+
"xarray_annotated_enabled", default=None
|
|
30
|
+
)
|
|
64
31
|
|
|
65
32
|
|
|
66
33
|
def _parse_enabled_env(value: str) -> bool:
|
|
@@ -76,26 +43,58 @@ def _parse_enabled_env(value: str) -> bool:
|
|
|
76
43
|
|
|
77
44
|
|
|
78
45
|
def resolve_enabled() -> bool:
|
|
79
|
-
"""
|
|
80
|
-
return _resolve(
|
|
81
|
-
ENABLED_ENV_VAR, _process_enabled, DEFAULT_ENABLED, _parse_enabled_env
|
|
82
|
-
)
|
|
83
|
-
|
|
46
|
+
"""Return whether xarray-annotated is active for this call.
|
|
84
47
|
|
|
85
|
-
|
|
86
|
-
|
|
48
|
+
Returns:
|
|
49
|
+
The innermost `enabled(...)` value if inside one, else the parsed
|
|
50
|
+
`XARRAY_ANNOTATED_ENABLED`, else `True`.
|
|
87
51
|
|
|
88
|
-
|
|
89
|
-
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If the environment variable holds an unrecognised value.
|
|
90
54
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
55
|
+
override = _override.get()
|
|
56
|
+
if override is not None:
|
|
57
|
+
return override
|
|
58
|
+
env = os.environ.get(ENABLED_ENV_VAR)
|
|
59
|
+
if env:
|
|
60
|
+
return _parse_enabled_env(env)
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@contextmanager
|
|
65
|
+
def enabled(value: bool | np.bool_) -> Generator[None]:
|
|
66
|
+
"""Switch xarray-annotated on or off for a block.
|
|
67
|
+
|
|
68
|
+
Off means no checks, so a body declaring `"Pa"` can receive `"hPa"`. Declarations
|
|
69
|
+
are still validated when a function is decorated.
|
|
70
|
+
|
|
71
|
+
The block beats `XARRAY_ANNOTATED_ENABLED` in both directions, and is
|
|
72
|
+
scoped to the current thread or async task. The variable accepts `1`,
|
|
73
|
+
`true`, `yes` or `on` and `0`, `false`, `no` or `off`, in any case; anything
|
|
74
|
+
else raises `ValueError` when a checked function is called.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
value: `False` to switch off, `True` to switch on.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
TypeError: If `value` is not a bool, as `bool("false")` would leave checks on.
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
>>> from typing import Annotated
|
|
84
|
+
>>> import xarray as xr
|
|
85
|
+
>>> import xarray_annotated as xra
|
|
86
|
+
>>> @xra.enforce
|
|
87
|
+
... def f(p: Annotated[xr.DataArray, xra.Unit("Pa")]):
|
|
88
|
+
... return p
|
|
89
|
+
>>> hpa = xr.DataArray([1.0], attrs={"units": "hPa"})
|
|
90
|
+
>>> with xra.enabled(False):
|
|
91
|
+
... f(hpa).attrs["units"]
|
|
92
|
+
'hPa'
|
|
99
93
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
if not isinstance(value, bool | np.bool_):
|
|
95
|
+
raise TypeError(f"enabled takes a bool, not {type(value).__name__}.")
|
|
96
|
+
token = _override.set(bool(value))
|
|
97
|
+
try:
|
|
98
|
+
yield
|
|
99
|
+
finally:
|
|
100
|
+
_override.reset(token)
|