drukbox-python-sdk 0.0.1__tar.gz → 0.0.2__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.
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/PKG-INFO +8 -4
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/README.md +7 -3
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/pyproject.toml +7 -2
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/src/drukbox_sdk/api.py +17 -7
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/tests/test_api.py +32 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/.github/workflows/on-main-merge.yml +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/.github/workflows/on-pull-request.yml +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/.github/workflows/release.yml +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/.gitignore +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/AGENTS.md +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/src/drukbox_sdk/__init__.py +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/src/drukbox_sdk/exceptions.py +0 -0
- {drukbox_python_sdk-0.0.1 → drukbox_python_sdk-0.0.2}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drukbox-python-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: Async Python client for Drukbox.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: httpx>=0.28
|
|
@@ -38,9 +38,13 @@ try:
|
|
|
38
38
|
env={"FOO": "bar"},
|
|
39
39
|
idempotency_key="agent-run-42",
|
|
40
40
|
)
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
41
|
+
# Dial whichever reachable address fits your network: host.external_ssh_host
|
|
42
|
+
# for the provider's public path (may be empty when the service runs an
|
|
43
|
+
# AWS-with-Tailscale-on deployment) or host.internal_ssh_host for the
|
|
44
|
+
# tailnet MagicDNS name (only when Tailscale is enabled). Use host.known_hosts
|
|
45
|
+
# for SSH host-key verification. If the provider mints a per-VM keypair
|
|
46
|
+
# (AWS with Tailscale off), host.private_key carries the private half —
|
|
47
|
+
# returned exactly once at create time; subsequent get_host returns None.
|
|
44
48
|
finally:
|
|
45
49
|
await sandbox.delete_host(host.id)
|
|
46
50
|
await sandbox.aclose()
|
|
@@ -30,9 +30,13 @@ try:
|
|
|
30
30
|
env={"FOO": "bar"},
|
|
31
31
|
idempotency_key="agent-run-42",
|
|
32
32
|
)
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
33
|
+
# Dial whichever reachable address fits your network: host.external_ssh_host
|
|
34
|
+
# for the provider's public path (may be empty when the service runs an
|
|
35
|
+
# AWS-with-Tailscale-on deployment) or host.internal_ssh_host for the
|
|
36
|
+
# tailnet MagicDNS name (only when Tailscale is enabled). Use host.known_hosts
|
|
37
|
+
# for SSH host-key verification. If the provider mints a per-VM keypair
|
|
38
|
+
# (AWS with Tailscale off), host.private_key carries the private half —
|
|
39
|
+
# returned exactly once at create time; subsequent get_host returns None.
|
|
36
40
|
finally:
|
|
37
41
|
await sandbox.delete_host(host.id)
|
|
38
42
|
await sandbox.aclose()
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "drukbox-python-sdk"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.2"
|
|
8
8
|
description = "Async Python client for Drukbox."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Tracks the lowest Python any current consumer supports. Bump only
|
|
@@ -45,5 +45,10 @@ testpaths = ["tests"]
|
|
|
45
45
|
[tool.pyright]
|
|
46
46
|
include = ["src", "tests"]
|
|
47
47
|
pythonVersion = "3.11"
|
|
48
|
-
typeCheckingMode = "
|
|
48
|
+
typeCheckingMode = "standard"
|
|
49
|
+
venvPath = "."
|
|
50
|
+
venv = ".venv"
|
|
49
51
|
reportMissingTypeStubs = false
|
|
52
|
+
reportUnknownMemberType = false
|
|
53
|
+
reportUnknownVariableType = false
|
|
54
|
+
reportUnknownArgumentType = false
|
|
@@ -61,13 +61,22 @@ _SANDBOX_HTTP_LIMITS = httpx.Limits(
|
|
|
61
61
|
class SandboxHost:
|
|
62
62
|
"""Snapshot of a provisioned host as returned by the service.
|
|
63
63
|
|
|
64
|
-
The shape mirrors the Drukbox ``Host`` schema.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
The shape mirrors the Drukbox ``Host`` schema. Either reachable
|
|
65
|
+
address may be empty/None depending on the provider and deployment:
|
|
66
|
+
``external_ssh_host`` carries the provider's public path (always
|
|
67
|
+
populated by exe.dev; empty for an AWS host with Tailscale on);
|
|
68
|
+
``internal_ssh_host`` carries the tailnet MagicDNS form, populated
|
|
69
|
+
only when the service runs with Tailscale enabled. The internal
|
|
70
|
+
path is always reached on port 22 by Tailscale convention, so
|
|
71
|
+
there is no ``internal_ssh_port``. Callers pick whichever path
|
|
72
|
+
they can reach and dial it themselves — this SDK doesn't speak SSH.
|
|
73
|
+
|
|
74
|
+
``private_key`` carries per-VM SSH private key material returned
|
|
75
|
+
exactly once at create time, when the provider mints fresh material
|
|
76
|
+
per instance (AWS with Tailscale off). Providers that use a
|
|
77
|
+
different SSH auth model (exe.dev's edge proxy, Tailscale ACLs
|
|
78
|
+
via tailscaled-SSH) return None here. A later ``get_host`` call
|
|
79
|
+
always returns None — the key is not persisted server-side.
|
|
71
80
|
"""
|
|
72
81
|
|
|
73
82
|
id: str
|
|
@@ -80,6 +89,7 @@ class SandboxHost:
|
|
|
80
89
|
internal_ssh_host: str | None
|
|
81
90
|
known_hosts: str
|
|
82
91
|
tailscale_device_id: str | None
|
|
92
|
+
private_key: str | None
|
|
83
93
|
last_error: str
|
|
84
94
|
created_at: str
|
|
85
95
|
updated_at: str
|
|
@@ -49,6 +49,7 @@ def _host_payload(**overrides: Any) -> dict[str, Any]:
|
|
|
49
49
|
"internal_ssh_host": "host-abc.example.ts.net",
|
|
50
50
|
"known_hosts": "ssh-ed25519 AAAA...\n",
|
|
51
51
|
"tailscale_device_id": None,
|
|
52
|
+
"private_key": None,
|
|
52
53
|
"last_error": "",
|
|
53
54
|
"created_at": "2026-05-28T12:00:00+00:00",
|
|
54
55
|
"updated_at": "2026-05-28T12:00:00+00:00",
|
|
@@ -199,6 +200,37 @@ async def test_unknown_fields_in_host_payload_are_ignored(api: SandboxAPI):
|
|
|
199
200
|
assert not hasattr(host, "future_field") # New field silently dropped.
|
|
200
201
|
|
|
201
202
|
|
|
203
|
+
@respx.mock
|
|
204
|
+
async def test_create_host_returns_private_key_when_provider_mints_one(api: SandboxAPI):
|
|
205
|
+
"""AWS with Tailscale-off mints a per-VM ed25519 keypair and returns
|
|
206
|
+
the private half exactly once at create time. The SDK must round-trip
|
|
207
|
+
it through to the caller."""
|
|
208
|
+
|
|
209
|
+
pem = "-----BEGIN OPENSSH PRIVATE KEY-----\nFAKE\n-----END OPENSSH PRIVATE KEY-----\n"
|
|
210
|
+
payload = _host_payload(provider="aws", private_key=pem, external_ssh_host="ec2-x.example")
|
|
211
|
+
respx.post(f"{BASE_URL}/hosts").mock(return_value=httpx.Response(201, json=payload))
|
|
212
|
+
|
|
213
|
+
host = await api.create_host()
|
|
214
|
+
|
|
215
|
+
assert host.provider == "aws"
|
|
216
|
+
assert host.private_key == pem
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@respx.mock
|
|
220
|
+
async def test_get_host_returns_none_private_key_after_create(api: SandboxAPI):
|
|
221
|
+
"""drukbox doesn't persist the key, so a subsequent GET should
|
|
222
|
+
return None regardless of what was returned at create time."""
|
|
223
|
+
|
|
224
|
+
payload = _host_payload(private_key=None)
|
|
225
|
+
respx.get(f"{BASE_URL}/hosts/{payload['id']}").mock(
|
|
226
|
+
return_value=httpx.Response(200, json=payload),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
host = await api.get_host(payload["id"])
|
|
230
|
+
|
|
231
|
+
assert host.private_key is None
|
|
232
|
+
|
|
233
|
+
|
|
202
234
|
# ---------------------------------------------------------------------------
|
|
203
235
|
# Error classification
|
|
204
236
|
# ---------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|