devhelm 1.2.0__tar.gz → 1.3.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 (58) hide show
  1. {devhelm-1.2.0 → devhelm-1.3.0}/PKG-INFO +2 -1
  2. {devhelm-1.2.0 → devhelm-1.3.0}/README.md +1 -0
  3. {devhelm-1.2.0 → devhelm-1.3.0}/docs/openapi/monitoring-api.json +9 -0
  4. {devhelm-1.2.0 → devhelm-1.3.0}/pyproject.toml +1 -1
  5. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/__init__.py +34 -0
  6. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_pagination.py +11 -2
  7. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/client.py +3 -0
  8. devhelm-1.3.0/src/devhelm/resources/dependencies.py +100 -0
  9. devhelm-1.3.0/src/devhelm/resources/services.py +322 -0
  10. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/types.py +32 -0
  11. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_client.py +19 -0
  12. devhelm-1.3.0/tests/test_dependencies.py +142 -0
  13. devhelm-1.3.0/tests/test_services.py +281 -0
  14. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_spec_parity.py +4 -0
  15. {devhelm-1.2.0 → devhelm-1.3.0}/uv.lock +1 -1
  16. devhelm-1.2.0/src/devhelm/resources/dependencies.py +0 -51
  17. {devhelm-1.2.0 → devhelm-1.3.0}/.github/workflows/ci.yml +0 -0
  18. {devhelm-1.2.0 → devhelm-1.3.0}/.github/workflows/release.yml +0 -0
  19. {devhelm-1.2.0 → devhelm-1.3.0}/.github/workflows/spec-check.yml +0 -0
  20. {devhelm-1.2.0 → devhelm-1.3.0}/.gitignore +0 -0
  21. {devhelm-1.2.0 → devhelm-1.3.0}/LICENSE +0 -0
  22. {devhelm-1.2.0 → devhelm-1.3.0}/Makefile +0 -0
  23. {devhelm-1.2.0 → devhelm-1.3.0}/scripts/emit_response_enums.py +0 -0
  24. {devhelm-1.2.0 → devhelm-1.3.0}/scripts/inject_strict_config.py +0 -0
  25. {devhelm-1.2.0 → devhelm-1.3.0}/scripts/regen-from.sh +0 -0
  26. {devhelm-1.2.0 → devhelm-1.3.0}/scripts/release.sh +0 -0
  27. {devhelm-1.2.0 → devhelm-1.3.0}/scripts/typegen.sh +0 -0
  28. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_enums.py +0 -0
  29. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_errors.py +0 -0
  30. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_generated.py +0 -0
  31. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_http.py +0 -0
  32. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/_validation.py +0 -0
  33. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/py.typed +0 -0
  34. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/__init__.py +0 -0
  35. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/alert_channels.py +0 -0
  36. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/api_keys.py +0 -0
  37. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/deploy_lock.py +0 -0
  38. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/environments.py +0 -0
  39. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/forensics.py +0 -0
  40. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/incidents.py +0 -0
  41. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/maintenance_windows.py +0 -0
  42. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/monitors.py +0 -0
  43. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/notification_policies.py +0 -0
  44. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/resource_groups.py +0 -0
  45. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/secrets.py +0 -0
  46. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/status.py +0 -0
  47. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/status_pages.py +0 -0
  48. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/tags.py +0 -0
  49. {devhelm-1.2.0 → devhelm-1.3.0}/src/devhelm/resources/webhooks.py +0 -0
  50. {devhelm-1.2.0 → devhelm-1.3.0}/tests/__init__.py +0 -0
  51. {devhelm-1.2.0 → devhelm-1.3.0}/tests/run_sdk.py +0 -0
  52. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_errors.py +0 -0
  53. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_http.py +0 -0
  54. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_maintenance_windows.py +0 -0
  55. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_negative_validation.py +0 -0
  56. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_schemas.py +0 -0
  57. {devhelm-1.2.0 → devhelm-1.3.0}/tests/test_typing.py +0 -0
  58. {devhelm-1.2.0 → devhelm-1.3.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.3.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
@@ -13520,6 +13520,15 @@
13520
13520
  "type": "boolean"
13521
13521
  }
13522
13522
  },
