cf-xarray 0.9.4__tar.gz → 0.10.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 (93) hide show
  1. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/workflows/ci.yaml +2 -2
  2. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/workflows/pypi.yaml +2 -2
  3. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.pre-commit-config.yaml +8 -8
  4. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.readthedocs.yml +3 -2
  5. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/CITATION.cff +4 -0
  6. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/PKG-INFO +3 -4
  7. cf_xarray-0.10.0/cf_xarray/_version.py +1 -0
  8. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/accessor.py +28 -49
  9. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/coding.py +6 -3
  10. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/formatting.py +2 -2
  11. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/geometry.py +37 -32
  12. cf_xarray-0.10.0/cf_xarray/parametric.py +821 -0
  13. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/scripts/make_doc.py +3 -1
  14. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/sgrid.py +2 -1
  15. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_accessor.py +17 -45
  16. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_geometry.py +1 -1
  17. cf_xarray-0.10.0/cf_xarray/tests/test_parametric.py +615 -0
  18. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/utils.py +3 -1
  19. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray.egg-info/PKG-INFO +3 -4
  20. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray.egg-info/SOURCES.txt +2 -0
  21. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray.egg-info/requires.txt +1 -1
  22. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/ci/doc.yml +2 -2
  23. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/parametricz.md +1 -1
  24. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/whats-new.rst +1 -3
  25. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/pyproject.toml +4 -5
  26. cf_xarray-0.9.4/cf_xarray/_version.py +0 -1
  27. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.binder/environment.yml +0 -0
  28. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.deepsource.toml +0 -0
  29. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/dependabot.yml +0 -0
  30. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/release.yml +0 -0
  31. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/workflows/parse_logs.py +0 -0
  32. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/workflows/testpypi-release.yaml +0 -0
  33. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.github/workflows/upstream-dev-ci.yaml +0 -0
  34. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.gitignore +0 -0
  35. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/.tributors +0 -0
  36. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/LICENSE +0 -0
  37. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/README.rst +0 -0
  38. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/__init__.py +0 -0
  39. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/criteria.py +0 -0
  40. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/datasets.py +0 -0
  41. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/helpers.py +0 -0
  42. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/options.py +0 -0
  43. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/py.typed +0 -0
  44. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/scripts/print_versions.py +0 -0
  45. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/__init__.py +0 -0
  46. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/conftest.py +0 -0
  47. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_coding.py +0 -0
  48. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_helpers.py +0 -0
  49. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_options.py +0 -0
  50. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_scripts.py +0 -0
  51. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/tests/test_units.py +0 -0
  52. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray/units.py +0 -0
  53. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray.egg-info/dependency_links.txt +0 -0
  54. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/cf_xarray.egg-info/top_level.txt +0 -0
  55. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/ci/environment-no-optional-deps.yml +0 -0
  56. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/ci/environment.yml +0 -0
  57. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/ci/upstream-dev-env.yml +0 -0
  58. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/codecov.yml +0 -0
  59. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/2D_bounds_averaged.png +0 -0
  60. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/2D_bounds_error.png +0 -0
  61. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/2D_bounds_nonunique.png +0 -0
  62. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/Makefile +0 -0
  63. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/dataset-diagram-logo.tex +0 -0
  64. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/full-logo.png +0 -0
  65. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/logo.png +0 -0
  66. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/logo.svg +0 -0
  67. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/rich-repr-example.png +0 -0
  68. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/_static/style.css +0 -0
  69. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/api.rst +0 -0
  70. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/bounds.md +0 -0
  71. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/cartopy_rotated_pole.png +0 -0
  72. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/coding.md +0 -0
  73. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/conf.py +0 -0
  74. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/contributing.rst +0 -0
  75. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/coord_axes.md +0 -0
  76. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/custom-criteria.md +0 -0
  77. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/dsg.md +0 -0
  78. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/examples/introduction.ipynb +0 -0
  79. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/faq.md +0 -0
  80. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/flags.md +0 -0
  81. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/geometry.md +0 -0
  82. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/grid_mappings.md +0 -0
  83. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/howtouse.md +0 -0
  84. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/index.rst +0 -0
  85. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/make.bat +0 -0
  86. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/plotting.md +0 -0
  87. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/provenance.md +0 -0
  88. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/quickstart.md +0 -0
  89. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/roadmap.rst +0 -0
  90. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/selecting.md +0 -0
  91. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/sgrid_ugrid.md +0 -0
  92. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/doc/units.md +0 -0
  93. {cf_xarray-0.9.4 → cf_xarray-0.10.0}/setup.cfg +0 -0
