devnomads 0.1.3__tar.gz → 0.2.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.
Files changed (38) hide show
  1. {devnomads-0.1.3 → devnomads-0.2.0}/.gitlab-ci.yml +1 -0
  2. devnomads-0.2.0/Makefile +27 -0
  3. {devnomads-0.1.3 → devnomads-0.2.0}/PKG-INFO +70 -4
  4. {devnomads-0.1.3 → devnomads-0.2.0}/README.md +69 -3
  5. devnomads-0.2.0/openapi.json +3629 -0
  6. {devnomads-0.1.3 → devnomads-0.2.0}/pyproject.toml +1 -1
  7. devnomads-0.2.0/src/devnomads/api/_services.py +916 -0
  8. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/api/client.py +5 -2
  9. devnomads-0.2.0/tests/test_services.py +149 -0
  10. devnomads-0.2.0/tools/generate.py +285 -0
  11. {devnomads-0.1.3 → devnomads-0.2.0}/uv.lock +1 -1
  12. {devnomads-0.1.3 → devnomads-0.2.0}/.flake8 +0 -0
  13. {devnomads-0.1.3 → devnomads-0.2.0}/.gitignore +0 -0
  14. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/__init__.py +0 -0
  15. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/__init__.py +0 -0
  16. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/challenge_server.py +0 -0
  17. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/client.py +0 -0
  18. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/dns01.py +0 -0
  19. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/errors.py +0 -0
  20. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/http01.py +0 -0
  21. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/keys.py +0 -0
  22. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/acme/verify.py +0 -0
  23. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/api/__init__.py +0 -0
  24. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/api/credentials.py +0 -0
  25. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/api/errors.py +0 -0
  26. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/dns/__init__.py +0 -0
  27. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/dns/errors.py +0 -0
  28. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/dns/names.py +0 -0
  29. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/dns/zones.py +0 -0
  30. {devnomads-0.1.3 → devnomads-0.2.0}/src/devnomads/py.typed +0 -0
  31. {devnomads-0.1.3 → devnomads-0.2.0}/tests/conftest.py +0 -0
  32. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_acme_challenges.py +0 -0
  33. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_acme_client.py +0 -0
  34. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_acme_keys.py +0 -0
  35. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_client.py +0 -0
  36. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_credentials.py +0 -0
  37. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_dns.py +0 -0
  38. {devnomads-0.1.3 → devnomads-0.2.0}/tests/test_names.py +0 -0
@@ -12,6 +12,7 @@ default:
12
12
  check:
13
13
  stage: check
14
14
  script:
15
+ - uv run python tools/generate.py --check
15
16
  - uv run black --check .
16
17
  - uv run isort --check-only .
17
18
  - uv run flake8 src tests
@@ -0,0 +1,27 @@
1
+ .PHONY: spec generate build check test
2
+
3
+ # refresh the committed OpenAPI snapshot from the live API
4
+ spec:
5
+ curl -sf "https://api.devnomads.nl/docs?api-docs.json" -o openapi.json
6
+
7
+ # regenerate src/devnomads/api/_services.py from openapi.json
8
+ generate:
9
+ uv run python tools/generate.py
10
+ uv run black -q .
11
+ uv run isort -q .
12
+
13
+ # full build: refresh spec, regenerate, verify
14
+ build: spec generate check
15
+
16
+ check:
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 tools
21
+ uv run bandit -q -r src
22
+ uv run semgrep scan --quiet --error --config p/python src
23
+ uv run mypy src
24
+ uv run pytest -q
25
+
26
+ test:
27
+ uv run pytest -v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devnomads
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Python client for the DevNomads API (transport, DNS, ACME)
5
5
  Project-URL: Homepage, https://devnomads.nl
6
6
  Author-email: Loek Geleijn <support@devnomads.nl>
@@ -18,8 +18,8 @@ Description-Content-Type: text/markdown
18
18
  # devnomads
19
19
 
