drukbox-python-sdk 0.0.5__tar.gz → 0.0.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drukbox-python-sdk
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: Async Python client for Drukbox.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.28
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "drukbox-python-sdk"
7
- version = "0.0.5"
7
+ version = "0.0.6"
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
@@ -18,6 +18,7 @@ from .exceptions import (
18
18
  SandboxProvisioningError,
19
19
  SandboxResponseError,
20
20
  SandboxUnavailableError,
21
+ SandboxValidationError,
21
22
  )
22
23
 
23
24
  __all__ = [
@@ -32,4 +33,5 @@ __all__ = [
32
33
  "SandboxProvisioningError",
33
34
  "SandboxResponseError",
34
35
  "SandboxUnavailableError",
36
+ "SandboxValidationError",
35
37
  ]
@@ -45,6 +45,7 @@ from .exceptions import (
45
45
  SandboxProvisioningError,
46
46
  SandboxResponseError,
47
47
  SandboxUnavailableError,
48
+ SandboxValidationError,
48
49
  )
49
50
 
50
51
  # Explicit pool budget. The httpx defaults (100 / 20) are plenty for
@@ -350,6 +351,14 @@ class SandboxAPI:
350
351
  if response.status_code == 503:
351
352
  raise SandboxUnavailableError(json_response.get("detail", "service unavailable"))
352
353
 
354
+ if response.status_code == 422:
355
+ # FastAPI's 422 detail is a list of error dicts; join the messages
356
+ # so it reads as a line, not a raw list repr.
357
+ detail = json_response.get("detail", "validation error")
358
+ if isinstance(detail, list):
359
+ detail = "; ".join(item.get("msg", "") for item in detail) or "validation error"
360
+ raise SandboxValidationError(detail)
361
+
353
362
  if response.status_code >= 400:
354
363
  raise SandboxResponseError(json_response.get("detail", "error"))
355
364
  return json_response
@@ -50,5 +50,14 @@ class SandboxProvisioningError(SandboxAPIError):
50
50
  """
51
51
 
52
52
 
53
+ class SandboxValidationError(SandboxAPIError):
54
+ """422 — the service rejected the request payload as invalid.
55
+
56
+ e.g. an env key that isn't a valid environment-variable name, or a
57
+ proxy target URL carrying a path or credentials. The request is
58
+ malformed, so retrying it unchanged won't help — fix the input.
59
+ """
60
+
61
+
53
62
  class SandboxResponseError(SandboxAPIError):
54
63
  """Everything else the service returned that the SDK doesn't classify."""
@@ -31,6 +31,7 @@ from drukbox_sdk import (
31
31
  SandboxProvisioningError,
32
32
  SandboxResponseError,
33
33
  SandboxUnavailableError,
34
+ SandboxValidationError,
34
35
  )
35
36
 
36
37
  BASE_URL = "https://sandbox.test"
@@ -333,6 +334,28 @@ async def test_409_raises_sandbox_conflict_error(api: SandboxAPI):
333
334
  await api.delete_host(host_id)
334
335
 
335
336
 
337
+ @respx.mock
338
+ async def test_422_raises_sandbox_validation_error(api: SandboxAPI):
339
+ # FastAPI's 422 detail is a list of error dicts; the SDK flattens it to a
340
+ # readable line and raises the dedicated validation error (don't-retry).
341
+ respx.post(f"{BASE_URL}/hosts").mock(
342
+ return_value=httpx.Response(
343
+ 422,
344
+ json={
345
+ "detail": [
346
+ {
347
+ "loc": ["body", "env"],
348
+ "msg": "env keys must be valid environment variable names: FOO BAR",
349
+ }
350
+ ]
351
+ },
352
+ ),
353
+ )
354
+
355
+ with pytest.raises(SandboxValidationError, match="env keys must be valid"):
356
+ await api.create_host(env={"FOO BAR": "x"})
357
+
358
+
336
359
  @respx.mock
337
360
  async def test_500_raises_sandbox_response_error(api: SandboxAPI):
338
361
  respx.get(f"{BASE_URL}/hosts").mock(