Skip to content

Commit 43cfdec

Browse files
authored
Item assets (#1476)
* 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
1 parent 7e8abc1 commit 43cfdec

26 files changed

+712
-429
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
## [Unreleased]
44

5+
6+
### Added
7+
8+
- Top-level `item_assets` dict on `Collection`s ([#1476](https://github.com/stac-utils/pystac/pull/1476))
9+
510
### Changed
611

712
- Write STAC v1.1.0 ([#1427](https://github.com/stac-utils/pystac/pull/1427))
813
- Use [uv](https://github.com/astral-sh/uv) for development dependencies and docs ([#1439](https://github.com/stac-utils/pystac/pull/1439))
914
- 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)
15+
- Deprecated `ItemAssetExtension` ([#1476](https://github.com/stac-utils/pystac/pull/1476))
1016

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

docs/api/item_assets.rst

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pystac.item_assets
2+
==================
3+
4+
.. automodule:: pystac.item_assets
5+
:members:
6+
:undoc-members:
7+
:noindex:

docs/api/pystac.rst

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pystac
1515
Summaries
1616
Item
1717
Asset
18+
ItemAssetDefinition
1819
CommonMetadata
1920
ItemCollection
2021
Link
@@ -116,6 +117,14 @@ Asset
116117
:members:
117118
:undoc-members:
118119

120+
ItemAssetDefinition
121+
-------------------
122+
123+
.. autoclass:: pystac.ItemAssetDefinition
124+
:members:
125+
:undoc-members:
126+
127+
119128
CommonMetadata
120129
--------------
121130

pystac/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"RangeSummary",
3434
"Item",
3535
"Asset",
36+
"ItemAssetDefinition",
3637
"ItemCollection",
3738
"Provider",
3839
"ProviderRole",
@@ -81,6 +82,7 @@
8182
from pystac.summaries import RangeSummary, Summaries
8283
from pystac.asset import Asset
8384
from pystac.item import Item
85+
from pystac.item_assets import ItemAssetDefinition
8486
from pystac.item_collection import ItemCollection
8587
from pystac.provider import ProviderRole, Provider
8688
from pystac.utils import HREF

pystac/collection.py

+58
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pystac.asset import Asset, Assets
2121
from pystac.catalog import Catalog
2222
from pystac.errors import DeprecatedWarning, ExtensionNotImplemented, STACTypeError
23+
from pystac.item_assets import ItemAssetDefinition, _ItemAssets
2324
from pystac.layout import HrefLayoutStrategy
2425
from pystac.link import Link
2526
from pystac.provider import Provider
@@ -553,6 +554,7 @@ def __init__(
553554
self.keywords = keywords
554555
self.providers = providers
555556
self.summaries = summaries or Summaries.empty()
557+
self._item_assets: _ItemAssets | None = None
556558

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

736+
@property
737+
def item_assets(self) -> dict[str, ItemAssetDefinition]:
738+
"""Accessor for `item_assets
739+
<https://github.com/radiantearth/stac-spec/blob/v1.1.0/collection-spec/collection-spec.md#item_assets>`__
740+
on this collection.
741+
742+
Example::
743+
744+
.. code-block:: python
745+
746+
>>> print(collection.item_assets)
747+
{'thumbnail': <pystac.item_assets.ItemAssetDefinition at 0x72aea0420750>,
748+
'metadata': <pystac.item_assets.ItemAssetDefinition at 0x72aea017dc90>,
749+
'B5': <pystac.item_assets.ItemAssetDefinition at 0x72aea017efd0>,
750+
'B6': <pystac.item_assets.ItemAssetDefinition at 0x72aea016d5d0>,
751+
'B7': <pystac.item_assets.ItemAssetDefinition at 0x72aea016e050>,
752+
'B8': <pystac.item_assets.ItemAssetDefinition at 0x72aea016da90>}
753+
>>> collection.item_assets["thumbnail"].title
754+
'Thumbnail'
755+
756+
Set attributes on :class:`~pystac.ItemAssetDefinition` objects
757+
758+
.. code-block:: python
759+
760+
>>> collection.item_assets["thumbnail"].title = "New Title"
761+
762+
Add to the ``item_assets`` dict:
763+
764+
.. code-block:: python
765+
766+
>>> collection.item_assets["B4"] = {
767+
'type': 'image/tiff; application=geotiff; profile=cloud-optimized',
768+
'eo:bands': [{'name': 'B4', 'common_name': 'red'}]
769+
}
770+
>>> collection.item_assets["B4"].owner == collection
771+
True
772+
"""
773+
if self._item_assets is None:
774+
self._item_assets = _ItemAssets(self)
775+
return self._item_assets
776+
777+
@item_assets.setter
778+
def item_assets(
779+
self, item_assets: dict[str, ItemAssetDefinition | dict[str, Any]] | None
780+
) -> None:
781+
# clear out the cached value
782+
self._item_assets = None
783+
784+
if item_assets is None:
785+
self.extra_fields.pop("item_assets")
786+
else:
787+
self.extra_fields["item_assets"] = {
788+
k: v if isinstance(v, dict) else v.to_dict()
789+
for k, v in item_assets.items()
790+
}
791+
734792
def update_extent_from_items(self) -> None:
735793
"""
736794
Update datetime and bbox based on all items to a single bbox and time window.

pystac/extensions/base.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from abc import ABC, abstractmethod
66
from collections.abc import Iterable
77
from typing import (
8-
TYPE_CHECKING,
98
Any,
109
Generic,
1110
TypeVar,
@@ -14,9 +13,6 @@
1413

1514
import pystac
1615

17-
if TYPE_CHECKING:
18-
from pystac.extensions.item_assets import AssetDefinition
19-
2016
VERSION_REGEX = re.compile("/v[0-9].[0-9].*/")
2117

2218

@@ -158,7 +154,7 @@ def has_extension(cls, obj: S) -> bool:
158154
@classmethod
159155
def validate_owner_has_extension(
160156
cls,
161-
asset: pystac.Asset | AssetDefinition,
157+
asset: pystac.Asset | pystac.ItemAssetDefinition,
162158
add_if_missing: bool = False,
163159
) -> None:
164160
"""
@@ -190,7 +186,7 @@ def validate_owner_has_extension(
190186
@classmethod
191187
def ensure_owner_has_extension(
192188
cls,
193-
asset_or_link: pystac.Asset | AssetDefinition | pystac.Link,
189+
asset_or_link: pystac.Asset | pystac.ItemAssetDefinition | pystac.Link,
194190
add_if_missing: bool = False,
195191
) -> None:
196192
"""Given an :class:`~pystac.Asset`, checks if the asset's owner has this

pystac/extensions/classification.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
)
1717

1818
import pystac
19-
from pystac.extensions import item_assets
2019
from pystac.extensions.base import (
2120
ExtensionManagementMixin,
2221
PropertiesExtension,
@@ -27,7 +26,7 @@
2726
from pystac.serialization.identify import STACJSONDescription, STACVersionID
2827
from pystac.utils import get_required, map_opt
2928

30-
T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition, RasterBand)
29+
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition, RasterBand)
3130

3231
SCHEMA_URI_PATTERN: str = (
3332
"https://stac-extensions.github.io/classification/v{version}/schema.json"
@@ -492,7 +491,7 @@ class ClassificationExtension(
492491
"""An abstract class that can be used to extend the properties of
493492
:class:`~pystac.Item`, :class:`~pystac.Asset`,
494493
:class:`~pystac.extension.raster.RasterBand`, or
495-
:class:`~pystac.extension.item_assets.AssetDefinition` with properties from the
494+
:class:`~pystac.ItemAssetDefinition` with properties from the
496495
:stac-ext:`Classification Extension <classification>`. This class is generic
497496
over the type of STAC object being extended.
498497
@@ -602,7 +601,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
602601
603602
This extension can be applied to instances of :class:`~pystac.Item`,
604603
:class:`~pystac.Asset`,
605-
:class:`~pystac.extensions.item_assets.AssetDefinition`, or
604+
:class:`~pystac.ItemAssetDefinition`, or
606605
:class:`~pystac.extension.raster.RasterBand`.
607606
608607
Raises:
@@ -614,7 +613,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
614613
elif isinstance(obj, pystac.Asset):
615614
cls.ensure_owner_has_extension(obj, add_if_missing)
616615
return cast(ClassificationExtension[T], AssetClassificationExtension(obj))
617-
elif isinstance(obj, item_assets.AssetDefinition):
616+
elif isinstance(obj, pystac.ItemAssetDefinition):
618617
cls.ensure_owner_has_extension(obj, add_if_missing)
619618
return cast(
620619
ClassificationExtension[T], ItemAssetsClassificationExtension(obj)
@@ -665,17 +664,19 @@ def __repr__(self) -> str:
665664

666665

667666
class ItemAssetsClassificationExtension(
668-
ClassificationExtension[item_assets.AssetDefinition]
667+
ClassificationExtension[pystac.ItemAssetDefinition]
669668
):
670669
properties: dict[str, Any]
671-
asset_defn: item_assets.AssetDefinition
670+
asset_defn: pystac.ItemAssetDefinition
672671

673-
def __init__(self, item_asset: item_assets.AssetDefinition):
672+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
674673
self.asset_defn = item_asset
675674
self.properties = item_asset.properties
676675

677676
def __repr__(self) -> str:
678-
return f"<ItemAssetsClassificationExtension AssetDefinition={self.asset_defn}"
677+
return (
678+
f"<ItemAssetsClassificationExtension ItemAssetDefinition={self.asset_defn}"
679+
)
679680

680681

681682
class RasterBandClassificationExtension(ClassificationExtension[RasterBand]):

pystac/extensions/datacube.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
from typing import Any, Generic, Literal, TypeVar, Union, cast
77

88
import pystac
9-
from pystac.extensions import item_assets
109
from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
1110
from pystac.extensions.hooks import ExtensionHooks
1211
from pystac.utils import StringEnum, get_required, map_opt
1312

1413
T = TypeVar(
15-
"T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition
14+
"T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
1615
)
1716

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

693692

694-
class ItemAssetsDatacubeExtension(DatacubeExtension[item_assets.AssetDefinition]):
693+
class ItemAssetsDatacubeExtension(DatacubeExtension[pystac.ItemAssetDefinition]):
695694
properties: dict[str, Any]
696-
asset_defn: item_assets.AssetDefinition
695+
asset_defn: pystac.ItemAssetDefinition
697696

698-
def __init__(self, item_asset: item_assets.AssetDefinition):
697+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
699698
self.asset_defn = item_asset
700699
self.properties = item_asset.properties
701700

pystac/extensions/eo.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
)
1515

1616
import pystac
17-
from pystac.extensions import item_assets, projection, view
17+
from pystac.extensions import projection, view
1818
from pystac.extensions.base import (
1919
ExtensionManagementMixin,
2020
PropertiesExtension,
@@ -25,7 +25,7 @@
2525
from pystac.summaries import RangeSummary
2626
from pystac.utils import get_required, map_opt
2727

28-
T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition)
28+
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition)
2929

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

538538

539-
class ItemAssetsEOExtension(EOExtension[item_assets.AssetDefinition]):
539+
class ItemAssetsEOExtension(EOExtension[pystac.ItemAssetDefinition]):
540540
properties: dict[str, Any]
541-
asset_defn: item_assets.AssetDefinition
541+
asset_defn: pystac.ItemAssetDefinition
542542

543543
def _get_bands(self) -> list[Band] | None:
544544
if BANDS_PROP not in self.properties:
@@ -550,7 +550,7 @@ def _get_bands(self) -> list[Band] | None:
550550
)
551551
)
552552

553-
def __init__(self, item_asset: item_assets.AssetDefinition):
553+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
554554
self.asset_defn = item_asset
555555
self.properties = item_asset.properties
556556

pystac/extensions/ext.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
from dataclasses import dataclass
22
from typing import Any, Generic, Literal, TypeVar, cast
33

4-
from pystac import Asset, Catalog, Collection, Item, Link, STACError
4+
from pystac import (
5+
Asset,
6+
Catalog,
7+
Collection,
8+
Item,
9+
ItemAssetDefinition,
10+
Link,
11+
STACError,
12+
)
513
from pystac.extensions.classification import ClassificationExtension
614
from pystac.extensions.datacube import DatacubeExtension
715
from pystac.extensions.eo import EOExtension
816
from pystac.extensions.file import FileExtension
917
from pystac.extensions.grid import GridExtension
10-
from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension
18+
from pystac.extensions.item_assets import ItemAssetsExtension
1119
from pystac.extensions.mgrs import MgrsExtension
1220
from pystac.extensions.pointcloud import PointcloudExtension
1321
from pystac.extensions.projection import ProjectionExtension
@@ -22,8 +30,8 @@
2230
from pystac.extensions.view import ViewExtension
2331
from pystac.extensions.xarray_assets import XarrayAssetsExtension
2432

25-
T = TypeVar("T", Asset, AssetDefinition, Link)
26-
U = TypeVar("U", Asset, AssetDefinition)
33+
T = TypeVar("T", Asset, ItemAssetDefinition, Link)
34+
U = TypeVar("U", Asset, ItemAssetDefinition)
2735

2836
EXTENSION_NAMES = Literal[
2937
"classification",
@@ -107,7 +115,7 @@ def cube(self) -> DatacubeExtension[Collection]:
107115
return DatacubeExtension.ext(self.stac_object)
108116

109117
@property
110-
def item_assets(self) -> dict[str, AssetDefinition]:
118+
def item_assets(self) -> dict[str, ItemAssetDefinition]:
111119
return ItemAssetsExtension.ext(self.stac_object).item_assets
112120

113121
@property
@@ -300,8 +308,8 @@ def xarray(self) -> XarrayAssetsExtension[Asset]:
300308

301309

302310
@dataclass
303-
class ItemAssetExt(_AssetExt[AssetDefinition]):
304-
stac_object: AssetDefinition
311+
class ItemAssetExt(_AssetExt[ItemAssetDefinition]):
312+
stac_object: ItemAssetDefinition
305313

306314

307315
@dataclass

0 commit comments

Comments
 (0)