"""Implements the :stac-ext:`Item Assets Definition Extension <item-assets>`."""
from __future__ import annotations
from copy import deepcopy
from typing import Any, Dict, List, Optional
import pystac
from pystac.extensions.base import ExtensionManagementMixin
from pystac.extensions.hooks import ExtensionHooks
from pystac.serialization.identify import STACJSONDescription, STACVersionID
from pystac.utils import get_required
SCHEMA_URI = "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json"
ITEM_ASSETS_PROP = "item_assets"
ASSET_TITLE_PROP = "title"
ASSET_DESC_PROP = "description"
ASSET_TYPE_PROP = "type"
ASSET_ROLES_PROP = "roles"
[docs]class AssetDefinition:
"""Object that contains details about the datafiles that will be included in member
Items for this Collection.
See the :stac-ext:`Asset Object <item-assets#asset-object>` for details.
"""
properties: Dict[str, Any]
owner: Optional[pystac.Collection]
def __init__(
self, properties: Dict[str, Any], owner: Optional[pystac.Collection] = None
) -> None:
self.properties = properties
self.owner = owner
def __eq__(self, o: object) -> bool:
if not isinstance(o, AssetDefinition):
return NotImplemented
return self.to_dict() == o.to_dict()
[docs] @classmethod
def create(
cls,
title: Optional[str],
description: Optional[str],
media_type: Optional[str],
roles: Optional[List[str]],
extra_fields: Optional[Dict[str, Any]] = None,
) -> AssetDefinition:
"""
Creates a new asset definition.
Args:
title : Displayed title for clients and users.
description : Description of the Asset providing additional details,
such as how it was processed or created.
`CommonMark 0.29 <http://commonmark.org/>`__ syntax MAY be used
for rich text representation.
media_type : `media type\
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
of the asset.
roles : `semantic roles
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
of the asset, similar to the use of rel in links.
extra_fields : Additional fields on the asset definition, e.g. from
extensions.
"""
asset_defn = cls({})
asset_defn.apply(
title=title,
description=description,
media_type=media_type,
roles=roles,
extra_fields=extra_fields,
)
return asset_defn
[docs] def apply(
self,
title: Optional[str],
description: Optional[str],
media_type: Optional[str],
roles: Optional[List[str]],
extra_fields: Optional[Dict[str, Any]] = None,
) -> None:
"""
Sets the properties for this asset definition.
Args:
title : Displayed title for clients and users.
description : Description of the Asset providing additional details,
such as how it was processed or created.
`CommonMark 0.29 <http://commonmark.org/>`__ syntax MAY be used
for rich text representation.
media_type : `media type\
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
of the asset.
roles : `semantic roles
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
of the asset, similar to the use of rel in links.
extra_fields : Additional fields on the asset definition, e.g. from
extensions.
"""
if extra_fields:
self.properties.update(extra_fields)
self.title = title
self.description = description
self.media_type = media_type
self.roles = roles
self.owner = None
[docs] def set_owner(self, obj: pystac.Collection) -> None:
"""Sets the owning item of this AssetDefinition.
The owning item will be used to resolve relative HREFs of this asset.
Args:
obj: The Collection that owns this asset.
"""
self.owner = obj
@property
def title(self) -> Optional[str]:
"""Gets or sets the displayed title for clients and users."""
return self.properties.get(ASSET_TITLE_PROP)
@title.setter
def title(self, v: Optional[str]) -> None:
if v is None:
self.properties.pop(ASSET_TITLE_PROP, None)
else:
self.properties[ASSET_TITLE_PROP] = v
@property
def description(self) -> Optional[str]:
"""Gets or sets a description of the Asset providing additional details, such as
how it was processed or created. `CommonMark 0.29 <http://commonmark.org/>`__
syntax MAY be used for rich text representation."""
return self.properties.get(ASSET_DESC_PROP)
@description.setter
def description(self, v: Optional[str]) -> None:
if v is None:
self.properties.pop(ASSET_DESC_PROP, None)
else:
self.properties[ASSET_DESC_PROP] = v
@property
def media_type(self) -> Optional[str]:
"""Gets or sets the `media type
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/catalog-spec/catalog-spec.md#media-types>`__
of the asset."""
return self.properties.get(ASSET_TYPE_PROP)
@media_type.setter
def media_type(self, v: Optional[str]) -> None:
if v is None:
self.properties.pop(ASSET_TYPE_PROP, None)
else:
self.properties[ASSET_TYPE_PROP] = v
@property
def roles(self) -> Optional[List[str]]:
"""Gets or sets the `semantic roles
<https://github.com/radiantearth/stac-spec/tree/v1.0.0/item-spec/item-spec.md#asset-role-types>`__
of the asset, similar to the use of rel in links."""
return self.properties.get(ASSET_ROLES_PROP)
@roles.setter
def roles(self, v: Optional[List[str]]) -> None:
if v is None:
self.properties.pop(ASSET_ROLES_PROP, None)
else:
self.properties[ASSET_ROLES_PROP] = v
[docs] def to_dict(self) -> Dict[str, Any]:
"""Returns a dictionary representing this ``AssetDefinition``."""
return deepcopy(self.properties)
[docs] def create_asset(self, href: str) -> pystac.Asset:
"""Creates a new :class:`~pystac.Asset` instance using the fields from this
``AssetDefinition`` and the given ``href``."""
return pystac.Asset(
href=href,
title=self.title,
description=self.description,
media_type=self.media_type,
roles=self.roles,
extra_fields={
k: v
for k, v in self.properties.items()
if k
not in {
ASSET_TITLE_PROP,
ASSET_DESC_PROP,
ASSET_TYPE_PROP,
ASSET_ROLES_PROP,
}
},
)
[docs]class ItemAssetsExtension(ExtensionManagementMixin[pystac.Collection]):
collection: pystac.Collection
def __init__(self, collection: pystac.Collection) -> None:
self.collection = collection
@property
def item_assets(self) -> Dict[str, AssetDefinition]:
"""Gets or sets a dictionary of assets that can be found in member Items. Maps
the asset key to an :class:`AssetDefinition` instance."""
result: Dict[str, Any] = get_required(
self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, ITEM_ASSETS_PROP
)
return {k: AssetDefinition(v, self.collection) for k, v in result.items()}
@item_assets.setter
def item_assets(self, v: Dict[str, AssetDefinition]) -> None:
self.collection.extra_fields[ITEM_ASSETS_PROP] = {
k: asset_def.properties for k, asset_def in v.items()
}
def __repr__(self) -> str:
return f"<ItemAssetsExtension collection.id = {self.collection.id}>"
[docs] @classmethod
def get_schema_uri(cls) -> str:
return SCHEMA_URI
[docs] @classmethod
def ext(
cls, obj: pystac.Collection, add_if_missing: bool = False
) -> ItemAssetsExtension:
"""Extends the given :class:`~pystac.Collection` with properties from the
:stac-ext:`Item Assets Extension <item-assets>`.
Raises:
pystac.ExtensionTypeError : If an invalid object type is passed.
"""
if isinstance(obj, pystac.Collection):
cls.validate_has_extension(obj, add_if_missing)
return cls(obj)
else:
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
[docs]class ItemAssetsExtensionHooks(ExtensionHooks):
schema_uri: str = SCHEMA_URI
prev_extension_ids = {"asset", "item-assets"}
stac_object_types = {pystac.STACObjectType.COLLECTION}
[docs] def migrate(
self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription
) -> None:
# Handle that the "item-assets" extension had the id of "assets", before
# collection assets (since removed) took over the ID of "assets"
if version < "1.0.0-beta.1" and "asset" in info.extensions:
if "assets" in obj:
obj["item_assets"] = obj["assets"]
del obj["assets"]
super().migrate(obj, version, info)
ITEM_ASSETS_EXTENSION_HOOKS: ExtensionHooks = ItemAssetsExtensionHooks()