pystac-ext-file 2.1.0rc0__tar.gz

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 (26) hide show
  1. pystac_ext_file-2.1.0rc0/.gitignore +163 -0
  2. pystac_ext_file-2.1.0rc0/PKG-INFO +37 -0
  3. pystac_ext_file-2.1.0rc0/README.md +13 -0
  4. pystac_ext_file-2.1.0rc0/pyproject.toml +36 -0
  5. pystac_ext_file-2.1.0rc0/pystac/extensions/file.py +385 -0
  6. pystac_ext_file-2.1.0rc0/pystac/extensions/py.typed +0 -0
  7. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_migrate_from_v1_0_0.yaml +48 -0
  8. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_migrate_from_v2_0_0.yaml +47 -0
  9. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_asset[calibrations-local_path-different-file.xml].yaml +74 -0
  10. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_asset[measurement-header_size-8192].yaml +74 -0
  11. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_asset[thumbnail-byte_order-little-endian].yaml +74 -0
  12. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_asset[thumbnail-checksum-90e40210163700a8a6501eccd00b6d3b44ddaed0].yaml +74 -0
  13. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_asset[thumbnail-size-1].yaml +74 -0
  14. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_link[about-byte_order-big-endian].yaml +74 -0
  15. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_link[about-checksum-90e40210163700a8a6501eccd00b6d3b44ddaedb].yaml +74 -0
  16. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_link[about-header_size-4092].yaml +74 -0
  17. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_link[about-local_path-a-path].yaml +74 -0
  18. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_set_field_on_link[about-size-129302].yaml +74 -0
  19. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_validate_catalog.yaml +74 -0
  20. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_validate_collection.yaml +74 -0
  21. pystac_ext_file-2.1.0rc0/tests/cassettes/test_file/test_validate_item.yaml +74 -0
  22. pystac_ext_file-2.1.0rc0/tests/data-files/catalog.json +29 -0
  23. pystac_ext_file-2.1.0rc0/tests/data-files/collection.json +50 -0
  24. pystac_ext_file-2.1.0rc0/tests/data-files/examples/1.0.0-beta.2/extensions/checksum/examples/sentinel1.json +95 -0
  25. pystac_ext_file-2.1.0rc0/tests/data-files/item.json +93 -0
  26. pystac_ext_file-2.1.0rc0/tests/test_file.py +324 -0
