pystac-ext-table 1.2.0__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.
@@ -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-table
3
+ Version: 1.2.0
4
+ Summary: Table 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,imagery,pystac,raster,table
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-table
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Table Extension](https://github.com/stac-extensions/table).
28
+ This extension provides fields for describing tabular data assets, including column definitions, row counts, primary/foreign keys, and storage formats.
29
+
30
+ ## Supported versions
31
+
32
+ - [v1.2.0](https://stac-extensions.github.io/table/v1.2.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. `1.2.0.post1`.
@@ -0,0 +1,13 @@
1
+ # pystac-ext-table
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Table Extension](https://github.com/stac-extensions/table).
4
+ This extension provides fields for describing tabular data assets, including column definitions, row counts, primary/foreign keys, and storage formats.
5
+
6
+ ## Supported versions
7
+
8
+ - [v1.2.0](https://stac-extensions.github.io/table/v1.2.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. `1.2.0.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-table"
3
+ description = "Table extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "1.2.0"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "table"]
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"]
File without changes
@@ -0,0 +1,324 @@
1
+ """Implements the :stac-ext:`Table Extension <table>`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import Any, Generic, Literal, TypeVar, cast
7
+
8
+ import pystac
9
+ from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
10
+ from pystac.extensions.hooks import ExtensionHooks
11
+ from pystac.utils import get_required
12
+
13
+ #: Generalized version of :class:`~pystac.Collection`, :class:`~pystac.Item`,
14
+ #: :class:`~pystac.Asset` or :class:`~pystac.ItemAssetDefinition`
15
+ T = TypeVar(
16
+ "T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
17
+ )
18
+
19
+ SCHEMA_URI = "https://stac-extensions.github.io/table/v1.2.0/schema.json"
20
+
21
+ PREFIX: str = "table:"
22
+ COLUMNS_PROP = PREFIX + "columns"
23
+ PRIMARY_GEOMETRY_PROP = PREFIX + "primary_geometry"
24
+ ROW_COUNT_PROP = PREFIX + "row_count"
25
+ STORAGE_OPTIONS_PROP = PREFIX + "storage_options"
26
+ TABLES_PROP = PREFIX + "tables"
27
+
28
+ # Column properties
29
+ COL_NAME_PROP = "name"
30
+ COL_DESCRIPTION_PROP = "description"
31
+ COL_TYPE_PROP = "type"
32
+
33
+ # Table properties
34
+ TBL_NAME_PROP = "name"
35
+ TBL_DESCRIPTION_PROP = "description"
36
+
37
+
38
+ class Column:
39
+ """Object representing a column of a table."""
40
+
41
+ properties: dict[str, Any]
42
+
43
+ def __init__(self, properties: dict[str, Any]):
44
+ self.properties = properties
45
+
46
+ @property
47
+ def name(self) -> str:
48
+ """The column name"""
49
+ return cast(
50
+ str,
51
+ get_required(
52
+ self.properties.get(COL_NAME_PROP), "table:column", COL_NAME_PROP
53
+ ),
54
+ )
55
+
56
+ @name.setter
57
+ def name(self, v: str) -> None:
58
+ self.properties[COL_NAME_PROP] = v
59
+
60
+ @property
61
+ def description(self) -> str | None:
62
+ """Detailed multi-line description to explain the column. `CommonMark 0.29
63
+ <http://commonmark.org/>`__ syntax MAY be used for rich text representation."""
64
+ return self.properties.get(COL_DESCRIPTION_PROP)
65
+
66
+ @description.setter
67
+ def description(self, v: str | None) -> None:
68
+ if v is None:
69
+ self.properties.pop(COL_DESCRIPTION_PROP, None)
70
+ else:
71
+ self.properties[COL_DESCRIPTION_PROP] = v
72
+
73
+ @property
74
+ def col_type(self) -> str | None:
75
+ """Data type of the column. If using a file format with a type system (like
76
+ Parquet), we recommend you use those types"""
77
+ return self.properties.get(COL_TYPE_PROP)
78
+
79
+ @col_type.setter
80
+ def col_type(self, v: str | None) -> None:
81
+ if v is None:
82
+ self.properties.pop(COL_TYPE_PROP, None)
83
+ else:
84
+ self.properties[COL_TYPE_PROP] = v
85
+
86
+ def to_dict(self) -> dict[str, Any]:
87
+ """Returns a dictionary representing this ``Column``."""
88
+ return self.properties
89
+
90
+
91
+ class Table:
92
+ """Object containing a high-level summary about a table"""
93
+
94
+ properties: dict[str, Any]
95
+
96
+ def __init__(self, properties: dict[str, Any]):
97
+ self.properties = properties
98
+
99
+ @property
100
+ def name(self) -> str:
101
+ """The table name"""
102
+ return cast(
103
+ str, get_required(self.properties.get(TBL_NAME_PROP), self, TBL_NAME_PROP)
104
+ )
105
+
106
+ @name.setter
107
+ def name(self, v: str) -> None:
108
+ self.properties[COL_NAME_PROP] = v
109
+
110
+ @property
111
+ def description(self) -> str | None:
112
+ """Detailed multi-line description to explain the table. `CommonMark 0.29
113
+ <http://commonmark.org/>`__ syntax MAY be used for rich text representation."""
114
+ return self.properties.get(COL_DESCRIPTION_PROP)
115
+
116
+ @description.setter
117
+ def description(self, v: str | None) -> None:
118
+ if v is None:
119
+ self.properties.pop(COL_DESCRIPTION_PROP, None)
120
+ else:
121
+ self.properties[COL_DESCRIPTION_PROP] = v
122
+
123
+ def to_dict(self) -> dict[str, Any]:
124
+ """Returns a dictionary representing this ``Table``."""
125
+ return self.properties
126
+
127
+
128
+ class TableExtension(
129
+ Generic[T],
130
+ PropertiesExtension,
131
+ ExtensionManagementMixin[pystac.Item | pystac.Collection],
132
+ ):
133
+ """An abstract class that can be used to extend the properties of a
134
+ :class:`~pystac.Collection`, :class:`~pystac.Item`, or :class:`~pystac.Asset` with
135
+ properties from the :stac-ext:`Datacube Extension <datacube>`. This class is
136
+ generic over the type of STAC Object to be extended (e.g. :class:`~pystac.Item`,
137
+ :class:`~pystac.Asset`).
138
+
139
+ To create a concrete instance of :class:`TableExtension`, use the
140
+ :meth:`TableExtension.ext` method. For example:
141
+
142
+ .. code-block:: python
143
+
144
+ >>> item: pystac.Item = ...
145
+ >>> tbl_ext = TableExtension.ext(item)
146
+
147
+ """
148
+
149
+ name: Literal["table"] = "table"
150
+
151
+ @classmethod
152
+ def get_schema_uri(cls) -> str:
153
+ return SCHEMA_URI
154
+
155
+ @classmethod
156
+ def ext(cls, obj: T, add_if_missing: bool = False) -> TableExtension[T]:
157
+ """Extend the given STAC Object with properties from the
158
+ :stac-ext:`Table Extension <table>`.
159
+
160
+ This extension can be applied to instances of :class:`~pystac.Collection`,
161
+ :class:`~pystac.Item` or :class:`~pystac.Asset`.
162
+
163
+ Raises:
164
+ pystac.ExtensionTypeError : If an invalid object type is passed.
165
+ """
166
+ if isinstance(obj, pystac.Collection):
167
+ cls.ensure_has_extension(obj, add_if_missing)
168
+ return cast(TableExtension[T], CollectionTableExtension(obj))
169
+ if isinstance(obj, pystac.Item):
170
+ cls.ensure_has_extension(obj, add_if_missing)
171
+ return cast(TableExtension[T], ItemTableExtension(obj))
172
+ if isinstance(obj, pystac.Asset):
173
+ cls.ensure_owner_has_extension(obj, add_if_missing)
174
+ return cast(TableExtension[T], AssetTableExtension(obj))
175
+ elif isinstance(obj, pystac.ItemAssetDefinition):
176
+ cls.ensure_owner_has_extension(obj, add_if_missing)
177
+ return cast(TableExtension[T], ItemAssetsTableExtension(obj))
178
+ else:
179
+ raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
180
+
181
+ @property
182
+ def columns(self) -> list[Column] | None:
183
+ """A list of :class:`Column` objects describing each column"""
184
+ v = self.properties.get(COLUMNS_PROP)
185
+ if v is None:
186
+ return None
187
+ return [Column(x) for x in v]
188
+
189
+ @columns.setter
190
+ def columns(self, v: list[Column] | None) -> None:
191
+ self._set_property(COLUMNS_PROP, v)
192
+
193
+ @property
194
+ def primary_geometry(self) -> str | None:
195
+ """The primary geometry column name"""
196
+ return self._get_property(PRIMARY_GEOMETRY_PROP, str)
197
+
198
+ @primary_geometry.setter
199
+ def primary_geometry(self, v: str | None) -> None:
200
+ if v is None:
201
+ self.properties.pop(PRIMARY_GEOMETRY_PROP, None)
202
+ else:
203
+ self.properties[PRIMARY_GEOMETRY_PROP] = v
204
+
205
+ @property
206
+ def row_count(self) -> int | None:
207
+ """The number of rows in the dataset"""
208
+ return self._get_property(ROW_COUNT_PROP, int)
209
+
210
+ @row_count.setter
211
+ def row_count(self, v: int | None) -> None:
212
+ if v is None:
213
+ self.properties.pop(ROW_COUNT_PROP, None)
214
+ else:
215
+ self.properties[ROW_COUNT_PROP] = v
216
+
217
+
218
+ class CollectionTableExtension(TableExtension[pystac.Collection]):
219
+ """A concrete implementation of :class:`TableExtension` on a
220
+ :class:`~pystac.Collection` that extends the properties of the Item to include
221
+ properties defined in the :stac-ext:`Table Extension <table>`.
222
+
223
+ This class should generally not be instantiated directly. Instead, call
224
+ :meth:`TableExtension.ext` on an :class:`~pystac.Collection` to extend it.
225
+ """
226
+
227
+ collection: pystac.Collection
228
+ properties: dict[str, Any]
229
+
230
+ def __init__(self, collection: pystac.Collection):
231
+ self.collection = collection
232
+ self.properties = collection.extra_fields
233
+
234
+ @property
235
+ def tables(self) -> dict[str, Table]:
236
+ """A mapping of table names to table objects"""
237
+ return get_required(self.properties.get(TABLES_PROP), self, TABLES_PROP)
238
+
239
+ @tables.setter
240
+ def tables(self, v: dict[str, Table]) -> None:
241
+ self.properties[TABLES_PROP] = v
242
+
243
+ def __repr__(self) -> str:
244
+ return f"<CollectionTableExtension Item id={self.collection.id}>"
245
+
246
+
247
+ class ItemTableExtension(TableExtension[pystac.Item]):
248
+ """A concrete implementation of :class:`TableExtension` on an
249
+ :class:`~pystac.Item` that extends the properties of the Item to include properties
250
+ defined in the :stac-ext:`Table Extension <table>`.
251
+
252
+ This class should generally not be instantiated directly. Instead, call
253
+ :meth:`TableExtension.ext` on an :class:`~pystac.Item` to extend it.
254
+ """
255
+
256
+ item: pystac.Item
257
+ properties: dict[str, Any]
258
+
259
+ def __init__(self, item: pystac.Item):
260
+ self.item = item
261
+ self.properties = item.properties
262
+
263
+ def __repr__(self) -> str:
264
+ return f"<ItemTableExtension Item id={self.item.id}>"
265
+
266
+
267
+ class AssetTableExtension(TableExtension[pystac.Asset]):
268
+ """A concrete implementation of :class:`TableExtension` on an
269
+ :class:`~pystac.Asset` that extends the Asset fields to include properties defined
270
+ in the :stac-ext:`Table Extension <table>`.
271
+
272
+ This class should generally not be instantiated directly. Instead, call
273
+ :meth:`TableExtension.ext` on an :class:`~pystac.Asset` to extend it.
274
+ """
275
+
276
+ asset_href: str
277
+ properties: dict[str, Any]
278
+ additional_read_properties: Iterable[dict[str, Any]] | None
279
+
280
+ def __init__(self, asset: pystac.Asset):
281
+ self.asset_href = asset.href
282
+ self.properties = asset.extra_fields
283
+ if asset.owner and isinstance(asset.owner, pystac.Item):
284
+ self.additional_read_properties = [asset.owner.properties]
285
+ else:
286
+ self.additional_read_properties = None
287
+
288
+ @property
289
+ def storage_options(self) -> dict[str, Any] | None:
290
+ """Additional keywords for opening the dataset"""
291
+ return self.properties.get(STORAGE_OPTIONS_PROP)
292
+
293
+ @storage_options.setter
294
+ def storage_options(self, v: dict[str, Any] | None) -> Any:
295
+ if v is None:
296
+ self.properties.pop(STORAGE_OPTIONS_PROP, None)
297
+ else:
298
+ self.properties[STORAGE_OPTIONS_PROP] = v
299
+
300
+ def __repr__(self) -> str:
301
+ return f"<AssetTableExtension Item id={self.asset_href}>"
302
+
303
+
304
+ class ItemAssetsTableExtension(TableExtension[pystac.ItemAssetDefinition]):
305
+ properties: dict[str, Any]
306
+ asset_defn: pystac.ItemAssetDefinition
307
+
308
+ def __init__(self, item_asset: pystac.ItemAssetDefinition):
309
+ self.asset_defn = item_asset
310
+ self.properties = item_asset.properties
311
+
312
+
313
+ class TableExtensinoHooks(ExtensionHooks):
314
+ schema_uri: str = SCHEMA_URI
315
+ prev_extension_ids = {
316
+ "table",
317
+ "https://stac-extensions.github.io/table/v1.0.0/schema.json",
318
+ "https://stac-extensions.github.io/table/v1.0.1/schema.json",
319
+ "https://stac-extensions.github.io/table/v1.1.0/schema.json",
320
+ }
321
+ stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM}
322
+
323
+
324
+ TABLE_EXTENSION_HOOKS: ExtensionHooks = TableExtensinoHooks()
@@ -0,0 +1,90 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/table/v1.2.0/schema.json
7
+ response:
8
+ body:
9
+ string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
10
+ \"https://stac-extensions.github.io/table/v1.2.0/schema.json#\",\n \"title\":
11
+ \"Table Extension\",\n \"description\": \"STAC Table Extension for STAC Items
12
+ and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": \"This
13
+ is the schema for STAC Items. Remove this object if this extension only applies
14
+ to Collections.\",\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
15
+ \ },\n {\n \"type\": \"object\",\n \"required\":
16
+ [\n \"type\",\n \"properties\",\n \"assets\"\n
17
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
18
+ \"Feature\"\n },\n \"properties\": {\n \"allOf\":
19
+ [\n {\n \"$comment\": \"Require fields here
20
+ for Item Properties.\",\n \"required\": []\n },\n
21
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
22
+ \ }\n ]\n },\n \"assets\":
23
+ {\n \"$comment\": \"This validates the fields in Item Assets,
24
+ but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\":
25
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
26
+ \ }\n }\n ]\n },\n {\n \"$comment\": \"This
27
+ is the schema for STAC Collections.\",\n \"type\": \"object\",\n \"allOf\":
28
+ [\n {\n \"required\": [\n \"type\"\n ],\n
29
+ \ \"properties\": {\n \"type\": {\n \"const\":
30
+ \"Collection\"\n }\n }\n },\n {\n \"$ref\":
31
+ \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n
32
+ \ {\n \"$comment\": \"This is the schema for the top-level
33
+ fields in a Collection. Remove this if this extension does not define top-level
34
+ fields for Collections.\",\n \"allOf\": [\n {\n \"$comment\":
35
+ \"Require fields here for Collections (top-level).\",\n \"required\":
36
+ []\n },\n {\n \"$ref\": \"#/definitions/fields\"\n
37
+ \ }\n ]\n },\n {\n \"$comment\":
38
+ \"This validates the fields in Collection Assets, but does not require them.\",\n
39
+ \ \"required\": [\n \"assets\"\n ],\n \"properties\":
40
+ {\n \"assets\": {\n \"type\": \"object\",\n \"not\":
41
+ {\n \"additionalProperties\": {\n \"not\":
42
+ {\n \"allOf\": [\n {\n \"$ref\":
43
+ \"#/definitions/require_any_field\"\n },\n {\n
44
+ \ \"$ref\": \"#/definitions/fields\"\n }\n
45
+ \ ]\n }\n }\n }\n
46
+ \ }\n }\n },\n {\n \"$comment\":
47
+ \"This is the schema for the fields in Item Asset Definitions. It doesn't
48
+ require any fields.\",\n \"required\": [\n \"item_assets\"\n
49
+ \ ],\n \"properties\": {\n \"item_assets\": {\n
50
+ \ \"type\": \"object\",\n \"not\": {\n \"additionalProperties\":
51
+ {\n \"not\": {\n \"allOf\": [\n {\n
52
+ \ \"$ref\": \"#/definitions/require_any_field\"\n },\n
53
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
54
+ \ }\n ]\n }\n }\n
55
+ \ }\n }\n }\n },\n {\n \"$comment\":
56
+ \"This is the schema for the fields in Summaries. By default, only checks
57
+ the existence of the properties, but not the schema of the summaries.\",\n
58
+ \ \"required\": [\n \"summaries\"\n ],\n \"properties\":
59
+ {\n \"summaries\": {\n \"$ref\": \"#/definitions/require_any_field\"\n
60
+ \ }\n }\n }\n ]\n }\n ],\n \"definitions\":
61
+ {\n \"stac_extensions\": {\n \"type\": \"object\",\n \"required\":
62
+ [\n \"stac_extensions\"\n ],\n \"properties\": {\n \"stac_extensions\":
63
+ {\n \"type\": \"array\",\n \"contains\": {\n \"const\":
64
+ \"https://stac-extensions.github.io/table/v1.2.0/schema.json\"\n }\n
65
+ \ }\n }\n },\n \"require_any_field\": {\n \"$comment\":
66
+ \"Please list all fields here so that we can force the existence of one of
67
+ them in other parts of the schemas.\",\n \"anyOf\": [\n {\n \"required\":
68
+ []\n }\n ]\n },\n \"fields\": {\n \"$comment\": \"Add
69
+ your new fields here. Don't require them here, do that above in the corresponding
70
+ schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"table:tables\":
71
+ {\n \"type\": \"array\",\n \"items\": {\n \"type\":
72
+ \"object\",\n \"required\": [\n \"name\"\n ],\n
73
+ \ \"properties\": {\n \"name\": {\n \"type\":
74
+ \"string\"\n },\n \"description\": {\n \"type\":
75
+ \"string\"\n }\n }\n },\n \"additionalProperties\":
76
+ false\n },\n \"table:columns\": {\n \"type\": \"array\",\n
77
+ \ \"items\": {\n \"type\": \"object\",\n \"required\":
78
+ [\n \"name\"\n ],\n \"properties\": {\n
79
+ \ \"name\": {\n \"type\": \"string\"\n },\n
80
+ \ \"description\": {\n \"type\": \"string\"\n },\n
81
+ \ \"type\": {\n \"type\": \"string\"\n }\n
82
+ \ }\n },\n \"additionalProperties\": false\n },\n
83
+ \ \"table:primary_geometry\": {\n \"type\": \"string\"\n },\n
84
+ \ \"table:row_count\": {\n \"type\": \"number\"\n }\n
85
+ \ }\n }\n }\n}\n"
86
+ headers: {}
87
+ status:
88
+ code: 200
89
+ message: OK
90
+ version: 1
@@ -0,0 +1,5 @@
1
+ # Source information
2
+
3
+ Example files yanked from the Table extension STAC specification:
4
+
5
+ https://github.com/stac-extensions/table/tree/f962838a51c0ce78c9aa180b7973e538e6ef900e/examples
@@ -0,0 +1,33 @@
1
+ {
2
+ "type": "Collection",
3
+ "id": "id",
4
+ "stac_version": "1.1.0",
5
+ "description": "desc",
6
+ "links": [],
7
+ "stac_extensions": [
8
+ "https://stac-extensions.github.io/table/v1.2.0/schema.json"
9
+ ],
10
+ "extent": {
11
+ "spatial": {
12
+ "bbox": [
13
+ [
14
+ 1,
15
+ 2,
16
+ 3,
17
+ 4
18
+ ]
19
+ ]
20
+ },
21
+ "temporal": {
22
+ "interval": [
23
+ [
24
+ null,
25
+ null
26
+ ]
27
+ ]
28
+ }
29
+ },
30
+ "license": "other",
31
+ "item_assets": {},
32
+ "table:columns": []
33
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "stac_version": "1.1.0",
3
+ "stac_extensions": [
4
+ "https://stac-extensions.github.io/table/v1.2.0/schema.json"
5
+ ],
6
+ "type": "Collection",
7
+ "id": "collection",
8
+ "title": "A title",
9
+ "description": "A description",
10
+ "license": "Apache-2.0",
11
+ "extent": {
12
+ "spatial": {
13
+ "bbox": [
14
+ [
15
+ 172.9,
16
+ 1.3,
17
+ 173,
18
+ 1.4
19
+ ]
20
+ ]
21
+ },
22
+ "temporal": {
23
+ "interval": [
24
+ [
25
+ "2015-06-23T00:00:00Z",
26
+ null
27
+ ]
28
+ ]
29
+ }
30
+ },
31
+ "assets": {
32
+ "example": {
33
+ "href": "https://example.com/examples/file.xyz"
34
+ }
35
+ },
36
+ "item_assets": {
37
+ "data": {
38
+ "roles": [
39
+ "data"
40
+ ],
41
+ "description": "description"
42
+ }
43
+ },
44
+ "summaries": {
45
+ "datetime": {
46
+ "minimum": "2015-06-23T00:00:00Z",
47
+ "maximum": "2019-07-10T13:44:56Z"
48
+ }
49
+ },
50
+ "table:columns": [
51
+ {
52
+ "name": "geometry",
53
+ "description": "The observation location.",
54
+ "type": "int64"
55
+ },
56
+ {
57
+ "name": "id",
58
+ "description": "The numerical identifier"
59
+ },
60
+ {
61
+ "name": "value"
62
+ }
63
+ ],
64
+ "table:primary_geometry": "geometry",
65
+ "links": [
66
+ {
67
+ "href": "https://example.com/examples/collection.json",
68
+ "rel": "self"
69
+ },
70
+ {
71
+ "href": "https://example.com/examples/item.json",
72
+ "rel": "item"
73
+ }
74
+ ]
75
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "stac_version": "1.1.0",
3
+ "stac_extensions": [
4
+ "https://stac-extensions.github.io/table/v1.2.0/schema.json"
5
+ ],
6
+ "type": "Feature",
7
+ "id": "item",
8
+ "bbox": [
9
+ 172.9,
10
+ 1.3,
11
+ 173,
12
+ 1.4
13
+ ],
14
+ "geometry": {
15
+ "type": "Polygon",
16
+ "coordinates": [
17
+ [
18
+ [
19
+ 172.9,
20
+ 1.3
21
+ ],
22
+ [
23
+ 173,
24
+ 1.3
25
+ ],
26
+ [
27
+ 173,
28
+ 1.4
29
+ ],
30
+ [
31
+ 172.9,
32
+ 1.4
33
+ ],
34
+ [
35
+ 172.9,
36
+ 1.3
37
+ ]
38
+ ]
39
+ ]
40
+ },
41
+ "properties": {
42
+ "datetime": "2020-12-11T22:38:32Z",
43
+ "table:columns": [
44
+ {
45
+ "name": "geometry",
46
+ "description": "The observation location.",
47
+ "type": "byte_array"
48
+ },
49
+ {
50
+ "name": "id",
51
+ "description": "The numerical identifier",
52
+ "type": "int64"
53
+ },
54
+ {
55
+ "name": "value",
56
+ "description": "The observed value",
57
+ "type": "float64"
58
+ }
59
+ ],
60
+ "table:primary_geometry": "geometry",
61
+ "table:row_count": 100
62
+ },
63
+ "links": [
64
+ {
65
+ "href": "https://example.com/examples/item.json",
66
+ "rel": "self"
67
+ }
68
+ ],
69
+ "assets": {
70
+ "data": {
71
+ "href": "https://example.com/examples/file.xyz"
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "stac_version": "1.1.0",
3
+ "stac_extensions": [
4
+ "https://stac-extensions.github.io/table/v1.2.0/schema.json"
5
+ ],
6
+ "type": "Collection",
7
+ "id": "collection",
8
+ "title": "A title",
9
+ "description": "A description",
10
+ "license": "Apache-2.0",
11
+ "extent": {
12
+ "spatial": {
13
+ "bbox": [
14
+ [
15
+ 172.9,
16
+ 1.3,
17
+ 173,
18
+ 1.4
19
+ ]
20
+ ]
21
+ },
22
+ "temporal": {
23
+ "interval": [
24
+ [
25
+ "2015-06-23T00:00:00Z",
26
+ null
27
+ ]
28
+ ]
29
+ }
30
+ },
31
+ "assets": {
32
+ "example": {
33
+ "href": "https://example.com/examples/file.xyz"
34
+ }
35
+ },
36
+ "item_assets": {
37
+ "data": {
38
+ "roles": [
39
+ "data"
40
+ ],
41
+ "description": "description"
42
+ }
43
+ },
44
+ "summaries": {
45
+ "datetime": {
46
+ "minimum": "2015-06-23T00:00:00Z",
47
+ "maximum": "2019-07-10T13:44:56Z"
48
+ }
49
+ },
50
+ "table:tables": [
51
+ {
52
+ "name": "first table",
53
+ "description": "This is my first table"
54
+ },
55
+ {
56
+ "name": "Second table"
57
+ }
58
+ ],
59
+ "links": [
60
+ {
61
+ "href": "https://example.com/examples/collection.json",
62
+ "rel": "self"
63
+ },
64
+ {
65
+ "href": "https://example.com/examples/item.json",
66
+ "rel": "item"
67
+ }
68
+ ]
69
+ }
@@ -0,0 +1,98 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ import pystac
6
+ from pystac import ExtensionTypeError, Item
7
+ from pystac.extensions.table import Column, TableExtension
8
+
9
+ DATA_FILES = Path(__file__).resolve().parent / "data-files"
10
+
11
+
12
+ @pytest.fixture
13
+ def table_item() -> Item:
14
+ return pystac.Item.from_file(str(DATA_FILES / "item.json"))
15
+
16
+
17
+ @pytest.mark.vcr()
18
+ def test_validate(table_item: Item) -> None:
19
+ table_item.validate()
20
+
21
+
22
+ def test_extension_not_implemented(table_item: Item) -> None:
23
+ # Should raise exception if item does not include extension URI
24
+ table_item.stac_extensions.remove(TableExtension.get_schema_uri())
25
+
26
+ with pytest.raises(pystac.ExtensionNotImplemented):
27
+ _ = TableExtension.ext(table_item)
28
+
29
+ # Should raise exception if owning item does not include extension URI
30
+ asset = table_item.assets["data"]
31
+
32
+ with pytest.raises(pystac.ExtensionNotImplemented):
33
+ _ = TableExtension.ext(asset)
34
+
35
+ # Should succeed if Asset has no owner
36
+ ownerless_asset = pystac.Asset.from_dict(asset.to_dict())
37
+ _ = TableExtension.ext(ownerless_asset)
38
+
39
+
40
+ def test_item_ext_add_to(table_item: Item) -> None:
41
+ table_item.stac_extensions.remove(TableExtension.get_schema_uri())
42
+
43
+ _ = TableExtension.ext(table_item, add_if_missing=True)
44
+
45
+ assert TableExtension.get_schema_uri() in table_item.stac_extensions
46
+
47
+
48
+ def test_asset_ext_add_to(table_item: Item) -> None:
49
+ table_item.stac_extensions.remove(TableExtension.get_schema_uri())
50
+
51
+ assert TableExtension.get_schema_uri() not in table_item.stac_extensions
52
+ asset = table_item.assets["data"]
53
+
54
+ _ = TableExtension.ext(asset, add_if_missing=True)
55
+ assert TableExtension.get_schema_uri() in table_item.stac_extensions
56
+
57
+
58
+ def test_should_raise_when_passing_invalid_extension_object() -> None:
59
+ with pytest.raises(
60
+ ExtensionTypeError, match=r"^TableExtension does not apply to type 'object'$"
61
+ ):
62
+ # calling it wrong on purpose so ------v
63
+ TableExtension.ext(object()) # type: ignore
64
+
65
+
66
+ def test_item_with_table_extension_is_serilalizable_and_roundtrips(
67
+ tmp_path: Path,
68
+ table_item: Item,
69
+ ) -> None:
70
+ # add column metadata
71
+ tab_ext = TableExtension.ext(table_item, add_if_missing=True)
72
+ columns = [
73
+ Column({"name": "col_1", "type": "str"}),
74
+ Column({"name": "col_2", "type": "byte_array"}),
75
+ ]
76
+ tab_ext.columns = columns
77
+ table_item.save_object(dest_href=str(tmp_path / "item.json"))
78
+ assert all(isinstance(c, Column) for c in tab_ext.columns)
79
+ assert all(
80
+ before.properties == after.properties
81
+ for before, after in zip(columns, tab_ext.columns)
82
+ )
83
+
84
+
85
+ @pytest.mark.parametrize(
86
+ "schema_uri",
87
+ (
88
+ "https://stac-extensions.github.io/table/v1.0.0/schema.json",
89
+ "https://stac-extensions.github.io/table/v1.0.1/schema.json",
90
+ "https://stac-extensions.github.io/table/v1.1.0/schema.json",
91
+ ),
92
+ )
93
+ def test_migrate(schema_uri: str, table_item: Item) -> None:
94
+ item_dict = table_item.to_dict(include_self_link=False, transform_hrefs=False)
95
+ item_dict["stac_extensions"] = [schema_uri]
96
+ assert Item.from_dict(item_dict, migrate=True).stac_extensions == [
97
+ "https://stac-extensions.github.io/table/v1.2.0/schema.json"
98
+ ]