Skip to content

Commit dda56b6

Browse files
committed
Issue #699/#780 skip "no band source" warnings when walking STAC constructs
no need to trigger low level warnings when traversing and merging larger STAC hierarchies
1 parent 506d799 commit dda56b6

File tree

2 files changed

+77
-16
lines changed

2 files changed

+77
-16
lines changed

openeo/metadata.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,10 @@ def merge(cls, band_lists: Iterable[_BandList]) -> _BandList:
701701
return cls(unique(all_bands, key=lambda b: b.name))
702702

703703

704+
_ON_EMPTY_WARN = "warn"
705+
_ON_EMPTY_IGNORE = "ignore"
706+
707+
704708
class _StacMetadataParser:
705709
"""
706710
Helper to extract openEO metadata from STAC metadata resource
@@ -791,7 +795,7 @@ def bands_from_stac_object(self, obj: Union[pystac.STACObject, pystac.Asset]) ->
791795
else:
792796
raise ValueError(f"Unsupported STAC object: {obj!r}")
793797

794-
def bands_from_stac_catalog(self, catalog: pystac.Catalog) -> _BandList:
798+
def bands_from_stac_catalog(self, catalog: pystac.Catalog, *, on_empty: str = _ON_EMPTY_WARN) -> _BandList:
795799
# TODO: "eo:bands" vs "bands" priority based on STAC and EO extension version information
796800
summaries = catalog.extra_fields.get("summaries", {})
797801
if "eo:bands" in summaries:
@@ -801,12 +805,17 @@ def bands_from_stac_catalog(self, catalog: pystac.Catalog) -> _BandList:
801805
elif "bands" in summaries:
802806
return _BandList(self._band_from_common_bands_metadata(b) for b in summaries["bands"])
803807

804-
# TODO: instead of warning: exception, or return None?
805-
self._warn("bands_from_stac_catalog: no band name source found")
808+
if on_empty == _ON_EMPTY_WARN:
809+
self._warn("bands_from_stac_catalog: no band name source found")
806810
return _BandList([])
807811

808812
def bands_from_stac_collection(
809-
self, collection: pystac.Collection, *, consult_items: bool = True, consult_assets: bool = True
813+
self,
814+
collection: pystac.Collection,
815+
*,
816+
consult_items: bool = True,
817+
consult_assets: bool = True,
818+
on_empty: str = _ON_EMPTY_WARN,
810819
) -> _BandList:
811820
# TODO: "eo:bands" vs "bands" priority based on STAC and EO extension version information
812821
self._log(f"bands_from_stac_collection with {collection.summaries.lists.keys()=}")
@@ -836,18 +845,25 @@ def bands_from_stac_collection(
836845
# If no band metadata so far: traverse items in collection
837846
elif consult_items:
838847
bands = _BandList.merge(
839-
self.bands_from_stac_item(item=i, consult_collection=False, consult_assets=consult_assets)
848+
self.bands_from_stac_item(
849+
item=i, consult_collection=False, consult_assets=consult_assets, on_empty=_ON_EMPTY_IGNORE
850+
)
840851
for i in collection.get_items()
841852
)
842853
if bands:
843854
return bands
844855

845-
# TODO: instead of warning: exception, or return None?
846-
self._warn("bands_from_stac_collection: no band name source found")
856+
if on_empty == _ON_EMPTY_WARN:
857+
self._warn("bands_from_stac_collection: no band name source found")
847858
return _BandList([])
848859

849860
def bands_from_stac_item(
850-
self, item: pystac.Item, *, consult_collection: bool = True, consult_assets: bool = True
861+
self,
862+
item: pystac.Item,
863+
*,
864+
consult_collection: bool = True,
865+
consult_assets: bool = True,
866+
on_empty: str = _ON_EMPTY_WARN,
851867
) -> _BandList:
852868
# TODO: "eo:bands" vs "bands" priority based on STAC and EO extension version information
853869
self._log(f"bands_from_stac_item with {item.properties.keys()=}")
@@ -859,19 +875,21 @@ def bands_from_stac_item(
859875
return self.bands_from_stac_collection(collection=parent_collection)
860876
elif consult_assets:
861877
# TODO: filter on asset roles?
862-
bands = _BandList.merge(self.bands_from_stac_asset(asset=a) for a in item.get_assets().values())
878+
bands = _BandList.merge(
879+
self.bands_from_stac_asset(asset=a, on_empty=_ON_EMPTY_IGNORE) for a in item.get_assets().values()
880+
)
863881
if bands:
864882
return bands
865883

866-
# TODO: instead of warning: exception, or return None?
867-
self._warn("bands_from_stac_item: no band name source found")
884+
if on_empty == _ON_EMPTY_WARN:
885+
self._warn("bands_from_stac_item: no band name source found")
868886
return _BandList([])
869887

870888
def _warn_undeclared_metadata(self, *, field: str, ext: str):
871889
"""Helper to warn about using metadata from undeclared STAC extension"""
872890
self._warn(f"Using {field!r} metadata, but STAC extension {ext} was not declared.")
873891

874-
def bands_from_stac_asset(self, asset: pystac.Asset) -> _BandList:
892+
def bands_from_stac_asset(self, asset: pystac.Asset, *, on_empty: str = _ON_EMPTY_WARN) -> _BandList:
875893
# TODO: "eo:bands" vs "bands" priority based on STAC and EO extension version information
876894
if _PYSTAC_1_9_EXTENSION_INTERFACE and asset.owner and asset.ext.has("eo") and asset.ext.eo.bands is not None:
877895
return _BandList(self._band_from_eo_bands_metadata(b) for b in asset.ext.eo.bands)
@@ -883,8 +901,8 @@ def bands_from_stac_asset(self, asset: pystac.Asset) -> _BandList:
883901
# TODO: avoid `extra_fields`, but built-in "bands" support seems to be scheduled for pystac V2
884902
return _BandList(self._band_from_common_bands_metadata(b) for b in asset.extra_fields["bands"])
885903

886-
# TODO: instead of warning: exception, or return None?
887-
self._warn("bands_from_stac_asset: no band name source found")
904+
if on_empty == _ON_EMPTY_WARN:
905+
self._warn("bands_from_stac_asset: no band name source found")
888906
return _BandList([])
889907

890908
def _bands_from_item_asset_definition(

tests/test_metadata.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,9 +1354,10 @@ def test_bands_from_stac_collection(self, data, expected, caplog, expected_warni
13541354
assert caplog.messages == expected_warnings
13551355

13561356
@pytest.mark.parametrize(
1357-
["entities", "kwargs", "expected"],
1357+
["entities", "kwargs", "expected", "expected_warnings"],
13581358
[
13591359
(
1360+
# Basic: Collection with one item with eo:bands
13601361
{
13611362
"collection.json": StacDummyBuilder.collection(
13621363
stac_version="1.0.0",
@@ -1374,8 +1375,10 @@ def test_bands_from_stac_collection(self, data, expected, caplog, expected_warni
13741375
},
13751376
{},
13761377
[Band("B02"), Band("B03")],
1378+
[],
13771379
),
13781380
(
1381+
# Collection with two items with different bands (eo:bands metadata)
13791382
{
13801383
"collection.json": StacDummyBuilder.collection(
13811384
stac_version="1.0.0",
@@ -1401,8 +1404,10 @@ def test_bands_from_stac_collection(self, data, expected, caplog, expected_warni
14011404
},
14021405
{},
14031406
[Band("B02"), Band("B03")],
1407+
[],
14041408
),
14051409
(
1410+
# Collection with two items with different bands in different metadata format
14061411
{
14071412
"collection.json": StacDummyBuilder.collection(
14081413
stac_version="1.0.0",
@@ -1427,8 +1432,10 @@ def test_bands_from_stac_collection(self, data, expected, caplog, expected_warni
14271432
},
14281433
{},
14291434
[Band("B02"), Band("B03")],
1435+
[],
14301436
),
14311437
(
1438+
# Collection with one item, with band metadata in asset
14321439
{
14331440
"collection.json": StacDummyBuilder.collection(
14341441
stac_version="1.0.0",
@@ -1450,15 +1457,51 @@ def test_bands_from_stac_collection(self, data, expected, caplog, expected_warni
14501457
},
14511458
{},
14521459
[Band("B02")],
1460+
[],
1461+
),
1462+
(
1463+
# Collection with multiple items and assets, only partially with band metadata
1464+
{
1465+
"collection.json": StacDummyBuilder.collection(
1466+
stac_version="1.0.0",
1467+
links=[
1468+
{"rel": "item", "href": "item1.json"},
1469+
{"rel": "item", "href": "item2.json"},
1470+
],
1471+
),
1472+
"item1.json": StacDummyBuilder.item(
1473+
stac_version="1.0.0",
1474+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1475+
assets={
1476+
"asset11": {
1477+
"href": "https://stac.test/asset11.tif",
1478+
"roles": ["data"],
1479+
"eo:bands": [{"name": "B02"}],
1480+
},
1481+
"asset12": {
1482+
"href": "https://stac.test/asset12.tif",
1483+
"roles": ["data"],
1484+
},
1485+
},
1486+
),
1487+
"item2.json": StacDummyBuilder.item(),
1488+
},
1489+
{},
1490+
[Band("B02")],
1491+
[],
14531492
),
14541493
],
14551494
)
1456-
def test_bands_from_stac_collection_consult_items(self, tmp_path, entities, kwargs, expected):
1495+
def test_bands_from_stac_collection_consult_items(
1496+
self, tmp_path, entities, kwargs, expected, caplog, expected_warnings
1497+
):
14571498
for name, content in entities.items():
14581499
(tmp_path / name).write_text(json.dumps(content))
14591500
collection = pystac.Collection.from_file(tmp_path / "collection.json")
14601501
assert _StacMetadataParser().bands_from_stac_collection(collection=collection) == expected
14611502

1503+
assert caplog.messages == expected_warnings
1504+
14621505
@pytest.mark.parametrize(
14631506
["data", "expected"],
14641507
[

0 commit comments

Comments
 (0)