portolan-python 0.1.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.
- portolan_python-0.1.0/.coverage +0 -0
- portolan_python-0.1.0/.github/workflows/release.yml +65 -0
- portolan_python-0.1.0/.gitignore +6 -0
- portolan_python-0.1.0/Makefile +29 -0
- portolan_python-0.1.0/PKG-INFO +224 -0
- portolan_python-0.1.0/README.md +210 -0
- portolan_python-0.1.0/docs/development.md +40 -0
- portolan_python-0.1.0/docs/distribution.md +54 -0
- portolan_python-0.1.0/docs/examples.md +134 -0
- portolan_python-0.1.0/docs/scope.md +31 -0
- portolan_python-0.1.0/pyproject.toml +53 -0
- portolan_python-0.1.0/src/portolan/__init__.py +29 -0
- portolan_python-0.1.0/src/portolan/catalog.py +307 -0
- portolan_python-0.1.0/src/portolan/py.typed +1 -0
- portolan_python-0.1.0/src/portolan/registry.py +152 -0
- portolan_python-0.1.0/src/portolan/validation.py +165 -0
- portolan_python-0.1.0/tests/test_catalog_api.py +593 -0
- portolan_python-0.1.0/tests/test_registry_api.py +282 -0
- portolan_python-0.1.0/uv.lock +618 -0
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
cache: pip
|
|
20
|
+
|
|
21
|
+
- name: Install build and test tools
|
|
22
|
+
run: python -m pip install --upgrade build twine ".[dev]"
|
|
23
|
+
|
|
24
|
+
- name: Check tag and package versions
|
|
25
|
+
shell: bash
|
|
26
|
+
run: |
|
|
27
|
+
package_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
28
|
+
test "${GITHUB_REF_NAME}" = "v${package_version}"
|
|
29
|
+
|
|
30
|
+
- name: Test
|
|
31
|
+
run: pytest
|
|
32
|
+
|
|
33
|
+
- name: Build distributions
|
|
34
|
+
run: python -m build
|
|
35
|
+
|
|
36
|
+
- name: Check distributions
|
|
37
|
+
run: python -m twine check dist/*
|
|
38
|
+
|
|
39
|
+
- name: Preserve distributions for PyPI
|
|
40
|
+
uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: distributions
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
- name: Create GitHub release
|
|
46
|
+
env:
|
|
47
|
+
GH_TOKEN: ${{ github.token }}
|
|
48
|
+
run: gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --verify-tag
|
|
49
|
+
|
|
50
|
+
publish-pypi:
|
|
51
|
+
needs: release
|
|
52
|
+
if: vars.PUBLISH_PYPI == 'true'
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
environment: pypi
|
|
55
|
+
permissions:
|
|
56
|
+
id-token: write
|
|
57
|
+
steps:
|
|
58
|
+
- name: Download distributions
|
|
59
|
+
uses: actions/download-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: distributions
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
- name: Publish to PyPI
|
|
65
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.PHONY: help setup test coverage lint typecheck check build clean
|
|
2
|
+
|
|
3
|
+
UV ?= uv
|
|
4
|
+
|
|
5
|
+
help:
|
|
6
|
+
@printf '%s\n' 'Targets: setup test coverage lint typecheck check build clean'
|
|
7
|
+
|
|
8
|
+
setup:
|
|
9
|
+
$(UV) sync --extra dev
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
$(UV) run --extra dev pytest
|
|
13
|
+
|
|
14
|
+
coverage:
|
|
15
|
+
$(UV) run --extra dev pytest --cov=portolan --cov-report=term-missing
|
|
16
|
+
|
|
17
|
+
lint:
|
|
18
|
+
$(UV) run --extra dev ruff check .
|
|
19
|
+
|
|
20
|
+
typecheck:
|
|
21
|
+
$(UV) run --extra dev mypy
|
|
22
|
+
|
|
23
|
+
check: lint typecheck test
|
|
24
|
+
|
|
25
|
+
build:
|
|
26
|
+
$(UV) build
|
|
27
|
+
|
|
28
|
+
clean:
|
|
29
|
+
rm -rf dist build *.egg-info .pytest_cache .ruff_cache .mypy_cache htmlcov
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: portolan-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight Python implementation of the Portolan specification.
|
|
5
|
+
Author: Portolan contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: mypy>=1.18.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: ruff>=0.13.0; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Portolan Python
|
|
16
|
+
|
|
17
|
+
A lightweight Python implementation of the Portolan specification.
|
|
18
|
+
|
|
19
|
+
`portolan-python` provides the core domain model, catalog access, validation, and conformance APIs required to work with Portolan catalogs from Python applications.
|
|
20
|
+
|
|
21
|
+
The project intentionally focuses on **Portolan itself**, rather than on geospatial data processing.
|
|
22
|
+
|
|
23
|
+
It does not aim to replace GDAL, Rasterio, GeoPandas, GeoParquet tooling, tile generators, GIS servers, or other specialized geospatial software.
|
|
24
|
+
|
|
25
|
+
## Motivation
|
|
26
|
+
|
|
27
|
+
Portolan defines an opinionated way of organizing and describing cloud-native geospatial data using existing standards and formats such as STAC, GeoParquet, COG, PMTiles, COPC, and Zarr.
|
|
28
|
+
|
|
29
|
+
Applications integrating with Portolan need a reliable implementation of that contract.
|
|
30
|
+
|
|
31
|
+
Without a reusable core library, every integration would need to independently implement:
|
|
32
|
+
|
|
33
|
+
* Portolan catalog semantics;
|
|
34
|
+
* STAC traversal;
|
|
35
|
+
* collection and asset handling;
|
|
36
|
+
* Portolan-specific requirements;
|
|
37
|
+
* link and HREF resolution;
|
|
38
|
+
* metadata validation;
|
|
39
|
+
* conformance rules;
|
|
40
|
+
* serialization and deserialization.
|
|
41
|
+
|
|
42
|
+
`portolan-python` provides that reusable implementation.
|
|
43
|
+
|
|
44
|
+
The core design principle is:
|
|
45
|
+
|
|
46
|
+
> **Portolan defines the contract. Specialized tools implement data processing.**
|
|
47
|
+
|
|
48
|
+
For example, this library may determine that an asset is a GeoParquet asset and expose its URI and metadata. It does not need to read the GeoParquet rows itself.
|
|
49
|
+
|
|
50
|
+
Likewise, it may identify a COG asset without becoming a raster processing library.
|
|
51
|
+
|
|
52
|
+
## Scope
|
|
53
|
+
|
|
54
|
+
`portolan-python` is responsible for the Portolan domain model and specification semantics.
|
|
55
|
+
|
|
56
|
+
Expected responsibilities include:
|
|
57
|
+
|
|
58
|
+
* opening Portolan catalogs;
|
|
59
|
+
* creating Portolan catalogs;
|
|
60
|
+
* reading and writing Portolan/STAC metadata;
|
|
61
|
+
* navigating catalogs, collections, items, assets, and links;
|
|
62
|
+
* resolving relative and absolute HREFs;
|
|
63
|
+
* exposing Portolan extensions and metadata;
|
|
64
|
+
* validating Portolan structures;
|
|
65
|
+
* checking Portolan conformance;
|
|
66
|
+
* exposing asset type and media-type information;
|
|
67
|
+
* handling specification versions;
|
|
68
|
+
* providing stable Python APIs for applications built on Portolan.
|
|
69
|
+
|
|
70
|
+
A conceptual API may look like:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from portolan import Catalog
|
|
74
|
+
|
|
75
|
+
catalog = Catalog.open("https://example.com/catalog.json")
|
|
76
|
+
|
|
77
|
+
for collection in catalog.collections():
|
|
78
|
+
print(collection.id)
|
|
79
|
+
|
|
80
|
+
for asset in collection.assets():
|
|
81
|
+
print(asset.href)
|
|
82
|
+
print(asset.media_type)
|
|
83
|
+
print(asset.roles)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Validation should similarly be available programmatically:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from portolan import Catalog, Validator
|
|
90
|
+
|
|
91
|
+
catalog = Catalog.open("./catalog.json")
|
|
92
|
+
|
|
93
|
+
result = Validator.validate(catalog)
|
|
94
|
+
|
|
95
|
+
if not result.valid:
|
|
96
|
+
for error in result.errors:
|
|
97
|
+
print(error)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The exact API will evolve during implementation, but it should remain small, explicit, typed, and independent from any CLI.
|
|
101
|
+
|
|
102
|
+
## Non-goals
|
|
103
|
+
|
|
104
|
+
This project should **not** become a general-purpose geospatial processing library.
|
|
105
|
+
|
|
106
|
+
In particular, the core should not be responsible for:
|
|
107
|
+
|
|
108
|
+
* reading GeoParquet feature data;
|
|
109
|
+
* writing GeoParquet datasets;
|
|
110
|
+
* converting Shapefile to GeoParquet;
|
|
111
|
+
* creating COGs;
|
|
112
|
+
* reading raster pixels;
|
|
113
|
+
* creating PMTiles;
|
|
114
|
+
* processing COPC;
|
|
115
|
+
* converting MrSID or ECW;
|
|
116
|
+
* extracting data from WFS;
|
|
117
|
+
* extracting data from ArcGIS;
|
|
118
|
+
* extracting data from CARTO;
|
|
119
|
+
* publishing data to GeoServer;
|
|
120
|
+
* running pygeoapi;
|
|
121
|
+
* managing QGIS projects.
|
|
122
|
+
|
|
123
|
+
Those capabilities belong to specialized libraries, applications, or optional integrations.
|
|
124
|
+
|
|
125
|
+
Portolan should be opinionated about **what constitutes a conformant Portolan catalog and asset**, not unnecessarily opinionated about **which software must produce or consume those assets**.
|
|
126
|
+
|
|
127
|
+
## Architecture
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
Portolan Specification
|
|
131
|
+
│
|
|
132
|
+
▼
|
|
133
|
+
portolan-python
|
|
134
|
+
┌────────────────────┐
|
|
135
|
+
│ Domain model │
|
|
136
|
+
│ Catalog access │
|
|
137
|
+
│ STAC semantics │
|
|
138
|
+
│ Portolan semantics │
|
|
139
|
+
│ HREF resolution │
|
|
140
|
+
│ Validation │
|
|
141
|
+
│ Conformance │
|
|
142
|
+
└─────────┬──────────┘
|
|
143
|
+
│
|
|
144
|
+
┌──────────────┼──────────────┐
|
|
145
|
+
▼ ▼ ▼
|
|
146
|
+
portolan-cli portolan-geoserver other apps
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Relationship with `portolan-cli`
|
|
150
|
+
|
|
151
|
+
`portolan-cli` should consume this library rather than implement Portolan domain logic itself.
|
|
152
|
+
|
|
153
|
+
For example:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
portolan validate
|
|
157
|
+
portolan inspect
|
|
158
|
+
portolan registry list
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
should be CLI representations of APIs provided by the Portolan Python ecosystem.
|
|
162
|
+
|
|
163
|
+
The CLI should remain an interface layer.
|
|
164
|
+
|
|
165
|
+
## Relationship with `portolan-geoserver`
|
|
166
|
+
|
|
167
|
+
`portolan-geoserver` uses this library to understand Portolan catalogs and uses `python-geoservercloud` to interact with GeoServer.
|
|
168
|
+
|
|
169
|
+
The responsibilities remain separated:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
portolan-python
|
|
173
|
+
│
|
|
174
|
+
│ understands Portolan
|
|
175
|
+
▼
|
|
176
|
+
portolan-geoserver
|
|
177
|
+
│
|
|
178
|
+
│ maps Portolan resources to GeoServer
|
|
179
|
+
▼
|
|
180
|
+
python-geoservercloud
|
|
181
|
+
│
|
|
182
|
+
│ manages GeoServer
|
|
183
|
+
▼
|
|
184
|
+
GeoServer
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`portolan-python` therefore contains no GeoServer-specific logic.
|
|
188
|
+
|
|
189
|
+
## Relationship with `portolan-java`
|
|
190
|
+
|
|
191
|
+
`portolan-java` is the Java counterpart of this project.
|
|
192
|
+
|
|
193
|
+
The two libraries should implement the same **conceptual Portolan contract**, while following the conventions of their respective languages.
|
|
194
|
+
|
|
195
|
+
They should not necessarily expose identical classes or method signatures.
|
|
196
|
+
|
|
197
|
+
The Portolan specification remains the source of truth.
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
Portolan Specification
|
|
201
|
+
/ \
|
|
202
|
+
/ \
|
|
203
|
+
portolan-python portolan-java
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Design principles
|
|
207
|
+
|
|
208
|
+
1. Specification first.
|
|
209
|
+
2. Small and stable public API.
|
|
210
|
+
3. No CLI dependency.
|
|
211
|
+
4. No GIS server dependency.
|
|
212
|
+
5. No mandatory geospatial processing stack.
|
|
213
|
+
6. Specialized formats are represented, not reimplemented.
|
|
214
|
+
7. External applications should not need to reimplement Portolan semantics.
|
|
215
|
+
8. The library should be suitable as a dependency of long-lived applications.
|
|
216
|
+
|
|
217
|
+
## Local documentation
|
|
218
|
+
|
|
219
|
+
- [Scope](docs/scope.md) defines what belongs in this core library.
|
|
220
|
+
- [Examples](docs/examples.md) shows basic API usage.
|
|
221
|
+
- [Development](docs/development.md) lists setup, test, and build commands.
|
|
222
|
+
- [Distribution](docs/distribution.md) explains releases, `pip`, and PyPI publishing.
|
|
223
|
+
|
|
224
|
+
---
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Portolan Python
|
|
2
|
+
|
|
3
|
+
A lightweight Python implementation of the Portolan specification.
|
|
4
|
+
|
|
5
|
+
`portolan-python` provides the core domain model, catalog access, validation, and conformance APIs required to work with Portolan catalogs from Python applications.
|
|
6
|
+
|
|
7
|
+
The project intentionally focuses on **Portolan itself**, rather than on geospatial data processing.
|
|
8
|
+
|
|
9
|
+
It does not aim to replace GDAL, Rasterio, GeoPandas, GeoParquet tooling, tile generators, GIS servers, or other specialized geospatial software.
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
Portolan defines an opinionated way of organizing and describing cloud-native geospatial data using existing standards and formats such as STAC, GeoParquet, COG, PMTiles, COPC, and Zarr.
|
|
14
|
+
|
|
15
|
+
Applications integrating with Portolan need a reliable implementation of that contract.
|
|
16
|
+
|
|
17
|
+
Without a reusable core library, every integration would need to independently implement:
|
|
18
|
+
|
|
19
|
+
* Portolan catalog semantics;
|
|
20
|
+
* STAC traversal;
|
|
21
|
+
* collection and asset handling;
|
|
22
|
+
* Portolan-specific requirements;
|
|
23
|
+
* link and HREF resolution;
|
|
24
|
+
* metadata validation;
|
|
25
|
+
* conformance rules;
|
|
26
|
+
* serialization and deserialization.
|
|
27
|
+
|
|
28
|
+
`portolan-python` provides that reusable implementation.
|
|
29
|
+
|
|
30
|
+
The core design principle is:
|
|
31
|
+
|
|
32
|
+
> **Portolan defines the contract. Specialized tools implement data processing.**
|
|
33
|
+
|
|
34
|
+
For example, this library may determine that an asset is a GeoParquet asset and expose its URI and metadata. It does not need to read the GeoParquet rows itself.
|
|
35
|
+
|
|
36
|
+
Likewise, it may identify a COG asset without becoming a raster processing library.
|
|
37
|
+
|
|
38
|
+
## Scope
|
|
39
|
+
|
|
40
|
+
`portolan-python` is responsible for the Portolan domain model and specification semantics.
|
|
41
|
+
|
|
42
|
+
Expected responsibilities include:
|
|
43
|
+
|
|
44
|
+
* opening Portolan catalogs;
|
|
45
|
+
* creating Portolan catalogs;
|
|
46
|
+
* reading and writing Portolan/STAC metadata;
|
|
47
|
+
* navigating catalogs, collections, items, assets, and links;
|
|
48
|
+
* resolving relative and absolute HREFs;
|
|
49
|
+
* exposing Portolan extensions and metadata;
|
|
50
|
+
* validating Portolan structures;
|
|
51
|
+
* checking Portolan conformance;
|
|
52
|
+
* exposing asset type and media-type information;
|
|
53
|
+
* handling specification versions;
|
|
54
|
+
* providing stable Python APIs for applications built on Portolan.
|
|
55
|
+
|
|
56
|
+
A conceptual API may look like:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from portolan import Catalog
|
|
60
|
+
|
|
61
|
+
catalog = Catalog.open("https://example.com/catalog.json")
|
|
62
|
+
|
|
63
|
+
for collection in catalog.collections():
|
|
64
|
+
print(collection.id)
|
|
65
|
+
|
|
66
|
+
for asset in collection.assets():
|
|
67
|
+
print(asset.href)
|
|
68
|
+
print(asset.media_type)
|
|
69
|
+
print(asset.roles)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Validation should similarly be available programmatically:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from portolan import Catalog, Validator
|
|
76
|
+
|
|
77
|
+
catalog = Catalog.open("./catalog.json")
|
|
78
|
+
|
|
79
|
+
result = Validator.validate(catalog)
|
|
80
|
+
|
|
81
|
+
if not result.valid:
|
|
82
|
+
for error in result.errors:
|
|
83
|
+
print(error)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The exact API will evolve during implementation, but it should remain small, explicit, typed, and independent from any CLI.
|
|
87
|
+
|
|
88
|
+
## Non-goals
|
|
89
|
+
|
|
90
|
+
This project should **not** become a general-purpose geospatial processing library.
|
|
91
|
+
|
|
92
|
+
In particular, the core should not be responsible for:
|
|
93
|
+
|
|
94
|
+
* reading GeoParquet feature data;
|
|
95
|
+
* writing GeoParquet datasets;
|
|
96
|
+
* converting Shapefile to GeoParquet;
|
|
97
|
+
* creating COGs;
|
|
98
|
+
* reading raster pixels;
|
|
99
|
+
* creating PMTiles;
|
|
100
|
+
* processing COPC;
|
|
101
|
+
* converting MrSID or ECW;
|
|
102
|
+
* extracting data from WFS;
|
|
103
|
+
* extracting data from ArcGIS;
|
|
104
|
+
* extracting data from CARTO;
|
|
105
|
+
* publishing data to GeoServer;
|
|
106
|
+
* running pygeoapi;
|
|
107
|
+
* managing QGIS projects.
|
|
108
|
+
|
|
109
|
+
Those capabilities belong to specialized libraries, applications, or optional integrations.
|
|
110
|
+
|
|
111
|
+
Portolan should be opinionated about **what constitutes a conformant Portolan catalog and asset**, not unnecessarily opinionated about **which software must produce or consume those assets**.
|
|
112
|
+
|
|
113
|
+
## Architecture
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
Portolan Specification
|
|
117
|
+
│
|
|
118
|
+
▼
|
|
119
|
+
portolan-python
|
|
120
|
+
┌────────────────────┐
|
|
121
|
+
│ Domain model │
|
|
122
|
+
│ Catalog access │
|
|
123
|
+
│ STAC semantics │
|
|
124
|
+
│ Portolan semantics │
|
|
125
|
+
│ HREF resolution │
|
|
126
|
+
│ Validation │
|
|
127
|
+
│ Conformance │
|
|
128
|
+
└─────────┬──────────┘
|
|
129
|
+
│
|
|
130
|
+
┌──────────────┼──────────────┐
|
|
131
|
+
▼ ▼ ▼
|
|
132
|
+
portolan-cli portolan-geoserver other apps
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Relationship with `portolan-cli`
|
|
136
|
+
|
|
137
|
+
`portolan-cli` should consume this library rather than implement Portolan domain logic itself.
|
|
138
|
+
|
|
139
|
+
For example:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
portolan validate
|
|
143
|
+
portolan inspect
|
|
144
|
+
portolan registry list
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
should be CLI representations of APIs provided by the Portolan Python ecosystem.
|
|
148
|
+
|
|
149
|
+
The CLI should remain an interface layer.
|
|
150
|
+
|
|
151
|
+
## Relationship with `portolan-geoserver`
|
|
152
|
+
|
|
153
|
+
`portolan-geoserver` uses this library to understand Portolan catalogs and uses `python-geoservercloud` to interact with GeoServer.
|
|
154
|
+
|
|
155
|
+
The responsibilities remain separated:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
portolan-python
|
|
159
|
+
│
|
|
160
|
+
│ understands Portolan
|
|
161
|
+
▼
|
|
162
|
+
portolan-geoserver
|
|
163
|
+
│
|
|
164
|
+
│ maps Portolan resources to GeoServer
|
|
165
|
+
▼
|
|
166
|
+
python-geoservercloud
|
|
167
|
+
│
|
|
168
|
+
│ manages GeoServer
|
|
169
|
+
▼
|
|
170
|
+
GeoServer
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`portolan-python` therefore contains no GeoServer-specific logic.
|
|
174
|
+
|
|
175
|
+
## Relationship with `portolan-java`
|
|
176
|
+
|
|
177
|
+
`portolan-java` is the Java counterpart of this project.
|
|
178
|
+
|
|
179
|
+
The two libraries should implement the same **conceptual Portolan contract**, while following the conventions of their respective languages.
|
|
180
|
+
|
|
181
|
+
They should not necessarily expose identical classes or method signatures.
|
|
182
|
+
|
|
183
|
+
The Portolan specification remains the source of truth.
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
Portolan Specification
|
|
187
|
+
/ \
|
|
188
|
+
/ \
|
|
189
|
+
portolan-python portolan-java
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Design principles
|
|
193
|
+
|
|
194
|
+
1. Specification first.
|
|
195
|
+
2. Small and stable public API.
|
|
196
|
+
3. No CLI dependency.
|
|
197
|
+
4. No GIS server dependency.
|
|
198
|
+
5. No mandatory geospatial processing stack.
|
|
199
|
+
6. Specialized formats are represented, not reimplemented.
|
|
200
|
+
7. External applications should not need to reimplement Portolan semantics.
|
|
201
|
+
8. The library should be suitable as a dependency of long-lived applications.
|
|
202
|
+
|
|
203
|
+
## Local documentation
|
|
204
|
+
|
|
205
|
+
- [Scope](docs/scope.md) defines what belongs in this core library.
|
|
206
|
+
- [Examples](docs/examples.md) shows basic API usage.
|
|
207
|
+
- [Development](docs/development.md) lists setup, test, and build commands.
|
|
208
|
+
- [Distribution](docs/distribution.md) explains releases, `pip`, and PyPI publishing.
|
|
209
|
+
|
|
210
|
+
---
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
The project uses `uv` for dependency management and build isolation.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This installs runtime and development dependencies from `pyproject.toml`.
|
|
12
|
+
|
|
13
|
+
## Test and check
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
make test
|
|
17
|
+
make lint
|
|
18
|
+
make typecheck
|
|
19
|
+
make check
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`make check` runs lint, type checking, and the test suite. The test command also
|
|
23
|
+
enforces the configured coverage floor.
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
make build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The build target creates source and wheel distributions in `dist/`.
|
|
32
|
+
|
|
33
|
+
## Clean local artifacts
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
make clean
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This removes local build, cache, and coverage directories. It does not remove
|
|
40
|
+
source files or the virtual environment.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Distribution
|
|
2
|
+
|
|
3
|
+
Tagged releases provide immutable Python source and wheel archives. PyPI is the
|
|
4
|
+
preferred package index once its trusted publisher is configured.
|
|
5
|
+
|
|
6
|
+
## Release artifacts
|
|
7
|
+
|
|
8
|
+
Push a tag that matches the version in `pyproject.toml`:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git tag v0.1.0
|
|
12
|
+
git push origin v0.1.0
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The release workflow runs the tests, builds both distributions, checks their
|
|
16
|
+
metadata, and creates a GitHub release. It attaches the `.whl` and `.tar.gz`
|
|
17
|
+
files to that release.
|
|
18
|
+
|
|
19
|
+
The workflow rejects a tag that does not match the project version. Change the
|
|
20
|
+
version in `pyproject.toml` before the next release.
|
|
21
|
+
|
|
22
|
+
## Install from GitHub
|
|
23
|
+
|
|
24
|
+
Install the wheel attached to a release:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m pip install \
|
|
28
|
+
https://github.com/jemacchi/portolan-python/releases/download/v0.1.0/portolan_python-0.1.0-py3-none-any.whl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This path needs no package index account.
|
|
32
|
+
|
|
33
|
+
## Publish to PyPI
|
|
34
|
+
|
|
35
|
+
The workflow supports PyPI trusted publishing. It uses a short-lived identity
|
|
36
|
+
token, so the repository does not store a permanent PyPI API token.
|
|
37
|
+
|
|
38
|
+
Complete these steps once:
|
|
39
|
+
|
|
40
|
+
1. Create the `portolan-python` project on PyPI, or add a pending publisher.
|
|
41
|
+
2. Add a trusted GitHub publisher for `jemacchi/portolan-python`.
|
|
42
|
+
3. Set the workflow name to `release.yml` and the environment to `pypi`.
|
|
43
|
+
4. Create the `pypi` environment in the GitHub repository.
|
|
44
|
+
5. Add the repository variable `PUBLISH_PYPI` with the value `true`.
|
|
45
|
+
|
|
46
|
+
After this setup, each valid version tag also publishes to PyPI. Consumers can
|
|
47
|
+
then use the normal command:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m pip install portolan-python==0.1.0
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
PyPI does not permit replacing a published version. Increase the version before
|
|
54
|
+
you publish another release.
|