From a0651ef1a234c4f88847ed44a61871c95794b798 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 May 2025 06:13:55 -0600 Subject: [PATCH 1/4] feat: permissive extent deserialization --- pystac/collection.py | 18 ++++++++++++++---- tests/test_collection.py | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) 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..403ea958c 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,19 @@ 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): + Collection.from_dict(d) + + +def test_from_dict_missing_extent(collection: Collection) -> None: + d = collection.to_dict() + del d["extent"] + with pytest.warns(UserWarning): + Collection.from_dict(d) From c78b871801127a243baa2bc74287d9c6ed3db92b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 May 2025 06:15:35 -0600 Subject: [PATCH 2/4] chore: update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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 From 7b5d9d224c51df21c5ef67c38f3ae8dce234d65d Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 May 2025 07:48:40 -0600 Subject: [PATCH 3/4] Update tests/test_collection.py Co-authored-by: Julia Signell --- tests/test_collection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_collection.py b/tests/test_collection.py index 403ea958c..9292d6ab3 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -828,7 +828,9 @@ def test_from_dict_null_extent(collection: Collection) -> None: d = collection.to_dict() d["extent"] = None with pytest.warns(UserWarning): - Collection.from_dict(d) + c = Collection.from_dict(d) + assert c.extent.spatial.to_dict()["bbox"] == [-90, -180, 90, 180] + assert c.extent.temporal.to_dict()["intervals"] == [None, None] def test_from_dict_missing_extent(collection: Collection) -> None: From 0d03ae693456cbfbe872507109ce09c311c7aa31 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 14 May 2025 07:53:27 -0600 Subject: [PATCH 4/4] fix: tests --- tests/test_collection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_collection.py b/tests/test_collection.py index 9292d6ab3..d44f82f57 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -829,12 +829,16 @@ def test_from_dict_null_extent(collection: Collection) -> None: 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()["intervals"] == [None, None] + + 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): - Collection.from_dict(d) + 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]]