From 99ea6aae2b65a0c9c197c076e9a8bd3df72621ec Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Sun, 8 Oct 2023 15:37:30 +0200 Subject: [PATCH 1/2] Assets keep their own `stac_extensions` so that they can be extended without being added to an Item. Deprecating validate_owner_has_extension Switching from validate_owner_has_extension to ensure_has_extension in extensions dealing with assets Extracting a final list of assets in the `to_dict` function for Items and Collections. --- pystac/asset.py | 6 ++++ pystac/collection.py | 16 ++++++++- pystac/extensions/base.py | 50 +++++++++++++++++++++-------- pystac/extensions/classification.py | 2 +- pystac/extensions/datacube.py | 2 +- pystac/extensions/eo.py | 2 +- pystac/extensions/file.py | 2 +- pystac/extensions/pointcloud.py | 2 +- pystac/extensions/projection.py | 2 +- pystac/extensions/raster.py | 2 +- pystac/extensions/sar.py | 2 +- pystac/extensions/sat.py | 2 +- pystac/extensions/storage.py | 2 +- pystac/extensions/table.py | 2 +- pystac/extensions/timestamps.py | 2 +- pystac/extensions/view.py | 2 +- pystac/extensions/xarray_assets.py | 2 +- pystac/item.py | 17 +++++++++- tests/extensions/test_custom.py | 10 +++++- 19 files changed, 96 insertions(+), 31 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index cbcf2c039..3e40f8903 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -35,6 +35,8 @@ class Asset: extra_fields : Optional, additional fields for this asset. This is used by extensions as a way to serialize and deserialize properties on asset object JSON. + stac_extensions : Optional, a list of schema URIs for STAC Extensions + implemented by this STAC Asset. """ href: str @@ -64,6 +66,9 @@ class Asset: """Optional, additional fields for this asset. This is used by extensions as a way to serialize and deserialize properties on asset object JSON.""" + stac_extensions: List[str] + """A list of schema URIs for STAC Extensions implemented by this STAC Asset.""" + def __init__( self, href: str, @@ -79,6 +84,7 @@ def __init__( self.media_type = media_type self.roles = roles self.extra_fields = extra_fields or {} + self.stac_extensions = None # The Item which owns this Asset. self.owner = None diff --git a/pystac/collection.py b/pystac/collection.py index 766bd27a8..4c9bbff14 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -577,8 +577,22 @@ def to_dict( ) d["extent"] = self.extent.to_dict() d["license"] = self.license + + # we use the fact that in recent Python versions, dict keys are ordered + # by default + stac_extensions: Optional[Dict[str, None]] = None if self.stac_extensions: - d["stac_extensions"] = self.stac_extensions + stac_extensions = dict.fromkeys(self.stac_extensions) + + for asset in self.assets.values(): + if stac_extensions and asset.stac_extensions: + stac_extensions.update(dict.fromkeys(asset.stac_extensions)) + elif asset.stac_extensions: + stac_extensions = dict.fromkeys(asset.stac_extensions) + + if stac_extensions is not None: + d["stac_extensions"] = list(stac_extensions.keys()) + if self.keywords: d["keywords"] = self.keywords if self.providers: diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 12d29ce37..6c92e7e5a 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -11,7 +11,7 @@ Type, TypeVar, Union, - cast, + Protocol, ) import pystac @@ -96,7 +96,11 @@ def _set_property( self.properties[prop_name] = v -S = TypeVar("S", bound=pystac.STACObject) +class STACExtendable(Protocol): + stac_extensions: List[str] + + +S = TypeVar("S", bound=STACExtendable) class ExtensionManagementMixin(Generic[S], ABC): @@ -150,6 +154,21 @@ def has_extension(cls, obj: S) -> bool: :attr:`pystac.STACObject.stac_extensions` for this extension's schema URI.""" schema_startswith = VERSION_REGEX.split(cls.get_schema_uri())[0] + "/" + if isinstance(obj, (pystac.Item, pystac.Collection)): + for asset in obj.assets.values(): + if asset.stac_extensions is not None and any( + uri.startswith(schema_startswith) + for uri in asset.stac_extensions + ): + return True + + elif isinstance(obj, pystac.Asset): + if obj.owner and obj.owner.stac_extensions is not None and any( + uri.startswith(schema_startswith) + for uri in obj.owner.stac_extensions + ): + return True + return obj.stac_extensions is not None and any( uri.startswith(schema_startswith) for uri in obj.stac_extensions ) @@ -173,15 +192,13 @@ def validate_owner_has_extension( STACError : If ``add_if_missing`` is ``True`` and ``asset.owner`` is ``None``. """ - if asset.owner is None: - if add_if_missing: - raise pystac.STACError( - "Attempted to use add_if_missing=True for an Asset with no owner. " - "Use Asset.set_owner or set add_if_missing=False." - ) - else: - return - return cls.ensure_has_extension(cast(S, asset.owner), add_if_missing) + + warnings.warn( + "validate_owner_has_extension is deprecated and will be removed in v2.0. " + "Use ensure_has_extension instead", + DeprecationWarning, + ) + return cls.ensure_has_extension(asset, add_if_missing) @classmethod def validate_has_extension(cls, obj: S, add_if_missing: bool = False) -> None: @@ -222,10 +239,15 @@ def ensure_has_extension(cls, obj: S, add_if_missing: bool = False) -> None: if add_if_missing: cls.add_to(obj) + if isinstance(obj, pystac.Asset): + cls.ensure_has_extension(obj.owner) + if not cls.has_extension(obj): - raise pystac.ExtensionNotImplemented( - f"Could not find extension schema URI {cls.get_schema_uri()} in object." - ) + if not obj.owner or not cls.has_extension(obj.owner): + raise pystac.ExtensionNotImplemented( + f"Could not find extension schema URI {cls.get_schema_uri()} " + "in object." + ) @classmethod def _ext_error_message(cls, obj: Any) -> str: diff --git a/pystac/extensions/classification.py b/pystac/extensions/classification.py index 63ff1757c..828a4f3ac 100644 --- a/pystac/extensions/classification.py +++ b/pystac/extensions/classification.py @@ -534,7 +534,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T] cls.ensure_has_extension(obj, add_if_missing) return cast(ClassificationExtension[T], ItemClassificationExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(ClassificationExtension[T], AssetClassificationExtension(obj)) elif isinstance(obj, item_assets.AssetDefinition): cls.ensure_has_extension( diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index 8d5a65fee..da07b0c99 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -543,7 +543,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> DatacubeExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(DatacubeExtension[T], ItemDatacubeExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index f2e81faa8..f2bb5d0ce 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -408,7 +408,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> EOExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(EOExtension[T], ItemEOExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(EOExtension[T], AssetEOExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 921f94352..ec605021f 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -224,7 +224,7 @@ def ext(cls, obj: pystac.Asset, add_if_missing: bool = False) -> FileExtension: This extension can be applied to instances of :class:`~pystac.Asset`. """ if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cls(obj) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index b197128c7..103aa2be9 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -455,7 +455,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> PointcloudExtension[T]: raise pystac.ExtensionTypeError( "Pointcloud extension does not apply to Collection Assets." ) - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 0a29c0680..50445c0f1 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -288,7 +288,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ProjectionExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/raster.py b/pystac/extensions/raster.py index 5a7115474..f31624e0d 100644 --- a/pystac/extensions/raster.py +++ b/pystac/extensions/raster.py @@ -735,7 +735,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> RasterExtension[T]: pystac.ExtensionTypeError : If an invalid object type is passed. """ if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(RasterExtension[T], AssetRasterExtension(obj)) elif isinstance(obj, item_assets.AssetDefinition): cls.ensure_has_extension( diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 7afec35a7..5644799e9 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -319,7 +319,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> SarExtension[T]: raise pystac.ExtensionTypeError( "SAR extension does not apply to Collection Assets." ) - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(SarExtension[T], AssetSarExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 8d89ca64a..3596dcd26 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -150,7 +150,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> SatExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(SatExtension[T], ItemSatExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(SatExtension[T], AssetSatExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/storage.py b/pystac/extensions/storage.py index 0d8bc94c9..fcfc4c401 100644 --- a/pystac/extensions/storage.py +++ b/pystac/extensions/storage.py @@ -152,7 +152,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> StorageExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(StorageExtension[T], ItemStorageExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(StorageExtension[T], AssetStorageExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/table.py b/pystac/extensions/table.py index 0d367492a..499ae6d48 100644 --- a/pystac/extensions/table.py +++ b/pystac/extensions/table.py @@ -158,7 +158,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> TableExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(TableExtension[T], ItemTableExtension(obj)) if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(TableExtension[T], AssetTableExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index a94065b95..e8efae87b 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -131,7 +131,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> TimestampsExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 7d25c401f..1dbd744bc 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -160,7 +160,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ViewExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(ViewExtension[T], ItemViewExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(ViewExtension[T], AssetViewExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index b41776110..0a4b85284 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -60,7 +60,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> XarrayAssetsExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return ItemXarrayAssetsExtension(obj) if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return AssetXarrayAssetsExtension(obj) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/item.py b/pystac/item.py index 4ef676b7f..709c1dd90 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -433,7 +433,22 @@ def to_dict( if self.bbox is not None: d["bbox"] = self.bbox - if self.stac_extensions is not None: + # we use the fact that in recent Python versions, dict keys are ordered + # by default + stac_extensions: Optional[Dict[str, None]] = None + if self.stac_extensions: + stac_extensions = dict.fromkeys(self.stac_extensions) + + for asset in self.assets.values(): + if stac_extensions and asset.stac_extensions: + stac_extensions.update(dict.fromkeys(asset.stac_extensions)) + elif asset.stac_extensions: + stac_extensions = dict.fromkeys(asset.stac_extensions) + + if stac_extensions is not None: + d["stac_extensions"] = list(stac_extensions.keys()) + + if stac_extensions is not None: d["stac_extensions"] = self.stac_extensions if self.collection_id: diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index 764173f48..8ee81f052 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -54,7 +54,7 @@ def get_schema_uri(cls) -> str: @classmethod def ext(cls, obj: T, add_if_missing: bool = False) -> "CustomExtension[T]": if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_has_extension(obj, add_if_missing) return cast(CustomExtension[T], AssetCustomExtension(obj)) if isinstance(obj, pystac.Item): cls.ensure_has_extension(obj, add_if_missing) @@ -152,6 +152,14 @@ def test_add_to_catalog(self) -> None: catalog_as_dict = catalog.to_dict() assert catalog_as_dict["test:prop"] == "foo" + def test_add_to_asset_no_owner(self) -> None: + asset = Asset("http://pystac.test/asset.tif") + custom = CustomExtension.ext(asset, add_if_missing=True) + assert CustomExtension.has_extension(asset) + custom.apply("bar") + asset_as_dict = asset.to_dict() + assert asset_as_dict["test:prop"] == "bar" + def test_add_to_collection(self) -> None: collection = Collection( "an-id", From 765a05f6b28ea3ae245db94829dfa6845dc17154 Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Thu, 12 Oct 2023 09:39:20 +0200 Subject: [PATCH 2/2] Using `_stac_extensions` to denote non-standard field in assets --- pystac/asset.py | 5 ++-- pystac/collection.py | 8 +++--- pystac/extensions/base.py | 53 +++++++++++++++++++++++---------------- pystac/item.py | 8 +++--- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/pystac/asset.py b/pystac/asset.py index 3e40f8903..64894f56c 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -66,7 +66,7 @@ class Asset: """Optional, additional fields for this asset. This is used by extensions as a way to serialize and deserialize properties on asset object JSON.""" - stac_extensions: List[str] + _stac_extensions: List[str] """A list of schema URIs for STAC Extensions implemented by this STAC Asset.""" def __init__( @@ -77,6 +77,7 @@ def __init__( media_type: Optional[str] = None, roles: Optional[List[str]] = None, extra_fields: Optional[Dict[str, Any]] = None, + stac_extensions: Optional[List[str]] = None, ) -> None: self.href = utils.make_posix_style(href) self.title = title @@ -84,7 +85,7 @@ def __init__( self.media_type = media_type self.roles = roles self.extra_fields = extra_fields or {} - self.stac_extensions = None + self._stac_extensions = stac_extensions # The Item which owns this Asset. self.owner = None diff --git a/pystac/collection.py b/pystac/collection.py index 4c9bbff14..712ab1ed9 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -585,10 +585,10 @@ def to_dict( stac_extensions = dict.fromkeys(self.stac_extensions) for asset in self.assets.values(): - if stac_extensions and asset.stac_extensions: - stac_extensions.update(dict.fromkeys(asset.stac_extensions)) - elif asset.stac_extensions: - stac_extensions = dict.fromkeys(asset.stac_extensions) + if stac_extensions and asset._stac_extensions: + stac_extensions.update(dict.fromkeys(asset._stac_extensions)) + elif asset._stac_extensions: + stac_extensions = dict.fromkeys(asset._stac_extensions) if stac_extensions is not None: d["stac_extensions"] = list(stac_extensions.keys()) diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 6c92e7e5a..36bd9f5ab 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -96,11 +96,7 @@ def _set_property( self.properties[prop_name] = v -class STACExtendable(Protocol): - stac_extensions: List[str] - - -S = TypeVar("S", bound=STACExtendable) +S = TypeVar("S", bound=Union[pystac.STACObject, pystac.Asset]) class ExtensionManagementMixin(Generic[S], ABC): @@ -134,19 +130,31 @@ def add_to(cls, obj: S) -> None: """Add the schema URI for this extension to the :attr:`~pystac.STACObject.stac_extensions` list for the given object, if it is not already present.""" - if obj.stac_extensions is None: - obj.stac_extensions = [cls.get_schema_uri()] - elif not cls.has_extension(obj): - obj.stac_extensions.append(cls.get_schema_uri()) + if isinstance(obj, pystac.Asset): + if obj._stac_extensions is None: + obj._stac_extensions = [cls.get_schema_uri()] + elif not cls.has_extension(obj): + obj._stac_extensions.append(cls.get_schema_uri()) + else: + if obj.stac_extensions is None: + obj.stac_extensions = [cls.get_schema_uri()] + elif not cls.has_extension(obj): + obj.stac_extensions.append(cls.get_schema_uri()) @classmethod def remove_from(cls, obj: S) -> None: """Remove the schema URI for this extension from the :attr:`pystac.STACObject.stac_extensions` list for the given object.""" + if obj.stac_extensions is not None: - obj.stac_extensions = [ - uri for uri in obj.stac_extensions if uri != cls.get_schema_uri() - ] + if isinstance(obj, pystac.Asset): + obj._stac_extensions = [ + uri for uri in obj._stac_extensions if uri != cls.get_schema_uri() + ] + else: + obj.stac_extensions = [ + uri for uri in obj.stac_extensions if uri != cls.get_schema_uri() + ] @classmethod def has_extension(cls, obj: S) -> bool: @@ -156,9 +164,9 @@ def has_extension(cls, obj: S) -> bool: if isinstance(obj, (pystac.Item, pystac.Collection)): for asset in obj.assets.values(): - if asset.stac_extensions is not None and any( + if asset._stac_extensions is not None and any( uri.startswith(schema_startswith) - for uri in asset.stac_extensions + for uri in asset._stac_extensions ): return True @@ -168,6 +176,11 @@ def has_extension(cls, obj: S) -> bool: for uri in obj.owner.stac_extensions ): return True + else: + return obj._stac_extensions is not None and any( + uri.startswith(schema_startswith) + for uri in obj._stac_extensions + ) return obj.stac_extensions is not None and any( uri.startswith(schema_startswith) for uri in obj.stac_extensions @@ -239,15 +252,11 @@ def ensure_has_extension(cls, obj: S, add_if_missing: bool = False) -> None: if add_if_missing: cls.add_to(obj) - if isinstance(obj, pystac.Asset): - cls.ensure_has_extension(obj.owner) - if not cls.has_extension(obj): - if not obj.owner or not cls.has_extension(obj.owner): - raise pystac.ExtensionNotImplemented( - f"Could not find extension schema URI {cls.get_schema_uri()} " - "in object." - ) + raise pystac.ExtensionNotImplemented( + f"Could not find extension schema URI {cls.get_schema_uri()} " + "in object." + ) @classmethod def _ext_error_message(cls, obj: Any) -> str: diff --git a/pystac/item.py b/pystac/item.py index 709c1dd90..bc5751d8d 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -440,10 +440,10 @@ def to_dict( stac_extensions = dict.fromkeys(self.stac_extensions) for asset in self.assets.values(): - if stac_extensions and asset.stac_extensions: - stac_extensions.update(dict.fromkeys(asset.stac_extensions)) - elif asset.stac_extensions: - stac_extensions = dict.fromkeys(asset.stac_extensions) + if stac_extensions and asset._stac_extensions: + stac_extensions.update(dict.fromkeys(asset._stac_extensions)) + elif asset._stac_extensions: + stac_extensions = dict.fromkeys(asset._stac_extensions) if stac_extensions is not None: d["stac_extensions"] = list(stac_extensions.keys())