pystac-core 1.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. pystac/__init__.py +260 -0
  2. pystac/asset.py +395 -0
  3. pystac/cache.py +349 -0
  4. pystac/catalog.py +1307 -0
  5. pystac/client.py +23 -0
  6. pystac/collection.py +899 -0
  7. pystac/common_metadata.py +251 -0
  8. pystac/errors.py +119 -0
  9. pystac/extensions/__init__.py +3 -0
  10. pystac/extensions/base.py +270 -0
  11. pystac/extensions/ext.py +458 -0
  12. pystac/extensions/hooks.py +111 -0
  13. pystac/html/JSON.jinja2 +57 -0
  14. pystac/html/Macros.jinja2 +34 -0
  15. pystac/html/__init__.py +3 -0
  16. pystac/html/jinja_env.py +15 -0
  17. pystac/item.py +539 -0
  18. pystac/item_assets.py +247 -0
  19. pystac/item_collection.py +257 -0
  20. pystac/layout.py +699 -0
  21. pystac/link.py +530 -0
  22. pystac/media_type.py +54 -0
  23. pystac/provider.py +122 -0
  24. pystac/py.typed +0 -0
  25. pystac/rel_type.py +29 -0
  26. pystac/serialization/__init__.py +14 -0
  27. pystac/serialization/common_properties.py +110 -0
  28. pystac/serialization/identify.py +300 -0
  29. pystac/serialization/migrate.py +193 -0
  30. pystac/stac_io.py +486 -0
  31. pystac/stac_object.py +701 -0
  32. pystac/static/__init__.py +0 -0
  33. pystac/static/fields-normalized.json +1 -0
  34. pystac/summaries.py +334 -0
  35. pystac/utils.py +627 -0
  36. pystac/validation/__init__.py +276 -0
  37. pystac/validation/jsonschemas/__init__.py +0 -0
  38. pystac/validation/jsonschemas/geojson/Feature.json +505 -0
  39. pystac/validation/jsonschemas/geojson/Geometry.json +218 -0
  40. pystac/validation/jsonschemas/stac-spec/v1.1.0/bands.json +24 -0
  41. pystac/validation/jsonschemas/stac-spec/v1.1.0/basics.json +34 -0
  42. pystac/validation/jsonschemas/stac-spec/v1.1.0/catalog.json +57 -0
  43. pystac/validation/jsonschemas/stac-spec/v1.1.0/collection.json +230 -0
  44. pystac/validation/jsonschemas/stac-spec/v1.1.0/common.json +30 -0
  45. pystac/validation/jsonschemas/stac-spec/v1.1.0/data-values.json +84 -0
  46. pystac/validation/jsonschemas/stac-spec/v1.1.0/datetime.json +53 -0
  47. pystac/validation/jsonschemas/stac-spec/v1.1.0/instrument.json +32 -0
  48. pystac/validation/jsonschemas/stac-spec/v1.1.0/item.json +347 -0
  49. pystac/validation/jsonschemas/stac-spec/v1.1.0/licensing.json +12 -0
  50. pystac/validation/jsonschemas/stac-spec/v1.1.0/provider.json +47 -0
  51. pystac/validation/local_validator.py +126 -0
  52. pystac/validation/schema_uri_map.py +351 -0
  53. pystac/validation/stac_validator.py +304 -0
  54. pystac/version.py +72 -0
  55. pystac_core-1.15.0.dist-info/METADATA +24 -0
  56. pystac_core-1.15.0.dist-info/RECORD +57 -0
  57. pystac_core-1.15.0.dist-info/WHEEL +4 -0