@@ -27,7 +27,7 @@ jobs:
27
27
  fail-fast: false
28
28
  matrix:
29
29
  os: ["ubuntu-latest"]
30
- python-version: ["3.9", "3.12"]
30
+ python-version: ["3.10", "3.12"]
31
31
  steps:
32
32
  - uses: actions/checkout@v4
33
33
  with:
@@ -91,7 +91,7 @@ jobs:
91
91
  shell: bash -l {0}
92
92
  strategy:
93
93
  matrix:
94
- python-version: ["3.9", "3.12"]
94
+ python-version: ["3.10", "3.12"]
95
95
  steps:
96
96
  - uses: actions/checkout@v4
97
97
  with:
@@ -72,7 +72,7 @@ jobs:
72
72
 
73
73
  - name: Publish package to TestPyPI
74
74
  if: github.event_name == 'push'
75
- uses: pypa/gh-action-pypi-publish@v1.9.0
75
+ uses: pypa/gh-action-pypi-publish@v1.10.2
76
76
  with:
77
77
  password: ${{ secrets.TESTPYPI_TOKEN }}
78
78
  repository_url: https://test.pypi.org/legacy/
@@ -96,6 +96,6 @@ jobs:
96
96
  name: releases
97
97
  path: dist
98
98
  - name: Publish package to PyPI
99
- uses: pypa/gh-action-pypi-publish@v1.9.0
99
+ uses: pypa/gh-action-pypi-publish@v1.10.2
100
100
  with:
101
101
  verbose: true
@@ -3,25 +3,25 @@ ci:
3
3
 
4
4
  repos:
5
5
  - repo: https://github.com/asottile/pyupgrade
6
- rev: v3.16.0
6
+ rev: v3.17.0
7
7
  hooks:
8
8
  - id: pyupgrade
9
- args: ["--py39-plus"]
9
+ args: ["--py310-plus"]
10
10
 
11
11
  - repo: https://github.com/astral-sh/ruff-pre-commit
12
12
  # Ruff version.
13
- rev: 'v0.5.0'
13
+ rev: 'v0.6.9'
14
14
  hooks:
15
15
  - id: ruff
16
16
  args: ["--show-fixes", "--fix"]
17
17
 
18
18
  - repo: https://github.com/psf/black-pre-commit-mirror
19
- rev: 24.4.2
19
+ rev: 24.10.0
20
20
  hooks:
21
21
  - id: black
22
22
 
23
23
  - repo: https://github.com/rstcheck/rstcheck
24
- rev: v6.2.0
24
+ rev: v6.2.4
25
25
  hooks:
26
26
  - id: rstcheck
27
27
  additional_dependencies: [sphinx, tomli]
@@ -36,7 +36,7 @@ repos:
36
36
  - mdformat-myst
37
37
 
38
38
  - repo: https://github.com/nbQA-dev/nbQA
39
- rev: 1.8.5
39
+ rev: 1.8.7
40
40
  hooks:
41
41
  - id: nbqa-black
42
42
  - id: nbqa-ruff
@@ -47,7 +47,7 @@ repos:
47
47
  additional_dependencies: [mdformat==0.7.17]
48
48
 
49
49
  - repo: https://github.com/pre-commit/pre-commit-hooks
50
- rev: v4.6.0
50
+ rev: v5.0.0
51
51
  hooks:
52
52
  - id: trailing-whitespace
53
53
  - id: end-of-file-fixer
@@ -67,7 +67,7 @@ repos:
67
67
  - id: validate-cff
68
68
 
69
69
  - repo: https://github.com/abravalheri/validate-pyproject
70
- rev: v0.18
70
+ rev: v0.20.2
71
71
  hooks:
72
72
  - id: validate-pyproject
73
73
 
@@ -1,15 +1,16 @@
1
1
  version: 2
2
2
 
3
3
  build:
4
- os: ubuntu-22.04
4
+ os: ubuntu-lts-latest
5
5
  tools:
6
- python: mambaforge-4.10
6
+ python: mambaforge-latest
7
7
  jobs:
8
8
  post_checkout:
9
9
  - (git --no-pager log --pretty="tformat:%s" -1 | grep -vqF "[skip-rtd]") || exit 183
10
10
  pre_install:
