mirror of
https://github.com/andrewthetechie/py-healthchecks.io.git
synced 2026-05-01 19:25:55 +02:00
chore: ci fixups and poetry update
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""healthchecks_io clients."""
|
||||
|
||||
from .async_client import AsyncClient # noqa: F401
|
||||
from .check_trap import CheckTrap # noqa: F401
|
||||
from .sync_client import Client # noqa: F401
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from urllib.parse import parse_qsl
|
||||
from urllib.parse import ParseResult
|
||||
from urllib.parse import unquote
|
||||
@@ -57,9 +54,7 @@ class AbstractClient(ABC):
|
||||
"""Finalizer method is called by weakref.finalize when the object is dereferenced to do cleanup of clients."""
|
||||
pass
|
||||
|
||||
def _get_api_request_url(
|
||||
self, path: str, params: Optional[Dict[str, Any]] = None
|
||||
) -> str:
|
||||
def _get_api_request_url(self, path: str, params: dict[str, Any] | None = None) -> str:
|
||||
"""Get a full request url for the healthchecks api.
|
||||
|
||||
Args:
|
||||
@@ -165,9 +160,7 @@ class AbstractClient(ABC):
|
||||
raise CheckNotFoundError(f"CHeck not found at {response.request.url}")
|
||||
|
||||
if response.status_code == 400:
|
||||
raise BadAPIRequestError(
|
||||
f"Bad request when requesting {response.request.url}. {response.text}"
|
||||
)
|
||||
raise BadAPIRequestError(f"Bad request when requesting {response.request.url}. {response.text}")
|
||||
|
||||
return response
|
||||
|
||||
@@ -208,21 +201,15 @@ class AbstractClient(ABC):
|
||||
raise HCAPIRateLimitError(f"Rate limited on {response.request.url}")
|
||||
|
||||
if response.status_code == 400:
|
||||
raise BadAPIRequestError(
|
||||
f"Bad request when requesting {response.request.url}. {response.text}"
|
||||
)
|
||||
raise BadAPIRequestError(f"Bad request when requesting {response.request.url}. {response.text}")
|
||||
|
||||
if response.status_code == 409:
|
||||
raise NonUniqueSlugError(
|
||||
f"Bad request, slug conflict {response.request.url}. {response.text}"
|
||||
)
|
||||
raise NonUniqueSlugError(f"Bad request, slug conflict {response.request.url}. {response.text}")
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _add_url_params(
|
||||
url: str, params: Dict[str, Union[str, int, bool]], replace: bool = True
|
||||
) -> str:
|
||||
def _add_url_params(url: str, params: dict[str, str | int | bool], replace: bool = True) -> str:
|
||||
"""Add GET params to provided URL being aware of existing.
|
||||
|
||||
:param url: string of target URL
|
||||
@@ -255,12 +242,7 @@ class AbstractClient(ABC):
|
||||
# get all the duplicated keys from params and urlencode them, we'll concat this to the params string later
|
||||
duplicated_params = [x for x in params if x in parsed_get_args]
|
||||
# get all the args that aren't duplicated and add them to parsed_get_args
|
||||
parsed_get_args.update(
|
||||
{
|
||||
key: parsed_params[key]
|
||||
for key in [x for x in params if x not in parsed_get_args]
|
||||
}
|
||||
)
|
||||
parsed_get_args.update({key: parsed_params[key] for key in [x for x in params if x not in parsed_get_args]})
|
||||
# if we have any duplicated parameters, urlencode them, we append them later
|
||||
extra_parameters = (
|
||||
f"&{urlencode({key: params[key] for key in duplicated_params}, doseq=True)}"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
"""An async healthchecks.io client."""
|
||||
|
||||
import asyncio
|
||||
from types import TracebackType
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
||||
from httpx import AsyncClient as HTTPXAsyncClient
|
||||
|
||||
@@ -29,7 +25,7 @@ class AsyncClient(AbstractClient):
|
||||
api_url: str = "https://healthchecks.io/api/",
|
||||
ping_url: str = "https://hc-ping.com/",
|
||||
api_version: int = 1,
|
||||
client: Optional[HTTPXAsyncClient] = None,
|
||||
client: HTTPXAsyncClient | None = None,
|
||||
) -> None:
|
||||
"""An AsyncClient can be used in code using asyncio to work with the Healthchecks.io api.
|
||||
|
||||
@@ -64,9 +60,9 @@ class AsyncClient(AbstractClient):
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
"""Context manager exit."""
|
||||
await self._afinalizer_method()
|
||||
@@ -118,7 +114,7 @@ class AsyncClient(AbstractClient):
|
||||
)
|
||||
return Check.from_api_result(response.json())
|
||||
|
||||
async def get_checks(self, tags: Optional[List[str]] = None) -> List[Check]:
|
||||
async def get_checks(self, tags: list[str] | None = None) -> list[Check]:
|
||||
"""Get a list of checks from the healthchecks api.
|
||||
|
||||
Args:
|
||||
@@ -213,7 +209,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_response(await self._client.delete(request_url))
|
||||
return Check.from_api_result(response.json())
|
||||
|
||||
async def get_check_pings(self, check_id: str) -> List[CheckPings]:
|
||||
async def get_check_pings(self, check_id: str) -> list[CheckPings]:
|
||||
"""Returns a list of pings this check has received.
|
||||
|
||||
This endpoint returns pings in reverse order (most recent first),
|
||||
@@ -241,10 +237,10 @@ class AsyncClient(AbstractClient):
|
||||
async def get_check_flips(
|
||||
self,
|
||||
check_id: str,
|
||||
seconds: Optional[int] = None,
|
||||
start: Optional[int] = None,
|
||||
end: Optional[int] = None,
|
||||
) -> List[CheckStatuses]:
|
||||
seconds: int | None = None,
|
||||
start: int | None = None,
|
||||
end: int | None = None,
|
||||
) -> list[CheckStatuses]:
|
||||
"""Returns a list of "flips" this check has experienced.
|
||||
|
||||
A flip is a change of status (from "down" to "up," or from "up" to "down").
|
||||
@@ -281,7 +277,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_response(await self._client.get(request_url))
|
||||
return [CheckStatuses(**status_data) for status_data in response.json()]
|
||||
|
||||
async def get_integrations(self) -> List[Optional[Integration]]:
|
||||
async def get_integrations(self) -> list[Integration | None]:
|
||||
"""Returns a list of integrations belonging to the project.
|
||||
|
||||
Raises:
|
||||
@@ -297,7 +293,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_response(await self._client.get(request_url))
|
||||
return [Integration.from_api_result(integration_dict) for integration_dict in response.json()["channels"]]
|
||||
|
||||
async def get_badges(self) -> Dict[str, Badges]:
|
||||
async def get_badges(self) -> dict[str, Badges]:
|
||||
"""Returns a dict of all tags in the project, with badge URLs for each tag.
|
||||
|
||||
Healthchecks.io provides badges in a few different formats:
|
||||
@@ -325,7 +321,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_response(await self._client.get(request_url))
|
||||
return {key: Badges.from_api_result(item) for key, item in response.json()["badges"].items()}
|
||||
|
||||
async def success_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
async def success_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that a job has completed successfully.
|
||||
|
||||
Can also be used to indicate a continuously running process is still running and healthy.
|
||||
@@ -359,7 +355,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_ping_response(await self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
async def start_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
async def start_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Sends a "job has started!" message to Healthchecks.io.
|
||||
|
||||
Sending a "start" signal is optional, but it enables a few extra features:
|
||||
@@ -395,7 +391,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_ping_response(await self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
async def fail_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
async def fail_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
@@ -429,7 +425,7 @@ class AsyncClient(AbstractClient):
|
||||
response = self.check_ping_response(await self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
async def exit_code_ping(self, exit_code: int, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
async def exit_code_ping(self, exit_code: int, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"""CheckTrap is a context manager to wrap around python code to communicate results to a Healthchecks check."""
|
||||
|
||||
from types import TracebackType
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
|
||||
from .async_client import AsyncClient
|
||||
from .exceptions import PingFailedError
|
||||
@@ -16,7 +13,7 @@ class CheckTrap:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: Union[Client, AsyncClient],
|
||||
client: Client | AsyncClient,
|
||||
uuid: str = "",
|
||||
slug: str = "",
|
||||
suppress_exceptions: bool = False,
|
||||
@@ -34,10 +31,10 @@ class CheckTrap:
|
||||
"""
|
||||
if uuid == "" and slug == "":
|
||||
raise Exception("Must pass a slug or an uuid")
|
||||
self.client: Union[Client, AsyncClient] = client
|
||||
self.client: Client | AsyncClient = client
|
||||
self.uuid: str = uuid
|
||||
self.slug: str = slug
|
||||
self.log_lines: List[str] = list()
|
||||
self.log_lines: list[str] = list()
|
||||
self.suppress_exceptions: bool = suppress_exceptions
|
||||
|
||||
def add_log(self, line: str) -> None:
|
||||
@@ -68,9 +65,7 @@ class CheckTrap:
|
||||
CheckTrap: self
|
||||
"""
|
||||
if isinstance(self.client, AsyncClient):
|
||||
raise WrongClientError(
|
||||
"You passed an AsyncClient, use this as an async context manager"
|
||||
)
|
||||
raise WrongClientError("You passed an AsyncClient, use this as an async context manager")
|
||||
result = self.client.start_ping(uuid=self.uuid, slug=self.slug)
|
||||
if not result[0]:
|
||||
raise PingFailedError(result[1])
|
||||
@@ -78,10 +73,10 @@ class CheckTrap:
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> Optional[bool]:
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> bool | None:
|
||||
"""Exit the context manager.
|
||||
|
||||
If there is an exception, add it to any log lines and send a fail ping.
|
||||
@@ -96,9 +91,7 @@ class CheckTrap:
|
||||
Optional[bool]: self.suppress_exceptions, if true will not raise any exceptions
|
||||
"""
|
||||
if exc_type is None:
|
||||
self.client.success_ping(
|
||||
self.uuid, self.slug, data="\n".join(self.log_lines)
|
||||
)
|
||||
self.client.success_ping(self.uuid, self.slug, data="\n".join(self.log_lines))
|
||||
else:
|
||||
self.add_log(str(exc))
|
||||
self.add_log(str(traceback))
|
||||
@@ -125,9 +118,7 @@ class CheckTrap:
|
||||
CheckTrap: self
|
||||
"""
|
||||
if isinstance(self.client, Client):
|
||||
raise WrongClientError(
|
||||
"You passed a sync Client, use this as a regular context manager"
|
||||
)
|
||||
raise WrongClientError("You passed a sync Client, use this as a regular context manager")
|
||||
result = await self.client.start_ping(self.uuid, self.slug)
|
||||
if not result[0]:
|
||||
raise PingFailedError(result[1])
|
||||
@@ -135,10 +126,10 @@ class CheckTrap:
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> Optional[bool]:
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> bool | None:
|
||||
"""Exit the context manager.
|
||||
|
||||
If there is an exception, add it to any log lines and send a fail ping.
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
"""An async healthchecks.io client."""
|
||||
|
||||
from types import TracebackType
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
||||
from httpx import Client as HTTPXClient
|
||||
|
||||
@@ -27,7 +23,7 @@ class Client(AbstractClient):
|
||||
api_url: str = "https://healthchecks.io/api/",
|
||||
ping_url: str = "https://hc-ping.com/",
|
||||
api_version: int = 1,
|
||||
client: Optional[HTTPXClient] = None,
|
||||
client: HTTPXClient | None = None,
|
||||
) -> None:
|
||||
"""An AsyncClient can be used in code using asyncio to work with the Healthchecks.io api.
|
||||
|
||||
@@ -62,9 +58,9 @@ class Client(AbstractClient):
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
"""Context manager exit."""
|
||||
self._finalizer_method()
|
||||
@@ -73,7 +69,7 @@ class Client(AbstractClient):
|
||||
"""Closes the httpx client."""
|
||||
self._client.close()
|
||||
|
||||
def get_checks(self, tags: Optional[List[str]] = None) -> List[checks.Check]:
|
||||
def get_checks(self, tags: list[str] | None = None) -> list[checks.Check]:
|
||||
"""Get a list of checks from the healthchecks api.
|
||||
|
||||
Args:
|
||||
@@ -208,7 +204,7 @@ class Client(AbstractClient):
|
||||
response = self.check_response(self._client.delete(request_url))
|
||||
return checks.Check.from_api_result(response.json())
|
||||
|
||||
def get_check_pings(self, check_id: str) -> List[checks.CheckPings]:
|
||||
def get_check_pings(self, check_id: str) -> list[checks.CheckPings]:
|
||||
"""Returns a list of pings this check has received.
|
||||
|
||||
This endpoint returns pings in reverse order (most recent first),
|
||||
@@ -234,10 +230,10 @@ class Client(AbstractClient):
|
||||
def get_check_flips(
|
||||
self,
|
||||
check_id: str,
|
||||
seconds: Optional[int] = None,
|
||||
start: Optional[int] = None,
|
||||
end: Optional[int] = None,
|
||||
) -> List[checks.CheckStatuses]:
|
||||
seconds: int | None = None,
|
||||
start: int | None = None,
|
||||
end: int | None = None,
|
||||
) -> list[checks.CheckStatuses]:
|
||||
"""Returns a list of "flips" this check has experienced.
|
||||
|
||||
A flip is a change of status (from "down" to "up," or from "up" to "down").
|
||||
@@ -272,7 +268,7 @@ class Client(AbstractClient):
|
||||
response = self.check_response(self._client.get(request_url))
|
||||
return [checks.CheckStatuses(**status_data) for status_data in response.json()]
|
||||
|
||||
def get_integrations(self) -> List[Optional[integrations.Integration]]:
|
||||
def get_integrations(self) -> list[integrations.Integration | None]:
|
||||
"""Returns a list of integrations belonging to the project.
|
||||
|
||||
Raises:
|
||||
@@ -290,7 +286,7 @@ class Client(AbstractClient):
|
||||
for integration_dict in response.json()["channels"]
|
||||
]
|
||||
|
||||
def get_badges(self) -> Dict[str, badges.Badges]:
|
||||
def get_badges(self) -> dict[str, badges.Badges]:
|
||||
"""Returns a dict of all tags in the project, with badge URLs for each tag.
|
||||
|
||||
Healthchecks.io provides badges in a few different formats:
|
||||
@@ -317,7 +313,7 @@ class Client(AbstractClient):
|
||||
response = self.check_response(self._client.get(request_url))
|
||||
return {key: badges.Badges.from_api_result(item) for key, item in response.json()["badges"].items()}
|
||||
|
||||
def success_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
def success_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that a job has completed successfully.
|
||||
|
||||
Can also be used to indicate a continuously running process is still running and healthy.
|
||||
@@ -351,7 +347,7 @@ class Client(AbstractClient):
|
||||
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
def start_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
def start_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Sends a "job has started!" message to Healthchecks.io.
|
||||
|
||||
Sending a "start" signal is optional, but it enables a few extra features:
|
||||
@@ -387,7 +383,7 @@ class Client(AbstractClient):
|
||||
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
def fail_ping(self, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
def fail_ping(self, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
@@ -421,7 +417,7 @@ class Client(AbstractClient):
|
||||
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||
return (True if response.status_code == 200 else False, response.text)
|
||||
|
||||
def exit_code_ping(self, exit_code: int, uuid: str = "", slug: str = "", data: str = "") -> Tuple[bool, str]:
|
||||
def exit_code_ping(self, exit_code: int, uuid: str = "", slug: str = "", data: str = "") -> tuple[bool, str]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
|
||||
Reference in New Issue
Block a user