hyperping 1.2.1__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.
- {hyperping-1.2.1 → hyperping-1.4.0}/PKG-INFO +1 -1
- {hyperping-1.2.1 → hyperping-1.4.0}/pyproject.toml +1 -1
- hyperping-1.4.0/scripts/verify_endpoints.py +166 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/__init__.py +33 -5
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_client.py +2 -3
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_incidents_mixin.py +3 -9
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_maintenance_mixin.py +3 -9
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_monitors_mixin.py +2 -6
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_outages_mixin.py +6 -13
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_statuspages_mixin.py +15 -13
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_incidents_mixin.py +3 -1
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_internals.py +1 -4
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_maintenance_mixin.py +3 -1
- hyperping-1.4.0/src/hyperping/_mcp_transport.py +145 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_monitors_mixin.py +1 -3
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_outages_mixin.py +4 -7
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_statuspages_mixin.py +12 -4
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_utils.py +2 -5
- hyperping-1.4.0/src/hyperping/_version.py +1 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/client.py +6 -4
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/endpoints.py +3 -0
- hyperping-1.4.0/src/hyperping/mcp_client.py +221 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/__init__.py +27 -1
- hyperping-1.4.0/src/hyperping/models/_integration_models.py +14 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_monitor_models.py +3 -8
- hyperping-1.4.0/src/hyperping/models/_observability_models.py +46 -0
- hyperping-1.4.0/src/hyperping/models/_oncall_models.py +27 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_outage_models.py +25 -0
- hyperping-1.4.0/src/hyperping/models/_reporting_models.py +17 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_async_client.py +14 -42
- hyperping-1.4.0/tests/unit/test_async_preexisting.py +325 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_healthchecks.py +8 -21
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_incidents.py +1 -3
- hyperping-1.4.0/tests/unit/test_mcp_client.py +161 -0
- hyperping-1.4.0/tests/unit/test_mcp_transport.py +161 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_monitors.py +5 -15
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_outages.py +3 -3
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_pagination.py +1 -3
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_statuspages.py +11 -15
- hyperping-1.2.1/src/hyperping/_version.py +0 -1
- {hyperping-1.2.1 → hyperping-1.4.0}/.gitignore +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/CHANGELOG.md +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/CONTRIBUTING.md +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/LICENSE +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/README.md +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_async_healthchecks_mixin.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_circuit_breaker.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_healthchecks_mixin.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_monitor_constants.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/_protocols.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/exceptions.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_healthcheck_models.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_incident_models.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_maintenance_models.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/models/_statuspage_models.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/src/hyperping/py.typed +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/__init__.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/__init__.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/conftest.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_maintenance.py +0 -0
- {hyperping-1.2.1 → hyperping-1.4.0}/tests/unit/test_sdk_surface.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperping
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Python SDK for the Hyperping uptime monitoring and incident management API
|
|
5
5
|
Project-URL: Homepage, https://github.com/develeap/hyperping-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/develeap/hyperping-python#readme
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hyperping"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "Python SDK for the Hyperping uptime monitoring and incident management API"
|
|
9
9
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verify speculative MCP-discovered endpoint paths against the live Hyperping API.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
export HYPERPING_API_KEY=sk_...
|
|
6
|
+
python scripts/verify_endpoints.py
|
|
7
|
+
|
|
8
|
+
All calls are read-only (GET only). Safe to run repeatedly.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
API_BASE = "https://api.hyperping.io"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Result:
|
|
22
|
+
method: str
|
|
23
|
+
path: str
|
|
24
|
+
status: int
|
|
25
|
+
ok: bool
|
|
26
|
+
snippet: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def probe(
|
|
30
|
+
client: httpx.Client,
|
|
31
|
+
method_name: str,
|
|
32
|
+
path: str,
|
|
33
|
+
params: dict | None = None,
|
|
34
|
+
) -> Result:
|
|
35
|
+
"""Send a GET request and return a Result."""
|
|
36
|
+
url = f"{API_BASE}{path}"
|
|
37
|
+
try:
|
|
38
|
+
resp = client.get(url, params=params or {})
|
|
39
|
+
body = resp.text[:120].replace("\n", " ")
|
|
40
|
+
return Result(
|
|
41
|
+
method=method_name,
|
|
42
|
+
path=path,
|
|
43
|
+
status=resp.status_code,
|
|
44
|
+
ok=resp.status_code < 400,
|
|
45
|
+
snippet=body,
|
|
46
|
+
)
|
|
47
|
+
except httpx.HTTPError as exc:
|
|
48
|
+
return Result(
|
|
49
|
+
method=method_name,
|
|
50
|
+
path=path,
|
|
51
|
+
status=0,
|
|
52
|
+
ok=False,
|
|
53
|
+
snippet=f"Connection error: {exc}",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main() -> int:
|
|
58
|
+
api_key = os.environ.get("HYPERPING_API_KEY", "")
|
|
59
|
+
if not api_key:
|
|
60
|
+
print("ERROR: Set HYPERPING_API_KEY environment variable.")
|
|
61
|
+
return 1
|
|
62
|
+
|
|
63
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
64
|
+
client = httpx.Client(headers=headers, timeout=15.0)
|
|
65
|
+
|
|
66
|
+
# -- Fetch real UUIDs for sub-resource endpoints --
|
|
67
|
+
print("Fetching monitor and outage UUIDs for sub-resource tests...")
|
|
68
|
+
monitor_uuid: str | None = None
|
|
69
|
+
outage_uuid: str | None = None
|
|
70
|
+
|
|
71
|
+
resp = client.get(f"{API_BASE}/v1/monitors")
|
|
72
|
+
if resp.status_code == 200:
|
|
73
|
+
data = resp.json()
|
|
74
|
+
monitors = data if isinstance(data, list) else data.get("monitors", [])
|
|
75
|
+
if monitors:
|
|
76
|
+
monitor_uuid = monitors[0].get("uuid") or monitors[0].get("id")
|
|
77
|
+
print(f" Monitor UUID: {monitor_uuid}")
|
|
78
|
+
|
|
79
|
+
resp = client.get(f"{API_BASE}/v2/outages")
|
|
80
|
+
if resp.status_code == 200:
|
|
81
|
+
data = resp.json()
|
|
82
|
+
outages = data if isinstance(data, list) else data.get("outages", [])
|
|
83
|
+
if outages:
|
|
84
|
+
outage_uuid = outages[0].get("uuid") or outages[0].get("id")
|
|
85
|
+
print(f" Outage UUID: {outage_uuid}")
|
|
86
|
+
|
|
87
|
+
print()
|
|
88
|
+
|
|
89
|
+
# -- Define all speculative endpoints to verify --
|
|
90
|
+
checks: list[tuple[str, str, dict | None]] = [
|
|
91
|
+
# Endpoint enum speculative paths
|
|
92
|
+
("get_status_summary", "/v2/status-summary", None),
|
|
93
|
+
("list_recent_alerts", "/v2/alerts", None),
|
|
94
|
+
("list_on_call_schedules", "/v2/on-call-schedules", None),
|
|
95
|
+
("list_escalation_policies", "/v2/escalation-policies", None),
|
|
96
|
+
("list_team_members", "/v2/team-members", None),
|
|
97
|
+
("list_integrations", "/v2/integrations", None),
|
|
98
|
+
# Monitor search
|
|
99
|
+
("search_monitors", "/v1/monitors/search", {"query": "test"}),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Sub-resource paths needing a real UUID
|
|
103
|
+
if monitor_uuid:
|
|
104
|
+
checks.extend([
|
|
105
|
+
(
|
|
106
|
+
"get_monitor_response_time",
|
|
107
|
+
f"/v2/reporting/response-time/{monitor_uuid}",
|
|
108
|
+
{"period": "24h"},
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"get_monitor_mtta",
|
|
112
|
+
f"/v2/reporting/mtta/{monitor_uuid}",
|
|
113
|
+
{"period": "30d"},
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
"get_monitor_anomalies",
|
|
117
|
+
f"/v1/monitors/{monitor_uuid}/anomalies",
|
|
118
|
+
None,
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
"get_monitor_http_logs",
|
|
122
|
+
f"/v1/monitors/{monitor_uuid}/http-logs",
|
|
123
|
+
{"page": "0", "limit": "5"},
|
|
124
|
+
),
|
|
125
|
+
])
|
|
126
|
+
else:
|
|
127
|
+
print("WARNING: No monitors found; skipping monitor sub-resource checks.\n")
|
|
128
|
+
|
|
129
|
+
if outage_uuid:
|
|
130
|
+
checks.append((
|
|
131
|
+
"get_outage_timeline",
|
|
132
|
+
f"/v2/outages/{outage_uuid}/timeline",
|
|
133
|
+
None,
|
|
134
|
+
))
|
|
135
|
+
else:
|
|
136
|
+
print("WARNING: No outages found; skipping outage sub-resource checks.\n")
|
|
137
|
+
|
|
138
|
+
# -- Run probes --
|
|
139
|
+
results: list[Result] = []
|
|
140
|
+
for method_name, path, params in checks:
|
|
141
|
+
r = probe(client, method_name, path, params)
|
|
142
|
+
results.append(r)
|
|
143
|
+
|
|
144
|
+
client.close()
|
|
145
|
+
|
|
146
|
+
# -- Print results table --
|
|
147
|
+
print(f"{'Method':<30} {'Path':<50} {'Status':<8} {'Result'}")
|
|
148
|
+
print("-" * 120)
|
|
149
|
+
for r in results:
|
|
150
|
+
tag = "OK" if r.ok else ("404" if r.status == 404 else "ERROR")
|
|
151
|
+
print(f"{r.method:<30} {r.path:<50} {r.status:<8} {tag}")
|
|
152
|
+
if not r.ok:
|
|
153
|
+
print(f" {r.snippet}")
|
|
154
|
+
|
|
155
|
+
# -- Summary --
|
|
156
|
+
ok_count = sum(1 for r in results if r.ok)
|
|
157
|
+
fail_count = sum(1 for r in results if not r.ok)
|
|
158
|
+
not_found = sum(1 for r in results if r.status == 404)
|
|
159
|
+
print()
|
|
160
|
+
print(f"Total: {len(results)} | OK: {ok_count} | 404: {not_found} | Other errors: {fail_count - not_found}")
|
|
161
|
+
|
|
162
|
+
return 1 if fail_count > 0 else 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
sys.exit(main())
|
|
@@ -15,6 +15,7 @@ Quick start::
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from hyperping._async_client import AsyncHyperpingClient
|
|
18
|
+
from hyperping._mcp_transport import MCP_URL
|
|
18
19
|
from hyperping._version import __version__
|
|
19
20
|
from hyperping.client import (
|
|
20
21
|
CircuitBreaker,
|
|
@@ -35,10 +36,13 @@ from hyperping.exceptions import (
|
|
|
35
36
|
HyperpingRateLimitError,
|
|
36
37
|
HyperpingValidationError,
|
|
37
38
|
)
|
|
39
|
+
from hyperping.mcp_client import HyperpingMcpClient
|
|
38
40
|
from hyperping.models import (
|
|
39
41
|
DEFAULT_REGIONS,
|
|
40
42
|
AddIncidentUpdateRequest,
|
|
43
|
+
AlertNotification,
|
|
41
44
|
DnsRecordType,
|
|
45
|
+
EscalationPolicy,
|
|
42
46
|
Healthcheck,
|
|
43
47
|
HealthcheckCreate,
|
|
44
48
|
HealthcheckUpdate,
|
|
@@ -49,11 +53,13 @@ from hyperping.models import (
|
|
|
49
53
|
IncidentUpdate,
|
|
50
54
|
IncidentUpdateRequest,
|
|
51
55
|
IncidentUpdateType,
|
|
56
|
+
Integration,
|
|
52
57
|
LocalizedText,
|
|
53
58
|
Maintenance,
|
|
54
59
|
MaintenanceCreate,
|
|
55
60
|
MaintenanceUpdate,
|
|
56
61
|
Monitor,
|
|
62
|
+
MonitorAnomaly,
|
|
57
63
|
MonitorBase,
|
|
58
64
|
MonitorCreate,
|
|
59
65
|
MonitorFrequency,
|
|
@@ -63,10 +69,14 @@ from hyperping.models import (
|
|
|
63
69
|
MonitorTimeout,
|
|
64
70
|
MonitorUpdate,
|
|
65
71
|
NotificationOption,
|
|
72
|
+
OnCallSchedule,
|
|
66
73
|
Outage,
|
|
67
74
|
OutageAction,
|
|
68
75
|
OutageDetail,
|
|
69
76
|
OutageStats,
|
|
77
|
+
OutageTimeline,
|
|
78
|
+
OutageTimelineEvent,
|
|
79
|
+
ProbeLog,
|
|
70
80
|
Region,
|
|
71
81
|
ReportPeriod,
|
|
72
82
|
RequestHeader,
|
|
@@ -74,6 +84,7 @@ from hyperping.models import (
|
|
|
74
84
|
StatusPageCreate,
|
|
75
85
|
StatusPageSubscriber,
|
|
76
86
|
StatusPageUpdate,
|
|
87
|
+
StatusSummary,
|
|
77
88
|
)
|
|
78
89
|
|
|
79
90
|
__all__ = [
|
|
@@ -82,6 +93,9 @@ __all__ = [
|
|
|
82
93
|
# Clients
|
|
83
94
|
"AsyncHyperpingClient",
|
|
84
95
|
"HyperpingClient",
|
|
96
|
+
"HyperpingMcpClient",
|
|
97
|
+
# MCP
|
|
98
|
+
"MCP_URL",
|
|
85
99
|
# Configuration
|
|
86
100
|
"RetryConfig",
|
|
87
101
|
"CircuitBreakerConfig",
|
|
@@ -137,6 +151,19 @@ __all__ = [
|
|
|
137
151
|
# Outages
|
|
138
152
|
"Outage",
|
|
139
153
|
"OutageAction",
|
|
154
|
+
"OutageTimeline",
|
|
155
|
+
"OutageTimelineEvent",
|
|
156
|
+
# Observability
|
|
157
|
+
"MonitorAnomaly",
|
|
158
|
+
"ProbeLog",
|
|
159
|
+
"AlertNotification",
|
|
160
|
+
# On-call
|
|
161
|
+
"OnCallSchedule",
|
|
162
|
+
"EscalationPolicy",
|
|
163
|
+
# Integrations
|
|
164
|
+
"Integration",
|
|
165
|
+
# Reporting
|
|
166
|
+
"StatusSummary",
|
|
140
167
|
# Healthchecks
|
|
141
168
|
"Healthcheck",
|
|
142
169
|
"HealthcheckCreate",
|
|
@@ -161,8 +188,7 @@ def __getattr__(name: str) -> object:
|
|
|
161
188
|
|
|
162
189
|
if name == "HYPERPING_API_BASE":
|
|
163
190
|
warnings.warn(
|
|
164
|
-
"HYPERPING_API_BASE is deprecated and will be removed in v0.3.0. "
|
|
165
|
-
"Use API_BASE instead.",
|
|
191
|
+
"HYPERPING_API_BASE is deprecated and will be removed in v0.3.0. Use API_BASE instead.",
|
|
166
192
|
DeprecationWarning,
|
|
167
193
|
stacklevel=2,
|
|
168
194
|
)
|
|
@@ -170,8 +196,7 @@ def __getattr__(name: str) -> object:
|
|
|
170
196
|
|
|
171
197
|
if name == "API_PATHS":
|
|
172
198
|
warnings.warn(
|
|
173
|
-
"API_PATHS is deprecated and will be removed in v0.3.0. "
|
|
174
|
-
"Use the Endpoint enum instead.",
|
|
199
|
+
"API_PATHS is deprecated and will be removed in v0.3.0. Use the Endpoint enum instead.",
|
|
175
200
|
DeprecationWarning,
|
|
176
201
|
stacklevel=2,
|
|
177
202
|
)
|
|
@@ -199,7 +224,10 @@ def __getattr__(name: str) -> object:
|
|
|
199
224
|
|
|
200
225
|
# Symbols removed from __all__ (H5) but still accessible for backward compat
|
|
201
226
|
_endpoint_helpers = {
|
|
202
|
-
"EndpointConfig",
|
|
227
|
+
"EndpointConfig",
|
|
228
|
+
"ENDPOINTS",
|
|
229
|
+
"get_endpoint_url",
|
|
230
|
+
"get_version_for_endpoint",
|
|
203
231
|
}
|
|
204
232
|
if name in _endpoint_helpers:
|
|
205
233
|
from hyperping import endpoints as _ep
|
|
@@ -178,6 +178,7 @@ class AsyncHyperpingClient(
|
|
|
178
178
|
)
|
|
179
179
|
if status in (400, 422):
|
|
180
180
|
from hyperping.exceptions import HyperpingValidationError
|
|
181
|
+
|
|
181
182
|
raise HyperpingValidationError(
|
|
182
183
|
message=f"Validation error: {error_msg}",
|
|
183
184
|
status_code=status,
|
|
@@ -321,9 +322,7 @@ class AsyncHyperpingClient(
|
|
|
321
322
|
continue
|
|
322
323
|
self._circuit_breaker.record_failure()
|
|
323
324
|
if isinstance(e, httpx.TimeoutException):
|
|
324
|
-
raise HyperpingAPIError(
|
|
325
|
-
f"Request timeout after {max_attempts} attempts"
|
|
326
|
-
) from e
|
|
325
|
+
raise HyperpingAPIError(f"Request timeout after {max_attempts} attempts") from e
|
|
327
326
|
raise HyperpingAPIError(f"Request failed: {e}") from e
|
|
328
327
|
|
|
329
328
|
raise HyperpingAPIError( # pragma: no cover
|
|
@@ -45,9 +45,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
|
|
|
45
45
|
if status:
|
|
46
46
|
params["status"] = status
|
|
47
47
|
|
|
48
|
-
response = await self._request(
|
|
49
|
-
"GET", Endpoint.INCIDENTS, params=params or None
|
|
50
|
-
)
|
|
48
|
+
response = await self._request("GET", Endpoint.INCIDENTS, params=params or None)
|
|
51
49
|
return parse_list(unwrap_list(response, "incidents"), Incident, "incident")
|
|
52
50
|
|
|
53
51
|
async def get_incident(self, incident_id: str) -> Incident:
|
|
@@ -114,9 +112,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
|
|
|
114
112
|
validate_id(incident_id, "incident_id")
|
|
115
113
|
payload = update.model_dump(exclude_none=True, by_alias=True)
|
|
116
114
|
response = expect_dict(
|
|
117
|
-
await self._request(
|
|
118
|
-
"PUT", f"{Endpoint.INCIDENTS}/{incident_id}", json=payload
|
|
119
|
-
),
|
|
115
|
+
await self._request("PUT", f"{Endpoint.INCIDENTS}/{incident_id}", json=payload),
|
|
120
116
|
"update_incident",
|
|
121
117
|
)
|
|
122
118
|
return Incident.model_validate(response)
|
|
@@ -145,9 +141,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
|
|
|
145
141
|
await self._request("POST", url, json=payload)
|
|
146
142
|
return await self.get_incident(incident_id)
|
|
147
143
|
|
|
148
|
-
async def resolve_incident(
|
|
149
|
-
self, incident_id: str, message: str | None = None
|
|
150
|
-
) -> Incident:
|
|
144
|
+
async def resolve_incident(self, incident_id: str, message: str | None = None) -> Incident:
|
|
151
145
|
"""Resolve an incident.
|
|
152
146
|
|
|
153
147
|
Args:
|
|
@@ -41,9 +41,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
|
|
|
41
41
|
if status:
|
|
42
42
|
params["status"] = status
|
|
43
43
|
|
|
44
|
-
response = await self._request(
|
|
45
|
-
"GET", Endpoint.MAINTENANCE, params=params or None
|
|
46
|
-
)
|
|
44
|
+
response = await self._request("GET", Endpoint.MAINTENANCE, params=params or None)
|
|
47
45
|
|
|
48
46
|
raw = unwrap_list(response, "maintenanceWindows")
|
|
49
47
|
if not raw and isinstance(response, dict) and "maintenance" in response:
|
|
@@ -64,9 +62,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
|
|
|
64
62
|
HyperpingNotFoundError: If maintenance not found
|
|
65
63
|
"""
|
|
66
64
|
validate_id(maintenance_id, "maintenance_id")
|
|
67
|
-
response = await self._request(
|
|
68
|
-
"GET", f"{Endpoint.MAINTENANCE}/{maintenance_id}"
|
|
69
|
-
)
|
|
65
|
+
response = await self._request("GET", f"{Endpoint.MAINTENANCE}/{maintenance_id}")
|
|
70
66
|
return Maintenance.model_validate(expect_dict(response, "get_maintenance"))
|
|
71
67
|
|
|
72
68
|
async def create_maintenance(self, maintenance: MaintenanceCreate) -> Maintenance:
|
|
@@ -127,9 +123,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
|
|
|
127
123
|
payload.update(partial)
|
|
128
124
|
|
|
129
125
|
response = expect_dict(
|
|
130
|
-
await self._request(
|
|
131
|
-
"PUT", f"{Endpoint.MAINTENANCE}/{maintenance_id}", json=payload
|
|
132
|
-
),
|
|
126
|
+
await self._request("PUT", f"{Endpoint.MAINTENANCE}/{maintenance_id}", json=payload),
|
|
133
127
|
"update_maintenance",
|
|
134
128
|
)
|
|
135
129
|
return Maintenance.model_validate(response)
|
|
@@ -99,9 +99,7 @@ class AsyncMonitorsMixin(_AsyncClientProtocol):
|
|
|
99
99
|
)
|
|
100
100
|
payload.update(update.model_dump(exclude_none=True))
|
|
101
101
|
|
|
102
|
-
response = await self._request(
|
|
103
|
-
"PUT", f"{Endpoint.MONITORS}/{monitor_id}", json=payload
|
|
104
|
-
)
|
|
102
|
+
response = await self._request("PUT", f"{Endpoint.MONITORS}/{monitor_id}", json=payload)
|
|
105
103
|
return Monitor.model_validate(expect_dict(response, "update_monitor"))
|
|
106
104
|
|
|
107
105
|
async def delete_monitor(self, monitor_id: str) -> None:
|
|
@@ -156,9 +154,7 @@ class AsyncMonitorsMixin(_AsyncClientProtocol):
|
|
|
156
154
|
HyperpingAPIError: On unexpected API errors.
|
|
157
155
|
"""
|
|
158
156
|
if period not in VALID_PERIODS:
|
|
159
|
-
raise ValueError(
|
|
160
|
-
f"Invalid period {period!r}. Valid values: {sorted(VALID_PERIODS)}"
|
|
161
|
-
)
|
|
157
|
+
raise ValueError(f"Invalid period {period!r}. Valid values: {sorted(VALID_PERIODS)}")
|
|
162
158
|
response = expect_dict(
|
|
163
159
|
await self._request("GET", Endpoint.REPORTS, params={"period": period}),
|
|
164
160
|
"get_all_reports",
|
|
@@ -56,9 +56,7 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
|
|
|
56
56
|
ValueError: If *status* or *outage_type* is not a recognised value.
|
|
57
57
|
"""
|
|
58
58
|
if status not in _VALID_STATUSES:
|
|
59
|
-
raise ValueError(
|
|
60
|
-
f"Invalid status {status!r}. Valid values: {sorted(_VALID_STATUSES)}"
|
|
61
|
-
)
|
|
59
|
+
raise ValueError(f"Invalid status {status!r}. Valid values: {sorted(_VALID_STATUSES)}")
|
|
62
60
|
if outage_type not in _VALID_TYPES:
|
|
63
61
|
raise ValueError(
|
|
64
62
|
f"Invalid outage_type {outage_type!r}. Valid values: {sorted(_VALID_TYPES)}"
|
|
@@ -75,7 +73,8 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
|
|
|
75
73
|
params["page"] = page
|
|
76
74
|
data = await self._request("GET", Endpoint.OUTAGES, params=params)
|
|
77
75
|
raw: list[Any] = (
|
|
78
|
-
data.get("outages", [])
|
|
76
|
+
data.get("outages", [])
|
|
77
|
+
if isinstance(data, dict)
|
|
79
78
|
else (data if isinstance(data, list) else [])
|
|
80
79
|
)
|
|
81
80
|
return parse_list(raw, Outage, "outage")
|
|
@@ -86,9 +85,7 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
|
|
|
86
85
|
logger.debug("Outage endpoint not available (404)")
|
|
87
86
|
return []
|
|
88
87
|
|
|
89
|
-
async def acknowledge_outage(
|
|
90
|
-
self, outage_id: str, message: str | None = None
|
|
91
|
-
) -> OutageAction:
|
|
88
|
+
async def acknowledge_outage(self, outage_id: str, message: str | None = None) -> OutageAction:
|
|
92
89
|
"""Acknowledge an outage.
|
|
93
90
|
|
|
94
91
|
Args:
|
|
@@ -110,9 +107,7 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
|
|
|
110
107
|
)
|
|
111
108
|
return OutageAction.from_raw(expect_dict(result, "outage operation"))
|
|
112
109
|
|
|
113
|
-
async def resolve_outage(
|
|
114
|
-
self, outage_id: str, message: str | None = None
|
|
115
|
-
) -> OutageAction:
|
|
110
|
+
async def resolve_outage(self, outage_id: str, message: str | None = None) -> OutageAction:
|
|
116
111
|
"""Resolve an outage.
|
|
117
112
|
|
|
118
113
|
Args:
|
|
@@ -163,9 +158,7 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
|
|
|
163
158
|
HyperpingNotFoundError: If outage not found.
|
|
164
159
|
"""
|
|
165
160
|
validate_id(outage_id, "outage_id")
|
|
166
|
-
result = await self._request(
|
|
167
|
-
"POST", f"{Endpoint.OUTAGES}/{outage_id}/unacknowledge"
|
|
168
|
-
)
|
|
161
|
+
result = await self._request("POST", f"{Endpoint.OUTAGES}/{outage_id}/unacknowledge")
|
|
169
162
|
return OutageAction.from_raw(expect_dict(result, "outage operation"))
|
|
170
163
|
|
|
171
164
|
async def delete_outage(self, outage_id: str) -> None:
|
|
@@ -69,8 +69,12 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
|
|
|
69
69
|
|
|
70
70
|
try:
|
|
71
71
|
return await collect_all_pages_async(
|
|
72
|
-
self._request,
|
|
73
|
-
|
|
72
|
+
self._request,
|
|
73
|
+
Endpoint.STATUSPAGES,
|
|
74
|
+
"statuspages",
|
|
75
|
+
params or None,
|
|
76
|
+
StatusPage,
|
|
77
|
+
"status page",
|
|
74
78
|
)
|
|
75
79
|
except HyperpingNotFoundError:
|
|
76
80
|
logger.debug("Status pages endpoint not available (404)")
|
|
@@ -131,9 +135,7 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
|
|
|
131
135
|
validate_id(status_page_id, "status_page_id")
|
|
132
136
|
payload = update.model_dump(exclude_none=True, by_alias=True)
|
|
133
137
|
response = expect_dict(
|
|
134
|
-
await self._request(
|
|
135
|
-
"PUT", f"{Endpoint.STATUSPAGES}/{status_page_id}", json=payload
|
|
136
|
-
),
|
|
138
|
+
await self._request("PUT", f"{Endpoint.STATUSPAGES}/{status_page_id}", json=payload),
|
|
137
139
|
"update_status_page",
|
|
138
140
|
)
|
|
139
141
|
return StatusPage.model_validate(response)
|
|
@@ -189,13 +191,15 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
|
|
|
189
191
|
)
|
|
190
192
|
|
|
191
193
|
return await collect_all_pages_async(
|
|
192
|
-
self._request,
|
|
193
|
-
|
|
194
|
+
self._request,
|
|
195
|
+
endpoint,
|
|
196
|
+
"subscribers",
|
|
197
|
+
params or None,
|
|
198
|
+
StatusPageSubscriber,
|
|
199
|
+
"subscriber",
|
|
194
200
|
)
|
|
195
201
|
|
|
196
|
-
async def add_subscriber(
|
|
197
|
-
self, status_page_id: str, email: str
|
|
198
|
-
) -> StatusPageSubscriber:
|
|
202
|
+
async def add_subscriber(self, status_page_id: str, email: str) -> StatusPageSubscriber:
|
|
199
203
|
"""Add a subscriber to a status page.
|
|
200
204
|
|
|
201
205
|
Args:
|
|
@@ -225,9 +229,7 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
|
|
|
225
229
|
)
|
|
226
230
|
return StatusPageSubscriber.model_validate(response)
|
|
227
231
|
|
|
228
|
-
async def remove_subscriber(
|
|
229
|
-
self, status_page_id: str, subscriber_id: str
|
|
230
|
-
) -> None:
|
|
232
|
+
async def remove_subscriber(self, status_page_id: str, subscriber_id: str) -> None:
|
|
231
233
|
"""Remove a subscriber from a status page.
|
|
232
234
|
|
|
233
235
|
Args:
|
|
@@ -48,7 +48,9 @@ class IncidentsMixin(_ClientProtocol):
|
|
|
48
48
|
params["status"] = status
|
|
49
49
|
|
|
50
50
|
response = self._request(
|
|
51
|
-
"GET",
|
|
51
|
+
"GET",
|
|
52
|
+
Endpoint.INCIDENTS,
|
|
53
|
+
params=params or None, # M20
|
|
52
54
|
)
|
|
53
55
|
return parse_list(unwrap_list(response, "incidents"), Incident, "incident")
|
|
54
56
|
|
|
@@ -28,7 +28,4 @@ def sanitize_for_log(data: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
28
28
|
"""
|
|
29
29
|
if data is None:
|
|
30
30
|
return None
|
|
31
|
-
return {
|
|
32
|
-
k: "[REDACTED]" if k.lower() in _SENSITIVE_LOG_KEYS else v
|
|
33
|
-
for k, v in data.items()
|
|
34
|
-
}
|
|
31
|
+
return {k: "[REDACTED]" if k.lower() in _SENSITIVE_LOG_KEYS else v for k, v in data.items()}
|
|
@@ -43,7 +43,9 @@ class MaintenanceMixin(_ClientProtocol):
|
|
|
43
43
|
params["status"] = status
|
|
44
44
|
|
|
45
45
|
response = self._request(
|
|
46
|
-
"GET",
|
|
46
|
+
"GET",
|
|
47
|
+
Endpoint.MAINTENANCE,
|
|
48
|
+
params=params or None, # M20
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
# API returns {"maintenanceWindows": [...]} as of current version
|