13523
+ {
13524
+ "name": "search",
13525
+ "in": "query",
13526
+ "description": "Case-insensitive substring match on service name or slug",
13527
+ "required": false,
13528
+ "schema": {
13529
+ "type": "string"
13530
+ }
13531
+ },
13523
13532
  {
13524
13533
  "name": "cursor",
13525
13534
  "in": "query",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devhelm"
3
- version = "1.2.0"
3
+ version = "1.3.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",
@@ -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)
@@ -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)}")
@@ -0,0 +1,322 @@
1
+ """Status Data catalog: third-party service status, components, incidents,
2
+ uptime, and maintenances.
3
+
4
+ These are the read-only ``/api/v1/services`` + ``/api/v1/categories``
5
+ endpoints backing DevHelm's vendor-status catalog (the data the dependency
6
+ tracker subscribes to). Services are addressed by slug (``"github"``) or
7
+ UUID interchangeably — every ``slug_or_id`` parameter accepts either.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import builtins
13
+ from datetime import date, datetime
14
+ from typing import TypeVar
15
+
16
+ import httpx
17
+ from pydantic import BaseModel
18
+
19
+ from devhelm._generated import (
20
+ BatchComponentUptimeDto,
21
+ CategoryDto,
22
+ ComponentUptimeDayDto,
23
+ GlobalStatusSummaryDto,
24
+ ScheduledMaintenanceDto,
25
+ ServiceCatalogDto,
26
+ ServiceComponentDto,
27
+ ServiceDayDetailDto,
28
+ ServiceDetailDto,
29
+ ServiceIncidentDetailDto,
30
+ ServiceIncidentDto,
31
+ ServiceLiveStatusDto,
32
+ ServiceUptimeResponse,
33
+ )
34
+ from devhelm._http import api_get, path_param
35
+ from devhelm._pagination import CursorPage, Page, _validate_page, fetch_cursor_page
36
+ from devhelm._validation import parse_list, parse_single
37
+
38
+ M = TypeVar("M", bound=BaseModel)
39
+
40
+ # Explicit primitive-only param dict avoids mypy's ``disallow_any_explicit``
41
+ # in strict mode while still accepting the shapes httpx serialises for us.
42
+ # List values serialise as repeated query keys (``status=a&status=b``).
43
+ _ParamValue = str | int | bool | list[str] | None
44
+ _ParamDict = dict[str, _ParamValue]
45
+
46
+
47
+ def _format_date(value: date | datetime | str) -> str:
48
+ """Normalise ``from``/``to`` calendar params to the ISO ``yyyy-MM-dd``
49
+ the API expects. ``datetime`` is truncated to its calendar day because
50
+ the endpoints reject full timestamps.
51
+ """
52
+ if isinstance(value, datetime):
53
+ return value.date().isoformat()
54
+ if isinstance(value, date):
55
+ return value.isoformat()
56
+ return value
57
+
58
+
59
+ def _window_params(
60
+ period: str, from_: date | datetime | str | None, to: date | datetime | str | None
61
+ ) -> _ParamDict:
62
+ """Pack the shared uptime-window params. ``period`` is always sent (the
63
+ API documents that an explicit ``from``/``to`` window wins over it when
64
+ both are supplied, so forwarding the default is harmless).
65
+ """
66
+ params: _ParamDict = {"period": period}
67
+ if from_ is not None:
68
+ params["from"] = _format_date(from_)
69
+ if to is not None:
70
+ params["to"] = _format_date(to)
71
+ return params
72
+
73
+
74
+ _BASE = "/api/v1/services"
75
+
76
+
77
+ def _service_path(slug_or_id: str) -> str:
78
+ return f"{_BASE}/{path_param(slug_or_id)}"
79
+
80
+
81
+ class Services:
82
+ """Status Data catalog: vendor services, components, incidents, uptime."""
83
+
84
+ def __init__(self, client: httpx.Client) -> None:
85
+ self._client = client
86
+
87
+ def _fetch_table(
88
+ self, path: str, model_class: type[M], params: _ParamDict | None = None
89
+ ) -> list[M]:
90
+ """Catalog list endpoints return the offset-page envelope
91
+ ``{data, hasNext, hasPrev, …}`` but are not actually paginated
92
+ server-side (no ``page``/``size`` params) — unwrap the envelope and
93
+ hand back the validated items directly.
94
+ """
95
+ resp = api_get(self._client, path, params=params or None)
96
+ envelope = _validate_page(resp)
97
+ return parse_list(model_class, envelope.data, f"GET {path}")
98
+
99
+ def list(
100
+ self,
101
+ *,
102
+ category: str | None = None,
103
+ status: str | None = None,
104
+ search: str | None = None,
105
+ cursor: str | None = None,
106
+ limit: int = 20,
107
+ ) -> CursorPage[ServiceCatalogDto]:
108
+ """List catalog services (cursor-paginated).
109
+
110
+ Optional server-side filters mirror the documented
111
+ ``GET /api/v1/services`` query params: ``category`` (exact
112
+ category name), ``status`` (current overall status, e.g.
113
+ ``"operational"``), and ``search`` (free-text match on name/slug).
114
+ Pass ``cursor`` from a previous page's ``next_cursor`` to continue.
115
+ """
116
+ filters: _ParamDict = {}
117
+ if category is not None:
118
+ filters["category"] = category
119
+ if status is not None:
120
+ filters["status"] = status
121
+ if search is not None:
122
+ filters["search"] = search
123
+ return fetch_cursor_page(
124
+ self._client,
125
+ _BASE,
126
+ ServiceCatalogDto,
127
+ cursor=cursor,
128
+ limit=limit,
129
+ extra_params=filters,
130
+ )
131
+
132
+ def get(self, slug_or_id: str, *, summary: bool = False) -> ServiceDetailDto:
133
+ """Get a service's detail view by slug or ID.
134
+
135
+ ``summary=True`` requests the trimmed payload (component groups
136
+ without leaf children) used by list-style consumers.
137
+ """
138
+ params: _ParamDict | None = {"summary": True} if summary else None
139
+ return parse_single(
140
+ ServiceDetailDto,
141
+ api_get(self._client, _service_path(slug_or_id), params=params),
142
+ f"GET {_BASE}/{slug_or_id}",
143
+ )
144
+
145
+ def live_status(self, slug_or_id: str) -> ServiceLiveStatusDto:
146
+ """Get a service's current live status (overall + per-component)."""
147
+ return parse_single(
148
+ ServiceLiveStatusDto,
149
+ api_get(self._client, f"{_service_path(slug_or_id)}/live-status"),
150
+ f"GET {_BASE}/{slug_or_id}/live-status",
151
+ )
152
+
153
+ # ``builtins.list`` below because the ``list`` *method* shadows the
154
+ # builtin inside the class body for everything defined after it.
155
+ def categories(self) -> builtins.list[CategoryDto]:
156
+ """List all service categories with their service counts."""
157
+ return self._fetch_table("/api/v1/categories", CategoryDto)
158
+
159
+ def summary(self) -> GlobalStatusSummaryDto:
160
+ """Get the global status summary across the whole catalog."""
161
+ return parse_single(
162
+ GlobalStatusSummaryDto,
163
+ api_get(self._client, f"{_BASE}/summary"),
164
+ f"GET {_BASE}/summary",
165
+ )
166
+
167
+ def components(
168
+ self, slug_or_id: str, *, group_id: str | None = None
169
+ ) -> builtins.list[ServiceComponentDto]:
170
+ """List a service's active components.
171
+
172
+ ``group_id`` restricts the result to direct children of that group
173
+ component.
174
+ """
175
+ params: _ParamDict = {}
176
+ if group_id is not None:
177
+ params["groupId"] = group_id
178
+ return self._fetch_table(
179
+ f"{_service_path(slug_or_id)}/components", ServiceComponentDto, params
180
+ )
181
+
182
+ def component_uptime(
183
+ self,
184
+ slug_or_id: str,
185
+ component_id: str,
186
+ *,
187
+ period: str = "30d",
188
+ from_: date | datetime | str | None = None,
189
+ to: date | datetime | str | None = None,
190
+ ) -> builtins.list[ComponentUptimeDayDto]:
191
+ """Get daily uptime data for one component.
192
+
193
+ Pass either a preset ``period`` (``7d``, ``30d``, ``90d``, ``1y``)
194
+ or an explicit ``from_``/``to`` calendar window (ISO ``yyyy-MM-dd``,
195
+ max 730 days; ``to`` defaults to today). The explicit window wins
196
+ when both are supplied.
197
+ """
198
+ return self._fetch_table(
199
+ f"{_service_path(slug_or_id)}/components/{path_param(component_id)}/uptime",
200
+ ComponentUptimeDayDto,
201
+ _window_params(period, from_, to),
202
+ )
203
+
204
+ def batch_component_uptime(
205
+ self,
206
+ slug_or_id: str,
207
+ *,
208
+ period: str = "30d",
209
+ from_: date | datetime | str | None = None,
210
+ to: date | datetime | str | None = None,
211
+ ) -> BatchComponentUptimeDto:
212
+ """Get daily uptime for every leaf component in a single request,
213
+ keyed by component ID.
214
+
215
+ Accepts the same window kwargs as :meth:`component_uptime`.
216
+ """
217
+ return parse_single(
218
+ BatchComponentUptimeDto,
219
+ api_get(
220
+ self._client,
221
+ f"{_service_path(slug_or_id)}/components/uptime",
222
+ params=_window_params(period, from_, to),
223
+ ),
224
+ f"GET {_BASE}/{slug_or_id}/components/uptime",
225
+ )
226
+
227
+ def day(self, slug_or_id: str, date: date | str) -> ServiceDayDetailDto:
228
+ """Get the per-component rollup for one UTC calendar day
229
+ (ISO ``yyyy-MM-dd``).
230
+ """
231
+ return parse_single(
232
+ ServiceDayDetailDto,
233
+ api_get(
234
+ self._client,
235
+ f"{_service_path(slug_or_id)}/days/{path_param(_format_date(date))}",
236
+ ),
237
+ f"GET {_BASE}/{slug_or_id}/days/{date}",
238
+ )
239
+
240
+ def incidents(
241
+ self,
242
+ slug_or_id: str | None = None,
243
+ *,
244
+ status: str | None = None,
245
+ from_: date | datetime | str | None = None,
246
+ category: str | None = None,
247
+ page: int = 0,
248
+ size: int = 20,
249
+ ) -> Page[ServiceIncidentDto]:
250
+ """List vendor incidents (paginated).
251
+
252
+ With ``slug_or_id``, lists incidents for that one service; without
253
+ it, lists incidents across the whole catalog. ``status`` filters by
254
+ incident status (e.g. ``"resolved"``), ``from_`` bounds the window
255
+ start, and ``category`` (cross-service mode only) restricts to one
256
+ service category.
257
+ """
258
+ if slug_or_id is None:
259
+ path = f"{_BASE}/incidents"
260
+ else:
261
+ path = f"{_service_path(slug_or_id)}/incidents"
262
+ params: _ParamDict = {"page": page, "size": size}
263
+ if status is not None:
264
+ params["status"] = status
265
+ if from_ is not None:
266
+ params["from"] = _format_date(from_)
267
+ if category is not None:
268
+ params["category"] = category
269
+
270
+ resp = api_get(self._client, path, params=params)
271
+ envelope = _validate_page(resp)
272
+ return Page(
273
+ data=parse_list(ServiceIncidentDto, envelope.data, f"GET {path}"),
274
+ has_next=envelope.hasNext,
275
+ has_prev=envelope.hasPrev,
276
+ total_elements=envelope.totalElements,
277
+ total_pages=envelope.totalPages,
278
+ )
279
+
280
+ def incident(self, slug_or_id: str, incident_id: str) -> ServiceIncidentDetailDto:
281
+ """Get one vendor incident with its full update timeline."""
282
+ return parse_single(
283
+ ServiceIncidentDetailDto,
284
+ api_get(
285
+ self._client,
286
+ f"{_service_path(slug_or_id)}/incidents/{path_param(incident_id)}",
287
+ ),
288
+ f"GET {_BASE}/{slug_or_id}/incidents/{incident_id}",
289
+ )
290
+
291
+ def uptime(
292
+ self, slug_or_id: str, *, period: str = "30d", granularity: str = "daily"
293
+ ) -> ServiceUptimeResponse:
294
+ """Get a service's uptime with per-bucket breakdown.
295
+
296
+ ``period`` is a preset window (``7d``, ``30d``, ``90d``, ``1y``);
297
+ ``granularity`` is ``"hourly"`` or ``"daily"``.
298
+ """
299
+ return parse_single(
300
+ ServiceUptimeResponse,
301
+ api_get(
302
+ self._client,
303
+ f"{_service_path(slug_or_id)}/uptime",
304
+ params={"period": period, "granularity": granularity},
305
+ ),
306
+ f"GET {_BASE}/{slug_or_id}/uptime",
307
+ )
308
+
309
+ def maintenances(
310
+ self, slug_or_id: str, *, status: str | builtins.list[str] | None = None
311
+ ) -> builtins.list[ScheduledMaintenanceDto]:
312
+ """List a service's scheduled maintenances.
313
+
314
+ ``status`` filters by maintenance status (``scheduled``,
315
+ ``in_progress``, ``completed``); pass a list to match several.
316
+ """
317
+ params: _ParamDict = {}
318
+ if status is not None:
319
+ params["status"] = status
320
+ return self._fetch_table(
321
+ f"{_service_path(slug_or_id)}/maintenances", ScheduledMaintenanceDto, params
322
+ )