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.
Files changed (40) hide show
  1. devnomads-0.2.1/.gitlab-ci.yml +82 -0
  2. {devnomads-0.2.0 → devnomads-0.2.1}/PKG-INFO +2 -2
  3. {devnomads-0.2.0 → devnomads-0.2.1}/pyproject.toml +2 -2
  4. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/client.py +1 -1
  5. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_client.py +50 -0
  6. devnomads-0.2.1/tools/bump_version.py +33 -0
  7. {devnomads-0.2.0 → devnomads-0.2.1}/uv.lock +1 -1
  8. devnomads-0.2.0/.gitlab-ci.yml +0 -43
  9. {devnomads-0.2.0 → devnomads-0.2.1}/.flake8 +0 -0
  10. {devnomads-0.2.0 → devnomads-0.2.1}/.gitignore +0 -0
  11. {devnomads-0.2.0 → devnomads-0.2.1}/Makefile +0 -0
  12. {devnomads-0.2.0 → devnomads-0.2.1}/README.md +0 -0
  13. {devnomads-0.2.0 → devnomads-0.2.1}/openapi.json +0 -0
  14. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/__init__.py +0 -0
  15. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/__init__.py +0 -0
  16. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/challenge_server.py +0 -0
  17. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/dns01.py +0 -0
  18. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/errors.py +0 -0
  19. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/http01.py +0 -0
  20. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/keys.py +0 -0
  21. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/acme/verify.py +0 -0
  22. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/__init__.py +0 -0
  23. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/_services.py +0 -0
  24. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/client.py +0 -0
  25. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/credentials.py +0 -0
  26. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/api/errors.py +0 -0
  27. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/__init__.py +0 -0
  28. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/errors.py +0 -0
  29. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/names.py +0 -0
  30. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/dns/zones.py +0 -0
  31. {devnomads-0.2.0 → devnomads-0.2.1}/src/devnomads/py.typed +0 -0
  32. {devnomads-0.2.0 → devnomads-0.2.1}/tests/conftest.py +0 -0
  33. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_challenges.py +0 -0
  34. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_acme_keys.py +0 -0
  35. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_client.py +0 -0
  36. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_credentials.py +0 -0
  37. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_dns.py +0 -0
  38. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_names.py +0 -0
  39. {devnomads-0.2.0 → devnomads-0.2.1}/tests/test_services.py +0 -0
  40. {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.0
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.0"
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.NewAccount(
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())
@@ -468,7 +468,7 @@ wheels = [
468
468
 
469
469
  [[package]]
470
470
  name = "devnomads"
471
- version = "0.2.0"
471
+ version = "0.2.1"
472
472
  source = { editable = "." }
473
473
  dependencies = [
474
474
  { name = "httpx" },
@@ -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