cf-xarray 0.11.0__tar.gz → 0.11.1__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.
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/PKG-INFO +1 -1
- cf_xarray-0.11.1/cf_xarray/_version.py +1 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/accessor.py +143 -72
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/utils.py +8 -5
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray.egg-info/PKG-INFO +1 -1
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/uv.lock +3 -3
- cf_xarray-0.11.0/cf_xarray/_version.py +0 -1
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.binder/environment.yml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.deepsource.toml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/dependabot.yml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/release.yml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/workflows/ci.yaml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/workflows/parse_logs.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/workflows/pypi.yaml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/workflows/testpypi-release.yaml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.github/workflows/upstream-dev-ci.yaml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.gitignore +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.pre-commit-config.yaml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.readthedocs.yml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/.tributors +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/CITATION.cff +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/LICENSE +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/README.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/__init__.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/coding.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/criteria.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/datasets.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/formatting.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/geometry.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/groupers.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/helpers.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/options.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/parametric.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/py.typed +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/scripts/make_doc.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/scripts/print_versions.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/sgrid.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/__init__.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/conftest.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_accessor.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_coding.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_geometry.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_groupers.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_helpers.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_options.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_parametric.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_scripts.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/tests/test_units.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray/units.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray.egg-info/SOURCES.txt +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray.egg-info/dependency_links.txt +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray.egg-info/requires.txt +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/cf_xarray.egg-info/top_level.txt +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/codecov.yml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/2D_bounds_averaged.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/2D_bounds_error.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/2D_bounds_nonunique.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/Makefile +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/dataset-diagram-logo.tex +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/full-logo.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/logo.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/logo.svg +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/rich-repr-example.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/_static/style.css +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/api.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/bounds.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/cartopy_rotated_pole.png +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/coding.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/conf.py +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/contributing.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/coord_axes.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/custom-criteria.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/dsg.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/examples/introduction.ipynb +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/faq.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/flags.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/geometry.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/grid_mappings.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/howtouse.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/index.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/make.bat +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/parametricz.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/plotting.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/provenance.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/quickstart.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/roadmap.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/selecting.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/sgrid_ugrid.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/units.md +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/doc/whats-new.rst +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/pyproject.toml +0 -0
- {cf_xarray-0.11.0 → cf_xarray-0.11.1}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.11.1"
|
|
@@ -27,6 +27,7 @@ import xarray as xr
|
|
|
27
27
|
from xarray import DataArray, Dataset
|
|
28
28
|
from xarray.core.groupby import GroupBy
|
|
29
29
|
from xarray.core.resample import Resample
|
|
30
|
+
from xarray.core.utils import Frozen
|
|
30
31
|
|
|
31
32
|
try:
|
|
32
33
|
from xarray.core.rolling import ( # type:ignore[import-not-found,no-redef,unused-ignore]
|
|
@@ -495,7 +496,10 @@ def _get_bounds(obj: DataArray | Dataset, key: Hashable) -> list[Hashable]:
|
|
|
495
496
|
return list(results)
|
|
496
497
|
|
|
497
498
|
|
|
498
|
-
|
|
499
|
+
@functools.lru_cache(maxsize=256)
|
|
500
|
+
def _parse_grid_mapping_attribute(
|
|
501
|
+
grid_mapping_attr: str,
|
|
502
|
+
) -> Mapping[str, list[Hashable]]:
|
|
499
503
|
"""
|
|
500
504
|
Parse a grid_mapping attribute that may contain multiple grid mappings.
|
|
501
505
|
|
|
@@ -507,11 +511,12 @@ def _parse_grid_mapping_attribute(grid_mapping_attr: str) -> dict[str, list[Hash
|
|
|
507
511
|
- Multiple: "spatial_ref: crs_4326: latitude longitude crs_27700: x27700 y27700"
|
|
508
512
|
-> {"spatial_ref": [], "crs_4326": ["latitude", "longitude"], "crs_27700": ["x27700", "y27700"]}
|
|
509
513
|
|
|
510
|
-
Returns a
|
|
514
|
+
Returns a read-only mapping from grid mapping variable name to its associated
|
|
515
|
+
coordinate variables. The result is memoized, so callers must not mutate it.
|
|
511
516
|
"""
|
|
512
517
|
# Check if there are colons indicating multiple mappings
|
|
513
518
|
if ":" not in grid_mapping_attr:
|
|
514
|
-
return {grid_mapping_attr.strip(): []}
|
|
519
|
+
return Frozen({grid_mapping_attr.strip(): []})
|
|
515
520
|
|
|
516
521
|
# Use regex to parse the format
|
|
517
522
|
# First, find all grid mapping variables (words before colons)
|
|
@@ -519,7 +524,7 @@ def _parse_grid_mapping_attribute(grid_mapping_attr: str) -> dict[str, list[Hash
|
|
|
519
524
|
grid_mappings = re.findall(grid_pattern, grid_mapping_attr)
|
|
520
525
|
|
|
521
526
|
if not grid_mappings:
|
|
522
|
-
return {grid_mapping_attr.strip(): []}
|
|
527
|
+
return Frozen({grid_mapping_attr.strip(): []})
|
|
523
528
|
|
|
524
529
|
result: dict[str, list[Hashable]] = {}
|
|
525
530
|
|
|
@@ -548,13 +553,13 @@ def _parse_grid_mapping_attribute(grid_mapping_attr: str) -> dict[str, list[Hash
|
|
|
548
553
|
else:
|
|
549
554
|
result[gm] = []
|
|
550
555
|
|
|
551
|
-
return result
|
|
556
|
+
return Frozen(result)
|
|
552
557
|
|
|
553
558
|
|
|
554
559
|
def _create_grid_mapping(
|
|
555
560
|
var_name: str,
|
|
556
561
|
ds: Dataset,
|
|
557
|
-
grid_mapping_dict:
|
|
562
|
+
grid_mapping_dict: Mapping[str, list[Hashable]],
|
|
558
563
|
) -> GridMapping:
|
|
559
564
|
"""
|
|
560
565
|
Create a GridMapping dataclass instance from a grid mapping variable.
|
|
@@ -1007,7 +1012,11 @@ def _getattr(
|
|
|
1007
1012
|
newmap.update(dict.fromkeys(inverted[key], value))
|
|
1008
1013
|
newmap.update({key: attribute[key] for key in unused_keys})
|
|
1009
1014
|
|
|
1010
|
-
skip: dict[
|
|
1015
|
+
skip: dict[
|
|
1016
|
+
str,
|
|
1017
|
+
list[Literal["coords", "measures", "grid_mapping_names", "geometries"]]
|
|
1018
|
+
| None,
|
|
1019
|
+
] = {
|
|
1011
1020
|
"data_vars": ["coords"],
|
|
1012
1021
|
"coords": None,
|
|
1013
1022
|
}
|
|
@@ -1048,7 +1057,8 @@ def _getattr(
|
|
|
1048
1057
|
def _getitem(
|
|
1049
1058
|
accessor: CFAccessor,
|
|
1050
1059
|
key: Hashable,
|
|
1051
|
-
skip: list[Literal["coords", "measures"]]
|
|
1060
|
+
skip: list[Literal["coords", "measures", "grid_mapping_names", "geometries"]]
|
|
1061
|
+
| None = None,
|
|
1052
1062
|
) -> DataArray: ...
|
|
1053
1063
|
|
|
1054
1064
|
|
|
@@ -1056,14 +1066,16 @@ def _getitem(
|
|
|
1056
1066
|
def _getitem(
|
|
1057
1067
|
accessor: CFAccessor,
|
|
1058
1068
|
key: Iterable[Hashable],
|
|
1059
|
-
skip: list[Literal["coords", "measures"]]
|
|
1069
|
+
skip: list[Literal["coords", "measures", "grid_mapping_names", "geometries"]]
|
|
1070
|
+
| None = None,
|
|
1060
1071
|
) -> Dataset: ...
|
|
1061
1072
|
|
|
1062
1073
|
|
|
1063
1074
|
def _getitem(
|
|
1064
1075
|
accessor: CFAccessor,
|
|
1065
1076
|
key: Hashable | Iterable[Hashable],
|
|
1066
|
-
skip: list[Literal["coords", "measures"]]
|
|
1077
|
+
skip: list[Literal["coords", "measures", "grid_mapping_names", "geometries"]]
|
|
1078
|
+
| None = None,
|
|
1067
1079
|
):
|
|
1068
1080
|
"""
|
|
1069
1081
|
Index into obj using key. Attaches CF associated variables.
|
|
@@ -1077,9 +1089,14 @@ def _getitem(
|
|
|
1077
1089
|
"""
|
|
1078
1090
|
|
|
1079
1091
|
obj = accessor._obj
|
|
1080
|
-
all_bounds = obj.cf.bounds if isinstance(obj, Dataset) else {}
|
|
1081
1092
|
kind = str(type(obj).__name__)
|
|
1082
1093
|
scalar_key = isinstance(key, Hashable)
|
|
1094
|
+
# obj.cf.bounds is expensive; only compute it when scalar lookup on a
|
|
1095
|
+
# Dataset actually needs to drop bounds variables.
|
|
1096
|
+
if not isinstance(obj, DataArray) and scalar_key:
|
|
1097
|
+
all_bounds = obj.cf.bounds
|
|
1098
|
+
else:
|
|
1099
|
+
all_bounds = {}
|
|
1083
1100
|
|
|
1084
1101
|
key_iter: Iterable[Hashable]
|
|
1085
1102
|
if isinstance(key, Hashable): # using scalar_key breaks mypy type narrowing
|
|
@@ -1127,60 +1144,90 @@ def _getitem(
|
|
|
1127
1144
|
|
|
1128
1145
|
custom_criteria = ChainMap(*OPTIONS["custom_criteria"])
|
|
1129
1146
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1147
|
+
# Fast path: when every key is just a plain variable in the Dataset (no CF
|
|
1148
|
+
# special meaning), skip the per-key classification loop below. Test the
|
|
1149
|
+
# cheap predicates first and only build the reserved-name set / consult
|
|
1150
|
+
# accessor.standard_names (a full attrs scan) if those could pass — the
|
|
1151
|
+
# common ds.cf["X"] / ds.cf["longitude"] paths must not pay that cost.
|
|
1152
|
+
fast_path = (
|
|
1153
|
+
isinstance(obj, Dataset)
|
|
1154
|
+
and not skip
|
|
1155
|
+
and all(k in obj._variables for k in key_iter)
|
|
1156
|
+
)
|
|
1157
|
+
if fast_path:
|
|
1158
|
+
reserved: set[Hashable] = set(_AXIS_NAMES).union(
|
|
1159
|
+
_COORD_NAMES,
|
|
1160
|
+
_GEOMETRY_TYPES,
|
|
1161
|
+
("geometry",),
|
|
1162
|
+
measures,
|
|
1163
|
+
grid_mapping_names,
|
|
1164
|
+
custom_criteria,
|
|
1165
|
+
cf_role_criteria,
|
|
1166
|
+
)
|
|
1167
|
+
standard_names = accessor.standard_names
|
|
1168
|
+
fast_path = all(k not in reserved and k not in standard_names for k in key_iter)
|
|
1169
|
+
|
|
1170
|
+
varnames: list[Hashable]
|
|
1171
|
+
coords: list[Hashable]
|
|
1172
|
+
if fast_path:
|
|
1173
|
+
varnames = list(key_iter)
|
|
1174
|
+
coords = []
|
|
1175
|
+
successful = dict.fromkeys(key_iter, True)
|
|
1176
|
+
else:
|
|
1177
|
+
varnames = []
|
|
1178
|
+
coords = []
|
|
1179
|
+
successful = dict.fromkeys(key_iter, False)
|
|
1180
|
+
for k in key_iter:
|
|
1181
|
+
if "coords" not in skip and k in _AXIS_NAMES + _COORD_NAMES:
|
|
1182
|
+
names = _get_all(obj, k)
|
|
1183
|
+
names = drop_bounds(names)
|
|
1184
|
+
check_results(names, k)
|
|
1185
|
+
successful[k] = bool(names)
|
|
1186
|
+
coords.extend(names)
|
|
1187
|
+
elif "measures" not in skip and k in measures:
|
|
1188
|
+
measure = _get_all(obj, k)
|
|
1189
|
+
check_results(measure, k)
|
|
1190
|
+
successful[k] = bool(measure)
|
|
1191
|
+
if measure:
|
|
1192
|
+
varnames.extend(measure)
|
|
1193
|
+
elif "grid_mapping_names" not in skip and k in grid_mapping_names:
|
|
1194
|
+
grid_mapping = _get_all(obj, k)
|
|
1195
|
+
check_results(grid_mapping, k)
|
|
1196
|
+
successful[k] = bool(grid_mapping)
|
|
1197
|
+
if grid_mapping:
|
|
1198
|
+
varnames.extend(grid_mapping)
|
|
1199
|
+
elif "geometries" not in skip and (k == "geometry" or k in _GEOMETRY_TYPES):
|
|
1200
|
+
geometries = _get_all(obj, k)
|
|
1201
|
+
if geometries and k in _GEOMETRY_TYPES:
|
|
1202
|
+
new = itertools.chain(
|
|
1203
|
+
_parse_related_geometry_vars(
|
|
1204
|
+
ChainMap(obj[g].attrs, obj[g].encoding)
|
|
1205
|
+
)
|
|
1206
|
+
for g in geometries
|
|
1158
1207
|
)
|
|
1159
|
-
|
|
1160
|
-
)
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
varnames.extend(stdnames - objcoords)
|
|
1183
|
-
coords.extend(stdnames & objcoords)
|
|
1208
|
+
geometries.extend(*new)
|
|
1209
|
+
if len(geometries) > 1 and scalar_key:
|
|
1210
|
+
raise ValueError(
|
|
1211
|
+
f"CF geometries must be represented by an Xarray Dataset. To request a Dataset in return please pass `[{k!r}]` instead."
|
|
1212
|
+
)
|
|
1213
|
+
successful[k] = bool(geometries)
|
|
1214
|
+
if geometries:
|
|
1215
|
+
varnames.extend(geometries)
|
|
1216
|
+
elif k in custom_criteria or k in cf_role_criteria:
|
|
1217
|
+
names = _get_all(obj, k)
|
|
1218
|
+
check_results(names, k)
|
|
1219
|
+
successful[k] = bool(names)
|
|
1220
|
+
varnames.extend(names)
|
|
1221
|
+
else:
|
|
1222
|
+
stdnames = set(_get_with_standard_name(obj, k))
|
|
1223
|
+
objcoords = set(obj.coords)
|
|
1224
|
+
stdnames = drop_bounds(stdnames)
|
|
1225
|
+
if "coords" in skip:
|
|
1226
|
+
stdnames -= objcoords
|
|
1227
|
+
check_results(stdnames, k)
|
|
1228
|
+
successful[k] = bool(stdnames)
|
|
1229
|
+
varnames.extend(stdnames - objcoords)
|
|
1230
|
+
coords.extend(stdnames & objcoords)
|
|
1184
1231
|
|
|
1185
1232
|
# these are not special names but could be variable names in underlying object
|
|
1186
1233
|
# we allow this so that we can return variables with appropriate CF auxiliary variables
|
|
@@ -2042,7 +2089,7 @@ class CFAccessor:
|
|
|
2042
2089
|
]
|
|
2043
2090
|
as_dataset = self._maybe_to_dataset().reset_coords()
|
|
2044
2091
|
|
|
2045
|
-
keys = {}
|
|
2092
|
+
keys: dict[str, str] = {}
|
|
2046
2093
|
for attr in set(all_attrs):
|
|
2047
2094
|
try:
|
|
2048
2095
|
keys.update(parse_cell_methods_attr(attr))
|
|
@@ -2835,14 +2882,38 @@ class CFDatasetAccessor(CFAccessor):
|
|
|
2835
2882
|
"""
|
|
2836
2883
|
|
|
2837
2884
|
obj = self._obj
|
|
2838
|
-
|
|
2885
|
+
variables = obj._variables
|
|
2886
|
+
|
|
2887
|
+
# Single linear scan to discover which variables in the dataset have
|
|
2888
|
+
# a ``bounds`` attribute pointing at another existing variable. The
|
|
2889
|
+
# naive implementation called ``apply_mapper(_get_bounds, ...)`` for
|
|
2890
|
+
# every key in ``self.keys() | set(variables)``, which itself fans
|
|
2891
|
+
# out through ``_get_all`` and runs the regex criteria against every
|
|
2892
|
+
# variable's attrs. That is O(num_keys × num_vars × num_criteria)
|
|
2893
|
+
# for an answer that is structurally a single attribute lookup.
|
|
2894
|
+
var_to_bounds: dict[Hashable, Hashable] = {}
|
|
2895
|
+
for name, var in variables.items():
|
|
2896
|
+
attrs_or_encoding = ChainMap(var.attrs, var.encoding)
|
|
2897
|
+
bounds_name = attrs_or_encoding.get("bounds")
|
|
2898
|
+
if bounds_name is not None and bounds_name in variables:
|
|
2899
|
+
var_to_bounds[name] = bounds_name
|
|
2900
|
+
|
|
2901
|
+
if not var_to_bounds:
|
|
2902
|
+
return {}
|
|
2839
2903
|
|
|
2840
|
-
vardict = {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2904
|
+
vardict: dict[Hashable, set[Hashable]] = {}
|
|
2905
|
+
# Direct variable-name keys: O(1) lookup, no mapper machinery.
|
|
2906
|
+
for name, bounds_name in var_to_bounds.items():
|
|
2907
|
+
vardict[name] = {bounds_name}
|
|
2908
|
+
|
|
2909
|
+
# CF keys (axes, coordinate names, custom criteria, ...): resolve to
|
|
2910
|
+
# their target variable names once, then read the precomputed
|
|
2911
|
+
# ``var_to_bounds`` map. ``_get_bounds`` is no longer invoked.
|
|
2912
|
+
for key in self.keys() - set(variables):
|
|
2913
|
+
target_vars = apply_mapper(_get_all, obj, key, error=False, default=[])
|
|
2914
|
+
bounds_vars = {var_to_bounds[v] for v in target_vars if v in var_to_bounds}
|
|
2915
|
+
if bounds_vars:
|
|
2916
|
+
vardict[key] = bounds_vars
|
|
2846
2917
|
|
|
2847
2918
|
return {k: sort_maybe_hashable(v) for k, v in vardict.items() if v}
|
|
2848
2919
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
import inspect
|
|
2
3
|
import os
|
|
3
4
|
import warnings
|
|
4
5
|
from collections import defaultdict
|
|
5
|
-
from collections.abc import Iterable
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
6
7
|
from typing import Any
|
|
7
8
|
from xml.etree import ElementTree
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
10
11
|
from xarray import DataArray
|
|
12
|
+
from xarray.core.utils import Frozen
|
|
11
13
|
|
|
12
14
|
try:
|
|
13
15
|
import cftime
|
|
@@ -64,7 +66,8 @@ def _is_datetime_like(da: DataArray) -> bool:
|
|
|
64
66
|
return False
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
@functools.lru_cache(maxsize=256)
|
|
70
|
+
def parse_cell_methods_attr(attr: str) -> Mapping[str, str]:
|
|
68
71
|
"""
|
|
69
72
|
Parse cell_methods attributes (format is 'measure: name').
|
|
70
73
|
|
|
@@ -75,14 +78,14 @@ def parse_cell_methods_attr(attr: str) -> dict[str, str]:
|
|
|
75
78
|
|
|
76
79
|
Returns
|
|
77
80
|
-------
|
|
78
|
-
|
|
81
|
+
Read-only mapping from measure to name.
|
|
79
82
|
"""
|
|
80
83
|
strings = [s for scolons in attr.split(":") for s in scolons.split()]
|
|
81
84
|
if len(strings) % 2 != 0:
|
|
82
85
|
raise ValueError(f"attrs['cell_measures'] = {attr!r} is malformed.")
|
|
83
86
|
|
|
84
|
-
return
|
|
85
|
-
zip(strings[slice(0, None, 2)], strings[slice(1, None, 2)], strict=False)
|
|
87
|
+
return Frozen(
|
|
88
|
+
dict(zip(strings[slice(0, None, 2)], strings[slice(1, None, 2)], strict=False))
|
|
86
89
|
)
|
|
87
90
|
|
|
88
91
|
|
|
@@ -2983,11 +2983,11 @@ wheels = [
|
|
|
2983
2983
|
|
|
2984
2984
|
[[package]]
|
|
2985
2985
|
name = "urllib3"
|
|
2986
|
-
version = "2.
|
|
2986
|
+
version = "2.7.0"
|
|
2987
2987
|
source = { registry = "https://pypi.org/simple" }
|
|
2988
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2988
|
+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
|
2989
2989
|
wheels = [
|
|
2990
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2990
|
+
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
|
2991
2991
|
]
|
|
2992
2992
|
|
|
2993
2993
|
[[package]]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.11.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|