scse-spatial 1.0.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.
- scse_spatial-1.0.0/PKG-INFO +136 -0
- scse_spatial-1.0.0/README.md +92 -0
- scse_spatial-1.0.0/pyproject.toml +182 -0
- scse_spatial-1.0.0/spatial/__init__.py +7 -0
- scse_spatial-1.0.0/spatial/__main__.py +4 -0
- scse_spatial-1.0.0/spatial/_version.py +2 -0
- scse_spatial-1.0.0/spatial/cli/__init__.py +19 -0
- scse_spatial-1.0.0/spatial/cli/alembic.py +85 -0
- scse_spatial-1.0.0/spatial/cli/imports.py +89 -0
- scse_spatial-1.0.0/spatial/cli/manager.py +161 -0
- scse_spatial-1.0.0/spatial/cli/server.py +24 -0
- scse_spatial-1.0.0/spatial/cli/workflow.py +367 -0
- scse_spatial-1.0.0/spatial/conf/__init__.py +2 -0
- scse_spatial-1.0.0/spatial/conf/credentials.py +73 -0
- scse_spatial-1.0.0/spatial/conf/logging.json +44 -0
- scse_spatial-1.0.0/spatial/conf/platforms.py +91 -0
- scse_spatial-1.0.0/spatial/conf/settings.py +397 -0
- scse_spatial-1.0.0/spatial/data/base/exceptions.py +24 -0
- scse_spatial-1.0.0/spatial/data/base/filters.py +65 -0
- scse_spatial-1.0.0/spatial/data/base/hierarchy.py +267 -0
- scse_spatial-1.0.0/spatial/data/base/pagination.py +76 -0
- scse_spatial-1.0.0/spatial/data/base/service.py +173 -0
- scse_spatial-1.0.0/spatial/data/features/__init__.py +2 -0
- scse_spatial-1.0.0/spatial/data/features/controller.py +113 -0
- scse_spatial-1.0.0/spatial/data/features/db.py +136 -0
- scse_spatial-1.0.0/spatial/data/features/dto.py +66 -0
- scse_spatial-1.0.0/spatial/data/features/service.py +765 -0
- scse_spatial-1.0.0/spatial/data/features/tiling.py +123 -0
- scse_spatial-1.0.0/spatial/data/file_storage.py +362 -0
- scse_spatial-1.0.0/spatial/data/hashing.py +30 -0
- scse_spatial-1.0.0/spatial/data/manifest/__init__.py +7 -0
- scse_spatial-1.0.0/spatial/data/manifest/db.py +127 -0
- scse_spatial-1.0.0/spatial/data/rasters/__init__.py +4 -0
- scse_spatial-1.0.0/spatial/data/rasters/controller.py +145 -0
- scse_spatial-1.0.0/spatial/data/rasters/db.py +214 -0
- scse_spatial-1.0.0/spatial/data/rasters/dto.py +86 -0
- scse_spatial-1.0.0/spatial/data/rasters/service.py +2150 -0
- scse_spatial-1.0.0/spatial/data/rasters/tile.py +35 -0
- scse_spatial-1.0.0/spatial/data/stac/__init__.py +1 -0
- scse_spatial-1.0.0/spatial/data/stac/io.py +81 -0
- scse_spatial-1.0.0/spatial/data/stac/service.py +401 -0
- scse_spatial-1.0.0/spatial/db/__init__.py +23 -0
- scse_spatial-1.0.0/spatial/db/base.py +33 -0
- scse_spatial-1.0.0/spatial/db/migrations/__init__.py +1 -0
- scse_spatial-1.0.0/spatial/db/migrations/env.py +109 -0
- scse_spatial-1.0.0/spatial/db/migrations/script.py.mako +26 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/64901b988c97_initial.py +219 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/7f3e2a1b9c4d_rename_url_columns_to_path.py +87 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/8a2b3c4d5e6f_store_tile_params_in_metadata.py +45 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/__init__.py +0 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/a1b2c3d4e5f6_drop_raster_step_columns.py +35 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/b1c3d5e7f9a1_add_raster_hierarchy_projection_epsg.py +32 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/c5f3a4b8d9e7_drop_zarr_variable_and_feature_crs.py +34 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/d2e4f6a8b0c2_add_jsonb_gin_indexes.py +48 -0
- scse_spatial-1.0.0/spatial/db/migrations/versions/e3f5a7c9d1b2_add_actor_attribution.py +49 -0
- scse_spatial-1.0.0/spatial/db/models.py +15 -0
- scse_spatial-1.0.0/spatial/projections.py +287 -0
- scse_spatial-1.0.0/spatial/py.typed +1 -0
- scse_spatial-1.0.0/spatial/server/__init__.py +8 -0
- scse_spatial-1.0.0/spatial/server/algorithm.py +54 -0
- scse_spatial-1.0.0/spatial/server/app.py +203 -0
- scse_spatial-1.0.0/spatial/server/data_serve.py +135 -0
- scse_spatial-1.0.0/spatial/server/dependencies.py +103 -0
- scse_spatial-1.0.0/spatial/server/exceptions.py +44 -0
- scse_spatial-1.0.0/spatial/server/mounts.py +76 -0
- scse_spatial-1.0.0/spatial/server/root.py +60 -0
- scse_spatial-1.0.0/spatial/server/schemas.py +29 -0
- scse_spatial-1.0.0/spatial/server/stac.py +98 -0
- scse_spatial-1.0.0/spatial/server/titiler.py +323 -0
- scse_spatial-1.0.0/spatial/spatial.py +282 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scse-spatial
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A registry enabling the indexing and querying of spatial data resources on other services.
|
|
5
|
+
Author: Max Wolschlager
|
|
6
|
+
Author-email: rxtx@ungut.at
|
|
7
|
+
Requires-Python: >=3.11,<3.15
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: alembic (>=1.12.0,<2.0.0)
|
|
14
|
+
Requires-Dist: dask (>=2024.0.0)
|
|
15
|
+
Requires-Dist: geoalchemy2 (>=0.20.0,<0.21.0)
|
|
16
|
+
Requires-Dist: geopandas (>=1.0,<2.0)
|
|
17
|
+
Requires-Dist: jsonschema (>=4.26.0,<5.0.0)
|
|
18
|
+
Requires-Dist: litestar (>=2.0.0,<3.0.0)
|
|
19
|
+
Requires-Dist: mapbox-vector-tile (>=2,<3)
|
|
20
|
+
Requires-Dist: morecantile (>=7.0.3,<8.0)
|
|
21
|
+
Requires-Dist: netcdf4 (>=1.7.3,<2.0.0)
|
|
22
|
+
Requires-Dist: pmtiles (>=3.7,<4)
|
|
23
|
+
Requires-Dist: psycopg[binary] (>=3.1.12,<4.0.0)
|
|
24
|
+
Requires-Dist: pydantic (>=2.4.2,<3.0.0)
|
|
25
|
+
Requires-Dist: pydantic-settings (>=2.0.3,<3.0.0)
|
|
26
|
+
Requires-Dist: pyproj (>=3.1,<4.0)
|
|
27
|
+
Requires-Dist: pystac[validation] (>=1.12,<1.13)
|
|
28
|
+
Requires-Dist: rasterio (>=1.4.3,<2.0.0)
|
|
29
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
30
|
+
Requires-Dist: rioxarray (>=0.19,<1.0)
|
|
31
|
+
Requires-Dist: s3fs (>=2024.10.0,<2027.0.0)
|
|
32
|
+
Requires-Dist: scse-toolkit (>=2,<3)
|
|
33
|
+
Requires-Dist: sqlalchemy (>=2.0.21,<3)
|
|
34
|
+
Requires-Dist: tippecanoe (>=2.45)
|
|
35
|
+
Requires-Dist: titiler-core (>=2.0,<3.0)
|
|
36
|
+
Requires-Dist: titiler-xarray[fs] (>=2.0,<3.0)
|
|
37
|
+
Requires-Dist: typer (>=0.24.1,<0.25.0)
|
|
38
|
+
Requires-Dist: uvicorn (>=0.52.1,<0.53.0)
|
|
39
|
+
Requires-Dist: xarray (>=2025.1.0,<2026)
|
|
40
|
+
Requires-Dist: xxhash (>=3.4.1,<4.0.0)
|
|
41
|
+
Requires-Dist: zarr (>=3.0.0,<4.0)
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# scse-spatial
|
|
45
|
+
|
|
46
|
+
A registry that indexes and queries spatial data resources on other services.
|
|
47
|
+
|
|
48
|
+
## Overview
|
|
49
|
+
|
|
50
|
+
scse-spatial is the spatial data extension to a scenario services web
|
|
51
|
+
application stack. It turns geodata files into fast, web-optimized resources
|
|
52
|
+
and serves them to any map app.
|
|
53
|
+
|
|
54
|
+
A workflow loads data. A web app reads it. This repository provides both
|
|
55
|
+
sides: the ingest path and the HTTP API.
|
|
56
|
+
|
|
57
|
+
### Feeding it
|
|
58
|
+
|
|
59
|
+
Workflows push data in with the CLI or a few lines of Python. The `Spatial`
|
|
60
|
+
object connects to a PostGIS database and to object storage. It exposes
|
|
61
|
+
services for rasters, features, and a STAC catalog.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
with Spatial("<platform>", settings=Settings()) as spatial:
|
|
65
|
+
spatial.rasters.sync_hierarchy(
|
|
66
|
+
"emissions",
|
|
67
|
+
[path],
|
|
68
|
+
parameters_class=EmissionParameters,
|
|
69
|
+
metadata_class=EmissionMetadata,
|
|
70
|
+
extract_meta=extract_em_meta,
|
|
71
|
+
)
|
|
72
|
+
spatial.stac.export_catalog()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The platform converts each source file into optimized formats, uploads them,
|
|
76
|
+
and indexes them. A re-run skips files whose content did not change.
|
|
77
|
+
|
|
78
|
+
Workflows live in their own repositories. See `WORKFLOWS.md` for more information.
|
|
79
|
+
|
|
80
|
+
### Connecting to a platform
|
|
81
|
+
|
|
82
|
+
In multi-tenant deployments the connection details come from the manager
|
|
83
|
+
API. Staff log in with their user account once; the credentials are cached
|
|
84
|
+
locally and used in place of the shared service secret.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
spatial login # prompts for username and password, caches them
|
|
88
|
+
spatial whoami # show the identity the CLI would use
|
|
89
|
+
spatial platforms # list the platforms you can access
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The manager DSN's secret material stays in the local environment through
|
|
93
|
+
`{env:...}` placeholders. Imports are attributed to the logged-in user.
|
|
94
|
+
|
|
95
|
+
### HTTP API
|
|
96
|
+
|
|
97
|
+
Web apps can read data over HTTP. A Litestar server lists hierarchies and
|
|
98
|
+
layers, renders map tiles through TiTiler, and streams vector tiles and
|
|
99
|
+
GeoJSON from object storage.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl -X PATCH http://<scse-spatial>/v1/{platform}/rasters/ \
|
|
103
|
+
-H "Content-Type: application/json" \
|
|
104
|
+
-d '{"hierarchy_id": 3, "parameters__contains": {"sector": "power"}}'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Point an scse-app at the `/v1` or `/titiler` endpoints, or open `/docs` and
|
|
108
|
+
`/titiler/docs` for interactive API docs. See `HTTP_API.md` for more information.
|
|
109
|
+
|
|
110
|
+
## Development Setup
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Install Poetry, minimum version >=1.2 required
|
|
114
|
+
pipx install poetry
|
|
115
|
+
|
|
116
|
+
# You may have to reinitialize your shell at this point.
|
|
117
|
+
source ~/.bashrc
|
|
118
|
+
|
|
119
|
+
# Activate in-project virtualenvs
|
|
120
|
+
poetry config virtualenvs.in-project true
|
|
121
|
+
|
|
122
|
+
# Add dynamic versioning plugin
|
|
123
|
+
poetry self add "poetry-dynamic-versioning[plugin]"
|
|
124
|
+
|
|
125
|
+
# Install dependencies
|
|
126
|
+
# (using "--with dev" if dev dependencies should be installed as well)
|
|
127
|
+
poetry install --with dev
|
|
128
|
+
|
|
129
|
+
# Activate virtual environment
|
|
130
|
+
poetry shell
|
|
131
|
+
|
|
132
|
+
# Copy the template environment configuration
|
|
133
|
+
cp template.env .env
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# scse-spatial
|
|
2
|
+
|
|
3
|
+
A registry that indexes and queries spatial data resources on other services.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
scse-spatial is the spatial data extension to a scenario services web
|
|
8
|
+
application stack. It turns geodata files into fast, web-optimized resources
|
|
9
|
+
and serves them to any map app.
|
|
10
|
+
|
|
11
|
+
A workflow loads data. A web app reads it. This repository provides both
|
|
12
|
+
sides: the ingest path and the HTTP API.
|
|
13
|
+
|
|
14
|
+
### Feeding it
|
|
15
|
+
|
|
16
|
+
Workflows push data in with the CLI or a few lines of Python. The `Spatial`
|
|
17
|
+
object connects to a PostGIS database and to object storage. It exposes
|
|
18
|
+
services for rasters, features, and a STAC catalog.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
with Spatial("<platform>", settings=Settings()) as spatial:
|
|
22
|
+
spatial.rasters.sync_hierarchy(
|
|
23
|
+
"emissions",
|
|
24
|
+
[path],
|
|
25
|
+
parameters_class=EmissionParameters,
|
|
26
|
+
metadata_class=EmissionMetadata,
|
|
27
|
+
extract_meta=extract_em_meta,
|
|
28
|
+
)
|
|
29
|
+
spatial.stac.export_catalog()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The platform converts each source file into optimized formats, uploads them,
|
|
33
|
+
and indexes them. A re-run skips files whose content did not change.
|
|
34
|
+
|
|
35
|
+
Workflows live in their own repositories. See `WORKFLOWS.md` for more information.
|
|
36
|
+
|
|
37
|
+
### Connecting to a platform
|
|
38
|
+
|
|
39
|
+
In multi-tenant deployments the connection details come from the manager
|
|
40
|
+
API. Staff log in with their user account once; the credentials are cached
|
|
41
|
+
locally and used in place of the shared service secret.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
spatial login # prompts for username and password, caches them
|
|
45
|
+
spatial whoami # show the identity the CLI would use
|
|
46
|
+
spatial platforms # list the platforms you can access
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The manager DSN's secret material stays in the local environment through
|
|
50
|
+
`{env:...}` placeholders. Imports are attributed to the logged-in user.
|
|
51
|
+
|
|
52
|
+
### HTTP API
|
|
53
|
+
|
|
54
|
+
Web apps can read data over HTTP. A Litestar server lists hierarchies and
|
|
55
|
+
layers, renders map tiles through TiTiler, and streams vector tiles and
|
|
56
|
+
GeoJSON from object storage.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl -X PATCH http://<scse-spatial>/v1/{platform}/rasters/ \
|
|
60
|
+
-H "Content-Type: application/json" \
|
|
61
|
+
-d '{"hierarchy_id": 3, "parameters__contains": {"sector": "power"}}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Point an scse-app at the `/v1` or `/titiler` endpoints, or open `/docs` and
|
|
65
|
+
`/titiler/docs` for interactive API docs. See `HTTP_API.md` for more information.
|
|
66
|
+
|
|
67
|
+
## Development Setup
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Install Poetry, minimum version >=1.2 required
|
|
71
|
+
pipx install poetry
|
|
72
|
+
|
|
73
|
+
# You may have to reinitialize your shell at this point.
|
|
74
|
+
source ~/.bashrc
|
|
75
|
+
|
|
76
|
+
# Activate in-project virtualenvs
|
|
77
|
+
poetry config virtualenvs.in-project true
|
|
78
|
+
|
|
79
|
+
# Add dynamic versioning plugin
|
|
80
|
+
poetry self add "poetry-dynamic-versioning[plugin]"
|
|
81
|
+
|
|
82
|
+
# Install dependencies
|
|
83
|
+
# (using "--with dev" if dev dependencies should be installed as well)
|
|
84
|
+
poetry install --with dev
|
|
85
|
+
|
|
86
|
+
# Activate virtual environment
|
|
87
|
+
poetry shell
|
|
88
|
+
|
|
89
|
+
# Copy the template environment configuration
|
|
90
|
+
cp template.env .env
|
|
91
|
+
|
|
92
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "scse-spatial"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A registry enabling the indexing and querying of spatial data resources on other services."
|
|
5
|
+
authors = ["Max Wolschlager <rxtx@ungut.at>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{ include = "spatial" }]
|
|
8
|
+
requires-poetry = ">=2.0,<3.0"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">=3.11, <3.15"
|
|
12
|
+
alembic = "^1.12.0"
|
|
13
|
+
sqlalchemy = ">=2.0.21,<3"
|
|
14
|
+
psycopg = { extras = ["binary"], version = "^3.1.12" }
|
|
15
|
+
geoalchemy2 = "^0.20.0"
|
|
16
|
+
|
|
17
|
+
pydantic = "^2.4.2"
|
|
18
|
+
litestar = "^2.0.0"
|
|
19
|
+
uvicorn = "^0.52.1"
|
|
20
|
+
|
|
21
|
+
pydantic-settings = "^2.0.3"
|
|
22
|
+
xxhash = "^3.4.1"
|
|
23
|
+
|
|
24
|
+
typer = "^0.24.1"
|
|
25
|
+
geopandas = "^1.0"
|
|
26
|
+
rasterio = "^1.4.3"
|
|
27
|
+
pyproj = ">=3.1,<4.0"
|
|
28
|
+
morecantile = ">=7.0.3,<8.0"
|
|
29
|
+
rioxarray = ">=0.19,<1.0"
|
|
30
|
+
xarray = ">=2025.1.0,<2026"
|
|
31
|
+
dask = ">=2024.0.0"
|
|
32
|
+
netcdf4 = "^1.7.3"
|
|
33
|
+
scse-toolkit = ">=2,<3"
|
|
34
|
+
titiler-core = "^2.0"
|
|
35
|
+
titiler-xarray = { version = "^2.0", extras = ["fs"] }
|
|
36
|
+
zarr = ">=3.0.0,<4.0"
|
|
37
|
+
s3fs = ">=2024.10.0,<2027.0.0"
|
|
38
|
+
pystac = { version = ">=1.12,<1.13", extras = ["validation"] }
|
|
39
|
+
pmtiles = ">=3.7,<4"
|
|
40
|
+
mapbox-vector-tile = ">=2,<3"
|
|
41
|
+
tippecanoe = ">=2.45"
|
|
42
|
+
rich = "^13.9.4"
|
|
43
|
+
jsonschema = "^4.26.0"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
[tool.poetry.group.dev]
|
|
47
|
+
optional = true
|
|
48
|
+
|
|
49
|
+
[tool.poetry.group.dev.dependencies]
|
|
50
|
+
pytest = "^7.4.2"
|
|
51
|
+
ruff = ">=0.5.3"
|
|
52
|
+
mypy = "^1.6.0"
|
|
53
|
+
pytest-cov = "^4.1.0"
|
|
54
|
+
httpx = "^0.27.2"
|
|
55
|
+
pandas-stubs = "^3.0.0.260204"
|
|
56
|
+
|
|
57
|
+
[tool.poetry.scripts]
|
|
58
|
+
spatial = "spatial.__main__:app"
|
|
59
|
+
|
|
60
|
+
[build-system]
|
|
61
|
+
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
|
|
62
|
+
build-backend = "poetry_dynamic_versioning.backend"
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
# Exclude a variety of commonly ignored directories.
|
|
66
|
+
exclude = [
|
|
67
|
+
".eggs",
|
|
68
|
+
".git",
|
|
69
|
+
".git-rewrite",
|
|
70
|
+
".mypy_cache",
|
|
71
|
+
".pytype",
|
|
72
|
+
".ruff_cache",
|
|
73
|
+
".venv",
|
|
74
|
+
"__pypackages__",
|
|
75
|
+
"build",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Same as Black.
|
|
79
|
+
line-length = 88
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
# Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default.
|
|
83
|
+
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
|
84
|
+
# McCabe complexity (`C901`) by default.
|
|
85
|
+
select = ["E", "F"]
|
|
86
|
+
ignore = []
|
|
87
|
+
|
|
88
|
+
fixable = ["ALL"]
|
|
89
|
+
unfixable = []
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.per-file-ignores]
|
|
92
|
+
"__init__.py" = ["E402", "F401", "F403"]
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
testpaths = ["tests"]
|
|
96
|
+
markers = [
|
|
97
|
+
"unit: heavily mocked unit tests",
|
|
98
|
+
"server: server endpoint tests",
|
|
99
|
+
"data: integration tests on a live database",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
[tool.mypy]
|
|
104
|
+
exclude = []
|
|
105
|
+
disable_error_code = ['override']
|
|
106
|
+
show_error_codes = true
|
|
107
|
+
plugins = ['pydantic.mypy']
|
|
108
|
+
# The following are equivalent to --strict mypy as seen in
|
|
109
|
+
# https://mypy.readthedocs.io/en/stable/existing_code.html#introduce-stricter-options
|
|
110
|
+
warn_unused_configs = true
|
|
111
|
+
warn_redundant_casts = true
|
|
112
|
+
warn_unused_ignores = true
|
|
113
|
+
strict_equality = true
|
|
114
|
+
extra_checks = true
|
|
115
|
+
check_untyped_defs = true
|
|
116
|
+
disallow_subclassing_any = true
|
|
117
|
+
disallow_untyped_decorators = true
|
|
118
|
+
disallow_any_generics = true
|
|
119
|
+
disallow_untyped_calls = true
|
|
120
|
+
disallow_incomplete_defs = true
|
|
121
|
+
disallow_untyped_defs = true
|
|
122
|
+
no_implicit_reexport = true
|
|
123
|
+
warn_return_any = true
|
|
124
|
+
# These are bonus, it seems:
|
|
125
|
+
disallow_any_unimported = true
|
|
126
|
+
no_implicit_optional = true
|
|
127
|
+
warn_unreachable = true
|
|
128
|
+
|
|
129
|
+
[[tool.mypy.overrides]]
|
|
130
|
+
module = "geopandas.*"
|
|
131
|
+
ignore_missing_imports = true
|
|
132
|
+
|
|
133
|
+
[[tool.mypy.overrides]]
|
|
134
|
+
module = "rasterio.*"
|
|
135
|
+
ignore_missing_imports = true
|
|
136
|
+
|
|
137
|
+
[[tool.mypy.overrides]]
|
|
138
|
+
module = "pystac.*"
|
|
139
|
+
ignore_missing_imports = true
|
|
140
|
+
|
|
141
|
+
[[tool.mypy.overrides]]
|
|
142
|
+
module = "tippecanoe.*"
|
|
143
|
+
ignore_missing_imports = true
|
|
144
|
+
|
|
145
|
+
[[tool.mypy.overrides]]
|
|
146
|
+
module = "pmtiles.*"
|
|
147
|
+
follow_imports = "skip"
|
|
148
|
+
ignore_missing_imports = true
|
|
149
|
+
|
|
150
|
+
[[tool.mypy.overrides]]
|
|
151
|
+
module = "mapbox_vector_tile.*"
|
|
152
|
+
ignore_missing_imports = true
|
|
153
|
+
|
|
154
|
+
[[tool.mypy.overrides]]
|
|
155
|
+
module = "shapely.*"
|
|
156
|
+
ignore_missing_imports = true
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
[[tool.mypy.overrides]]
|
|
160
|
+
module = "fsspec.*"
|
|
161
|
+
ignore_missing_imports = true
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
[[tool.mypy.overrides]]
|
|
165
|
+
module = "botocore.*"
|
|
166
|
+
ignore_missing_imports = true
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
[[tool.mypy.overrides]]
|
|
170
|
+
module = "boto3.*"
|
|
171
|
+
ignore_missing_imports = true
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
[[tool.mypy.overrides]]
|
|
175
|
+
module = "jsonschema.*"
|
|
176
|
+
ignore_missing_imports = true
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
[tool.poetry-dynamic-versioning]
|
|
180
|
+
enable = false
|
|
181
|
+
style = "pep440"
|
|
182
|
+
vcs = "git"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from ._version import __version__ as __version__
|
|
2
|
+
from ._version import __version_tuple__ as __version_tuple__
|
|
3
|
+
from .spatial import Spatial as Spatial
|
|
4
|
+
from .data.base.exceptions import Skip as Skip
|
|
5
|
+
from .data.features import Feature as Feature
|
|
6
|
+
from .data.rasters import RasterLayer as RasterLayer
|
|
7
|
+
from .data.rasters import TileParams as TileParams
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Command line interface of the spatial data platform.
|
|
2
|
+
|
|
3
|
+
See the CLI help for usage information:
|
|
4
|
+
|
|
5
|
+
.. code:: bash
|
|
6
|
+
|
|
7
|
+
spatial --help
|
|
8
|
+
spatial server --help
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from . import server, alembic, workflow, manager
|
|
13
|
+
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
app.add_typer(server.app, name="server")
|
|
16
|
+
app.add_typer(alembic.app, name="alembic")
|
|
17
|
+
app.add_typer(workflow.app)
|
|
18
|
+
app.add_typer(manager.app)
|
|
19
|
+
app.add_typer(manager.platforms_app, name="platforms")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from toolkit.db.alembic import AlembicCli
|
|
3
|
+
from typing_extensions import Annotated
|
|
4
|
+
|
|
5
|
+
from spatial.conf import Settings
|
|
6
|
+
from spatial.conf.platforms import (
|
|
7
|
+
resolve_dsn_env_tokens,
|
|
8
|
+
resolve_spatial_dsn,
|
|
9
|
+
)
|
|
10
|
+
from spatial.db import get_alembic_controller
|
|
11
|
+
|
|
12
|
+
app = AlembicCli(
|
|
13
|
+
help="Manages alembic migrations on selected platforms via subcommands."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def collect_platforms(
|
|
18
|
+
settings: Settings,
|
|
19
|
+
*,
|
|
20
|
+
platform: list[str] | None,
|
|
21
|
+
manager: bool,
|
|
22
|
+
) -> list[str]:
|
|
23
|
+
"""Collect the DSNs of the selected command targets."""
|
|
24
|
+
candidates: list[str] = []
|
|
25
|
+
if settings.manager_url is not None and (platform or manager):
|
|
26
|
+
try:
|
|
27
|
+
platforms = settings.get_manager_platforms()
|
|
28
|
+
except ValueError as exc:
|
|
29
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
30
|
+
if manager:
|
|
31
|
+
for p in platforms.list_spatial_platforms():
|
|
32
|
+
try:
|
|
33
|
+
dsn = resolve_spatial_dsn(p)
|
|
34
|
+
except ValueError:
|
|
35
|
+
continue # skip platforms with unresolvable dsn
|
|
36
|
+
candidates.append(dsn)
|
|
37
|
+
|
|
38
|
+
if platform is not None:
|
|
39
|
+
for name in platform:
|
|
40
|
+
platform_obj = platforms.get_spatial_platform(name)
|
|
41
|
+
dsn = resolve_spatial_dsn(platform_obj)
|
|
42
|
+
candidates.append(dsn)
|
|
43
|
+
|
|
44
|
+
if not candidates and settings.postgres_dsn is not None:
|
|
45
|
+
candidates.append(resolve_dsn_env_tokens(settings.postgres_dsn))
|
|
46
|
+
|
|
47
|
+
return candidates
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.callback()
|
|
51
|
+
def alembic(
|
|
52
|
+
ctx: typer.Context,
|
|
53
|
+
platform: Annotated[
|
|
54
|
+
list[str] | None,
|
|
55
|
+
typer.Option(
|
|
56
|
+
"--platform",
|
|
57
|
+
"-p",
|
|
58
|
+
help="Use (a) platform name(s) as an alembic command target.",
|
|
59
|
+
),
|
|
60
|
+
] = None,
|
|
61
|
+
manager: Annotated[
|
|
62
|
+
bool,
|
|
63
|
+
typer.Option(
|
|
64
|
+
help="Use platforms from the manager api as alembic targets.",
|
|
65
|
+
),
|
|
66
|
+
] = False,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Run alembic commands on platform databases.
|
|
69
|
+
Use '--platform/-p' or '--manager' to select the
|
|
70
|
+
target databases. Falls back to
|
|
71
|
+
`SPATIAL_POSTGRES_DSN` if no platform is specified."""
|
|
72
|
+
settings = Settings()
|
|
73
|
+
settings.configure_logging()
|
|
74
|
+
|
|
75
|
+
dsns = collect_platforms(settings, platform=platform, manager=manager)
|
|
76
|
+
|
|
77
|
+
if len(dsns) == 0:
|
|
78
|
+
raise typer.BadParameter(
|
|
79
|
+
"No platforms found. Use '--platform/-p', '--manager', or set "
|
|
80
|
+
"`SPATIAL_POSTGRES_DSN`."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
controllers = [get_alembic_controller(dsn) for dsn in dsns]
|
|
84
|
+
ctx.ensure_object(dict)
|
|
85
|
+
ctx.obj["controllers"] = controllers
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Resolve ``<module>:<name>`` references passed on the command line.
|
|
2
|
+
|
|
3
|
+
Workflows live in their own repositories, so the objects they hand to the
|
|
4
|
+
CLI (Pydantic models, extractor callables) cannot be imported by the CLI
|
|
5
|
+
itself. They are passed as ``<module>:<name>`` strings, resolved against
|
|
6
|
+
the working directory, and validated against an expected kind.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from importlib import import_module
|
|
13
|
+
from typing import Any, Callable, cast
|
|
14
|
+
|
|
15
|
+
import pydantic as pyd
|
|
16
|
+
import typer
|
|
17
|
+
|
|
18
|
+
ModelClassParser = Callable[[str], type[pyd.BaseModel]]
|
|
19
|
+
CallableParser = Callable[[str], Callable[..., Any]]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def import_member(reference: str) -> tuple[Any, str, str]:
|
|
23
|
+
"""Import the member named by ``<module>:<name>``.
|
|
24
|
+
|
|
25
|
+
The working directory is added to ``sys.path`` so a workflow repository
|
|
26
|
+
can point at its own modules without being installed.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
tuple[Any, str, str]
|
|
31
|
+
The imported object, its name, and its module path.
|
|
32
|
+
|
|
33
|
+
Raises
|
|
34
|
+
------
|
|
35
|
+
typer.BadParameter
|
|
36
|
+
If *reference* is malformed or the module cannot be imported.
|
|
37
|
+
"""
|
|
38
|
+
if ":" not in reference:
|
|
39
|
+
raise typer.BadParameter(
|
|
40
|
+
f"'{reference}' is not a '<module>:<name>' reference."
|
|
41
|
+
)
|
|
42
|
+
module_name, name = reference.rsplit(":", 1)
|
|
43
|
+
if not module_name or not name:
|
|
44
|
+
raise typer.BadParameter(
|
|
45
|
+
f"'{reference}' is not a '<module>:<name>' reference."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
cwd = "."
|
|
49
|
+
if cwd not in sys.path:
|
|
50
|
+
sys.path.insert(0, cwd)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
module = import_module(module_name)
|
|
54
|
+
except ImportError as exc:
|
|
55
|
+
raise typer.BadParameter(
|
|
56
|
+
f"Cannot import module '{module_name}' for reference '{reference}': {exc}"
|
|
57
|
+
) from exc
|
|
58
|
+
|
|
59
|
+
member = getattr(module, name, None)
|
|
60
|
+
if member is None:
|
|
61
|
+
raise typer.BadParameter(
|
|
62
|
+
f"'{name}' does not exist in module '{module_name}'."
|
|
63
|
+
)
|
|
64
|
+
return member, name, module_name
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_model_class(reference: str) -> type[pyd.BaseModel]:
|
|
68
|
+
"""Parse ``<module>:<name>`` into a Pydantic model class."""
|
|
69
|
+
member, name, module_name = import_member(reference)
|
|
70
|
+
if not isinstance(member, type) or not issubclass(member, pyd.BaseModel):
|
|
71
|
+
raise typer.BadParameter(
|
|
72
|
+
f"'{name}' in module '{module_name}' is not a Pydantic model."
|
|
73
|
+
)
|
|
74
|
+
return member
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_callable(reference: str) -> Callable[..., Any]:
|
|
78
|
+
"""Parse ``<module>:<name>`` into a callable."""
|
|
79
|
+
member, name, module_name = import_member(reference)
|
|
80
|
+
if not callable(member):
|
|
81
|
+
raise typer.BadParameter(
|
|
82
|
+
f"'{name}' in module '{module_name}' is not callable."
|
|
83
|
+
)
|
|
84
|
+
return cast(Callable[..., Any], member)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_csv_paths(value: str) -> list[str]:
|
|
88
|
+
"""Parse a comma-separated list of paths, dropping empty entries."""
|
|
89
|
+
return [item.strip() for item in value.split(",") if item.strip()]
|