from __future__ import annotations
import os
import shutil
from copy import copy, deepcopy
from html import escape
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union
from pystac import common_metadata, utils
from pystac.html.jinja_env import get_jinja_env
if TYPE_CHECKING:
from pystac.collection import Collection
from pystac.common_metadata import CommonMetadata
from pystac.item import Item
A = TypeVar("A", bound="Asset")
class Asset:
"""An object that contains a link to data associated with an Item or Collection that
can be downloaded or streamed.
Args:
href : Link to the asset object. Relative and absolute links are both
allowed.
title : Optional displayed title for clients and users.
description : A description of the Asset providing additional details,
such as how it was processed or created. CommonMark 0.29 syntax MAY be used
for rich text representation.
media_type : Optional description of the media type. Registered Media Types
are preferred. See :class:`~pystac.MediaType` for common media types.
roles : Optional, Semantic roles (i.e. thumbnail, overview,
data, metadata) of the asset.
extra_fields : Optional, additional fields for this asset. This is used
by extensions as a way to serialize and deserialize properties on asset
object JSON.
"""
href: str
"""Link to the asset object. Relative and absolute links are both allowed."""
title: Optional[str]
"""Optional displayed title for clients and users."""
description: Optional[str]
"""A description of the Asset providing additional details, such as how it was
processed or created. CommonMark 0.29 syntax MAY be used for rich text
representation."""
media_type: Optional[str]
"""Optional description of the media type. Registered Media Types are preferred.
See :class:`~pystac.MediaType` for common media types."""
roles: Optional[List[str]]
"""Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) of the
asset."""
owner: Optional[Union[Item, Collection]]
"""The :class:`~pystac.Item` or :class:`~pystac.Collection` that this asset belongs
to, or ``None`` if it has no owner."""
extra_fields: Dict[str, Any]
"""Optional, additional fields for this asset. This is used by extensions as a
way to serialize and deserialize properties on asset object JSON."""
[docs] def __init__(
self,
href: str,
title: Optional[str] = None,
description: Optional[str] = None,
media_type: Optional[str] = None,
roles: Optional[List[str]] = None,
extra_fields: Optional[Dict[str, Any]] = None,
) -> None:
self.href = utils.make_posix_style(href)
self.title = title
self.description = description
self.media_type = media_type
self.roles = roles
self.extra_fields = extra_fields or {}
# The Item which owns this Asset.
self.owner = None
[docs] def set_owner(self, obj: Union[Collection, Item]) -> None:
"""Sets the owning item of this Asset.
The owning item will be used to resolve relative HREFs of this asset.
Args:
obj: The Collection or Item that owns this asset.
"""
self.owner = obj
[docs] def get_absolute_href(self) -> Optional[str]:
"""Gets the absolute href for this asset, if possible.
If this Asset has no associated Item, and the asset HREF is a relative path,
this method will return ``None``. If the Item that owns the Asset has no
self HREF, this will also return ``None``.
Returns:
str: The absolute HREF of this asset, or None if an absolute HREF could not
be determined.
"""
if utils.is_absolute_href(self.href):
return self.href
else:
if self.owner is not None:
item_self = self.owner.get_self_href()
if item_self is not None:
return utils.make_absolute_href(self.href, item_self)
return None
[docs] def to_dict(self) -> Dict[str, Any]:
"""Returns this Asset as a dictionary.
Returns:
dict: A serialization of the Asset.
"""
d: Dict[str, Any] = {"href": self.href}
if self.media_type is not None:
d["type"] = self.media_type
if self.title is not None:
d["title"] = self.title
if self.description is not None:
d["description"] = self.description
if self.extra_fields is not None and len(self.extra_fields) > 0:
for k, v in self.extra_fields.items():
d[k] = v
if self.roles is not None:
d["roles"] = self.roles
return d
[docs] def clone(self) -> Asset:
"""Clones this asset. Makes a ``deepcopy`` of the
:attr:`~pystac.Asset.extra_fields`.
Returns:
Asset: The clone of this asset.
"""
cls = self.__class__
return cls(
href=self.href,
title=self.title,
description=self.description,
media_type=self.media_type,
roles=self.roles,
extra_fields=deepcopy(self.extra_fields),
)
[docs] def has_role(self, role: str) -> bool:
"""Check if a role exists in the Asset role list.
Args:
role: Role to check for existence.
Returns:
bool: True if role exists, else False.
"""
if self.roles is None:
return False
else:
return role in self.roles
@property
def common_metadata(self) -> CommonMetadata:
"""Access the asset's common metadata fields as a
:class:`~pystac.CommonMetadata` object."""
return common_metadata.CommonMetadata(self)
def __repr__(self) -> str:
return "<Asset href={}>".format(self.href)
def _repr_html_(self) -> str:
jinja_env = get_jinja_env()
if jinja_env:
template = jinja_env.get_template("JSON.jinja2")
return str(template.render(dict=self.to_dict()))
else:
return escape(repr(self))
[docs] @classmethod
def from_dict(cls: Type[A], d: Dict[str, Any]) -> A:
"""Constructs an Asset from a dict.
Returns:
Asset: The Asset deserialized from the JSON dict.
"""
d = copy(d)
href = d.pop("href")
media_type = d.pop("type", None)
title = d.pop("title", None)
description = d.pop("description", None)
roles = d.pop("roles", None)
properties = None
if any(d):
properties = d
return cls(
href=href,
media_type=media_type,
title=title,
description=description,
roles=roles,
extra_fields=properties,
)
[docs] def move(self, href: str) -> Asset:
"""Moves this asset's file to a new location on the local filesystem,
setting the asset href accordingly.
Modifies the asset in place, and returns the same asset.
Args:
href: The new asset location. Must be a local path. If relative
it must be relative to the owner object.
Returns:
Asset: The asset with the updated href.
"""
src = _absolute_href(self.href, self.owner, "move")
dst = _absolute_href(href, self.owner, "move")
shutil.move(src, dst)
self.href = href
return self
[docs] def copy(self, href: str) -> Asset:
"""Copies this asset's file to a new location on the local filesystem,
setting the asset href accordingly.
Modifies the asset in place, and returns the same asset.
Args:
href: The new asset location. Must be a local path. If relative
it must be relative to the owner object.
Returns:
Asset: The asset with the updated href.
"""
src = _absolute_href(self.href, self.owner, "copy")
dst = _absolute_href(href, self.owner, "copy")
shutil.copy2(src, dst)
self.href = href
return self
[docs] def delete(self) -> None:
"""Delete this asset's file. Does not delete the asset from the item
that owns it. See :func:`~pystac.Item.delete_asset` for that.
Does not modify the asset.
"""
href = _absolute_href(self.href, self.owner, "delete")
os.remove(href)
def _absolute_href(
href: str, owner: Optional[Union[Item, Collection]], action: str = "access"
) -> str:
if utils.is_absolute_href(href):
return href
else:
item_self = owner.get_self_href() if owner else None
if item_self is None:
raise ValueError(
f"Cannot {action} file if asset href ('{href}') is relative "
"and owner item is not set. Hint: try using "
":func:`~pystac.Item.make_asset_hrefs_absolute`"
)
return utils.make_absolute_href(href, item_self)