11
11
  - conda list sphinx
12
12
  - conda list cf_xarray
13
+ - conda list
13
14
 
14
15
  conda:
15
16
  environment: ci/doc.yml
@@ -83,6 +83,10 @@ authors:
83
83
  - family-names: Haëck
84
84
  given-names: Clément
85
85
  affiliation: Laboratoire d'Océanographie et du Climat (LOCEAN), Paris
86
+ - family-names: Boutte
87
+ given-names: Jason
88
+ orcid: 'https://orcid.org/0009-0009-3996-3772'
89
+ affiliation: Lawrence Livermore National Laboratory
86
90
  identifiers:
87
91
  - type: doi
88
92
  value: 10.5281/zenodo.4749735
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cf_xarray
3
- Version: 0.9.4
3
+ Version: 0.10.0
4
4
  Summary: A convenience wrapper for using CF attributes on xarray objects
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -214,14 +214,13 @@ Classifier: License :: OSI Approved :: Apache Software License
214
214
  Classifier: Natural Language :: English
215
215
  Classifier: Operating System :: OS Independent
216
216
  Classifier: Programming Language :: Python
217
- Classifier: Programming Language :: Python :: 3.9
218
217
  Classifier: Programming Language :: Python :: 3.10
219
218
  Classifier: Programming Language :: Python :: 3.11
220
219
  Classifier: Programming Language :: Python :: 3.12
221
- Requires-Python: >=3.9
220
+ Requires-Python: >=3.10
222
221
  Description-Content-Type: text/x-rst
223
222
  License-File: LICENSE
224
- Requires-Dist: xarray
223
+ Requires-Dist: xarray>=2022.03.0
225
224
  Provides-Extra: all
226
225
  Requires-Dist: matplotlib; extra == "all"
227
226
  Requires-Dist: pint!=0.24.0,>=0.18; extra == "all"
@@ -0,0 +1 @@
1
+ __version__ = "0.10.0"
@@ -5,14 +5,19 @@ import inspect
5
5
  import itertools
6
6
  import re
7
7
  from collections import ChainMap, namedtuple
8
- from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
8
+ from collections.abc import (
9
+ Callable,
10
+ Hashable,
11
+ Iterable,
12
+ Mapping,
13
+ MutableMapping,
14
+ Sequence,
15
+ )
9
16
  from datetime import datetime
10
17
  from typing import (
11
18
  Any,
12
- Callable,
13
19
  Literal,
14
20
  TypeVar,
15
- Union,
16
21
  cast,
17
22
  overload,
18
23
  )
@@ -25,7 +30,7 @@ from xarray.core.resample import Resample
25
30
  from xarray.core.rolling import Coarsen, Rolling
26
31
  from xarray.core.weighted import Weighted
27
32
 
28
- from . import sgrid
33
+ from . import parametric, sgrid
29
34
  from .criteria import (
30
35
  _DSG_ROLES,
31
36
  _GEOMETRY_TYPES,
@@ -83,7 +88,7 @@ ATTRS["time"] = ATTRS["T"]
83
88
  ATTRS["vertical"] = ATTRS["Z"]
84
89
 
85
90
  # Type for Mapper functions
86
- Mapper = Callable[[Union[DataArray, Dataset], Hashable], list[Hashable]]
91
+ Mapper = Callable[[DataArray | Dataset, Hashable], list[Hashable]]
87
92
 
88
93
  # Type for decorators
89
94
  F = TypeVar("F", bound=Callable[..., Any])
@@ -532,7 +537,7 @@ def _get_coords(obj: DataArray | Dataset, key: Hashable) -> list[Hashable]:
532
537
  def _variables(func: F) -> F:
533
538
  @functools.wraps(func)
534
539
  def wrapper(obj: DataArray | Dataset, key: Hashable) -> list[DataArray]:
535
- return [obj[k] for k in func(obj, key)] # type: ignore[misc]
540
+ return [obj[k] for k in func(obj, key)]
536
541
 
537
542
  return cast(F, wrapper)
538
543
 
@@ -1144,9 +1149,10 @@ def create_flag_dict(da) -> Mapping[Hashable, FlagParam]:
1144
1149
  )
1145
1150
 
1146
1151
  flag_params = tuple(
1147
- FlagParam(mask, value) for mask, value in zip(flag_masks, flag_values)
1152
+ FlagParam(mask, value)
1153
+ for mask, value in zip(flag_masks, flag_values, strict=False)
1148
1154
  )
