devhelm 1.2.0__tar.gz → 1.4.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 (59) hide show
  1. {devhelm-1.2.0 → devhelm-1.4.0}/PKG-INFO +2 -1
  2. {devhelm-1.2.0 → devhelm-1.4.0}/README.md +1 -0
  3. {devhelm-1.2.0 → devhelm-1.4.0}/docs/openapi/monitoring-api.json +52 -0
  4. {devhelm-1.2.0 → devhelm-1.4.0}/pyproject.toml +1 -1
  5. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/__init__.py +34 -0
  6. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_errors.py +37 -1
  7. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_http.py +1 -0
  8. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_pagination.py +11 -2
  9. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/client.py +3 -0
  10. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/api_keys.py +9 -1
  11. devhelm-1.4.0/src/devhelm/resources/dependencies.py +100 -0
  12. devhelm-1.4.0/src/devhelm/resources/services.py +322 -0
  13. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/types.py +32 -0
  14. {devhelm-1.2.0 → devhelm-1.4.0}/tests/run_sdk.py +32 -0
  15. devhelm-1.4.0/tests/test_api_keys.py +76 -0
  16. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_client.py +19 -0
  17. devhelm-1.4.0/tests/test_dependencies.py +142 -0
  18. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_errors.py +27 -0
  19. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_http.py +46 -2
  20. devhelm-1.4.0/tests/test_services.py +281 -0
  21. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_spec_parity.py +4 -0
  22. {devhelm-1.2.0 → devhelm-1.4.0}/uv.lock +1 -1
  23. devhelm-1.2.0/src/devhelm/resources/dependencies.py +0 -51
  24. {devhelm-1.2.0 → devhelm-1.4.0}/.github/workflows/ci.yml +0 -0
  25. {devhelm-1.2.0 → devhelm-1.4.0}/.github/workflows/release.yml +0 -0
  26. {devhelm-1.2.0 → devhelm-1.4.0}/.github/workflows/spec-check.yml +0 -0
  27. {devhelm-1.2.0 → devhelm-1.4.0}/.gitignore +0 -0
  28. {devhelm-1.2.0 → devhelm-1.4.0}/LICENSE +0 -0
  29. {devhelm-1.2.0 → devhelm-1.4.0}/Makefile +0 -0
  30. {devhelm-1.2.0 → devhelm-1.4.0}/scripts/emit_response_enums.py +0 -0
  31. {devhelm-1.2.0 → devhelm-1.4.0}/scripts/inject_strict_config.py +0 -0
  32. {devhelm-1.2.0 → devhelm-1.4.0}/scripts/regen-from.sh +0 -0
  33. {devhelm-1.2.0 → devhelm-1.4.0}/scripts/release.sh +0 -0
  34. {devhelm-1.2.0 → devhelm-1.4.0}/scripts/typegen.sh +0 -0
  35. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_enums.py +0 -0
  36. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_generated.py +0 -0
  37. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/_validation.py +0 -0
  38. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/py.typed +0 -0
  39. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/__init__.py +0 -0
  40. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/alert_channels.py +0 -0
  41. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/deploy_lock.py +0 -0
  42. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/environments.py +0 -0
  43. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/forensics.py +0 -0
  44. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/incidents.py +0 -0
  45. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/maintenance_windows.py +0 -0
  46. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/monitors.py +0 -0
  47. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/notification_policies.py +0 -0
  48. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/resource_groups.py +0 -0
  49. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/secrets.py +0 -0
  50. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/status.py +0 -0
  51. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/status_pages.py +0 -0
  52. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/tags.py +0 -0
  53. {devhelm-1.2.0 → devhelm-1.4.0}/src/devhelm/resources/webhooks.py +0 -0
  54. {devhelm-1.2.0 → devhelm-1.4.0}/tests/__init__.py +0 -0
  55. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_maintenance_windows.py +0 -0
  56. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_negative_validation.py +0 -0
  57. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_schemas.py +0 -0
  58. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_typing.py +0 -0
  59. {devhelm-1.2.0 → devhelm-1.4.0}/tests/test_validation_helpers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devhelm
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more
5
5
  Project-URL: Homepage, https://github.com/devhelmhq/sdk-python
