Skip to content

Commit 815cf7f

Browse files
committed
Merge branches 'item-assets-1' and 'item-assets-2' into item-assets
2 parents 398c68c + b799ef4 commit 815cf7f

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed

pystac/extensions/ia.py

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
"""Implements the :stac-ext:`Item Assets Definition Extension <item-assets>`."""
2+
3+
from __future__ import annotations
4+
5+
from copy import deepcopy
6+
from typing import TYPE_CHECKING, Any, Literal
7+
8+
import pystac
9+
from pystac.extensions.base import ExtensionManagementMixin
10+
from pystac.extensions.hooks import ExtensionHooks
11+
from pystac.serialization.identify import STACJSONDescription, STACVersionID
12+
from pystac.utils import get_required
13+
14+
if TYPE_CHECKING:
15+
from pystac.extensions.ext import ItemAssetExt
16+
17+
SCHEMA_URI = "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json"
18+
19+
ITEM_ASSETS_PROP = "item_assets"
20+
21+
ASSET_TITLE_PROP = "title"
22+
ASSET_DESC_PROP = "description"
23+
ASSET_TYPE_PROP = "type"
24+
ASSET_ROLES_PROP = "roles"
25+
26+
27+
class AssetDefinition:
28+
"""Object that contains details about the datafiles that will be included in member
29+
Items for this Collection.
30+
31+
See the :stac-ext:`Asset Object <item-assets#asset-object>` for details.
32+
"""
33+
34+
properties: dict[str, Any]
35+
36+
owner: pystac.Collection | None
37+
38+
def __init__(
39+
self, properties: dict[str, Any], owner: pystac.Collection | None = None
40+
) -> None:
41+
self.properties = properties
42+
self.owner = owner
43+
44+
def __eq__(self, o: object) -> bool:
45+
if not isinstance(o, AssetDefinition):
46+
return NotImplemented
47+
return self.to_dict() == o.to_dict()
48+
49+
@classmethod
50+
def create(
51+
cls,
52+
title: str | None,
53+
description: str | None,
54+
media_type: str | None,
55+
roles: list[str] | None,
56+
extra_fields: dict[str, Any] | None = None,
57+
) -> AssetDefinition:
58+
"""
59+
Creates a new asset definition.
60+
61+
Args:
62+
title : Displayed title for clients and users.
63+
description : Description of the Asset providing additional details,
64+
such as how it was processed or created.
65+
`CommonMark 0.29 <http://commonmark.org/>`__ syntax MAY be used
66+
for rich text representation.
67+
media_type : `media type\
68+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
69+
of the asset.
70+
roles : `semantic roles
71+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
72+
of the asset, similar to the use of rel in links.
73+
extra_fields : Additional fields on the asset definition, e.g. from
74+
extensions.
75+
"""
76+
asset_defn = cls({})
77+
asset_defn.apply(
78+
title=title,
79+
description=description,
80+
media_type=media_type,
81+
roles=roles,
82+
extra_fields=extra_fields,
83+
)
84+
return asset_defn
85+
86+
def apply(
87+
self,
88+
title: str | None,
89+
description: str | None,
90+
media_type: str | None,
91+
roles: list[str] | None,
92+
extra_fields: dict[str, Any] | None = None,
93+
) -> None:
94+
"""
95+
Sets the properties for this asset definition.
96+
97+
Args:
98+
title : Displayed title for clients and users.
99+
description : Description of the Asset providing additional details,
100+
such as how it was processed or created.
101+
`CommonMark 0.29 <http://commonmark.org/>`__ syntax MAY be used
102+
for rich text representation.
103+
media_type : `media type\
104+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
105+
of the asset.
106+
roles : `semantic roles
107+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
108+
of the asset, similar to the use of rel in links.
109+
extra_fields : Additional fields on the asset definition, e.g. from
110+
extensions.
111+
"""
112+
if extra_fields:
113+
self.properties.update(extra_fields)
114+
self.title = title
115+
self.description = description
116+
self.media_type = media_type
117+
self.roles = roles
118+
self.owner = None
119+
120+
def set_owner(self, obj: pystac.Collection) -> None:
121+
"""Sets the owning item of this AssetDefinition.
122+
123+
The owning item will be used to resolve relative HREFs of this asset.
124+
125+
Args:
126+
obj: The Collection that owns this asset.
127+
"""
128+
self.owner = obj
129+
130+
@property
131+
def title(self) -> str | None:
132+
"""Gets or sets the displayed title for clients and users."""
133+
return self.properties.get(ASSET_TITLE_PROP)
134+
135+
@title.setter
136+
def title(self, v: str | None) -> None:
137+
if v is None:
138+
self.properties.pop(ASSET_TITLE_PROP, None)
139+
else:
140+
self.properties[ASSET_TITLE_PROP] = v
141+
142+
@property
143+
def description(self) -> str | None:
144+
"""Gets or sets a description of the Asset providing additional details, such as
145+
how it was processed or created. `CommonMark 0.29 <http://commonmark.org/>`__
146+
syntax MAY be used for rich text representation."""
147+
return self.properties.get(ASSET_DESC_PROP)
148+
149+
@description.setter
150+
def description(self, v: str | None) -> None:
151+
if v is None:
152+
self.properties.pop(ASSET_DESC_PROP, None)
153+
else:
154+
self.properties[ASSET_DESC_PROP] = v
155+
156+
@property
157+
def media_type(self) -> str | None:
158+
"""Gets or sets the `media type
159+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
160+
of the asset."""
161+
return self.properties.get(ASSET_TYPE_PROP)
162+
163+
@media_type.setter
164+
def media_type(self, v: str | None) -> None:
165+
if v is None:
166+
self.properties.pop(ASSET_TYPE_PROP, None)
167+
else:
168+
self.properties[ASSET_TYPE_PROP] = v
169+
170+
@property
171+
def roles(self) -> list[str] | None:
172+
"""Gets or sets the `semantic roles
173+
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
174+
of the asset, similar to the use of rel in links."""
175+
return self.properties.get(ASSET_ROLES_PROP)
176+
177+
@roles.setter
178+
def roles(self, v: list[str] | None) -> None:
179+
if v is None:
180+
self.properties.pop(ASSET_ROLES_PROP, None)
181+
else:
182+
self.properties[ASSET_ROLES_PROP] = v
183+
184+
def to_dict(self) -> dict[str, Any]:
185+
"""Returns a dictionary representing this ``AssetDefinition``."""
186+
return deepcopy(self.properties)
187+
188+
def create_asset(self, href: str) -> pystac.Asset:
189+
"""Creates a new :class:`~pystac.Asset` instance using the fields from this
190+
``AssetDefinition`` and the given ``href``."""
191+
return pystac.Asset(
192+
href=href,
193+
title=self.title,
194+
description=self.description,
195+
media_type=self.media_type,
196+
roles=self.roles,
197+
extra_fields={
198+
k: v
199+
for k, v in self.properties.items()
200+
if k
201+
not in {
202+
ASSET_TITLE_PROP,
203+
ASSET_DESC_PROP,
204+
ASSET_TYPE_PROP,
205+
ASSET_ROLES_PROP,
206+
}
207+
},
208+
)
209+
210+
@property
211+
def ext(self) -> ItemAssetExt:
212+
"""Accessor for extension classes on this item_asset
213+
214+
Example::
215+
216+
collection.ext.item_assets["data"].ext.proj.epsg = 4326
217+
"""
218+
from pystac.extensions.ext import ItemAssetExt
219+
220+
return ItemAssetExt(stac_object=self)
221+
222+
223+
class ItemAssetsExtension(ExtensionManagementMixin[pystac.Collection]):
224+
name: Literal["item_assets"] = "item_assets"
225+
collection: pystac.Collection
226+
227+
def __init__(self, collection: pystac.Collection) -> None:
228+
self.collection = collection
229+
230+
@property
231+
def item_assets(self) -> dict[str, AssetDefinition]:
232+
"""Gets or sets a dictionary of assets that can be found in member Items. Maps
233+
the asset key to an :class:`AssetDefinition` instance."""
234+
result: dict[str, Any] = get_required(
235+
self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, ITEM_ASSETS_PROP
236+
)
237+
return {k: AssetDefinition(v, self.collection) for k, v in result.items()}
238+
239+
@item_assets.setter
240+
def item_assets(self, v: dict[str, AssetDefinition]) -> None:
241+
self.collection.extra_fields[ITEM_ASSETS_PROP] = {
242+
k: asset_def.properties for k, asset_def in v.items()
243+
}
244+
245+
def __repr__(self) -> str:
246+
return f"<ItemAssetsExtension collection.id = {self.collection.id}>"
247+
248+
@classmethod
249+
def get_schema_uri(cls) -> str:
250+
return SCHEMA_URI
251+
252+
@classmethod
253+
def ext(
254+
cls, obj: pystac.Collection, add_if_missing: bool = False
255+
) -> ItemAssetsExtension:
256+
"""Extends the given :class:`~pystac.Collection` with properties from the
257+
:stac-ext:`Item Assets Extension <item-assets>`.
258+
259+
Raises:
260+
261+
pystac.ExtensionTypeError : If an invalid object type is passed.
262+
"""
263+
if isinstance(obj, pystac.Collection):
264+
cls.ensure_has_extension(obj, add_if_missing)
265+
return cls(obj)
266+
else:
267+
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
268+
269+
270+
class ItemAssetsExtensionHooks(ExtensionHooks):
271+
schema_uri: str = SCHEMA_URI
272+
prev_extension_ids = {"asset", "item-assets"}
273+
stac_object_types = {pystac.STACObjectType.COLLECTION}
274+
275+
def migrate(
276+
self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription
277+
) -> None:
278+
# Handle that the "item-assets" extension had the id of "assets", before
279+
# collection assets (since removed) took over the ID of "assets"
280+
if version < "1.0.0-beta.1" and "asset" in info.extensions:
281+
if "assets" in obj:
282+
obj["item_assets"] = obj["assets"]
283+
del obj["assets"]
284+
285+
super().migrate(obj, version, info)
286+
287+
288+
ITEM_ASSETS_EXTENSION_HOOKS: ExtensionHooks = ItemAssetsExtensionHooks()

0 commit comments

Comments
 (0)