@@ -0,0 +1,163 @@
1
+ *.pyc
2
+ *.egg-info
3
+ *.eggs
4
+ .DS_Store
5
+ data
6
+ config.json
7
+ stdout*
8
+ /integration*
9
+ .idea
10
+ .vscode
11
+ .actrc
12
+
13
+
14
+ # Sphinx documentation
15
+ .ipynb_checkpoints/
16
+
17
+ docs/tutorials/pystac-example*
18
+ docs/tutorials/spacenet-stac/
19
+ docs/tutorials/spacenet-cog-stac/
20
+ docs/tutorials/data/
21
+ docs/quickstart_stac/
22
+
23
+ # Byte-compiled / optimized / DLL files
24
+ __pycache__/
25
+ *.py[cod]
26
+ *$py.class
27
+
28
+ # C extensions
29
+ *.so
30
+
31
+ # Distribution / packaging
32
+ .Python
33
+ build/
34
+ develop-eggs/
35
+ dist/
36
+ downloads/
37
+ eggs/
38
+ .eggs/
39
+ lib/
40
+ lib64/
41
+ parts/
42
+ sdist/
43
+ var/
44
+ wheels/
45
+ share/python-wheels/
46
+ *.egg-info/
47
+ .installed.cfg
48
+ *.egg
49
+ MANIFEST
50
+
51
+ # PyInstaller
52
+ # Usually these files are written by a python script from a template
53
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
54
+ *.manifest
55
+ *.spec
56
+
57
+ # Installer logs
58
+ pip-log.txt
59
+ pip-delete-this-directory.txt
60
+
61
+ # Unit test / coverage reports
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage.xml
70
+ *.cover
71
+ *.py,cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ cover/
75
+
76
+ # Translations
77
+ *.mo
78
+ *.pot
79
+
80
+ # Django stuff:
81
+ *.log
82
+ local_settings.py
83
+ db.sqlite3
84
+ db.sqlite3-journal
85
+
86
+ # Flask stuff:
87
+ instance/
88
+ .webassets-cache
89
+
90
+ # Scrapy stuff:
91
+ .scrapy
92
+
93
+ # Sphinx documentation
94
+ docs/_build/
95
+
96
+ # PyBuilder
97
+ .pybuilder/
98
+ target/
99
+
100
+ # Jupyter Notebook
101
+ .ipynb_checkpoints
102
+
103
+ # IPython
104
+ profile_default/
105
+ ipython_config.py
106
+
107
+ # pyenv
108
+ # For a library or package, you might want to ignore these files since the code is
109
+ # intended to run in multiple environments; otherwise, check them in:
110
+ # .python-version
111
+
112
+ # pipenv
113
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
114
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
115
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
116
+ # install all needed dependencies.
117
+ # Pipfile.lock
118
+
119
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
120
+ __pypackages__/
121
+
122
+ # Celery stuff
123
+ celerybeat-schedule
124
+ celerybeat.pid
125
+
126
+ # SageMath parsed files
127
+ *.sage.py
128
+
129
+ # Environments
130
+ .env
131
+ .venv
132
+ env/
133
+ venv/
134
+ ENV/
135
+ env.bak/
136
+ venv.bak/
137
+
138
+ # Spyder project settings
139
+ .spyderproject
140
+ .spyproject
141
+
142
+ # Rope project settings
143
+ .ropeproject
144
+
145
+ # mkdocs documentation
146
+ /site
147
+
148
+ # mypy
149
+ .mypy_cache/
150
+ .dmypy.json
151
+ dmypy.json
152
+
153
+ # Pyre type checker
154
+ .pyre/
155
+
156
+ # pytype static type analyzer
157
+ .pytype/
158
+
159
+ # Cython debug symbols
160
+ cython_debug/
161
+
162
+ # asv environments
163
+ .asv
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystac-ext-file
3
+ Version: 2.1.0rc0
4
+ Summary: File extension for PySTAC
5
+ Project-URL: Documentation, https://pystac.readthedocs.io
6
+ Project-URL: Repository, https://github.com/stac-utils/pystac
7
+ Project-URL: Issues, https://github.com/stac-utils/pystac/issues
8
+ Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
9
+ Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
10
+ License: Apache-2.0
11
+ Keywords: STAC,catalog,file,imagery,pystac,raster
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pystac-core
23
+ Description-Content-Type: text/markdown
24
+
25
+ # pystac-ext-file
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [File Info Extension](https://github.com/stac-extensions/file).
28
+ This extension provides fields for describing file details such as byte order, checksum, header size, and data type for assets.
29
+
30
+ ## Supported versions
31
+
32
+ - [v2.1.0](https://stac-extensions.github.io/file/v2.1.0/schema.json)
33
+
34
+ ## Versioning
35
+
36
+ This package's version corresponds to the version of the extension specification it targets.
37
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `2.1.0.post1`.
@@ -0,0 +1,13 @@
1
+ # pystac-ext-file
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [File Info Extension](https://github.com/stac-extensions/file).
4
+ This extension provides fields for describing file details such as byte order, checksum, header size, and data type for assets.
5
+
6
+ ## Supported versions
7
+
8
+ - [v2.1.0](https://stac-extensions.github.io/file/v2.1.0/schema.json)
9
+
10
+ ## Versioning
11
+
12
+ This package's version corresponds to the version of the extension specification it targets.
13
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `2.1.0.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-file"
3
+ description = "File extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "2.1.0-rc.0"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "file"]
9
+ license = { text = "Apache-2.0" }
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: Apache Software License",
14
+ "Natural Language :: English",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.10"
22
+ dependencies = ["pystac-core"]
23
+
24
+ [project.urls]
25
+ Documentation = "https://pystac.readthedocs.io"
26
+ Repository = "https://github.com/stac-utils/pystac"
27
+ Issues = "https://github.com/stac-utils/pystac/issues"
28
+ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md"
29
+ Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["pystac"]
@@ -0,0 +1,385 @@
1
+ """Implements the :stac-ext:`File Info Extension <file>`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import Any, Generic, Literal, TypeVar, cast
7
+
8
+ from pystac import (
9
+ Asset,
10
+ Catalog,
11
+ Collection,
12
+ ExtensionTypeError,
13
+ Item,
14
+ Link,
15
+ STACObjectType,
16
+ )
17
+ from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
18
+ from pystac.extensions.hooks import ExtensionHooks
19
+ from pystac.serialization.identify import (
20
+ OldExtensionShortIDs,
21
+ STACJSONDescription,
22
+ STACVersionID,
23
+ )
24
+ from pystac.utils import StringEnum, get_required, map_opt
25
+
26
+ #: Generalized version of :class:`~pystac.Asset`, :class:`~pystac.Link`,
27
+ T = TypeVar("T", Asset, Link)
28
+
29
+ SCHEMA_URI = "https://stac-extensions.github.io/file/v2.1.0/schema.json"
30
+
31
+ PREFIX = "file:"
32
+ BYTE_ORDER_PROP = PREFIX + "byte_order"
33
+ CHECKSUM_PROP = PREFIX + "checksum"
34
+ HEADER_SIZE_PROP = PREFIX + "header_size"
35
+ SIZE_PROP = PREFIX + "size"
36
+ VALUES_PROP = PREFIX + "values"
37
+ LOCAL_PATH_PROP = PREFIX + "local_path"
38
+
39
+
40
+ class ByteOrder(StringEnum):
41
+ """List of allows values for the ``"file:byte_order"`` field defined by the
42
+ :stac-ext:`File Info Extension <file>`."""
43
+
44
+ LITTLE_ENDIAN = "little-endian"
45
+ BIG_ENDIAN = "big-endian"
46
+
47
+
48
+ class MappingObject:
49
+ """Represents a value map used by assets that are used as classification layers, and
50
+ give details about the values in the asset and their meanings."""
51
+
52
+ properties: dict[str, Any]
53
+
54
+ def __init__(self, properties: dict[str, Any]) -> None:
55
+ self.properties = properties
56
+
57
+ def apply(self, values: list[Any], summary: str) -> None:
58
+ """Sets the properties for this :class:`~MappingObject` instance.
59
+
60
+ Args:
61
+ values : The value(s) in the file. At least one array element is required.
62
+ summary : A short description of the value(s).
63
+ """
64
+ self.values = values
65
+ self.summary = summary
66
+
67
+ @classmethod
68
+ def create(cls, values: list[Any], summary: str) -> MappingObject:
69
+ """Creates a new :class:`~MappingObject` instance.
70
+
71
+ Args:
72
+ values : The value(s) in the file. At least one array element is required.
73
+ summary : A short description of the value(s).
74
+ """
75
+ m = cls({})
76
+ m.apply(values=values, summary=summary)
77
+ return m
78
+
79
+ @property
80
+ def values(self) -> list[Any]:
81
+ """Gets or sets the list of value(s) in the file. At least one array element is
82
+ required."""
83
+ return get_required(self.properties.get("values"), self, "values")
84
+
85
+ @values.setter
86
+ def values(self, v: list[Any]) -> None:
87
+ self.properties["values"] = v
88
+
89
+ @property
90
+ def summary(self) -> str:
91
+ """Gets or sets the short description of the value(s)."""
92
+ return cast(str, get_required(self.properties.get("summary"), self, "summary"))
93
+
94
+ @summary.setter
95
+ def summary(self, v: str) -> None:
96
+ self.properties["summary"] = v
97
+
98
+ @classmethod
99
+ def from_dict(cls, d: dict[str, Any]) -> MappingObject:
100
+ return cls.create(**d)
101
+
102
+ def to_dict(self) -> dict[str, Any]:
103
+ return self.properties
104
+
105
+
106
+ class FileExtension(
107
+ Generic[T],
108
+ PropertiesExtension,
109
+ ExtensionManagementMixin[Catalog | Collection | Item],
110
+ ):
111
+ """A class that can be used to extend the properties of an :class:`~pystac.Asset`
112
+ or :class:`~pystac.Link` with properties from the
113
+ :stac-ext:`File Info Extension <file>`.
114
+
115
+ To create an instance of :class:`FileExtension`, use the
116
+ :meth:`FileExtension.ext` method. For example:
117
+
118
+ .. code-block:: python
119
+
120
+ >>> asset: pystac.Asset = ...
121
+ >>> file_ext = FileExtension.ext(asset)
122
+ """
123
+
124
+ name: Literal["file"] = "file"
125
+
126
+ def apply(
127
+ self,
128
+ byte_order: ByteOrder | None = None,
129
+ checksum: str | None = None,
130
+ header_size: int | None = None,
131
+ size: int | None = None,
132
+ values: list[MappingObject] | None = None,
133
+ local_path: str | None = None,
134
+ ) -> None:
135
+ """Applies file extension properties to the extended Item.
136
+
137
+ Args:
138
+ byte_order : Optional byte order of integer values in the file. One of
139
+ ``"big-endian"`` or ``"little-endian"``.
140
+ checksum : Optional multihash for the corresponding file,
141
+ encoded as hexadecimal (base 16) string with lowercase letters.
142
+ header_size : Optional header size of the file, in bytes.
143
+ size : Optional size of the file, in bytes.
144
+ values : Optional list of :class:`~MappingObject` instances that lists the
145
+ values that are in the file and describe their meaning. See the
146
+ :stac-ext:`Mapping Object <file#mapping-object>` docs for an example.
147
+ If given, at least one array element is required.
148
+ """
149
+ self.byte_order = byte_order
150
+ self.checksum = checksum
151
+ self.header_size = header_size
152
+ self.size = size
153
+ self.values = values
154
+ self.local_path = local_path
155
+
156
+ @property
157
+ def byte_order(self) -> ByteOrder | None:
158
+ """Gets or sets the byte order of integer values in the file. One of big-endian
159
+ or little-endian."""
160
+ return self._get_property(BYTE_ORDER_PROP, ByteOrder)
161
+
162
+ @byte_order.setter
163
+ def byte_order(self, v: ByteOrder | None) -> None:
164
+ self._set_property(BYTE_ORDER_PROP, v)
165
+
166
+ @property
167
+ def checksum(self) -> str | None:
168
+ """Get or sets the multihash for the corresponding file, encoded as hexadecimal
169
+ (base 16) string with lowercase letters."""
170
+ return self._get_property(CHECKSUM_PROP, str)
171
+
172
+ @checksum.setter
173
+ def checksum(self, v: str | None) -> None:
174
+ self._set_property(CHECKSUM_PROP, v)
175
+
176
+ @property
177
+ def header_size(self) -> int | None:
178
+ """Get or sets the header size of the file, in bytes."""
179
+ return self._get_property(HEADER_SIZE_PROP, int)
180
+
181
+ @header_size.setter
182
+ def header_size(self, v: int | None) -> None:
183
+ self._set_property(HEADER_SIZE_PROP, v)
184
+
185
+ @property
186
+ def local_path(self) -> str | None:
187
+ """Get or sets a relative local path for the asset/link.
188
+
189
+ The ``file:local_path`` field indicates a **relative** path that
190
+ can be used by clients for different purposes to organize the
191
+ files locally. For compatibility reasons the name-separator
192
+ character in paths **must** be ``/`` and the Windows separator ``\\``
193
+ is **not** allowed.
194
+ """
195
+ return self._get_property(LOCAL_PATH_PROP, str)
196
+
197
+ @local_path.setter
198
+ def local_path(self, v: str | None) -> None:
199
+ self._set_property(LOCAL_PATH_PROP, v, pop_if_none=True)
200
+
201
+ @property
202
+ def size(self) -> int | None:
203
+ """Get or sets the size of the file, in bytes."""
204
+ return self._get_property(SIZE_PROP, int)
205
+
206
+ @size.setter
207
+ def size(self, v: int | None) -> None:
208
+ self._set_property(SIZE_PROP, v)
209
+
210
+ @property
211
+ def values(self) -> list[MappingObject] | None:
212
+ """Get or sets the list of :class:`~MappingObject` instances that lists the
213
+ values that are in the file and describe their meaning. See the
214
+ :stac-ext:`Mapping Object <file#mapping-object>` docs for an example. If given,
215
+ at least one array element is required."""
216
+ return map_opt(
217
+ lambda values: [
218
+ MappingObject.from_dict(mapping_obj) for mapping_obj in values
219
+ ],
220
+ self._get_property(VALUES_PROP, list[dict[str, Any]]),
221
+ )
222
+
223
+ @values.setter
224
+ def values(self, v: list[MappingObject] | None) -> None:
225
+ self._set_property(
226
+ VALUES_PROP,
227
+ map_opt(
228
+ lambda values: [mapping_obj.to_dict() for mapping_obj in values], v
229
+ ),
230
+ )
231
+
232
+ @classmethod
233
+ def get_schema_uri(cls) -> str:
234
+ return SCHEMA_URI
235
+
236
+ @classmethod
237
+ def ext(cls, obj: Asset | Link, add_if_missing: bool = False) -> FileExtension[T]:
238
+ """Extends the given STAC Object with properties from the :stac-ext:`File Info
239
+ Extension <file>`.
240
+
241
+ This extension can be applied to instances of :class:`~pystac.Asset` or
242
+ :class:`~pystac.Link`
243
+ """
244
+ if isinstance(obj, Asset):
245
+ cls.ensure_owner_has_extension(obj, add_if_missing)
246
+ return cast(FileExtension[T], AssetFileExtension(obj))
247
+ elif isinstance(obj, Link):
248
+ cls.ensure_owner_has_extension(obj, add_if_missing)
249
+ return cast(FileExtension[T], LinkFileExtension(obj))
250
+ else:
251
+ raise ExtensionTypeError(cls._ext_error_message(obj))
252
+
253
+
254
+ class AssetFileExtension(FileExtension[Asset]):
255
+ """A concrete implementation of :class:`FileExtension` on an
256
+ :class:`~pystac.Asset` that extends the Asset fields to include properties defined
257
+ in the :stac-ext:`File Info Extension <file>`.
258
+
259
+ This class should generally not be instantiated directly. Instead, call
260
+ :meth:`FileExtension.ext` on an :class:`~pystac.Asset` to extend it.
261
+ """
262
+
263
+ asset_href: str
264
+ """The ``href`` value of the :class:`~pystac.Asset` being extended."""
265
+
266
+ properties: dict[str, Any]
267
+ """The :class:`~pystac.Asset` fields, including extension properties."""
268
+
269
+ additional_read_properties: Iterable[dict[str, Any]] | None = None
270
+ """If present, this will be a list containing 1 dictionary representing the
271
+ properties of the owner."""
272
+
273
+ def __init__(self, asset: Asset):
274
+ self.asset_href = asset.href
275
+ self.properties = asset.extra_fields
276
+ if asset.owner and hasattr(asset.owner, "properties"):
277
+ self.additional_read_properties = [asset.owner.properties]
278
+
279
+ def __repr__(self) -> str:
280
+ return f"<AssetFileExtension Asset href={self.asset_href}>"
281
+
282
+
283
+ class LinkFileExtension(FileExtension[Link]):
284
+ """A concrete implementation of :class:`FileExtension` on an
285
+ :class:`~pystac.Link` that extends the Link fields to include properties defined
286
+ in the :stac-ext:`File Info Extension <file>`.
287
+
288
+ This class should generally not be instantiated directly. Instead, call
289
+ :meth:`FileExtension.ext` on an :class:`~pystac.Link` to extend it.
290
+ """
291
+
292
+ link_href: str
293
+ """The ``href`` value of the :class:`~pystac.Link` being extended."""
294
+
295
+ properties: dict[str, Any]
296
+ """The :class:`~pystac.Link` fields, including extension properties."""
297
+
298
+ additional_read_properties: Iterable[dict[str, Any]] | None = None
299
+ """If present, this will be a list containing 1 dictionary representing the
300
+ properties of the owner."""
301
+
302
+ def __init__(self, link: Link):
303
+ self.link_href = link.href
304
+ self.properties = link.extra_fields
305
+ if link.owner and hasattr(link.owner, "properties"):
306
+ self.additional_read_properties = [link.owner.properties]
307
+
308
+ def __repr__(self) -> str:
309
+ return f"<LinkFileExtension Link href={self.link_href}>"
310
+
311
+
312
+ class FileExtensionHooks(ExtensionHooks):
313
+ schema_uri: str = SCHEMA_URI
314
+ prev_extension_ids = {
315
+ "file",
316
+ "https://stac-extensions.github.io/file/v1.0.0/schema.json",
317
+ "https://stac-extensions.github.io/file/v2.0.0/schema.json",
318
+ }
319
+ stac_object_types = {
320
+ STACObjectType.ITEM,
321
+ STACObjectType.COLLECTION,
322
+ STACObjectType.CATALOG,
323
+ }
324
+ removed_fields = {
325
+ "file:bits_per_sample",
326
+ "file:data_type",
327
+ "file:nodata",
328
+ "file:unit",
329
+ }
330
+
331
+ def migrate(
332
+ self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription
333
+ ) -> None:
334
+ # The checksum field was previously its own extension.
335
+ old_checksum: dict[str, str] | None = None
336
+ if info.version_range.latest_valid_version() < "v1.0.0-rc.2":
337
+ if OldExtensionShortIDs.CHECKSUM.value in info.extensions:
338
+ old_item_checksum = obj["properties"].get("checksum:multihash")
339
+ if old_item_checksum is not None:
340
+ if old_checksum is None:
341
+ old_checksum = {}
342
+ old_checksum["__item__"] = old_item_checksum
343
+ for asset_key, asset in obj["assets"].items():
344
+ old_asset_checksum = asset.get("checksum:multihash")
345
+ if old_asset_checksum is not None:
346
+ if old_checksum is None:
347
+ old_checksum = {}
348
+ old_checksum[asset_key] = old_asset_checksum
349
+
350
+ try:
351
+ obj["stac_extensions"].remove(OldExtensionShortIDs.CHECKSUM.value)
352
+ except ValueError:
353
+ pass
354
+
355
+ super().migrate(obj, version, info)
356
+
357
+ if old_checksum is not None:
358
+ if SCHEMA_URI not in obj["stac_extensions"]:
359
+ obj["stac_extensions"].append(SCHEMA_URI)
360
+ for key in old_checksum:
361
+ if key == "__item__":
362
+ obj["properties"][CHECKSUM_PROP] = old_checksum[key]
363
+ else:
364
+ obj["assets"][key][CHECKSUM_PROP] = old_checksum[key]
365
+
366
+ found_fields = {}
367
+ for asset_key, asset in obj.get("assets", {}).items():
368
+ if values := set(asset.keys()).intersection(self.removed_fields):
369
+ found_fields[asset_key] = values
370
+
371
+ if found_fields:
372
+ import warnings
373
+
374
+ warnings.warn(
375
+ f"Assets {list(found_fields.keys())} contain fields: "
376
+ f"{list(set.union(*found_fields.values()))} which "
377
+ "were removed from the file extension spec in v2.0.0. Please "
378
+ "consult the release notes "
379
+ "(https://github.com/stac-extensions/file/releases/tag/v2.0.0) "
380
+ "for instructions on how to migrate these fields.",
381
+ UserWarning,
382
+ )
383
+
384
+
385
+ FILE_EXTENSION_HOOKS: ExtensionHooks = FileExtensionHooks()
File without changes
@@ -0,0 +1,48 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://raw.githubusercontent.com/stac-extensions/file/v1.0.0/examples/item.json
7
+ response:
8
+ body:
9
+ string: "{\n \"id\": \"S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616\",\n
10
+ \ \"type\": \"Feature\",\n \"stac_version\": \"1.0.0-beta.2\",\n \"stac_extensions\":
11
+ [\n \"https://stac-extensions.github.io/file/v1.0.0/schema.json\"\n ],\n
12
+ \ \"bbox\": [\n -70.275032,\n -64.72924,\n -65.087479,\n -51.105831\n
13
+ \ ],\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\":
14
+ [\n [\n [\n -67.071648,\n -64.72924\n ],\n
15
+ \ [\n -65.087479,\n -56.674374\n ],\n [\n
16
+ \ -68.033211,\n -51.105831\n ],\n [\n -70.275032,\n
17
+ \ -59.805672\n ],\n [\n -67.071648,\n -64.72924\n
18
+ \ ]\n ]\n ]\n },\n \"properties\": {\n \"datetime\": \"2018-11-03T23:58:55Z\"\n
19
+ \ },\n \"assets\": {\n \"noises\": {\n \"href\": \"./annotation/calibration/noise-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml\",\n
20
+ \ \"title\": \"Calibration Schema\",\n \"type\": \"text/xml\",\n
21
+ \ \"file:checksum\": \"90e40210a30d1711e81a4b11ef67b28744321659\"\n },\n
22
+ \ \"calibrations\": {\n \"href\": \"./annotation/calibration/calibration-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml\",\n
23
+ \ \"title\": \"Noise Schema\",\n \"type\": \"text/xml\",\n \"file:checksum\":
24
+ \"90e402104fc5351af67db0b8f1746efe421a05e4\"\n },\n \"products\": {\n
25
+ \ \"href\": \"./annotation/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml\",\n
26
+ \ \"title\": \"Product Schema\",\n \"type\": \"text/xml\",\n \"file:checksum\":
27
+ \"90e402107a7f2588a85362b9beea2a12d4514d45\"\n },\n \"measurement\":
28
+ {\n \"href\": \"./measurement/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.tiff\",\n
29
+ \ \"title\": \"Measurements\",\n \"type\": \"image/tiff\",\n \"sar:polarizations\":
30
+ [\n \"HH\"\n ],\n \"file:byte_order\": \"little-endian\",\n
31
+ \ \"file:data_type\": \"uint16\",\n \"file:size\": 209715200,\n \"file:header_size\":
32
+ 4096,\n \"file:checksum\": \"90e40210163700a8a6501eccd00b6d3b44ddaed0\"\n
33
+ \ },\n \"thumbnail\": {\n \"href\": \"./preview/quick-look.png\",\n
34
+ \ \"title\": \"Thumbnail\",\n \"type\": \"image/png\",\n \"file:byte_order\":
35
+ \"big-endian\",\n \"file:data_type\": \"uint8\",\n \"file:size\":
36
+ 146484,\n \"file:checksum\": \"90e40210f52acd32b09769d3b1871b420789456c\"\n
37
+ \ }\n },\n \"links\": [\n {\n \"rel\": \"self\",\n \"href\":
38
+ \"https://example.com/collections/sentinel-1/items/S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616\"\n
39
+ \ },\n {\n \"rel\": \"parent\",\n \"href\": \"https://example.com/collections/sentinel-1\",\n
40
+ \ \"file:checksum\": \"11146d97123fd2c02dec9a1b6d3b13136dbe600cf966\"\n
41
+ \ },\n {\n \"rel\": \"root\",\n \"href\": \"https://example.com/collections\",\n
42
+ \ \"file:checksum\": \"1114fa4b9d69fdddc7c1be7bed9440621400b383b43f\"\n
43
+ \ }\n ]\n}"
44
+ headers: {}
45
+ status:
46
+ code: 200
47
+ message: OK
48
+ version: 1