6
6
  Project-URL: Repository, https://github.com/devhelmhq/sdk-python.git
@@ -108,6 +108,7 @@ The client exposes the following resource modules:
108
108
  | `client.api_keys` | API key management |
109
109
  | `client.dependencies` | Service dependency tracking |
110
110
  | `client.deploy_lock` | Deploy lock for safe deployments |
111
+ | `client.services` | Status Data catalog: vendor services, components, incidents, uptime |
111
112
  | `client.status` | Dashboard overview |
112
113
 
113
114
  ## Pagination
@@ -84,6 +84,7 @@ The client exposes the following resource modules:
84
84
  | `client.api_keys` | API key management |
85
85
  | `client.dependencies` | Service dependency tracking |
86
86
  | `client.deploy_lock` | Deploy lock for safe deployments |
87
+ | `client.services` | Status Data catalog: vendor services, components, incidents, uptime |
87
88
  | `client.status` | Dashboard overview |
88
89
 
89
90
  ## Pagination
@@ -2274,6 +2274,18 @@
2274
2274
  ],
2275
2275
  "summary": "List categories with service counts",
2276
2276
  "operationId": "listCategories",
2277
+ "parameters": [
2278
+ {
2279
+ "name": "publishedOnly",
2280
+ "in": "query",
2281
+ "description": "Count only published services (curated public pSEO set); default false",
2282
+ "required": false,
2283
+ "schema": {
2284
+ "type": "boolean",
2285
+ "default": false
2286
+ }
2287
+ }
2288
+ ],
2277
2289
  "responses": {
2278
2290
  "200": {
2279
2291
  "description": "OK",
@@ -13520,6 +13532,24 @@
13520
13532
  "type": "boolean"
13521
13533
  }
13522
13534
  },
13535
+ {
13536
+ "name": "search",
13537
+ "in": "query",
13538
+ "description": "Case-insensitive substring match on service name or slug",
13539
+ "required": false,
13540
+ "schema": {
13541
+ "type": "string"
13542
+ }
13543
+ },
13544
+ {
13545
+ "name": "sort",
13546
+ "in": "query",
13547
+ "description": "Result ordering: 'recent' (default, newest first) or 'curated' (curated/recognizable first)",
13548
+ "required": false,
13549
+ "schema": {
13550
+ "type": "string"
13551
+ }
13552
+ },
13523
13553
  {
13524
13554
  "name": "cursor",
13525
13555
  "in": "query",
@@ -13661,6 +13691,16 @@
13661
13691
  "type": "boolean",
13662
13692
  "default": false
13663
13693
  }
13694
+ },
13695
+ {
13696
+ "name": "publishedOnly",
13697
+ "in": "query",
13698
+ "description": "Resolve only published services (curated public pSEO set); 404 otherwise. Default false",
13699
+ "required": false,
13700
+ "schema": {
13701
+ "type": "boolean",
13702
+ "default": false
13703
+ }
13664
13704
  }
13665
13705
  ],