1149
- return dict(zip(flag_meanings, flag_params))
1155
+ return dict(zip(flag_meanings, flag_params, strict=False))
1150
1156
 
1151
1157
 
1152
1158
  class CFAccessor:
@@ -1362,7 +1368,7 @@ class CFAccessor:
1362
1368
  kwargs: dict[str, Any] | None = None,
1363
1369
  ):
1364
1370
  if coords is not None:
1365
- if isinstance(coords, (Hashable, DataArray)):
1371
+ if isinstance(coords, Hashable | DataArray):
1366
1372
  coords_iter: Iterable[Hashable | DataArray] = [coords]
1367
1373
  else:
1368
1374
  coords_iter = coords
@@ -2748,13 +2754,8 @@ class CFDatasetAccessor(CFAccessor):
2748
2754
  """
2749
2755
  ds = self._obj
2750
2756
 
2751
- requirements = {
2752
- "ocean_s_coordinate_g1": {"depth_c", "depth", "s", "C", "eta"},
2753
- "ocean_s_coordinate_g2": {"depth_c", "depth", "s", "C", "eta"},
2754
- "ocean_sigma_coordinate": {"sigma", "eta", "depth"},
2755
- }
2756
-
2757
2757
  allterms = self.formula_terms
2758
+
2758
2759
  for dim in allterms:
2759
2760
  if prefix is None:
2760
2761
  assert (
@@ -2776,6 +2777,7 @@ class CFDatasetAccessor(CFAccessor):
2776
2777
  suffix = dim.split("_")
2777
2778
  zname = f"{prefix}_" + "_".join(suffix[1:])
2778
2779
 
2780
+ # never touched, if standard name is missing it's not included in allterms
2779
2781
  if "standard_name" not in ds[dim].attrs:
2780
2782
  continue
2781
2783
  stdname = ds[dim].attrs["standard_name"]
@@ -2784,46 +2786,23 @@ class CFDatasetAccessor(CFAccessor):
2784
2786
  terms = {}
2785
2787
  for key, value in allterms[dim].items():
2786
2788
  if value not in ds:
2789
+ # is this ever hit, if variable is missing it's missing in decoded allterms
2787
2790
  raise KeyError(
2788
2791
  f"Variable {value!r} is required to decode coordinate for {dim!r}"
2789
2792
  " but it is absent in the Dataset."
2790
2793
  )
2791
- terms[key] = ds[value]
2792
-
2793
- absent_terms = requirements[stdname] - set(terms)
2794
- if absent_terms:
2795
- raise KeyError(f"Required terms {absent_terms} absent in dataset.")
2796
-
2797
- if stdname == "ocean_s_coordinate_g1":
2798
- # S(k,j,i) = depth_c * s(k) + (depth(j,i) - depth_c) * C(k)
2799
- S = (
2800
- terms["depth_c"] * terms["s"]
2801
- + (terms["depth"] - terms["depth_c"]) * terms["C"]
2802
- )
2794
+ # keys should be case insensitive
2795
+ terms[key.lower()] = ds[value]
2803
2796
 
2804
- # z(n,k,j,i) = S(k,j,i) + eta(n,j,i) * (1 + S(k,j,i) / depth(j,i))
2805
- ztemp = S + terms["eta"] * (1 + S / terms["depth"])
2806
-
2807
- elif stdname == "ocean_s_coordinate_g2":
2808
- # make sure all necessary terms are present in terms
2809
- # (depth_c * s(k) + depth(j,i) * C(k)) / (depth_c + depth(j,i))
2810
- S = (terms["depth_c"] * terms["s"] + terms["depth"] * terms["C"]) / (
2811
- terms["depth_c"] + terms["depth"]
2812
- )
2813
-
2814
- # z(n,k,j,i) = eta(n,j,i) + (eta(n,j,i) + depth(j,i)) * S(k,j,i)
2815
- ztemp = terms["eta"] + (terms["eta"] + terms["depth"]) * S
2816
-
2817
- elif stdname == "ocean_sigma_coordinate":
2818
- # z(n,k,j,i) = eta(n,j,i) + sigma(k)*(depth(j,i)+eta(n,j,i))
2819
- ztemp = terms["eta"] + terms["sigma"] * (terms["depth"] + terms["eta"])
2820
-
2821
- else:
2797
+ try:
2798
+ transform = parametric.TRANSFORM_FROM_STDNAME[stdname]
2799
+ except KeyError:
2800
+ # Should occur since stdname is check before
2822
2801
  raise NotImplementedError(
2823
- f"Coordinate function for {stdname!r} not implemented yet. Contributions welcome!"
2824
- )
2802
+ f"Coordinate function for {stdname!r} not implmented yet. Contributions welcome!"
2803
+ ) from None
2825
2804
 
2826
- ds.coords[zname] = ztemp
2805
+ ds.coords[zname] = transform.from_terms(terms)
2827
2806
 
2828
2807
 
2829
2808
  @xr.register_dataarray_accessor("cf")
@@ -3018,7 +2997,7 @@ class CFDataArrayAccessor(CFAccessor):
3018
2997
  x = self._obj.astype("i")
3019
2998
  bit_comp = x & bit_mask
3020
2999
 
3021
- for i, (flag, value) in enumerate(zip(flags_reduced, values)):
3000
+ for i, (flag, value) in enumerate(zip(flags_reduced, values, strict=False)):
3022
3001
  bit = bit_comp.isel(_mask=i)
3023
3002
  if value is not None:
3024
3003
  out[flag] = bit == value
@@ -46,7 +46,7 @@ def encode_multi_index_as_compress(ds, idxnames=None):
46
46
  encoded = ds.reset_index(idxnames)
47
47
  for idxname in idxnames:
48
48
  mindex = ds.indexes[idxname]
49
- coords = dict(zip(mindex.names, mindex.levels))
49
+ coords = dict(zip(mindex.names, mindex.levels, strict=False))
50
50
  encoded.update(coords)
51
51
  for c in coords:
52
52
  encoded[c].attrs = ds[c].attrs
@@ -112,13 +112,16 @@ def decode_compress_to_multi_index(encoded, idxnames=None):
112
112
 
113
113
  variables = {
114
114
  dim: encoded[dim].isel({dim: xr.Variable(data=index, dims=idxname)})
115
- for dim, index in zip(names, indices)
115
+ for dim, index in zip(names, indices, strict=False)
116
116
  }
117
117
  decoded = decoded.assign_coords(variables).set_xindex(
118
118
  names, PandasMultiIndex
119
119
  )
120
120
  except ImportError:
121
- arrays = [encoded[dim].data[index] for dim, index in zip(names, indices)]
121
+ arrays = [
122
+ encoded[dim].data[index]
123
+ for dim, index in zip(names, indices, strict=False)
124
+ ]
122
125
  mindex = pd.MultiIndex.from_arrays(arrays, names=names)
123
126
  decoded.coords[idxname] = mindex
124
127
 
@@ -268,14 +268,14 @@ def _format_flags(accessor, rich):
268
268
  table.add_column("Value", justify="right")
269
269
  table.add_column("Bits", justify="center")
270
270
 
271
- for val, bit, key in zip(value_text, bit_text, flag_dict):
271
+ for val, bit, key in zip(value_text, bit_text, flag_dict, strict=False):
272
272
  table.add_row(_format_cf_name(key, rich), val, bit)
273
273
 
274
274
  return table
275
275
 
276
276
  else:
277
277
  rows = []
278
- for val, bit, key in zip(value_text, bit_text, flag_dict):
278
+ for val, bit, key in zip(value_text, bit_text, flag_dict, strict=False):
279
279
  rows.append(
280
280
  f"{TAB}{_format_cf_name(key, rich):>{key_width}}: {TAB} {val} {bit}"
281
281
  )
@@ -324,7 +324,7 @@ def encode_geometries(ds: xr.Dataset):
324
324
  # TODO: this is incomplete. It works for vector data cubes where one of the geometry vars
325
325
  # is a dimension coordinate.
326
326
  if name in var.dims:
327
- var = var.copy()
327
+ var = var.copy(deep=False)
328
328
  var._attrs = copy.deepcopy(var._attrs)
329
329
  var.attrs["geometry"] = container_name
330
330
  # The grid_mapping and coordinates attributes can be carried by the geometry container
@@ -459,11 +459,8 @@ def shapely_to_cf(
459
459
  "and set the grid mapping variable as a coordinate",
460
460
  )
461
461
 
462
- # Get all types to call the appropriate translation function.
463
- types = {
464
- geom.item().geom_type if isinstance(geom, xr.DataArray) else geom.geom_type
465
- for geom in geometries
466
- }
462
+ as_data = geometries.data if isinstance(geometries, xr.DataArray) else geometries
463
+ type_ = as_data[0].geom_type
467
464
 
468
465
  grid_mapping_varname = None
469
466
  if (
@@ -482,16 +479,21 @@ def shapely_to_cf(
482
479
  suffix=suffix, grid_mapping_name=grid_mapping, grid_mapping=grid_mapping_varname
483
480
  )
484
481
 
485
- if types.issubset({"Point", "MultiPoint"}):
486
- ds = points_to_cf(geometries, names=names)
487
- elif types.issubset({"LineString", "MultiLineString"}):
488
- ds = lines_to_cf(geometries, names=names)
489
- elif types.issubset({"Polygon", "MultiPolygon"}):
490
- ds = polygons_to_cf(geometries, names=names)
491
- else:
482
+ try:
483
+ if type_ in ["Point", "MultiPoint"]:
484
+ ds = points_to_cf(geometries, names=names)
485
+ elif type_ in ["LineString", "MultiLineString"]:
486
+ ds = lines_to_cf(geometries, names=names)
487
+ elif type_ in ["Polygon", "MultiPolygon"]:
488
+ ds = polygons_to_cf(geometries, names=names)
489
+ else:
490
+ raise ValueError(
491
+ f"This geometry type is not supported in CF-compliant datasets. Got {type_}"
492
+ )
493
+ except NotImplementedError as e:
492
494
  raise ValueError(
493
- f"Mixed geometry types are not supported in CF-compliant datasets. Got {types}"
494
- )
495
+ "Error converting geometries. Possibly you have provided mixed geometry types."
496
+ ) from e
495
497
 
496
498
  return ds
497
499
 
@@ -841,7 +843,7 @@ def polygons_to_cf(
841
843
  node_count = part_node_count
842
844
  elif len(offsets) >= 2:
843
845
  indices = np.take(offsets[0], offsets[1])
844
- interior_ring = np.isin(offsets[0], indices, invert=True)[:-1].astype(int)
846
+ interior_ring = np.isin(offsets[0], indices, invert=True)[:-1]
845
847
 
846
848
  if len(offsets) == 3:
847
849
  indices = np.take(indices, offsets[2])
@@ -852,29 +854,32 @@ def polygons_to_cf(
852
854
  crdX = geom_coords[:, 0]
853
855
  crdY = geom_coords[:, 1]
854
856
 
857
+ data_vars = {names.node_count: (dim, node_count)}
858
+ geometry_attrs = names.geometry_container_attrs
859
+
860
+ # Special case when we have no MultiPolygons and no holes
861
+ if len(part_node_count) != len(node_count):
862
+ data_vars[names.part_node_count] = (names.part_dim, part_node_count)
863
+ geometry_attrs["part_node_count"] = names.part_node_count
864
+
865
+ # Special case when we have no holes
866
+ if interior_ring.any():
867
+ data_vars[names.interior_ring] = (names.part_dim, interior_ring)
868
+ geometry_attrs["interior_ring"] = names.interior_ring
869
+
870
+ data_vars[names.container_name] = ( # type: ignore[assignment]
871
+ (),
872
+ np.nan,
873
+ {"geometry_type": "polygon", **geometry_attrs},
874
+ )
855
875
  ds = xr.Dataset(
856
- data_vars={
857
- names.node_count: xr.DataArray(node_count, dims=(dim,)),
858
- names.container_name: xr.DataArray(
859
- data=np.nan,
860
- attrs={"geometry_type": "polygon", **names.geometry_container_attrs},
861
- ),
862
- },
876
+ data_vars=data_vars,
863
877
  coords=names.coords(x=x, y=y, crdX=crdX, crdY=crdY, dim=dim),
864
878
  )
865
879
 
866
880
  if coord is not None:
867
881
  ds = ds.assign_coords({dim: coord})
868
882
 
869
- # Special case when we have no MultiPolygons and no holes
870
- if len(part_node_count) != len(node_count):
871
- ds[names.part_node_count] = xr.DataArray(part_node_count, dims=names.part_dim)
872
- ds[names.container_name].attrs["part_node_count"] = names.part_node_count
873
-
874
- # Special case when we have no holes
875
- if (interior_ring != 0).any():
876
- ds[names.interior_ring] = xr.DataArray(interior_ring, dims=names.part_dim)
877
- ds[names.container_name].attrs["interior_ring"] = names.interior_ring
878
883
  return ds
879
884
 
880
885