pystac/__init__.py ADDED
@@ -0,0 +1,260 @@
1
+ # isort: skip_file
2
+ """
3
+ PySTAC is a library for working with SpatioTemporal Asset Catalogs (STACs)
4
+ """
5
+
6
+ from pkgutil import extend_path
7
+
8
+ __path__ = extend_path(__path__, __name__)
9
+
10
+ __all__ = [
11
+ "__version__",
12
+ "TemplateError",
13
+ "STACError",
14
+ "STACTypeError",
15
+ "DuplicateObjectKeyError",
16
+ "ExtensionAlreadyExistsError",
17
+ "ExtensionNotImplemented",
18
+ "ExtensionTypeError",
19
+ "RequiredPropertyMissing",
20
+ "STACValidationError",
21
+ "DeprecatedWarning",
22
+ "MediaType",
23
+ "RelType",
24
+ "StacIO",
25
+ "STACObject",
26
+ "STACObjectType",
27
+ "Link",
28
+ "HIERARCHICAL_LINKS",
29
+ "Catalog",
30
+ "CatalogType",
31
+ "Collection",
32
+ "Extent",
33
+ "SpatialExtent",
34
+ "TemporalExtent",
35
+ "Summaries",
36
+ "CommonMetadata",
37
+ "RangeSummary",
38
+ "Item",
39
+ "Asset",
40
+ "ItemAssetDefinition",
41
+ "ItemCollection",
42
+ "Provider",
43
+ "ProviderRole",
44
+ "read_file",
45
+ "read_dict",
46
+ "write_file",
47
+ "get_stac_version",
48
+ "set_stac_version",
49
+ ]
50
+
51
+ import warnings
52
+ from typing import Any
53
+
54
+ from pystac.errors import (
55
+ TemplateError,
56
+ STACError,
57
+ STACTypeError,
58
+ DuplicateObjectKeyError,
59
+ ExtensionAlreadyExistsError,
60
+ ExtensionNotImplemented,
61
+ ExtensionTypeError,
62
+ RequiredPropertyMissing,
63
+ STACValidationError,
64
+ DeprecatedWarning,
65
+ )
66
+
67
+ from pystac.version import (
68
+ __version__,
69
+ get_stac_version,
70
+ set_stac_version,
71
+ )
72
+ from pystac.media_type import MediaType
73
+ from pystac.rel_type import RelType
74
+ from pystac.stac_io import StacIO
75
+ from pystac.stac_object import STACObject, STACObjectType
76
+ from pystac.link import Link, HIERARCHICAL_LINKS
77
+ from pystac.catalog import Catalog, CatalogType
78
+ from pystac.collection import (
79
+ Collection,
80
+ Extent,
81
+ SpatialExtent,
82
+ TemporalExtent,
83
+ )
84
+ from pystac.common_metadata import CommonMetadata
85
+ from pystac.summaries import RangeSummary, Summaries
86
+ from pystac.asset import Asset
87
+ from pystac.item import Item
88
+ from pystac.item_assets import ItemAssetDefinition
89
+ from pystac.item_collection import ItemCollection
90
+ from pystac.provider import ProviderRole, Provider
91
+ from pystac.utils import HREF
92
+
93
+ import pystac.extensions.hooks
94
+ import pystac.extensions.classification
95
+ import pystac.extensions.datacube
96
+ import pystac.extensions.eo
97
+ import pystac.extensions.file
98
+ import pystac.extensions.grid
99
+ import pystac.extensions.item_assets
100
+
101
+ with warnings.catch_warnings():
102
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
103
+ import pystac.extensions.label
104
+ import pystac.extensions.mgrs
105
+ import pystac.extensions.mlm
106
+ import pystac.extensions.pointcloud
107
+ import pystac.extensions.projection
108
+ import pystac.extensions.raster
109
+ import pystac.extensions.sar
110
+ import pystac.extensions.sat
111
+ import pystac.extensions.scientific
112
+ import pystac.extensions.storage
113
+ import pystac.extensions.table
114
+ import pystac.extensions.timestamps
115
+ import pystac.extensions.version
116
+ import pystac.extensions.view
117
+ import pystac.extensions.xarray_assets
118
+
119
+ EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks(
120
+ [
121
+ pystac.extensions.classification.CLASSIFICATION_EXTENSION_HOOKS,
122
+ pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS,
123
+ pystac.extensions.eo.EO_EXTENSION_HOOKS,
124
+ pystac.extensions.file.FILE_EXTENSION_HOOKS,
125
+ pystac.extensions.grid.GRID_EXTENSION_HOOKS,
126
+ pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS,
127
+ pystac.extensions.label.LABEL_EXTENSION_HOOKS,
128
+ pystac.extensions.mgrs.MGRS_EXTENSION_HOOKS,
129
+ pystac.extensions.mlm.MLM_EXTENSION_HOOKS,
130
+ pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS,
131
+ pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS,
132
+ pystac.extensions.raster.RASTER_EXTENSION_HOOKS,
133
+ pystac.extensions.sar.SAR_EXTENSION_HOOKS,
134
+ pystac.extensions.sat.SAT_EXTENSION_HOOKS,
135
+ pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS,
136
+ pystac.extensions.storage.STORAGE_EXTENSION_HOOKS,
137
+ pystac.extensions.table.TABLE_EXTENSION_HOOKS,
138
+ pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS,
139
+ pystac.extensions.version.VERSION_EXTENSION_HOOKS,
140
+ pystac.extensions.view.VIEW_EXTENSION_HOOKS,
141
+ pystac.extensions.xarray_assets.XARRAY_ASSETS_EXTENSION_HOOKS,
142
+ ]
143
+ )
144
+
145
+
146
+ def read_file(href: HREF, stac_io: StacIO | None = None) -> STACObject:
147
+ """Reads a STAC object from a file.
148
+
149
+ This method will return either a Catalog, a Collection, or an Item based on what
150
+ the file contains.
151
+
152
+ This is a convenience method for :meth:`StacIO.read_stac_object
153
+ <pystac.StacIO.read_stac_object>`
154
+
155
+ Args:
156
+ href : The HREF to read the object from.
157
+ stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
158
+ provided, will use :meth:`StacIO.default` to create an instance.
159
+
160
+ Returns:
161
+ The specific STACObject implementation class that is represented
162
+ by the JSON read from the file located at HREF.
163
+
164
+ Raises:
165
+ STACTypeError : If the file at ``href`` does not represent a valid
166
+ :class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
167
+ is not a :class:`~pystac.STACObject` and must be read using
168
+ :meth:`ItemCollection.from_file <pystac.ItemCollection.from_file>`
169
+ """
170
+ if stac_io is None:
171
+ stac_io = StacIO.default()
172
+ return stac_io.read_stac_object(href)
173
+
174
+
175
+ def write_file(
176
+ obj: STACObject,
177
+ include_self_link: bool = True,
178
+ dest_href: HREF | None = None,
179
+ stac_io: StacIO | None = None,
180
+ ) -> None:
181
+ """Writes a STACObject to a file.
182
+
183
+ This will write only the Catalog, Collection or Item ``obj``. It will not attempt
184
+ to write any other objects that are linked to ``obj``; if you'd like functionality
185
+ to save off catalogs recursively see :meth:`Catalog.save <pystac.Catalog.save>`.
186
+
187
+ This method will write the JSON of the object to the object's assigned "self" link
188
+ or to the dest_href if provided. To set the self link, see
189
+ :meth:`STACObject.set_self_href <pystac.STACObject.set_self_href>`.
190
+
191
+ Convenience method for :meth:`STACObject.from_file <pystac.STACObject.from_file>`
192
+
193
+ Args:
194
+ obj : The STACObject to save.
195
+ include_self_link : If ``True``, include the ``"self"`` link with this object.
196
+ Otherwise, leave out the self link.
197
+ dest_href : Optional HREF to save the file to. If ``None``, the object will be
198
+ saved to the object's ``"self"`` href.
199
+ stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
200
+ provided, will use :meth:`StacIO.default` to create an instance.
201
+ """
202
+ if stac_io is None:
203
+ stac_io = StacIO.default()
204
+ import os
205
+
206
+ dest_href = None if dest_href is None else str(os.fspath(dest_href))
207
+ obj.save_object(
208
+ include_self_link=include_self_link, dest_href=dest_href, stac_io=stac_io
209
+ )
210
+
211
+
212
+ def read_dict(
213
+ d: dict[str, Any],
214
+ href: str | None = None,
215
+ root: Catalog | None = None,
216
+ stac_io: StacIO | None = None,
217
+ ) -> STACObject:
218
+ """Reads a :class:`~STACObject` or :class:`~ItemCollection` from a JSON-like dict
219
+ representing a serialized STAC object.
220
+
221
+ This method will return either a :class:`~Catalog`, :class:`~Collection`,
222
+ or :class`~Item` based on the contents of the dict.
223
+
224
+ This is a convenience method for either
225
+ :meth:`StacIO.stac_object_from_dict <pystac.StacIO.stac_object_from_dict>`.
226
+
227
+ Args:
228
+ d : The dict to parse.
229
+ href : Optional href that is the file location of the object being
230
+ parsed.
231
+ root : Optional root of the catalog for this object.
232
+ If provided, the root's resolved object cache can be used to search for
233
+ previously resolved instances of the STAC object.
234
+ stac_io: Optional :class:`~StacIO` instance to use for reading. If ``None``,
235
+ the default instance will be used.
236
+
237
+ Raises:
238
+ STACTypeError : If the ``d`` dictionary does not represent a valid
239
+ :class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
240
+ is not a :class:`~pystac.STACObject` and must be read using
241
+ :meth:`ItemCollection.from_dict <pystac.ItemCollection.from_dict>`
242
+ """
243
+ if stac_io is None:
244
+ stac_io = StacIO.default()
245
+ return stac_io.stac_object_from_dict(d, href, root)
246
+
247
+
248
+ def __getattr__(name: str) -> Any:
249
+ if name == "validation":
250
+ import warnings
251
+ import pystac.validation
252
+
253
+ warnings.warn(
254
+ "pystac.validation will not be automatically imported to the package in "
255
+ "pystac v2.0. Instead, import it directly: `import pystac.validation`",
256
+ DeprecationWarning,
257
+ stacklevel=2,
258
+ )
259
+ return pystac.validation
260
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
pystac/asset.py ADDED
@@ -0,0 +1,395 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from copy import copy, deepcopy
5
+ from typing import TYPE_CHECKING, Any, Protocol, TypeVar
6
+
7
+ from pystac import MediaType, STACError, common_metadata, utils
8
+ from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href
9
+
10
+ if TYPE_CHECKING:
11
+ from pystac.common_metadata import CommonMetadata
12
+ from pystac.extensions.ext import AssetExt
13
+
14
+ #: Generalized version of :class:`Asset`
15
+ A = TypeVar("A", bound="Asset")
16
+
17
+
18
+ class Asset:
19
+ """An object that contains a link to data associated with an Item or Collection that
20
+ can be downloaded or streamed.
21
+
22
+ Args:
23
+ href : Link to the asset object. Relative and absolute links are both
24
+ allowed.
25
+ title : Optional displayed title for clients and users.
26
+ description : A description of the Asset providing additional details,
27
+ such as how it was processed or created. CommonMark 0.29 syntax MAY be used
28
+ for rich text representation.
29
+ media_type : Optional description of the media type. Registered Media Types
30
+ are preferred. See :class:`~pystac.MediaType` for common media types.
31
+ roles : Optional, Semantic roles (i.e. thumbnail, overview,
32
+ data, metadata) of the asset.
33
+ extra_fields : Optional, additional fields for this asset. This is used
34
+ by extensions as a way to serialize and deserialize properties on asset
35
+ object JSON.
36
+ """
37
+
38
+ href: str
39
+ """Link to the asset object. Relative and absolute links are both allowed."""
40
+
41
+ title: str | None
42
+ """Optional displayed title for clients and users."""
43
+
44
+ description: str | None
45
+ """A description of the Asset providing additional details, such as how it was
46
+ processed or created. CommonMark 0.29 syntax MAY be used for rich text
47
+ representation."""
48
+
49
+ media_type: str | None
50
+ """Optional description of the media type. Registered Media Types are preferred.
51
+ See :class:`~pystac.MediaType` for common media types."""
52
+
53
+ roles: list[str] | None
54
+ """Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) of the
55
+ asset."""
56
+
57
+ owner: Assets | None
58
+ """The :class:`~pystac.Item` or :class:`~pystac.Collection` that this asset belongs
59
+ to, or ``None`` if it has no owner."""
60
+
61
+ extra_fields: dict[str, Any]
62
+ """Optional, additional fields for this asset. This is used by extensions as a
63
+ way to serialize and deserialize properties on asset object JSON."""
64
+
65
+ def __init__(
66
+ self,
67
+ href: str,
68
+ title: str | None = None,
69
+ description: str | None = None,
70
+ media_type: str | None = None,
71
+ roles: list[str] | None = None,
72
+ extra_fields: dict[str, Any] | None = None,
73
+ ) -> None:
74
+ self.href = utils.make_posix_style(href)
75
+ self.title = title
76
+ self.description = description
77
+ self.media_type = media_type
78
+ self.roles = roles
79
+ self.extra_fields = extra_fields or {}
80
+
81
+ # The Item which owns this Asset.
82
+ self.owner = None
83
+
84
+ def set_owner(self, obj: Assets) -> None:
85
+ """Sets the owning item of this Asset.
86
+
87
+ The owning item will be used to resolve relative HREFs of this asset.
88
+
89
+ Args:
90
+ obj: The Collection or Item that owns this asset.
91
+ """
92
+ self.owner = obj
93
+
94
+ def get_absolute_href(self) -> str | None:
95
+ """Gets the absolute href for this asset, if possible.
96
+
97
+ If this Asset has no associated Item, and the asset HREF is a relative path,
98
+ this method will return ``None``. If the Item that owns the Asset has no
99
+ self HREF, this will also return ``None``.
100
+
101
+ Returns:
102
+ str: The absolute HREF of this asset, or None if an absolute HREF could not
103
+ be determined.
104
+ """
105
+ item_self = self.owner.get_self_href() if self.owner is not None else None
106
+ if utils.is_absolute_href(self.href, item_self):
107
+ return self.href
108
+ else:
109
+ if item_self is not None:
110
+ return utils.make_absolute_href(self.href, item_self)
111
+ return None
112
+
113
+ def to_dict(self) -> dict[str, Any]:
114
+ """Returns this Asset as a dictionary.
115
+
116
+ Returns:
117
+ dict: A serialization of the Asset.
118
+ """
119
+
120
+ d: dict[str, Any] = {"href": self.href}
121
+
122
+ if self.media_type is not None:
123
+ d["type"] = self.media_type
124
+
125
+ if self.title is not None:
126
+ d["title"] = self.title
127
+
128
+ if self.description is not None:
129
+ d["description"] = self.description
130
+
131
+ if self.extra_fields is not None and len(self.extra_fields) > 0:
132
+ for k, v in self.extra_fields.items():
133
+ d[k] = v
134
+
135
+ if self.roles is not None:
136
+ d["roles"] = self.roles
137
+
138
+ return d
139
+
140
+ def clone(self) -> Asset:
141
+ """Clones this asset. Makes a ``deepcopy`` of the
142
+ :attr:`~pystac.Asset.extra_fields`.
143
+
144
+ Returns:
145
+ Asset: The clone of this asset.
146
+ """
147
+ cls = self.__class__
148
+ return cls(
149
+ href=self.href,
150
+ title=self.title,
151
+ description=self.description,
152
+ media_type=self.media_type,
153
+ roles=self.roles,
154
+ extra_fields=deepcopy(self.extra_fields),
155
+ )
156
+
157
+ def has_role(self, role: str) -> bool:
158
+ """Check if a role exists in the Asset role list.
159
+
160
+ Args:
161
+ role: Role to check for existence.
162
+
163
+ Returns:
164
+ bool: True if role exists, else False.
165
+ """
166
+ if self.roles is None:
167
+ return False
168
+ else:
169
+ return role in self.roles
170
+
171
+ @property
172
+ def common_metadata(self) -> CommonMetadata:
173
+ """Access the asset's common metadata fields as a
174
+ :class:`~pystac.CommonMetadata` object."""
175
+ return common_metadata.CommonMetadata(self)
176
+
177
+ def __repr__(self) -> str:
178
+ return f"<Asset href={self.href}>"
179
+
180
+ def _repr_html_(self) -> str:
181
+ from html import escape
182
+
183
+ from pystac.html.jinja_env import get_jinja_env
184
+
185
+ jinja_env = get_jinja_env()
186
+ if jinja_env:
187
+ template = jinja_env.get_template("JSON.jinja2")
188
+ return str(template.render(dict=self.to_dict(), plain=escape(repr(self))))
189
+ else:
190
+ return escape(repr(self))
191
+
192
+ @classmethod
193
+ def from_dict(cls: type[A], d: dict[str, Any]) -> A:
194
+ """Constructs an Asset from a dict.
195
+
196
+ Returns:
197
+ Asset: The Asset deserialized from the JSON dict.
198
+ """
199
+ d = copy(d)
200
+ href = d.pop("href")
201
+ media_type = d.pop("type", None)
202
+ title = d.pop("title", None)
203
+ description = d.pop("description", None)
204
+ roles = d.pop("roles", None)
205
+ properties = None
206
+ if any(d):
207
+ properties = d
208
+
209
+ return cls(
210
+ href=href,
211
+ media_type=media_type,
212
+ title=title,
213
+ description=description,
214
+ roles=roles,
215
+ extra_fields=properties,
216
+ )
217
+
218
+ def move(self, href: str) -> Asset:
219
+ """Moves this asset's file to a new location on the local filesystem,
220
+ setting the asset href accordingly.
221
+
222
+ Modifies the asset in place, and returns the same asset.
223
+
224
+ Args:
225
+ href: The new asset location. Must be a local path. If relative
226
+ it must be relative to the owner object.
227
+
228
+ Returns:
229
+ Asset: The asset with the updated href.
230
+ """
231
+ src = _absolute_href(self.href, self.owner, "move")
232
+ dst = _absolute_href(href, self.owner, "move")
233
+ shutil.move(src, dst)
234
+ self.href = href
235
+ return self
236
+
237
+ def copy(self, href: str) -> Asset:
238
+ """Copies this asset's file to a new location on the local filesystem,
239
+ setting the asset href accordingly.
240
+
241
+ Modifies the asset in place, and returns the same asset.
242
+
243
+ Args:
244
+ href: The new asset location. Must be a local path. If relative
245
+ it must be relative to the owner object.
246
+
247
+ Returns:
248
+ Asset: The asset with the updated href.
249
+ """
250
+ src = _absolute_href(self.href, self.owner, "copy")
251
+ dst = _absolute_href(href, self.owner, "copy")
252
+ shutil.copy2(src, dst)
253
+ self.href = href
254
+ return self
255
+
256
+ def delete(self) -> None:
257
+ """Delete this asset's file. Does not delete the asset from the item
258
+ that owns it. See :meth:`~pystac.Item.delete_asset` for that.
259
+
260
+ Does not modify the asset.
261
+ """
262
+ import os
263
+
264
+ href = _absolute_href(self.href, self.owner, "delete")
265
+ os.remove(href)
266
+
267
+ @property
268
+ def ext(self) -> AssetExt:
269
+ """Accessor for extension classes on this asset
270
+
271
+ Example::
272
+
273
+ asset.ext.proj.code = "EPSG:4326"
274
+ """
275
+ from pystac.extensions.ext import AssetExt
276
+
277
+ return AssetExt(stac_object=self)
278
+
279
+
280
+ class Assets(Protocol):
281
+ """Protocol, with functionality, for STAC objects that have assets."""
282
+
283
+ assets: dict[str, Asset]
284
+ """The asset dictionary."""
285
+
286
+ def get_assets(
287
+ self,
288
+ media_type: str | MediaType | None = None,
289
+ role: str | None = None,
290
+ ) -> dict[str, Asset]:
291
+ """Get this object's assets.
292
+
293
+ Args:
294
+ media_type: If set, filter the assets such that only those with a
295
+ matching ``media_type`` are returned.
296
+ role: If set, filter the assets such that only those with a matching
297
+ ``role`` are returned.
298
+
299
+ Returns:
300
+ Dict[str, Asset]: A dictionary of assets that match ``media_type``
301
+ and/or ``role`` if set or else all of this object's assets.
302
+ """
303
+ return {
304
+ k: deepcopy(v)
305
+ for k, v in self.assets.items()
306
+ if (media_type is None or v.media_type == media_type)
307
+ and (role is None or v.has_role(role))
308
+ }
309
+
310
+ def add_asset(self, key: str, asset: Asset) -> None:
311
+ """Adds an Asset to this object.
312
+
313
+ Args:
314
+ key : The unique key of this asset.
315
+ asset : The Asset to add.
316
+ """
317
+ asset.set_owner(self)
318
+ self.assets[key] = asset
319
+
320
+ def delete_asset(self, key: str) -> None:
321
+ """Deletes the asset at the given key, and removes the asset's data
322
+ file from the local filesystem.
323
+
324
+ It is an error to attempt to delete an asset's file if it is on a
325
+ remote filesystem.
326
+
327
+ To delete the asset without removing the file, use `del item.assets["key"]`.
328
+
329
+ Args:
330
+ key: The unique key of this asset.
331
+ """
332
+ asset = self.assets[key]
333
+ asset.set_owner(self)
334
+ asset.delete()
335
+
336
+ del self.assets[key]
337
+
338
+ def make_asset_hrefs_relative(self) -> Assets:
339
+ """Modify each asset's HREF to be relative to this object's self HREF.
340
+
341
+ Returns:
342
+ Item: self
343
+ """
344
+ self_href = self.get_self_href()
345
+ for asset in self.assets.values():
346
+ if is_absolute_href(asset.href, self_href):
347
+ if self_href is None:
348
+ raise STACError(
349
+ "Cannot make asset HREFs relative if no self_href is set."
350
+ )
351
+ asset.href = make_relative_href(asset.href, self_href)
352
+ return self
353
+
354
+ def make_asset_hrefs_absolute(self) -> Assets:
355
+ """Modify each asset's HREF to be absolute.
356
+
357
+ Any asset HREFs that are relative will be modified to absolute based on this
358
+ item's self HREF.
359
+
360
+ Returns:
361
+ Assets: self
362
+ """
363
+ self_href = self.get_self_href()
364
+ for asset in self.assets.values():
365
+ if not is_absolute_href(asset.href, self_href):
366
+ if self_href is None:
367
+ raise STACError(
368
+ "Cannot make relative asset HREFs absolute "
369
+ "if no self_href is set."
370
+ )
371
+ asset.href = make_absolute_href(asset.href, self_href)
372
+ return self
373
+
374
+ def get_self_href(self) -> str | None:
375
+ """Abstract definition of STACObject.get_self_href.
376
+
377
+ Needed to make the `make_asset_hrefs_{absolute|relative}` methods pass
378
+ type checking. Refactoring out all the link behavior in STACObject to
379
+ its own protocol would be too heavy, so we just use this stub instead.
380
+ """
381
+ ...
382
+
383
+
384
+ def _absolute_href(href: str, owner: Assets | None, action: str = "access") -> str:
385
+ item_self = owner.get_self_href() if owner else None
386
+ if utils.is_absolute_href(href, item_self):
387
+ return href
388
+ else:
389
+ if item_self is None:
390
+ raise ValueError(
391
+ f"Cannot {action} file if asset href ('{href}') is relative "
392
+ "and owner item is not set. Hint: try using "
393
+ ":func:`~pystac.Item.make_asset_hrefs_absolute`"
394
+ )
395
+ return utils.make_absolute_href(href, item_self)