Skip to content

Commit

Permalink
Item assets (#1476)
Browse files Browse the repository at this point in the history
* Move item_assets out of extensions

* Move item_assets to dummy file

* Move item_assets out of dummy file

* Add new `item_assets` access pattern

* Export AssetDefinition

* Add deprecated warning and update tests

* Remove schema uri from stac_extensions on migrate

* Update docs

* Add some more tests

* Remove TYPE_CHECKING

* Update changelog
  • Loading branch information
jsignell authored Jan 6, 2025
1 parent 7e8abc1 commit 43cfdec
Show file tree
Hide file tree
Showing 26 changed files with 712 additions and 429 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

## [Unreleased]


### Added

- Top-level `item_assets` dict on `Collection`s ([#1476](https://github.com/stac-utils/pystac/pull/1476))

### Changed

- Write STAC v1.1.0 ([#1427](https://github.com/stac-utils/pystac/pull/1427))
- Use [uv](https://github.com/astral-sh/uv) for development dependencies and docs ([#1439](https://github.com/stac-utils/pystac/pull/1439))
- Correctly detect absolute file path ref on windows, reflecting change in python 3.13 ([#1475](https://github.com/stac-utils/pystac/pull/14750)) (only effects python 3.13)
- Deprecated `ItemAssetExtension` ([#1476](https://github.com/stac-utils/pystac/pull/1476))

## [v1.11.0] - 2024-09-26

Expand Down
7 changes: 7 additions & 0 deletions docs/api/item_assets.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pystac.item_assets
==================

.. automodule:: pystac.item_assets
:members:
:undoc-members:
:noindex:
9 changes: 9 additions & 0 deletions docs/api/pystac.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pystac
Summaries
Item
Asset
ItemAssetDefinition
CommonMetadata
ItemCollection
Link
Expand Down Expand Up @@ -116,6 +117,14 @@ Asset
:members:
:undoc-members:

ItemAssetDefinition
-------------------

.. autoclass:: pystac.ItemAssetDefinition
:members:
:undoc-members:


CommonMetadata
--------------

Expand Down
2 changes: 2 additions & 0 deletions pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"RangeSummary",
"Item",
"Asset",
"ItemAssetDefinition",
"ItemCollection",
"Provider",
"ProviderRole",
Expand Down Expand Up @@ -81,6 +82,7 @@
from pystac.summaries import RangeSummary, Summaries
from pystac.asset import Asset
from pystac.item import Item
from pystac.item_assets import ItemAssetDefinition
from pystac.item_collection import ItemCollection
from pystac.provider import ProviderRole, Provider
from pystac.utils import HREF
Expand Down
58 changes: 58 additions & 0 deletions pystac/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pystac.asset import Asset, Assets
from pystac.catalog import Catalog
from pystac.errors import DeprecatedWarning, ExtensionNotImplemented, STACTypeError
from pystac.item_assets import ItemAssetDefinition, _ItemAssets
from pystac.layout import HrefLayoutStrategy
from pystac.link import Link
from pystac.provider import Provider
Expand Down Expand Up @@ -553,6 +554,7 @@ def __init__(
self.keywords = keywords
self.providers = providers
self.summaries = summaries or Summaries.empty()
self._item_assets: _ItemAssets | None = None

self.assets = {}
if assets is not None:
Expand Down Expand Up @@ -731,6 +733,62 @@ def get_item(self, id: str, recursive: bool = False) -> Item | None:
return super().get_item(id, recursive=recursive)
raise e

@property
def item_assets(self) -> dict[str, ItemAssetDefinition]:
"""Accessor for `item_assets
<https://github.com/radiantearth/stac-spec/blob/v1.1.0/collection-spec/collection-spec.md#item_assets>`__
on this collection.
Example::
.. code-block:: python
>>> print(collection.item_assets)
{'thumbnail': <pystac.item_assets.ItemAssetDefinition at 0x72aea0420750>,
'metadata': <pystac.item_assets.ItemAssetDefinition at 0x72aea017dc90>,
'B5': <pystac.item_assets.ItemAssetDefinition at 0x72aea017efd0>,
'B6': <pystac.item_assets.ItemAssetDefinition at 0x72aea016d5d0>,
'B7': <pystac.item_assets.ItemAssetDefinition at 0x72aea016e050>,
'B8': <pystac.item_assets.ItemAssetDefinition at 0x72aea016da90>}
>>> collection.item_assets["thumbnail"].title
'Thumbnail'
Set attributes on :class:`~pystac.ItemAssetDefinition` objects
.. code-block:: python
>>> collection.item_assets["thumbnail"].title = "New Title"
Add to the ``item_assets`` dict:
.. code-block:: python
>>> collection.item_assets["B4"] = {
'type': 'image/tiff; application=geotiff; profile=cloud-optimized',
'eo:bands': [{'name': 'B4', 'common_name': 'red'}]
}
>>> collection.item_assets["B4"].owner == collection
True
"""
if self._item_assets is None:
self._item_assets = _ItemAssets(self)
return self._item_assets

@item_assets.setter
def item_assets(
self, item_assets: dict[str, ItemAssetDefinition | dict[str, Any]] | None
) -> None:
# clear out the cached value
self._item_assets = None

if item_assets is None:
self.extra_fields.pop("item_assets")
else:
self.extra_fields["item_assets"] = {
k: v if isinstance(v, dict) else v.to_dict()
for k, v in item_assets.items()
}

def update_extent_from_items(self) -> None:
"""
Update datetime and bbox based on all items to a single bbox and time window.
Expand Down
8 changes: 2 additions & 6 deletions pystac/extensions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from abc import ABC, abstractmethod
from collections.abc import Iterable
from typing import (
TYPE_CHECKING,
Any,
Generic,
TypeVar,
Expand All @@ -14,9 +13,6 @@

import pystac

if TYPE_CHECKING:
from pystac.extensions.item_assets import AssetDefinition

VERSION_REGEX = re.compile("/v[0-9].[0-9].*/")


Expand Down Expand Up @@ -158,7 +154,7 @@ def has_extension(cls, obj: S) -> bool:
@classmethod
def validate_owner_has_extension(
cls,
asset: pystac.Asset | AssetDefinition,
asset: pystac.Asset | pystac.ItemAssetDefinition,
add_if_missing: bool = False,
) -> None:
"""
Expand Down Expand Up @@ -190,7 +186,7 @@ def validate_owner_has_extension(
@classmethod
def ensure_owner_has_extension(
cls,
asset_or_link: pystac.Asset | AssetDefinition | pystac.Link,
asset_or_link: pystac.Asset | pystac.ItemAssetDefinition | pystac.Link,
add_if_missing: bool = False,
) -> None:
"""Given an :class:`~pystac.Asset`, checks if the asset's owner has this
Expand Down
19 changes: 10 additions & 9 deletions pystac/extensions/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
)

import pystac
from pystac.extensions import item_assets
from pystac.extensions.base import (
ExtensionManagementMixin,
PropertiesExtension,
Expand All @@ -27,7 +26,7 @@
from pystac.serialization.identify import STACJSONDescription, STACVersionID
from pystac.utils import get_required, map_opt

T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition, RasterBand)
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition, RasterBand)

SCHEMA_URI_PATTERN: str = (
"https://stac-extensions.github.io/classification/v{version}/schema.json"
Expand Down Expand Up @@ -492,7 +491,7 @@ class ClassificationExtension(
"""An abstract class that can be used to extend the properties of
:class:`~pystac.Item`, :class:`~pystac.Asset`,
:class:`~pystac.extension.raster.RasterBand`, or
:class:`~pystac.extension.item_assets.AssetDefinition` with properties from the
:class:`~pystac.ItemAssetDefinition` with properties from the
:stac-ext:`Classification Extension <classification>`. This class is generic
over the type of STAC object being extended.
Expand Down Expand Up @@ -602,7 +601,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
This extension can be applied to instances of :class:`~pystac.Item`,
:class:`~pystac.Asset`,
:class:`~pystac.extensions.item_assets.AssetDefinition`, or
:class:`~pystac.ItemAssetDefinition`, or
:class:`~pystac.extension.raster.RasterBand`.
Raises:
Expand All @@ -614,7 +613,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
elif isinstance(obj, pystac.Asset):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(ClassificationExtension[T], AssetClassificationExtension(obj))
elif isinstance(obj, item_assets.AssetDefinition):
elif isinstance(obj, pystac.ItemAssetDefinition):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(
ClassificationExtension[T], ItemAssetsClassificationExtension(obj)
Expand Down Expand Up @@ -665,17 +664,19 @@ def __repr__(self) -> str:


class ItemAssetsClassificationExtension(
ClassificationExtension[item_assets.AssetDefinition]
ClassificationExtension[pystac.ItemAssetDefinition]
):
properties: dict[str, Any]
asset_defn: item_assets.AssetDefinition
asset_defn: pystac.ItemAssetDefinition

def __init__(self, item_asset: item_assets.AssetDefinition):
def __init__(self, item_asset: pystac.ItemAssetDefinition):
self.asset_defn = item_asset
self.properties = item_asset.properties

def __repr__(self) -> str:
return f"<ItemAssetsClassificationExtension AssetDefinition={self.asset_defn}"
return (
f"<ItemAssetsClassificationExtension ItemAssetDefinition={self.asset_defn}"
)


class RasterBandClassificationExtension(ClassificationExtension[RasterBand]):
Expand Down
11 changes: 5 additions & 6 deletions pystac/extensions/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
from typing import Any, Generic, Literal, TypeVar, Union, cast

import pystac
from pystac.extensions import item_assets
from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
from pystac.extensions.hooks import ExtensionHooks
from pystac.utils import StringEnum, get_required, map_opt

T = TypeVar(
"T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition
"T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
)

SCHEMA_URI = "https://stac-extensions.github.io/datacube/v2.2.0/schema.json"
Expand Down Expand Up @@ -619,7 +618,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> DatacubeExtension[T]:
elif isinstance(obj, pystac.Asset):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(DatacubeExtension[T], AssetDatacubeExtension(obj))
elif isinstance(obj, item_assets.AssetDefinition):
elif isinstance(obj, pystac.ItemAssetDefinition):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(DatacubeExtension[T], ItemAssetsDatacubeExtension(obj))
else:
Expand Down Expand Up @@ -691,11 +690,11 @@ def __repr__(self) -> str:
return f"<AssetDatacubeExtension Item id={self.asset_href}>"


class ItemAssetsDatacubeExtension(DatacubeExtension[item_assets.AssetDefinition]):
class ItemAssetsDatacubeExtension(DatacubeExtension[pystac.ItemAssetDefinition]):
properties: dict[str, Any]
asset_defn: item_assets.AssetDefinition
asset_defn: pystac.ItemAssetDefinition

def __init__(self, item_asset: item_assets.AssetDefinition):
def __init__(self, item_asset: pystac.ItemAssetDefinition):
self.asset_defn = item_asset
self.properties = item_asset.properties

Expand Down
12 changes: 6 additions & 6 deletions pystac/extensions/eo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)

import pystac
from pystac.extensions import item_assets, projection, view
from pystac.extensions import projection, view
from pystac.extensions.base import (
ExtensionManagementMixin,
PropertiesExtension,
Expand All @@ -25,7 +25,7 @@
from pystac.summaries import RangeSummary
from pystac.utils import get_required, map_opt

T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition)
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition)

SCHEMA_URI: str = "https://stac-extensions.github.io/eo/v1.1.0/schema.json"
SCHEMA_URIS: list[str] = [
Expand Down Expand Up @@ -409,7 +409,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> EOExtension[T]:
elif isinstance(obj, pystac.Asset):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(EOExtension[T], AssetEOExtension(obj))
elif isinstance(obj, item_assets.AssetDefinition):
elif isinstance(obj, pystac.ItemAssetDefinition):
cls.ensure_owner_has_extension(obj, add_if_missing)
return cast(EOExtension[T], ItemAssetsEOExtension(obj))
else:
Expand Down Expand Up @@ -536,9 +536,9 @@ def __repr__(self) -> str:
return f"<AssetEOExtension Asset href={self.asset_href}>"


class ItemAssetsEOExtension(EOExtension[item_assets.AssetDefinition]):
class ItemAssetsEOExtension(EOExtension[pystac.ItemAssetDefinition]):
properties: dict[str, Any]
asset_defn: item_assets.AssetDefinition
asset_defn: pystac.ItemAssetDefinition

def _get_bands(self) -> list[Band] | None:
if BANDS_PROP not in self.properties:
Expand All @@ -550,7 +550,7 @@ def _get_bands(self) -> list[Band] | None:
)
)

def __init__(self, item_asset: item_assets.AssetDefinition):
def __init__(self, item_asset: pystac.ItemAssetDefinition):
self.asset_defn = item_asset
self.properties = item_asset.properties

Expand Down
22 changes: 15 additions & 7 deletions pystac/extensions/ext.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from dataclasses import dataclass
from typing import Any, Generic, Literal, TypeVar, cast

from pystac import Asset, Catalog, Collection, Item, Link, STACError
from pystac import (
Asset,
Catalog,
Collection,
Item,
ItemAssetDefinition,
Link,
STACError,
)
from pystac.extensions.classification import ClassificationExtension
from pystac.extensions.datacube import DatacubeExtension
from pystac.extensions.eo import EOExtension
from pystac.extensions.file import FileExtension
from pystac.extensions.grid import GridExtension
from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension
from pystac.extensions.item_assets import ItemAssetsExtension
from pystac.extensions.mgrs import MgrsExtension
from pystac.extensions.pointcloud import PointcloudExtension
from pystac.extensions.projection import ProjectionExtension
Expand All @@ -22,8 +30,8 @@
from pystac.extensions.view import ViewExtension
from pystac.extensions.xarray_assets import XarrayAssetsExtension

T = TypeVar("T", Asset, AssetDefinition, Link)
U = TypeVar("U", Asset, AssetDefinition)
T = TypeVar("T", Asset, ItemAssetDefinition, Link)
U = TypeVar("U", Asset, ItemAssetDefinition)

EXTENSION_NAMES = Literal[
"classification",
Expand Down Expand Up @@ -107,7 +115,7 @@ def cube(self) -> DatacubeExtension[Collection]:
return DatacubeExtension.ext(self.stac_object)

@property
def item_assets(self) -> dict[str, AssetDefinition]:
def item_assets(self) -> dict[str, ItemAssetDefinition]:
return ItemAssetsExtension.ext(self.stac_object).item_assets

@property
Expand Down Expand Up @@ -300,8 +308,8 @@ def xarray(self) -> XarrayAssetsExtension[Asset]:


@dataclass
class ItemAssetExt(_AssetExt[AssetDefinition]):
stac_object: AssetDefinition
class ItemAssetExt(_AssetExt[ItemAssetDefinition]):
stac_object: ItemAssetDefinition


@dataclass
Expand Down
Loading

0 comments on commit 43cfdec

Please sign in to comment.