Skip to content

Commit c2c2d40

Browse files
authored
Merge pull request #131 from d-v-b/chore/use-zarr-cm
use zarr-cm for defining zarr conventions metadata
2 parents e842ece + 98327d4 commit c2c2d40

File tree

18 files changed

+1507
-1704
lines changed

18 files changed

+1507
-1704
lines changed

docs/geozarr-minispec.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ This example shows a geospatial Dataset with two spatial data variables. The `za
165165
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
166166
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
167167
"name": "spatial:",
168-
"description": "Spatial coordinate and transformation information"
168+
"description": "Spatial coordinate information"
169169
}
170170
],
171171
"proj:code": "EPSG:32633",
@@ -329,7 +329,7 @@ A minimal 2-level geospatial pyramid — well-suited for web mapping or non-EO d
329329
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
330330
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
331331
"name": "spatial:",
332-
"description": "Spatial coordinate and transformation information"
332+
"description": "Spatial coordinate information"
333333
}
334334
],
335335
"multiscales": {
@@ -388,7 +388,7 @@ A complete Sentinel-2 L2A scene with 6 resolution levels (variable downsampling
388388
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
389389
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
390390
"name": "spatial:",
391-
"description": "Spatial coordinate and transformation information"
391+
"description": "Spatial coordinate information"
392392
}
393393
],
394394
"multiscales": {

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies = [
3636
"rioxarray>=0.13.0",
3737
"cf-xarray>=0.8.0",
3838
"typing-extensions>=4.15.0",
39+
"zarr-cm>=0.2.0",
3940
"aiohttp>=3.8.1",
4041
"s3fs>=2024.6.0",
4142
"boto3>=1.34.0",

src/eopf_geozarr/data_api/geozarr/common.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
Any,
1313
Final,
1414
Literal,
15-
NotRequired,
1615
Self,
1716
TypeGuard,
1817
TypeVar,
@@ -22,7 +21,7 @@
2221
from cf_xarray.utils import parse_cf_standard_name_table
2322
from pydantic import AfterValidator, BaseModel, Field, model_validator
2423
from pydantic.experimental.missing_sentinel import MISSING
25-
from typing_extensions import Protocol, TypedDict, runtime_checkable
24+
from typing_extensions import Protocol, runtime_checkable
2625

2726
from eopf_geozarr.data_api.geozarr.projjson import ProjJSON # noqa: TC001
2827

@@ -40,29 +39,6 @@ class UNSET_TYPE:
4039
GEO_PROJ_VERSION: Final = "0.1"
4140

4241

43-
class ZarrConventionMetadata(BaseModel):
44-
uuid: str | MISSING = MISSING
45-
schema_url: str | MISSING = MISSING
46-
spec_url: str | MISSING = MISSING
47-
name: str | MISSING = MISSING
48-
description: str | MISSING = MISSING
49-
50-
@model_validator(mode="after")
51-
def ensure_identifiable(self) -> Self:
52-
if self.uuid is MISSING and self.schema_url is MISSING and self.spec_url is MISSING:
53-
raise ValueError("At least one of uuid, schema_url, or spec_url must be provided.")
54-
55-
return self
56-
57-
58-
class ZarrConventionMetadataJSON(TypedDict):
59-
uuid: NotRequired[str]
60-
schema_url: NotRequired[str]
61-
name: NotRequired[str]
62-
description: NotRequired[str]
63-
spec_url: NotRequired[str]
64-
65-
6642
class ProjAttrs(BaseModel, extra="allow"):
6743
"""
6844
Zarr attributes for coordinate reference system (CRS) encoding.

src/eopf_geozarr/data_api/geozarr/geoproj.py

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,16 @@
44

55
from __future__ import annotations
66

7-
from typing import Literal
8-
97
from pydantic import BaseModel, Field, model_validator
10-
from typing_extensions import TypedDict
8+
from zarr_cm import geo_proj
119

12-
from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata, is_none
10+
from eopf_geozarr.data_api.geozarr.common import is_none
1311
from eopf_geozarr.data_api.geozarr.projjson import ProjJSON # noqa: TC001
1412

15-
PROJ_UUID: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = "f17cb550-5864-4468-aeb7-f3180cfb622f"
16-
17-
18-
class ProjConvention(TypedDict):
19-
uuid: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"]
20-
name: Literal["proj:"]
21-
schema_url: Literal[
22-
"https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
23-
]
24-
spec_url: Literal["https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"]
25-
description: Literal["Coordinate reference system information for geospatial data"]
26-
27-
28-
class ProjConventionMetadata(ZarrConventionMetadata):
29-
uuid: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = PROJ_UUID
30-
name: Literal["proj:"] = "proj:"
31-
schema_url: Literal[
32-
"https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
33-
] = "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
34-
spec_url: Literal["https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"] = (
35-
"https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"
36-
)
37-
description: Literal["Coordinate reference system information for geospatial data"] = (
38-
"Coordinate reference system information for geospatial data"
39-
)
13+
PROJ_UUID = geo_proj.UUID
14+
15+
# Re-export the zarr-cm TypedDict for the convention metadata object
16+
ProjConvention = geo_proj.GeoProjAttrs
4017

4118

4219
class Proj(BaseModel):

src/eopf_geozarr/data_api/geozarr/multiscales/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Zarr multiscales convention support."""
22

33
from .geozarr import MultiscaleGroupAttrs, MultiscaleMeta
4-
from .zcm import MULTISCALE_CONVENTION_METADATA, Multiscales, ScaleLevel, ScaleLevelJSON
4+
from .zcm import Multiscales, ScaleLevel, ScaleLevelJSON
55

66
__all__ = [
7-
"MULTISCALE_CONVENTION_METADATA",
87
"MultiscaleGroupAttrs",
98
"MultiscaleMeta",
109
"Multiscales",

src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from pydantic import BaseModel, model_validator
66
from pydantic.experimental.missing_sentinel import MISSING
77
from typing_extensions import TypedDict
8-
9-
from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata # noqa: TC001
8+
from zarr_cm import ConventionMetadataObject # noqa: TC002
109

1110
from . import tms, zcm
1211

@@ -56,7 +55,7 @@ class MultiscaleGroupAttrs(BaseModel):
5655
multiscales: MultiscaleAttrs
5756
"""
5857

59-
zarr_conventions: tuple[ZarrConventionMetadata, ...] | MISSING = MISSING
58+
zarr_conventions: tuple[ConventionMetadataObject, ...] | MISSING = MISSING
6059
multiscales: MultiscaleMeta
6160

6261
_zcm_multiscales: zcm.Multiscales | None = None
Lines changed: 21 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,26 @@
11
from __future__ import annotations
22

3-
from typing import Final, Literal, NotRequired
4-
53
from pydantic import BaseModel, field_validator
64
from pydantic.experimental.missing_sentinel import MISSING
7-
from typing_extensions import TypedDict
8-
9-
from eopf_geozarr.data_api.geozarr.common import (
10-
ZarrConventionMetadata,
11-
ZarrConventionMetadataJSON,
12-
)
13-
14-
ConventionID = Literal["d35379db-88df-4056-af3a-620245f8e347"]
15-
CONVENTION_ID: Final[ConventionID] = "d35379db-88df-4056-af3a-620245f8e347"
16-
17-
ConventionSchemaURL = Literal[
18-
"https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json"
19-
]
20-
CONVENTION_SCHEMA_URL: Final[ConventionSchemaURL] = (
21-
"https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json"
22-
)
23-
24-
ConventionSpecURL = Literal["https://github.com/zarr-conventions/multiscales/blob/v1/README.md"]
25-
CONVENTION_SPEC_URL: Final[ConventionSpecURL] = (
26-
"https://github.com/zarr-conventions/multiscales/blob/v1/README.md"
27-
)
28-
29-
ConventionDescription = Literal["Multiscale layout of zarr datasets"]
30-
CONVENTION_DESCRIPTION: Final[ConventionDescription] = "Multiscale layout of zarr datasets"
31-
32-
ConventionName = Literal["multiscales"]
33-
CONVENTION_NAME: Final[ConventionName] = "multiscales"
34-
35-
36-
class MultiscaleConventionMetadata(ZarrConventionMetadata):
37-
uuid: ConventionID = CONVENTION_ID
38-
schema_url: ConventionSchemaURL = CONVENTION_SCHEMA_URL
39-
name: ConventionName = CONVENTION_NAME
40-
description: ConventionDescription = CONVENTION_DESCRIPTION
41-
spec_url: ConventionSpecURL = CONVENTION_SPEC_URL
42-
43-
44-
class MultiscaleConventionMetadataJSON(TypedDict):
45-
"""
46-
A TypedDict representation of the Multiscales convention metadata
47-
"""
5+
from zarr_cm import ConventionMetadataObject
6+
from zarr_cm import multiscales as multiscales_cm
487

49-
uuid: NotRequired[ConventionID]
50-
schema_url: NotRequired[ConventionSchemaURL]
51-
name: NotRequired[ConventionName]
52-
description: NotRequired[ConventionDescription]
53-
spec_url: NotRequired[ConventionSpecURL]
8+
# Convention constants from zarr-cm
9+
CONVENTION_ID = multiscales_cm.UUID
10+
CONVENTION_SCHEMA_URL = multiscales_cm.SCHEMA_URL
11+
CONVENTION_SPEC_URL = multiscales_cm.SPEC_URL
12+
CONVENTION_NAME = multiscales_cm.CMO["name"]
13+
CONVENTION_DESCRIPTION = multiscales_cm.CMO["description"]
5414

55-
56-
# A final dict representation of the Multiscales convention metadata
57-
MULTISCALE_CONVENTION_METADATA: Final[MultiscaleConventionMetadataJSON] = {
58-
"uuid": CONVENTION_ID,
59-
"schema_url": CONVENTION_SCHEMA_URL,
60-
"name": CONVENTION_NAME,
61-
"description": CONVENTION_DESCRIPTION,
62-
"spec_url": CONVENTION_SPEC_URL,
63-
}
15+
# Re-export zarr-cm TypedDicts
16+
TransformJSON = multiscales_cm.Transform
17+
ScaleLevelJSON = multiscales_cm.LayoutObject
18+
MultiscalesJSON = multiscales_cm.MultiscalesAttrs
19+
MultiscalesConventionAttrsJSON = multiscales_cm.MultiscalesConventionAttrs
6420

6521

6622
class ZarrConventionAttrs(BaseModel):
67-
zarr_conventions: tuple[ZarrConventionMetadata, ...]
23+
zarr_conventions: tuple[ConventionMetadataObject, ...]
6824

6925
model_config = {"extra": "allow"}
7026

@@ -74,11 +30,6 @@ class Transform(BaseModel):
7430
translation: tuple[float, ...] | MISSING = MISSING
7531

7632

77-
class TransformJSON(TypedDict):
78-
scale: NotRequired[tuple[float, ...]]
79-
translation: NotRequired[tuple[float, ...]]
80-
81-
8233
class ScaleLevel(BaseModel):
8334
asset: str
8435
derived_from: str | MISSING = MISSING
@@ -88,52 +39,29 @@ class ScaleLevel(BaseModel):
8839
model_config = {"extra": "allow"}
8940

9041

91-
class ScaleLevelJSON(TypedDict):
92-
asset: str
93-
derived_from: NotRequired[str]
94-
transform: TransformJSON
95-
resampling_method: NotRequired[str]
96-
97-
9842
class Multiscales(BaseModel):
9943
layout: tuple[ScaleLevel, ...]
10044
resampling_method: str | MISSING = MISSING
10145

10246
model_config = {"extra": "allow"}
10347

10448

105-
class MultiscalesJSON(TypedDict):
106-
version: NotRequired[str]
107-
layout: tuple[ScaleLevelJSON, ...]
108-
resampling_method: NotRequired[str]
109-
110-
11149
class MultiscalesAttrs(ZarrConventionAttrs):
11250
multiscales: Multiscales
11351
model_config = {"extra": "allow"}
11452

11553
@field_validator("zarr_conventions", mode="after")
11654
@classmethod
11755
def ensure_multiscales_convention(
118-
cls, value: tuple[ZarrConventionMetadata, ...]
119-
) -> tuple[ZarrConventionMetadata, ...]:
56+
cls, value: tuple[ConventionMetadataObject, ...]
57+
) -> tuple[ConventionMetadataObject, ...]:
12058
"""
12159
Iterate over the elements of zarr_conventions and check that at least one of them is
12260
multiscales
12361
"""
124-
success: bool = False
125-
errors: dict[int, ValueError] = {}
126-
for idx, convention_meta in enumerate(value):
127-
try:
128-
MultiscaleConventionMetadata(**convention_meta.model_dump())
129-
success = True
130-
except ValueError as e:
131-
errors[idx] = e
132-
if not success:
133-
raise ValueError("Multiscales convention not found. Errors: " + str(errors))
62+
expected_uuid = multiscales_cm.CMO["uuid"]
63+
if not any(c["uuid"] == expected_uuid for c in value):
64+
raise ValueError(
65+
f"Multiscales convention (uuid={expected_uuid}) not found in zarr_conventions"
66+
)
13467
return value
135-
136-
137-
class MultiscalesAttrsJSON(TypedDict):
138-
zarr_conventions: tuple[ZarrConventionMetadataJSON, ...]
139-
multiscales: MultiscalesJSON

src/eopf_geozarr/data_api/geozarr/spatial.py

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,15 @@
44

55
from __future__ import annotations
66

7-
from typing import Literal
8-
97
from pydantic import BaseModel, Field, model_validator
10-
from typing_extensions import TypedDict
11-
12-
from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata, is_none
13-
14-
SPATIAL_UUID: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"] = (
15-
"689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
16-
)
17-
18-
19-
class SpatialConvention(TypedDict):
20-
uuid: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"]
21-
name: Literal["spatial:"]
22-
schema_url: Literal[
23-
"https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
24-
]
25-
spec_url: Literal["https://github.com/zarr-conventions/spatial/blob/v1/README.md"]
26-
description: Literal["Spatial coordinate and transformation information"]
27-
28-
29-
class SpatialConventionMetadata(ZarrConventionMetadata):
30-
uuid: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"] = SPATIAL_UUID
31-
name: Literal["spatial:"] = "spatial:"
32-
schema_url: Literal[
33-
"https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
34-
] = "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
35-
spec_url: Literal["https://github.com/zarr-conventions/spatial/blob/v1/README.md"] = (
36-
"https://github.com/zarr-conventions/spatial/blob/v1/README.md"
37-
)
38-
description: Literal["Spatial coordinate and transformation information"] = (
39-
"Spatial coordinate and transformation information"
40-
)
8+
from zarr_cm import spatial as spatial_cm
9+
10+
from eopf_geozarr.data_api.geozarr.common import is_none
11+
12+
SPATIAL_UUID = spatial_cm.UUID
13+
14+
# Re-export the zarr-cm TypedDict for the convention metadata object
15+
SpatialConvention = spatial_cm.SpatialAttrs
4116

4217

4318
class Spatial(BaseModel):

0 commit comments

Comments
 (0)