|
14 | 14 | from typing import Any, Final, Literal |
15 | 15 |
|
16 | 16 | from pydantic import BaseModel, Field, field_validator, model_validator |
| 17 | +from zarr_cm import proj as _proj_cm |
17 | 18 |
|
18 | 19 | from geozarr_toolkit.conventions.common import ZarrConventionMetadata, is_none |
19 | 20 |
|
20 | | -PROJ_UUID: Final[Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"]] = ( |
21 | | - "f17cb550-5864-4468-aeb7-f3180cfb622f" |
22 | | -) |
23 | | -PROJ_SCHEMA_URL: Final[str] = ( |
24 | | - "https://raw.githubusercontent.com/zarr-conventions/proj/refs/tags/v0.1/schema.json" |
25 | | -) |
26 | | -PROJ_SPEC_URL: Final[str] = "https://github.com/zarr-conventions/proj/blob/v0.1/README.md" |
| 21 | +# Convention identity (UUID, schema/spec URLs, description) is re-exported from |
| 22 | +# zarr-cm so there is a single source of truth for it across the ecosystem. |
| 23 | +PROJ_UUID: Final[str] = _proj_cm.UUID |
| 24 | +PROJ_SCHEMA_URL: Final[str] = _proj_cm.SCHEMA_URL |
| 25 | +PROJ_SPEC_URL: Final[str] = _proj_cm.SPEC_URL |
| 26 | +PROJ_DESCRIPTION: Final[str] = _proj_cm.CMO["description"] |
27 | 27 |
|
28 | 28 |
|
29 | 29 | class ProjConventionMetadata(ZarrConventionMetadata): |
30 | 30 | """Metadata for the proj convention in zarr_conventions array.""" |
31 | 31 |
|
32 | | - uuid: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = PROJ_UUID |
| 32 | + uuid: str = PROJ_UUID |
| 33 | + # `name` is intentionally not sourced from `zarr_cm.proj.CMO`: that CMO |
| 34 | + # carries `"proj:"` (with a trailing colon), whereas the proj spec and |
| 35 | + # this toolkit use the bare convention name `"proj"`. |
33 | 36 | name: Literal["proj"] = "proj" |
34 | 37 | schema_url: str = PROJ_SCHEMA_URL |
35 | 38 | spec_url: str = PROJ_SPEC_URL |
36 | | - description: str = "Coordinate reference system information for geospatial data" |
| 39 | + description: str = PROJ_DESCRIPTION |
37 | 40 |
|
38 | 41 |
|
39 | 42 | _CODE_PATTERN = re.compile(r"^[A-Z]+:[0-9]+$") |
@@ -74,11 +77,16 @@ def validate_code_format(cls, v: str | None) -> str | None: |
74 | 77 |
|
75 | 78 | @model_validator(mode="after") |
76 | 79 | def validate_at_least_one_crs(self) -> Proj: |
77 | | - """Validate that at least one CRS field is provided.""" |
78 | | - if not any([self.code, self.wkt2, self.projjson]): |
79 | | - raise ValueError( |
80 | | - "At least one of proj:code, proj:wkt2, or proj:projjson must be provided" |
81 | | - ) |
| 80 | + """Validate that at least one CRS field is provided. |
| 81 | +
|
| 82 | + Delegates the spec-level "at least one of proj:code/proj:wkt2/ |
| 83 | + proj:projjson" rule to `zarr_cm.proj.validate` so the requirement |
| 84 | + stays defined in a single place (zarr-cm). The stricter |
| 85 | + `^[A-Z]+:[0-9]+$` code pattern (`validate_code_format`) and the |
| 86 | + pyproj resolution check (`validate_code_resolves`) are geozarr-toolkit |
| 87 | + additions layered on top. |
| 88 | + """ |
| 89 | + _proj_cm.validate(self.model_dump(by_alias=True, exclude_none=True)) |
82 | 90 | return self |
83 | 91 |
|
84 | 92 | @model_validator(mode="after") |
|
0 commit comments