portolan-python 0.1.0__tar.gz → 0.1.1__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 → portolan_python-0.1.1}/.coverage +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/.github/workflows/release.yml +22 -5
- {portolan_python-0.1.0 → portolan_python-0.1.1}/PKG-INFO +1 -1
- {portolan_python-0.1.0 → portolan_python-0.1.1}/docs/distribution.md +4 -17
- {portolan_python-0.1.0 → portolan_python-0.1.1}/pyproject.toml +1 -1
- {portolan_python-0.1.0 → portolan_python-0.1.1}/src/portolan/registry.py +10 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/tests/test_registry_api.py +16 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/uv.lock +1 -1
- {portolan_python-0.1.0 → portolan_python-0.1.1}/.gitignore +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/Makefile +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/README.md +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/docs/development.md +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/docs/examples.md +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/docs/scope.md +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/src/portolan/__init__.py +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/src/portolan/catalog.py +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/src/portolan/py.typed +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/src/portolan/validation.py +0 -0
- {portolan_python-0.1.0 → portolan_python-0.1.1}/tests/test_catalog_api.py +0 -0
|
Binary file
|
|
@@ -4,6 +4,15 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags:
|
|
6
6
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
tag:
|
|
10
|
+
description: Existing version tag to publish
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
13
|
+
|
|
14
|
+
env:
|
|
15
|
+
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
|
|
7
16
|
|
|
8
17
|
jobs:
|
|
9
18
|
release:
|
|
@@ -12,6 +21,8 @@ jobs:
|
|
|
12
21
|
contents: write
|
|
13
22
|
steps:
|
|
14
23
|
- uses: actions/checkout@v6
|
|
24
|
+
with:
|
|
25
|
+
ref: ${{ env.RELEASE_TAG }}
|
|
15
26
|
|
|
16
27
|
- uses: actions/setup-python@v6
|
|
17
28
|
with:
|
|
@@ -25,7 +36,7 @@ jobs:
|
|
|
25
36
|
shell: bash
|
|
26
37
|
run: |
|
|
27
38
|
package_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
28
|
-
test "${
|
|
39
|
+
test "${RELEASE_TAG}" = "v${package_version}"
|
|
29
40
|
|
|
30
41
|
- name: Test
|
|
31
42
|
run: pytest
|
|
@@ -37,7 +48,7 @@ jobs:
|
|
|
37
48
|
run: python -m twine check dist/*
|
|
38
49
|
|
|
39
50
|
- name: Preserve distributions for PyPI
|
|
40
|
-
uses: actions/upload-artifact@
|
|
51
|
+
uses: actions/upload-artifact@v7
|
|
41
52
|
with:
|
|
42
53
|
name: distributions
|
|
43
54
|
path: dist/
|
|
@@ -45,18 +56,24 @@ jobs:
|
|
|
45
56
|
- name: Create GitHub release
|
|
46
57
|
env:
|
|
47
58
|
GH_TOKEN: ${{ github.token }}
|
|
48
|
-
|
|
59
|
+
shell: bash
|
|
60
|
+
run: |
|
|
61
|
+
if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
|
|
62
|
+
gh release upload "${RELEASE_TAG}" dist/* --clobber
|
|
63
|
+
else
|
|
64
|
+
gh release create "${RELEASE_TAG}" dist/* --generate-notes --verify-tag
|
|
65
|
+
fi
|
|
49
66
|
|
|
50
67
|
publish-pypi:
|
|
51
68
|
needs: release
|
|
52
|
-
if: vars.PUBLISH_PYPI == 'true'
|
|
69
|
+
if: vars.PUBLISH_PYPI == 'true' || github.event_name == 'workflow_dispatch'
|
|
53
70
|
runs-on: ubuntu-latest
|
|
54
71
|
environment: pypi
|
|
55
72
|
permissions:
|
|
56
73
|
id-token: write
|
|
57
74
|
steps:
|
|
58
75
|
- name: Download distributions
|
|
59
|
-
uses: actions/download-artifact@
|
|
76
|
+
uses: actions/download-artifact@v8
|
|
60
77
|
with:
|
|
61
78
|
name: distributions
|
|
62
79
|
path: dist/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Distribution
|
|
2
2
|
|
|
3
3
|
Tagged releases provide immutable Python source and wheel archives. PyPI is the
|
|
4
|
-
preferred package index
|
|
4
|
+
preferred package index for published versions.
|
|
5
5
|
|
|
6
6
|
## Release artifacts
|
|
7
7
|
|
|
@@ -30,25 +30,12 @@ python -m pip install \
|
|
|
30
30
|
|
|
31
31
|
This path needs no package index account.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Install from PyPI
|
|
34
34
|
|
|
35
|
-
|
|
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:
|
|
35
|
+
Install a published version from PyPI with:
|
|
48
36
|
|
|
49
37
|
```bash
|
|
50
38
|
python -m pip install portolan-python==0.1.0
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
you publish another release.
|
|
41
|
+
Omit the version constraint to install the latest published version.
|
|
@@ -73,15 +73,19 @@ def download_registry_catalog(
|
|
|
73
73
|
fetch_json: Callable[[str], JsonObject] | None = None,
|
|
74
74
|
) -> Path:
|
|
75
75
|
"""Download a published catalog snapshot for local workflows."""
|
|
76
|
+
_validate_remote_url(catalog_url)
|
|
76
77
|
fetch = fetch_json or _fetch_json
|
|
77
78
|
catalog = fetch(catalog_url)
|
|
78
79
|
catalog_id = str(catalog.get("id") or _fallback_catalog_id(catalog_url))
|
|
80
|
+
if catalog_id in {"", ".", ".."} or Path(catalog_id).name != catalog_id or "\\" in catalog_id:
|
|
81
|
+
raise ValueError(f"Catalog id must be a safe directory name: {catalog_id}")
|
|
79
82
|
catalog_root = output_dir / catalog_id
|
|
80
83
|
_write_catalog_tree(catalog_url, catalog, catalog_url, catalog_root, fetch)
|
|
81
84
|
return catalog_root
|
|
82
85
|
|
|
83
86
|
|
|
84
87
|
def _fetch_json(url: str) -> JsonObject:
|
|
88
|
+
_validate_remote_url(url)
|
|
85
89
|
request = Request(url, headers={"User-Agent": "portolan-python"})
|
|
86
90
|
with urlopen(request, timeout=30) as response:
|
|
87
91
|
data = json.loads(response.read().decode("utf-8"))
|
|
@@ -90,6 +94,12 @@ def _fetch_json(url: str) -> JsonObject:
|
|
|
90
94
|
return data
|
|
91
95
|
|
|
92
96
|
|
|
97
|
+
def _validate_remote_url(url: str) -> None:
|
|
98
|
+
parsed = urlparse(url)
|
|
99
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
100
|
+
raise ValueError(f"Registry URL must use HTTP or HTTPS: {url}")
|
|
101
|
+
|
|
102
|
+
|
|
93
103
|
def _write_catalog_tree(
|
|
94
104
|
document_url: str,
|
|
95
105
|
document: JsonObject,
|
|
@@ -280,3 +280,19 @@ def test_download_registry_catalog_handles_collections_without_asset_objects(
|
|
|
280
280
|
(catalog_root / "roads" / "collection.json").read_text(encoding="utf-8")
|
|
281
281
|
)
|
|
282
282
|
assert collection["assets"] == []
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_download_registry_catalog_rejects_id_outside_output_directory(tmp_path: Path) -> None:
|
|
286
|
+
catalog = {"type": "Catalog", "id": "../../outside", "links": []}
|
|
287
|
+
|
|
288
|
+
with pytest.raises(ValueError, match="safe directory name"):
|
|
289
|
+
download_registry_catalog(
|
|
290
|
+
"https://example.test/demo/catalog.json",
|
|
291
|
+
tmp_path,
|
|
292
|
+
fetch_json=lambda url: catalog,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_download_registry_catalog_rejects_non_http_url(tmp_path: Path) -> None:
|
|
297
|
+
with pytest.raises(ValueError, match="must use HTTP or HTTPS"):
|
|
298
|
+
download_registry_catalog("file:///tmp/catalog.json", tmp_path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|