chore: ci fixups and poetry update

This commit is contained in:
Andrew Herrington
2024-05-05 12:20:49 -05:00
parent cb862fa2c5
commit d0b986025e
24 changed files with 829 additions and 1026 deletions

View File

@@ -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

View File

@@ -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)}"

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.