pystac-ext-table 1.2.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.
- pystac_ext_table-1.2.0rc0/.gitignore +163 -0
- pystac_ext_table-1.2.0rc0/PKG-INFO +37 -0
- pystac_ext_table-1.2.0rc0/README.md +13 -0
- pystac_ext_table-1.2.0rc0/pyproject.toml +36 -0
- pystac_ext_table-1.2.0rc0/pystac/extensions/py.typed +0 -0
- pystac_ext_table-1.2.0rc0/pystac/extensions/table.py +324 -0
- pystac_ext_table-1.2.0rc0/tests/cassettes/test_table/test_validate.yaml +90 -0
- pystac_ext_table-1.2.0rc0/tests/data-files/README.md +5 -0
- pystac_ext_table-1.2.0rc0/tests/data-files/collection-2.json +33 -0
- pystac_ext_table-1.2.0rc0/tests/data-files/collection.json +75 -0
- pystac_ext_table-1.2.0rc0/tests/data-files/item.json +74 -0
- pystac_ext_table-1.2.0rc0/tests/data-files/table-collection.json +69 -0
- pystac_ext_table-1.2.0rc0/tests/test_table.py +98 -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-table
|
|
3
|
+
Version: 1.2.0rc0
|
|
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-rc.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,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
|
+
]
|