Skip to content

Commit 5dcbb92

Browse files
authored
Merge pull request #5 from devhelmhq/chore/codegen-audit-phase-2
feat(errors): add code + request_id to DevhelmApiError
2 parents da35cf3 + ff38d2d commit 5dcbb92

10 files changed

Lines changed: 404 additions & 126 deletions

File tree

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,38 @@ print(results.has_more)
108108

109109
## Error Handling
110110

111+
The SDK raises three top-level error types (see
112+
[`040-codegen-policies.md`](https://github.com/devhelmhq/mono/blob/main/cowork/design/040-codegen-policies.md)):
113+
114+
- `DevhelmValidationError` — local request/response shape validation failed.
115+
- `DevhelmApiError` — the API returned a non-2xx status. Subclassed by HTTP
116+
class for ergonomics: `DevhelmAuthError` (401/403), `DevhelmNotFoundError`
117+
(404), `DevhelmConflictError` (409), `DevhelmRateLimitError` (429),
118+
`DevhelmServerError` (5xx).
119+
- `DevhelmTransportError` — the request never reached a server response
120+
(connection refused, timeout, TLS failure, etc.).
121+
122+
Every `DevhelmApiError` carries:
123+
124+
- `status` — the HTTP status code
125+
- `code` — coarse machine-readable category (e.g. `NOT_FOUND`,
126+
`RATE_LIMITED`); switch on this, not the human-readable `message`
127+
- `request_id` — the per-request id from the `X-Request-Id` response header;
128+
always include this in support tickets
129+
111130
```python
112-
from devhelm import Devhelm, DevhelmError, AuthError
131+
from devhelm import Devhelm, DevhelmAuthError, DevhelmError
113132

114133
client = Devhelm(token="bad-token", org_id="1", workspace_id="1")
115134

116135
try:
117136
client.monitors.list()
118-
except AuthError as e:
119-
print(f"Auth failed: {e.message} (HTTP {e.status})")
137+
except DevhelmAuthError as e:
138+
print(f"Auth failed: {e.message} (HTTP {e.status}, request_id={e.request_id})")
120139
except DevhelmError as e:
121140
print(f"API error [{e.code}]: {e.message}")
122141
```
123142

124-
Error codes: `AUTH`, `NOT_FOUND`, `CONFLICT`, `VALIDATION`, `API`.
125-
126143
## Development
127144

128145
```bash

docs/openapi/monitoring-api.json

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10128,8 +10128,7 @@
1012810128
"$ref": "#/components/schemas/TestNotificationPolicyRequest"
1012910129
}
1013010130
}
10131-
},
10132-
"required": true
10131+
}
1013310132
},
1013410133
"responses": {
1013510134
"200": {
@@ -12921,6 +12920,7 @@
1292112920
"Status Data"
1292212921
],
1292312922
"summary": "Get a single service by slug or UUID with current status, components, and recent incidents",
12923+
"description": "When ``summary=true``, the inline ``components`` list is trimmed to groups + showcase leaves + currently-impacted leaves + ungrouped leaves, and a ``componentsSummary`` block is added with the trimmed counts. Powers SSR for vendors with hundreds of components (Snowflake, Cloudflare, DigitalOcean) without OOM-ing the renderer. Default false for full back-compat.",
1292412924
"operationId": "getService",
1292512925
"parameters": [
1292612926
{
@@ -12930,6 +12930,16 @@
1293012930
"schema": {
1293112931
"type": "string"
1293212932
}
12933+
},
12934+
{
12935+
"name": "summary",
12936+
"in": "query",
12937+
"description": "Return a curated subset of components (groups + showcase + impacted + ungrouped) and a componentsSummary block; default false",
12938+
"required": false,
12939+
"schema": {
12940+
"type": "boolean",
12941+
"default": false
12942+
}
1293312943
}
1293412944
],
1293512945
"responses": {
@@ -13032,6 +13042,7 @@
1303213042
"Status Data"
1303313043
],
1303413044
"summary": "List active components for a service with current status and inline uptime",
13045+
"description": "When ``groupId`` is supplied, only direct children of that group are returned — used by the pSEO renderer to lazy-load the leaves under a group that summary mode trimmed. Without ``groupId`` the response includes every active component for the service.",
1303513046
"operationId": "getComponents",
1303613047
"parameters": [
1303713048
{
@@ -13041,6 +13052,16 @@
1304113052
"schema": {
1304213053
"type": "string"
1304313054
}
13055+
},
13056+
{
13057+
"name": "groupId",
13058+
"in": "query",
13059+
"description": "Restrict result to direct children of this group component id",
13060+
"required": false,
13061+
"schema": {
13062+
"type": "string",
13063+
"format": "uuid"
13064+
}
1304413065
}
1304513066
],
1304613067
"responses": {
@@ -21894,6 +21915,35 @@
2189421915
},
2189521916
"description": "A single component position"
2189621917
},
21918+
"ComponentsSummaryDto": {
21919+
"required": [
21920+
"groupComponentCounts",
21921+
"includedCount",
21922+
"totalCount"
21923+
],
21924+
"type": "object",
21925+
"properties": {
21926+
"totalCount": {
21927+
"type": "integer",
21928+
"description": "Total active components for this service across all groups",
21929+
"format": "int32"
21930+
},
21931+
"includedCount": {
21932+
"type": "integer",
21933+
"description": "Number of components actually returned in the inline ``components`` list",
21934+
"format": "int32"
21935+
},
21936+
"groupComponentCounts": {
21937+
"type": "object",
21938+
"additionalProperties": {
21939+
"type": "integer",
21940+
"description": "Per-group active leaf count, keyed by group component id (UUID stringified). Empty when the service has no groups; lets the UI render \"show all N\" affordances without a second round trip",
21941+
"format": "int32"
21942+
},
21943+
"description": "Per-group active leaf count, keyed by group component id (UUID stringified). Empty when the service has no groups; lets the UI render \"show all N\" affordances without a second round trip"
21944+
}
21945+
}
21946+
},
2189721947
"ComponentStatusDto": {
2189821948
"required": [
2189921949
"id",
@@ -22239,8 +22289,7 @@
2223922289
"CreateEnvironmentRequest": {
2224022290
"required": [
2224122291
"name",
22242-
"slug",
22243-
"isDefault"
22292+
"slug"
2224422293
],
2224522294
"type": "object",
2224622295
"properties": {
@@ -22269,7 +22318,8 @@
2226922318
},
2227022319
"isDefault": {
2227122320
"type": "boolean",
22272-
"description": "Whether this is the default environment for new monitors"
22321+
"description": "Whether this is the default environment for new monitors (default: false)",
22322+
"nullable": true
2227322323
}
2227422324
}
2227522325
},
@@ -22992,11 +23042,23 @@
2299223042
"subscribedEvents": {
2299323043
"minItems": 1,
2299423044
"type": "array",
22995-
"description": "Event types to deliver, e.g. monitor.created, incident.resolved",
23045+
"description": "Event types to deliver",
2299623046
"items": {
2299723047
"minLength": 1,
2299823048
"type": "string",
22999-
"description": "Event types to deliver, e.g. monitor.created, incident.resolved"
23049+
"enum": [
23050+
"monitor.created",
23051+
"monitor.updated",
23052+
"monitor.deleted",
23053+
"incident.created",
23054+
"incident.resolved",
23055+
"incident.reopened",
23056+
"service.status_changed",
23057+
"service.component_changed",
23058+
"service.incident_created",
23059+
"service.incident_updated",
23060+
"service.incident_resolved"
23061+
]
2300023062
}
2300123063
}
2300223064
}
@@ -23882,6 +23944,7 @@
2388223944
},
2388323945
"ErrorResponse": {
2388423946
"required": [
23947+
"code",
2388523948
"message",
2388623949
"status",
2388723950
"timestamp"
@@ -23894,6 +23957,11 @@
2389423957
"format": "int32",
2389523958
"example": 404
2389623959
},
23960+
"code": {
23961+
"type": "string",
23962+
"description": "Coarse machine-readable error category (e.g. NOT_FOUND, RATE_LIMITED); stable per status",
23963+
"example": "NOT_FOUND"
23964+
},
2389723965
"message": {
2389823966
"type": "string",
2389923967
"description": "Human-readable error message; safe to surface to end users",
@@ -23904,13 +23972,21 @@
2390423972
"description": "Server time when the error was produced (epoch milliseconds)",
2390523973
"format": "int64",
2390623974
"example": 1737302400000
23975+
},
23976+
"requestId": {
23977+
"type": "string",
23978+
"description": "Opaque per-request id; same value as the X-Request-Id response header. Use in support tickets.",
23979+
"nullable": true,
23980+
"example": "5b6f7a8c-1234-4d5e-9f0a-1b2c3d4e5f6a"
2390723981
}
2390823982
},
2390923983
"description": "Uniform error envelope returned for every non-2xx response",
2391023984
"example": {
2391123985
"status": 404,
23986+
"code": "NOT_FOUND",
2391223987
"message": "Monitor not found",
23913-
"timestamp": 1737302400000
23988+
"timestamp": 1737302400000,
23989+
"requestId": "5b6f7a8c-1234-4d5e-9f0a-1b2c3d4e5f6a"
2391423990
}
2391523991
},
2391623992
"EscalationChain": {
@@ -25430,7 +25506,18 @@
2543025506
"properties": {
2543125507
"type": {
2543225508
"type": "string",
25433-
"description": "Rule type, e.g. severity_gte, monitor_id_in, region_in"
25509+
"description": "Rule type used to evaluate incidents and status events",
25510+
"enum": [
25511+
"severity_gte",
25512+
"monitor_id_in",
25513+
"region_in",
25514+
"incident_status",
25515+
"monitor_type_in",
25516+
"service_id_in",
25517+
"resource_group_id_in",
25518+
"component_name_in",
25519+
"monitor_tag_in"
25520+
]
2543425521
},
2543525522
"value": {
2543625523
"type": "string",
@@ -28149,6 +28236,14 @@
2814928236
"$ref": "#/components/schemas/ServiceComponentDto"
2815028237
}
2815128238
},
28239+
"componentsSummary": {
28240+
"nullable": true,
28241+
"allOf": [
28242+
{
28243+
"$ref": "#/components/schemas/ComponentsSummaryDto"
28244+
}
28245+
]
28246+
},
2815228247
"uptime": {
2815328248
"nullable": true,
2815428249
"allOf": [
@@ -32518,7 +32613,19 @@
3251832613
"nullable": true,
3251932614
"items": {
3252032615
"type": "string",
32521-
"description": "Replace subscribed events; null preserves current"
32616+
"enum": [
32617+
"monitor.created",
32618+
"monitor.updated",
32619+
"monitor.deleted",
32620+
"incident.created",
32621+
"incident.resolved",
32622+
"incident.reopened",
32623+
"service.status_changed",
32624+
"service.component_changed",
32625+
"service.incident_created",
32626+
"service.incident_updated",
32627+
"service.incident_resolved"
32628+
]
3252232629
}
3252332630
},
3252432631
"enabled": {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "devhelm"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more"
55
authors = [{ name = "DevHelm", email = "hello@devhelm.io" }]
66
license = "MIT"

src/devhelm/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more."""
22

33
from devhelm._errors import (
4-
AuthError,
54
DevhelmApiError,
65
DevhelmAuthError,
76
DevhelmConflictError,
@@ -140,7 +139,6 @@
140139
"DevhelmRateLimitError",
141140
"DevhelmServerError",
142141
"DevhelmTransportError",
143-
"AuthError",
144142
# Pagination
145143
"Page",
146144
"CursorPage",

0 commit comments

Comments
 (0)