Skip to content

Commit

Permalink
Merge pull request #450 from duckontheweb/change/gh-370-ext-exception
Browse files Browse the repository at this point in the history
Raise exception when extending object that does not have schema URI
  • Loading branch information
Jon Duckworth authored Jun 17, 2021
2 parents 7f8e7aa + 5f2296f commit a729cd2
Show file tree
Hide file tree
Showing 41 changed files with 1,316 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[report]
fail_under = 90
fail_under = 91

[run]
source = pystac
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
STAC Items ([#430](https://github.com/stac-utils/pystac/pull/430))
- Support for Python 3.9 ([#420](https://github.com/stac-utils/pystac/pull/420))
- 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))
- Attempting to extend a `STACObject` that does not contain the extension's schema URI in
`stac_extensions` raises new `ExtensionNotImplementedError` ([#450](https://github.com/stac-utils/pystac/pull/450))

### Changed

Expand All @@ -20,6 +22,9 @@
`StacIO.read_text` ([#433](https://github.com/stac-utils/pystac/pull/433))
- `FileExtension` updated to work with File Info Extension v2.0.0 ([#442](https://github.com/stac-utils/pystac/pull/442))
- `FileExtension` only operates on `pystac.Asset` instances ([#442](https://github.com/stac-utils/pystac/pull/442))
- `*Extension.ext` methods now have an optional `add_if_missing` argument, which will
add the extension schema URI to the object's `stac_extensions` list if it is not
present ([#450](https://github.com/stac-utils/pystac/pull/450))

### Fixed

Expand Down Expand Up @@ -377,4 +382,3 @@ Initial release.
[v0.3.2]: <https://github.com/stac-utils/pystac/compare/v0.3.1..v0.3.2>
[v0.3.1]: <https://github.com/stac-utils/pystac/compare/v0.3.0..v0.3.1>
[v0.3.0]: <https://github.com/stac-utils/pystac/tree/v0.3.0>

37 changes: 37 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,43 @@ AssetProjectionExtension
:members:
:show-inheritance:

Raster Extension
----------------

DataType
~~~~~~~~

.. autoclass:: pystac.extensions.raster.DataType
:members:
:undoc-members:
:show-inheritance:

Statistics
~~~~~~~~~~

.. autoclass:: pystac.extensions.raster.Statistics
:members:

Histogram
~~~~~~~~~

.. autoclass:: pystac.extensions.raster.Histogram
:members:

RasterBand
~~~~~~~~~~

.. autoclass:: pystac.extensions.raster.RasterBand
:members:

RasterExtension
~~~~~~~~~~~~~~~

.. autoclass:: pystac.extensions.raster.RasterExtension
:members:
:show-inheritance:
:inherited-members:

Scientific Extension
--------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ example, to format all the Python code, run ``pre-commit run --all-files black``

You can also install a Git pre-commit hook which will run the relevant linters and
formatters on any staged code when committing. This will be much faster than running on
all files, which is usually[#]_ only required when changing the pre-commit version or
all files, which is usually [#]_ only required when changing the pre-commit version or
configuration. Once installed you can bypass this check by adding the ``--no-verify``
flag to Git commit commands, as in ``git commit --no-verify``.

Expand Down
1 change: 1 addition & 0 deletions pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
STACError,
STACTypeError,
ExtensionAlreadyExistsError,
ExtensionNotImplemented,
ExtensionTypeError,
RequiredPropertyMissing,
STACValidationError,
Expand Down
5 changes: 5 additions & 0 deletions pystac/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class ExtensionAlreadyExistsError(Exception):
pass


class ExtensionNotImplemented(Exception):
"""Attempted to extend a STAC object that does not implement the given
extension."""


class RequiredPropertyMissing(Exception):
"""This error is raised when a required value was expected
to be there but was missing or None. This will happen, for example,
Expand Down
24 changes: 19 additions & 5 deletions pystac/extensions/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import Generic, Iterable, List, Optional, Dict, Any, Type, TypeVar, Union

from pystac import Collection, RangeSummary, STACObject, Summaries
import pystac


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

summaries: Summaries
summaries: pystac.Summaries
"""The summaries for the :class:`~pystac.Collection` being extended."""

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

def _set_summary(
self,
prop_key: str,
v: Optional[Union[List[Any], RangeSummary[Any], Dict[str, Any]]],
v: Optional[Union[List[Any], pystac.RangeSummary[Any], Dict[str, Any]]],
) -> None:
if v is None:
self.summaries.remove(prop_key)
Expand Down Expand Up @@ -77,7 +77,7 @@ def _set_property(
self.properties[prop_name] = v


S = TypeVar("S", bound=STACObject)
S = TypeVar("S", bound=pystac.STACObject)


class ExtensionManagementMixin(Generic[S], ABC):
Expand Down Expand Up @@ -124,3 +124,17 @@ def has_extension(cls, obj: S) -> bool:
obj.stac_extensions is not None
and cls.get_schema_uri() in obj.stac_extensions
)

@classmethod
def validate_has_extension(cls, obj: Union[S, pystac.Asset]) -> None:
"""Given a :class:`~pystac.STACObject` or :class:`pystac.Asset` instance, checks
if the object (or its owner in the case of an Asset) has this extension's schema
URI in it's :attr:`~pystac.STACObject.stac_extensions` list."""
extensible = obj.owner if isinstance(obj, pystac.Asset) else obj
if (
extensible is not None
and cls.get_schema_uri() not in extensible.stac_extensions
):
raise pystac.ExtensionNotImplemented(
f"Could not find extension schema URI {cls.get_schema_uri()} in object."
)
13 changes: 11 additions & 2 deletions pystac/extensions/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,22 @@ def dimensions(self, v: Dict[str, Dimension]) -> None:
def get_schema_uri(cls) -> str:
return SCHEMA_URI

@staticmethod
def ext(obj: T) -> "DatacubeExtension[T]":
@classmethod
def ext(cls, obj: T, add_if_missing: bool = False) -> "DatacubeExtension[T]":
if isinstance(obj, pystac.Collection):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cast(DatacubeExtension[T], CollectionDatacubeExtension(obj))
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cast(DatacubeExtension[T], ItemDatacubeExtension(obj))
elif isinstance(obj, pystac.Asset):
if add_if_missing and obj.owner is not None:
cls.add_to(obj.owner)
cls.validate_has_extension(obj)
return cast(DatacubeExtension[T], AssetDatacubeExtension(obj))
else:
raise pystac.ExtensionTypeError(
Expand Down
10 changes: 8 additions & 2 deletions pystac/extensions/eo.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ def cloud_cover(self, v: Optional[float]) -> None:
def get_schema_uri(cls) -> str:
return SCHEMA_URI

@staticmethod
def ext(obj: T) -> "EOExtension[T]":
@classmethod
def ext(cls, obj: T, add_if_missing: bool = False) -> "EOExtension[T]":
"""Extends the given STAC Object with properties from the :stac-ext:`Electro-Optical
Extension <eo>`.
Expand All @@ -359,8 +359,14 @@ def ext(obj: T) -> "EOExtension[T]":
pystac.ExtensionTypeError : If an invalid object type is passed.
"""
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cast(EOExtension[T], ItemEOExtension(obj))
elif isinstance(obj, pystac.Asset):
if add_if_missing and isinstance(obj.owner, pystac.Item):
cls.add_to(obj.owner)
cls.validate_has_extension(obj)
return cast(EOExtension[T], AssetEOExtension(obj))
else:
raise pystac.ExtensionTypeError(
Expand Down
12 changes: 10 additions & 2 deletions pystac/extensions/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,21 @@ def get_schema_uri(cls) -> str:
return SCHEMA_URI

@classmethod
def ext(cls, obj: pystac.Asset) -> "FileExtension":
def ext(cls, obj: pystac.Asset, add_if_missing: bool = False) -> "FileExtension":
"""Extends the given STAC Object with properties from the :stac-ext:`File Info
Extension <file>`.
This extension can be applied to instances of :class:`~pystac.Asset`.
"""
return cls(obj)
if isinstance(obj, pystac.Asset):
if add_if_missing and isinstance(obj.owner, pystac.Item):
cls.add_to(obj.owner)
cls.validate_has_extension(obj)
return cls(obj)
else:
raise pystac.ExtensionTypeError(
f"File Info extension does not apply to type {type(obj)}"
)


class FileExtensionHooks(ExtensionHooks):
Expand Down
13 changes: 11 additions & 2 deletions pystac/extensions/item_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,17 @@ def get_schema_uri(cls) -> str:
return SCHEMA_URI

@classmethod
def ext(cls, collection: pystac.Collection) -> "ItemAssetsExtension":
return cls(collection)
def ext(
cls, obj: pystac.Collection, add_if_missing: bool = False
) -> "ItemAssetsExtension":
if isinstance(obj, pystac.Collection):
if add_if_missing:
cls.add_to(obj)
return cls(obj)
else:
raise pystac.ExtensionTypeError(
f"Item Assets extension does not apply to type {type(obj)}"
)


class ItemAssetsExtensionHooks(ExtensionHooks):
Expand Down
12 changes: 10 additions & 2 deletions pystac/extensions/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,13 +639,21 @@ def get_schema_uri(cls) -> str:
return SCHEMA_URI

@classmethod
def ext(cls, obj: pystac.Item) -> "LabelExtension":
def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "LabelExtension":
"""Extends the given STAC Object with properties from the :stac-ext:`Label
Extension <label>`.
This extension can be applied to instances of :class:`~pystac.Item`.
"""
return cls(obj)
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cls(obj)
else:
raise pystac.ExtensionTypeError(
f"Label extension does not apply to type {type(obj)}"
)


class LabelExtensionHooks(ExtensionHooks):
Expand Down
12 changes: 9 additions & 3 deletions pystac/extensions/pointcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,15 +522,21 @@ def statistics(self, v: Optional[List[PointcloudStatistic]]) -> None:
def get_schema_uri(cls) -> str:
return SCHEMA_URI

@staticmethod
def ext(obj: T) -> "PointcloudExtension[T]":
@classmethod
def ext(cls, obj: T, add_if_missing: bool = False) -> "PointcloudExtension[T]":
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cast(PointcloudExtension[T], ItemPointcloudExtension(obj))
elif isinstance(obj, pystac.Asset):
if add_if_missing and isinstance(obj.owner, pystac.Item):
cls.add_to(obj.owner)
cls.validate_has_extension(obj)
return cast(PointcloudExtension[T], AssetPointcloudExtension(obj))
else:
raise pystac.ExtensionTypeError(
f"File extension does not apply to type {type(obj)}"
f"Pointcloud extension does not apply to type {type(obj)}"
)


Expand Down
12 changes: 9 additions & 3 deletions pystac/extensions/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ def transform(self, v: Optional[List[float]]) -> None:
def get_schema_uri(cls) -> str:
return SCHEMA_URI

@staticmethod
def ext(obj: T) -> "ProjectionExtension[T]":
@classmethod
def ext(cls, obj: T, add_if_missing: bool = False) -> "ProjectionExtension[T]":
"""Extends the given STAC Object with properties from the :stac-ext:`Projection
Extension <projection>`.
Expand All @@ -250,12 +250,18 @@ def ext(obj: T) -> "ProjectionExtension[T]":
pystac.ExtensionTypeError : If an invalid object type is passed.
"""
if isinstance(obj, pystac.Item):
if add_if_missing:
cls.add_to(obj)
cls.validate_has_extension(obj)
return cast(ProjectionExtension[T], ItemProjectionExtension(obj))
elif isinstance(obj, pystac.Asset):
if add_if_missing and isinstance(obj.owner, pystac.Item):
cls.add_to(obj.owner)
cls.validate_has_extension(obj)
return cast(ProjectionExtension[T], AssetProjectionExtension(obj))
else:
raise pystac.ExtensionTypeError(
f"File extension does not apply to type {type(obj)}"
f"Projection extension does not apply to type {type(obj)}"
)

@staticmethod
Expand Down
Loading

0 comments on commit a729cd2

Please sign in to comment.