diff --git a/CHANGELOG.md b/CHANGELOG.md index 07de048e6..816a64128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ media type value for these types and new media types COPC and VND_PMTILES ([#1554](https://github.com/stac-utils/pystac/pull/1554)) +### Fixed + +- More permissive collection extent deserialization ([#1559](https://github.com/stac-utils/pystac/pull/1559)) + ## [v1.13.0] - 2025-04-15 ### Added diff --git a/pystac/collection.py b/pystac/collection.py index 200b2f10d..be8693d6c 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,7 +1,7 @@ from __future__ import annotations import warnings -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from copy import deepcopy from datetime import datetime, timezone from typing import ( @@ -71,7 +71,7 @@ class SpatialExtent: def __init__( self, - bboxes: Bboxes | list[float | int], + bboxes: Bboxes | Sequence[float | int], extra_fields: dict[str, Any] | None = None, ) -> None: if not isinstance(bboxes, list): @@ -199,7 +199,7 @@ class TemporalExtent: def __init__( self, - intervals: TemporalIntervals | list[datetime | None], + intervals: TemporalIntervals | Sequence[datetime | None], extra_fields: dict[str, Any] | None = None, ): if not isinstance(intervals, list): @@ -652,7 +652,17 @@ def from_dict( id = d.pop("id") description = d.pop("description") license = d.pop("license") - extent = Extent.from_dict(d.pop("extent")) + if extent_dict := d.pop("extent", None): + extent = Extent.from_dict(extent_dict) + else: + warnings.warn( + "Collection is missing extent, setting default spatial and " + "temporal extents" + ) + extent = Extent( + spatial=SpatialExtent([-90, -180, 90, 180]), + temporal=TemporalExtent([None, None]), + ) title = d.pop("title", None) stac_extensions = d.pop("stac_extensions", None) keywords = d.pop("keywords", None) diff --git a/tests/test_collection.py b/tests/test_collection.py index 591dd42c6..d44f82f57 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -400,7 +400,7 @@ def test_temporal_extent_allows_single_interval() -> None: end_datetime = str_to_datetime("2022-01-31T23:59:59Z") interval = [start_datetime, end_datetime] - temporal_extent = TemporalExtent(intervals=interval) # type: ignore + temporal_extent = TemporalExtent(intervals=interval) assert temporal_extent.intervals == [interval] @@ -820,3 +820,25 @@ def test_from_items_with_providers(sample_item_collection: ItemCollection) -> No provider = collection.providers[0] assert provider and provider.name == "pystac" + + +def test_from_dict_null_extent(collection: Collection) -> None: + # https://github.com/stac-utils/pystac/issues/1558 + # https://github.com/EOPF-Sample-Service/eopf-stac/issues/18 + d = collection.to_dict() + d["extent"] = None + with pytest.warns(UserWarning): + c = Collection.from_dict(d) + + assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.temporal.to_dict()["interval"] == [[None, None]] + + +def test_from_dict_missing_extent(collection: Collection) -> None: + d = collection.to_dict() + del d["extent"] + with pytest.warns(UserWarning): + c = Collection.from_dict(d) + + assert c.extent.spatial.to_dict()["bbox"] == [[-90, -180, 90, 180]] + assert c.extent.temporal.to_dict()["interval"] == [[None, None]]