Skip to content

Commit 9ddab16

Browse files
committed
Implement #370 for EO Extension
1 parent 7f8e7aa commit 9ddab16

File tree

6 files changed

+51
-7
lines changed

6 files changed

+51
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
STAC Items ([#430](https://github.com/stac-utils/pystac/pull/430))
1313
- Support for Python 3.9 ([#420](https://github.com/stac-utils/pystac/pull/420))
1414
- Migration for pre-1.0.0-rc.1 Stats Objects (renamed to Range Objects in 1.0.0-rc.3) ([#447](https://github.com/stac-utils/pystac/pull/447))
15+
- Attempt to extend a `STACObject` that does not contain the extension's schema URI in
16+
`stac_extensions` raises new `ExtensionNotImplementedError`.
1517

1618
### Changed
1719

pystac/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
STACError,
88
STACTypeError,
99
ExtensionAlreadyExistsError,
10+
ExtensionNotImplemented,
1011
ExtensionTypeError,
1112
RequiredPropertyMissing,
1213
STACValidationError,

pystac/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class ExtensionAlreadyExistsError(Exception):
3535
pass
3636

3737

38+
class ExtensionNotImplemented(Exception):
39+
"""Attempted to extend a STAC object that does not implement the given
40+
extension."""
41+
42+
3843
class RequiredPropertyMissing(Exception):
3944
"""This error is raised when a required value was expected
4045
to be there but was missing or None. This will happen, for example,

pystac/extensions/base.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from abc import ABC, abstractmethod
22
from typing import Generic, Iterable, List, Optional, Dict, Any, Type, TypeVar, Union
33

4-
from pystac import Collection, RangeSummary, STACObject, Summaries
4+
import pystac
55

66

77
class SummariesExtension:
@@ -12,16 +12,16 @@ class SummariesExtension:
1212
extension-specific class that inherits from this class and instantiate that. See
1313
:class:`~pystac.extensions.eo.SummariesEOExtension` for an example."""
1414

15-
summaries: Summaries
15+
summaries: pystac.Summaries
1616
"""The summaries for the :class:`~pystac.Collection` being extended."""
1717

18-
def __init__(self, collection: Collection) -> None:
18+
def __init__(self, collection: pystac.Collection) -> None:
1919
self.summaries = collection.summaries
2020

2121
def _set_summary(
2222
self,
2323
prop_key: str,
24-
v: Optional[Union[List[Any], RangeSummary[Any], Dict[str, Any]]],
24+
v: Optional[Union[List[Any], pystac.RangeSummary[Any], Dict[str, Any]]],
2525
) -> None:
2626
if v is None:
2727
self.summaries.remove(prop_key)
@@ -77,7 +77,7 @@ def _set_property(
7777
self.properties[prop_name] = v
7878

7979

80-
S = TypeVar("S", bound=STACObject)
80+
S = TypeVar("S", bound=pystac.STACObject)
8181

8282

8383
class ExtensionManagementMixin(Generic[S], ABC):
@@ -124,3 +124,17 @@ def has_extension(cls, obj: S) -> bool:
124124
obj.stac_extensions is not None
125125
and cls.get_schema_uri() in obj.stac_extensions
126126
)
127+
128+
@classmethod
129+
def validate_has_extension(cls, obj: Union[S, pystac.Asset]) -> None:
130+
"""Given a :class:`~pystac.STACObject` or :class:`pystac.Asset` instance, checks
131+
if the object (or its owner in the case of an Asset) has this extension's schema
132+
URI in it's :attr:`~pystac.STACObject.stac_extensions` list."""
133+
extensible = obj.owner if isinstance(obj, pystac.Asset) else obj
134+
if (
135+
extensible is not None
136+
and cls.get_schema_uri() not in extensible.stac_extensions
137+
):
138+
raise pystac.ExtensionNotImplemented(
139+
f"Could not find extension schema URI {cls.get_schema_uri()} in object."
140+
)

pystac/extensions/eo.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ def cloud_cover(self, v: Optional[float]) -> None:
346346
def get_schema_uri(cls) -> str:
347347
return SCHEMA_URI
348348

349-
@staticmethod
350-
def ext(obj: T) -> "EOExtension[T]":
349+
@classmethod
350+
def ext(cls, obj: T) -> "EOExtension[T]":
351351
"""Extends the given STAC Object with properties from the :stac-ext:`Electro-Optical
352352
Extension <eo>`.
353353
@@ -359,8 +359,10 @@ def ext(obj: T) -> "EOExtension[T]":
359359
pystac.ExtensionTypeError : If an invalid object type is passed.
360360
"""
361361
if isinstance(obj, pystac.Item):
362+
cls.validate_has_extension(obj)
362363
return cast(EOExtension[T], ItemEOExtension(obj))
363364
elif isinstance(obj, pystac.Asset):
365+
cls.validate_has_extension(obj)
364366
return cast(EOExtension[T], AssetEOExtension(obj))
365367
else:
366368
raise pystac.ExtensionTypeError(

tests/extensions/test_eo.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,23 @@ def test_extend_invalid_object(self) -> None:
264264

265265
with self.assertRaises(pystac.ExtensionTypeError):
266266
EOExtension.ext(link) # type: ignore
267+
268+
def test_extension_not_implemented(self) -> None:
269+
# Should raise exception if Item does not include extension URI
270+
item = pystac.Item.from_file(self.PLAIN_ITEM)
271+
272+
with self.assertRaises(pystac.ExtensionNotImplemented):
273+
_ = EOExtension.ext(item)
274+
275+
# Should raise exception if owning Item does not include extension URI
276+
asset = item.assets["thumbnail"]
277+
278+
with self.assertRaises(pystac.ExtensionNotImplemented):
279+
_ = EOExtension.ext(asset)
280+
281+
# Should succeed if Asset has no owner
282+
stac_io = pystac.StacIO.default()
283+
item_dict = stac_io.read_json(self.LANDSAT_EXAMPLE_URI)
284+
asset_dict = item_dict["assets"]["B11"]
285+
ownerless_asset = pystac.Asset.from_dict(asset_dict)
286+
_ = EOExtension.ext(ownerless_asset)

0 commit comments

Comments
 (0)