devnomads 0.2.0__tar.gz → 0.2.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.
- devnomads-0.2.1/.gitlab-ci.yml +82 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/PKG-INFO +2 -2
- {devnomads-0.2.0 → devnomads-0.2.1}/pyproject.toml +2 -2
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/client.py +1 -1
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_client.py +50 -0
- devnomads-0.2.1/tools/bump_version.py +33 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/uv.lock +1 -1
- devnomads-0.2.0/.gitlab-ci.yml +0 -43
- {devnomads-0.2.0 → devnomads-0.2.1}/.flake8 +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/.gitignore +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/Makefile +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/README.md +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/openapi.json +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/__init__.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/__init__.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/challenge_server.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/dns01.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/errors.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/http01.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/keys.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/verify.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/__init__.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/_services.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/client.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/credentials.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/errors.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/__init__.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/errors.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/names.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/zones.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/py.typed +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/conftest.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_challenges.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_keys.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_client.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_credentials.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_dns.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_names.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_services.py +0 -0
- {devnomads-0.2.0 → devnomads-0.2.1}/tools/generate.py +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- check
|
|
3
|
+
- test
|
|
4
|
+
- publish
|
|
5
|
+
|
|
6
|
+
default:
|
|
7
|
+
image: ghcr.io/astral-sh/uv:python3.14-alpine
|
|
8
|
+
before_script:
|
|
9
|
+
# dev tools + the acme extra, so linting and tests cover devnomads.acme.
|
|
10
|
+
- uv sync --dev --extra acme
|
|
11
|
+
|
|
12
|
+
check:
|
|
13
|
+
stage: check
|
|
14
|
+
rules:
|
|
15
|
+
- if: $CI_PIPELINE_SOURCE != "trigger"
|
|
16
|
+
script:
|
|
17
|
+
- uv run python tools/generate.py --check
|
|
18
|
+
- uv run black --check .
|
|
19
|
+
- uv run isort --check-only .
|
|
20
|
+
- uv run flake8 src tests
|
|
21
|
+
- uv run bandit -r src
|
|
22
|
+
- uv run semgrep scan --quiet --error --config p/python src
|
|
23
|
+
- uv run mypy src
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
stage: test
|
|
27
|
+
rules:
|
|
28
|
+
- if: $CI_PIPELINE_SOURCE != "trigger"
|
|
29
|
+
parallel:
|
|
30
|
+
matrix:
|
|
31
|
+
- PYTHON: ["3.10", "3.14"]
|
|
32
|
+
image: ghcr.io/astral-sh/uv:python$PYTHON-alpine
|
|
33
|
+
script:
|
|
34
|
+
- uv run pytest -v
|
|
35
|
+
|
|
36
|
+
# Runs on version tags (v0.1.0). PYPI_TOKEN (masked) and
|
|
37
|
+
# PYPI_PUBLISH_URL are CI variables pointing at pypi.org; the
|
|
38
|
+
# PYPI_TEST_* variants hold the test.pypi.org equivalents - swap them
|
|
39
|
+
# in to rehearse a release without publishing for real.
|
|
40
|
+
publish:
|
|
41
|
+
stage: publish
|
|
42
|
+
rules:
|
|
43
|
+
- if: $CI_COMMIT_TAG =~ /^v/
|
|
44
|
+
before_script: []
|
|
45
|
+
script:
|
|
46
|
+
- uv build
|
|
47
|
+
- uv publish --publish-url "$PYPI_PUBLISH_URL" --token "$PYPI_TOKEN"
|
|
48
|
+
|
|
49
|
+
# Triggered by the DevNomads API deploy pipeline (a pipeline trigger
|
|
50
|
+
# token). Refreshes the OpenAPI spec, regenerates the SDK, and - only if
|
|
51
|
+
# the spec actually changed - auto-bumps the patch version, commits the
|
|
52
|
+
# regenerated code back, and pushes a tag. The publish job above turns
|
|
53
|
+
# that tag into a PyPI release. Runs only on a trigger, so it never
|
|
54
|
+
# recurses on the branch/tag pushes it creates.
|
|
55
|
+
#
|
|
56
|
+
# Requires CI variables:
|
|
57
|
+
# GITLAB_PUSH_TOKEN - project/group access token (Maintainer role,
|
|
58
|
+
# write_repository scope) allowed to push the default branch and tags.
|
|
59
|
+
# PYPI_TOKEN / PYPI_PUBLISH_URL - used by the publish job on the tag.
|
|
60
|
+
auto-release:
|
|
61
|
+
stage: check
|
|
62
|
+
rules:
|
|
63
|
+
- if: $CI_PIPELINE_SOURCE == "trigger"
|
|
64
|
+
resource_group: auto-release
|
|
65
|
+
script:
|
|
66
|
+
- apk add --no-cache git curl
|
|
67
|
+
- curl -sf "https://api.devnomads.nl/docs?api-docs.json" -o openapi.json
|
|
68
|
+
- uv run python tools/generate.py
|
|
69
|
+
- uv run black -q . && uv run isort -q .
|
|
70
|
+
- |
|
|
71
|
+
if git diff --quiet -- openapi.json src/devnomads/api/_services.py; then
|
|
72
|
+
echo "spec unchanged; nothing to release"; exit 0
|
|
73
|
+
fi
|
|
74
|
+
- NEW=$(uv run python tools/bump_version.py)
|
|
75
|
+
- uv lock
|
|
76
|
+
- git config user.name "devnomads-ci"
|
|
77
|
+
- git config user.email "ci@devnomads.nl"
|
|
78
|
+
- git add openapi.json src/devnomads/api/_services.py pyproject.toml uv.lock
|
|
79
|
+
- 'git commit -m "chore: auto-regenerate sdk from api spec (v${NEW})"'
|
|
80
|
+
- git tag -a "v${NEW}" -m "v${NEW}"
|
|
81
|
+
- git remote set-url origin "https://oauth2:${GITLAB_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
|
82
|
+
- git push origin "HEAD:${CI_DEFAULT_BRANCH}" --follow-tags
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devnomads
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Python client for the DevNomads API (transport, DNS, ACME)
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python client library for the DevNomads API (transport, DNS, ACME)
|
|
5
5
|
Project-URL: Homepage, https://devnomads.nl
|
|
6
6
|
Author-email: Loek Geleijn <support@devnomads.nl>
|
|
7
7
|
License: MIT
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devnomads"
|
|
7
|
-
version = "0.2.
|
|
8
|
-
description = "Python client for the DevNomads API (transport, DNS, ACME)"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Python client library for the DevNomads API (transport, DNS, ACME)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -76,7 +76,7 @@ class AcmeClient:
|
|
|
76
76
|
directory = messages.Directory.from_json(net.get(self.directory_url).json())
|
|
77
77
|
client = acme_client.ClientV2(directory, net)
|
|
78
78
|
|
|
79
|
-
new_account = messages.
|
|
79
|
+
new_account = messages.NewRegistration(
|
|
80
80
|
contact=(f"mailto:{self.contact_email}",) if self.contact_email else (),
|
|
81
81
|
terms_of_service_agreed=True,
|
|
82
82
|
only_return_existing=False,
|
|
@@ -62,3 +62,53 @@ def test_split_fullchain_no_marker():
|
|
|
62
62
|
leaf_pem, chain_pem = _split_fullchain("garbage")
|
|
63
63
|
assert leaf_pem == "garbage"
|
|
64
64
|
assert chain_pem == ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_init_client_registers_with_new_registration(monkeypatch, tmp_path):
|
|
68
|
+
"""_init_client must build a real acme.messages.NewRegistration and hand
|
|
69
|
+
it to new_account; this exercises the message construction that a wrong
|
|
70
|
+
class name (e.g. the non-existent NewAccount) would break."""
|
|
71
|
+
|
|
72
|
+
from acme import messages
|
|
73
|
+
|
|
74
|
+
from devnomads.acme import client as client_mod
|
|
75
|
+
|
|
76
|
+
captured = {}
|
|
77
|
+
|
|
78
|
+
class FakeResponse:
|
|
79
|
+
def json(self):
|
|
80
|
+
return {}
|
|
81
|
+
|
|
82
|
+
class FakeNetwork:
|
|
83
|
+
def __init__(self, *args, **kwargs):
|
|
84
|
+
self.account = None
|
|
85
|
+
|
|
86
|
+
def get(self, url):
|
|
87
|
+
return FakeResponse()
|
|
88
|
+
|
|
89
|
+
class FakeClientV2:
|
|
90
|
+
def __init__(self, directory, net):
|
|
91
|
+
self.net = net
|
|
92
|
+
|
|
93
|
+
def new_account(self, registration):
|
|
94
|
+
captured["registration"] = registration
|
|
95
|
+
return SimpleNamespace(uri="https://acme.example/acct/1")
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr(
|
|
98
|
+
client_mod, "load_or_create_account_key", lambda *a, **k: object()
|
|
99
|
+
)
|
|
100
|
+
monkeypatch.setattr(client_mod.acme_client, "ClientNetwork", FakeNetwork)
|
|
101
|
+
monkeypatch.setattr(client_mod.acme_client, "ClientV2", FakeClientV2)
|
|
102
|
+
monkeypatch.setattr(
|
|
103
|
+
client_mod.messages.Directory, "from_json", lambda data: object()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
ac = client_mod.AcmeClient(
|
|
107
|
+
str(tmp_path / "account.pem"), contact_email="ops@example.com"
|
|
108
|
+
)
|
|
109
|
+
client, _account_key = ac._init_client()
|
|
110
|
+
|
|
111
|
+
registration = captured["registration"]
|
|
112
|
+
assert isinstance(registration, messages.NewRegistration)
|
|
113
|
+
assert registration.contact == ("mailto:ops@example.com",)
|
|
114
|
+
assert client.net.account is not None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Bump the patch version in pyproject.toml and print the new version.
|
|
3
|
+
|
|
4
|
+
Used by the auto-release CI job so each spec-driven regeneration gets a
|
|
5
|
+
fresh, monotonically increasing version (PyPI versions are immutable).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import pathlib
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
PYPROJECT = pathlib.Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> int:
|
|
18
|
+
text = PYPROJECT.read_text()
|
|
19
|
+
match = re.search(r'^version = "(\d+)\.(\d+)\.(\d+)"', text, re.MULTILINE)
|
|
20
|
+
if not match:
|
|
21
|
+
print("error: could not find a version in pyproject.toml", file=sys.stderr)
|
|
22
|
+
return 1
|
|
23
|
+
major, minor, patch = (int(g) for g in match.groups())
|
|
24
|
+
new_version = f"{major}.{minor}.{patch + 1}"
|
|
25
|
+
PYPROJECT.write_text(
|
|
26
|
+
text[: match.start()] + f'version = "{new_version}"' + text[match.end() :]
|
|
27
|
+
)
|
|
28
|
+
print(new_version)
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
raise SystemExit(main())
|
devnomads-0.2.0/.gitlab-ci.yml
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
stages:
|
|
2
|
-
- check
|
|
3
|
-
- test
|
|
4
|
-
- publish
|
|
5
|
-
|
|
6
|
-
default:
|
|
7
|
-
image: ghcr.io/astral-sh/uv:python3.14-alpine
|
|
8
|
-
before_script:
|
|
9
|
-
# dev tools + the acme extra, so linting and tests cover devnomads.acme.
|
|
10
|
-
- uv sync --dev --extra acme
|
|
11
|
-
|
|
12
|
-
check:
|
|
13
|
-
stage: check
|
|
14
|
-
script:
|
|
15
|
-
- uv run python tools/generate.py --check
|
|
16
|
-
- uv run black --check .
|
|
17
|
-
- uv run isort --check-only .
|
|
18
|
-
- uv run flake8 src tests
|
|
19
|
-
- uv run bandit -r src
|
|
20
|
-
- uv run semgrep scan --quiet --error --config p/python src
|
|
21
|
-
- uv run mypy src
|
|
22
|
-
|
|
23
|
-
test:
|
|
24
|
-
stage: test
|
|
25
|
-
parallel:
|
|
26
|
-
matrix:
|
|
27
|
-
- PYTHON: ["3.10", "3.14"]
|
|
28
|
-
image: ghcr.io/astral-sh/uv:python$PYTHON-alpine
|
|
29
|
-
script:
|
|
30
|
-
- uv run pytest -v
|
|
31
|
-
|
|
32
|
-
# Runs on version tags (v0.1.0). PYPI_TOKEN (masked) and
|
|
33
|
-
# PYPI_PUBLISH_URL are CI variables pointing at pypi.org; the
|
|
34
|
-
# PYPI_TEST_* variants hold the test.pypi.org equivalents - swap them
|
|
35
|
-
# in to rehearse a release without publishing for real.
|
|
36
|
-
publish:
|
|
37
|
-
stage: publish
|
|
38
|
-
rules:
|
|
39
|
-
- if: $CI_COMMIT_TAG =~ /^v/
|
|
40
|
-
before_script: []
|
|
41
|
-
script:
|
|
42
|
-
- uv build
|
|
43
|
-
- uv publish --publish-url "$PYPI_PUBLISH_URL" --token "$PYPI_TOKEN"
|
|
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
|
|
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
|
|
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
|