13666
13706
  "responses": {
@@ -15358,6 +15398,18 @@
15358
15398
  "summary": "Global status summary across all services",
15359
15399
  "description": "Returns aggregate counts of services by status and a list of services currently experiencing issues.",
15360
15400
  "operationId": "getGlobalStatusSummary",
15401
+ "parameters": [
15402
+ {
15403
+ "name": "publishedOnly",
15404
+ "in": "query",
15405
+ "description": "Aggregate only published services (curated public pSEO set); default false",
15406
+ "required": false,
15407
+ "schema": {
15408
+ "type": "boolean",
15409
+ "default": false
15410
+ }
15411
+ }
15412
+ ],
15361
15413
  "responses": {
15362
15414
  "200": {
15363
15415
  "description": "OK",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devhelm"
3
- version = "1.2.0"
3
+ version = "1.4.0"
4
4
  description = "DevHelm SDK for Python — typed client for monitors, incidents, alerting, and more"
5
5
  authors = [{ name = "DevHelm", email = "hello@devhelm.io" }]
6
6
  license = "MIT"
@@ -29,6 +29,7 @@ from devhelm.resources.monitors import Monitors
29
29
  from devhelm.resources.notification_policies import NotificationPolicies
30
30
  from devhelm.resources.resource_groups import ResourceGroups
31
31
  from devhelm.resources.secrets import Secrets
32
+ from devhelm.resources.services import Services
32
33
  from devhelm.resources.status import Status
33
34
  from devhelm.resources.status_pages import StatusPages
34
35
  from devhelm.resources.tags import Tags
@@ -46,8 +47,11 @@ from devhelm.types import (
46
47
  ApiKeyDto,
47
48
  AssertionSeverity,
48
49
  AssertionTestResultDto,
50
+ BatchComponentUptimeDto,
51
+ CategoryDto,
49
52
  CheckResultDto,
50
53
  CheckTraceDto,
54
+ ComponentUptimeDayDto,
51
55
  ConfirmationPolicyType,
52
56
  CreateAlertChannelRequest,
53
57
  CreateApiKeyRequest,
@@ -69,6 +73,7 @@ from devhelm.types import (
69
73
  DashboardOverviewDto,
70
74
  DeployLockDto,
71
75
  EnvironmentDto,
76
+ GlobalStatusSummaryDto,
72
77
  IncidentDetailDto,
73
78
  IncidentDto,
74
79
  IncidentNewStatus,
@@ -78,6 +83,7 @@ from devhelm.types import (
78
83
  IncidentStatus,
79
84
  IncidentTimelineDto,
80
85
  IncidentUpdateCreatedBy,
86
+ LifecycleStatus,
81
87
  LinkedIncidentStatus,
82
88
  MaintenanceWindowDto,
83
89
  MembershipStatus,
@@ -99,8 +105,18 @@ from devhelm.types import (
99
105
  ResourceGroupHealthStatus,
100
106
  ResourceGroupMemberDto,
101
107
  RuleEvaluationDto,
108
+ ScheduledMaintenanceDto,
102
109
  SecretDto,
110
+ ServiceCatalogDto,
111
+ ServiceComponentDto,
112
+ ServiceDayDetailDto,
113
+ ServiceDetailDto,
114
+ ServiceIncidentDetailDto,
115
+ ServiceIncidentDto,
116
+ ServiceLiveStatusDto,
117
+ ServiceSubscribeRequest,
103
118
  ServiceSubscriptionDto,
119
+ ServiceUptimeResponse,
104
120
  StatusPageBranding,
105
121
  StatusPageComponentCurrentStatus,
106
122
  StatusPageComponentDto,
@@ -124,6 +140,7 @@ from devhelm.types import (
124
140
  TriggerRuleSeverity,
125
141
  TriggerRuleType,
126
142
  UpdateAlertChannelRequest,
143
+ UpdateAlertSensitivityRequest,
127
144
  UpdateAssertionSeverity,
128
145
  UpdateEnvironmentRequest,
129
146
  UpdateMaintenanceWindowRequest,
@@ -184,6 +201,7 @@ __all__ = [
184
201
  "Dependencies",
185
202
  "DeployLock",
186
203
  "MaintenanceWindows",
204
+ "Services",
187
205
  "Status",
188
206
  "StatusPages",
189
207
  # Response DTOs
@@ -216,6 +234,19 @@ __all__ = [
216
234
  "ApiKeyDto",
217
235
  "ApiKeyCreateResponse",
218
236
  "ServiceSubscriptionDto",
237
+ "ServiceCatalogDto",
238
+ "ServiceDetailDto",
239
+ "ServiceLiveStatusDto",
240
+ "ServiceComponentDto",
241
+ "ServiceIncidentDto",
242
+ "ServiceIncidentDetailDto",
243
+ "ServiceUptimeResponse",
244
+ "ServiceDayDetailDto",
245
+ "ScheduledMaintenanceDto",
246
+ "CategoryDto",
247
+ "GlobalStatusSummaryDto",
248
+ "BatchComponentUptimeDto",
249
+ "ComponentUptimeDayDto",
219
250
  "MonitorVersionDto",
220
251
  "CheckResultDto",
221
252
  "DashboardOverviewDto",
@@ -261,6 +292,8 @@ __all__ = [
261
292
  "UpdateWebhookEndpointRequest",
262
293
  "CreateApiKeyRequest",
263
294
  "AcquireDeployLockRequest",
295
+ "ServiceSubscribeRequest",
296
+ "UpdateAlertSensitivityRequest",
264
297
  # Enum aliases (descriptive names for codegen-numbered enums)
265
298
  "AffectedComponentStatus",
266
299
  "AlertDeliveryStatus",
@@ -272,6 +305,7 @@ __all__ = [
272
305
  "IncidentSeverity",
273
306
  "IncidentStatus",
274
307
  "IncidentUpdateCreatedBy",
308
+ "LifecycleStatus",
275
309
  "LinkedIncidentStatus",
276
310
  "MemberStatus",
277
311
  "MembershipStatus",
@@ -82,6 +82,11 @@ class DevhelmApiError(DevhelmError):
82
82
  The optional `request_id` field is the per-request id emitted by the
83
83
  API as the `X-Request-Id` response header and embedded in the JSON
84
84
  error body. Always include it in support tickets.
85
+
86
+ The optional `retry_after` field is the parsed value of the
87
+ `Retry-After` response header in whole seconds. It's populated on
88
+ rate-limit (429) responses that include the header so callers can back
89
+ off for exactly as long as the server asked; ``None`` otherwise.
85
90
  """
86
91
 
87
92
  status: int
@@ -93,6 +98,7 @@ class DevhelmApiError(DevhelmError):
93
98
  # narrowing. (Subclasses still inherit the same `str` type.)
94
99
  code: str
95
100
  request_id: str | None
101
+ retry_after: int | None
96
102
 
97
103
  def __init__(
98
104
  self,
@@ -103,6 +109,7 @@ class DevhelmApiError(DevhelmError):
103
109
  body: dict[str, Any] | str | None = None,
104
110
  code: str | None = None,
105
111
  request_id: str | None = None,
112
+ retry_after: int | None = None,
106
113
  ) -> None:
107
114
  super().__init__(message)
108
115
  self.status = status
@@ -113,6 +120,9 @@ class DevhelmApiError(DevhelmError):
113
120
  # `err.code` is never ``None`` for callers switching on category.
114
121
  self.code = code or "API_ERROR"
115
122
  self.request_id = request_id
123
+ # Parsed from the `Retry-After` response header (seconds). Populated
124
+ # on 429 / 503 responses that include it; ``None`` otherwise.
125
+ self.retry_after = retry_after
116
126
 
117
127
 
118
128
  class DevhelmAuthError(DevhelmApiError):
@@ -152,8 +162,28 @@ class DevhelmTransportError(DevhelmError):
152
162
  self.__cause__ = cause
153
163
 
154
164
 
165
+ def _parse_retry_after(value: str | None) -> int | None:
166
+ """Parse a ``Retry-After`` header value into whole seconds.
167
+
168
+ The API emits ``Retry-After`` as an integer number of seconds. We parse
169
+ defensively: any non-integer value (an HTTP-date form, or garbage from a
170
+ misbehaving proxy) yields ``None`` rather than raising, so a malformed
171
+ header can never break error construction.
172
+ """
173
+ if value is None:
174
+ return None
175
+ try:
176
+ return int(value)
177
+ except (TypeError, ValueError):
178
+ return None
179
+
180
+
155
181
  def error_from_response(
156
- status: int, body: str, *, request_id: str | None = None
182
+ status: int,
183
+ body: str,
184
+ *,
185
+ request_id: str | None = None,
186
+ retry_after: str | None = None,
157
187
  ) -> DevhelmApiError:
158
188
  """Map an HTTP error response to a typed DevhelmApiError subclass.
159
189
 
@@ -161,6 +191,11 @@ def error_from_response(
161
191
  pulled out at the call site (rather than re-parsed from the body) so the
162
192
  SDK still surfaces the id even when the server returns a non-JSON body
163
193
  (e.g. an HTML error page from a misconfigured proxy).
194
+
195
+ `retry_after` is the raw value of the `Retry-After` response header,
196
+ pulled out at the call site for the same reason. It's parsed into whole
197
+ seconds and surfaced as ``err.retry_after`` (e.g. on 429 responses) so
198
+ callers can back off for exactly as long as the server asked.
164
199
  """
165
200
  message = f"HTTP {status}"
166
201
  detail: str | None = None
@@ -195,6 +230,7 @@ def error_from_response(
195
230
  "body": parsed_body,
196
231
  "code": code,
197
232
  "request_id": resolved_request_id,
233
+ "retry_after": _parse_retry_after(retry_after),
198
234
  }
199
235
 
200
236
  if status in (401, 403):
@@ -180,6 +180,7 @@ def checked_fetch(response: httpx.Response) -> _JsonResponse:
180
180
  response.status_code,
181
181
  response.text,
182
182
  request_id=response.headers.get("x-request-id"),
183
+ retry_after=response.headers.get("retry-after"),
183
184
  )
184
185
 
185
186
 
@@ -151,9 +151,18 @@ def fetch_cursor_page(
151
151
  model_class: type[M],
152
152
  cursor: str | None = None,
153
153
  limit: int | None = None,
154
+ *,
155
+ extra_params: dict[str, Any] | None = None,
154
156
  ) -> CursorPage[M]:
155
- """Fetch a single page from a cursor-paginated endpoint with validation."""
156
- params: dict[str, Any] = {}
157
+ """Fetch a single page from a cursor-paginated endpoint with validation.
158
+
159
+ ``extra_params`` is merged into the request so callers can forward
160
+ server-side filter kwargs (``category``, ``search``, …) alongside the
161
+ cursor controls. Pagination keys (``cursor``, ``limit``) always win
162
+ over user-supplied ``extra_params`` to keep the iterator's invariants
163
+ intact.
164
+ """
165
+ params: dict[str, Any] = dict(extra_params) if extra_params else {}
157
166
  if cursor:
158
167
  params["cursor"] = cursor
159
168
  if limit:
@@ -15,6 +15,7 @@ from devhelm.resources.monitors import Monitors
15
15
  from devhelm.resources.notification_policies import NotificationPolicies
16
16
  from devhelm.resources.resource_groups import ResourceGroups
17
17
  from devhelm.resources.secrets import Secrets
18
+ from devhelm.resources.services import Services
18
19
  from devhelm.resources.status import Status
19
20
  from devhelm.resources.status_pages import StatusPages
20
21
  from devhelm.resources.tags import Tags
@@ -53,6 +54,7 @@ class Devhelm:
53
54
  dependencies: Dependencies
54
55
  deploy_lock: DeployLock
55
56
  maintenance_windows: MaintenanceWindows
57
+ services: Services
56
58
  status: Status
57
59
  status_pages: StatusPages
58
60
 
@@ -99,5 +101,6 @@ class Devhelm:
99
101
  self.dependencies = Dependencies(client)
100
102
  self.deploy_lock = DeployLock(client)
101
103
  self.maintenance_windows = MaintenanceWindows(client)
104
+ self.services = Services(client)
102
105
  self.status = Status(client)
103
106
  self.status_pages = StatusPages(client)
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import httpx
4
4
 
5
5
  from devhelm._generated import ApiKeyCreateResponse, ApiKeyDto, CreateApiKeyRequest
6
- from devhelm._http import api_delete, api_post, path_param
6
+ from devhelm._http import api_delete, api_get, api_post, path_param
7
7
  from devhelm._pagination import Page, fetch_all_pages, fetch_page
8
8
  from devhelm._validation import RequestBody, parse_single, validate_request
9
9
 
@@ -22,6 +22,14 @@ class ApiKeys:
22
22
  """List API keys with manual page control."""
23
23
  return fetch_page(self._client, "/api/v1/api-keys", ApiKeyDto, page, size)
24
24
 
25
+ def get(self, id: int | str) -> ApiKeyDto:
26
+ """Get a single API key by ID."""
27
+ return parse_single(
28
+ ApiKeyDto,
29
+ api_get(self._client, f"/api/v1/api-keys/{path_param(id)}"),
30
+ f"GET /api/v1/api-keys/{id}",
31
+ )
32
+
25
33
  def create(self, body: RequestBody[CreateApiKeyRequest]) -> ApiKeyCreateResponse:
26
34
  """Create an API key. Returns the key value (shown only once)."""
27
35
  body = validate_request(CreateApiKeyRequest, body, "apiKeys.create")
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ import httpx
4
+
5
+ from devhelm._generated import (
6
+ ServiceSubscribeRequest,
7
+ ServiceSubscriptionDto,
8
+ UpdateAlertSensitivityRequest,
9
+ )
10
+ from devhelm._http import api_delete, api_get, api_patch, api_post, path_param
11
+ from devhelm._pagination import Page, fetch_all_pages, fetch_page
12
+ from devhelm._validation import parse_single, validate_request
13
+
14
+
15
+ class Dependencies:
16
+ """Service dependency tracking (service subscriptions)."""
17
+
18
+ def __init__(self, client: httpx.Client) -> None:
19
+ self._client = client
20
+
21
+ def list(self) -> list[ServiceSubscriptionDto]:
22
+ """List all tracked service dependencies."""
23
+ return fetch_all_pages(
24
+ self._client, "/api/v1/service-subscriptions", ServiceSubscriptionDto
25
+ )
26
+
27
+ def list_page(self, page: int, size: int) -> Page[ServiceSubscriptionDto]:
28
+ """List tracked dependencies with manual page control."""
29
+ return fetch_page(
30
+ self._client,
31
+ "/api/v1/service-subscriptions",
32
+ ServiceSubscriptionDto,
33
+ page,
34
+ size,
35
+ )
36
+
37
+ def get(self, id: int | str) -> ServiceSubscriptionDto:
38
+ """Get a tracked dependency by ID."""
39
+ return parse_single(
40
+ ServiceSubscriptionDto,
41
+ api_get(self._client, f"/api/v1/service-subscriptions/{path_param(id)}"),
42
+ f"GET /api/v1/service-subscriptions/{id}",
43
+ )
44
+
45
+ def track(
46
+ self,
47
+ slug: str,
48
+ *,
49
+ component_id: str | None = None,
50
+ alert_sensitivity: str | None = None,
51
+ ) -> ServiceSubscriptionDto:
52
+ """Track a new service dependency by slug.
53
+
54
+ ``component_id`` subscribes to one component instead of the whole
55
+ service. ``alert_sensitivity`` is one of ``ALL``,
56
+ ``INCIDENTS_ONLY``, ``MAJOR_ONLY``, or ``AWARENESS`` (the API
57
+ default — silent tracking with no alert fan-out). The request body
58
+ is omitted entirely when neither kwarg is provided.
59
+ """
60
+ body: ServiceSubscribeRequest | None = None
61
+ if component_id is not None or alert_sensitivity is not None:
62
+ fields: dict[str, str] = {}
63
+ if component_id is not None:
64
+ fields["componentId"] = component_id
65
+ if alert_sensitivity is not None:
66
+ fields["alertSensitivity"] = alert_sensitivity
67
+ body = validate_request(
68
+ ServiceSubscribeRequest, fields, "dependencies.track"
69
+ )
70
+ return parse_single(
71
+ ServiceSubscriptionDto,
72
+ api_post(
73
+ self._client, f"/api/v1/service-subscriptions/{path_param(slug)}", body
74
+ ),
75
+ f"POST /api/v1/service-subscriptions/{slug}",
76
+ )
77
+
78
+ def update_alert_sensitivity(
79
+ self, subscription_id: int | str, alert_sensitivity: str
80
+ ) -> ServiceSubscriptionDto:
81
+ """Update the alert sensitivity on a tracked dependency.
82
+
83
+ ``alert_sensitivity`` is one of ``ALL``, ``INCIDENTS_ONLY``,
84
+ ``MAJOR_ONLY``, or ``AWARENESS``.
85
+ """
86
+ body = validate_request(
87
+ UpdateAlertSensitivityRequest,
88
+ {"alertSensitivity": alert_sensitivity},
89
+ "dependencies.update_alert_sensitivity",
90
+ )
91
+ path = f"/api/v1/service-subscriptions/{path_param(subscription_id)}"
92
+ return parse_single(
93
+ ServiceSubscriptionDto,
94
+ api_patch(self._client, f"{path}/alert-sensitivity", body),
95
+ f"PATCH /api/v1/service-subscriptions/{subscription_id}/alert-sensitivity",
96
+ )
97
+
98
+ def delete(self, id: int | str) -> None:
99
+ """Remove a tracked dependency."""
100
+ api_delete(self._client, f"/api/v1/service-subscriptions/{path_param(id)}")