zarr-cm 0.1.0__tar.gz → 0.2.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 (46) hide show
  1. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/PKG-INFO +1 -1
  2. zarr_cm-0.2.0/docs/api.md +42 -0
  3. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/docs/index.md +34 -0
  4. zarr_cm-0.2.0/src/zarr_cm/__init__.py +275 -0
  5. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/_version.py +2 -2
  6. zarr_cm-0.2.0/tests/test_multi.py +238 -0
  7. zarr_cm-0.1.0/docs/api.md +0 -26
  8. zarr_cm-0.1.0/src/zarr_cm/__init__.py +0 -44
  9. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.copier-answers.yml +0 -0
  10. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.git_archival.txt +0 -0
  11. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.gitattributes +0 -0
  12. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.github/CONTRIBUTING.md +0 -0
  13. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.github/dependabot.yml +0 -0
  14. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.github/release.yml +0 -0
  15. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.github/workflows/cd.yml +0 -0
  16. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.github/workflows/ci.yml +0 -0
  17. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.gitignore +0 -0
  18. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.pre-commit-config.yaml +0 -0
  19. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/.readthedocs.yaml +0 -0
  20. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/LICENSE +0 -0
  21. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/README.md +0 -0
  22. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/mkdocs.yml +0 -0
  23. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/noxfile.py +0 -0
  24. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/pyproject.toml +0 -0
  25. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/_core.py +0 -0
  26. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/_version.pyi +0 -0
  27. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/geo_proj.py +0 -0
  28. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/license.py +0 -0
  29. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/multiscales.py +0 -0
  30. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/py.typed +0 -0
  31. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/spatial.py +0 -0
  32. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/src/zarr_cm/uom.py +0 -0
  33. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/conftest.py +0 -0
  34. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/geo-proj.json +0 -0
  35. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/license.json +0 -0
  36. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/multiscales.json +0 -0
  37. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/spatial.json +0 -0
  38. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/uom.json +0 -0
  39. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/schemas/zarr-conventions-schema.json +0 -0
  40. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_docs.py +0 -0
  41. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_geo_proj.py +0 -0
  42. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_license.py +0 -0
  43. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_multiscales.py +0 -0
  44. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_package.py +0 -0
  45. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_spatial.py +0 -0
  46. {zarr_cm-0.1.0 → zarr_cm-0.2.0}/tests/test_uom.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zarr-cm
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python implementation of Zarr Conventions Metadata
5
5
  Project-URL: Homepage, https://github.com/zarr-conventions/zarr-cm
6
6
  Project-URL: Bug Tracker, https://github.com/zarr-conventions/zarr-cm/issues
