From 941d02ee240041c650a0e7a345dda341c2a9b286 Mon Sep 17 00:00:00 2001 From: Khaled Salhab Date: Fri, 17 Apr 2026 03:39:35 +0300 Subject: [PATCH] fix: add missing sync outage methods, improve maintenance update robustness - Add create_outage, delete_outage, get_outage, unacknowledge_outage to sync OutagesMixin (were only available in async client) - Use consistent field set for maintenance update payload construction - Improve error context in add_incident_update when refresh fails --- src/hyperping/_incidents_mixin.py | 12 +++++- src/hyperping/_maintenance_mixin.py | 11 +++-- src/hyperping/_outages_mixin.py | 63 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/hyperping/_incidents_mixin.py b/src/hyperping/_incidents_mixin.py index cd75080..dd88486 100644 --- a/src/hyperping/_incidents_mixin.py +++ b/src/hyperping/_incidents_mixin.py @@ -12,6 +12,7 @@ from hyperping._protocols import _ClientProtocol from hyperping._utils import expect_dict, parse_list, unwrap_list, validate_id from hyperping.endpoints import Endpoint +from hyperping.exceptions import HyperpingAPIError from hyperping.models import ( AddIncidentUpdateRequest, # canonical name (M18) Incident, @@ -144,7 +145,16 @@ def add_incident_update( payload = update.model_dump(exclude_none=True, by_alias=True) url = f"{Endpoint.INCIDENTS}/{incident_id}/updates" self._request("POST", url, json=payload) # Returns {"message": "..."} — not a full Incident - return self.get_incident(incident_id) + try: + return self.get_incident(incident_id) + except HyperpingAPIError as exc: + raise HyperpingAPIError( + f"Incident update was posted successfully but refreshing " + f"incident {incident_id!r} failed: {exc}", + status_code=exc.status_code, + response_body=exc.response_body, + request_id=exc.request_id, + ) from exc def resolve_incident(self, incident_id: str, message: str | None = None) -> Incident: """Resolve an incident. diff --git a/src/hyperping/_maintenance_mixin.py b/src/hyperping/_maintenance_mixin.py index e02b697..fde15f3 100644 --- a/src/hyperping/_maintenance_mixin.py +++ b/src/hyperping/_maintenance_mixin.py @@ -125,12 +125,11 @@ def update_maintenance( current = self.get_maintenance(maintenance_id) partial = update.model_dump(exclude_none=True, by_alias=True, mode="json") - payload: dict[str, object] = { - "name": current.name, - "start_date": current.start_date, - "end_date": current.end_date, - "monitors": current.monitors, - } + payload: dict[str, object] = current.model_dump( + mode="json", + exclude_none=True, + include={"name", "start_date", "end_date", "monitors"}, + ) payload.update(partial) response = expect_dict( diff --git a/src/hyperping/_outages_mixin.py b/src/hyperping/_outages_mixin.py index 0979d2d..15549f2 100644 --- a/src/hyperping/_outages_mixin.py +++ b/src/hyperping/_outages_mixin.py @@ -140,3 +140,66 @@ def escalate_outage(self, outage_id: str) -> OutageAction: validate_id(outage_id, "outage_id") # H8 result = self._request("POST", f"{Endpoint.OUTAGES}/{outage_id}/escalate") return OutageAction.from_raw(expect_dict(result, "outage operation")) + + def unacknowledge_outage(self, outage_id: str) -> OutageAction: + """Unacknowledge an outage. + + Args: + outage_id: Outage UUID. + + Returns: + :class:`~hyperping.models.OutageAction` with the action result. + + Raises: + HyperpingNotFoundError: If outage not found. + """ + validate_id(outage_id, "outage_id") # H8 + result = self._request( + "POST", f"{Endpoint.OUTAGES}/{outage_id}/unacknowledge" + ) + return OutageAction.from_raw(expect_dict(result, "outage operation")) + + def delete_outage(self, outage_id: str) -> None: + """Delete an outage. + + Args: + outage_id: Outage UUID. + + Raises: + HyperpingNotFoundError: If outage not found. + """ + validate_id(outage_id, "outage_id") # H8 + self._request("DELETE", f"{Endpoint.OUTAGES}/{outage_id}") + + def create_outage(self, monitor_uuid: str) -> Outage: + """Create a manual outage for a monitor. + + Args: + monitor_uuid: Monitor UUID to create the outage for. + + Returns: + Created :class:`~hyperping.models.Outage` object. + + Raises: + HyperpingValidationError: If the payload fails server-side validation. + HyperpingAPIError: On unexpected API errors. + """ + payload = {"monitor_uuid": monitor_uuid} + result = self._request("POST", Endpoint.OUTAGES, json=payload) + return Outage.model_validate(expect_dict(result, "create_outage")) + + def get_outage(self, outage_id: str) -> Outage: + """Get a single outage by ID. + + Args: + outage_id: Outage UUID. + + Returns: + :class:`~hyperping.models.Outage` object. + + Raises: + HyperpingNotFoundError: If outage not found. + """ + validate_id(outage_id, "outage_id") # H8 + result = self._request("GET", f"{Endpoint.OUTAGES}/{outage_id}") + return Outage.model_validate(expect_dict(result, "get_outage"))