hyperping 1.8.0__tar.gz → 1.9.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.8.0 → hyperping-1.9.0}/CHANGELOG.md +75 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/PKG-INFO +5 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/pyproject.toml +10 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/__init__.py +8 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_incidents_mixin.py +49 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_maintenance_mixin.py +51 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_mcp_client.py +50 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_incidents_mixin.py +65 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_maintenance_mixin.py +79 -0
- hyperping-1.9.0/src/hyperping/_version.py +1 -0
- hyperping-1.9.0/src/hyperping/cli/_app.py +52 -0
- hyperping-1.9.0/src/hyperping/cli/_config.py +24 -0
- hyperping-1.9.0/src/hyperping/cli/_incidents.py +87 -0
- hyperping-1.9.0/src/hyperping/cli/_monitors.py +90 -0
- hyperping-1.9.0/src/hyperping/cli/_output.py +58 -0
- hyperping-1.9.0/src/hyperping/cli/_statuspages.py +57 -0
- hyperping-1.9.0/src/hyperping/cli/_tenant.py +75 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/exceptions.py +31 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/mcp_client.py +50 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/__init__.py +7 -1
- hyperping-1.9.0/src/hyperping/models/_integration_models.py +27 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_oncall_models.py +34 -3
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_statuspage_models.py +12 -1
- hyperping-1.9.0/tests/unit/__init__.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_async_mcp_client.py +151 -10
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_async_preexisting.py +71 -0
- hyperping-1.9.0/tests/unit/test_cli_config.py +28 -0
- hyperping-1.9.0/tests/unit/test_cli_incidents.py +121 -0
- hyperping-1.9.0/tests/unit/test_cli_monitors.py +95 -0
- hyperping-1.9.0/tests/unit/test_cli_statuspages.py +67 -0
- hyperping-1.9.0/tests/unit/test_cli_tenant.py +90 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_incidents.py +84 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_maintenance.py +150 -1
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_mcp_client.py +145 -10
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_statuspages.py +24 -0
- hyperping-1.8.0/src/hyperping/_version.py +0 -1
- hyperping-1.8.0/src/hyperping/models/_integration_models.py +0 -14
- {hyperping-1.8.0 → hyperping-1.9.0}/.gitignore +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/CONTRIBUTING.md +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/LICENSE +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/README.md +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/SECURITY.md +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/scripts/verify_endpoints.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_client.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_healthchecks_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_mcp_transport.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_monitors_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_outages_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_async_statuspages_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_circuit_breaker.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_healthchecks_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_internals.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_mcp_transport.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_monitor_constants.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_monitors_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_outages_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_protocols.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_statuspages_mixin.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/_utils.py +0 -0
- {hyperping-1.8.0/tests → hyperping-1.9.0/src/hyperping/cli}/__init__.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/client.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/endpoints.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_healthcheck_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_incident_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_maintenance_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_monitor_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_observability_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_outage_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/models/_reporting_models.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/src/hyperping/py.typed +0 -0
- {hyperping-1.8.0/tests/unit → hyperping-1.9.0/tests}/__init__.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/conftest.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_async_client.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_async_mcp_transport.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_client_coverage.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_healthchecks.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_mcp_transport.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_monitors.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_outages.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_pagination.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_per_endpoint_circuit_breaker.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_sdk_surface.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_security_base_url.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_security_breaker_cap.py +0 -0
- {hyperping-1.8.0 → hyperping-1.9.0}/tests/unit/test_security_exception_redaction.py +0 -0
|
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.9.0] - 2026-07-14
|
|
11
|
+
|
|
12
|
+
Corrective minor release. It renames the bundled console script (breaking) and
|
|
13
|
+
retroactively documents breaking changes that shipped mislabeled in 1.8.0/1.8.1
|
|
14
|
+
(see Upgrade Notes). If you pinned `~=1.8.0` or `~=1.8.1` you already received
|
|
15
|
+
those breaking changes silently; this entry explains them.
|
|
16
|
+
|
|
17
|
+
### BREAKING
|
|
18
|
+
|
|
19
|
+
- **The bundled console script is renamed `hyp` → `hyperping`.** 1.8.1 introduced
|
|
20
|
+
a `hyp` entry point for the SDK's CLI. `hyp` is the long-standing command of the
|
|
21
|
+
separate `hyperping-automation` tool; when both are installed the SDK's script
|
|
22
|
+
silently shadowed it (last-writer-wins on `bin/hyp`), breaking that tool's
|
|
23
|
+
commands and exposing the SDK's unguarded write commands under a familiar name.
|
|
24
|
+
Invoke the SDK CLI as `hyperping …` now. (Removing/renaming a console script is
|
|
25
|
+
a breaking change; it is the reason this is 1.9.0, not 1.8.2.)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`create_maintenance_windows` / `create_incidents` now surface partial failures.**
|
|
30
|
+
If a later chunk fails after earlier objects were created, they raise
|
|
31
|
+
`HyperpingPartialBatchError` carrying the already-created objects (`.created`,
|
|
32
|
+
`.completed`, `.total`) instead of discarding them, so callers can record or
|
|
33
|
+
clean up rather than orphaning windows/incidents silently.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **`create_incidents()`** (sync + async): splits a broadcast incident's status
|
|
38
|
+
pages into chunks of at most `MAX_STATUSPAGES_PER_INCIDENT` (51), mirroring
|
|
39
|
+
`create_maintenance_windows`. `create_incident()` now raises
|
|
40
|
+
`HyperpingValidationError` above the cap instead of silently failing to persist.
|
|
41
|
+
NOTE: the 51 cap for incidents is assumed identical to maintenance (same
|
|
42
|
+
status-page attachment path) and has not been independently measured against
|
|
43
|
+
the live API.
|
|
44
|
+
- **`HyperpingPartialBatchError`** and **`MAX_STATUSPAGES_PER_INCIDENT`** exported
|
|
45
|
+
from the package root.
|
|
46
|
+
|
|
47
|
+
### Upgrade Notes (breaking changes that shipped mislabeled in 1.8.0 / 1.8.1)
|
|
48
|
+
|
|
49
|
+
These are not new in 1.9.0; they are documented here because 1.8.0/1.8.1 changed
|
|
50
|
+
them without an upgrade note, which is why consumers were caught out:
|
|
51
|
+
|
|
52
|
+
- **1.8.0** reconciled the `Integration`, `EscalationPolicy`, and `TeamMember`
|
|
53
|
+
models against the production API: `Integration.active` was **removed** and the
|
|
54
|
+
integration-type field key is now **`channel`** (was `type`); a new
|
|
55
|
+
`EscalationStep` shape was introduced. Any code reading `Integration.active` or
|
|
56
|
+
sending `type=` breaks.
|
|
57
|
+
- **1.8.1** added the `hyp` console script (renamed here) and the status-page /
|
|
58
|
+
maintenance fixes; it was released as a patch despite the CLI addition.
|
|
59
|
+
|
|
60
|
+
Guidance: pin `hyperping>=1.9.0,<2` and, if you consume the CLI, use `hyperping`.
|
|
61
|
+
|
|
62
|
+
## [1.8.1] - 2026-07-14
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **`list_status_pages()` returned an empty list against the live v2 API.** The
|
|
67
|
+
`StatusPage` model required a `subdomain` field, but the API returns the hosted
|
|
68
|
+
subdomain under the key `hostedsubdomain` (and omits it entirely for custom-domain
|
|
69
|
+
pages). Every record failed validation and was silently skipped. `subdomain` is now
|
|
70
|
+
optional and aliased to `hostedsubdomain`; `hostname` and `url` are also parsed.
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- **`create_maintenance()` now guards against the silent status-page overflow.**
|
|
75
|
+
Hyperping's v1 maintenance-windows API accepts a create with more than 51 status
|
|
76
|
+
pages (returns a uuid) but never persists the window. `create_maintenance` now raises
|
|
77
|
+
`HyperpingValidationError` when `statuspages` exceeds `MAX_STATUSPAGES_PER_MAINTENANCE`
|
|
78
|
+
(51), instead of returning a phantom window. Split larger sets across multiple windows.
|
|
79
|
+
- **`create_maintenance_windows()`** (sync + async): convenience helper that splits a
|
|
80
|
+
large `statuspages` list into consecutive windows of at most 51 pages, so callers can
|
|
81
|
+
cover more pages than one window allows. Each window carries the full monitor set (the
|
|
82
|
+
API requires at least one monitor per window). `MAX_STATUSPAGES_PER_MAINTENANCE` is
|
|
83
|
+
exported for callers that chunk themselves (e.g. a broadcast-to-all-tenants command).
|
|
84
|
+
|
|
10
85
|
## [1.8.0] - 2026-05-31
|
|
11
86
|
|
|
12
87
|
This is a security-focused release. It closes several credential-leak and validation gaps surfaced by an independent audit. One change is breaking for local-development workflows; see Upgrade Notes below.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperping
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.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
|
|
@@ -12,6 +12,7 @@ License: MIT
|
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Keywords: api-client,httpx,hyperping,incident,monitoring,pydantic,python,sdk,status-page,uptime
|
|
14
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Console
|
|
15
16
|
Classifier: Intended Audience :: Developers
|
|
16
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
18
|
Classifier: Operating System :: OS Independent
|
|
@@ -25,6 +26,8 @@ Classifier: Typing :: Typed
|
|
|
25
26
|
Requires-Python: >=3.11
|
|
26
27
|
Requires-Dist: httpx<1.0,>=0.27
|
|
27
28
|
Requires-Dist: pydantic<3.0,>=2.0
|
|
29
|
+
Provides-Extra: cli
|
|
30
|
+
Requires-Dist: typer[all]<1.0,>=0.12; extra == 'cli'
|
|
28
31
|
Provides-Extra: dev
|
|
29
32
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
33
|
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
@@ -33,6 +36,7 @@ Requires-Dist: pytest-cov; extra == 'dev'
|
|
|
33
36
|
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
34
37
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
35
38
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
39
|
+
Requires-Dist: typer[all]<1.0,>=0.12; extra == 'dev'
|
|
36
40
|
Description-Content-Type: text/markdown
|
|
37
41
|
|
|
38
42
|
# hyperping
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hyperping"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.9.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"}
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
24
|
"Topic :: Internet :: WWW/HTTP",
|
|
25
25
|
"Typing :: Typed",
|
|
26
|
+
"Environment :: Console",
|
|
26
27
|
]
|
|
27
28
|
dependencies = [
|
|
28
29
|
"httpx>=0.27,<1.0",
|
|
@@ -30,6 +31,7 @@ dependencies = [
|
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
[project.optional-dependencies]
|
|
34
|
+
cli = ["typer[all]>=0.12,<1.0"]
|
|
33
35
|
dev = [
|
|
34
36
|
"pytest>=9.0.3",
|
|
35
37
|
"pytest-cov",
|
|
@@ -38,8 +40,15 @@ dev = [
|
|
|
38
40
|
"mypy>=1.10",
|
|
39
41
|
"pydantic",
|
|
40
42
|
"pip-audit>=2.7",
|
|
43
|
+
"typer[all]>=0.12,<1.0",
|
|
41
44
|
]
|
|
42
45
|
|
|
46
|
+
[project.scripts]
|
|
47
|
+
# Console script is 'hyperping' (NOT 'hyp'): 'hyp' collides with the long-lived
|
|
48
|
+
# hyperping-automation CLI (hyp_status) and, when both are installed, silently
|
|
49
|
+
# shadows it. See CHANGELOG 1.9.0 BREAKING notes.
|
|
50
|
+
hyperping = "hyperping.cli._app:app"
|
|
51
|
+
|
|
43
52
|
[project.urls]
|
|
44
53
|
Homepage = "https://github.com/develeap/hyperping-python"
|
|
45
54
|
Documentation = "https://github.com/develeap/hyperping-python#readme"
|
|
@@ -16,6 +16,8 @@ Quick start::
|
|
|
16
16
|
|
|
17
17
|
from hyperping._async_client import AsyncHyperpingClient
|
|
18
18
|
from hyperping._async_mcp_client import AsyncHyperpingMcpClient
|
|
19
|
+
from hyperping._incidents_mixin import MAX_STATUSPAGES_PER_INCIDENT
|
|
20
|
+
from hyperping._maintenance_mixin import MAX_STATUSPAGES_PER_MAINTENANCE
|
|
19
21
|
from hyperping._version import __version__
|
|
20
22
|
from hyperping.client import (
|
|
21
23
|
CircuitBreaker,
|
|
@@ -34,6 +36,7 @@ from hyperping.exceptions import (
|
|
|
34
36
|
HyperpingAPIError,
|
|
35
37
|
HyperpingAuthError,
|
|
36
38
|
HyperpingNotFoundError,
|
|
39
|
+
HyperpingPartialBatchError,
|
|
37
40
|
HyperpingRateLimitError,
|
|
38
41
|
HyperpingValidationError,
|
|
39
42
|
)
|
|
@@ -44,6 +47,7 @@ from hyperping.models import (
|
|
|
44
47
|
AlertHistory,
|
|
45
48
|
DnsRecordType,
|
|
46
49
|
EscalationPolicy,
|
|
50
|
+
EscalationStep,
|
|
47
51
|
Healthcheck,
|
|
48
52
|
HealthcheckCreate,
|
|
49
53
|
HealthcheckUpdate,
|
|
@@ -116,6 +120,7 @@ __all__ = [
|
|
|
116
120
|
"APIVersion",
|
|
117
121
|
# Exceptions
|
|
118
122
|
"HyperpingAPIError",
|
|
123
|
+
"HyperpingPartialBatchError",
|
|
119
124
|
"HyperpingAuthError",
|
|
120
125
|
"HyperpingNotFoundError",
|
|
121
126
|
"HyperpingRateLimitError",
|
|
@@ -149,6 +154,8 @@ __all__ = [
|
|
|
149
154
|
"IncidentStatus",
|
|
150
155
|
"IncidentUpdateCreate",
|
|
151
156
|
# Maintenance
|
|
157
|
+
"MAX_STATUSPAGES_PER_INCIDENT",
|
|
158
|
+
"MAX_STATUSPAGES_PER_MAINTENANCE",
|
|
152
159
|
"Maintenance",
|
|
153
160
|
"MaintenanceCreate",
|
|
154
161
|
"MaintenanceUpdate",
|
|
@@ -168,6 +175,7 @@ __all__ = [
|
|
|
168
175
|
"ProbeLogResponse",
|
|
169
176
|
# On-call
|
|
170
177
|
"OnCallSchedule",
|
|
178
|
+
"EscalationStep",
|
|
171
179
|
"EscalationPolicy",
|
|
172
180
|
"TeamMember",
|
|
173
181
|
# Integrations
|
|
@@ -9,9 +9,15 @@ from __future__ import annotations
|
|
|
9
9
|
import logging
|
|
10
10
|
from datetime import UTC, datetime
|
|
11
11
|
|
|
12
|
+
from hyperping._incidents_mixin import MAX_STATUSPAGES_PER_INCIDENT
|
|
12
13
|
from hyperping._protocols import _AsyncClientProtocol
|
|
13
14
|
from hyperping._utils import expect_dict, parse_list, unwrap_list, validate_id
|
|
14
15
|
from hyperping.endpoints import Endpoint
|
|
16
|
+
from hyperping.exceptions import (
|
|
17
|
+
HyperpingAPIError,
|
|
18
|
+
HyperpingPartialBatchError,
|
|
19
|
+
HyperpingValidationError,
|
|
20
|
+
)
|
|
15
21
|
from hyperping.models import (
|
|
16
22
|
AddIncidentUpdateRequest,
|
|
17
23
|
Incident,
|
|
@@ -81,6 +87,15 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
|
|
|
81
87
|
v3 API returns {"message": "...", "uuid": "..."} on create,
|
|
82
88
|
not the full incident object. The full incident is fetched after creation.
|
|
83
89
|
"""
|
|
90
|
+
n_statuspages = len(incident.statuspages or [])
|
|
91
|
+
if n_statuspages > MAX_STATUSPAGES_PER_INCIDENT:
|
|
92
|
+
raise HyperpingValidationError(
|
|
93
|
+
f"An incident can reference at most {MAX_STATUSPAGES_PER_INCIDENT} "
|
|
94
|
+
f"status pages, but {n_statuspages} were supplied. Above this limit "
|
|
95
|
+
f"Hyperping's API is expected to accept the create (returns a uuid) but "
|
|
96
|
+
f"silently fail to persist it. Use create_incidents() to split the "
|
|
97
|
+
f"status pages across multiple incidents."
|
|
98
|
+
)
|
|
84
99
|
payload = incident.model_dump(exclude_none=True, by_alias=True, mode="json")
|
|
85
100
|
response = expect_dict(
|
|
86
101
|
await self._request("POST", Endpoint.INCIDENTS, json=payload),
|
|
@@ -90,6 +105,40 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
|
|
|
90
105
|
return await self.get_incident(response["uuid"])
|
|
91
106
|
return Incident.model_validate(response)
|
|
92
107
|
|
|
108
|
+
async def create_incidents(
|
|
109
|
+
self,
|
|
110
|
+
incident: IncidentCreate,
|
|
111
|
+
*,
|
|
112
|
+
chunk_size: int = MAX_STATUSPAGES_PER_INCIDENT,
|
|
113
|
+
) -> list[Incident]:
|
|
114
|
+
"""Async mirror of
|
|
115
|
+
:meth:`~hyperping._incidents_mixin.IncidentsMixin.create_incidents`.
|
|
116
|
+
"""
|
|
117
|
+
if not 1 <= chunk_size <= MAX_STATUSPAGES_PER_INCIDENT:
|
|
118
|
+
raise HyperpingValidationError(
|
|
119
|
+
f"chunk_size must be between 1 and "
|
|
120
|
+
f"{MAX_STATUSPAGES_PER_INCIDENT}, got {chunk_size}."
|
|
121
|
+
)
|
|
122
|
+
pages = list(incident.statuspages or [])
|
|
123
|
+
if len(pages) <= chunk_size:
|
|
124
|
+
return [await self.create_incident(incident)]
|
|
125
|
+
chunks = [pages[i : i + chunk_size] for i in range(0, len(pages), chunk_size)]
|
|
126
|
+
created: list[Incident] = []
|
|
127
|
+
for idx, chunk_pages in enumerate(chunks):
|
|
128
|
+
chunk = incident.model_copy(update={"statuspages": chunk_pages})
|
|
129
|
+
try:
|
|
130
|
+
created.append(await self.create_incident(chunk))
|
|
131
|
+
except HyperpingAPIError as exc:
|
|
132
|
+
raise HyperpingPartialBatchError(
|
|
133
|
+
f"create_incidents failed on incident {idx + 1} of "
|
|
134
|
+
f"{len(chunks)}: {exc}. {len(created)} incident(s) were already "
|
|
135
|
+
f"created and remain live.",
|
|
136
|
+
created=created,
|
|
137
|
+
completed=len(created),
|
|
138
|
+
total=len(chunks),
|
|
139
|
+
) from exc
|
|
140
|
+
return created
|
|
141
|
+
|
|
93
142
|
async def update_incident(
|
|
94
143
|
self,
|
|
95
144
|
incident_id: str,
|
|
@@ -9,9 +9,15 @@ from __future__ import annotations
|
|
|
9
9
|
import logging
|
|
10
10
|
from datetime import UTC, datetime
|
|
11
11
|
|
|
12
|
+
from hyperping._maintenance_mixin import MAX_STATUSPAGES_PER_MAINTENANCE
|
|
12
13
|
from hyperping._protocols import _AsyncClientProtocol
|
|
13
14
|
from hyperping._utils import expect_dict, parse_list, unwrap_list, validate_id
|
|
14
15
|
from hyperping.endpoints import Endpoint
|
|
16
|
+
from hyperping.exceptions import (
|
|
17
|
+
HyperpingAPIError,
|
|
18
|
+
HyperpingPartialBatchError,
|
|
19
|
+
HyperpingValidationError,
|
|
20
|
+
)
|
|
15
21
|
from hyperping.models import (
|
|
16
22
|
Maintenance,
|
|
17
23
|
MaintenanceCreate,
|
|
@@ -82,6 +88,15 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
|
|
|
82
88
|
v1 API returns {"uuid": "..."} on create, not the full maintenance object.
|
|
83
89
|
The full maintenance window is fetched after creation.
|
|
84
90
|
"""
|
|
91
|
+
n_statuspages = len(maintenance.statuspages or [])
|
|
92
|
+
if n_statuspages > MAX_STATUSPAGES_PER_MAINTENANCE:
|
|
93
|
+
raise HyperpingValidationError(
|
|
94
|
+
f"A maintenance window can reference at most "
|
|
95
|
+
f"{MAX_STATUSPAGES_PER_MAINTENANCE} status pages, but {n_statuspages} "
|
|
96
|
+
f"were supplied. Above this limit Hyperping's API accepts the create "
|
|
97
|
+
f"(returns a uuid) but silently fails to persist the window. Split the "
|
|
98
|
+
f"status pages across multiple maintenance windows."
|
|
99
|
+
)
|
|
85
100
|
payload = maintenance.model_dump(exclude_none=True, by_alias=True, mode="json")
|
|
86
101
|
response = expect_dict(
|
|
87
102
|
await self._request("POST", Endpoint.MAINTENANCE, json=payload),
|
|
@@ -91,6 +106,42 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
|
|
|
91
106
|
return await self.get_maintenance(response["uuid"])
|
|
92
107
|
return Maintenance.model_validate(response)
|
|
93
108
|
|
|
109
|
+
async def create_maintenance_windows(
|
|
110
|
+
self,
|
|
111
|
+
maintenance: MaintenanceCreate,
|
|
112
|
+
*,
|
|
113
|
+
chunk_size: int = MAX_STATUSPAGES_PER_MAINTENANCE,
|
|
114
|
+
) -> list[Maintenance]:
|
|
115
|
+
"""Create one or more windows, splitting status pages into chunks.
|
|
116
|
+
|
|
117
|
+
Async mirror of
|
|
118
|
+
:meth:`~hyperping._maintenance_mixin.MaintenanceMixin.create_maintenance_windows`.
|
|
119
|
+
"""
|
|
120
|
+
if not 1 <= chunk_size <= MAX_STATUSPAGES_PER_MAINTENANCE:
|
|
121
|
+
raise HyperpingValidationError(
|
|
122
|
+
f"chunk_size must be between 1 and "
|
|
123
|
+
f"{MAX_STATUSPAGES_PER_MAINTENANCE}, got {chunk_size}."
|
|
124
|
+
)
|
|
125
|
+
pages = list(maintenance.statuspages or [])
|
|
126
|
+
if len(pages) <= chunk_size:
|
|
127
|
+
return [await self.create_maintenance(maintenance)]
|
|
128
|
+
chunks = [pages[i : i + chunk_size] for i in range(0, len(pages), chunk_size)]
|
|
129
|
+
windows: list[Maintenance] = []
|
|
130
|
+
for idx, chunk_pages in enumerate(chunks):
|
|
131
|
+
chunk = maintenance.model_copy(update={"statuspages": chunk_pages})
|
|
132
|
+
try:
|
|
133
|
+
windows.append(await self.create_maintenance(chunk))
|
|
134
|
+
except HyperpingAPIError as exc:
|
|
135
|
+
raise HyperpingPartialBatchError(
|
|
136
|
+
f"create_maintenance_windows failed on window {idx + 1} of "
|
|
137
|
+
f"{len(chunks)}: {exc}. {len(windows)} window(s) were already "
|
|
138
|
+
f"created and remain live.",
|
|
139
|
+
created=windows,
|
|
140
|
+
completed=len(windows),
|
|
141
|
+
total=len(chunks),
|
|
142
|
+
) from exc
|
|
143
|
+
return windows
|
|
144
|
+
|
|
94
145
|
async def update_maintenance(
|
|
95
146
|
self,
|
|
96
147
|
maintenance_id: str,
|
|
@@ -21,7 +21,7 @@ from pydantic import SecretStr
|
|
|
21
21
|
from hyperping._async_mcp_transport import AsyncMcpTransport
|
|
22
22
|
from hyperping.endpoints import MCP_URL
|
|
23
23
|
from hyperping.models._integration_models import Integration
|
|
24
|
-
from hyperping.models._monitor_models import Monitor
|
|
24
|
+
from hyperping.models._monitor_models import Monitor, MonitorCreate
|
|
25
25
|
from hyperping.models._observability_models import MonitorAnomaly, ProbeLogResponse
|
|
26
26
|
from hyperping.models._oncall_models import EscalationPolicy, OnCallSchedule, TeamMember
|
|
27
27
|
from hyperping.models._outage_models import OutageTimeline
|
|
@@ -270,3 +270,52 @@ class AsyncHyperpingMcpClient:
|
|
|
270
270
|
data = await self._call("search_monitors_by_name", {"query": query})
|
|
271
271
|
raw = data if isinstance(data, list) else []
|
|
272
272
|
return [Monitor.model_validate(m) for m in raw]
|
|
273
|
+
|
|
274
|
+
async def create_monitor(self, monitor: MonitorCreate) -> Monitor:
|
|
275
|
+
"""Create a new monitor.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
monitor: Monitor configuration.
|
|
279
|
+
"""
|
|
280
|
+
return Monitor.model_validate(
|
|
281
|
+
await self._call("create_monitor", monitor.model_dump(exclude_none=True))
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
async def update_monitor(self, monitor_uuid: str, **kwargs: Any) -> Monitor:
|
|
285
|
+
"""Update an existing monitor.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
monitor_uuid: Monitor UUID.
|
|
289
|
+
**kwargs: Fields to update.
|
|
290
|
+
"""
|
|
291
|
+
return Monitor.model_validate(
|
|
292
|
+
await self._call("update_monitor", {"uuid": monitor_uuid, **kwargs})
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def pause_monitor(self, monitor_uuid: str) -> Monitor:
|
|
296
|
+
"""Pause a monitor.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
monitor_uuid: Monitor UUID.
|
|
300
|
+
"""
|
|
301
|
+
return Monitor.model_validate(
|
|
302
|
+
await self._call("pause_monitor", {"uuid": monitor_uuid})
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
async def resume_monitor(self, monitor_uuid: str) -> Monitor:
|
|
306
|
+
"""Resume a paused monitor.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
monitor_uuid: Monitor UUID.
|
|
310
|
+
"""
|
|
311
|
+
return Monitor.model_validate(
|
|
312
|
+
await self._call("resume_monitor", {"uuid": monitor_uuid})
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
async def delete_monitor(self, monitor_uuid: str) -> None:
|
|
316
|
+
"""Delete a monitor.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
monitor_uuid: Monitor UUID.
|
|
320
|
+
"""
|
|
321
|
+
await self._call("delete_monitor", {"uuid": monitor_uuid})
|
|
@@ -12,7 +12,11 @@ from datetime import UTC, datetime
|
|
|
12
12
|
from hyperping._protocols import _ClientProtocol
|
|
13
13
|
from hyperping._utils import expect_dict, parse_list, unwrap_list, validate_id
|
|
14
14
|
from hyperping.endpoints import Endpoint
|
|
15
|
-
from hyperping.exceptions import
|
|
15
|
+
from hyperping.exceptions import (
|
|
16
|
+
HyperpingAPIError,
|
|
17
|
+
HyperpingPartialBatchError,
|
|
18
|
+
HyperpingValidationError,
|
|
19
|
+
)
|
|
16
20
|
from hyperping.models import (
|
|
17
21
|
AddIncidentUpdateRequest, # canonical name (M18)
|
|
18
22
|
Incident,
|
|
@@ -24,6 +28,14 @@ from hyperping.models import (
|
|
|
24
28
|
|
|
25
29
|
logger = logging.getLogger(__name__)
|
|
26
30
|
|
|
31
|
+
# Incidents attach status pages via the same mechanism as maintenance windows,
|
|
32
|
+
# which the API caps at 51 per request (see MAX_STATUSPAGES_PER_MAINTENANCE):
|
|
33
|
+
# beyond the cap the create is accepted but silently not persisted. The
|
|
34
|
+
# incident cap has NOT been independently verified against the live API; it is
|
|
35
|
+
# assumed identical because the status-page attachment is the same backend
|
|
36
|
+
# path. Adjust if the incident endpoint is later measured to differ.
|
|
37
|
+
MAX_STATUSPAGES_PER_INCIDENT = 51
|
|
38
|
+
|
|
27
39
|
|
|
28
40
|
class IncidentsMixin(_ClientProtocol):
|
|
29
41
|
"""Incident-related API operations."""
|
|
@@ -87,6 +99,15 @@ class IncidentsMixin(_ClientProtocol):
|
|
|
87
99
|
v3 API returns {"message": "...", "uuid": "..."} on create,
|
|
88
100
|
not the full incident object. The full incident is fetched after creation.
|
|
89
101
|
"""
|
|
102
|
+
n_statuspages = len(incident.statuspages or [])
|
|
103
|
+
if n_statuspages > MAX_STATUSPAGES_PER_INCIDENT:
|
|
104
|
+
raise HyperpingValidationError(
|
|
105
|
+
f"An incident can reference at most {MAX_STATUSPAGES_PER_INCIDENT} "
|
|
106
|
+
f"status pages, but {n_statuspages} were supplied. Above this limit "
|
|
107
|
+
f"Hyperping's API is expected to accept the create (returns a uuid) but "
|
|
108
|
+
f"silently fail to persist it. Use create_incidents() to split the "
|
|
109
|
+
f"status pages across multiple incidents."
|
|
110
|
+
)
|
|
90
111
|
payload = incident.model_dump(exclude_none=True, by_alias=True, mode="json")
|
|
91
112
|
response = expect_dict(
|
|
92
113
|
self._request("POST", Endpoint.INCIDENTS, json=payload),
|
|
@@ -98,6 +119,49 @@ class IncidentsMixin(_ClientProtocol):
|
|
|
98
119
|
return self.get_incident(response["uuid"])
|
|
99
120
|
return Incident.model_validate(response)
|
|
100
121
|
|
|
122
|
+
def create_incidents(
|
|
123
|
+
self,
|
|
124
|
+
incident: IncidentCreate,
|
|
125
|
+
*,
|
|
126
|
+
chunk_size: int = MAX_STATUSPAGES_PER_INCIDENT,
|
|
127
|
+
) -> list[Incident]:
|
|
128
|
+
"""Create one or more incidents, splitting status pages into chunks.
|
|
129
|
+
|
|
130
|
+
Mirrors :meth:`create_maintenance_windows`: a broadcast incident that
|
|
131
|
+
targets more status pages than the per-request cap is split into
|
|
132
|
+
consecutive incidents of at most ``chunk_size`` pages. Because the page
|
|
133
|
+
sets are disjoint, each status page still shows exactly one incident.
|
|
134
|
+
|
|
135
|
+
Returns the created incidents in page order (a single incident when the
|
|
136
|
+
pages fit in one chunk). Raises :class:`HyperpingValidationError` for a
|
|
137
|
+
bad ``chunk_size`` and :class:`HyperpingPartialBatchError` if a later
|
|
138
|
+
chunk fails after earlier incidents were already created.
|
|
139
|
+
"""
|
|
140
|
+
if not 1 <= chunk_size <= MAX_STATUSPAGES_PER_INCIDENT:
|
|
141
|
+
raise HyperpingValidationError(
|
|
142
|
+
f"chunk_size must be between 1 and "
|
|
143
|
+
f"{MAX_STATUSPAGES_PER_INCIDENT}, got {chunk_size}."
|
|
144
|
+
)
|
|
145
|
+
pages = list(incident.statuspages or [])
|
|
146
|
+
if len(pages) <= chunk_size:
|
|
147
|
+
return [self.create_incident(incident)]
|
|
148
|
+
chunks = [pages[i : i + chunk_size] for i in range(0, len(pages), chunk_size)]
|
|
149
|
+
created: list[Incident] = []
|
|
150
|
+
for idx, chunk_pages in enumerate(chunks):
|
|
151
|
+
chunk = incident.model_copy(update={"statuspages": chunk_pages})
|
|
152
|
+
try:
|
|
153
|
+
created.append(self.create_incident(chunk))
|
|
154
|
+
except HyperpingAPIError as exc:
|
|
155
|
+
raise HyperpingPartialBatchError(
|
|
156
|
+
f"create_incidents failed on incident {idx + 1} of "
|
|
157
|
+
f"{len(chunks)}: {exc}. {len(created)} incident(s) were already "
|
|
158
|
+
f"created and remain live.",
|
|
159
|
+
created=created,
|
|
160
|
+
completed=len(created),
|
|
161
|
+
total=len(chunks),
|
|
162
|
+
) from exc
|
|
163
|
+
return created
|
|
164
|
+
|
|
101
165
|
def update_incident(
|
|
102
166
|
self,
|
|
103
167
|
incident_id: str,
|
|
@@ -12,12 +12,26 @@ from datetime import UTC, datetime
|
|
|
12
12
|
from hyperping._protocols import _ClientProtocol
|
|
13
13
|
from hyperping._utils import expect_dict, parse_list, unwrap_list, validate_id
|
|
14
14
|
from hyperping.endpoints import Endpoint
|
|
15
|
+
from hyperping.exceptions import (
|
|
16
|
+
HyperpingAPIError,
|
|
17
|
+
HyperpingPartialBatchError,
|
|
18
|
+
HyperpingValidationError,
|
|
19
|
+
)
|
|
15
20
|
from hyperping.models import (
|
|
16
21
|
Maintenance,
|
|
17
22
|
MaintenanceCreate,
|
|
18
23
|
MaintenanceUpdate,
|
|
19
24
|
)
|
|
20
25
|
|
|
26
|
+
# Hyperping's v1 maintenance-windows API silently drops a create when the
|
|
27
|
+
# ``statuspages`` array exceeds this many entries: the POST still returns a
|
|
28
|
+
# ``{"uuid": ...}`` but the window is never persisted (the follow-up GET 404s
|
|
29
|
+
# and it is absent from the list). Empirically the cutoff is 51 (51 persists,
|
|
30
|
+
# 52+ vanishes), verified against the live API on 2026-07-14. Guard the create
|
|
31
|
+
# so callers get a clear error instead of a phantom window; split larger sets
|
|
32
|
+
# across multiple windows.
|
|
33
|
+
MAX_STATUSPAGES_PER_MAINTENANCE = 51
|
|
34
|
+
|
|
21
35
|
logger = logging.getLogger(__name__)
|
|
22
36
|
|
|
23
37
|
|
|
@@ -88,6 +102,15 @@ class MaintenanceMixin(_ClientProtocol):
|
|
|
88
102
|
v1 API returns {"uuid": "..."} on create, not the full maintenance object.
|
|
89
103
|
The full maintenance window is fetched after creation.
|
|
90
104
|
"""
|
|
105
|
+
n_statuspages = len(maintenance.statuspages or [])
|
|
106
|
+
if n_statuspages > MAX_STATUSPAGES_PER_MAINTENANCE:
|
|
107
|
+
raise HyperpingValidationError(
|
|
108
|
+
f"A maintenance window can reference at most "
|
|
109
|
+
f"{MAX_STATUSPAGES_PER_MAINTENANCE} status pages, but {n_statuspages} "
|
|
110
|
+
f"were supplied. Above this limit Hyperping's API accepts the create "
|
|
111
|
+
f"(returns a uuid) but silently fails to persist the window. Split the "
|
|
112
|
+
f"status pages across multiple maintenance windows."
|
|
113
|
+
)
|
|
91
114
|
payload = maintenance.model_dump(exclude_none=True, by_alias=True, mode="json")
|
|
92
115
|
response = expect_dict(
|
|
93
116
|
self._request("POST", Endpoint.MAINTENANCE, json=payload),
|
|
@@ -99,6 +122,62 @@ class MaintenanceMixin(_ClientProtocol):
|
|
|
99
122
|
return self.get_maintenance(response["uuid"])
|
|
100
123
|
return Maintenance.model_validate(response)
|
|
101
124
|
|
|
125
|
+
def create_maintenance_windows(
|
|
126
|
+
self,
|
|
127
|
+
maintenance: MaintenanceCreate,
|
|
128
|
+
*,
|
|
129
|
+
chunk_size: int = MAX_STATUSPAGES_PER_MAINTENANCE,
|
|
130
|
+
) -> list[Maintenance]:
|
|
131
|
+
"""Create one or more windows, splitting status pages into chunks.
|
|
132
|
+
|
|
133
|
+
Hyperping silently fails to persist a window with more than
|
|
134
|
+
:data:`MAX_STATUSPAGES_PER_MAINTENANCE` status pages (see
|
|
135
|
+
:meth:`create_maintenance`). This helper splits a large ``statuspages``
|
|
136
|
+
list into consecutive windows of at most ``chunk_size`` pages so every
|
|
137
|
+
page is covered. Each window carries the full ``monitors`` set (the API
|
|
138
|
+
rejects a window with no monitors); overlapping monitor coverage across
|
|
139
|
+
the windows is harmless.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
maintenance: Creation data; ``statuspages`` may exceed the limit.
|
|
143
|
+
chunk_size: Max status pages per window (1..
|
|
144
|
+
:data:`MAX_STATUSPAGES_PER_MAINTENANCE`).
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
The created windows in page order (a single window when the pages
|
|
148
|
+
fit in one chunk).
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
HyperpingValidationError: If ``chunk_size`` is out of range.
|
|
152
|
+
"""
|
|
153
|
+
if not 1 <= chunk_size <= MAX_STATUSPAGES_PER_MAINTENANCE:
|
|
154
|
+
raise HyperpingValidationError(
|
|
155
|
+
f"chunk_size must be between 1 and "
|
|
156
|
+
f"{MAX_STATUSPAGES_PER_MAINTENANCE}, got {chunk_size}."
|
|
157
|
+
)
|
|
158
|
+
pages = list(maintenance.statuspages or [])
|
|
159
|
+
if len(pages) <= chunk_size:
|
|
160
|
+
return [self.create_maintenance(maintenance)]
|
|
161
|
+
chunks = [pages[i : i + chunk_size] for i in range(0, len(pages), chunk_size)]
|
|
162
|
+
windows: list[Maintenance] = []
|
|
163
|
+
for idx, chunk_pages in enumerate(chunks):
|
|
164
|
+
chunk = maintenance.model_copy(update={"statuspages": chunk_pages})
|
|
165
|
+
try:
|
|
166
|
+
windows.append(self.create_maintenance(chunk))
|
|
167
|
+
except HyperpingAPIError as exc:
|
|
168
|
+
# Earlier windows are already live and are NOT rolled back; hand
|
|
169
|
+
# them back so the caller can record or clean them up rather than
|
|
170
|
+
# orphaning them silently.
|
|
171
|
+
raise HyperpingPartialBatchError(
|
|
172
|
+
f"create_maintenance_windows failed on window {idx + 1} of "
|
|
173
|
+
f"{len(chunks)}: {exc}. {len(windows)} window(s) were already "
|
|
174
|
+
f"created and remain live.",
|
|
175
|
+
created=windows,
|
|
176
|
+
completed=len(windows),
|
|
177
|
+
total=len(chunks),
|
|
178
|
+
) from exc
|
|
179
|
+
return windows
|
|
180
|
+
|
|
102
181
|
def update_maintenance(
|
|
103
182
|
self,
|
|
104
183
|
maintenance_id: str,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.9.0"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Main hyp CLI application."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from hyperping._version import __version__
|
|
10
|
+
from hyperping.cli._incidents import incident_app
|
|
11
|
+
from hyperping.cli._monitors import monitor_app
|
|
12
|
+
from hyperping.cli._statuspages import statuspage_app
|
|
13
|
+
from hyperping.cli._tenant import tenant_app
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
name="hyp",
|
|
17
|
+
help="Hyperping CLI: manage monitors, incidents, and status pages.",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app.add_typer(monitor_app, name="monitor")
|
|
22
|
+
app.add_typer(incident_app, name="incident")
|
|
23
|
+
app.add_typer(statuspage_app, name="statuspage")
|
|
24
|
+
app.add_typer(tenant_app, name="tenant")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _version_callback(value: bool) -> None:
|
|
28
|
+
if value:
|
|
29
|
+
typer.echo(f"hyp {__version__}")
|
|
30
|
+
raise typer.Exit()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.callback()
|
|
34
|
+
def main(
|
|
35
|
+
ctx: typer.Context,
|
|
36
|
+
api_key: Annotated[
|
|
37
|
+
str | None,
|
|
38
|
+
typer.Option("--api-key", envvar="HYPERPING_API_KEY", help="Hyperping API key."),
|
|
39
|
+
] = None,
|
|
40
|
+
json_output: Annotated[
|
|
41
|
+
bool,
|
|
42
|
+
typer.Option("--json", help="Output as JSON."),
|
|
43
|
+
] = False,
|
|
44
|
+
version: Annotated[
|
|
45
|
+
bool | None,
|
|
46
|
+
typer.Option("--version", callback=_version_callback, is_eager=True, help="Show version."),
|
|
47
|
+
] = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Hyperping CLI."""
|
|
50
|
+
ctx.ensure_object(dict)
|
|
51
|
+
ctx.obj["api_key"] = api_key
|
|
52
|
+
ctx.obj["json"] = json_output
|