@@ -0,0 +1,42 @@
1
+ # API Reference
2
+
3
+ ## Multi-convention
4
+
5
+ <!-- prettier-ignore -->
6
+ ::: zarr_cm
7
+ options:
8
+ members:
9
+ - CONVENTION_NAMES
10
+ - ALL_CONVENTION_KEYS
11
+ - MultiConventionAttrs
12
+ - create_many
13
+ - validate_many
14
+ - validate_all
15
+ - insert_many
16
+ - extract_many
17
+ - extract_all
18
+
19
+ ## Core
20
+
21
+ <!-- prettier-ignore -->
22
+ ::: zarr_cm._core
23
+
24
+ ## geo-proj
25
+
26
+ ::: zarr_cm.geo_proj
27
+
28
+ ## spatial
29
+
30
+ ::: zarr_cm.spatial
31
+
32
+ ## multiscales
33
+
34
+ ::: zarr_cm.multiscales
35
+
36
+ ## license
37
+
38
+ ::: zarr_cm.license
39
+
40
+ ## uom
41
+
42
+ ::: zarr_cm.uom
@@ -73,3 +73,37 @@ print(extracted)
73
73
  ```
74
74
 
75
75
  <!-- blacken-docs:on -->
76
+
77
+ ## Multiple conventions
78
+
79
+ `create_many`, `insert_many`, `extract_many`, and `validate_many` work with
80
+ several conventions at once, keyed by convention name. `extract_all` and
81
+ `validate_all` are shortcuts that operate on all known conventions.
82
+
83
+ <!-- blacken-docs:off -->
84
+ <!-- prettier-ignore -->
85
+ ```python
86
+ from zarr_cm import create_many, extract_all
87
+
88
+ # Create attributes with multiple conventions at once
89
+ attrs = create_many(
90
+ {
91
+ "geo-proj": {"proj:code": "EPSG:4326"},
92
+ "spatial": {"spatial:dimensions": ["y", "x"]},
93
+ "license": {"spdx": "MIT"},
94
+ }
95
+ )
96
+ print(sorted(attrs.keys()))
97
+ #> ['license', 'proj:code', 'spatial:dimensions', 'zarr_conventions']
98
+
99
+ # Extract all known conventions
100
+ remaining, extracted = extract_all(attrs)
101
+ print(remaining)
102
+ #> {}
103
+ print(sorted(extracted.keys()))
104
+ #> ['geo-proj', 'license', 'spatial']
105
+ print(extracted["geo-proj"])
106
+ #> {'proj:code': 'EPSG:4326'}
107
+ ```
108
+
109
+ <!-- blacken-docs:on -->
@@ -0,0 +1,275 @@
1
+ """
2
+ Copyright (c) 2026 Davis Bennett. All rights reserved.
3
+
4
+ zarr-cm: Python implementation of Zarr Conventions Metadata
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import typing
10
+ from typing import Any, Final, Literal, NotRequired, TypedDict
11
+
12
+ if typing.TYPE_CHECKING:
13
+ import types
14
+ from collections.abc import Iterable
15
+
16
+ from . import geo_proj, multiscales, spatial, uom
17
+ from . import license as license_
18
+ from ._core import (
19
+ ConventionAttrs,
20
+ ConventionMetadataObject,
21
+ validate_convention_metadata_object,
22
+ )
23
+ from ._version import version as __version__
24
+ from .geo_proj import GeoProjAttrs, GeoProjConventionAttrs
25
+ from .license import LicenseAttrs, LicenseConventionAttrs
26
+ from .multiscales import (
27
+ LayoutObject,
28
+ MultiscalesAttrs,
29
+ MultiscalesConventionAttrs,
30
+ Transform,
31
+ )
32
+ from .spatial import SpatialAttrs, SpatialConventionAttrs
33
+ from .uom import UCUM, UomAttrs, UomConventionAttrs
34
+
35
+ ConventionName = Literal["geo-proj", "spatial", "multiscales", "license", "uom"]
36
+
37
+ _REGISTRY: Final[dict[ConventionName, types.ModuleType]] = {
38
+ "geo-proj": geo_proj,
39
+ "spatial": spatial,
40
+ "multiscales": multiscales,
41
+ "license": license_,
42
+ "uom": uom,
43
+ }
44
+
45
+ CONVENTION_NAMES: Final = frozenset(_REGISTRY)
46
+
47
+ ALL_CONVENTION_KEYS: Final = frozenset(
48
+ geo_proj.CONVENTION_KEYS
49
+ | spatial.CONVENTION_KEYS
50
+ | multiscales.CONVENTION_KEYS
51
+ | license_.CONVENTION_KEYS
52
+ | uom.CONVENTION_KEYS
53
+ )
54
+
55
+ MultiConventionAttrs = TypedDict(
56
+ "MultiConventionAttrs",
57
+ {
58
+ "zarr_conventions": NotRequired[list[ConventionMetadataObject]],
59
+ # geo-proj
60
+ "proj:code": NotRequired[str],
61
+ "proj:wkt2": NotRequired[str],
62
+ "proj:projjson": NotRequired[dict[str, Any]],
63
+ # spatial
64
+ "spatial:dimensions": NotRequired[list[str]],
65
+ "spatial:bbox": NotRequired[list[float]],
66
+ "spatial:transform_type": NotRequired[str],
67
+ "spatial:transform": NotRequired[list[float]],
68
+ "spatial:shape": NotRequired[list[int]],
69
+ "spatial:registration": NotRequired[str],
70
+ # multiscales
71
+ "multiscales": NotRequired[MultiscalesAttrs],
72
+ # license
73
+ "license": NotRequired[LicenseAttrs],
74
+ # uom
75
+ "uom": NotRequired[UomAttrs],
76
+ },
77
+ )
78
+
79
+
80
+ def _get_module(name: ConventionName) -> types.ModuleType:
81
+ """Look up convention module by display name, raise ValueError if unknown."""
82
+ try:
83
+ return _REGISTRY[name]
84
+ except KeyError:
85
+ msg = f"Unknown convention {name!r}. Valid names: {sorted(CONVENTION_NAMES)}"
86
+ raise ValueError(msg) from None
87
+
88
+
89
+ def _detect_conventions(attrs: dict[str, Any]) -> frozenset[ConventionName]:
90
+ """Identify which conventions are present by matching UUIDs in zarr_conventions."""
91
+ uuids = {cmo.get("uuid") for cmo in attrs.get("zarr_conventions", [])}
92
+ return frozenset(name for name, mod in _REGISTRY.items() if mod.UUID in uuids)
93
+
94
+
95
+ def create_many(
96
+ conventions: dict[ConventionName, dict[str, Any]],
97
+ ) -> dict[str, Any]:
98
+ """Create and insert multiple conventions into a single attributes dict.
99
+
100
+ Parameters
101
+ ----------
102
+ conventions
103
+ Mapping from convention display name (e.g. ``"geo-proj"``) to
104
+ already-formed convention data (the ``AttrsT`` value).
105
+
106
+ Returns
107
+ -------
108
+ dict[str, Any]
109
+ A new attributes dict containing all convention data and a
110
+ combined ``zarr_conventions`` array.
111
+ """
112
+ result: dict[str, Any] = {}
113
+ for name, data in conventions.items():
114
+ mod = _get_module(name)
115
+ mod.validate(data)
116
+ result = mod.insert(result, data, overwrite=True)
117
+ return result
118
+
119
+
120
+ def validate_many(
121
+ attrs: dict[str, Any],
122
+ conventions: Iterable[ConventionName],
123
+ ) -> dict[str, Any]:
124
+ """Validate multiple conventions within an attributes dict.
125
+
126
+ Parameters
127
+ ----------
128
+ attrs
129
+ The attributes dict to validate.
130
+ conventions
131
+ Convention names to validate.
132
+
133
+ Returns
134
+ -------
135
+ dict[str, Any]
136
+ The input *attrs* (pass-through on success).
137
+ """
138
+ for name in conventions:
139
+ mod = _get_module(name)
140
+ _, extracted = mod.extract(attrs)
141
+ mod.validate(dict(extracted))
142
+ return attrs
143
+
144
+
145
+ def insert_many(
146
+ attrs: dict[str, Any],
147
+ conventions: dict[ConventionName, dict[str, Any]],
148
+ *,
149
+ overwrite: bool = False,
150
+ ) -> dict[str, Any]:
151
+ """Insert multiple conventions into an attributes dict.
152
+
153
+ Parameters
154
+ ----------
155
+ attrs
156
+ The existing attributes dict.
157
+ conventions
158
+ Mapping from convention display name to already-formed convention data.
159
+ overwrite
160
+ If False (default), raise ``ValueError`` when *attrs* already
161
+ contains keys present in a convention's data.
162
+
163
+ Returns
164
+ -------
165
+ dict[str, Any]
166
+ A new attributes dict with all convention data merged in.
167
+ """
168
+ result = attrs
169
+ for name, data in conventions.items():
170
+ mod = _get_module(name)
171
+ mod.validate(data)
172
+ result = mod.insert(result, data, overwrite=overwrite)
173
+ return result
174
+
175
+
176
+ def extract_many(
177
+ attrs: dict[str, Any],
178
+ conventions: Iterable[ConventionName],
179
+ ) -> tuple[dict[str, Any], dict[ConventionName, dict[str, Any]]]:
180
+ """Extract multiple conventions from an attributes dict.
181
+
182
+ Parameters
183
+ ----------
184
+ attrs
185
+ The attributes dict to extract from.
186
+ conventions
187
+ Convention names to extract.
188
+
189
+ Returns
190
+ -------
191
+ tuple[dict[str, Any], dict[str, dict[str, Any]]]
192
+ ``(remaining_attrs, extracted)`` where *extracted* maps
193
+ convention names to their convention data dicts.
194
+ """
195
+ remaining = attrs
196
+ extracted: dict[ConventionName, dict[str, Any]] = {}
197
+ for name in conventions:
198
+ mod = _get_module(name)
199
+ remaining, data = mod.extract(remaining)
200
+ extracted[name] = dict(data)
201
+ return remaining, extracted
202
+
203
+
204
+ def validate_all(
205
+ attrs: dict[str, Any],
206
+ ) -> dict[str, Any]:
207
+ """Validate all detected conventions within an attributes dict.
208
+
209
+ Detects which conventions are present by matching UUIDs in
210
+ ``zarr_conventions``, then validates each one.
211
+
212
+ Parameters
213
+ ----------
214
+ attrs
215
+ The attributes dict to validate.
216
+
217
+ Returns
218
+ -------
219
+ dict[str, Any]
220
+ The input *attrs* (pass-through on success).
221
+ """
222
+ return validate_many(attrs, _detect_conventions(attrs))
223
+
224
+
225
+ def extract_all(
226
+ attrs: dict[str, Any],
227
+ ) -> tuple[dict[str, Any], dict[ConventionName, dict[str, Any]]]:
228
+ """Extract all detected conventions from an attributes dict.
229
+
230
+ Detects which conventions are present by matching UUIDs in
231
+ ``zarr_conventions``, then extracts each one.
232
+
233
+ Parameters
234
+ ----------
235
+ attrs
236
+ The attributes dict to extract from.
237
+
238
+ Returns
239
+ -------
240
+ tuple[dict[str, Any], dict[str, dict[str, Any]]]
241
+ ``(remaining_attrs, extracted)`` where *extracted* maps
242
+ convention names to their convention data dicts.
243
+ """
244
+ return extract_many(attrs, _detect_conventions(attrs))
245
+
246
+
247
+ __all__ = [
248
+ "ALL_CONVENTION_KEYS",
249
+ "CONVENTION_NAMES",
250
+ "UCUM",
251
+ "ConventionAttrs",
252
+ "ConventionMetadataObject",
253
+ "ConventionName",
254
+ "GeoProjAttrs",
255
+ "GeoProjConventionAttrs",
256
+ "LayoutObject",
257
+ "LicenseAttrs",
258
+ "LicenseConventionAttrs",
259
+ "MultiConventionAttrs",
260
+ "MultiscalesAttrs",
261
+ "MultiscalesConventionAttrs",
262
+ "SpatialAttrs",
263
+ "SpatialConventionAttrs",
264
+ "Transform",
265
+ "UomAttrs",
266
+ "UomConventionAttrs",
267
+ "__version__",
268
+ "create_many",
269
+ "extract_all",
270
+ "extract_many",
271
+ "insert_many",
272
+ "validate_all",
273
+ "validate_convention_metadata_object",
274
+ "validate_many",
275
+ ]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.0'
32
- __version_tuple__ = version_tuple = (0, 1, 0)
31
+ __version__ = version = '0.2.0'
32
+ __version_tuple__ = version_tuple = (0, 2, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,238 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, get_args
4
+
5
+ import pytest
6
+
7
+ import zarr_cm
8
+ from zarr_cm import (
9
+ ALL_CONVENTION_KEYS,
10
+ CONVENTION_NAMES,
11
+ ConventionName,
12
+ create_many,
13
+ extract_all,
14
+ extract_many,
15
+ insert_many,
16
+ validate_all,
17
+ validate_many,
18
+ )
19
+
20
+
21
+ def test_convention_names_constant() -> None:
22
+ assert (
23
+ frozenset({"geo-proj", "spatial", "multiscales", "license", "uom"})
24
+ == CONVENTION_NAMES
25
+ )
26
+
27
+
28
+ def test_all_convention_keys_constant() -> None:
29
+ assert (
30
+ frozenset(
31
+ {
32
+ "proj:code",
33
+ "proj:wkt2",
34
+ "proj:projjson",
35
+ "spatial:dimensions",
36
+ "spatial:bbox",
37
+ "spatial:transform_type",
38
+ "spatial:transform",
39
+ "spatial:shape",
40
+ "spatial:registration",
41
+ "multiscales",
42
+ "license",
43
+ "uom",
44
+ }
45
+ )
46
+ == ALL_CONVENTION_KEYS
47
+ )
48
+
49
+
50
+ def test_create_many_single() -> None:
51
+ result = create_many({"geo-proj": {"proj:code": "EPSG:4326"}})
52
+ assert result["proj:code"] == "EPSG:4326"
53
+ assert len(result["zarr_conventions"]) == 1
54
+
55
+
56
+ def test_create_many_mixed() -> None:
57
+ result = create_many(
58
+ {
59
+ "geo-proj": {"proj:code": "EPSG:4326"},
60
+ "license": {"spdx": "MIT"},
61
+ }
62
+ )
63
+ assert result["proj:code"] == "EPSG:4326"
64
+ assert result["license"] == {"spdx": "MIT"}
65
+ assert len(result["zarr_conventions"]) == 2
66
+
67
+
68
+ def test_create_many_all() -> None:
69
+ result = create_many(
70
+ {
71
+ "geo-proj": {"proj:code": "EPSG:4326"},
72
+ "spatial": {"spatial:dimensions": ["y", "x"]},
73
+ "multiscales": {"layout": [{"asset": "0"}]},
74
+ "license": {"spdx": "MIT"},
75
+ "uom": {"ucum": {"unit": "kg"}},
76
+ }
77
+ )
78
+ assert len(result["zarr_conventions"]) == 5
79
+ assert result["proj:code"] == "EPSG:4326"
80
+ assert result["spatial:dimensions"] == ["y", "x"]
81
+ assert result["multiscales"]["layout"] == [{"asset": "0"}]
82
+ assert result["license"] == {"spdx": "MIT"}
83
+ assert result["uom"]["ucum"]["unit"] == "kg"
84
+
85
+
86
+ def test_create_many_invalid_name() -> None:
87
+ with pytest.raises(ValueError, match="Unknown convention"):
88
+ create_many({"not-a-convention": {}}) # type: ignore[dict-item]
89
+
90
+
91
+ def test_create_many_invalid_data() -> None:
92
+ with pytest.raises(ValueError, match="Exactly one"):
93
+ create_many({"geo-proj": {}})
94
+
95
+
96
+ def test_validate_many() -> None:
97
+ attrs = create_many(
98
+ {
99
+ "geo-proj": {"proj:code": "EPSG:4326"},
100
+ "license": {"spdx": "MIT"},
101
+ }
102
+ )
103
+ result = validate_many(attrs, ["geo-proj", "license"])
104
+ assert result is attrs
105
+
106
+
107
+ def test_validate_many_subset() -> None:
108
+ attrs = create_many(
109
+ {
110
+ "geo-proj": {"proj:code": "EPSG:4326"},
111
+ "license": {"spdx": "MIT"},
112
+ }
113
+ )
114
+ result = validate_many(attrs, ["geo-proj"])
115
+ assert result is attrs
116
+
117
+
118
+ def test_validate_many_invalid() -> None:
119
+ attrs = create_many({"license": {"spdx": "MIT"}})
120
+ # Corrupt the license data
121
+ attrs["license"] = {}
122
+ with pytest.raises(ValueError, match="At least one"):
123
+ validate_many(attrs, ["license"])
124
+
125
+
126
+ def test_validate_all() -> None:
127
+ attrs = create_many(
128
+ {
129
+ "geo-proj": {"proj:code": "EPSG:4326"},
130
+ "license": {"spdx": "MIT"},
131
+ "uom": {"ucum": {"unit": "kg"}},
132
+ }
133
+ )
134
+ result = validate_all(attrs)
135
+ assert result is attrs
136
+
137
+
138
+ def test_insert_many_empty_attrs() -> None:
139
+ result = insert_many(
140
+ {},
141
+ {
142
+ "geo-proj": {"proj:code": "EPSG:4326"},
143
+ "license": {"spdx": "MIT"},
144
+ },
145
+ )
146
+ assert result["proj:code"] == "EPSG:4326"
147
+ assert result["license"] == {"spdx": "MIT"}
148
+
149
+
150
+ def test_insert_many_preserves_attrs() -> None:
151
+ result = insert_many(
152
+ {"foo": "bar"},
153
+ {"geo-proj": {"proj:code": "EPSG:4326"}},
154
+ )
155
+ assert result["foo"] == "bar"
156
+ assert result["proj:code"] == "EPSG:4326"
157
+
158
+
159
+ def test_insert_many_collision_raises() -> None:
160
+ attrs = {"proj:code": "EPSG:3857"}
161
+ with pytest.raises(ValueError, match="overwritten"):
162
+ insert_many(attrs, {"geo-proj": {"proj:code": "EPSG:4326"}})
163
+
164
+
165
+ def test_insert_many_overwrite() -> None:
166
+ attrs = {"proj:code": "EPSG:3857"}
167
+ result = insert_many(
168
+ attrs,
169
+ {"geo-proj": {"proj:code": "EPSG:4326"}},
170
+ overwrite=True,
171
+ )
172
+ assert result["proj:code"] == "EPSG:4326"
173
+
174
+
175
+ def test_extract_many() -> None:
176
+ attrs = create_many(
177
+ {
178
+ "geo-proj": {"proj:code": "EPSG:4326"},
179
+ "license": {"spdx": "MIT"},
180
+ }
181
+ )
182
+ remaining, extracted = extract_many(attrs, ["geo-proj", "license"])
183
+ assert remaining == {}
184
+ assert extracted["geo-proj"] == {"proj:code": "EPSG:4326"}
185
+ assert extracted["license"] == {"spdx": "MIT"}
186
+
187
+
188
+ def test_extract_many_subset() -> None:
189
+ attrs = create_many(
190
+ {
191
+ "geo-proj": {"proj:code": "EPSG:4326"},
192
+ "license": {"spdx": "MIT"},
193
+ }
194
+ )
195
+ remaining, extracted = extract_many(attrs, ["geo-proj"])
196
+ assert "geo-proj" in extracted
197
+ assert "license" not in extracted
198
+ # license data stays in remaining
199
+ assert remaining["license"] == {"spdx": "MIT"}
200
+
201
+
202
+ def test_extract_many_preserves_remaining() -> None:
203
+ attrs = create_many({"geo-proj": {"proj:code": "EPSG:4326"}})
204
+ attrs["foo"] = "bar"
205
+ remaining, extracted = extract_many(attrs, ["geo-proj"])
206
+ assert remaining == {"foo": "bar"}
207
+ assert extracted["geo-proj"] == {"proj:code": "EPSG:4326"}
208
+
209
+
210
+ def test_extract_all() -> None:
211
+ conventions: dict[ConventionName, dict[str, Any]] = {
212
+ "geo-proj": {"proj:code": "EPSG:4326"},
213
+ "license": {"spdx": "MIT"},
214
+ }
215
+ attrs = create_many(conventions)
216
+ remaining, extracted = extract_all(attrs)
217
+ assert remaining == {}
218
+ assert extracted["geo-proj"] == {"proj:code": "EPSG:4326"}
219
+ assert extracted["license"] == {"spdx": "MIT"}
220
+
221
+
222
+ def test_convention_name_literal_matches_registry() -> None:
223
+ for name in get_args(ConventionName):
224
+ assert hasattr(zarr_cm, name.replace("-", "_")), (
225
+ f"{name!r} is not a module in zarr_cm"
226
+ )
227
+
228
+
229
+ def test_roundtrip() -> None:
230
+ conventions: dict[ConventionName, dict[str, Any]] = {
231
+ "geo-proj": {"proj:code": "EPSG:4326"},
232
+ "spatial": {"spatial:dimensions": ["y", "x"]},
233
+ "license": {"spdx": "MIT"},
234
+ }
235
+ attrs = create_many(conventions)
236
+ remaining, extracted = extract_many(attrs, conventions.keys())
237
+ assert remaining == {}
238
+ assert extracted == conventions
zarr_cm-0.1.0/docs/api.md DELETED
@@ -1,26 +0,0 @@
1
- # API Reference
2
-
3
- ## Core
4
-
5
- <!-- prettier-ignore -->
6
- ::: zarr_cm._core
7
-
8
- ## geo-proj
9
-
10
- ::: zarr_cm.geo_proj
11
-
12
- ## spatial
13
-
14
- ::: zarr_cm.spatial
15
-
16
- ## multiscales
17
-
18
- ::: zarr_cm.multiscales
19
-
20
- ## license
21
-
22
- ::: zarr_cm.license
23
-
24
- ## uom
25
-
26
- ::: zarr_cm.uom
@@ -1,44 +0,0 @@
1
- """
2
- Copyright (c) 2026 Davis Bennett. All rights reserved.
3
-
4
- zarr-cm: Python implementation of Zarr Conventions Metadata
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from ._core import (
10
- ConventionAttrs,
11
- ConventionMetadataObject,
12
- validate_convention_metadata_object,
13
- )
14
- from ._version import version as __version__
15
- from .geo_proj import GeoProjAttrs, GeoProjConventionAttrs
16
- from .license import LicenseAttrs, LicenseConventionAttrs
17
- from .multiscales import (
18
- LayoutObject,
19
- MultiscalesAttrs,
20
- MultiscalesConventionAttrs,
21
- Transform,
22
- )
23
- from .spatial import SpatialAttrs, SpatialConventionAttrs
24
- from .uom import UCUM, UomAttrs, UomConventionAttrs
25
-
26
- __all__ = [
27
- "UCUM",
28
- "ConventionAttrs",
29
- "ConventionMetadataObject",
30
- "GeoProjAttrs",
31
- "GeoProjConventionAttrs",
32
- "LayoutObject",
33
- "LicenseAttrs",
34
- "LicenseConventionAttrs",
35
- "MultiscalesAttrs",
36
- "MultiscalesConventionAttrs",
37
- "SpatialAttrs",
38
- "SpatialConventionAttrs",
39
- "Transform",
40
- "UomAttrs",
41
- "UomConventionAttrs",
42
- "__version__",
43
- "validate_convention_metadata_object",
44
- ]
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