diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 00f7ca57..b059bc53 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,19 +10,25 @@ permissions:
contents: read
jobs:
- pre-commit:
+ lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- with:
- persist-credentials: false
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
- with:
- python-version: '3.12'
- - name: Install pre-commit
- run: pip install pre-commit
- - name: Run pre-commit
- run: pre-commit run --all-files
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+ with:
+ python-version: "3.12"
+ - name: Install uv
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
+ with:
+ enable-cache: true
+ - name: Install dependencies
+ # The pyright pre-commit hook runs `uv run --frozen pyright`, so the
+ # project environment must be present.
+ run: uv sync --group dev --group test
+ - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
test:
runs-on: ${{ matrix.os }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f19c8a37..52e24ad7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,13 +11,12 @@ repos:
args: ["--fix", "--show-fixes"]
- id: ruff-format
- - repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.17.0
+ - repo: local
hooks:
- - id: mypy
- language_version: python
- exclude: tests/.*
- additional_dependencies:
- - types-attrs
- - typing-extensions>=4.15.0
- - pydantic>=2.12
+ - id: pyright
+ name: pyright
+ language: system
+ entry: uv run --frozen pyright
+ pass_filenames: false
+ always_run: true
+ types_or: [python, pyi]
diff --git a/pyproject.toml b/pyproject.toml
index 2d371778..bd22dce3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,7 +36,7 @@ dependencies = [
"rioxarray>=0.13.0",
"cf-xarray>=0.8.0",
"typing-extensions>=4.15.0",
- "zarr-cm>=0.2.0",
+ "zarr-cm>=0.4.1",
"aiohttp>=3.14.0",
"s3fs>=2024.6.0",
"boto3>=1.34.0",
@@ -48,7 +48,7 @@ dependencies = [
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
- "mypy>=1.0.0",
+ "pyright>=1.1.390",
"pre-commit>=3.0.0",
"bandit[toml]>=1.7.0",
]
@@ -149,40 +149,19 @@ ignore = [
"TRY003", # Long exception messages outside class - common pattern
]
-[tool.mypy]
-python_version = "3.12"
-warn_return_any = true
-warn_unused_configs = true
-disallow_untyped_defs = true
-disallow_incomplete_defs = true
-check_untyped_defs = true
-disallow_untyped_decorators = true
-no_implicit_optional = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-warn_no_return = true
-warn_unreachable = true
-strict_equality = true
-plugins = ["pydantic.mypy"]
-
-[tool.pydantic-mypy]
-init_forbid_extra = true
-init_typed = true
-warn_required_dynamic_aliases = true
-warn_untyped_fields = true
-
-[[tool.mypy.overrides]]
-module = ["zarr.*", "xarray.*", "rioxarray.*", "cf_xarray.*", "dask.*"]
-ignore_missing_imports = true
-
-[[tool.mypy.overrides]]
-module = [
- "eopf_geozarr.data_api.s1",
- "eopf_geozarr.data_api.s2",
- "eopf_geozarr.data_api.geozarr.v2",
- "eopf_geozarr.data_api.geozarr.store",
-]
-disable_error_code = ["valid-type"]
+[tool.pyright]
+include = ["src", "tests"]
+pythonVersion = "3.12"
+typeCheckingMode = "standard"
+# Several runtime deps ship no type stubs; we can't fix their types here, so
+# don't report missing stubs/sources for them. (Imports still resolve because
+# the packages are installed in the environment.)
+reportMissingTypeStubs = false
+reportMissingModuleSource = false
+# Match the strictness we relied on under mypy.
+reportUnnecessaryTypeIgnoreComment = true
+reportReturnType = "error"
+reportUnnecessaryCast = "error"
[tool.pytest.ini_options]
minversion = "7.0"
diff --git a/src/eopf_geozarr/cli.py b/src/eopf_geozarr/cli.py
index 53c3ee21..805c9237 100755
--- a/src/eopf_geozarr/cli.py
+++ b/src/eopf_geozarr/cli.py
@@ -354,7 +354,7 @@ def format_data_vars(data_vars: dict[str, xr.DataArray]) -> str:
# Fallback to simple format if xarray HTML fails
vars_html = []
for name, var in data_vars.items():
- dims_str = format_dimensions(dict(zip(var.dims, var.shape, strict=True)))
+ dims_str = format_dimensions(dict(zip(map(str, var.dims), var.shape, strict=True)))
dtype_str = str(var.dtype)
vars_html.append(
f"""
@@ -450,7 +450,7 @@ def render_node(node: xr.DataTree, path: str = "", level: int = 0) -> str:
Variables
- {format_data_vars(node.ds.data_vars)}
+ {format_data_vars({str(k): v for k, v in node.ds.data_vars.items()})}
"""
diff --git a/src/eopf_geozarr/conversion/fs_utils.py b/src/eopf_geozarr/conversion/fs_utils.py
index f55a3ab0..b31ed878 100644
--- a/src/eopf_geozarr/conversion/fs_utils.py
+++ b/src/eopf_geozarr/conversion/fs_utils.py
@@ -3,7 +3,7 @@
import json
import os
from collections.abc import Mapping
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Final, Literal, cast
from urllib.parse import urlparse
import s3fs
@@ -16,6 +16,8 @@
if TYPE_CHECKING:
import xarray as xr
+ZarrOpenMode = Literal["r", "r+", "a", "w", "w-"]
+
_MISSING = object() # sentinel for missing optional attrs
@@ -52,18 +54,44 @@ def replace_json_invalid_floats(obj: object) -> object:
return obj
+def _sanitize_attrs(attrs: Mapping[str, object]) -> dict[str, object]:
+ """Sanitize an attributes mapping, returning a typed ``dict``.
+
+ Wraps :func:`replace_json_invalid_floats` (which is typed ``object -> object``)
+ and verifies the dict-in/dict-out invariant at runtime instead of casting.
+ """
+ sanitized = replace_json_invalid_floats(dict(attrs))
+ if not isinstance(sanitized, dict): # pragma: no cover - invariant guard
+ raise TypeError(f"expected a dict after sanitizing attrs, got {type(sanitized).__name__}")
+ return sanitized
+
+
+_ZARR_MODES: Final = ("r", "r+", "a", "w", "w-")
+
+
+def _zarr_mode(mode: str) -> ZarrOpenMode:
+ """Validate *mode* against zarr's accepted access modes and narrow its type.
+
+ Checks the value at runtime instead of casting a bare ``str`` to the
+ ``Literal`` zarr expects.
+ """
+ if mode not in _ZARR_MODES:
+ raise ValueError(f"Invalid zarr access mode {mode!r}; expected one of {_ZARR_MODES}")
+ return mode
+
+
class NanCompatibleJSONEncoder(json.JSONEncoder):
"""
Custom JSON encoder that converts NaN, Inf, -Inf values to JSON-safe equivalents
to ensure valid JSON output.
"""
- def encode(self, obj: Any) -> str:
+ def encode(self, o: Any) -> str:
"""
Encode object to JSON string, converting NaN values to "NaN".
"""
- converted_obj = replace_json_invalid_floats(obj)
+ converted_obj = replace_json_invalid_floats(o)
return super().encode(converted_obj)
@@ -87,7 +115,7 @@ def sanitize_dataset_attributes(ds: "xr.Dataset") -> "xr.Dataset":
ds_clean = ds.copy()
# Sanitize dataset attributes
- ds_clean.attrs = replace_json_invalid_floats(ds_clean.attrs)
+ ds_clean.attrs = _sanitize_attrs(ds_clean.attrs)
# Sanitize variable attributes
for var_name in ds_clean.data_vars:
@@ -95,14 +123,14 @@ def sanitize_dataset_attributes(ds: "xr.Dataset") -> "xr.Dataset":
# Preserve _FillValue as-is — xarray encodes it via FillValueCoder on write;
# converting np.nan to the string "NaN" would break FillValueCoder.encode.
fill_value = var.attrs.get("_FillValue", _MISSING)
- var.attrs = replace_json_invalid_floats(var.attrs)
+ var.attrs = _sanitize_attrs(var.attrs)
if fill_value is not _MISSING:
var.attrs["_FillValue"] = fill_value
# Sanitize coordinate attributes
for coord_name in ds_clean.coords:
coord = ds_clean[coord_name]
- coord.attrs = replace_json_invalid_floats(coord.attrs)
+ coord.attrs = _sanitize_attrs(coord.attrs)
return ds_clean
@@ -405,7 +433,12 @@ def open_s3_zarr_group(s3_path: str, mode: str = "r", **s3_kwargs: Any) -> zarr.
Zarr group
"""
storage_options = get_s3_storage_options(s3_path, **s3_kwargs)
- return zarr.open_group(s3_path, mode=mode, zarr_format=3, storage_options=storage_options)
+ return zarr.open_group(
+ s3_path,
+ mode=_zarr_mode(mode),
+ zarr_format=3,
+ storage_options=cast("dict[str, object]", storage_options),
+ )
def get_s3_credentials_info() -> S3Credentials:
@@ -589,4 +622,9 @@ def open_zarr_group(path: str, mode: str = "r", **kwargs: Any) -> zarr.Group:
Zarr group
"""
storage_options = get_storage_options(path, **kwargs)
- return zarr.open_group(path, mode=mode, zarr_format=3, storage_options=storage_options)
+ return zarr.open_group(
+ path,
+ mode=_zarr_mode(mode),
+ zarr_format=3,
+ storage_options=cast("dict[str, object] | None", storage_options),
+ )
diff --git a/src/eopf_geozarr/conversion/geozarr.py b/src/eopf_geozarr/conversion/geozarr.py
index e2a13b37..8f9c68f4 100644
--- a/src/eopf_geozarr/conversion/geozarr.py
+++ b/src/eopf_geozarr/conversion/geozarr.py
@@ -18,25 +18,23 @@
import os
import time
from collections.abc import Hashable, Iterable, Mapping, Sequence
-from typing import Any
+from typing import TYPE_CHECKING, Any, cast
import numpy as np
import structlog
import xarray as xr
import zarr
+import zarr.core.common
+import zarr.core.group
from pyproj import CRS
from rasterio.warp import calculate_default_transform
from zarr.codecs import BloscCodec
from zarr.core.sync import sync
from zarr.storage import StoreLike
from zarr.storage._common import make_store_path
-from zarr_cm import geo_proj
-from zarr_cm import multiscales as multiscales_cm
-from zarr_cm import spatial as spatial_cm
from eopf_geozarr.data_api.geozarr.multiscales import zcm
from eopf_geozarr.data_api.geozarr.multiscales.geozarr import (
- MultiscaleGroupAttrs,
MultiscaleMeta,
)
from eopf_geozarr.types import (
@@ -52,6 +50,10 @@
from .fs_utils import sanitize_dataset_attributes
from .sentinel1_reprojection import reproject_sentinel1_with_gcps
+if TYPE_CHECKING:
+ from zarr.core.common import JSON
+ from zarr_cm import MultiscalesAttrs
+
log = structlog.get_logger()
@@ -372,7 +374,8 @@ def iterative_copy(
consolidated=False,
zarr_format=3,
encoding=encoding,
- storage_options=storage_options,
+ # xarray stubs type storage_options as dict[str, str]; S3FsOptions is broader
+ storage_options=storage_options, # pyright: ignore[reportArgumentType]
)
dt_result[relative_path] = xr.DataTree(ds)
@@ -507,7 +510,7 @@ def write_geozarr_group(
# Create GeoZarr-spec compliant multiscales
if _is_sentinel1(dt_input):
assert gcp_group is not None, "GCP group required for processing Sentinel-1"
- ds_gcp = dt_input[gcp_group].to_dataset()
+ ds_gcp: xr.Dataset | None = dt_input[gcp_group].to_dataset()
# For Sentinel-1, ds_gcp is set to None since data is now reprojected and doesn't need GCP handling
ds_gcp = None
else:
@@ -585,7 +588,7 @@ def create_geozarr_compliant_multiscales(
compressor = BloscCodec(cname="zstd", clevel=3, shuffle="shuffle")
# Get spatial information from the first data variable
- data_vars = [var for var in ds.data_vars if not utils.is_grid_mapping_variable(ds, var)]
+ data_vars = [var for var in ds.data_vars if not utils.is_grid_mapping_variable(ds, str(var))]
if not data_vars:
return {}
@@ -672,26 +675,27 @@ def _spatial_transform_for(
scale_level_data["spatial:transform"] = spatial_tf
layout.append(zcm.ScaleLevel(**scale_level_data))
- multiscale_attrs = MultiscaleGroupAttrs(
- zarr_conventions=(multiscales_cm.CMO, spatial_cm.CMO, geo_proj.CMO),
- multiscales=MultiscaleMeta(
- layout=layout,
- resampling_method="average",
- ),
+ # Validate + serialize the multiscales block via the project model (which
+ # also covers the ZCM/TMS duality), then hand all conventions to zarr-cm,
+ # which validates each and emits the matching CMOs in order (multiscales,
+ # spatial, proj). proj is included only when a CRS is available.
+ multiscales_data = cast(
+ "MultiscalesAttrs",
+ MultiscaleMeta(layout=tuple(layout), resampling_method="average").model_dump(),
+ )
+ attrs_to_write = utils.build_convention_attrs(
+ multiscales=multiscales_data,
+ spatial={
+ "spatial:dimensions": ["y", "x"],
+ "spatial:bbox": list(native_bounds),
+ "spatial:registration": "pixel",
+ },
+ crs=native_crs or None,
)
- attrs_to_write = multiscale_attrs.model_dump()
- if native_crs and native_bounds:
- attrs_to_write["spatial:dimensions"] = ["y", "x"]
- attrs_to_write["spatial:bbox"] = list(native_bounds)
- attrs_to_write["spatial:registration"] = "pixel"
- if hasattr(native_crs, "to_epsg") and native_crs.to_epsg():
- attrs_to_write["proj:code"] = f"EPSG:{native_crs.to_epsg()}"
- elif hasattr(native_crs, "to_wkt"):
- attrs_to_write["proj:wkt2"] = native_crs.to_wkt()
group_path = fs_utils.normalize_path(f"{output_path}/{group_name.lstrip('/')}")
zarr_group = fs_utils.open_zarr_group(group_path, mode="r+")
- zarr_group.attrs.update(attrs_to_write)
+ zarr_group.attrs.update(cast("dict[str, JSON]", attrs_to_write))
log.info("Added multiscales metadata to group %s", group_name)
@@ -756,7 +760,8 @@ def _spatial_transform_for(
zarr_format=3,
encoding=encoding,
align_chunks=align_chunks_flag,
- storage_options=storage_options,
+ # xarray stubs type storage_options as dict[str, str]; S3FsOptions is broader
+ storage_options=storage_options, # pyright: ignore[reportArgumentType]
)
overview_datasets[asset_name] = overview_ds
@@ -1011,7 +1016,7 @@ def write_dataset_band_by_band_with_validation(
)
# Get data variables
- data_vars = [var for var in ds.data_vars if not utils.is_grid_mapping_variable(ds, var)]
+ data_vars = [var for var in ds.data_vars if not utils.is_grid_mapping_variable(ds, str(var))]
successful_vars = []
failed_vars = []
@@ -1044,9 +1049,10 @@ def cleanup_prefix(prefix: str) -> None:
for var in data_vars:
# Check if this variable already exists and is valid
if not force_overwrite and store_exists:
- if utils.validate_existing_band_data(existing_dataset, var, ds):
+ assert existing_dataset is not None # guaranteed by store_exists
+ if utils.validate_existing_band_data(existing_dataset, str(var), ds):
ds.drop_vars(str(var))
- ds[var] = existing_dataset[var] # type: ignore[index]
+ ds[var] = existing_dataset[var]
log.info("✅ Band %s already exists and is valid, skipping.", var)
skipped_vars.append(var)
successful_vars.append(var)
@@ -1114,7 +1120,8 @@ def cleanup_prefix(prefix: str) -> None:
consolidated=False,
zarr_format=3,
encoding=var_encoding,
- storage_options=store_storage_options,
+ # xarray stubs type storage_options as dict[str, str]; S3FsOptions is broader
+ storage_options=store_storage_options, # pyright: ignore[reportArgumentType]
)
log.info(" ✅ Successfully wrote", var=var)
@@ -1388,15 +1395,17 @@ def _create_encoding(
for var in ds.data_vars:
if hasattr(ds[var].data, "chunks"):
current_chunks = ds[var].chunks
+ assert current_chunks is not None # guaranteed by hasattr(..., "chunks")
if len(current_chunks) >= 2:
chunking = tuple(
current_chunks[i][0] if len(current_chunks[i]) > 0 else ds[var].shape[i]
for i in range(len(current_chunks))
)
else:
- chunking = (
- current_chunks[0][0] if len(current_chunks[0]) > 0 else ds[var].shape[0],
- )
+ chunks_list = list(current_chunks)
+ first_chunks = list(chunks_list[0])
+ first_shape = list(ds[var].shape)
+ chunking = (first_chunks[0] if len(first_chunks) > 0 else first_shape[0],)
else:
data_shape = ds[var].shape
if len(data_shape) >= 2:
@@ -1404,7 +1413,7 @@ def _create_encoding(
chunk_x = min(spatial_chunk, data_shape[-1])
chunking = (1, chunk_y, chunk_x) if len(data_shape) == 3 else (chunk_y, chunk_x)
else:
- chunking = (min(spatial_chunk, data_shape[-1]),)
+ chunking = (min(spatial_chunk, list(data_shape)[-1]),)
var_encoding: XarrayEncodingJSON = {
"compressors": [compressor],
@@ -1429,7 +1438,7 @@ def _create_geozarr_encoding(
encoding: dict[Hashable, XarrayEncodingJSON] = {}
chunks: tuple[int, ...]
for var in ds.data_vars:
- if utils.is_grid_mapping_variable(ds, var):
+ if utils.is_grid_mapping_variable(ds, str(var)):
encoding[var] = {"compressors": None}
else:
data_shape = ds[var].shape
@@ -1480,7 +1489,7 @@ def _create_geozarr_encoding(
)
else:
# For 1D data, use the full dimension
- shards = (data_shape[0],)
+ shards = (next(iter(data_shape)),)
log.info(
" 🔧 Sharding config",
var=var,
@@ -1641,7 +1650,7 @@ def _add_grid_mapping_variable(
# Ensure all data variables have the grid_mapping attribute
for var_name in overview_ds.data_vars:
if (
- not utils.is_grid_mapping_variable(overview_ds, var_name)
+ not utils.is_grid_mapping_variable(overview_ds, str(var_name))
and "grid_mapping" not in overview_ds[var_name].attrs
):
overview_ds[var_name].attrs["grid_mapping"] = grid_mapping_var_name
@@ -1695,4 +1704,16 @@ def _is_sentinel1(dt: xr.DataTree) -> bool:
def get_zarr_group(data: xr.DataTree) -> zarr.Group:
- return data._close.__self__.zarr_group
+ # `_close` is a bound method of the backend store on an opened DataTree;
+ # `__self__` retrieves that store, which exposes `zarr_group`. These are
+ # xarray/zarr internals without public type information, so resolve them
+ # defensively and verify the result is actually a zarr.Group.
+ close = data._close
+ store = getattr(close, "__self__", None)
+ group = getattr(store, "zarr_group", None)
+ if not isinstance(group, zarr.Group):
+ raise TypeError(
+ "Could not resolve a zarr.Group from the DataTree backend "
+ f"(got {type(group).__name__}); the xarray/zarr internals may have changed."
+ )
+ return group
diff --git a/src/eopf_geozarr/conversion/sentinel1_reprojection.py b/src/eopf_geozarr/conversion/sentinel1_reprojection.py
index be90abe3..7681e931 100644
--- a/src/eopf_geozarr/conversion/sentinel1_reprojection.py
+++ b/src/eopf_geozarr/conversion/sentinel1_reprojection.py
@@ -5,8 +5,11 @@
to geographic coordinates (lat/lon) using Ground Control Points (GCPs).
"""
+from typing import Any
+
import numpy as np
import rasterio
+import rasterio.control # Import submodule for GroundControlPoint attribute access
import rioxarray # noqa: F401 # Import to enable .rio accessor
import structlog
import xarray as xr
@@ -74,6 +77,9 @@ def reproject_sentinel1_with_gcps(
gcps=gcps,
)
+ # calculate_default_transform sizes the grid, so width and height are populated
+ assert width is not None
+ assert height is not None
log.info("Calculated target dimensions", width=width, height=height)
log.info("Transform", transform=str(transform))
@@ -101,8 +107,13 @@ def reproject_sentinel1_with_gcps(
data_vars=reprojected_data_vars, coords=target_coords, attrs=ds.attrs.copy()
)
- # Set CRS information
+ # Set CRS information. `rio.write_crs` is untyped (returns Any), so verify
+ # the result is a Dataset rather than asserting it with a cast.
reprojected_ds = reprojected_ds.rio.write_crs(target_crs)
+ if not isinstance(reprojected_ds, xr.Dataset):
+ raise TypeError(
+ f"expected an xarray.Dataset after write_crs, got {type(reprojected_ds).__name__}"
+ )
log.info("✅ Successfully reprojected Sentinel-1 data", target_crs=target_crs)
return reprojected_ds
@@ -178,7 +189,7 @@ def _create_target_coordinates(
}
-def _determine_nodata_value(data_var: xr.DataArray) -> float | np.floating:
+def _determine_nodata_value(data_var: xr.DataArray) -> float:
"""
Determine appropriate nodata value based on data type and existing attributes.
@@ -289,7 +300,7 @@ def _reproject_2d_array(
# Initialize destination array with nodata values
if np.isnan(nodata_value):
dst_array = np.full((dst_height, dst_width), np.nan, dtype=np.float32)
- dst_dtype = np.float32
+ dst_dtype: np.dtype[Any] | type[np.floating[Any]] = np.float32
else:
dst_array = np.full((dst_height, dst_width), nodata_value, dtype=src_array.dtype)
dst_dtype = src_array.dtype
diff --git a/src/eopf_geozarr/conversion/utils.py b/src/eopf_geozarr/conversion/utils.py
index e7125c97..ff5e8d2c 100644
--- a/src/eopf_geozarr/conversion/utils.py
+++ b/src/eopf_geozarr/conversion/utils.py
@@ -1,15 +1,77 @@
"""Utility functions for GeoZarr conversion."""
-from typing import Any
+from typing import Any, Protocol, cast, runtime_checkable
import numpy as np
import rasterio # noqa: F401 # Import to enable .rio accessor
import structlog
import xarray as xr
+import zarr_cm
+from zarr_cm import GeoProjAttrs, MultiConventionAttrs, MultiscalesAttrs, SpatialAttrs
log = structlog.get_logger()
+@runtime_checkable
+class CRSLike(Protocol):
+ """A coordinate reference system that can serialize to EPSG/WKT2.
+
+ Both ``pyproj.CRS`` and ``rasterio.crs.CRS`` satisfy this; the conversion
+ code accepts either, so we depend on the shared interface rather than a
+ concrete class.
+ """
+
+ def to_epsg(self) -> int | None: ...
+
+ def to_wkt(self) -> str: ...
+
+
+def proj_attrs_for_crs(crs: CRSLike | None) -> GeoProjAttrs:
+ """Build the ``proj`` convention data keys for a CRS.
+
+ Prefers an EPSG code (``proj:code``) and falls back to WKT2
+ (``proj:wkt2``). Returns an empty mapping when *crs* is ``None`` or exposes
+ no EPSG code.
+ """
+ if crs is None:
+ return GeoProjAttrs()
+ epsg = crs.to_epsg()
+ if epsg:
+ return GeoProjAttrs({"proj:code": f"EPSG:{epsg}"})
+ return GeoProjAttrs({"proj:wkt2": crs.to_wkt()})
+
+
+def build_convention_attrs(
+ *,
+ spatial: SpatialAttrs,
+ crs: CRSLike | None,
+ multiscales: MultiscalesAttrs | None = None,
+) -> MultiConventionAttrs:
+ """Build validated multiscales + ``spatial`` + ``proj`` convention attributes.
+
+ Delegates to :func:`zarr_cm.create_many`, which validates each convention's
+ data and emits the matching convention-metadata objects into a combined
+ ``zarr_conventions`` array. The CMOs are ordered multiscales (if present),
+ then spatial, then proj. *spatial* holds the ``spatial:*`` keys; the proj
+ keys are derived from *crs* via :func:`proj_attrs_for_crs`.
+
+ The proj convention is only included when *crs* yields a usable CRS
+ representation; otherwise only the other conventions are emitted (a proj
+ convention with no CRS field is invalid).
+ """
+ conventions: dict[zarr_cm.ConventionName, MultiscalesAttrs | SpatialAttrs | GeoProjAttrs] = {}
+ if multiscales is not None:
+ conventions["multiscales"] = multiscales
+ conventions["spatial"] = spatial
+ proj = proj_attrs_for_crs(crs)
+ if proj:
+ conventions["geo-proj"] = proj
+ # create_many validates each convention and emits its CMO. It returns a
+ # generic JSON dict; narrow to the combined convention TypedDict.
+ result = zarr_cm.create_many(conventions)
+ return cast("MultiConventionAttrs", result)
+
+
# Sentinel: distinguish "no explicit fill_value" from a legitimate `None`.
UNSET: Any = object()
diff --git a/src/eopf_geozarr/data_api/geozarr/common.py b/src/eopf_geozarr/data_api/geozarr/common.py
index 8236611e..cbbbd90c 100644
--- a/src/eopf_geozarr/data_api/geozarr/common.py
+++ b/src/eopf_geozarr/data_api/geozarr/common.py
@@ -80,7 +80,7 @@ class BaseDataArrayAttrs(BaseModel, extra="allow"):
----------
"""
- grid_mapping: str | MISSING = MISSING # type: ignore[valid-type]
+ grid_mapping: str | MISSING = MISSING
class GridMappingAttrs(BaseModel, extra="allow"):
diff --git a/src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py b/src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py
index d1a5efd3..e507ae3e 100644
--- a/src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py
+++ b/src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py
@@ -5,6 +5,9 @@
from pydantic import BaseModel, model_validator
from pydantic.experimental.missing_sentinel import MISSING
from typing_extensions import TypedDict
+
+# Runtime import (not TYPE_CHECKING): pydantic resolves this annotation when
+# building MultiscaleGroupAttrs, so the name must exist at runtime.
from zarr_cm import ConventionMetadataObject # noqa: TC002
from . import tms, zcm
@@ -16,17 +19,17 @@ class MultiscaleMeta(BaseModel):
or ZCM multiscale metadata
"""
- layout: tuple[zcm.ScaleLevel, ...] | MISSING = MISSING # type: ignore[valid-type]
- resampling_method: str | MISSING = MISSING # type: ignore[valid-type]
- tile_matrix_set: tms.TileMatrixSet | MISSING = MISSING # type: ignore[valid-type]
- tile_matrix_limits: dict[str, tms.TileMatrixLimit] | MISSING = MISSING # type: ignore[valid-type]
+ layout: tuple[zcm.ScaleLevel, ...] | MISSING = MISSING
+ resampling_method: str | MISSING = MISSING
+ tile_matrix_set: tms.TileMatrixSet | MISSING = MISSING
+ tile_matrix_limits: dict[str, tms.TileMatrixLimit] | MISSING = MISSING
@model_validator(mode="after")
def valid_zcm(self) -> Self:
"""
Ensure that the ZCM metadata, if present, is valid
"""
- if self.layout is not MISSING: # type: ignore[comparison-overlap]
+ if self.layout is not MISSING:
zcm.Multiscales(**self.model_dump())
return self
@@ -36,7 +39,7 @@ def valid_tms(self) -> Self:
"""
Ensure that the TMS metadata, if present, is valid
"""
- if self.tile_matrix_set is not MISSING: # type: ignore[comparison-overlap]
+ if self.tile_matrix_set is not MISSING:
tms.Multiscales(**self.model_dump())
return self
@@ -55,7 +58,7 @@ class MultiscaleGroupAttrs(BaseModel):
multiscales: MultiscaleAttrs
"""
- zarr_conventions: tuple[ConventionMetadataObject, ...] | MISSING = MISSING # type: ignore[valid-type]
+ zarr_conventions: tuple[ConventionMetadataObject, ...] | MISSING = MISSING
multiscales: MultiscaleMeta
_zcm_multiscales: zcm.Multiscales | None = None
@@ -67,15 +70,18 @@ def valid_zcm_and_tms(self) -> Self:
Ensure that the ZCM metadata, if present, is valid, and that TMS metadata, if present,
is valid, and that at least one of the two is present.
"""
- if self.zarr_conventions is not MISSING: # type: ignore[comparison-overlap]
+ if self.zarr_conventions is not MISSING:
self._zcm_multiscales = zcm.Multiscales(
layout=self.multiscales.layout,
resampling_method=self.multiscales.resampling_method,
)
- if self.multiscales.tile_matrix_limits is not MISSING: # type: ignore[comparison-overlap]
+ if self.multiscales.tile_matrix_limits is not MISSING:
self._tms_multiscales = tms.Multiscales(
tile_matrix_limits=self.multiscales.tile_matrix_limits,
- resampling_method=self.multiscales.resampling_method, # type: ignore[arg-type]
+ # ``resampling_method`` is typed ``str | MISSING`` here but tms.Multiscales
+ # constrains it to the ``ResamplingMethod`` literal; pydantic validates the
+ # value at runtime.
+ resampling_method=self.multiscales.resampling_method, # pyright: ignore[reportArgumentType]
tile_matrix_set=self.multiscales.tile_matrix_set,
)
if self._tms_multiscales is None and self._zcm_multiscales is None:
diff --git a/src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py b/src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py
index 650ea07c..1c8f07cd 100644
--- a/src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py
+++ b/src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py
@@ -9,8 +9,12 @@
CONVENTION_ID = multiscales_cm.UUID
CONVENTION_SCHEMA_URL = multiscales_cm.SCHEMA_URL
CONVENTION_SPEC_URL = multiscales_cm.SPEC_URL
-CONVENTION_NAME = multiscales_cm.CMO["name"]
-CONVENTION_DESCRIPTION = multiscales_cm.CMO["description"]
+_CONVENTION_NAME = multiscales_cm.CMO.get("name")
+assert _CONVENTION_NAME is not None
+CONVENTION_NAME = _CONVENTION_NAME
+_CONVENTION_DESCRIPTION = multiscales_cm.CMO.get("description")
+assert _CONVENTION_DESCRIPTION is not None
+CONVENTION_DESCRIPTION = _CONVENTION_DESCRIPTION
# Re-export zarr-cm TypedDicts
TransformJSON = multiscales_cm.Transform
@@ -26,22 +30,22 @@ class ZarrConventionAttrs(BaseModel):
class Transform(BaseModel):
- scale: tuple[float, ...] | MISSING = MISSING # type: ignore[valid-type]
- translation: tuple[float, ...] | MISSING = MISSING # type: ignore[valid-type]
+ scale: tuple[float, ...] | MISSING = MISSING
+ translation: tuple[float, ...] | MISSING = MISSING
class ScaleLevel(BaseModel):
asset: str
- derived_from: str | MISSING = MISSING # type: ignore[valid-type]
- transform: Transform | MISSING = MISSING # type: ignore[valid-type]
- resampling_method: str | MISSING = MISSING # type: ignore[valid-type]
+ derived_from: str | MISSING = MISSING
+ transform: Transform | MISSING = MISSING
+ resampling_method: str | MISSING = MISSING
model_config = {"extra": "allow"}
class Multiscales(BaseModel):
layout: tuple[ScaleLevel, ...]
- resampling_method: str | MISSING = MISSING # type: ignore[valid-type]
+ resampling_method: str | MISSING = MISSING
model_config = {"extra": "allow"}
@@ -59,8 +63,9 @@ def ensure_multiscales_convention(
Iterate over the elements of zarr_conventions and check that at least one of them is
multiscales
"""
- expected_uuid = multiscales_cm.CMO["uuid"]
- if not any(c["uuid"] == expected_uuid for c in value):
+ expected_uuid = multiscales_cm.CMO.get("uuid")
+ assert expected_uuid is not None
+ if not any(c.get("uuid") == expected_uuid for c in value):
raise ValueError(
f"Multiscales convention (uuid={expected_uuid}) not found in zarr_conventions"
)
diff --git a/src/eopf_geozarr/data_api/geozarr/store.py b/src/eopf_geozarr/data_api/geozarr/store.py
index 61a6a443..0835ed25 100644
--- a/src/eopf_geozarr/data_api/geozarr/store.py
+++ b/src/eopf_geozarr/data_api/geozarr/store.py
@@ -91,13 +91,19 @@ class GeoZarrScaleLevel(ScaleLevel):
class GeoZarrMultiscaleMeta(MultiscaleMeta):
"""Multiscale metadata where every layout entry is a `GeoZarrScaleLevel`."""
- layout: tuple[GeoZarrScaleLevel, ...]
+ # Intentionally tightens the base ``layout`` field: ``GeoZarrScaleLevel`` is a
+ # subclass of ``ScaleLevel`` and the optional ``MISSING`` default is dropped to make
+ # the field mandatory in this store-level profile. pyright flags the narrowed,
+ # now-required override on a mutable (invariant) field.
+ layout: tuple[GeoZarrScaleLevel, ...] # pyright: ignore[reportGeneralTypeIssues, reportIncompatibleVariableOverride]
class GeoZarrMultiscaleGroupAttrs(MultiscaleGroupAttrs):
"""Multiscale group attributes with a mandatory `spatial:bbox`."""
- multiscales: GeoZarrMultiscaleMeta
+ # Intentionally tightens the base ``multiscales`` field to the ``GeoZarrMultiscaleMeta``
+ # subclass; pyright flags the narrowed override on a mutable (invariant) field.
+ multiscales: GeoZarrMultiscaleMeta # pyright: ignore[reportIncompatibleVariableOverride]
spatial_bbox: list[float] = Field(alias="spatial:bbox", min_length=4, max_length=4)
model_config = ConfigDict(
diff --git a/src/eopf_geozarr/data_api/geozarr/v2.py b/src/eopf_geozarr/data_api/geozarr/v2.py
index 6e889db7..772f13d0 100644
--- a/src/eopf_geozarr/data_api/geozarr/v2.py
+++ b/src/eopf_geozarr/data_api/geozarr/v2.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Literal, Self
+from typing import TYPE_CHECKING, Any, Literal, Self, cast
from pydantic import ConfigDict, Field, model_validator
from pydantic_zarr.v2 import ArraySpec, GroupSpec, auto_attributes
@@ -10,7 +10,9 @@
from eopf_geozarr.data_api.geozarr.common import (
BaseDataArrayAttrs,
DatasetAttrs,
+ DatasetLike,
GridMappingAttrs,
+ GroupLike,
check_grid_mapping,
check_valid_coordinates,
)
@@ -52,8 +54,11 @@ class DataArray(ArraySpec[DataArrayAttrs]):
https://github.com/zarr-developers/geozarr-spec/blob/main/geozarr-spec.md#geozarr-dataarray
"""
+ # The override intentionally widens the accepted argument types (e.g. plain mappings for
+ # ``attributes``) and adds a ``dimension_names`` parameter, so the signature is not a strict
+ # subtype of the parent's. This is by design and does not change runtime behavior.
@classmethod
- def from_array(
+ def from_array( # type: ignore[override]
cls,
array: Any,
chunks: tuple[int, ...] | Literal["auto"] = "auto",
@@ -71,13 +76,15 @@ def from_array(
auto_attrs = dict(auto_attributes(array)) if attributes == "auto" else dict(attributes)
if dimension_names != "auto":
auto_attrs = auto_attrs | {XARRAY_DIMS_KEY: tuple(dimension_names)}
- return super().from_array( # type: ignore[no-any-return]
+ # ``auto_attrs``/``fill_value``/``filters`` are validated/coerced by pydantic at
+ # construction time; cast to the parent's declared types to satisfy the static checker.
+ return super().from_array(
array=array,
chunks=chunks,
- attributes=auto_attrs,
- fill_value=fill_value,
+ attributes=cast("Literal['auto'] | DataArrayAttrs", auto_attrs),
+ fill_value=cast("Literal['auto'] | float | None", fill_value),
order=order,
- filters=filters,
+ filters=cast("Literal['auto'] | list[dict[str, Any]] | None", filters),
dimension_separator=dimension_separator,
compressor=compressor,
)
@@ -94,7 +101,7 @@ def check_array_dimensions(self) -> Self:
@property
def array_dimensions(self) -> tuple[str, ...]:
- return self.attributes.array_dimensions # type: ignore[no-any-return]
+ return self.attributes.array_dimensions
class GridMappingVariable(ArraySpec[GridMappingAttrs]):
@@ -127,11 +134,17 @@ def check_valid_coordinates(self) -> Self:
GroupSpec[Any, Any]
The validated GeoZarr DataSet.
"""
- return check_valid_coordinates(self)
+ # ``self`` structurally satisfies the ``GroupLike`` protocol, but mypy cannot bind the
+ # helper's TypeVar to ``Self``; cast through the protocol and back to ``Self`` (the helper
+ # returns the same object).
+ check_valid_coordinates(cast("GroupLike", self))
+ return self
@model_validator(mode="after")
def check_grid_mapping(self) -> Self:
- return check_grid_mapping(self)
+ # See note above: ``self`` satisfies ``DatasetLike`` but the TypeVar can't bind to ``Self``.
+ check_grid_mapping(cast("DatasetLike", self))
+ return self
class MultiscaleGroup(GroupSpec[MultiscaleGroupAttrs, DataArray | GroupSpec[Any, Any]]):
diff --git a/src/eopf_geozarr/data_api/geozarr/v3.py b/src/eopf_geozarr/data_api/geozarr/v3.py
index 9359b22d..ee2a487c 100644
--- a/src/eopf_geozarr/data_api/geozarr/v3.py
+++ b/src/eopf_geozarr/data_api/geozarr/v3.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Any, Self
+from typing import Any, Self, cast
from pydantic import model_validator
from pydantic_zarr.v3 import ArraySpec, GroupSpec
@@ -10,6 +10,8 @@
from eopf_geozarr.data_api.geozarr.common import (
BaseDataArrayAttrs,
DatasetAttrs,
+ DatasetLike,
+ GroupLike,
check_grid_mapping,
check_valid_coordinates,
)
@@ -29,8 +31,9 @@ class DataArray(ArraySpec[BaseDataArrayAttrs]):
https://github.com/zarr-developers/geozarr-spec/blob/main/geozarr-spec.md#geozarr-dataarray
"""
- # The dimension names must be a tuple of strings
- dimension_names: tuple[str, ...]
+ # GeoZarr requires dimension names, so tighten the parent's optional
+ # `tuple[str | None, ...] | None` field to a required tuple of strings.
+ dimension_names: tuple[str, ...] # pyright: ignore[reportGeneralTypeIssues, reportIncompatibleVariableOverride]
@property
def array_dimensions(self) -> tuple[str, ...]:
@@ -55,11 +58,17 @@ def check_valid_coordinates(self) -> Self:
GroupSpec[Any, Any]
The validated GeoZarr DataSet.
"""
- return check_valid_coordinates(self)
+ # ``self`` structurally satisfies the ``GroupLike`` protocol, but mypy cannot bind the
+ # helper's TypeVar to ``Self``; cast through the protocol and back to ``Self`` (the helper
+ # returns the same object).
+ check_valid_coordinates(cast("GroupLike", self))
+ return self
@model_validator(mode="after")
def validate_grid_mapping(self) -> Self:
- return check_grid_mapping(self)
+ # See note above: ``self`` satisfies ``DatasetLike`` but the TypeVar can't bind to ``Self``.
+ check_grid_mapping(cast("DatasetLike", self))
+ return self
class MultiscaleGroup(GroupSpec[MultiscaleGroupAttrs, DataArray | GroupSpec[Any, Any]]):
diff --git a/src/eopf_geozarr/data_api/s1.py b/src/eopf_geozarr/data_api/s1.py
index d8bb91f1..00f0bed5 100644
--- a/src/eopf_geozarr/data_api/s1.py
+++ b/src/eopf_geozarr/data_api/s1.py
@@ -47,7 +47,7 @@ class Sentinel1DataArray(ArraySpec[Sentinel1DataArrayAttrs]):
# Conditions groups
-class Sentinel1AntennaPatternMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1AntennaPatternMembers(TypedDict, closed=True, total=False):
"""Members for antenna_pattern group.
All fields are optional to support different product variants.
@@ -64,53 +64,75 @@ class Sentinel1AntennaPatternMembers(TypedDict, closed=True, total=False): # ty
terrain_height: ArraySpec[Any]
-class Sentinel1AntennaPatternGroup(
- GroupSpec[DatasetAttrs, Sentinel1AntennaPatternMembers] # type: ignore[type-var]
-):
+class Sentinel1AntennaPatternGroup(GroupSpec[DatasetAttrs, Sentinel1AntennaPatternMembers]):
"""Antenna pattern group containing antenna characteristics."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def count(self) -> ArraySpec[Any]:
"""Get count array."""
- return self.members["count"]
+ value = self.members.get("count")
+ if value is None:
+ raise KeyError("count")
+ return value
@property
def elevation_angle(self) -> ArraySpec[Any]:
"""Get elevation_angle array."""
- return self.members["elevation_angle"]
+ value = self.members.get("elevation_angle")
+ if value is None:
+ raise KeyError("elevation_angle")
+ return value
@property
def incidence_angle(self) -> ArraySpec[Any]:
"""Get incidence_angle array."""
- return self.members["incidence_angle"]
+ value = self.members.get("incidence_angle")
+ if value is None:
+ raise KeyError("incidence_angle")
+ return value
@property
def roll(self) -> ArraySpec[Any]:
"""Get roll array."""
- return self.members["roll"]
+ value = self.members.get("roll")
+ if value is None:
+ raise KeyError("roll")
+ return value
@property
def slant_range_time_ap(self) -> ArraySpec[Any]:
"""Get slant_range_time_ap array."""
- return self.members["slant_range_time_ap"]
+ value = self.members.get("slant_range_time_ap")
+ if value is None:
+ raise KeyError("slant_range_time_ap")
+ return value
@property
def swath(self) -> ArraySpec[Any]:
"""Get swath array."""
- return self.members["swath"]
+ value = self.members.get("swath")
+ if value is None:
+ raise KeyError("swath")
+ return value
@property
def terrain_height(self) -> ArraySpec[Any]:
"""Get terrain_height array."""
- return self.members["terrain_height"]
+ value = self.members.get("terrain_height")
+ if value is None:
+ raise KeyError("terrain_height")
+ return value
-class Sentinel1AttitudeMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1AttitudeMembers(TypedDict, closed=True, total=False):
"""Members for attitude group."""
azimuth_time: ArraySpec[Any]
@@ -126,66 +148,99 @@ class Sentinel1AttitudeMembers(TypedDict, closed=True, total=False): # type: ig
yaw: ArraySpec[Any]
-class Sentinel1AttitudeGroup(GroupSpec[DatasetAttrs, Sentinel1AttitudeMembers]): # type: ignore[type-var]
+class Sentinel1AttitudeGroup(GroupSpec[DatasetAttrs, Sentinel1AttitudeMembers]):
"""Attitude group containing spacecraft attitude data."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def pitch(self) -> ArraySpec[Any]:
"""Get pitch array."""
- return self.members["pitch"]
+ value = self.members.get("pitch")
+ if value is None:
+ raise KeyError("pitch")
+ return value
@property
def q0(self) -> ArraySpec[Any]:
"""Get q0 array."""
- return self.members["q0"]
+ value = self.members.get("q0")
+ if value is None:
+ raise KeyError("q0")
+ return value
@property
def q1(self) -> ArraySpec[Any]:
"""Get q1 array."""
- return self.members["q1"]
+ value = self.members.get("q1")
+ if value is None:
+ raise KeyError("q1")
+ return value
@property
def q2(self) -> ArraySpec[Any]:
"""Get q2 array."""
- return self.members["q2"]
+ value = self.members.get("q2")
+ if value is None:
+ raise KeyError("q2")
+ return value
@property
def q3(self) -> ArraySpec[Any]:
"""Get q3 array."""
- return self.members["q3"]
+ value = self.members.get("q3")
+ if value is None:
+ raise KeyError("q3")
+ return value
@property
def roll(self) -> ArraySpec[Any]:
"""Get roll array."""
- return self.members["roll"]
+ value = self.members.get("roll")
+ if value is None:
+ raise KeyError("roll")
+ return value
@property
def wx(self) -> ArraySpec[Any]:
"""Get wx array."""
- return self.members["wx"]
+ value = self.members.get("wx")
+ if value is None:
+ raise KeyError("wx")
+ return value
@property
def wy(self) -> ArraySpec[Any]:
"""Get wy array."""
- return self.members["wy"]
+ value = self.members.get("wy")
+ if value is None:
+ raise KeyError("wy")
+ return value
@property
def wz(self) -> ArraySpec[Any]:
"""Get wz array."""
- return self.members["wz"]
+ value = self.members.get("wz")
+ if value is None:
+ raise KeyError("wz")
+ return value
@property
def yaw(self) -> ArraySpec[Any]:
"""Get yaw array."""
- return self.members["yaw"]
+ value = self.members.get("yaw")
+ if value is None:
+ raise KeyError("yaw")
+ return value
-class Sentinel1AzimuthFmRateMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1AzimuthFmRateMembers(TypedDict, closed=True, total=False):
"""Members for azimuth_fm_rate group."""
azimuth_fm_rate_polynomial: ArraySpec[Any]
@@ -193,28 +248,35 @@ class Sentinel1AzimuthFmRateMembers(TypedDict, closed=True, total=False): # typ
t0: ArraySpec[Any]
-class Sentinel1AzimuthFmRateGroup(
- GroupSpec[DatasetAttrs, Sentinel1AzimuthFmRateMembers] # type: ignore[type-var]
-):
+class Sentinel1AzimuthFmRateGroup(GroupSpec[DatasetAttrs, Sentinel1AzimuthFmRateMembers]):
"""Azimuth FM rate group."""
@property
def azimuth_fm_rate_polynomial(self) -> ArraySpec[Any]:
"""Get azimuth_fm_rate_polynomial array."""
- return self.members["azimuth_fm_rate_polynomial"]
+ value = self.members.get("azimuth_fm_rate_polynomial")
+ if value is None:
+ raise KeyError("azimuth_fm_rate_polynomial")
+ return value
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def t0(self) -> ArraySpec[Any]:
"""Get t0 array."""
- return self.members["t0"]
+ value = self.members.get("t0")
+ if value is None:
+ raise KeyError("t0")
+ return value
-class Sentinel1CoordinateConversionMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1CoordinateConversionMembers(TypedDict, closed=True, total=False):
"""Members for coordinate_conversion group."""
azimuth_time: ArraySpec[Any]
@@ -226,42 +288,60 @@ class Sentinel1CoordinateConversionMembers(TypedDict, closed=True, total=False):
class Sentinel1CoordinateConversionGroup(
- GroupSpec[DatasetAttrs, Sentinel1CoordinateConversionMembers] # type: ignore[type-var]
+ GroupSpec[DatasetAttrs, Sentinel1CoordinateConversionMembers]
):
"""Coordinate conversion group."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def gr0(self) -> ArraySpec[Any]:
"""Get gr0 array."""
- return self.members["gr0"]
+ value = self.members.get("gr0")
+ if value is None:
+ raise KeyError("gr0")
+ return value
@property
def grsr_coefficients(self) -> ArraySpec[Any]:
"""Get grsr_coefficients array."""
- return self.members["grsr_coefficients"]
+ value = self.members.get("grsr_coefficients")
+ if value is None:
+ raise KeyError("grsr_coefficients")
+ return value
@property
def slant_range_time(self) -> ArraySpec[Any]:
"""Get slant_range_time array."""
- return self.members["slant_range_time"]
+ value = self.members.get("slant_range_time")
+ if value is None:
+ raise KeyError("slant_range_time")
+ return value
@property
def sr0(self) -> ArraySpec[Any]:
"""Get sr0 array."""
- return self.members["sr0"]
+ value = self.members.get("sr0")
+ if value is None:
+ raise KeyError("sr0")
+ return value
@property
def srgr_coefficients(self) -> ArraySpec[Any]:
"""Get srgr_coefficients array."""
- return self.members["srgr_coefficients"]
+ value = self.members.get("srgr_coefficients")
+ if value is None:
+ raise KeyError("srgr_coefficients")
+ return value
-class Sentinel1DopplerCentroidMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1DopplerCentroidMembers(TypedDict, closed=True, total=False):
"""Members for doppler_centroid group."""
azimuth_time: ArraySpec[Any]
@@ -275,58 +355,83 @@ class Sentinel1DopplerCentroidMembers(TypedDict, closed=True, total=False): # t
t0: ArraySpec[Any]
-class Sentinel1DopplerCentroidGroup(
- GroupSpec[DatasetAttrs, Sentinel1DopplerCentroidMembers] # type: ignore[type-var]
-):
+class Sentinel1DopplerCentroidGroup(GroupSpec[DatasetAttrs, Sentinel1DopplerCentroidMembers]):
"""Doppler centroid group."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def data_dc_polynomial(self) -> ArraySpec[Any]:
"""Get data_dc_polynomial array."""
- return self.members["data_dc_polynomial"]
+ value = self.members.get("data_dc_polynomial")
+ if value is None:
+ raise KeyError("data_dc_polynomial")
+ return value
@property
def data_dc_rms_error(self) -> ArraySpec[Any]:
"""Get data_dc_rms_error array."""
- return self.members["data_dc_rms_error"]
+ value = self.members.get("data_dc_rms_error")
+ if value is None:
+ raise KeyError("data_dc_rms_error")
+ return value
@property
def data_dc_rms_error_above_threshold(self) -> ArraySpec[Any]:
"""Get data_dc_rms_error_above_threshold array."""
- return self.members["data_dc_rms_error_above_threshold"]
+ value = self.members.get("data_dc_rms_error_above_threshold")
+ if value is None:
+ raise KeyError("data_dc_rms_error_above_threshold")
+ return value
@property
def degree(self) -> ArraySpec[Any]:
"""Get degree array."""
- return self.members["degree"]
+ value = self.members.get("degree")
+ if value is None:
+ raise KeyError("degree")
+ return value
@property
def fine_dce_azimuth_start_time(self) -> ArraySpec[Any]:
"""Get fine_dce_azimuth_start_time array."""
- return self.members["fine_dce_azimuth_start_time"]
+ value = self.members.get("fine_dce_azimuth_start_time")
+ if value is None:
+ raise KeyError("fine_dce_azimuth_start_time")
+ return value
@property
def fine_dce_azimuth_stop_time(self) -> ArraySpec[Any]:
"""Get fine_dce_azimuth_stop_time array."""
- return self.members["fine_dce_azimuth_stop_time"]
+ value = self.members.get("fine_dce_azimuth_stop_time")
+ if value is None:
+ raise KeyError("fine_dce_azimuth_stop_time")
+ return value
@property
def geometry_dc_polynomial(self) -> ArraySpec[Any]:
"""Get geometry_dc_polynomial array."""
- return self.members["geometry_dc_polynomial"]
+ value = self.members.get("geometry_dc_polynomial")
+ if value is None:
+ raise KeyError("geometry_dc_polynomial")
+ return value
@property
def t0(self) -> ArraySpec[Any]:
"""Get t0 array."""
- return self.members["t0"]
+ value = self.members.get("t0")
+ if value is None:
+ raise KeyError("t0")
+ return value
-class Sentinel1GcpMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1GcpMembers(TypedDict, closed=True, total=False):
"""Members for GCP (Ground Control Points) group.
All fields are optional to support different product variants (S1A, S1C).
@@ -346,66 +451,99 @@ class Sentinel1GcpMembers(TypedDict, closed=True, total=False): # type: ignore[
slant_range_time_gcp: ArraySpec[Any]
-class Sentinel1GcpGroup(GroupSpec[DatasetAttrs, Sentinel1GcpMembers]): # type: ignore[type-var]
+class Sentinel1GcpGroup(GroupSpec[DatasetAttrs, Sentinel1GcpMembers]):
"""Ground Control Points (GCP) group."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def azimuth_time_gcp(self) -> ArraySpec[Any]:
"""Get azimuth_time_gcp array."""
- return self.members["azimuth_time_gcp"]
+ value = self.members.get("azimuth_time_gcp")
+ if value is None:
+ raise KeyError("azimuth_time_gcp")
+ return value
@property
def elevation_angle(self) -> ArraySpec[Any]:
"""Get elevation_angle array."""
- return self.members["elevation_angle"]
+ value = self.members.get("elevation_angle")
+ if value is None:
+ raise KeyError("elevation_angle")
+ return value
@property
def ground_range(self) -> ArraySpec[Any]:
"""Get ground_range array."""
- return self.members["ground_range"]
+ value = self.members.get("ground_range")
+ if value is None:
+ raise KeyError("ground_range")
+ return value
@property
def height(self) -> ArraySpec[Any]:
"""Get height array."""
- return self.members["height"]
+ value = self.members.get("height")
+ if value is None:
+ raise KeyError("height")
+ return value
@property
def incidence_angle(self) -> ArraySpec[Any]:
"""Get incidence_angle array."""
- return self.members["incidence_angle"]
+ value = self.members.get("incidence_angle")
+ if value is None:
+ raise KeyError("incidence_angle")
+ return value
@property
def latitude(self) -> ArraySpec[Any]:
"""Get latitude array."""
- return self.members["latitude"]
+ value = self.members.get("latitude")
+ if value is None:
+ raise KeyError("latitude")
+ return value
@property
def line(self) -> ArraySpec[Any]:
"""Get line array."""
- return self.members["line"]
+ value = self.members.get("line")
+ if value is None:
+ raise KeyError("line")
+ return value
@property
def longitude(self) -> ArraySpec[Any]:
"""Get longitude array."""
- return self.members["longitude"]
+ value = self.members.get("longitude")
+ if value is None:
+ raise KeyError("longitude")
+ return value
@property
def pixel(self) -> ArraySpec[Any]:
"""Get pixel array."""
- return self.members["pixel"]
+ value = self.members.get("pixel")
+ if value is None:
+ raise KeyError("pixel")
+ return value
@property
def slant_range_time_gcp(self) -> ArraySpec[Any]:
"""Get slant_range_time_gcp array."""
- return self.members["slant_range_time_gcp"]
+ value = self.members.get("slant_range_time_gcp")
+ if value is None:
+ raise KeyError("slant_range_time_gcp")
+ return value
-class Sentinel1OrbitMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1OrbitMembers(TypedDict, closed=True, total=False):
"""Members for orbit group."""
axis: ArraySpec[Any]
@@ -414,31 +552,43 @@ class Sentinel1OrbitMembers(TypedDict, closed=True, total=False): # type: ignor
velocity: ArraySpec[Any]
-class Sentinel1OrbitGroup(GroupSpec[DatasetAttrs, Sentinel1OrbitMembers]): # type: ignore[type-var]
+class Sentinel1OrbitGroup(GroupSpec[DatasetAttrs, Sentinel1OrbitMembers]):
"""Orbit group containing spacecraft position and velocity."""
@property
def axis(self) -> ArraySpec[Any]:
"""Get axis array."""
- return self.members["axis"]
+ value = self.members.get("axis")
+ if value is None:
+ raise KeyError("axis")
+ return value
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def position(self) -> ArraySpec[Any]:
"""Get position array."""
- return self.members["position"]
+ value = self.members.get("position")
+ if value is None:
+ raise KeyError("position")
+ return value
@property
def velocity(self) -> ArraySpec[Any]:
"""Get velocity array."""
- return self.members["velocity"]
+ value = self.members.get("velocity")
+ if value is None:
+ raise KeyError("velocity")
+ return value
-class Sentinel1ReferenceReplicaMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1ReferenceReplicaMembers(TypedDict, closed=True, total=False):
"""Members for reference_replica group.
Closed TypedDict - only reference replica coefficient array keys are allowed.
@@ -450,28 +600,35 @@ class Sentinel1ReferenceReplicaMembers(TypedDict, closed=True, total=False): #
reference_replica_phase_coefficients: ArraySpec[Any]
-class Sentinel1ReferenceReplicaGroup(
- GroupSpec[DatasetAttrs, Sentinel1ReferenceReplicaMembers] # type: ignore[type-var]
-):
+class Sentinel1ReferenceReplicaGroup(GroupSpec[DatasetAttrs, Sentinel1ReferenceReplicaMembers]):
"""Reference replica group."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def reference_replica_amplitude_coefficients(self) -> ArraySpec[Any]:
"""Get reference_replica_amplitude_coefficients array."""
- return self.members["reference_replica_amplitude_coefficients"]
+ value = self.members.get("reference_replica_amplitude_coefficients")
+ if value is None:
+ raise KeyError("reference_replica_amplitude_coefficients")
+ return value
@property
def reference_replica_phase_coefficients(self) -> ArraySpec[Any]:
"""Get reference_replica_phase_coefficients array."""
- return self.members["reference_replica_phase_coefficients"]
+ value = self.members.get("reference_replica_phase_coefficients")
+ if value is None:
+ raise KeyError("reference_replica_phase_coefficients")
+ return value
-class Sentinel1ReplicaMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1ReplicaMembers(TypedDict, closed=True, total=False):
"""Members for replica group.
Closed TypedDict - only pulse replica data array keys are allowed.
@@ -491,89 +648,126 @@ class Sentinel1ReplicaMembers(TypedDict, closed=True, total=False): # type: ign
relative_pg_product_valid_flag: ArraySpec[Any]
-class Sentinel1ReplicaGroup(GroupSpec[DatasetAttrs, Sentinel1ReplicaMembers]): # type: ignore[type-var]
+class Sentinel1ReplicaGroup(GroupSpec[DatasetAttrs, Sentinel1ReplicaMembers]):
"""Replica group containing pulse replica data."""
@property
def absolute_pg_product_valid_flag(self) -> ArraySpec[Any]:
"""Get absolute_pg_product_valid_flag array."""
- return self.members["absolute_pg_product_valid_flag"]
+ value = self.members.get("absolute_pg_product_valid_flag")
+ if value is None:
+ raise KeyError("absolute_pg_product_valid_flag")
+ return value
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def cross_correlation_peak_location(self) -> ArraySpec[Any]:
"""Get cross_correlation_peak_location array."""
- return self.members["cross_correlation_peak_location"]
+ value = self.members.get("cross_correlation_peak_location")
+ if value is None:
+ raise KeyError("cross_correlation_peak_location")
+ return value
@property
def cross_correlation_pslr(self) -> ArraySpec[Any]:
"""Get cross_correlation_pslr array."""
- return self.members["cross_correlation_pslr"]
+ value = self.members.get("cross_correlation_pslr")
+ if value is None:
+ raise KeyError("cross_correlation_pslr")
+ return value
@property
def internal_time_delay(self) -> ArraySpec[Any]:
"""Get internal_time_delay array."""
- return self.members["internal_time_delay"]
+ value = self.members.get("internal_time_delay")
+ if value is None:
+ raise KeyError("internal_time_delay")
+ return value
@property
def model_pg_product_amplitude(self) -> ArraySpec[Any]:
"""Get model_pg_product_amplitude array."""
- return self.members["model_pg_product_amplitude"]
+ value = self.members.get("model_pg_product_amplitude")
+ if value is None:
+ raise KeyError("model_pg_product_amplitude")
+ return value
@property
def model_pg_product_phase(self) -> ArraySpec[Any]:
"""Get model_pg_product_phase array."""
- return self.members["model_pg_product_phase"]
+ value = self.members.get("model_pg_product_phase")
+ if value is None:
+ raise KeyError("model_pg_product_phase")
+ return value
@property
def pg_product_amplitude(self) -> ArraySpec[Any]:
"""Get pg_product_amplitude array."""
- return self.members["pg_product_amplitude"]
+ value = self.members.get("pg_product_amplitude")
+ if value is None:
+ raise KeyError("pg_product_amplitude")
+ return value
@property
def pg_product_phase(self) -> ArraySpec[Any]:
"""Get pg_product_phase array."""
- return self.members["pg_product_phase"]
+ value = self.members.get("pg_product_phase")
+ if value is None:
+ raise KeyError("pg_product_phase")
+ return value
@property
def reconstructed_replica_valid_flag(self) -> ArraySpec[Any]:
"""Get reconstructed_replica_valid_flag array."""
- return self.members["reconstructed_replica_valid_flag"]
+ value = self.members.get("reconstructed_replica_valid_flag")
+ if value is None:
+ raise KeyError("reconstructed_replica_valid_flag")
+ return value
@property
def relative_pg_product_valid_flag(self) -> ArraySpec[Any]:
"""Get relative_pg_product_valid_flag array."""
- return self.members["relative_pg_product_valid_flag"]
+ value = self.members.get("relative_pg_product_valid_flag")
+ if value is None:
+ raise KeyError("relative_pg_product_valid_flag")
+ return value
-class Sentinel1TerrainHeightMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1TerrainHeightMembers(TypedDict, closed=True, total=False):
"""Members for terrain_height group."""
azimuth_time: ArraySpec[Any]
terrain_height: ArraySpec[Any]
-class Sentinel1TerrainHeightGroup(
- GroupSpec[DatasetAttrs, Sentinel1TerrainHeightMembers] # type: ignore[type-var]
-):
+class Sentinel1TerrainHeightGroup(GroupSpec[DatasetAttrs, Sentinel1TerrainHeightMembers]):
"""Terrain height group."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def terrain_height(self) -> ArraySpec[Any]:
"""Get terrain_height array."""
- return self.members["terrain_height"]
+ value = self.members.get("terrain_height")
+ if value is None:
+ raise KeyError("terrain_height")
+ return value
-class Sentinel1ConditionsMembers(TypedDict, closed=True): # type: ignore[call-arg]
+class Sentinel1ConditionsMembers(TypedDict, closed=True):
"""Members for conditions group.
Closed TypedDict - only antenna_pattern, attitude, azimuth_fm_rate, etc. keys are allowed.
@@ -591,7 +785,7 @@ class Sentinel1ConditionsMembers(TypedDict, closed=True): # type: ignore[call-a
terrain_height: Sentinel1TerrainHeightGroup
-class Sentinel1ConditionsGroup(GroupSpec[DatasetAttrs, Sentinel1ConditionsMembers]): # type: ignore[type-var]
+class Sentinel1ConditionsGroup(GroupSpec[DatasetAttrs, Sentinel1ConditionsMembers]):
"""Conditions group containing acquisition and processing metadata."""
def get_antenna_pattern(self) -> Sentinel1AntennaPatternGroup | None:
@@ -636,7 +830,7 @@ def get_terrain_height(self) -> Sentinel1TerrainHeightGroup | None:
# Quality groups
-class Sentinel1CalibrationMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1CalibrationMembers(TypedDict, closed=True, total=False):
"""Members for calibration group."""
azimuth_time: ArraySpec[Any]
@@ -649,51 +843,75 @@ class Sentinel1CalibrationMembers(TypedDict, closed=True, total=False): # type:
sigma_nought: ArraySpec[Any]
-class Sentinel1CalibrationGroup(GroupSpec[DatasetAttrs, Sentinel1CalibrationMembers]): # type: ignore[type-var]
+class Sentinel1CalibrationGroup(GroupSpec[DatasetAttrs, Sentinel1CalibrationMembers]):
"""Calibration group containing radiometric calibration data."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def beta_nought(self) -> ArraySpec[Any]:
"""Get beta_nought array."""
- return self.members["beta_nought"]
+ value = self.members.get("beta_nought")
+ if value is None:
+ raise KeyError("beta_nought")
+ return value
@property
def dn(self) -> ArraySpec[Any]:
"""Get dn array."""
- return self.members["dn"]
+ value = self.members.get("dn")
+ if value is None:
+ raise KeyError("dn")
+ return value
@property
def gamma(self) -> ArraySpec[Any]:
"""Get gamma array."""
- return self.members["gamma"]
+ value = self.members.get("gamma")
+ if value is None:
+ raise KeyError("gamma")
+ return value
@property
def ground_range(self) -> ArraySpec[Any]:
"""Get ground_range array."""
- return self.members["ground_range"]
+ value = self.members.get("ground_range")
+ if value is None:
+ raise KeyError("ground_range")
+ return value
@property
def line(self) -> ArraySpec[Any]:
"""Get line array."""
- return self.members["line"]
+ value = self.members.get("line")
+ if value is None:
+ raise KeyError("line")
+ return value
@property
def pixel(self) -> ArraySpec[Any]:
"""Get pixel array."""
- return self.members["pixel"]
+ value = self.members.get("pixel")
+ if value is None:
+ raise KeyError("pixel")
+ return value
@property
def sigma_nought(self) -> ArraySpec[Any]:
"""Get sigma_nought array."""
- return self.members["sigma_nought"]
+ value = self.members.get("sigma_nought")
+ if value is None:
+ raise KeyError("sigma_nought")
+ return value
-class Sentinel1NoiseMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1NoiseMembers(TypedDict, closed=True, total=False):
"""Members for noise group."""
azimuth_time: ArraySpec[Any]
@@ -701,26 +919,35 @@ class Sentinel1NoiseMembers(TypedDict, closed=True, total=False): # type: ignor
number_of_noise_lines: ArraySpec[Any]
-class Sentinel1NoiseGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseMembers]): # type: ignore[type-var]
+class Sentinel1NoiseGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseMembers]):
"""Noise group containing noise estimation data."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def noise_power_correction_factor(self) -> ArraySpec[Any]:
"""Get noise_power_correction_factor array."""
- return self.members["noise_power_correction_factor"]
+ value = self.members.get("noise_power_correction_factor")
+ if value is None:
+ raise KeyError("noise_power_correction_factor")
+ return value
@property
def number_of_noise_lines(self) -> ArraySpec[Any]:
"""Get number_of_noise_lines array."""
- return self.members["number_of_noise_lines"]
+ value = self.members.get("number_of_noise_lines")
+ if value is None:
+ raise KeyError("number_of_noise_lines")
+ return value
-class Sentinel1NoiseAzimuthMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1NoiseAzimuthMembers(TypedDict, closed=True, total=False):
"""Members for noise_azimuth group."""
first_azimuth_time: ArraySpec[Any]
@@ -732,46 +959,67 @@ class Sentinel1NoiseAzimuthMembers(TypedDict, closed=True, total=False): # type
swath: ArraySpec[Any]
-class Sentinel1NoiseAzimuthGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseAzimuthMembers]): # type: ignore[type-var]
+class Sentinel1NoiseAzimuthGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseAzimuthMembers]):
"""Noise azimuth group containing azimuth noise vectors."""
@property
def first_azimuth_time(self) -> ArraySpec[Any]:
"""Get first_azimuth_time array."""
- return self.members["first_azimuth_time"]
+ value = self.members.get("first_azimuth_time")
+ if value is None:
+ raise KeyError("first_azimuth_time")
+ return value
@property
def first_range_sample(self) -> ArraySpec[Any]:
"""Get first_range_sample array."""
- return self.members["first_range_sample"]
+ value = self.members.get("first_range_sample")
+ if value is None:
+ raise KeyError("first_range_sample")
+ return value
@property
def last_azimuth_time(self) -> ArraySpec[Any]:
"""Get last_azimuth_time array."""
- return self.members["last_azimuth_time"]
+ value = self.members.get("last_azimuth_time")
+ if value is None:
+ raise KeyError("last_azimuth_time")
+ return value
@property
def last_range_sample(self) -> ArraySpec[Any]:
"""Get last_range_sample array."""
- return self.members["last_range_sample"]
+ value = self.members.get("last_range_sample")
+ if value is None:
+ raise KeyError("last_range_sample")
+ return value
@property
def line(self) -> ArraySpec[Any]:
"""Get line array."""
- return self.members["line"]
+ value = self.members.get("line")
+ if value is None:
+ raise KeyError("line")
+ return value
@property
def noise_azimuth_lut(self) -> ArraySpec[Any]:
"""Get noise_azimuth_lut array."""
- return self.members["noise_azimuth_lut"]
+ value = self.members.get("noise_azimuth_lut")
+ if value is None:
+ raise KeyError("noise_azimuth_lut")
+ return value
@property
def swath(self) -> ArraySpec[Any]:
"""Get swath array."""
- return self.members["swath"]
+ value = self.members.get("swath")
+ if value is None:
+ raise KeyError("swath")
+ return value
-class Sentinel1NoiseRangeMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1NoiseRangeMembers(TypedDict, closed=True, total=False):
"""Members for noise_range group."""
azimuth_time: ArraySpec[Any]
@@ -781,36 +1029,51 @@ class Sentinel1NoiseRangeMembers(TypedDict, closed=True, total=False): # type:
pixel: ArraySpec[Any]
-class Sentinel1NoiseRangeGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseRangeMembers]): # type: ignore[type-var]
+class Sentinel1NoiseRangeGroup(GroupSpec[DatasetAttrs, Sentinel1NoiseRangeMembers]):
"""Noise range group containing range noise vectors."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def ground_range(self) -> ArraySpec[Any]:
"""Get ground_range array."""
- return self.members["ground_range"]
+ value = self.members.get("ground_range")
+ if value is None:
+ raise KeyError("ground_range")
+ return value
@property
def line(self) -> ArraySpec[Any]:
"""Get line array."""
- return self.members["line"]
+ value = self.members.get("line")
+ if value is None:
+ raise KeyError("line")
+ return value
@property
def noise_range_lut(self) -> ArraySpec[Any]:
"""Get noise_range_lut array."""
- return self.members["noise_range_lut"]
+ value = self.members.get("noise_range_lut")
+ if value is None:
+ raise KeyError("noise_range_lut")
+ return value
@property
def pixel(self) -> ArraySpec[Any]:
"""Get pixel array."""
- return self.members["pixel"]
+ value = self.members.get("pixel")
+ if value is None:
+ raise KeyError("pixel")
+ return value
-class Sentinel1QualityMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1QualityMembers(TypedDict, closed=True, total=False):
"""Members for quality group.
Closed TypedDict with optional fields to support different product variants:
@@ -824,7 +1087,7 @@ class Sentinel1QualityMembers(TypedDict, closed=True, total=False): # type: ign
noise_range: Sentinel1NoiseRangeGroup
-class Sentinel1QualityGroup(GroupSpec[DatasetAttrs, Sentinel1QualityMembers]): # type: ignore[type-var]
+class Sentinel1QualityGroup(GroupSpec[DatasetAttrs, Sentinel1QualityMembers]):
"""Quality group containing quality assurance and calibration data.
Supports both S1A (with noise_azimuth, noise_range) and S1C (without them) products.
@@ -848,7 +1111,7 @@ def get_noise_range(self) -> Sentinel1NoiseRangeGroup | None:
# Measurements
-class Sentinel1MeasurementsMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel1MeasurementsMembers(TypedDict, closed=True, total=False):
"""Members for measurements group."""
azimuth_time: ArraySpec[Any]
@@ -858,37 +1121,52 @@ class Sentinel1MeasurementsMembers(TypedDict, closed=True, total=False): # type
pixel: ArraySpec[Any]
-class Sentinel1MeasurementsGroup(GroupSpec[DatasetAttrs, Sentinel1MeasurementsMembers]): # type: ignore[type-var]
+class Sentinel1MeasurementsGroup(GroupSpec[DatasetAttrs, Sentinel1MeasurementsMembers]):
"""Measurements group containing SAR imagery data."""
@property
def azimuth_time(self) -> ArraySpec[Any]:
"""Get azimuth_time array."""
- return self.members["azimuth_time"]
+ value = self.members.get("azimuth_time")
+ if value is None:
+ raise KeyError("azimuth_time")
+ return value
@property
def grd(self) -> ArraySpec[Any]:
"""Get grd array."""
- return self.members["grd"]
+ value = self.members.get("grd")
+ if value is None:
+ raise KeyError("grd")
+ return value
@property
def ground_range(self) -> ArraySpec[Any]:
"""Get ground_range array."""
- return self.members["ground_range"]
+ value = self.members.get("ground_range")
+ if value is None:
+ raise KeyError("ground_range")
+ return value
@property
def line(self) -> ArraySpec[Any]:
"""Get line array."""
- return self.members["line"]
+ value = self.members.get("line")
+ if value is None:
+ raise KeyError("line")
+ return value
@property
def pixel(self) -> ArraySpec[Any]:
"""Get pixel array."""
- return self.members["pixel"]
+ value = self.members.get("pixel")
+ if value is None:
+ raise KeyError("pixel")
+ return value
# Polarization group
-class Sentinel1PolarizationMembers(TypedDict, closed=True): # type: ignore[call-arg]
+class Sentinel1PolarizationMembers(TypedDict, closed=True):
"""Members for polarization group.
Closed TypedDict - only conditions, measurements, quality keys are allowed.
@@ -899,7 +1177,7 @@ class Sentinel1PolarizationMembers(TypedDict, closed=True): # type: ignore[call
quality: Sentinel1QualityGroup
-class Sentinel1PolarizationGroup(GroupSpec[DatasetAttrs, Sentinel1PolarizationMembers]): # type: ignore[type-var]
+class Sentinel1PolarizationGroup(GroupSpec[DatasetAttrs, Sentinel1PolarizationMembers]):
"""Polarization-specific group containing all data for one polarization."""
@property
diff --git a/src/eopf_geozarr/data_api/s2.py b/src/eopf_geozarr/data_api/s2.py
index 3cfdbd02..c86c48a7 100644
--- a/src/eopf_geozarr/data_api/s2.py
+++ b/src/eopf_geozarr/data_api/s2.py
@@ -222,7 +222,7 @@ class Sentinel2ArrayAttributes(BaseModel):
# Resolution-level members for probability data arrays
-class ProbabilityArrayMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class ProbabilityArrayMembers(TypedDict, closed=True, total=False):
"""Members for probability arrays at a specific resolution (r10m, r20m, r60m).
Closed TypedDict - contains probability arrays (cld, snw) and per-band/coordinate arrays.
@@ -237,20 +237,20 @@ class ProbabilityArrayMembers(TypedDict, closed=True, total=False): # type: ign
# Probability resolution groups (r10m, r20m, r60m)
-class ProbabilityResolutionMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class ProbabilityResolutionMembers(TypedDict, closed=True, total=False):
"""Members for probability data containing resolution-level groups (r10m, r20m, r60m).
Closed TypedDict - contains resolution groups as subgroups.
All fields are optional since not all resolutions are always present.
"""
- r10m: GroupSpec[Any, ProbabilityArrayMembers] # type: ignore[type-var]
- r20m: GroupSpec[Any, ProbabilityArrayMembers] # type: ignore[type-var]
- r60m: GroupSpec[Any, ProbabilityArrayMembers] # type: ignore[type-var]
+ r10m: GroupSpec[Any, ProbabilityArrayMembers]
+ r20m: GroupSpec[Any, ProbabilityArrayMembers]
+ r60m: GroupSpec[Any, ProbabilityArrayMembers]
# Resolution-level members for quicklook data arrays
-class QuicklookArrayMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class QuicklookArrayMembers(TypedDict, closed=True, total=False):
"""Members for quicklook arrays at a specific resolution.
Closed TypedDict - typically contains TCI (True Color Image) and optional band/coordinate arrays.
@@ -264,20 +264,20 @@ class QuicklookArrayMembers(TypedDict, closed=True, total=False): # type: ignor
# Quicklook resolution groups (r10m, r20m, r60m)
-class QuicklookResolutionMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class QuicklookResolutionMembers(TypedDict, closed=True, total=False):
"""Members for quicklook data containing resolution-level groups (r10m, r20m, r60m).
Closed TypedDict - contains resolution groups as subgroups.
All fields are optional since not all resolutions are always present.
"""
- r10m: GroupSpec[Any, QuicklookArrayMembers] # type: ignore[type-var]
- r20m: GroupSpec[Any, QuicklookArrayMembers] # type: ignore[type-var]
- r60m: GroupSpec[Any, QuicklookArrayMembers] # type: ignore[type-var]
+ r10m: GroupSpec[Any, QuicklookArrayMembers]
+ r20m: GroupSpec[Any, QuicklookArrayMembers]
+ r60m: GroupSpec[Any, QuicklookArrayMembers]
# Mask members - contains resolution-level groups or various classification/detector groups
-class ConditionsMaskMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class ConditionsMaskMembers(TypedDict, closed=True, total=False):
"""Members for mask subgroup in conditions.
Closed TypedDict - can contain either:
@@ -297,7 +297,7 @@ class ConditionsMaskMembers(TypedDict, closed=True, total=False): # type: ignor
# Geometry members - contains angle and orientation groups/arrays
-class GeometryMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class GeometryMembers(TypedDict, closed=True, total=False):
"""Members for geometry group containing sun and viewing angles.
Closed TypedDict - contains angle and geometry groups/arrays with flexible internal structure.
@@ -321,7 +321,7 @@ class GeometryMembers(TypedDict, closed=True, total=False): # type: ignore[call
# Meteorology members - contains CAMS and ECMWF atmospheric data
-class MeteorologyMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class MeteorologyMembers(TypedDict, closed=True, total=False):
"""Members for meteorology group containing CAMS and ECMWF atmospheric data.
Closed TypedDict - contains subgroups for different meteorological data sources.
@@ -458,7 +458,7 @@ class Sentinel2CoordinateArray(ArraySpec[Sentinel2DataArrayAttrs]):
# TypedDict definitions for members structure
-class Sentinel2ResolutionMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel2ResolutionMembers(TypedDict, closed=True, total=False):
"""Members dict for a resolution dataset (r10m, r20m, r60m).
Closed TypedDict - no extra keys are allowed beyond those explicitly defined.
@@ -483,11 +483,11 @@ class Sentinel2ResolutionMembers(TypedDict, closed=True, total=False): # type:
b12: ArraySpec[Any]
-class Sentinel2ResolutionDataset(GroupSpec[DatasetAttrs, Sentinel2ResolutionMembers]): # type: ignore[type-var]
+class Sentinel2ResolutionDataset(GroupSpec[DatasetAttrs, Sentinel2ResolutionMembers]):
"""A single resolution dataset within reflectance (r10m, r20m, r60m)."""
-class Sentinel2ReflectanceMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel2ReflectanceMembers(TypedDict, closed=True, total=False):
"""Members for reflectance group.
Closed TypedDict - only r10m, r20m, r60m keys are allowed.
@@ -498,11 +498,11 @@ class Sentinel2ReflectanceMembers(TypedDict, closed=True, total=False): # type:
r60m: Sentinel2ResolutionDataset
-class Sentinel2ReflectanceGroup(GroupSpec[DatasetAttrs, Sentinel2ReflectanceMembers]): # type: ignore[type-var]
+class Sentinel2ReflectanceGroup(GroupSpec[DatasetAttrs, Sentinel2ReflectanceMembers]):
"""Reflectance data organized by resolution."""
-class Sentinel2MeasurementsMembers(TypedDict, closed=True): # type: ignore[call-arg]
+class Sentinel2MeasurementsMembers(TypedDict, closed=True):
"""Members for measurements group.
Closed TypedDict - only 'reflectance' key is allowed.
@@ -511,7 +511,7 @@ class Sentinel2MeasurementsMembers(TypedDict, closed=True): # type: ignore[call
reflectance: Sentinel2ReflectanceGroup
-class Sentinel2MeasurementsGroup(GroupSpec[DatasetAttrs, Sentinel2MeasurementsMembers]): # type: ignore[type-var]
+class Sentinel2MeasurementsGroup(GroupSpec[DatasetAttrs, Sentinel2MeasurementsMembers]):
"""Measurements group containing reflectance data."""
@property
@@ -523,7 +523,7 @@ def reflectance(self) -> Sentinel2ReflectanceGroup:
# Quality data groups - need resolution-level typed groups
-class Sentinel2AtmosphereResolutionMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel2AtmosphereResolutionMembers(TypedDict, closed=True, total=False):
"""Members for atmosphere data at a specific resolution.
Closed TypedDict - may contain aot and/or wvp arrays depending on available data.
@@ -537,12 +537,12 @@ class Sentinel2AtmosphereResolutionMembers(TypedDict, closed=True, total=False):
class Sentinel2AtmosphereResolutionDataset(
- GroupSpec[DatasetAttrs, Sentinel2AtmosphereResolutionMembers] # type: ignore[type-var]
+ GroupSpec[DatasetAttrs, Sentinel2AtmosphereResolutionMembers]
):
"""Atmosphere data at a single resolution."""
-class Sentinel2AtmosphereMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel2AtmosphereMembers(TypedDict, closed=True, total=False):
"""Members for atmosphere group containing resolution datasets."""
r10m: Sentinel2AtmosphereResolutionDataset
@@ -550,25 +550,23 @@ class Sentinel2AtmosphereMembers(TypedDict, closed=True, total=False): # type:
r60m: Sentinel2AtmosphereResolutionDataset
-class Sentinel2AtmosphereDataset(GroupSpec[DatasetAttrs, Sentinel2AtmosphereMembers]): # type: ignore[type-var]
+class Sentinel2AtmosphereDataset(GroupSpec[DatasetAttrs, Sentinel2AtmosphereMembers]):
"""Atmosphere quality data (AOT, WVP) at multiple resolutions."""
-class Sentinel2ProbabilityDataset(
- GroupSpec[DatasetAttrs, ProbabilityResolutionMembers] # type: ignore[type-var]
-):
+class Sentinel2ProbabilityDataset(GroupSpec[DatasetAttrs, ProbabilityResolutionMembers]):
"""Probability data (cloud, snow) at multiple resolutions."""
-class Sentinel2QuicklookDataset(GroupSpec[DatasetAttrs, QuicklookResolutionMembers]): # type: ignore[type-var]
+class Sentinel2QuicklookDataset(GroupSpec[DatasetAttrs, QuicklookResolutionMembers]):
"""True Color Image (TCI) quicklook data at multiple resolutions."""
-class Sentinel2MaskDataset(GroupSpec[DatasetAttrs, ConditionsMaskMembers]): # type: ignore[type-var]
+class Sentinel2MaskDataset(GroupSpec[DatasetAttrs, ConditionsMaskMembers]):
"""Mask data containing classification and detector footprints."""
-class Sentinel2QualityMembers(TypedDict, closed=True, total=False): # type: ignore[call-arg]
+class Sentinel2QualityMembers(TypedDict, closed=True, total=False):
"""Members for quality group.
Closed TypedDict with optional fields to accommodate different product levels:
@@ -583,7 +581,7 @@ class Sentinel2QualityMembers(TypedDict, closed=True, total=False): # type: ign
mask: Sentinel2MaskDataset
-class Sentinel2QualityGroup(GroupSpec[DatasetAttrs, Sentinel2QualityMembers]): # type: ignore[type-var]
+class Sentinel2QualityGroup(GroupSpec[DatasetAttrs, Sentinel2QualityMembers]):
"""Quality group containing atmosphere, probability, classification, and quicklook data.
Supports both L2A products (Sentinel-2A, 2C) and L1C products (Sentinel-2B).
@@ -607,19 +605,19 @@ def mask(self) -> Sentinel2MaskDataset | None:
# Conditions groups
-class Sentinel2GeometryGroup(GroupSpec[DatasetAttrs, GeometryMembers]): # type: ignore[type-var]
+class Sentinel2GeometryGroup(GroupSpec[DatasetAttrs, GeometryMembers]):
"""Geometry group containing sun and viewing angles."""
-class Sentinel2MeteorologyGroup(GroupSpec[DatasetAttrs, MeteorologyMembers]): # type: ignore[type-var]
+class Sentinel2MeteorologyGroup(GroupSpec[DatasetAttrs, MeteorologyMembers]):
"""Meteorology group containing CAMS and ECMWF atmospheric data."""
-class Sentinel2ConditionsMaskGroup(GroupSpec[DatasetAttrs, ConditionsMaskMembers]): # type: ignore[type-var]
+class Sentinel2ConditionsMaskGroup(GroupSpec[DatasetAttrs, ConditionsMaskMembers]):
"""Mask subgroup in conditions."""
-class Sentinel2ConditionsMembers(TypedDict, closed=True): # type: ignore[call-arg]
+class Sentinel2ConditionsMembers(TypedDict, closed=True):
"""Members for conditions group.
Closed TypedDict - only geometry, mask, meteorology keys are allowed.
@@ -630,7 +628,7 @@ class Sentinel2ConditionsMembers(TypedDict, closed=True): # type: ignore[call-a
meteorology: Sentinel2MeteorologyGroup
-class Sentinel2ConditionsGroup(GroupSpec[DatasetAttrs, Sentinel2ConditionsMembers]): # type: ignore[type-var]
+class Sentinel2ConditionsGroup(GroupSpec[DatasetAttrs, Sentinel2ConditionsMembers]):
"""Conditions group containing geometry and meteorology data."""
def geometry(self) -> Sentinel2GeometryGroup | None:
@@ -647,7 +645,7 @@ def meteorology(self) -> Sentinel2MeteorologyGroup | None:
# Root model
-class Sentinel2RootMembers(TypedDict, closed=True): # type: ignore[call-arg]
+class Sentinel2RootMembers(TypedDict, closed=True):
"""Members for Sentinel-2 root group.
Closed TypedDict - only measurements, quality, conditions keys are allowed.
@@ -658,7 +656,7 @@ class Sentinel2RootMembers(TypedDict, closed=True): # type: ignore[call-arg]
conditions: Sentinel2ConditionsGroup
-class Sentinel2Root(GroupSpec[Sentinel2RootAttrs, Sentinel2RootMembers]): # type: ignore[type-var]
+class Sentinel2Root(GroupSpec[Sentinel2RootAttrs, Sentinel2RootMembers]):
"""Complete Sentinel-2 EOPF Zarr hierarchy.
The hierarchy follows EOPF organization:
diff --git a/src/eopf_geozarr/pyz/common.py b/src/eopf_geozarr/pyz/common.py
index 3b5bee04..fd21b00f 100644
--- a/src/eopf_geozarr/pyz/common.py
+++ b/src/eopf_geozarr/pyz/common.py
@@ -103,7 +103,7 @@ def _format_array_html(arr: Any) -> str:
if value is None:
continue
dtype_str = str(value).strip()
- value_str = dtype_str if dtype_str else "(not set)"
+ value_str = dtype_str or "(not set)"
# Skip data_type if we already handled it via dtype
elif prop_name == "data_type":
if getattr(arr, "dtype", None) is not None:
@@ -149,13 +149,12 @@ def _format_array_html(arr: Any) -> str:
""
)
- # Get items based on type
- if is_dict_attrs:
- attrs_dict = attributes
- items = list(attrs_dict.items())
- else: # is_model_attrs
- attrs_dict = attributes.model_dump()
- items = list(attrs_dict.items())
+ # Get items based on type. Use direct isinstance checks (not the
+ # boolean flags) so the type checker narrows `attributes`.
+ if isinstance(attributes, dict):
+ items = list(attributes.items())
+ else:
+ items = list(attributes.model_dump().items())
for key, value in items:
if isinstance(value, dict):
diff --git a/src/eopf_geozarr/pyz/v2.py b/src/eopf_geozarr/pyz/v2.py
index 052834fb..52605eb1 100644
--- a/src/eopf_geozarr/pyz/v2.py
+++ b/src/eopf_geozarr/pyz/v2.py
@@ -40,9 +40,13 @@ class MyGroup(GroupSpec[Any, MyMembers])
TArraySpecType = TypeVar("TArraySpecType")
-class GroupSpec(GroupSpecV2[TAttr, TMembers]):
+class GroupSpec(GroupSpecV2[TAttr, TMembers]): # type: ignore[type-var]
+ # TMembers is bound to the full members mapping (e.g. a TypedDict) by design,
+ # whereas the parent's second type parameter expects a single member item type.
attributes: TAttr
- members: TMembers
+ # members holds the full mapping (TMembers) rather than the parent's
+ # Mapping[str, TItem] | None; this is the intended structure for this subclass.
+ members: TMembers # type: ignore[assignment]
def __repr__(self) -> str:
"""Return a condensed text representation of the GroupSpec."""
diff --git a/src/eopf_geozarr/pyz/v3.py b/src/eopf_geozarr/pyz/v3.py
index fabdf4e6..0613e507 100644
--- a/src/eopf_geozarr/pyz/v3.py
+++ b/src/eopf_geozarr/pyz/v3.py
@@ -41,8 +41,12 @@ class MyGroup(GroupSpec[Any, MyMembers])
class GroupSpec(GroupSpecV3[TAttr, TMembers]):
+ # TMembers is bound to the full members mapping (e.g. a TypedDict) by design,
+ # whereas the parent's second type parameter expects a single member item type.
attributes: TAttr
- members: TMembers
+ # members holds the full mapping (TMembers) rather than the parent's
+ # Mapping[str, TItem] | None; this is the intended structure for this subclass.
+ members: TMembers # type: ignore[assignment]
def __repr__(self) -> str:
"""Return a condensed text representation of the GroupSpec."""
diff --git a/src/eopf_geozarr/s2_optimization/s2_converter.py b/src/eopf_geozarr/s2_optimization/s2_converter.py
index fd303b69..6a868087 100644
--- a/src/eopf_geozarr/s2_optimization/s2_converter.py
+++ b/src/eopf_geozarr/s2_optimization/s2_converter.py
@@ -5,7 +5,7 @@
from __future__ import annotations
import time
-from typing import Any, TypedDict
+from typing import TYPE_CHECKING, TypedDict
import structlog
import xarray as xr
@@ -20,6 +20,9 @@
from .s2_multiscale import create_multiscale_from_datatree
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
log = structlog.get_logger()
@@ -66,13 +69,16 @@ def initialize_crs_from_dataset(dt_input: xr.DataTree) -> CRS | None:
continue
dataset = group_node.ds
- # Check if dataset has rio accessor with CRS
+ # Check if dataset has rio accessor with CRS. rioxarray returns a
+ # rasterio CRS; convert it to a pyproj CRS (the declared return type),
+ # which also validates the value at runtime.
if hasattr(dataset, "rio"):
try:
- crs = dataset.rio.crs
- if crs is not None:
- log.info("Initialized CRS from dataset", crs=str(crs))
- return crs
+ rio_crs = dataset.rio.crs
+ if rio_crs is not None:
+ ds_crs = CRS.from_user_input(rio_crs)
+ log.info("Initialized CRS from dataset", crs=str(ds_crs))
+ return ds_crs
except Exception:
log.debug("Failed to get CRS from dataset rio accessor")
@@ -80,10 +86,11 @@ def initialize_crs_from_dataset(dt_input: xr.DataTree) -> CRS | None:
for var in dataset.data_vars.values():
if hasattr(var, "rio"):
try:
- crs = var.rio.crs
- if crs is not None:
- log.info("Initialized CRS from variable", crs=str(crs))
- return crs
+ rio_crs = var.rio.crs
+ if rio_crs is not None:
+ var_crs = CRS.from_user_input(rio_crs)
+ log.info("Initialized CRS from variable", crs=str(var_crs))
+ return var_crs
except Exception:
log.debug("Failed to get CRS from variable rio accessor")
@@ -263,7 +270,7 @@ def convert_s2_optimized(
return result_dt
-def simple_root_consolidation(output_path: str, datasets: dict[str, dict]) -> None:
+def simple_root_consolidation(output_path: str, datasets: Mapping[str, object]) -> None:
"""Simple root-level metadata consolidation with proper zarr group creation."""
# create missing intermediary groups (/conditions, /quality, etc.)
# using the keys of the datasets dict
@@ -318,6 +325,19 @@ def simple_root_consolidation(output_path: str, datasets: dict[str, dict]) -> No
zarr.consolidate_metadata(output_path, zarr_format=3)
+def _as_bbox(value: object) -> tuple[float, float, float, float] | None:
+ """Return *value* as a 4-tuple of floats, or ``None`` if it is not one.
+
+ ``spatial:bbox`` is read from stored metadata, so its type is not known
+ statically; this verifies the shape at runtime rather than asserting it.
+ """
+ if not isinstance(value, (list, tuple)) or len(value) != 4:
+ return None
+ if not all(isinstance(v, (int, float)) for v in value):
+ return None
+ return (float(value[0]), float(value[1]), float(value[2]), float(value[3]))
+
+
def write_store_root_bbox(output_path: str) -> None:
"""Write `spatial:bbox` and `proj:code` at the store root.
@@ -336,14 +356,18 @@ def _walk(group: zarr.Group) -> None:
attrs = dict(group.attrs)
bbox = attrs.get("spatial:bbox")
code = attrs.get("proj:code")
- if bbox is not None and len(bbox) == 4:
+ # spatial:bbox comes from stored metadata; verify it is a 4-element
+ # numeric sequence before use rather than trusting the type.
+ corners = _as_bbox(bbox)
+ if corners is not None:
+ x0, y0, x1, y1 = corners
if code and code != "EPSG:4326":
transformer = Transformer.from_crs(code, "EPSG:4326", always_xy=True)
- xmin, ymin = transformer.transform(bbox[0], bbox[1])
- xmax, ymax = transformer.transform(bbox[2], bbox[3])
+ xmin, ymin = transformer.transform(x0, y0)
+ xmax, ymax = transformer.transform(x1, y1)
bboxes_4326.append((xmin, ymin, xmax, ymax))
else:
- bboxes_4326.append(tuple(float(v) for v in bbox)) # type: ignore[arg-type]
+ bboxes_4326.append((x0, y0, x1, y1))
for child in group.groups():
_walk(child[1])
@@ -408,7 +432,7 @@ def create_result_datatree(output_path: str) -> xr.DataTree:
def is_sentinel2_dataset(group: zarr.Group) -> bool:
from eopf_geozarr.pyz.v2 import GroupSpec
- adapter = TypeAdapter(Sentinel1Root | Sentinel2Root) # type: ignore[var-annotated]
+ adapter = TypeAdapter(Sentinel1Root | Sentinel2Root)
try:
model = adapter.validate_python(GroupSpec.from_zarr(group).model_dump())
except ValueError as e:
@@ -418,7 +442,16 @@ def is_sentinel2_dataset(group: zarr.Group) -> bool:
return isinstance(model, Sentinel2Root)
-def validate_optimized_dataset(dataset_path: str) -> dict[str, Any]:
+class ValidationResult(TypedDict):
+ """Result of validating an optimized Sentinel-2 dataset."""
+
+ is_valid: bool
+ issues: list[str]
+ warnings: list[str]
+ summary: dict[str, object]
+
+
+def validate_optimized_dataset(dataset_path: str) -> ValidationResult:
"""
Validate an optimized Sentinel-2 dataset.
diff --git a/src/eopf_geozarr/s2_optimization/s2_data_consolidator.py b/src/eopf_geozarr/s2_optimization/s2_data_consolidator.py
index 7ca56db5..67cf071c 100644
--- a/src/eopf_geozarr/s2_optimization/s2_data_consolidator.py
+++ b/src/eopf_geozarr/s2_optimization/s2_data_consolidator.py
@@ -159,7 +159,7 @@ def _extract_geometry_data(self) -> None:
# Consolidate all geometry variables
for var_name in ds.data_vars:
- self.geometry_data[var_name] = ds[var_name]
+ self.geometry_data[str(var_name)] = ds[var_name]
def _extract_meteorology_data(self) -> None:
"""Extract meteorological data (CAMS and ECMWF)."""
diff --git a/src/eopf_geozarr/s2_optimization/s2_multiscale.py b/src/eopf_geozarr/s2_optimization/s2_multiscale.py
index d9743069..35a06520 100644
--- a/src/eopf_geozarr/s2_optimization/s2_multiscale.py
+++ b/src/eopf_geozarr/s2_optimization/s2_multiscale.py
@@ -6,25 +6,22 @@
from __future__ import annotations
from itertools import pairwise
-from typing import TYPE_CHECKING, Any, Literal
+from typing import TYPE_CHECKING, Any, Literal, cast
import numpy as np
import structlog
import xarray as xr
-from dask import delayed
+import zarr
from dask.array import from_delayed
+from dask.delayed import delayed
from pydantic.experimental.missing_sentinel import MISSING
from pyproj import CRS
from zarr.codecs import CastValue
-from zarr_cm import geo_proj
-from zarr_cm import multiscales as multiscales_cm
-from zarr_cm import spatial as spatial_cm
from eopf_geozarr.conversion import utils
from eopf_geozarr.conversion.fs_utils import sanitize_dataset_attributes
from eopf_geozarr.data_api.geozarr.multiscales import zcm
from eopf_geozarr.data_api.geozarr.multiscales.geozarr import (
- MultiscaleGroupAttrs,
MultiscaleMeta,
)
from eopf_geozarr.data_api.geozarr.types import (
@@ -40,7 +37,8 @@
if TYPE_CHECKING:
from collections.abc import Hashable, Mapping
- import zarr
+ from zarr_cm import MultiscalesAttrs
+ from zarr_cm import spatial as spatial_cm
from eopf_geozarr.types import OverviewLevelJSON
@@ -107,7 +105,9 @@ def _preferred_spatial_transform(
rio_value = dataset.rio.transform
if callable(rio_value):
rio_value = rio_value()
- rio_values = tuple(float(value) for value in tuple(rio_value)[:6])
+ # rio transform value is dynamically typed; it is iterable at runtime.
+ rio_iter = cast("tuple[float, ...]", tuple(rio_value)) # pyright: ignore[reportArgumentType]
+ rio_values = tuple(float(value) for value in rio_iter[:6])
if len(rio_values) == 6:
rio_transform = (
rio_values[0],
@@ -139,11 +139,13 @@ def _coarsen_variable(var_name: str, var_data: xr.DataArray, factor: int) -> xr.
var_type = determine_variable_type(var_name, var_data)
coarsened = var_data.coarsen({"x": factor, "y": factor}, boundary="trim")
if var_type in ("reflectance", "probability"):
- result = coarsened.mean()
+ # xarray stubs omit reduction methods on DataArrayCoarsen.
+ result = coarsened.mean() # type: ignore[attr-defined]
elif var_type == "classification":
result = coarsened.reduce(subsample_2)
elif var_type == "quality_mask":
- result = coarsened.max()
+ # xarray stubs omit reduction methods on DataArrayCoarsen.
+ result = coarsened.max() # type: ignore[attr-defined]
else:
raise ValueError(f"Unknown variable type {var_type}")
@@ -152,9 +154,9 @@ def _coarsen_variable(var_name: str, var_data: xr.DataArray, factor: int) -> xr.
# inspects encoding (e.g. to push CF scale-offset into a codec pipeline)
# would see an empty encoding on every coarsened level.
encoding = var_data.encoding
- result = result.astype(var_data.dtype)
- result.encoding = encoding
- return result
+ cast_result: xr.DataArray = result.astype(var_data.dtype)
+ cast_result.encoding = encoding
+ return cast_result
def inject_missing_bands(
@@ -255,7 +257,7 @@ def create_multiscale_from_datatree(
Returns:
Dictionary of processed groups
"""
- processed_groups = {}
+ processed_groups: dict[str, Any] = {}
# The scale levels in the output data. 10, 20, 60 already exist in the source data.
# Step 1: Copy all original groups as-is
@@ -409,14 +411,20 @@ def create_multiscale_from_datatree(
# Step 3: Add multiscales metadata to parent groups
log.info("Adding multiscales metadata to parent groups")
- # Get the parent group (it was created when writing the resolution groups)
+ # Get the parent group (it was created when writing the resolution groups).
+ # `output_group[base_path]` is typed `Array | Group`; `base_path` always
+ # addresses a group (the reflectance parent), so verify that at runtime.
parent_group = output_group[base_path]
+ if not isinstance(parent_group, zarr.Group):
+ raise TypeError(
+ f"expected a zarr.Group at {base_path!r}, got {type(parent_group).__name__}"
+ )
- dt_multiscale = add_multiscales_metadata_to_parent(
+ add_multiscales_metadata_to_parent(
parent_group,
resolution_groups,
)
- processed_groups[base_path] = dt_multiscale
+ processed_groups[base_path] = None
return processed_groups
@@ -533,7 +541,7 @@ def create_measurements_encoding(
for key in keep_keys:
if key in var_data.encoding:
- var_encoding[key] = var_data.encoding[key] # type: ignore[literal-required]
+ var_encoding[key] = var_data.encoding[key]
if len(set(var_data.encoding.keys()) - XARRAY_ENCODING_KEYS) > 0:
log.warning(
@@ -546,13 +554,13 @@ def create_measurements_encoding(
# otherwise leak into the output).
is_float = np.issubdtype(var_data.dtype, np.floating)
var_data.attrs = utils.sanitize_array_attrs(var_data.attrs, is_decoded_float=is_float)
- encoding[var_name] = var_encoding
+ encoding[str(var_name)] = var_encoding
# Add coordinate encoding and sanitize coord attrs (e.g. drop
# ``_eopf_attrs`` from datetime coords carried in from the source).
for coord_name, coord_data in dataset.coords.items():
coord_data.attrs = utils.sanitize_array_attrs(coord_data.attrs)
- encoding[coord_name] = {"compressors": []} # type: ignore[typeddict-item]
+ encoding[str(coord_name)] = {"compressors": []} # type: ignore[typeddict-item]
return encoding
@@ -613,8 +621,12 @@ def calculate_simple_shard_dimensions(
def add_multiscales_metadata_to_parent(
group: zarr.Group,
res_groups: Mapping[str, xr.Dataset],
-) -> xr.DataTree:
- """Add GeoZarr-compliant multiscales metadata to parent group."""
+) -> None:
+ """Add GeoZarr-compliant multiscales metadata to parent group.
+
+ Returns ``None`` in all cases: metadata is written directly to ``group``
+ via ``group.attrs.update`` rather than returned as a DataTree.
+ """
# Sort by resolution (finest to coarsest)
res_order = {
"r10m": 10,
@@ -632,7 +644,7 @@ def add_multiscales_metadata_to_parent(
"Skipping {} - only one resolution available",
base_path=group.path,
)
- return None
+ return
# Get CRS and bounds from first available dataset (load from output path)
first_res = all_resolutions[0]
@@ -642,14 +654,14 @@ def add_multiscales_metadata_to_parent(
native_crs = first_dataset.rio.crs if hasattr(first_dataset, "rio") else None
if native_crs is None:
log.info("No CRS found, skipping multiscales metadata", base_path=group.path)
- return None
+ return
# Calculate bounds directly from coordinates for consistency with the data arrays
if "x" not in first_dataset.coords or "y" not in first_dataset.coords:
log.error(
"Missing x/y coordinates in dataset, cannot determine bounds", base_path=group.path
)
- return None
+ return
x_coords = first_dataset.x.values
y_coords = first_dataset.y.values
@@ -668,6 +680,8 @@ def add_multiscales_metadata_to_parent(
dataset = res_groups[res_name]
+ # Defensive guard retained for runtime safety even though the typed
+ # contract (Mapping[str, xr.Dataset]) means mypy proves it unreachable.
if dataset is None:
continue
@@ -771,9 +785,9 @@ def add_multiscales_metadata_to_parent(
if len(overview_levels) < 2:
log.info(" Could not create overview levels for {}", base_path=group.path)
- return None
+ return
- layout: list[zcm.ScaleLevel] | MISSING = MISSING # type: ignore[valid-type]
+ layout: list[zcm.ScaleLevel] | MISSING = MISSING
layout = []
@@ -804,6 +818,7 @@ def add_multiscales_metadata_to_parent(
scale_level_data["transform"] = multiscale_transform
# Add spatial properties
+ assert "spatial_shape" in overview_level # always populated by the producer above
scale_level_data["spatial:shape"] = overview_level["spatial_shape"]
if "spatial_transform" in overview_level:
spatial_transform = overview_level["spatial_transform"]
@@ -813,42 +828,35 @@ def add_multiscales_metadata_to_parent(
scale_level = zcm.ScaleLevel(**scale_level_data)
layout.append(scale_level)
- # Create convention metadata for all three conventions
- multiscale_attrs = MultiscaleGroupAttrs(
- zarr_conventions=(
- multiscales_cm.CMO,
- spatial_cm.CMO,
- geo_proj.CMO,
- ),
- multiscales=MultiscaleMeta(
- layout=layout,
- resampling_method="average",
- ),
- )
- # Write multiscale attributes directly to the parent group
- attrs_to_write = multiscale_attrs.model_dump()
+ # Validate + serialize the multiscales block via the project model (which
+ # also covers the ZCM/TMS duality), then hand all three conventions to
+ # zarr-cm, which validates each and emits the matching CMOs in order
+ # (multiscales, spatial, proj).
+ multiscales_data = cast(
+ "MultiscalesAttrs",
+ MultiscaleMeta(layout=tuple(layout), resampling_method="average").model_dump(),
+ )
- # Add spatial and proj attributes at group level following specifications
+ attrs_to_write: dict[str, Any] = {}
if native_crs and native_bounds:
- # Add spatial convention attributes
- attrs_to_write["spatial:dimensions"] = ["y", "x"] # Required field
- attrs_to_write["spatial:bbox"] = list(native_bounds) # [xmin, ymin, xmax, ymax]
- attrs_to_write["spatial:registration"] = "pixel" # Default registration type
-
- # Add proj convention attributes
- if hasattr(native_crs, "to_epsg") and native_crs.to_epsg():
- attrs_to_write["proj:code"] = f"EPSG:{native_crs.to_epsg()}"
- elif hasattr(native_crs, "to_wkt"):
- attrs_to_write["proj:wkt2"] = native_crs.to_wkt()
+ attrs_to_write.update(
+ utils.build_convention_attrs(
+ multiscales=multiscales_data,
+ spatial={
+ "spatial:dimensions": ["y", "x"],
+ "spatial:bbox": list(native_bounds), # [xmin, ymin, xmax, ymax]
+ "spatial:registration": "pixel",
+ },
+ crs=native_crs,
+ )
+ )
# Write attributes directly to the zarr group
group.attrs.update(attrs_to_write)
log.info("Added %s multiscale levels to %s", len(overview_levels), group.path)
- return None # No DataTree to return since we wrote directly to the group
-
def create_original_encoding(dataset: xr.Dataset) -> dict[str, XarrayDataArrayEncoding]:
"""Write a group preserving its original chunking and encoding."""
@@ -856,7 +864,7 @@ def create_original_encoding(dataset: xr.Dataset) -> dict[str, XarrayDataArrayEn
# Simple encoding that preserves original structure
compressor = BloscCodec(cname="zstd", clevel=3, shuffle="shuffle", blocksize=0)
- encoding = {}
+ encoding: dict[str, XarrayDataArrayEncoding] = {}
for var_name in dataset.data_vars:
# start with the original encoding
@@ -865,7 +873,7 @@ def create_original_encoding(dataset: xr.Dataset) -> dict[str, XarrayDataArrayEn
var_encoding["compressors"] = (compressor,)
for key in XARRAY_ENCODING_KEYS - {"compressors", "fill_value"}:
if key in var_data.encoding:
- var_encoding[key] = var_data.encoding[key] # type: ignore[literal-required]
+ var_encoding[key] = var_data.encoding[key]
# Set the zarr-level `fill_value` explicitly rather than letting xarray
# decide — different xarray versions infer different defaults from the
# variable's `_FillValue`. See `explicit_fill_value` for the rationale.
@@ -881,11 +889,11 @@ def create_original_encoding(dataset: xr.Dataset) -> dict[str, XarrayDataArrayEn
# Sanitize source-only attributes (replace dict — ``.update`` cannot
# remove keys, so stale ``_eopf_attrs`` would otherwise leak through).
var_data.attrs = utils.sanitize_array_attrs(var_data.attrs)
- encoding[var_name] = var_encoding
+ encoding[str(var_name)] = var_encoding
for coord_name, coord_data in dataset.coords.items():
coord_data.attrs = utils.sanitize_array_attrs(coord_data.attrs)
- encoding[coord_name] = {"compressors": None}
+ encoding[str(coord_name)] = {"compressors": None}
return encoding
@@ -912,7 +920,7 @@ def create_downsampled_resolution_group(source_dataset: xr.Dataset, factor: int)
for var_name, var_data in source_dataset.data_vars.items():
if var_data.ndim < 2:
continue
- lazy_vars[var_name] = _coarsen_variable(var_name, var_data, factor)
+ lazy_vars[var_name] = _coarsen_variable(str(var_name), var_data, factor)
if not lazy_vars:
return xr.Dataset()
@@ -962,7 +970,7 @@ def create_downsampled_coordinates(
# Copy any other coordinates that might exist
coords.update(
{
- coord_name: coord_data
+ str(coord_name): coord_data
for coord_name, coord_data in level_2_dataset.coords.items()
if coord_name not in ["x", "y"]
}
@@ -976,9 +984,9 @@ def create_lazy_downsample_operation_from_existing(
) -> xr.DataArray:
"""Create lazy downsampling operation from existing data."""
- @delayed # type: ignore[misc]
+ @delayed
def downsample_operation() -> Any:
- var_type = determine_variable_type(source_data.name, source_data)
+ var_type = determine_variable_type(str(source_data.name), source_data)
return downsample_variable(source_data, target_height, target_width, var_type)
# Create delayed operation
@@ -1040,8 +1048,14 @@ def stream_write_dataset(
"Level path {} already exists. Skipping write.",
dataset_path=path,
)
+ # The zarr backend accepts a zarr `Store` here at runtime, but xarray's
+ # `open_dataset` stub only types the first arg as path/buffer/datastore.
return xr.open_dataset(
- group.store, engine="zarr", chunks={}, decode_coords="all", group=path
+ group.store, # type: ignore[arg-type]
+ engine="zarr",
+ chunks={},
+ decode_coords="all",
+ group=path,
)
log.info("Streaming computation and write to {}", dataset_path=path)
@@ -1079,8 +1093,11 @@ def stream_write_dataset(
# Try to get current client for better status monitoring
try:
client = distributed.Client.current()
- # Use client.compute to get a proper Future with status
+ # client.compute is untyped (returns Any); verify we got a
+ # Future rather than asserting it with a cast.
future = client.compute(write_job)
+ if not isinstance(future, distributed.Future):
+ raise TypeError(f"expected a distributed.Future, got {type(future).__name__}")
log.info("Using distributed client for write job monitoring")
try:
@@ -1146,9 +1163,11 @@ def write_geo_metadata(
# TODO : Remove once rioxarray supports writing these conventions directly
# https://github.com/corteva/rioxarray/pull/883
- # Add spatial convention attributes
- dataset.attrs["spatial:dimensions"] = ["y", "x"] # Required field
- dataset.attrs["spatial:registration"] = "pixel" # Default registration type
+ # Assemble spatial convention data
+ spatial_data: spatial_cm.SpatialAttrs = {
+ "spatial:dimensions": ["y", "x"], # Required field
+ "spatial:registration": "pixel", # Default registration type
+ }
# Calculate and add spatial bbox if coordinates are available
if "x" in dataset.coords and "y" in dataset.coords:
@@ -1156,33 +1175,23 @@ def write_geo_metadata(
y_coords = dataset.coords["y"].values
x_min, x_max = float(x_coords.min()), float(x_coords.max())
y_min, y_max = float(y_coords.min()), float(y_coords.max())
- dataset.attrs["spatial:bbox"] = [x_min, y_min, x_max, y_max]
+ spatial_data["spatial:bbox"] = [x_min, y_min, x_max, y_max]
spatial_transform = _preferred_spatial_transform(dataset)
# Only add spatial:transform if we have valid transform data (not all zeros)
if spatial_transform is not None and not all(t == 0 for t in spatial_transform):
- dataset.attrs["spatial:transform"] = list(spatial_transform)
+ spatial_data["spatial:transform"] = list(spatial_transform)
# Add spatial shape if data variables exist
if dataset.data_vars:
first_var = next(iter(dataset.data_vars.values()))
if first_var.ndim >= 2:
height, width = first_var.shape[-2:]
- dataset.attrs["spatial:shape"] = [height, width]
-
- # Add proj convention attributes
- if hasattr(crs, "to_epsg") and crs.to_epsg():
- dataset.attrs["proj:code"] = f"EPSG:{crs.to_epsg()}"
- elif hasattr(crs, "to_wkt"):
- dataset.attrs["proj:wkt2"] = crs.to_wkt()
+ spatial_data["spatial:shape"] = [height, width]
- # Add zarr convention declarations
- conventions = [
- spatial_cm.CMO,
- geo_proj.CMO,
- ]
- dataset.attrs["zarr_conventions"] = conventions
+ # Build validated spatial + proj convention attrs (data + CMOs) via zarr-cm
+ dataset.attrs.update(utils.build_convention_attrs(spatial=spatial_data, crs=crs))
def rechunk_dataset_for_encoding(
@@ -1194,11 +1203,11 @@ def rechunk_dataset_for_encoding(
When using Zarr v3 sharding, Dask chunks must align with shard dimensions to avoid
checksum validation errors.
"""
- rechunked_vars = {}
+ rechunked_vars: dict[Hashable, xr.DataArray] = {}
for var_name, var_data in dataset.data_vars.items():
- if var_name in encoding:
- var_encoding = encoding[var_name]
+ if str(var_name) in encoding:
+ var_encoding = encoding[str(var_name)]
# If sharding is enabled, rechunk based on shard dimensions
if "shards" in var_encoding and var_encoding["shards"] is not None:
diff --git a/tests/_test_data/geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json b/tests/_test_data/geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
index 48a69cc8..8bc9e17a 100644
--- a/tests/_test_data/geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
+++ b/tests/_test_data/geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
@@ -4117,22 +4117,22 @@
"zarr_conventions": [
{
"uuid": "d35379db-88df-4056-af3a-620245f8e347",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"name": "multiscales",
"description": "Multiscale layout of zarr datasets"
},
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4277,15 +4277,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4752,15 +4752,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -5842,15 +5842,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -6856,15 +6856,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -7946,15 +7946,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -9037,15 +9037,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10141,15 +10141,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10457,15 +10457,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10773,15 +10773,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -11096,15 +11096,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -11636,15 +11636,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -12362,15 +12362,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -12816,15 +12816,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
diff --git a/tests/_test_data/geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json b/tests/_test_data/geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
index 9d0f84b2..15039c49 100644
--- a/tests/_test_data/geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
+++ b/tests/_test_data/geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
@@ -3812,22 +3812,22 @@
"zarr_conventions": [
{
"uuid": "d35379db-88df-4056-af3a-620245f8e347",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"name": "multiscales",
"description": "Multiscale layout of zarr datasets"
},
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -3972,15 +3972,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4447,15 +4447,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4921,15 +4921,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -5627,15 +5627,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -6101,15 +6101,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -6576,15 +6576,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -7064,15 +7064,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -7604,15 +7604,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -8330,15 +8330,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
diff --git a/tests/_test_data/geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json b/tests/_test_data/geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
index be9ee053..0b19623b 100644
--- a/tests/_test_data/geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
+++ b/tests/_test_data/geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
@@ -4117,22 +4117,22 @@
"zarr_conventions": [
{
"uuid": "d35379db-88df-4056-af3a-620245f8e347",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"name": "multiscales",
"description": "Multiscale layout of zarr datasets"
},
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4277,15 +4277,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -4752,15 +4752,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -5842,15 +5842,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -6856,15 +6856,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -7946,15 +7946,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -9037,15 +9037,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10141,15 +10141,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10457,15 +10457,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -10773,15 +10773,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -11096,15 +11096,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -11636,15 +11636,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -12362,15 +12362,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
@@ -12816,15 +12816,15 @@
"zarr_conventions": [
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"name": "spatial:",
"description": "Spatial coordinate information"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
}
diff --git a/tests/_test_data/optimized_geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json b/tests/_test_data/optimized_geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
index fa20e80d..5b335149 100644
--- a/tests/_test_data/optimized_geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
+++ b/tests/_test_data/optimized_geozarr_examples/S2A_MSIL2A_20251008T100041_N0511_R122_T32TQM_20251008T122613.json
@@ -4218,22 +4218,22 @@
{
"description": "Multiscale layout of zarr datasets",
"name": "multiscales",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"uuid": "d35379db-88df-4056-af3a-620245f8e347"
},
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -4262,15 +4262,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -4830,15 +4830,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -6195,15 +6195,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -7463,15 +7463,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -8828,15 +8828,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -10196,15 +10196,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -11575,15 +11575,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -11891,15 +11891,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -12207,15 +12207,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -12530,15 +12530,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -13070,15 +13070,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -13796,15 +13796,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -14250,15 +14250,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
diff --git a/tests/_test_data/optimized_geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json b/tests/_test_data/optimized_geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
index caf32e63..d75fa908 100644
--- a/tests/_test_data/optimized_geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
+++ b/tests/_test_data/optimized_geozarr_examples/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.json
@@ -3913,22 +3913,22 @@
{
"description": "Multiscale layout of zarr datasets",
"name": "multiscales",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"uuid": "d35379db-88df-4056-af3a-620245f8e347"
},
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -3957,15 +3957,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -4525,15 +4525,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -5090,15 +5090,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -5958,15 +5958,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -6523,15 +6523,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -7091,15 +7091,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -7670,15 +7670,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -8210,15 +8210,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -8936,15 +8936,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
diff --git a/tests/_test_data/optimized_geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json b/tests/_test_data/optimized_geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
index 9ed8a49e..7eb259c3 100644
--- a/tests/_test_data/optimized_geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
+++ b/tests/_test_data/optimized_geozarr_examples/S2C_MSIL2A_20250811T112131_N0511_R037_T29TPF_20250811T152216.json
@@ -4218,22 +4218,22 @@
{
"description": "Multiscale layout of zarr datasets",
"name": "multiscales",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v0.1/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/multiscales/blob/v0.1/README.md",
"uuid": "d35379db-88df-4056-af3a-620245f8e347"
},
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -4262,15 +4262,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -4830,15 +4830,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -6195,15 +6195,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -7463,15 +7463,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -8828,15 +8828,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -10196,15 +10196,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -11575,15 +11575,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -11891,15 +11891,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -12207,15 +12207,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -12530,15 +12530,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -13070,15 +13070,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -13796,15 +13796,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
@@ -14250,15 +14250,15 @@
{
"description": "Spatial coordinate information",
"name": "spatial:",
- "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/54d81b7ced0376e63ee10f34db31db7d08dcc28d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/spatial/blob/54d81b7ced0376e63ee10f34db31db7d08dcc28d/README.md",
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
},
{
"description": "Coordinate reference system information for geospatial data",
"name": "proj:",
- "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
- "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
+ "schema_url": "https://raw.githubusercontent.com/zarr-conventions/proj/5ca5b2f92e5c7245f957d9128b289ee535f0720d/schema.json",
+ "spec_url": "https://github.com/zarr-conventions/proj/blob/5ca5b2f92e5c7245f957d9128b289ee535f0720d/README.md",
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f"
}
]
diff --git a/tests/conftest.py b/tests/conftest.py
index c08bade3..59746f8f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -28,7 +28,9 @@ def read_json(path: pathlib.Path) -> dict[str, object]:
"""
Read the contents of path as JSON
"""
- return json.loads(path.read_text())
+ obj = json.loads(path.read_text())
+ assert isinstance(obj, dict)
+ return obj
def get_stem(p: pathlib.Path) -> str:
@@ -40,8 +42,9 @@ def create_group_from_json(source_path: pathlib.Path, out_path: pathlib.Path) ->
Create a Zarr V2 group from a JSON model
"""
out_dir = out_path / (source_path.stem + ".zarr")
- g = GroupSpecV2(**read_json(source_path))
- g.to_zarr(out_dir, path="")
+ g: GroupSpecV2 = GroupSpecV2.model_validate(read_json(source_path))
+ # to_zarr is annotated to take a Store but accepts a path-like at runtime.
+ g.to_zarr(out_dir, path="") # type: ignore[arg-type]
return out_dir
@@ -87,8 +90,9 @@ def s2_geozarr_group_example(request: pytest.FixtureRequest) -> zarr.Group:
Return a memory-backed Zarr V3 Group based on a sentinel 2 product converted to geozarr
"""
source_path: pathlib.Path = request.param
- store = {}
- return GroupSpecV3(**read_json(source_path)).to_zarr(store, path="")
+ store: dict[str, bytes] = {}
+ # to_zarr is annotated to take a Store but accepts a dict-backed store at runtime.
+ return GroupSpecV3.model_validate(read_json(source_path)).to_zarr(store, path="") # type: ignore[arg-type]
@pytest.fixture(params=optimized_geozarr_example_paths, ids=get_stem)
@@ -97,8 +101,9 @@ def s2_optimized_geozarr_group_example(request: pytest.FixtureRequest) -> zarr.G
Return a memory-backed Zarr V3 Group based on a sentinel 2 product converted to geozarr
"""
source_path: pathlib.Path = request.param
- store = {}
- return GroupSpecV3(**read_json(source_path)).to_zarr(store, path="")
+ store: dict[str, bytes] = {}
+ # to_zarr is annotated to take a Store but accepts a dict-backed store at runtime.
+ return GroupSpecV3.model_validate(read_json(source_path)).to_zarr(store, path="") # type: ignore[arg-type]
@pytest.fixture(params=zcm_multiscales_example_paths, ids=get_stem)
@@ -207,7 +212,7 @@ def _verify_geozarr_spec_compliance(output_path: pathlib.Path, group: str) -> No
# Check coordinates
for coord_name in ds.coords:
- if coord_name not in ["spatial_ref"]: # Skip CRS coordinate
+ if coord_name != "spatial_ref": # Skip CRS coordinate
assert "_ARRAY_DIMENSIONS" in ds[coord_name].attrs, (
f"Missing _ARRAY_DIMENSIONS for coordinate {coord_name} in {group}"
)
diff --git a/tests/test_array_attrs.py b/tests/test_array_attrs.py
index 53ef3e0a..a4234a11 100644
--- a/tests/test_array_attrs.py
+++ b/tests/test_array_attrs.py
@@ -46,7 +46,8 @@ def _is_float_array(node: dict) -> bool:
@pytest.fixture(params=_SNAPSHOTS, ids=lambda p: p.stem)
def snapshot(request: pytest.FixtureRequest) -> dict:
- return json.loads(request.param.read_text())
+ loaded: dict = json.loads(request.param.read_text())
+ return loaded
def test_no_eopf_attrs(snapshot: dict) -> None:
@@ -163,7 +164,9 @@ def test_fill_value_masking_roundtrip(tmp_path: pathlib.Path) -> None:
"NaN cells in converter output should be masked when opened with "
"use_zarr_fill_value_as_mask=True"
)
- assert masked.mask[0, 0], "nodata corner cell must be masked"
- assert not masked.mask[-1, -1], "valid cell must not be masked"
+ mask = masked.mask
+ assert isinstance(mask, np.ndarray), "mask must be an array, not a scalar"
+ assert mask[0, 0], "nodata corner cell must be masked"
+ assert not mask[-1, -1], "valid cell must not be masked"
finally:
reopened.close()
diff --git a/tests/test_cli_e2e.py b/tests/test_cli_e2e.py
index 6b11b120..b91ce2b9 100644
--- a/tests/test_cli_e2e.py
+++ b/tests/test_cli_e2e.py
@@ -140,7 +140,7 @@ def test_cli_convert_real_sentinel2_data(s2_group_example: Path, tmp_path: Path)
GroupSpec.from_zarr(zarr.open_group(output_path)).model_dump()
)
assert expected_structure_json == observed_structure_json, view_json_diff(
- expected_structure_json, observed_structure_json
+ dict(expected_structure_json), dict(observed_structure_json)
)
@@ -206,7 +206,7 @@ def test_cli_crs_groups_option() -> None:
assert "Groups that need CRS information added" in result.stdout, "Help text should be present"
-def test_cli_convert_with_crs_groups(s2_group_example, tmp_path: Path) -> None:
+def test_cli_convert_with_crs_groups(s2_group_example: Path, tmp_path: Path) -> None:
"""
Test CLI conversion with --crs-groups option using real Sentinel-2 data.
diff --git a/tests/test_convention_attrs.py b/tests/test_convention_attrs.py
new file mode 100644
index 00000000..f3ddf576
--- /dev/null
+++ b/tests/test_convention_attrs.py
@@ -0,0 +1,142 @@
+"""Tests for the zarr-cm convention-attribute helpers in conversion.utils."""
+
+from __future__ import annotations
+
+from typing import cast
+
+import pytest
+from pyproj import CRS
+from zarr_cm import geo_proj
+from zarr_cm import multiscales as multiscales_cm
+from zarr_cm import spatial as spatial_cm
+
+from eopf_geozarr.conversion.utils import (
+ build_convention_attrs,
+ proj_attrs_for_crs,
+)
+
+
+def test_proj_attrs_for_crs_epsg() -> None:
+ """A CRS with an EPSG code yields proj:code."""
+ crs = CRS.from_epsg(32632)
+ assert proj_attrs_for_crs(crs) == {"proj:code": "EPSG:32632"}
+
+
+def test_proj_attrs_for_crs_wkt_fallback() -> None:
+ """A CRS without an EPSG code falls back to proj:wkt2."""
+ # A bare WKT-defined CRS with no authority code.
+ crs = CRS.from_wkt(CRS.from_epsg(4326).to_wkt())
+ out = proj_attrs_for_crs(crs)
+ # from_wkt of an EPSG CRS still resolves an epsg in most pyproj versions, so
+ # accept either, but the key must be one of the two proj CRS keys.
+ assert set(out) <= {"proj:code", "proj:wkt2"}
+ assert len(out) == 1
+
+
+def test_proj_attrs_for_crs_none() -> None:
+ """No CRS yields an empty dict (no proj keys)."""
+ assert proj_attrs_for_crs(None) == {}
+
+
+def test_build_convention_attrs_data_and_cmos() -> None:
+ """The helper emits spatial+proj data keys plus their CMOs, in order."""
+ bbox = [300000.0, 4990200.0, 409800.0, 5100000.0]
+ out = build_convention_attrs(
+ spatial={
+ "spatial:dimensions": ["y", "x"],
+ "spatial:bbox": bbox,
+ "spatial:registration": "pixel",
+ },
+ crs=CRS.from_epsg(32632),
+ )
+ data: dict[str, object] = dict(out)
+ assert data["spatial:dimensions"] == ["y", "x"]
+ assert data["spatial:bbox"] == bbox
+ assert data["spatial:registration"] == "pixel"
+ assert data["proj:code"] == "EPSG:32632"
+ conventions = out.get("zarr_conventions")
+ assert conventions is not None
+ names = [c.get("name") for c in conventions]
+ assert names == [spatial_cm.CMO.get("name"), geo_proj.CMO.get("name")]
+
+
+def test_build_convention_attrs_matches_handwritten() -> None:
+ """Output is byte-equivalent to the previous hand-assembled dict."""
+ bbox = [300000.0, 4990200.0, 409800.0, 5100000.0]
+ hand: dict[str, object] = {
+ "spatial:dimensions": ["y", "x"],
+ "spatial:bbox": bbox,
+ "spatial:registration": "pixel",
+ "proj:code": "EPSG:32632",
+ }
+ out = build_convention_attrs(
+ spatial={
+ "spatial:dimensions": ["y", "x"],
+ "spatial:bbox": bbox,
+ "spatial:registration": "pixel",
+ },
+ crs=CRS.from_epsg(32632),
+ )
+ data = {k: v for k, v in out.items() if k != "zarr_conventions"}
+ assert data == hand
+
+
+def test_build_convention_attrs_validates() -> None:
+ """Invalid spatial data is rejected by zarr-cm validation."""
+ with pytest.raises(ValueError, match="spatial:dimensions"):
+ build_convention_attrs(
+ # missing required dimensions — exercises runtime validation
+ spatial=cast("spatial_cm.SpatialAttrs", {"spatial:registration": "pixel"}),
+ crs=CRS.from_epsg(32632),
+ )
+
+
+def test_build_convention_attrs_with_multiscales() -> None:
+ """With multiscales, CMOs are ordered [multiscales, spatial, proj]."""
+ out = build_convention_attrs(
+ multiscales=cast(
+ "multiscales_cm.MultiscalesAttrs",
+ {
+ "layout": [
+ {"asset": "r10m", "spatial:shape": [10980, 10980]},
+ {
+ "asset": "r20m",
+ "derived_from": "r10m",
+ "transform": {"scale": [2.0, 2.0], "translation": [0.0, 0.0]},
+ "spatial:shape": [5490, 5490],
+ },
+ ],
+ "resampling_method": "average",
+ },
+ ),
+ spatial={"spatial:dimensions": ["y", "x"]},
+ crs=CRS.from_epsg(32632),
+ )
+ conventions = out.get("zarr_conventions")
+ assert conventions is not None
+ names = [c.get("name") for c in conventions]
+ assert names == [
+ multiscales_cm.CMO.get("name"),
+ spatial_cm.CMO.get("name"),
+ geo_proj.CMO.get("name"),
+ ]
+ # extra layout keys (spatial:shape) survive the round-trip
+ multiscales = out.get("multiscales")
+ assert multiscales is not None
+ layout = multiscales["layout"]
+ assert isinstance(layout, list)
+ first_layout: dict[str, object] = dict(layout[0])
+ assert first_layout["spatial:shape"] == [10980, 10980]
+
+
+def test_build_convention_attrs_multiscales_validation() -> None:
+ """zarr-cm rejects a layout entry with derived_from but no transform."""
+ with pytest.raises(ValueError, match="transform"):
+ build_convention_attrs(
+ multiscales={
+ "layout": [{"asset": "r20m", "derived_from": "r10m"}], # no transform
+ "resampling_method": "average",
+ },
+ spatial={"spatial:dimensions": ["y", "x"]},
+ crs=CRS.from_epsg(32632),
+ )
diff --git a/tests/test_data_api/conftest.py b/tests/test_data_api/conftest.py
index be567726..03537803 100644
--- a/tests/test_data_api/conftest.py
+++ b/tests/test_data_api/conftest.py
@@ -5,13 +5,9 @@
import re
from collections.abc import Mapping
from pathlib import Path
-from typing import TYPE_CHECKING
import pytest
-if TYPE_CHECKING:
- from typing import Any
-
def extract_json_code_blocks(
markdown_content: str,
@@ -53,7 +49,8 @@ def extract_json_code_blocks(
end_line: int = i # Line with closing ```
raw_json: str = "\n".join(json_lines)
- parsed_json: Any = json.loads(raw_json)
+ parsed_json: object = json.loads(raw_json)
+ assert isinstance(parsed_json, dict)
code_blocks[(start_line + 1, end_line)] = parsed_json
i += 1
diff --git a/tests/test_data_api/test_geoproj.py b/tests/test_data_api/test_geoproj.py
index 5949099c..c9f734ce 100644
--- a/tests/test_data_api/test_geoproj.py
+++ b/tests/test_data_api/test_geoproj.py
@@ -14,7 +14,7 @@ class TestProj:
def test_proj_with_epsg_code(self) -> None:
"""Test creation with EPSG code."""
- proj = Proj(**{"proj:code": "EPSG:4326"})
+ proj = Proj.model_validate({"proj:code": "EPSG:4326"})
assert proj.code == "EPSG:4326"
assert proj.wkt2 is None
@@ -23,7 +23,7 @@ def test_proj_with_epsg_code(self) -> None:
def test_proj_with_wkt2(self) -> None:
"""Test creation with WKT2 string."""
wkt2_example = 'GEOGCRS["WGS 84",DATUM["World Geodetic System 1984"]]'
- proj = Proj(**{"proj:wkt2": wkt2_example})
+ proj = Proj.model_validate({"proj:wkt2": wkt2_example})
assert proj.wkt2 == wkt2_example
assert proj.code is None
@@ -36,7 +36,7 @@ def test_proj_with_projjson(self) -> None:
"type": "GeographicCRS",
"name": "WGS 84",
}
- proj = Proj(**{"proj:projjson": projjson_data})
+ proj = Proj.model_validate({"proj:projjson": projjson_data})
assert proj.projjson is not None
assert proj.code is None
@@ -45,7 +45,7 @@ def test_proj_with_projjson(self) -> None:
def test_proj_validation_error_no_crs(self) -> None:
"""Test that missing all CRS fields raises ValidationError."""
with pytest.raises(ValidationError) as exc_info:
- Proj()
+ Proj() # pyright: ignore[reportCallIssue] # no-args construction tests the validation error
assert "At least one of proj:code, proj:wkt2, or proj:projjson must be provided" in str(
exc_info.value
@@ -54,7 +54,7 @@ def test_proj_validation_error_no_crs(self) -> None:
def test_proj_multiple_crs_fields(self) -> None:
"""Test that multiple CRS fields can be provided."""
wkt2_example = 'GEOGCRS["WGS 84",DATUM["World Geodetic System 1984"]]'
- proj = Proj(**{"proj:code": "EPSG:4326", "proj:wkt2": wkt2_example})
+ proj = Proj.model_validate({"proj:code": "EPSG:4326", "proj:wkt2": wkt2_example})
assert proj.code == "EPSG:4326"
assert proj.wkt2 == wkt2_example
@@ -62,7 +62,7 @@ def test_proj_multiple_crs_fields(self) -> None:
def test_proj_serialization_by_alias(self) -> None:
"""Test that serialization uses aliases (proj: prefixes)."""
- proj = Proj(**{"proj:code": "EPSG:32633"})
+ proj = Proj.model_validate({"proj:code": "EPSG:32633"})
result = proj.model_dump()
# Should serialize with proj: prefix
@@ -74,7 +74,7 @@ def test_proj_serialization_by_alias(self) -> None:
def test_proj_none_fields_excluded(self) -> None:
"""Test that None fields are excluded from serialization."""
- proj = Proj(**{"proj:code": "EPSG:4326"})
+ proj = Proj.model_validate({"proj:code": "EPSG:4326"})
result = proj.model_dump()
# None fields should be excluded
@@ -86,8 +86,8 @@ def test_proj_none_fields_excluded(self) -> None:
def test_proj_extra_fields_allowed(self) -> None:
"""Test that extra fields are allowed."""
- proj = Proj(
- **{
+ proj = Proj.model_validate(
+ {
"proj:code": "EPSG:4326",
"custom_field": "custom_value",
"proj:custom": "also_allowed",
@@ -104,7 +104,7 @@ def test_proj_roundtrip_serialization(self) -> None:
original_data = {"proj:code": "EPSG:32633", "proj:wkt2": 'PROJCRS["WGS 84 / UTM zone 33N"]'}
# Create model, serialize, then recreate
- proj1 = Proj(**original_data)
+ proj1 = Proj.model_validate(original_data)
serialized = proj1.model_dump()
proj2 = Proj(**serialized)
@@ -124,8 +124,8 @@ def test_geoproj_is_proj_alias(self) -> None:
def test_geoproj_functionality(self) -> None:
"""Test that GeoProj works exactly like Proj."""
# Create using both classes
- proj_instance = Proj(**{"proj:code": "EPSG:4326"})
- geoproj_instance = GeoProj(**{"proj:code": "EPSG:4326"})
+ proj_instance = Proj.model_validate({"proj:code": "EPSG:4326"})
+ geoproj_instance = GeoProj.model_validate({"proj:code": "EPSG:4326"})
# Should be instances of the same class
assert type(proj_instance) is type(geoproj_instance)
diff --git a/tests/test_data_api/test_geozarr/test_common.py b/tests/test_data_api/test_geozarr/test_common.py
index fa54ffb8..e7286726 100644
--- a/tests/test_data_api/test_geozarr/test_common.py
+++ b/tests/test_data_api/test_geozarr/test_common.py
@@ -1,11 +1,14 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from collections.abc import Mapping
+from typing import TYPE_CHECKING
import numpy as np
import pytest
from pydantic_zarr.core import tuplify_json
+from pydantic_zarr.v2 import AnyGroupSpec as AnyGroupSpec_V2
from pydantic_zarr.v2 import GroupSpec as GroupSpec_V2
+from pydantic_zarr.v3 import AnyGroupSpec as AnyGroupSpec_V3
from pydantic_zarr.v3 import GroupSpec as GroupSpec_V3
from eopf_geozarr.data_api.geozarr.common import (
@@ -33,7 +36,7 @@
DataArray_V3.from_array(np.arange(10), dimension_names=("time",)),
],
)
-def test_datarraylike(obj: DataArray_V2 | DataArray_V3) -> None:
+def test_datarraylike(obj: object) -> None:
"""
Test that the DataArrayLike protocol works correctly
"""
@@ -41,7 +44,7 @@ def test_datarraylike(obj: DataArray_V2 | DataArray_V3) -> None:
@pytest.mark.parametrize("obj", [GroupSpec_V2(attributes={}), GroupSpec_V3(attributes={})])
-def test_grouplike(obj: GroupSpec_V3[Any, Any] | GroupSpec_V2[Any, Any]) -> None:
+def test_grouplike(obj: AnyGroupSpec_V3 | AnyGroupSpec_V2) -> None:
"""
Test that the GroupLike protocol works correctly
"""
@@ -80,9 +83,12 @@ def test_multiscales_round_trip(s2_optimized_geozarr_group_example: zarr.Group)
"""
Ensure that we can round-trip multiscale metadata through the `Multiscales` model.
"""
- source_untyped = GroupSpec_V3.from_zarr(s2_optimized_geozarr_group_example)
+ source_untyped: AnyGroupSpec_V3 = GroupSpec_V3.from_zarr(s2_optimized_geozarr_group_example)
flat = source_untyped.to_flat()
- meta = flat["/measurements/reflectance"].attributes["multiscales"]
+ attributes = flat["/measurements/reflectance"].attributes
+ assert isinstance(attributes, Mapping)
+ meta = attributes["multiscales"]
+ assert isinstance(meta, Mapping)
# pull out the multiscales keys, ignore extra
submodel = tuplify_json({k: meta[k] for k in ZCMMultiscales.model_fields if k in meta})
assert ZCMMultiscales(**submodel).model_dump() == submodel
@@ -95,11 +101,11 @@ def test_projattrs_crs_required() -> None:
with pytest.raises(
ValueError, match=r"One of 'code', 'wkt2', or 'projjson' must be provided\."
):
- ProjAttrs()
+ ProjAttrs() # pyright: ignore[reportCallIssue] # no-args construction tests the validation error
def test_projattrs_json_examples(
- proj_attrs_examples: dict[tuple[int, int], dict[str, Any]],
+ proj_attrs_examples: dict[tuple[int, int], dict[str, object]],
) -> None:
"""
Test that proj attributes in the JSON examples of the proj extension README are valid.
@@ -108,19 +114,22 @@ def test_projattrs_json_examples(
for json_block in proj_attrs_examples.values():
# Check if this JSON block contains geo.proj attributes
- if "attributes" in json_block and isinstance(json_block["attributes"], dict):
- geo: Any = json_block["attributes"].get("geo")
+ attributes = json_block.get("attributes")
+ if isinstance(attributes, dict):
+ geo: object = attributes.get("geo")
if geo and isinstance(geo, dict) and "proj" in geo:
proj_examples_found += 1
- proj_data: dict[str, Any] = geo["proj"]
+ proj_data_obj: object = geo["proj"]
+ assert isinstance(proj_data_obj, dict)
+ proj_data: dict[str, object] = proj_data_obj
# Validate that ProjAttrs can parse this data
- proj_attrs: ProjAttrs = ProjAttrs(**proj_data)
+ proj_attrs: ProjAttrs = ProjAttrs.model_validate(proj_data)
# Verify that all fields from the original data are present in the model
for key, value in proj_data.items():
if value is not None:
- model_value: Any = getattr(proj_attrs, key)
+ model_value: object = getattr(proj_attrs, key)
# Handle tuple/list comparison for transform and bbox fields
if isinstance(value, list) and isinstance(model_value, tuple):
assert tuple(value) == model_value, f"Field {key} mismatch"
diff --git a/tests/test_data_api/test_geozarr/test_multiscales/test_geozarr.py b/tests/test_data_api/test_geozarr/test_multiscales/test_geozarr.py
index a9c9418c..3391d796 100644
--- a/tests/test_data_api/test_geozarr/test_multiscales/test_geozarr.py
+++ b/tests/test_data_api/test_geozarr/test_multiscales/test_geozarr.py
@@ -1,7 +1,8 @@
-from typing import Any, Literal
+from typing import Literal
import pytest
from pydantic.experimental.missing_sentinel import MISSING
+from zarr_cm import ConventionMetadataObject
from zarr_cm import multiscales as multiscales_cm
from eopf_geozarr.data_api.geozarr.multiscales import tms, zcm
@@ -18,13 +19,13 @@ def test_multiscale_group_attrs(multiscale_flavor: set[Literal["zcm", "tms"]]) -
"""
zcm_meta: dict[str, object] = {}
tms_meta: dict[str, object] = {}
- zarr_conventions_meta: MISSING | tuple[Any, ...] = MISSING
+ zarr_conventions_meta: tuple[ConventionMetadataObject, ...] | MISSING = MISSING
if "zcm" in multiscale_flavor:
layout = (
zcm.ScaleLevel(
asset="level_0",
- transform={"scale": (1.0, 1.0), "translation": (0.0, 0.0)},
+ transform=zcm.Transform(scale=(1.0, 1.0), translation=(0.0, 0.0)),
),
)
zcm_meta = zcm.Multiscales(layout=layout, resampling_method="nearest").model_dump()
@@ -58,13 +59,17 @@ def test_multiscale_group_attrs(multiscale_flavor: set[Literal["zcm", "tms"]]) -
)
},
).model_dump()
- multiscale_meta = MultiscaleMeta(**{**zcm_meta, **tms_meta})
+ multiscale_meta = MultiscaleMeta.model_validate({**zcm_meta, **tms_meta})
multiscale_group_attrs = MultiscaleGroupAttrs(
zarr_conventions=zarr_conventions_meta, multiscales=multiscale_meta
)
if "zcm" in multiscale_flavor:
assert "zcm" in multiscale_group_attrs.multiscale_meta
- assert multiscale_group_attrs.multiscale_meta["zcm"] == zcm.Multiscales(**zcm_meta)
+ assert multiscale_group_attrs.multiscale_meta["zcm"] == zcm.Multiscales.model_validate(
+ zcm_meta
+ )
if "tms" in multiscale_flavor:
assert "tms" in multiscale_group_attrs.multiscale_meta
- assert multiscale_group_attrs.multiscale_meta["tms"] == tms.Multiscales(**tms_meta)
+ assert multiscale_group_attrs.multiscale_meta["tms"] == tms.Multiscales.model_validate(
+ tms_meta
+ )
diff --git a/tests/test_data_api/test_geozarr/test_multiscales/test_zcm.py b/tests/test_data_api/test_geozarr/test_multiscales/test_zcm.py
index 32d3dc4c..52be4251 100644
--- a/tests/test_data_api/test_geozarr/test_multiscales/test_zcm.py
+++ b/tests/test_data_api/test_geozarr/test_multiscales/test_zcm.py
@@ -30,7 +30,7 @@ def test_scale_level_from_group() -> None:
"""
meta = {"group": "1", "from_group": "0"}
with pytest.raises(ValidationError):
- ScaleLevel(**meta)
+ ScaleLevel.model_validate(meta)
def test_scalelevel_json() -> None:
@@ -46,4 +46,4 @@ def test_scalelevel_json() -> None:
},
"resampling_method": "nearest",
}
- assert ScaleLevel(**x).model_dump() == x
+ assert ScaleLevel.model_validate(x).model_dump() == x
diff --git a/tests/test_data_api/test_projjson.py b/tests/test_data_api/test_projjson.py
index ada54398..ae2214f8 100644
--- a/tests/test_data_api/test_projjson.py
+++ b/tests/test_data_api/test_projjson.py
@@ -7,8 +7,6 @@
from __future__ import annotations
-from typing import Any
-
import pytest
from pydantic import ValidationError
@@ -39,51 +37,52 @@ class TestBasicModels:
def test_id_model(self) -> None:
"""Test Id model validation"""
# Valid ID with required fields
- id_data: dict[str, Any] = {"authority": "EPSG", "code": 4326}
- id_obj: Id = Id(**id_data)
+ id_data: dict[str, object] = {"authority": "EPSG", "code": 4326}
+ id_obj: Id = Id.model_validate(id_data)
assert id_obj.authority == "EPSG"
assert id_obj.code == 4326
# ID with string code
- id_data_str: dict[str, Any] = {"authority": "EPSG", "code": "4326"}
- id_obj_str: Id = Id(**id_data_str)
+ id_data_str: dict[str, object] = {"authority": "EPSG", "code": "4326"}
+ id_obj_str: Id = Id.model_validate(id_data_str)
assert id_obj_str.code == "4326"
# ID with optional fields
- id_full: dict[str, Any] = {
+ id_full: dict[str, object] = {
"authority": "EPSG",
"code": 4326,
"version": "10.095",
"authority_citation": "EPSG Geodetic Parameter Dataset",
"uri": "urn:ogc:def:crs:EPSG::4326",
}
- id_obj_full: Id = Id(**id_full)
+ id_obj_full: Id = Id.model_validate(id_full)
assert id_obj_full.version == "10.095"
assert id_obj_full.uri == "urn:ogc:def:crs:EPSG::4326"
# Missing required field should raise ValidationError
with pytest.raises(ValidationError):
- Id(authority="EPSG") # missing code
+ Id(authority="EPSG") # type: ignore[call-arg] # missing code (intentional)
def test_unit_model(self) -> None:
"""Test Unit model validation"""
- unit_data: dict[str, Any] = {
+ unit_data: dict[str, object] = {
"type": "Unit",
"name": "metre",
"conversion_factor": 1.0,
}
- unit: Unit = Unit(**unit_data)
+ unit: Unit = Unit.model_validate(unit_data)
assert unit.name == "metre"
assert unit.conversion_factor == 1.0
# With ID
- unit_with_id: dict[str, Any] = {
+ unit_with_id: dict[str, object] = {
"type": "Unit",
"name": "degree",
"conversion_factor": 0.017453292519943295,
"id": {"authority": "EPSG", "code": 9122},
}
- unit = Unit(**unit_with_id)
+ unit = Unit.model_validate(unit_with_id)
+ assert unit.id is not None
assert unit.id.authority == "EPSG"
assert unit.id.code == 9122
@@ -101,17 +100,18 @@ def test_bbox_model(self) -> None:
# Missing required field
with pytest.raises(ValidationError):
- BBox(east_longitude=180.0, west_longitude=-180.0) # missing latitude fields
+ # missing latitude fields (intentional)
+ BBox(east_longitude=180.0, west_longitude=-180.0) # type: ignore[call-arg]
def test_axis_model(self) -> None:
"""Test Axis model validation"""
- axis_data: dict[str, str] = {
+ axis_data: dict[str, object] = {
"type": "Axis",
"name": "Geodetic latitude",
"abbreviation": "Lat",
"direction": "north",
}
- axis: Axis = Axis(**axis_data)
+ axis: Axis = Axis.model_validate(axis_data)
assert axis.name == "Geodetic latitude"
assert axis.direction == "north"
@@ -121,7 +121,7 @@ def test_axis_model(self) -> None:
type="Axis",
name="Invalid",
abbreviation="Inv",
- direction="invalid_direction",
+ direction="invalid_direction", # type: ignore[arg-type] # invalid direction (intentional)
)
@@ -130,35 +130,35 @@ class TestEllipsoidModel:
def test_ellipsoid_with_semi_axes(self) -> None:
"""Test ellipsoid with semi-major and semi-minor axes"""
- ellipsoid_data: dict[str, Any] = {
+ ellipsoid_data: dict[str, object] = {
"type": "Ellipsoid",
"name": "WGS 84",
"semi_major_axis": 6378137.0,
"semi_minor_axis": 6356752.314245179,
}
- ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data)
+ ellipsoid: Ellipsoid = Ellipsoid.model_validate(ellipsoid_data)
assert ellipsoid.name == "WGS 84"
assert ellipsoid.semi_major_axis == 6378137.0
def test_ellipsoid_with_inverse_flattening(self) -> None:
"""Test ellipsoid with inverse flattening"""
- ellipsoid_data: dict[str, Any] = {
+ ellipsoid_data: dict[str, object] = {
"type": "Ellipsoid",
"name": "WGS 84",
"semi_major_axis": 6378137.0,
"inverse_flattening": 298.257223563,
}
- ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data)
+ ellipsoid: Ellipsoid = Ellipsoid.model_validate(ellipsoid_data)
assert ellipsoid.inverse_flattening == 298.257223563
def test_ellipsoid_sphere(self) -> None:
"""Test spherical ellipsoid (equal radii)"""
- ellipsoid_data: dict[str, Any] = {
+ ellipsoid_data: dict[str, object] = {
"type": "Ellipsoid",
"name": "Sphere",
"radius": 6371000.0,
}
- ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data)
+ ellipsoid: Ellipsoid = Ellipsoid.model_validate(ellipsoid_data)
assert ellipsoid.radius == 6371000.0
@@ -167,7 +167,7 @@ class TestCoordinateSystemModel:
def test_ellipsoidal_coordinate_system(self) -> None:
"""Test ellipsoidal coordinate system"""
- cs_data: dict[str, Any] = {
+ cs_data: dict[str, object] = {
"type": "CoordinateSystem",
"subtype": "ellipsoidal",
"axis": [
@@ -195,14 +195,14 @@ def test_ellipsoidal_coordinate_system(self) -> None:
},
],
}
- cs: CoordinateSystem = CoordinateSystem(**cs_data)
+ cs: CoordinateSystem = CoordinateSystem.model_validate(cs_data)
assert cs.subtype == "ellipsoidal"
assert len(cs.axis) == 2
assert cs.axis[0].name == "Geodetic latitude"
def test_cartesian_coordinate_system(self) -> None:
"""Test Cartesian coordinate system"""
- cs_data: dict[str, Any] = {
+ cs_data: dict[str, object] = {
"type": "CoordinateSystem",
"subtype": "Cartesian",
"axis": [
@@ -222,7 +222,7 @@ def test_cartesian_coordinate_system(self) -> None:
},
],
}
- cs: CoordinateSystem = CoordinateSystem(**cs_data)
+ cs: CoordinateSystem = CoordinateSystem.model_validate(cs_data)
assert cs.subtype == "Cartesian"
assert cs.axis[0].direction == "east"
assert cs.axis[1].direction == "north"
@@ -233,7 +233,7 @@ class TestCRSModels:
def test_geodetic_crs_wgs84(self) -> None:
"""Test WGS 84 geodetic CRS"""
- wgs84_data: dict[str, Any] = {
+ wgs84_data: dict[str, object] = {
"type": "GeographicCRS",
"name": "WGS 84",
"datum": {
@@ -276,14 +276,16 @@ def test_geodetic_crs_wgs84(self) -> None:
},
"id": {"authority": "EPSG", "code": 4326},
}
- crs: GeodeticCRS = GeodeticCRS(**wgs84_data)
+ crs: GeodeticCRS = GeodeticCRS.model_validate(wgs84_data)
assert crs.name == "WGS 84"
+ assert crs.datum is not None
assert crs.datum.name == "World Geodetic System 1984"
+ assert crs.id is not None
assert crs.id.code == 4326
def test_projected_crs_utm(self) -> None:
"""Test UTM projected CRS"""
- utm_data: dict[str, Any] = {
+ utm_data: dict[str, object] = {
"type": "ProjectedCRS",
"name": "WGS 84 / UTM zone 33N",
"base_crs": {
@@ -356,14 +358,14 @@ def test_projected_crs_utm(self) -> None:
],
},
}
- crs: ProjectedCRS = ProjectedCRS(**utm_data)
+ crs: ProjectedCRS = ProjectedCRS.model_validate(utm_data)
assert crs.name == "WGS 84 / UTM zone 33N"
assert crs.base_crs.name == "WGS 84"
assert crs.conversion.name == "UTM zone 33N"
def test_compound_crs(self) -> None:
"""Test compound CRS with horizontal and vertical components"""
- compound_data: dict[str, Any] = {
+ compound_data: dict[str, object] = {
"type": "CompoundCRS",
"name": "WGS 84 + EGM96 height",
"components": [
@@ -388,11 +390,15 @@ def test_compound_crs(self) -> None:
},
],
}
- crs: CompoundCRS = CompoundCRS(**compound_data)
+ crs: CompoundCRS = CompoundCRS.model_validate(compound_data)
assert crs.name == "WGS 84 + EGM96 height"
assert len(crs.components) == 2
- assert crs.components[0].name == "WGS 84"
- assert crs.components[1].name == "EGM96 height"
+ component_0 = crs.components[0]
+ component_1 = crs.components[1]
+ assert isinstance(component_0, GeodeticCRS)
+ assert isinstance(component_1, VerticalCRS)
+ assert component_0.name == "WGS 84"
+ assert component_1.name == "EGM96 height"
class TestDatumEnsemble:
@@ -400,7 +406,7 @@ class TestDatumEnsemble:
def test_datum_ensemble_creation(self) -> None:
"""Test creation of datum ensemble"""
- ensemble_data: dict[str, Any] = {
+ ensemble_data: dict[str, object] = {
"type": "DatumEnsemble",
"name": "World Geodetic System 1984 ensemble",
"members": [
@@ -416,7 +422,7 @@ def test_datum_ensemble_creation(self) -> None:
},
"accuracy": "2.0",
}
- ensemble: DatumEnsemble = DatumEnsemble(**ensemble_data)
+ ensemble: DatumEnsemble = DatumEnsemble.model_validate(ensemble_data)
assert ensemble.name == "World Geodetic System 1984 ensemble"
assert len(ensemble.members) == 3
assert ensemble.accuracy == "2.0"
@@ -427,7 +433,7 @@ class TestOperations:
def test_coordinate_metadata(self) -> None:
"""Test coordinate metadata"""
- metadata_data: dict[str, Any] = {
+ metadata_data: dict[str, object] = {
"type": "CoordinateMetadata",
"crs": {
"type": "GeographicCRS",
@@ -445,13 +451,14 @@ def test_coordinate_metadata(self) -> None:
},
"coordinateEpoch": 2020.0,
}
- metadata: CoordinateMetadata = CoordinateMetadata(**metadata_data)
+ metadata: CoordinateMetadata = CoordinateMetadata.model_validate(metadata_data)
assert metadata.coordinateEpoch == 2020.0
+ assert isinstance(metadata.crs, GeodeticCRS)
assert metadata.crs.name == "WGS 84"
def test_single_operation(self) -> None:
"""Test single operation (transformation)"""
- operation_data: dict[str, Any] = {
+ operation_data: dict[str, object] = {
"type": "Transformation",
"name": "NAD27 to NAD83 (1)",
"method": {"type": "OperationMethod", "name": "NADCON"},
@@ -469,9 +476,10 @@ def test_single_operation(self) -> None:
],
"accuracy": "0.15",
}
- operation: SingleOperation = SingleOperation(**operation_data)
+ operation: SingleOperation = SingleOperation.model_validate(operation_data)
assert operation.name == "NAD27 to NAD83 (1)"
assert operation.accuracy == "0.15"
+ assert operation.parameters is not None
assert len(operation.parameters) == 2
@@ -480,27 +488,28 @@ class TestValidationEdgeCases:
def test_invalid_crs_type(self) -> None:
"""Test invalid CRS type raises ValidationError"""
- invalid_data: dict[str, Any] = {
+ invalid_data: dict[str, object] = {
"type": "InvalidCRS", # Invalid type
"name": "Invalid CRS",
}
with pytest.raises(ValidationError):
- GeodeticCRS(**invalid_data)
+ GeodeticCRS(**invalid_data) # type: ignore[arg-type] # building model from dict[str, object] (intentional)
def test_missing_required_fields(self) -> None:
"""Test missing required fields raise ValidationError"""
# Missing name for CRS
with pytest.raises(ValidationError):
- GeodeticCRS(type="GeographicCRS")
+ GeodeticCRS(type="GeographicCRS") # type: ignore[call-arg] # missing name (intentional)
# Missing ellipsoid for geodetic reference frame
with pytest.raises(ValidationError):
- GeodeticReferenceFrame(type="GeodeticReferenceFrame", name="Test Datum")
+ # missing ellipsoid (intentional)
+ GeodeticReferenceFrame(type="GeodeticReferenceFrame", name="Test Datum") # type: ignore[call-arg]
def test_mutually_exclusive_fields(self) -> None:
"""Test that mutually exclusive fields are properly validated"""
# Cannot have both id and ids
- invalid_data: dict[str, Any] = {
+ invalid_data: dict[str, object] = {
"type": "Unit",
"name": "metre",
"conversion_factor": 1.0,
@@ -511,27 +520,27 @@ def test_mutually_exclusive_fields(self) -> None:
# For now, we'll just ensure the model can be created with either field
with pytest.raises(ValidationError):
- Unit(**invalid_data)
+ Unit(**invalid_data) # type: ignore[arg-type] # building model from dict[str, object] (intentional)
# Valid with id only
- valid_with_id: dict[str, Any] = {
+ valid_with_id: dict[str, object] = {
"type": "Unit",
"name": "metre",
"conversion_factor": 1.0,
"id": {"authority": "EPSG", "code": 9001},
}
- unit: Unit = Unit(**valid_with_id)
+ unit: Unit = Unit.model_validate(valid_with_id)
assert unit.id is not None
assert unit.ids is None
# Valid with ids only
- valid_with_ids: dict[str, Any] = {
+ valid_with_ids: dict[str, object] = {
"type": "Unit",
"name": "metre",
"conversion_factor": 1.0,
"ids": [{"authority": "EPSG", "code": 9001}],
}
- unit = Unit(**valid_with_ids)
+ unit = Unit.model_validate(valid_with_ids)
assert unit.ids is not None
assert unit.id is None
@@ -542,7 +551,7 @@ class TestSerializationDeserialization:
def test_round_trip_serialization(self) -> None:
"""Test that models can be serialized to JSON and back"""
# Create a simple CRS
- crs_data: dict[str, Any] = {
+ crs_data: dict[str, object] = {
"type": "GeographicCRS",
"name": "WGS 84",
"datum": {
@@ -558,14 +567,16 @@ def test_round_trip_serialization(self) -> None:
}
# Create model instance
- original_crs: GeodeticCRS = GeodeticCRS(**crs_data)
+ original_crs: GeodeticCRS = GeodeticCRS.model_validate(crs_data)
# Deserialize back to model
- json_data: dict[str, Any] = original_crs.model_dump()
- reconstructed_crs: GeodeticCRS = GeodeticCRS(**json_data)
+ json_data: dict[str, object] = original_crs.model_dump()
+ reconstructed_crs: GeodeticCRS = GeodeticCRS.model_validate(json_data)
# Verify they're equivalent
assert reconstructed_crs.name == original_crs.name
+ assert reconstructed_crs.datum is not None
+ assert original_crs.datum is not None
assert reconstructed_crs.datum.name == original_crs.datum.name
assert reconstructed_crs.datum.ellipsoid.name == original_crs.datum.ellipsoid.name
@@ -574,17 +585,17 @@ def test_projjson_union_type(self) -> None:
# Test with different types that should all be valid ProjJSON
# Ellipsoid
- ellipsoid_data: dict[str, Any] = {
+ ellipsoid_data: dict[str, object] = {
"type": "Ellipsoid",
"name": "WGS 84",
"semi_major_axis": 6378137.0,
"inverse_flattening": 298.257223563,
}
- ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data)
+ ellipsoid: Ellipsoid = Ellipsoid.model_validate(ellipsoid_data)
assert ellipsoid.name == "WGS 84"
# CRS
- crs_data: dict[str, Any] = {
+ crs_data: dict[str, object] = {
"type": "GeographicCRS",
"name": "WGS 84",
"datum": {
@@ -593,26 +604,26 @@ def test_projjson_union_type(self) -> None:
"ellipsoid": ellipsoid_data,
},
}
- crs: GeodeticCRS = GeodeticCRS(**crs_data)
+ crs: GeodeticCRS = GeodeticCRS.model_validate(crs_data)
assert crs.name == "WGS 84"
class TestRoundTripSerialization:
"""Test round-trip serialization with real PROJ JSON examples."""
- def test_projected_crs_round_trip(self, projected_crs_json: dict[str, Any]) -> None:
+ def test_projected_crs_round_trip(self, projected_crs_json: dict[str, object]) -> None:
"""Test round-trip serialization of projected CRS example."""
# Parse JSON to Pydantic model
from eopf_geozarr.data_api.geozarr.projjson import ProjectedCRS
# Create model from JSON
- original_crs: ProjectedCRS = ProjectedCRS(**projected_crs_json)
+ original_crs: ProjectedCRS = ProjectedCRS.model_validate(projected_crs_json)
# Serialize back to dict
- serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True)
+ serialized: dict[str, object] = original_crs.model_dump(exclude_none=True)
# Create model from serialized data
- round_trip_crs: ProjectedCRS = ProjectedCRS(**serialized)
+ round_trip_crs: ProjectedCRS = ProjectedCRS.model_validate(serialized)
# Verify key properties are preserved
assert round_trip_crs.name == original_crs.name
@@ -620,88 +631,100 @@ def test_projected_crs_round_trip(self, projected_crs_json: dict[str, Any]) -> N
assert round_trip_crs.base_crs.name == original_crs.base_crs.name
assert round_trip_crs.conversion.name == original_crs.conversion.name
if original_crs.id:
+ assert round_trip_crs.id is not None
assert round_trip_crs.id.authority == original_crs.id.authority
assert round_trip_crs.id.code == original_crs.id.code
- def test_bound_crs_round_trip(self, bound_crs_json: dict[str, Any]) -> None:
+ def test_bound_crs_round_trip(self, bound_crs_json: dict[str, object]) -> None:
"""Test round-trip serialization of bound CRS example."""
from eopf_geozarr.data_api.geozarr.projjson import BoundCRS
# Create model from JSON
- original_crs: BoundCRS = BoundCRS(**bound_crs_json)
+ original_crs: BoundCRS = BoundCRS.model_validate(bound_crs_json)
# Serialize back to dict
- serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True)
+ serialized: dict[str, object] = original_crs.model_dump(exclude_none=True)
# Create model from serialized data
- round_trip_crs: BoundCRS = BoundCRS(**serialized)
+ round_trip_crs: BoundCRS = BoundCRS.model_validate(serialized)
# Verify key properties are preserved
assert round_trip_crs.type == original_crs.type
+ # A nested BoundCRS has no "name"; this example's source/target are named CRSs.
+ assert not isinstance(round_trip_crs.source_crs, BoundCRS)
+ assert not isinstance(round_trip_crs.target_crs, BoundCRS)
+ assert not isinstance(original_crs.source_crs, BoundCRS)
+ assert not isinstance(original_crs.target_crs, BoundCRS)
assert round_trip_crs.source_crs.name == original_crs.source_crs.name
assert round_trip_crs.target_crs.name == original_crs.target_crs.name
assert round_trip_crs.transformation.name == original_crs.transformation.name
- def test_compound_crs_round_trip(self, compound_crs_json: dict[str, Any]) -> None:
+ def test_compound_crs_round_trip(self, compound_crs_json: dict[str, object]) -> None:
"""Test round-trip serialization of compound CRS example."""
from eopf_geozarr.data_api.geozarr.projjson import CompoundCRS
# Create model from JSON
- original_crs: CompoundCRS = CompoundCRS(**compound_crs_json)
+ original_crs: CompoundCRS = CompoundCRS.model_validate(compound_crs_json)
# Serialize back to dict
- serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True)
+ serialized: dict[str, object] = original_crs.model_dump(exclude_none=True)
# Create model from serialized data
- round_trip_crs: CompoundCRS = CompoundCRS(**serialized)
+ round_trip_crs: CompoundCRS = CompoundCRS.model_validate(serialized)
# Verify key properties are preserved
assert round_trip_crs.name == original_crs.name
assert round_trip_crs.type == original_crs.type
assert len(round_trip_crs.components) == len(original_crs.components)
for i, component in enumerate(round_trip_crs.components):
- assert component.name == original_crs.components[i].name
+ # Compound components are named CRSs, never a nested BoundCRS.
+ assert not isinstance(component, BoundCRS)
+ original_component = original_crs.components[i]
+ assert not isinstance(original_component, BoundCRS)
+ assert component.name == original_component.name
- def test_datum_ensemble_round_trip(self, datum_ensemble_json: dict[str, Any]) -> None:
+ def test_datum_ensemble_round_trip(self, datum_ensemble_json: dict[str, object]) -> None:
"""Test round-trip serialization of datum ensemble example."""
from eopf_geozarr.data_api.geozarr.projjson import GeodeticCRS
# Create model from JSON
- original_crs: GeodeticCRS = GeodeticCRS(**datum_ensemble_json)
+ original_crs: GeodeticCRS = GeodeticCRS.model_validate(datum_ensemble_json)
# Serialize back to dict
- serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True)
+ serialized: dict[str, object] = original_crs.model_dump(exclude_none=True)
# Create model from serialized data
- round_trip_crs: GeodeticCRS = GeodeticCRS(**serialized)
+ round_trip_crs: GeodeticCRS = GeodeticCRS.model_validate(serialized)
# Verify key properties are preserved
assert round_trip_crs.name == original_crs.name
assert round_trip_crs.type == original_crs.type
if original_crs.datum_ensemble:
+ assert round_trip_crs.datum_ensemble is not None
assert round_trip_crs.datum_ensemble.name == original_crs.datum_ensemble.name
assert len(round_trip_crs.datum_ensemble.members) == len(
original_crs.datum_ensemble.members
)
- def test_transformation_round_trip(self, transformation_json: dict[str, Any]) -> None:
+ def test_transformation_round_trip(self, transformation_json: dict[str, object]) -> None:
"""Test round-trip serialization of transformation example."""
from eopf_geozarr.data_api.geozarr.projjson import SingleOperation
# Create model from JSON
- original_op: SingleOperation = SingleOperation(**transformation_json)
+ original_op: SingleOperation = SingleOperation.model_validate(transformation_json)
# Serialize back to dict
- serialized: dict[str, Any] = original_op.model_dump(exclude_none=True)
+ serialized: dict[str, object] = original_op.model_dump(exclude_none=True)
# Create model from serialized data
- round_trip_op: SingleOperation = SingleOperation(**serialized)
+ round_trip_op: SingleOperation = SingleOperation.model_validate(serialized)
# Verify key properties are preserved
assert round_trip_op.name == original_op.name
assert round_trip_op.type == original_op.type
assert round_trip_op.method.name == original_op.method.name
if original_op.parameters:
+ assert round_trip_op.parameters is not None
assert len(round_trip_op.parameters) == len(original_op.parameters)
def test_all_examples_round_trip(self, projjson_example: dict[str, object]) -> None:
@@ -726,6 +749,7 @@ def test_all_examples_round_trip(self, projjson_example: dict[str, object]) -> N
# Get the model class based on type
obj_type = projjson_example.get("type")
+ assert isinstance(obj_type, str)
model_class = type_mapping[obj_type]
diff --git a/tests/test_data_api/test_s1.py b/tests/test_data_api/test_s1.py
index 98d9917e..5e7ce77d 100644
--- a/tests/test_data_api/test_s1.py
+++ b/tests/test_data_api/test_s1.py
@@ -15,7 +15,7 @@
def test_sentinel1_roundtrip(s1_json_example: dict[str, object]) -> None:
"""Test that we can round-trip JSON data without loss"""
- model1 = Sentinel1Root(**s1_json_example)
+ model1 = Sentinel1Root.model_validate(s1_json_example)
dumped = model1.model_dump()
- model2 = Sentinel1Root(**dumped)
+ model2 = Sentinel1Root.model_validate(dumped)
assert model1.model_dump() == model2.model_dump()
diff --git a/tests/test_data_api/test_s2.py b/tests/test_data_api/test_s2.py
index a17fa138..ad7c45c7 100644
--- a/tests/test_data_api/test_s2.py
+++ b/tests/test_data_api/test_s2.py
@@ -15,7 +15,7 @@
def test_sentinel2_roundtrip(s2_json_example: dict[str, object]) -> None:
"""Test that we can round-trip JSON data without loss"""
- model1 = Sentinel2Root(**s2_json_example)
+ model1 = Sentinel2Root.model_validate(s2_json_example)
dumped = model1.model_dump()
- model2 = Sentinel2Root(**dumped)
+ model2 = Sentinel2Root.model_validate(dumped)
assert model1.model_dump() == model2.model_dump()
diff --git a/tests/test_data_api/test_spatial.py b/tests/test_data_api/test_spatial.py
index aa757644..0ee7464f 100644
--- a/tests/test_data_api/test_spatial.py
+++ b/tests/test_data_api/test_spatial.py
@@ -13,7 +13,8 @@ class TestSpatial:
def test_minimal_required_fields(self) -> None:
"""Test creation with only required fields."""
- spatial = Spatial(**{"spatial:dimensions": ["y", "x"]})
+ data: dict[str, object] = {"spatial:dimensions": ["y", "x"]}
+ spatial = Spatial.model_validate(data)
assert spatial.dimensions == ["y", "x"]
assert spatial.bbox is None
@@ -25,13 +26,13 @@ def test_minimal_required_fields(self) -> None:
def test_missing_required_dimensions(self) -> None:
"""Test that missing dimensions field raises ValidationError."""
with pytest.raises(ValidationError) as exc_info:
- Spatial()
+ Spatial() # type: ignore[call-arg] # intentionally missing required field
assert "spatial:dimensions" in str(exc_info.value)
def test_full_spatial_metadata(self) -> None:
"""Test creation with all fields populated."""
- data = {
+ data: dict[str, object] = {
"spatial:dimensions": ["y", "x"],
"spatial:bbox": [500000.0, 4900000.0, 600000.0, 5000000.0],
"spatial:transform_type": "affine",
@@ -40,7 +41,7 @@ def test_full_spatial_metadata(self) -> None:
"spatial:registration": "pixel",
}
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
assert spatial.dimensions == ["y", "x"]
assert spatial.bbox == [500000.0, 4900000.0, 600000.0, 5000000.0]
@@ -51,13 +52,13 @@ def test_full_spatial_metadata(self) -> None:
def test_3d_spatial_data(self) -> None:
"""Test spatial model with 3D data."""
- data = {
+ data: dict[str, object] = {
"spatial:dimensions": ["z", "y", "x"],
"spatial:bbox": [500000.0, 4900000.0, 0.0, 600000.0, 5000000.0, 100.0],
"spatial:shape": [10, 1000, 1000],
}
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
assert spatial.dimensions == ["z", "y", "x"]
assert spatial.bbox == [500000.0, 4900000.0, 0.0, 600000.0, 5000000.0, 100.0]
@@ -65,14 +66,14 @@ def test_3d_spatial_data(self) -> None:
def test_serialization_by_alias(self) -> None:
"""Test that serialization uses aliases (spatial: prefixes)."""
- data = {
+ data: dict[str, object] = {
"spatial:dimensions": ["y", "x"],
"spatial:bbox": [0.0, 0.0, 100.0, 100.0],
"spatial:transform": [1.0, 0.0, 0.0, 0.0, -1.0, 100.0],
"spatial:shape": [100, 100],
}
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
result = spatial.model_dump()
# Should serialize with spatial: prefixes
@@ -91,7 +92,8 @@ def test_serialization_by_alias(self) -> None:
def test_none_fields_excluded(self) -> None:
"""Test that None fields are excluded from serialization."""
- spatial = Spatial(**{"spatial:dimensions": ["y", "x"]})
+ data: dict[str, object] = {"spatial:dimensions": ["y", "x"]}
+ spatial = Spatial.model_validate(data)
result = spatial.model_dump()
# None fields should be excluded
@@ -105,27 +107,30 @@ def test_none_fields_excluded(self) -> None:
def test_node_registration(self) -> None:
"""Test node registration type."""
- data = {"spatial:dimensions": ["y", "x"], "spatial:registration": "node"}
+ data: dict[str, object] = {"spatial:dimensions": ["y", "x"], "spatial:registration": "node"}
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
assert spatial.registration == "node"
def test_non_affine_transform_type(self) -> None:
"""Test non-affine transform types."""
- data = {"spatial:dimensions": ["y", "x"], "spatial:transform_type": "rpc"}
+ data: dict[str, object] = {
+ "spatial:dimensions": ["y", "x"],
+ "spatial:transform_type": "rpc",
+ }
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
assert spatial.transform_type == "rpc"
def test_extra_fields_allowed(self) -> None:
"""Test that extra fields are allowed."""
- data = {
+ data: dict[str, object] = {
"spatial:dimensions": ["y", "x"],
"custom_field": "custom_value",
"spatial:custom": "also_allowed",
}
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
result = spatial.model_dump()
assert result["custom_field"] == "custom_value"
@@ -133,7 +138,7 @@ def test_extra_fields_allowed(self) -> None:
def test_roundtrip_serialization(self) -> None:
"""Test that serialization and deserialization preserves data."""
- original_data = {
+ original_data: dict[str, object] = {
"spatial:dimensions": ["y", "x"],
"spatial:bbox": [500000.0, 4900000.0, 600000.0, 5000000.0],
"spatial:transform": [10.0, 0.0, 500000.0, 0.0, -10.0, 5000000.0],
@@ -143,7 +148,7 @@ def test_roundtrip_serialization(self) -> None:
}
# Create model, serialize, then recreate
- spatial1 = Spatial(**original_data)
+ spatial1 = Spatial.model_validate(original_data)
serialized = spatial1.model_dump()
spatial2 = Spatial(**serialized)
@@ -157,23 +162,26 @@ def test_roundtrip_serialization(self) -> None:
def test_invalid_dimensions_none(self) -> None:
"""Test that None dimensions raise ValidationError."""
+ data: dict[str, object] = {"spatial:dimensions": None} # intentionally invalid value
with pytest.raises(ValidationError):
- Spatial(**{"spatial:dimensions": None})
+ Spatial.model_validate(data)
def test_empty_dimensions_not_allowed(self) -> None:
"""Test that empty dimensions raise ValidationError."""
+ empty_data: dict[str, object] = {"spatial:dimensions": []}
with pytest.raises(ValidationError) as exc_info:
- Spatial(**{"spatial:dimensions": []})
+ Spatial.model_validate(empty_data)
assert "spatial:dimensions must contain at least one dimension" in str(exc_info.value)
- data = {
+ data: dict[str, object] = {
"spatial:dimensions": ["y", "x"],
"spatial:transform_type": "affine",
"spatial:transform": [10.0, 0.0, 500000.0, 0.0, -10.0], # Only 5 elements
}
# Currently this will pass, but in the future we might want validation
- spatial = Spatial(**data)
+ spatial = Spatial.model_validate(data)
+ assert spatial.transform is not None
assert len(spatial.transform) == 5 # Current behavior
# Future: might want to validate for exactly 6 elements for affine
diff --git a/tests/test_data_api/test_v2.py b/tests/test_data_api/test_v2.py
index a3386033..78bdc161 100644
--- a/tests/test_data_api/test_v2.py
+++ b/tests/test_data_api/test_v2.py
@@ -1,6 +1,7 @@
from __future__ import annotations
-from typing import Any
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, cast
import numpy as np
import pytest
@@ -12,6 +13,9 @@
check_valid_coordinates,
)
+if TYPE_CHECKING:
+ from eopf_geozarr.data_api.geozarr.common import GroupLike
+
def test_invalid_dimension_names() -> None:
msg = r"The _ARRAY_DIMENSIONS attribute has length 3, which does not match the number of dimensions for this array \(got 2\)"
@@ -35,10 +39,13 @@ def test_valid(data_shape: tuple[int, ...]) -> None:
f"dim_{idx}": DataArray.from_array(np.arange(s), dimension_names=(f"dim_{idx}",))
for idx, s in enumerate(data_shape)
}
- group = GroupSpec[Any, DataArray](
+ group = GroupSpec[Mapping[str, object], DataArray](
attributes={}, members={"base": base_array, **coords_arrays}
)
- assert check_valid_coordinates(group) == group
+ # ``group`` structurally satisfies ``GroupLike``, but mypy cannot bind the
+ # invariant ``Mapping`` value type, mirroring the cast used in the source.
+ group_like = cast("GroupLike", group)
+ assert check_valid_coordinates(group_like) == group_like
@staticmethod
@pytest.mark.parametrize("data_shape", [(10,), (10, 12)])
@@ -59,9 +66,9 @@ def test_invalid_coordinates(
f"dim_{idx}": DataArray.from_array(np.arange(s + 1), dimension_names=(f"dim_{idx}",))
for idx, s in enumerate(data_shape)
}
- group = GroupSpec[Any, DataArray](
+ group = GroupSpec[Mapping[str, object], DataArray](
attributes={}, members={"base": base_array, **coords_arrays}
)
msg = "Dimension .* for array 'base' has a shape mismatch:"
with pytest.raises(ValueError, match=msg):
- check_valid_coordinates(group)
+ check_valid_coordinates(cast("GroupLike", group))
diff --git a/tests/test_data_api/test_v3.py b/tests/test_data_api/test_v3.py
index 09771b01..c2a16fa4 100644
--- a/tests/test_data_api/test_v3.py
+++ b/tests/test_data_api/test_v3.py
@@ -1,4 +1,5 @@
-from typing import Any
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, cast
import numpy as np
import pytest
@@ -12,6 +13,9 @@
check_valid_coordinates,
)
+if TYPE_CHECKING:
+ from eopf_geozarr.data_api.geozarr.common import GroupLike
+
class TestCheckValidCoordinates:
@staticmethod
@@ -29,10 +33,13 @@ def test_valid(data_shape: tuple[int, ...]) -> None:
f"dim_{idx}": DataArray.from_array(np.arange(s), dimension_names=(f"dim_{idx}",))
for idx, s in enumerate(data_shape)
}
- group = GroupSpec[Any, DataArray](
+ group = GroupSpec[Mapping[str, object], DataArray](
attributes={}, members={"base": base_array, **coords_arrays}
)
- assert check_valid_coordinates(group) == group
+ # ``group`` structurally satisfies ``GroupLike``, but mypy cannot bind the
+ # invariant ``Mapping`` value type, mirroring the cast used in the source.
+ group_like = cast("GroupLike", group)
+ assert check_valid_coordinates(group_like) == group_like
@staticmethod
@pytest.mark.parametrize("data_shape", [(10,), (10, 12)])
@@ -53,19 +60,19 @@ def test_invalid_coordinates(
f"dim_{idx}": DataArray.from_array(np.arange(s + 1), dimension_names=(f"dim_{idx}",))
for idx, s in enumerate(data_shape)
}
- group = GroupSpec[Any, DataArray](
+ group = GroupSpec[Mapping[str, object], DataArray](
attributes={}, members={"base": base_array, **coords_arrays}
)
msg = "Dimension .* for array 'base' has a shape mismatch:"
with pytest.raises(ValueError, match=msg):
- check_valid_coordinates(group)
+ check_valid_coordinates(cast("GroupLike", group))
-def test_dataarray_round_trip(s2_geozarr_group_example: Any) -> None:
+def test_dataarray_round_trip(s2_geozarr_group_example: zarr.Group) -> None:
"""
Ensure that we can round-trip dataarray attributes through the `Multiscales` model.
"""
- source_untyped = GroupSpec.from_zarr(s2_geozarr_group_example)
+ source_untyped: GroupSpec = GroupSpec.from_zarr(s2_geozarr_group_example)
flat = source_untyped.to_flat()
for val in flat.values():
if isinstance(val, ArraySpec) and val.dimension_names is not None:
@@ -73,7 +80,7 @@ def test_dataarray_round_trip(s2_geozarr_group_example: Any) -> None:
assert DataArray(**model_json).model_dump() == model_json
-def test_multiscale_attrs_round_trip(s2_geozarr_group_example: Any) -> None:
+def test_multiscale_attrs_round_trip(s2_geozarr_group_example: zarr.Group) -> None:
"""
Test that multiscale datasets round-trip through the `Multiscales` model
"""
diff --git a/tests/test_docs.py b/tests/test_docs.py
index 82763904..b9422d38 100644
--- a/tests/test_docs.py
+++ b/tests/test_docs.py
@@ -10,6 +10,7 @@
#> hello
"""
+import uuid
from io import StringIO
from pathlib import Path
@@ -92,7 +93,7 @@ def _validate_output(example: CodeExample, captured_output: str) -> None:
# Get all examples and group them by group ID
_all_examples = list(find_examples("docs"))
-_examples_by_group = {}
+_examples_by_group: dict[uuid.UUID | None, list[CodeExample]] = {}
for ex in _all_examples:
if ex.prefix == "python":
if ex.group not in _examples_by_group:
@@ -120,7 +121,7 @@ def test_doc_example_group(group_examples: list[CodeExample]) -> None:
#> hello
"""
# Execute all examples in the group sequentially to share state
- namespace = {}
+ namespace: dict[str, object] = {}
any_skipped = False
for example in group_examples:
diff --git a/tests/test_fs_utils.py b/tests/test_fs_utils.py
index 872f93a9..fec0cee7 100644
--- a/tests/test_fs_utils.py
+++ b/tests/test_fs_utils.py
@@ -66,7 +66,7 @@ def test_get_s3_credentials_info() -> None:
@patch("eopf_geozarr.conversion.fs_utils.s3fs.S3FileSystem")
-def test_validate_s3_access_success(mock_s3fs) -> None:
+def test_validate_s3_access_success(mock_s3fs: Mock) -> None:
"""Test successful S3 access validation."""
mock_fs = Mock()
mock_fs.ls.return_value = ["file1", "file2"]
@@ -79,7 +79,7 @@ def test_validate_s3_access_success(mock_s3fs) -> None:
@patch("eopf_geozarr.conversion.fs_utils.s3fs.S3FileSystem")
-def test_validate_s3_access_failure(mock_s3fs) -> None:
+def test_validate_s3_access_failure(mock_s3fs: Mock) -> None:
"""Test failed S3 access validation."""
mock_fs = Mock()
mock_fs.ls.side_effect = Exception("Access denied")
@@ -87,6 +87,7 @@ def test_validate_s3_access_failure(mock_s3fs) -> None:
success, error = validate_s3_access("s3://test-bucket/path")
assert success is False
+ assert error is not None
assert "Access denied" in error
@@ -101,11 +102,13 @@ def test_get_s3_storage_options() -> None:
):
options = get_s3_storage_options("s3://test-bucket/path")
- assert options["anon"] is False
- assert options["use_ssl"] is True
- assert options["client_kwargs"]["region_name"] == "us-west-2"
- assert options["endpoint_url"] == "https://s3.example.com"
- assert options["client_kwargs"]["endpoint_url"] == "https://s3.example.com"
+ assert options.get("anon") is False
+ assert options.get("use_ssl") is True
+ client_kwargs = options.get("client_kwargs")
+ assert client_kwargs is not None
+ assert client_kwargs.get("region_name") == "us-west-2"
+ assert options.get("endpoint_url") == "https://s3.example.com"
+ assert client_kwargs.get("endpoint_url") == "https://s3.example.com"
def test_get_storage_options() -> None:
@@ -114,9 +117,11 @@ def test_get_storage_options() -> None:
with patch.dict("os.environ", {"AWS_DEFAULT_REGION": "us-west-2"}):
options = get_storage_options("s3://test-bucket/path")
assert options is not None
- assert options["anon"] is False
- assert options["use_ssl"] is True
- assert options["client_kwargs"]["region_name"] == "us-west-2"
+ assert options.get("anon") is False
+ assert options.get("use_ssl") is True
+ client_kwargs = options.get("client_kwargs")
+ assert client_kwargs is not None
+ assert client_kwargs.get("region_name") == "us-west-2"
# Test local path
options = get_storage_options("/local/path")
@@ -142,7 +147,7 @@ def test_normalize_path() -> None:
@patch("eopf_geozarr.conversion.fs_utils.get_filesystem")
-def test_path_exists(mock_get_filesystem) -> None:
+def test_path_exists(mock_get_filesystem: Mock) -> None:
"""Test unified path existence check."""
mock_fs = Mock()
mock_fs.exists.return_value = True
@@ -160,7 +165,7 @@ def test_path_exists(mock_get_filesystem) -> None:
@patch("eopf_geozarr.conversion.fs_utils.get_filesystem")
-def test_write_json_metadata(mock_get_filesystem) -> None:
+def test_write_json_metadata(mock_get_filesystem: Mock) -> None:
"""Test unified JSON metadata writing."""
from unittest.mock import MagicMock, mock_open
@@ -183,7 +188,7 @@ def test_write_json_metadata(mock_get_filesystem) -> None:
@patch("eopf_geozarr.conversion.fs_utils.get_filesystem")
-def test_read_json_metadata(mock_get_filesystem) -> None:
+def test_read_json_metadata(mock_get_filesystem: Mock) -> None:
"""Test unified JSON metadata reading."""
from unittest.mock import MagicMock, mock_open
diff --git a/tests/test_integration_sentinel1.py b/tests/test_integration_sentinel1.py
index 137cd53b..13f20925 100644
--- a/tests/test_integration_sentinel1.py
+++ b/tests/test_integration_sentinel1.py
@@ -21,7 +21,7 @@
class MockSentinel1L1GRDBuilder:
"""Builder class to generate a sample EOPF Sentinel-1 Level 1 GRD data product for testing purpose."""
- def __init__(self, product_id) -> None:
+ def __init__(self, product_id: str) -> None:
self.product_title = "S01SIWGRD"
self.product_id = product_id
@@ -32,7 +32,7 @@ def __init__(self, product_id) -> None:
self.nlines = 552
self.npixels = 1131
- def create_coordinates(self, az_dim_size, gr_dim_size) -> xr.Coordinates:
+ def create_coordinates(self, az_dim_size: int, gr_dim_size: int) -> xr.Coordinates:
coords = {
self.az_dim: pd.date_range(
start="2017-05-08T16:48:30",
@@ -182,7 +182,7 @@ def temp_output_dir() -> Generator[str, None, None]:
shutil.rmtree(temp_dir)
-def test_no_gcp_group(temp_output_dir, sample_sentinel1_datatree) -> None:
+def test_no_gcp_group(temp_output_dir: str, sample_sentinel1_datatree: xr.DataTree) -> None:
output_path = Path(temp_output_dir) / "temp.zarr"
with pytest.raises(ValueError, match=r"Detected Sentinel-1.*GCP group not provided"):
@@ -193,7 +193,9 @@ def test_no_gcp_group(temp_output_dir, sample_sentinel1_datatree) -> None:
)
-def test_invalid_gcp_group_raises_error(temp_output_dir, sample_sentinel1_datatree) -> None:
+def test_invalid_gcp_group_raises_error(
+ temp_output_dir: str, sample_sentinel1_datatree: xr.DataTree
+) -> None:
"""Test that specifying a non-existent GCP group raises an error."""
output_path = Path(temp_output_dir) / "test_s1_invalid_gcp.zarr"
groups = ["measurements"]
@@ -216,7 +218,7 @@ def test_invalid_gcp_group_raises_error(temp_output_dir, sample_sentinel1_datatr
],
)
def test_sentinel1_gcp_conversion(
- temp_output_dir, sample_sentinel1_datatree, polarization_group
+ temp_output_dir: str, sample_sentinel1_datatree: xr.DataTree, polarization_group: str
) -> None:
"""Test conversion of Sentinel-1 data with GCPs."""
# Prepare test
diff --git a/tests/test_integration_sentinel2.py b/tests/test_integration_sentinel2.py
index bae02360..91b6b7bc 100644
--- a/tests/test_integration_sentinel2.py
+++ b/tests/test_integration_sentinel2.py
@@ -230,7 +230,7 @@ def temp_output_dir() -> Generator[str, None, None]:
def test_complete_sentinel2_conversion_notebook_workflow(
- sample_sentinel2_datatree, temp_output_dir
+ sample_sentinel2_datatree: xr.DataTree, temp_output_dir: str
) -> None:
"""
Test complete conversion following the notebook workflow.
@@ -290,7 +290,9 @@ def test_complete_sentinel2_conversion_notebook_workflow(
@pytest.mark.slow
-def test_performance_characteristics(sample_sentinel2_datatree, temp_output_dir) -> None:
+def test_performance_characteristics(
+ sample_sentinel2_datatree: xr.DataTree, temp_output_dir: str
+) -> None:
"""
Test performance characteristics following notebook analysis.
diff --git a/tests/test_reprojection_validation.py b/tests/test_reprojection_validation.py
index aa5bbab1..abb944ce 100755
--- a/tests/test_reprojection_validation.py
+++ b/tests/test_reprojection_validation.py
@@ -16,7 +16,7 @@
class MockSentinel1L1GRDBuilder:
"""Builder class to generate a sample EOPF Sentinel-1 Level 1 GRD data product for testing purpose."""
- def __init__(self, product_id) -> None:
+ def __init__(self, product_id: str) -> None:
self.product_title = "S01SIWGRD"
self.product_id = product_id
self.az_dim = "azimuth_time"
@@ -25,7 +25,7 @@ def __init__(self, product_id) -> None:
self.nlines = 552
self.npixels = 1131
- def create_coordinates(self, az_dim_size, gr_dim_size) -> xr.Coordinates:
+ def create_coordinates(self, az_dim_size: int, gr_dim_size: int) -> xr.Coordinates:
coords = {
self.az_dim: pd.date_range(
start="2017-05-08T16:48:30",
diff --git a/tests/test_s2_converter_simplified.py b/tests/test_s2_converter_simplified.py
index 69cff728..0bbf746b 100644
--- a/tests/test_s2_converter_simplified.py
+++ b/tests/test_s2_converter_simplified.py
@@ -254,8 +254,10 @@ def test_write_store_root_bbox_reprojects_utm_to_wgs84(tmp_path: Path) -> None:
root_attrs = dict(zarr.open_group(store_path, mode="r").attrs)
bbox = root_attrs["spatial:bbox"]
+ assert isinstance(bbox, list)
+ assert all(isinstance(coord, float) for coord in bbox)
assert len(bbox) == 4
- xmin, ymin, xmax, ymax = bbox
+ xmin, ymin, xmax, ymax = (coord for coord in bbox if isinstance(coord, float))
# Roughly 7.0-8.0E, 44.1-45.1N after reprojection
assert 6.0 < xmin < 8.0, bbox
assert 43.0 < ymin < 45.0, bbox
diff --git a/tests/test_s2_data_consolidator.py b/tests/test_s2_data_consolidator.py
index 6102a25d..be82a85e 100644
--- a/tests/test_s2_data_consolidator.py
+++ b/tests/test_s2_data_consolidator.py
@@ -187,7 +187,7 @@ def sample_s2_datatree(self) -> MagicMock:
}
# Mock the dataset access
- def mock_getitem(self, path: str) -> MagicMock:
+ def mock_getitem(self: object, path: str) -> MagicMock:
mock_node = MagicMock()
if "r10m" in path:
if "reflectance" in path:
@@ -220,7 +220,7 @@ def mock_getitem(self, path: str) -> MagicMock:
mock_dt.__getitem__ = mock_getitem
return mock_dt
- def test_init(self, sample_s2_datatree) -> None:
+ def test_init(self, sample_s2_datatree: MagicMock) -> None:
"""Test consolidator initialization."""
consolidator = S2DataConsolidator(sample_s2_datatree)
@@ -229,7 +229,7 @@ def test_init(self, sample_s2_datatree) -> None:
assert consolidator.geometry_data == {}
assert consolidator.meteorology_data == {}
- def test_consolidate_all_data(self, sample_s2_datatree) -> None:
+ def test_consolidate_all_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test complete data consolidation."""
consolidator = S2DataConsolidator(sample_s2_datatree)
measurements, geometry, meteorology = consolidator.consolidate_all_data()
@@ -253,7 +253,7 @@ def test_consolidate_all_data(self, sample_s2_datatree) -> None:
assert "atmosphere" in measurements[resolution]
assert "probability" in measurements[resolution]
- def test_extract_reflectance_bands(self, sample_s2_datatree) -> None:
+ def test_extract_reflectance_bands(self, sample_s2_datatree: MagicMock) -> None:
"""Test reflectance band extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -274,7 +274,7 @@ def test_extract_reflectance_bands(self, sample_s2_datatree) -> None:
assert "b01" in consolidator.measurements_data[60]["bands"]
assert "b09" in consolidator.measurements_data[60]["bands"]
- def test_extract_quality_data(self, sample_s2_datatree) -> None:
+ def test_extract_quality_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test quality data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -283,7 +283,7 @@ def test_extract_quality_data(self, sample_s2_datatree) -> None:
assert "quality_b02" in consolidator.measurements_data[10]["quality"]
assert "quality_b03" in consolidator.measurements_data[10]["quality"]
- def test_extract_detector_footprints(self, sample_s2_datatree) -> None:
+ def test_extract_detector_footprints(self, sample_s2_datatree: MagicMock) -> None:
"""Test detector footprint extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -292,7 +292,7 @@ def test_extract_detector_footprints(self, sample_s2_datatree) -> None:
assert "detector_footprint_b02" in consolidator.measurements_data[10]["detector_footprints"]
assert "detector_footprint_b03" in consolidator.measurements_data[10]["detector_footprints"]
- def test_extract_atmosphere_data(self, sample_s2_datatree) -> None:
+ def test_extract_atmosphere_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test atmosphere data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -301,7 +301,7 @@ def test_extract_atmosphere_data(self, sample_s2_datatree) -> None:
assert "aot" in consolidator.measurements_data[20]["atmosphere"]
assert "wvp" in consolidator.measurements_data[20]["atmosphere"]
- def test_extract_classification_data(self, sample_s2_datatree) -> None:
+ def test_extract_classification_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test classification data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -309,7 +309,7 @@ def test_extract_classification_data(self, sample_s2_datatree) -> None:
# Classification should be at 20m resolution
assert "scl" in consolidator.measurements_data[20]["classification"]
- def test_extract_probability_data(self, sample_s2_datatree) -> None:
+ def test_extract_probability_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test probability data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_measurements_data()
@@ -318,7 +318,7 @@ def test_extract_probability_data(self, sample_s2_datatree) -> None:
assert "cld" in consolidator.measurements_data[20]["probability"]
assert "snw" in consolidator.measurements_data[20]["probability"]
- def test_extract_geometry_data(self, sample_s2_datatree) -> None:
+ def test_extract_geometry_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test geometry data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_geometry_data()
@@ -329,7 +329,7 @@ def test_extract_geometry_data(self, sample_s2_datatree) -> None:
assert "view_zenith_angle" in consolidator.geometry_data
assert "view_azimuth_angle" in consolidator.geometry_data
- def test_extract_meteorology_data(self, sample_s2_datatree) -> None:
+ def test_extract_meteorology_data(self, sample_s2_datatree: MagicMock) -> None:
"""Test meteorology data extraction."""
consolidator = S2DataConsolidator(sample_s2_datatree)
consolidator._extract_meteorology_data()
@@ -412,7 +412,9 @@ def sample_data_dict(self) -> dict[str, dict[str, xr.DataArray]]:
},
}
- def test_create_consolidated_dataset_success(self, sample_data_dict) -> None:
+ def test_create_consolidated_dataset_success(
+ self, sample_data_dict: dict[str, dict[str, xr.DataArray]]
+ ) -> None:
"""Test successful dataset creation."""
ds = create_consolidated_dataset(sample_data_dict, resolution=10)
@@ -434,14 +436,20 @@ def test_create_consolidated_dataset_success(self, sample_data_dict) -> None:
def test_create_consolidated_dataset_empty_data(self) -> None:
"""Test dataset creation with empty data."""
- empty_data_dict = {"bands": {}, "quality": {}, "atmosphere": {}}
+ empty_data_dict: dict[str, dict[str, xr.DataArray]] = {
+ "bands": {},
+ "quality": {},
+ "atmosphere": {},
+ }
ds = create_consolidated_dataset(empty_data_dict, resolution=20)
# Should return empty dataset
assert isinstance(ds, xr.Dataset)
assert len(ds.data_vars) == 0
- def test_create_consolidated_dataset_with_crs(self, sample_data_dict) -> None:
+ def test_create_consolidated_dataset_with_crs(
+ self, sample_data_dict: dict[str, dict[str, xr.DataArray]]
+ ) -> None:
"""Test dataset creation with CRS information."""
# Add CRS to one of the data arrays
sample_data_dict["bands"]["b02"] = sample_data_dict["bands"]["b02"].rio.write_crs(
@@ -513,7 +521,7 @@ def complete_s2_datatree(self) -> MagicMock:
coords={"time": time, "x": x_20m, "y": y_20m},
)
- def mock_getitem(self, path: str) -> MagicMock:
+ def mock_getitem(self: object, path: str) -> MagicMock:
mock_node = MagicMock()
if "/measurements/reflectance/r10m" in path:
mock_node.to_dataset.return_value = reflectance_10m
@@ -526,7 +534,7 @@ def mock_getitem(self, path: str) -> MagicMock:
mock_dt.__getitem__ = mock_getitem
return mock_dt
- def test_end_to_end_consolidation(self, complete_s2_datatree) -> None:
+ def test_end_to_end_consolidation(self, complete_s2_datatree: MagicMock) -> None:
"""Test complete end-to-end consolidation and dataset creation."""
# Step 1: Consolidate data
consolidator = S2DataConsolidator(complete_s2_datatree)
diff --git a/tests/test_s2_multiscale.py b/tests/test_s2_multiscale.py
index a0ad955f..29c57117 100644
--- a/tests/test_s2_multiscale.py
+++ b/tests/test_s2_multiscale.py
@@ -4,6 +4,7 @@
import json
import pathlib
+from collections.abc import Mapping, Sequence
from itertools import pairwise
from pathlib import Path
from unittest.mock import patch
@@ -17,6 +18,7 @@
from structlog.testing import capture_logs
from zarr.codecs import BloscCodec, CastValue, ScaleOffset
from zarr.core.dtype import Int16
+from zarr.core.metadata import ArrayV3Metadata
from eopf_geozarr.s2_optimization.s2_multiscale import (
_coarsen_variable,
@@ -70,7 +72,9 @@ def _dataset(resolution: int, size: int, x0: float, y0: float) -> xr.Dataset:
{"band": (["y", "x"], np.ones((size, size), dtype=np.uint16))},
coords={"x": x, "y": y},
)
- return ds.rio.write_crs("EPSG:32631")
+ crs_ds = ds.rio.write_crs("EPSG:32631")
+ assert isinstance(crs_ds, xr.Dataset)
+ return crs_ds
r10m = _dataset(10, 12, 600000.0, 4900020.0)
r120m = _dataset(120, 3, 600030.0, 4899990.0)
@@ -86,9 +90,17 @@ def stale_transform() -> tuple[float, float, float, float, float, float]:
{"r10m": r10m, "r120m": r120m},
)
- layout = parent_group.attrs["multiscales"]["layout"]
- derived_level = next(level for level in layout if level["asset"] == "r120m")
- assert tuple(derived_level["spatial:transform"]) == (
+ multiscales = parent_group.attrs["multiscales"]
+ assert isinstance(multiscales, Mapping)
+ layout = multiscales["layout"]
+ assert isinstance(layout, Sequence)
+ derived_level = next(
+ level for level in layout if isinstance(level, Mapping) and level["asset"] == "r120m"
+ )
+ assert isinstance(derived_level, Mapping)
+ transform = derived_level["spatial:transform"]
+ assert isinstance(transform, Sequence)
+ assert tuple(transform) == (
120.0,
0.0,
600030.0,
@@ -101,8 +113,8 @@ def stale_transform() -> tuple[float, float, float, float, float, float]:
def test_calculate_simple_shard_dimensions() -> None:
"""Test simplified shard dimensions calculation."""
# Test 3D data (time, y, x) - shards are multiples of chunks
- data_shape = (5, 1024, 1024)
- chunks = (1, 256, 256)
+ data_shape: tuple[int, ...] = (5, 1024, 1024)
+ chunks: tuple[int, ...] = (1, 256, 256)
shard_dims = calculate_simple_shard_dimensions(data_shape, chunks)
@@ -181,8 +193,8 @@ def test_create_measurements_encoding(keep_scale_offset: bool, sample_dataset: x
# Check that encoding is created for all variables
for var_name in sample_dataset.data_vars:
- assert var_name in encoding
- var_encoding = encoding[var_name]
+ assert str(var_name) in encoding
+ var_encoding = encoding[str(var_name)]
# Check basic encoding structure
assert "chunks" in var_encoding
@@ -194,11 +206,11 @@ def test_create_measurements_encoding(keep_scale_offset: bool, sample_dataset: x
# Check coordinate encoding
for coord_name in sample_dataset.coords:
- if coord_name in encoding:
+ if str(coord_name) in encoding:
# Coordinates may have either compressor or compressors set to None
assert (
- encoding[coord_name].get("compressor") is None
- or encoding[coord_name].get("compressors") is None
+ encoding[str(coord_name)].get("compressor") is None
+ or encoding[str(coord_name)].get("compressors") is None
)
# Store data and check that we are conditionally applying the scale-offset transformation
# based on the request passed to the encoding
@@ -220,7 +232,8 @@ def test_create_measurements_encoding_time_chunking(sample_dataset: xr.Dataset)
for var_name in sample_dataset.data_vars:
if sample_dataset[var_name].ndim == 3: # 3D variable with time
- chunks = encoding[var_name]["chunks"]
+ chunks = encoding[str(var_name)].get("chunks")
+ assert chunks is not None
assert chunks[0] == 1 # Time dimension should be chunked to 1
@@ -240,7 +253,7 @@ def test_calculate_aligned_chunk_size() -> None:
@pytest.mark.filterwarnings("ignore:.*:FutureWarning")
@pytest.mark.filterwarnings("ignore:.*:UserWarning")
def test_create_multiscale_from_datatree(
- s2_group_example: zarr.Group,
+ s2_group_example: pathlib.Path,
tmp_path: pathlib.Path,
) -> None:
"""Snapshot test: a single canonical parametrization (keep_scale_offset=False,
@@ -253,7 +266,13 @@ def test_create_multiscale_from_datatree(
output_path = str(tmp_path / "output.zarr")
input_group = zarr.open_group(s2_group_example)
output_group = zarr.create_group(output_path)
- dt_input = xr.open_datatree(input_group.store, engine="zarr", chunks="auto")
+ # xarray's open_datatree accepts a zarr store at runtime, but its stub does
+ # not list Store among the accepted input types.
+ dt_input = xr.open_datatree(
+ input_group.store, # pyright: ignore[reportArgumentType]
+ engine="zarr",
+ chunks="auto",
+ )
# Capture log output using structlog's testing context manager
with capture_logs():
@@ -291,7 +310,9 @@ def test_create_multiscale_from_datatree(
# check that all multiscale levels have the same data type
# this check is redundant with the later check, but it's expedient to check this here.
# eventually this check should be spun out into its own test
- _, res_groups = zip(*observed_group["measurements/reflectance"].groups(), strict=False)
+ reflectance_group = observed_group["measurements/reflectance"]
+ assert isinstance(reflectance_group, zarr.Group)
+ _, res_groups = zip(*reflectance_group.groups(), strict=False)
dtype_mismatch: set[object] = set()
for group_a, group_b in pairwise(res_groups):
@@ -421,6 +442,7 @@ def test_create_multiscale_from_datatree_behavior(
# ------------------------------------------------------------------
for group_path, var_name in _ORIGINAL_GROUPS.items():
arr = zarr.open_array(output_path, path=f"{group_path}/{var_name}")
+ assert isinstance(arr.metadata, ArrayV3Metadata)
codec_names = [type(c).__name__ for c in arr.metadata.codecs]
if keep_scale_offset:
@@ -461,6 +483,7 @@ def test_create_multiscale_from_datatree_behavior(
assert ds.data_vars, f"{group_path} has no variables"
for name in ds.data_vars:
arr = zarr.open_array(output_path, path=f"{group_path}/{name}")
+ assert isinstance(arr.metadata, ArrayV3Metadata)
codec_names = [type(c).__name__ for c in arr.metadata.codecs]
if keep_scale_offset:
diff --git a/tests/test_scale_offset.py b/tests/test_scale_offset.py
index 18826e0f..913c1c84 100644
--- a/tests/test_scale_offset.py
+++ b/tests/test_scale_offset.py
@@ -37,4 +37,4 @@ def test_scale_offset_from_cf_round_trip() -> None:
)
arr[:] = unpacked_values
- np.testing.assert_array_almost_equal(arr[:], unpacked_values)
+ np.testing.assert_array_almost_equal(np.asarray(arr[:]), unpacked_values)
diff --git a/tests/test_titiler_integration.py b/tests/test_titiler_integration.py
index 3d761c76..925e019e 100644
--- a/tests/test_titiler_integration.py
+++ b/tests/test_titiler_integration.py
@@ -9,6 +9,10 @@
- titiler-xarray, httpx installed
"""
+# titiler/fastapi/starlette are optional (the `downstream-titiler` group); this
+# module is skipped at runtime when they're absent, so don't flag their imports.
+# pyright: reportMissingImports=false
+
from __future__ import annotations
import pathlib
@@ -89,7 +93,7 @@ def _open_group(path: pathlib.Path, group: str) -> xr.Dataset:
def _band_vars(ds: xr.Dataset) -> list[str]:
"""Get band variable names, excluding spatial_ref."""
- return [v for v in ds.data_vars if v != "spatial_ref"]
+ return [str(v) for v in ds.data_vars if v != "spatial_ref"]
class TestTitilerInfo:
diff --git a/uv.lock b/uv.lock
index 6f1926ed..88d9e183 100644
--- a/uv.lock
+++ b/uv.lock
@@ -214,46 +214,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
]
-[[package]]
-name = "ast-serialize"
-version = "0.5.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" },
- { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" },
- { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" },
- { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" },
- { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" },
- { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" },
- { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" },
- { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" },
- { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" },
- { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" },
- { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" },
- { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" },
- { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" },
- { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" },
- { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" },
- { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" },
- { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" },
- { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" },
- { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" },
- { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" },
- { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" },
- { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" },
- { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" },
- { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" },
- { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" },
- { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" },
- { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" },
- { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" },
- { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" },
- { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" },
- { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" },
- { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" },
-]
-
[[package]]
name = "attrs"
version = "26.1.0"
@@ -831,7 +791,7 @@ wheels = [
[[package]]
name = "eopf-geozarr"
-version = "0.10.0"
+version = "0.10.1"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -854,8 +814,8 @@ dependencies = [
[package.dev-dependencies]
dev = [
{ name = "bandit" },
- { name = "mypy" },
{ name = "pre-commit" },
+ { name = "pyright" },
{ name = "pytest" },
{ name = "pytest-cov" },
]
@@ -894,14 +854,14 @@ requires-dist = [
{ name = "typing-extensions", specifier = ">=4.15.0" },
{ name = "xarray", specifier = ">=2025.7.1" },
{ name = "zarr", extras = ["cast-value-rs"], specifier = ">=3.2.0" },
- { name = "zarr-cm", specifier = ">=0.2.0" },
+ { name = "zarr-cm", git = "https://github.com/zarr-conventions/zarr-cm.git?rev=main" },
]
[package.metadata.requires-dev]
dev = [
{ name = "bandit", extras = ["toml"], specifier = ">=1.7.0" },
- { name = "mypy", specifier = ">=1.0.0" },
{ name = "pre-commit", specifier = ">=3.0.0" },
+ { name = "pyright", specifier = ">=1.1.390" },
{ name = "pytest", specifier = ">=7.0.0" },
{ name = "pytest-cov", specifier = ">=4.0.0" },
]
@@ -1201,66 +1161,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/94/a8066f84d62ab666d61ef97deba1a33126e3e5c0c0da2c458ada17053ed6/jsondiff-2.2.1-py3-none-any.whl", hash = "sha256:b1f0f7e2421881848b1d556d541ac01a91680cfcc14f51a9b62cdf4da0e56722", size = 13440, upload-time = "2024-08-29T04:09:04.955Z" },
]
-[[package]]
-name = "librt"
-version = "0.11.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" },
- { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" },
- { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" },
- { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" },
- { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" },
- { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" },
- { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" },
- { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" },
- { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" },
- { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" },
- { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" },
- { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" },
- { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" },
- { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" },
- { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" },
- { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" },
- { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" },
- { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" },
- { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" },
- { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" },
- { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" },
- { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" },
- { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" },
- { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" },
- { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" },
- { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" },
- { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" },
- { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" },
- { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" },
- { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" },
- { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" },
- { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" },
- { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" },
- { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" },
- { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" },
- { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" },
- { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" },
- { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" },
- { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" },
- { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" },
- { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" },
- { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" },
- { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" },
- { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" },
- { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" },
- { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" },
- { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" },
- { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" },
- { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" },
- { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" },
- { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" },
- { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" },
-]
-
[[package]]
name = "locket"
version = "1.0.0"
@@ -1616,50 +1516,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
]
-[[package]]
-name = "mypy"
-version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "ast-serialize" },
- { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
- { name = "mypy-extensions" },
- { name = "pathspec" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" },
- { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" },
- { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" },
- { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" },
- { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" },
- { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" },
- { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" },
- { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" },
- { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" },
- { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" },
- { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" },
- { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" },
- { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" },
- { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" },
- { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" },
- { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" },
- { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" },
- { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" },
- { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" },
- { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" },
- { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" },
- { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" },
- { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" },
- { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" },
- { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" },
- { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" },
- { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" },
- { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" },
- { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" },
-]
-
[[package]]
name = "mypy-extensions"
version = "1.1.0"
@@ -2310,6 +2166,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/15/73/a7141a1a0559bf1a7aa42a11c879ceb19f02f5c6c371c6d57fd86cefd4d1/pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4", size = 6391844, upload-time = "2025-08-14T12:05:40.745Z" },
]
+[[package]]
+name = "pyright"
+version = "1.1.410"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodeenv" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" },
+]
+
[[package]]
name = "pystac"
version = "1.14.3"
@@ -3157,11 +3026,10 @@ cast-value-rs = [
[[package]]
name = "zarr-cm"
-version = "0.2.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d8/40/99c941d23a7cd84fb194b3eaa2ca2d0d85f5135aa07e2c4f2c12febfef43/zarr_cm-0.2.0.tar.gz", hash = "sha256:737f5b181e8a2456643c423f49b59d879531498c13c7345860f0e718ef73d8a8", size = 24773, upload-time = "2026-02-06T14:47:57.588Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/54/60/06c371c10a321db63ec46634c9498a3c491754214c0e3bfc27410c528d99/zarr_cm-0.2.0-py3-none-any.whl", hash = "sha256:7f4c778d70d37ccb6e10e1509f4c0a488689027dcebcd6b64fa89a899f101e11", size = 14222, upload-time = "2026-02-06T14:47:56.465Z" },
+version = "0.4.1.dev13+ge94a3ee8d"
+source = { git = "https://github.com/zarr-conventions/zarr-cm.git?rev=main#e94a3ee8db52689883753f5b928f8331c3d01b15" }
+dependencies = [
+ { name = "typing-extensions" },
]
[[package]]