20
20
  Python client library for the [DevNomads](https://devnomads.nl) API:
21
- HTTP transport, DNS zone/record management, and ACME certificate
22
- issuance.
21
+ HTTP transport, a generated resource SDK covering the whole API, DNS
22
+ zone/record management, and ACME certificate issuance.
23
23
 
24
24
  It is the shared foundation of the DevNomads command-line tools - `dnctl`
25
25
  and `dnsync` - exposed so you can build against the same primitives
@@ -33,6 +33,7 @@ directly instead of reimplementing them.
33
33
  - [Architecture](#architecture)
34
34
  - [Authentication](#authentication)
35
35
  - [Transport](#transport)
36
+ - [Services](#services)
36
37
  - [DNS](#dns)
37
38
  - [Certificates](#certificates)
38
39
  - [Errors](#errors)
@@ -45,6 +46,8 @@ directly instead of reimplementing them.
45
46
 
46
47
  - Authenticated HTTP transport with retries (`Retry-After` aware) and
47
48
  Laravel resource-envelope unwrapping.
49
+ - A generated resource SDK covering the whole API as client
50
+ attributes, e.g. `client.servers.list()`.
48
51
  - DNS zone discovery (longest-suffix matching), merge-aware TXT updates,
49
52
  and generic record management.
50
53
  - ACME certificate issuance over DNS-01 and HTTP-01 - including
@@ -85,13 +88,17 @@ The package is split into three layers by dependency weight. Import only
85
88
  what you need.
86
89
 
87
90
  ```text
88
- devnomads.api transport: auth, retries, errors (httpx)
91
+ devnomads.api transport + generated resource SDK (httpx)
89
92
 
90
93
  devnomads.dns zones, records, TXT/ACME challenges (httpx)
91
94
 
92
95
  devnomads.acme ACME issuance: DNS-01 + HTTP-01 (+ acme extra deps)
93
96
  ```
94
97
 
98
+ `devnomads.api` carries both the low-level `Client` transport and a
99
+ generated SDK that maps the full API onto client attributes (see
100
+ [Services](#services)).
101
+
95
102
  The DevNomads DNS API is a PowerDNS proxy scoped to the zones a key may
96
103
  access, so zone ids and record names are absolute (trailing dot) and TXT
97
104
  content is stored quoted; the library handles these conventions for you.
@@ -182,6 +189,55 @@ JSON and:
182
189
  resource reads as absence;
183
190
  - raises `AuthError` on `401`/`403` and `ApiError` on any other `4xx`.
184
191
 
192
+ ## Services
193
+
194
+ Beyond the curated DNS and ACME helpers, the `Client` exposes the rest
195
+ of the API through a generated, resource-attribute SDK. Each top-level
196
+ resource is an attribute on the client; nested resources chain as
197
+ further attributes; operations are methods.
198
+
199
+ ```python
200
+ from devnomads.api import Client
201
+
202
+ with Client.from_environment() as client:
203
+ client.servers.list()
204
+ client.servers.show(server_id)
205
+ client.databases.clusters.list()
206
+ client.databases.create(body={"name": "blog"})
207
+ client.emails.mailboxes.list(service_id)
208
+ client.emails.mailboxes.create(service_id, body={...})
209
+ client.proxies.up(service_id)
210
+ ```
211
+
212
+ The top-level resources are `servers`, `databases`, `emails`, `spams`,
213
+ `containers`, `proxies`, `sites`, `buckets`, `domains`, `forwards`,
214
+ `searches`, `apps`, and `handles`. (DNS stays the curated
215
+ `devnomads.dns` interface and is not generated here.)
216
+
217
+ ### Conventions
218
+
219
+ - **Path parameters** are positional, in URL order, snake-cased from
220
+ the path template - e.g. `client.emails.mailboxes.show(service_id,
221
+ emailaddress)`.
222
+ - **Query parameters** go through `params=` as a dict.
223
+ - **Request bodies** (POST/PUT/PATCH) go through `body=`, sent as JSON.
224
+ - **Method names** map the API action: `index` -> `list`, `store`/
225
+ `create` -> `create`, `show`, `update`, `destroy`/`delete` ->
226
+ `delete`, `patch`; any other action (`state`, `deploy`, `logs`, `up`,
227
+ `down`, `enable`, ...) passes through snake-cased.
228
+
229
+ Returns are the parsed JSON payload (envelope already unwrapped). The
230
+ OpenAPI spec ships no component schemas, so there are no typed response
231
+ models - methods return `Any`. The SDK is generated from the committed
232
+ `openapi.json`; see [Development](#development).
233
+
234
+ ```python
235
+ mailbox = client.emails.mailboxes.create(
236
+ service_id, body={"address": "hi@example.com"}
237
+ )
238
+ aliases = client.emails.aliases.list(service_id, params={"page": 2})
239
+ ```
240
+
185
241
  ## DNS
186
242
 
187
243
  `Dns` wraps a `Client` with zone-scoped operations. Zone discovery lists
@@ -459,6 +515,16 @@ uv run pytest
459
515
  uv run black . && uv run flake8 src tests
460
516
  ```
461
517
 
518
+ The `Services` SDK in `src/devnomads/api/_services.py` is generated from
519
+ the committed `openapi.json` snapshot. Regenerate it after refreshing
520
+ the spec; CI fails if the checked-in file is stale.
521
+
522
+ ```bash
523
+ make spec # refresh openapi.json from the live API
524
+ make generate # rewrite _services.py from openapi.json
525
+ make check # lint + type + test + generate --check
526
+ ```
527
+
462
528
  ## License
463
529
 
464
530
  MIT
@@ -1,8 +1,8 @@
1
1
  # devnomads
2
2
 
3
3
  Python client library for the [DevNomads](https://devnomads.nl) API:
4
- HTTP transport, DNS zone/record management, and ACME certificate
5
- issuance.
4
+ HTTP transport, a generated resource SDK covering the whole API, DNS
5
+ zone/record management, and ACME certificate issuance.
6
6
 
7
7
  It is the shared foundation of the DevNomads command-line tools - `dnctl`
8
8
  and `dnsync` - exposed so you can build against the same primitives
@@ -16,6 +16,7 @@ directly instead of reimplementing them.
16
16
  - [Architecture](#architecture)
17
17
  - [Authentication](#authentication)
18
18
  - [Transport](#transport)
19
+ - [Services](#services)
19
20
  - [DNS](#dns)
20
21
  - [Certificates](#certificates)
21
22
  - [Errors](#errors)
@@ -28,6 +29,8 @@ directly instead of reimplementing them.
28
29
 
29
30
  - Authenticated HTTP transport with retries (`Retry-After` aware) and
30
31
  Laravel resource-envelope unwrapping.
32
+ - A generated resource SDK covering the whole API as client
33
+ attributes, e.g. `client.servers.list()`.
31
34
  - DNS zone discovery (longest-suffix matching), merge-aware TXT updates,
32
35
  and generic record management.
33
36
  - ACME certificate issuance over DNS-01 and HTTP-01 - including
@@ -68,13 +71,17 @@ The package is split into three layers by dependency weight. Import only
68
71
  what you need.
69
72
 
70
73
  ```text
71
- devnomads.api transport: auth, retries, errors (httpx)
74
+ devnomads.api transport + generated resource SDK (httpx)
72
75
 
73
76
  devnomads.dns zones, records, TXT/ACME challenges (httpx)
74
77
 
75
78
  devnomads.acme ACME issuance: DNS-01 + HTTP-01 (+ acme extra deps)
76
79
  ```
77
80
 
81
+ `devnomads.api` carries both the low-level `Client` transport and a
82
+ generated SDK that maps the full API onto client attributes (see
83
+ [Services](#services)).
84
+
78
85
  The DevNomads DNS API is a PowerDNS proxy scoped to the zones a key may
79
86
  access, so zone ids and record names are absolute (trailing dot) and TXT
80
87
  content is stored quoted; the library handles these conventions for you.
@@ -165,6 +172,55 @@ JSON and:
165
172
  resource reads as absence;
166
173
  - raises `AuthError` on `401`/`403` and `ApiError` on any other `4xx`.
167
174
 
175
+ ## Services
176
+
177
+ Beyond the curated DNS and ACME helpers, the `Client` exposes the rest
178
+ of the API through a generated, resource-attribute SDK. Each top-level
179
+ resource is an attribute on the client; nested resources chain as
180
+ further attributes; operations are methods.
181
+
182
+ ```python
183
+ from devnomads.api import Client
184
+
185
+ with Client.from_environment() as client:
186
+ client.servers.list()
187
+ client.servers.show(server_id)
188
+ client.databases.clusters.list()
189
+ client.databases.create(body={"name": "blog"})
190
+ client.emails.mailboxes.list(service_id)
191
+ client.emails.mailboxes.create(service_id, body={...})
192
+ client.proxies.up(service_id)
193
+ ```
194
+
195
+ The top-level resources are `servers`, `databases`, `emails`, `spams`,
196
+ `containers`, `proxies`, `sites`, `buckets`, `domains`, `forwards`,
197
+ `searches`, `apps`, and `handles`. (DNS stays the curated
198
+ `devnomads.dns` interface and is not generated here.)
199
+
200
+ ### Conventions
201
+
202
+ - **Path parameters** are positional, in URL order, snake-cased from
203
+ the path template - e.g. `client.emails.mailboxes.show(service_id,
204
+ emailaddress)`.
205
+ - **Query parameters** go through `params=` as a dict.
206
+ - **Request bodies** (POST/PUT/PATCH) go through `body=`, sent as JSON.
207
+ - **Method names** map the API action: `index` -> `list`, `store`/
208
+ `create` -> `create`, `show`, `update`, `destroy`/`delete` ->
209
+ `delete`, `patch`; any other action (`state`, `deploy`, `logs`, `up`,
210
+ `down`, `enable`, ...) passes through snake-cased.
211
+
212
+ Returns are the parsed JSON payload (envelope already unwrapped). The
213
+ OpenAPI spec ships no component schemas, so there are no typed response
214
+ models - methods return `Any`. The SDK is generated from the committed
215
+ `openapi.json`; see [Development](#development).
216
+
217
+ ```python
218
+ mailbox = client.emails.mailboxes.create(
219
+ service_id, body={"address": "hi@example.com"}
220
+ )
221
+ aliases = client.emails.aliases.list(service_id, params={"page": 2})
222
+ ```
223
+
168
224
  ## DNS
169
225
 
170
226
  `Dns` wraps a `Client` with zone-scoped operations. Zone discovery lists
@@ -442,6 +498,16 @@ uv run pytest
442
498
  uv run black . && uv run flake8 src tests
443
499
  ```
444
500
 
501
+ The `Services` SDK in `src/devnomads/api/_services.py` is generated from
502
+ the committed `openapi.json` snapshot. Regenerate it after refreshing
503
+ the spec; CI fails if the checked-in file is stale.
504
+
505
+ ```bash
506
+ make spec # refresh openapi.json from the live API
507
+ make generate # rewrite _services.py from openapi.json
508
+ make check # lint + type + test + generate --check
509
+ ```
510
+
445
511
  ## License
446
512
 
447
513
  MIT