mirror of
https://github.com/andrewthetechie/py-healthchecks.io.git
synced 2025-12-06 09:38:33 +01:00
add context manager features
This commit is contained in:
@@ -19,6 +19,14 @@ Either the Client or AsyncClient can be used as a ContextManager (or Async Conte
|
|||||||
This is probably the easiest way to use the Clients for one-off scripts. If you do not need to keep a client open for multiple requests, just use
|
This is probably the easiest way to use the Clients for one-off scripts. If you do not need to keep a client open for multiple requests, just use
|
||||||
the context manager.
|
the context manager.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
When using either of the client types as a context manager, the httpx client underlying the client will be closed when the context manager exits.
|
||||||
|
|
||||||
|
Since we allow you to pass in a client on creation, its possible to use a shared client with this library. If you then use the client as a contextmanager,
|
||||||
|
it will close that shared client.
|
||||||
|
|
||||||
|
Just a thing to be aware of!
|
||||||
|
|
||||||
|
|
||||||
Sync
|
Sync
|
||||||
----
|
----
|
||||||
@@ -95,3 +103,39 @@ If you want to use the client in an async program, use AsyncClient instead of Cl
|
|||||||
|
|
||||||
check = await client.create_check(CreateCheck(name="New Check", tags="tag1 tag2")
|
check = await client.create_check(CreateCheck(name="New Check", tags="tag1 tag2")
|
||||||
print(check)
|
print(check)
|
||||||
|
|
||||||
|
|
||||||
|
CheckTrap
|
||||||
|
---------
|
||||||
|
|
||||||
|
Ever wanted to run some code and wrape it in a healthcheck check without thinking about it?
|
||||||
|
|
||||||
|
That's what CheckTrap is for.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from healthchecks_io import Client, AsyncClient, CheckCreate, CheckTrap
|
||||||
|
|
||||||
|
client = Client(api_key="myapikey")
|
||||||
|
|
||||||
|
# create a new check, or use an existing one already with just its uuid.
|
||||||
|
check = await client.create_check(CreateCheck(name="New Check", tags="tag1 tag2")
|
||||||
|
|
||||||
|
with CheckTrap(client, check.uuid):
|
||||||
|
# when entering the context manager, sends a start ping to your check
|
||||||
|
run_my_thing_to_monitor()
|
||||||
|
|
||||||
|
# If your method exits without an exception, sends a success ping
|
||||||
|
# If there's an exception, a failure ping will be sent with the exception and traceback
|
||||||
|
|
||||||
|
client = AsyncClient(ping_key="ping_key")
|
||||||
|
|
||||||
|
# works with async too, and the ping api and slugs
|
||||||
|
with CheckTrap(client, check.slug) as ct:
|
||||||
|
# when entering the context manager, sends a start ping to your check
|
||||||
|
# Add custom logs to what gets sent to healthchecks. Reminder, only the first 10k bytes get saved
|
||||||
|
ct.add_log("My custom log message")
|
||||||
|
run_my_thing_to_monitor()
|
||||||
|
|
||||||
|
# If your method exits without an exception, sends a success ping
|
||||||
|
# If there's an exception, a failure ping will be sent with the exception and traceback
|
||||||
|
|||||||
@@ -4,18 +4,22 @@ __version__ = "0.0.0" # noqa: E402
|
|||||||
|
|
||||||
from .client import AsyncClient # noqa: F401, E402
|
from .client import AsyncClient # noqa: F401, E402
|
||||||
from .client import Client # noqa: F401, E402
|
from .client import Client # noqa: F401, E402
|
||||||
|
from .client import CheckTrap # noqa: F401, E402
|
||||||
from .client.exceptions import BadAPIRequestError # noqa: F401, E402
|
from .client.exceptions import BadAPIRequestError # noqa: F401, E402
|
||||||
from .client.exceptions import CheckNotFoundError # noqa: F401, E402
|
from .client.exceptions import CheckNotFoundError # noqa: F401, E402
|
||||||
from .client.exceptions import HCAPIAuthError # noqa: F401, E402
|
from .client.exceptions import HCAPIAuthError # noqa: F401, E402
|
||||||
from .client.exceptions import HCAPIError # noqa: F401, E402
|
from .client.exceptions import HCAPIError # noqa: F401, E402
|
||||||
from .client.exceptions import HCAPIRateLimitError # noqa: F401, E402
|
from .client.exceptions import HCAPIRateLimitError # noqa: F401, E402
|
||||||
from .client.exceptions import NonUniqueSlugError # noqa: F401, E402
|
from .client.exceptions import NonUniqueSlugError # noqa: F401, E402
|
||||||
|
from .client.exceptions import WrongClientError # noqa: F401, E402
|
||||||
|
from .client.exceptions import PingFailedError # noqa: F401, E402
|
||||||
from .schemas import Check, CheckCreate, CheckPings, CheckStatuses # noqa: F401, E402
|
from .schemas import Check, CheckCreate, CheckPings, CheckStatuses # noqa: F401, E402
|
||||||
from .schemas import Integration, Badges, CheckUpdate # noqa: F401, E402
|
from .schemas import Integration, Badges, CheckUpdate # noqa: F401, E402
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AsyncClient",
|
"AsyncClient",
|
||||||
"Client",
|
"Client",
|
||||||
|
"CheckTrap",
|
||||||
"BadAPIRequestError",
|
"BadAPIRequestError",
|
||||||
"CheckNotFoundError",
|
"CheckNotFoundError",
|
||||||
"HCAPIAuthError",
|
"HCAPIAuthError",
|
||||||
@@ -23,6 +27,8 @@ __all__ = [
|
|||||||
"CheckNotFoundError",
|
"CheckNotFoundError",
|
||||||
"HCAPIRateLimitError",
|
"HCAPIRateLimitError",
|
||||||
"NonUniqueSlugError",
|
"NonUniqueSlugError",
|
||||||
|
"WrongClientError",
|
||||||
|
"PingFailedError",
|
||||||
"Check",
|
"Check",
|
||||||
"CheckCreate",
|
"CheckCreate",
|
||||||
"CheckUpdate",
|
"CheckUpdate",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""healthchecks_io clients."""
|
"""healthchecks_io clients."""
|
||||||
from .async_client import AsyncClient # noqa: F401
|
from .async_client import AsyncClient # noqa: F401
|
||||||
|
from .check_trap import CheckTrap # noqa: F401
|
||||||
from .sync_client import Client # noqa: F401
|
from .sync_client import Client # noqa: F401
|
||||||
|
|
||||||
__all__ = ["AsyncClient", "Client"]
|
__all__ = ["AsyncClient", "Client", "CheckTrap"]
|
||||||
|
|||||||
@@ -341,7 +341,9 @@ class AsyncClient(AbstractClient):
|
|||||||
for key, item in response.json()["badges"].items()
|
for key, item in response.json()["badges"].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
async def success_ping(self, uuid: str = "", slug: 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.
|
"""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.
|
Can also be used to indicate a continuously running process is still running and healthy.
|
||||||
@@ -357,6 +359,7 @@ class AsyncClient(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to "".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -371,10 +374,14 @@ class AsyncClient(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "")
|
ping_url = self._get_ping_url(uuid, slug, "")
|
||||||
response = self.check_ping_response(await self._client.get(ping_url))
|
response = self.check_ping_response(
|
||||||
|
await self._client.post(ping_url, content=data)
|
||||||
|
)
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
async def start_ping(self, uuid: str = "", slug: 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.
|
"""Sends a "job has started!" message to Healthchecks.io.
|
||||||
|
|
||||||
Sending a "start" signal is optional, but it enables a few extra features:
|
Sending a "start" signal is optional, but it enables a few extra features:
|
||||||
@@ -392,6 +399,7 @@ class AsyncClient(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to "".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -406,10 +414,14 @@ class AsyncClient(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "/start")
|
ping_url = self._get_ping_url(uuid, slug, "/start")
|
||||||
response = self.check_ping_response(await self._client.get(ping_url))
|
response = self.check_ping_response(
|
||||||
|
await self._client.post(ping_url, content=data)
|
||||||
|
)
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
async def fail_ping(self, uuid: str = "", slug: 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.
|
"""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.
|
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||||
@@ -425,6 +437,7 @@ class AsyncClient(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to "".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -439,11 +452,13 @@ class AsyncClient(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "/fail")
|
ping_url = self._get_ping_url(uuid, slug, "/fail")
|
||||||
response = self.check_ping_response(await self._client.get(ping_url))
|
response = self.check_ping_response(
|
||||||
|
await self._client.post(ping_url, content=data)
|
||||||
|
)
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
async def exit_code_ping(
|
async def exit_code_ping(
|
||||||
self, exit_code: int, uuid: str = "", slug: str = ""
|
self, exit_code: int, uuid: str = "", slug: str = "", data: str = ""
|
||||||
) -> Tuple[bool, str]:
|
) -> Tuple[bool, str]:
|
||||||
"""Signals to Healthchecks.io that the job has failed.
|
"""Signals to Healthchecks.io that the job has failed.
|
||||||
|
|
||||||
@@ -461,6 +476,7 @@ class AsyncClient(AbstractClient):
|
|||||||
exit_code (int): Exit code to sent, int from 0 to 255
|
exit_code (int): Exit code to sent, int from 0 to 255
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to "".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -475,5 +491,7 @@ class AsyncClient(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, f"/{exit_code}")
|
ping_url = self._get_ping_url(uuid, slug, f"/{exit_code}")
|
||||||
response = self.check_ping_response(await self._client.get(ping_url))
|
response = self.check_ping_response(
|
||||||
|
await self._client.post(ping_url, content=data)
|
||||||
|
)
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|||||||
165
src/healthchecks_io/client/check_trap.py
Normal file
165
src/healthchecks_io/client/check_trap.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""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
|
||||||
|
from .exceptions import WrongClientError
|
||||||
|
from .sync_client import Client
|
||||||
|
|
||||||
|
|
||||||
|
class CheckTrap:
|
||||||
|
"""CheckTrap is a context manager to wrap around python code to communicate results to a Healthchecks check."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
client: Union[Client, AsyncClient],
|
||||||
|
uuid: str = "",
|
||||||
|
slug: str = "",
|
||||||
|
suppress_exceptions: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""A context manager to wrap around python code to communicate results to a Healthchecks check.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client (Union[Client, AsyncClient]): healthchecks_io client, async or sync
|
||||||
|
uuid (str): uuid of the check. Defaults to "".
|
||||||
|
slug (str): slug of the check, exclusion wiht uuid. Defaults to "".
|
||||||
|
suppress_exceptions (bool): If true, do not raise any exceptions. Defaults to False.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: Raised if a slug and a uuid is passed
|
||||||
|
"""
|
||||||
|
if uuid == "" and slug == "":
|
||||||
|
raise Exception("Must pass a slug or an uuid")
|
||||||
|
self.client: Union[Client, AsyncClient] = client
|
||||||
|
self.uuid: str = uuid
|
||||||
|
self.slug: str = slug
|
||||||
|
self.log_lines: List[str] = list()
|
||||||
|
self.suppress_exceptions: bool = suppress_exceptions
|
||||||
|
|
||||||
|
def add_log(self, line: str) -> None:
|
||||||
|
"""Add a line to the context manager's log that is sent with the check.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): String to add to the logs
|
||||||
|
"""
|
||||||
|
self.log_lines.append(line)
|
||||||
|
|
||||||
|
def __enter__(self) -> "CheckTrap":
|
||||||
|
"""Enter the context manager.
|
||||||
|
|
||||||
|
Sends a start ping to the check represented by self.uuid or self.slug.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
WrongClientError: Raised when using an AsyncClient with this as a sync client manager
|
||||||
|
PingFailedError: When a ping fails for any reason not handled by a custom exception
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404 or response text has "not found" in it
|
||||||
|
BadAPIRequestError: Raised when status_code is 400, or if you pass a uuid and a slug, or if
|
||||||
|
pinging by a slug and do not have a ping key set
|
||||||
|
HCAPIRateLimitError: Raised when status code is 429 or response text has "rate limited" in it
|
||||||
|
NonUniqueSlugError: Raused when status code is 409.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CheckTrap: self
|
||||||
|
"""
|
||||||
|
if isinstance(self.client, AsyncClient):
|
||||||
|
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])
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: Optional[Type[BaseException]],
|
||||||
|
exc: Optional[BaseException],
|
||||||
|
traceback: Optional[TracebackType],
|
||||||
|
) -> Optional[bool]:
|
||||||
|
"""Exit the context manager.
|
||||||
|
|
||||||
|
If there is an exception, add it to any log lines and send a fail ping.
|
||||||
|
Otherwise, send a success ping with any log lines appended.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exc_type (Optional[Type[BaseException]]): [description]
|
||||||
|
exc (Optional[BaseException]): [description]
|
||||||
|
traceback (Optional[TracebackType]): [description]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.add_log(str(exc))
|
||||||
|
self.add_log(str(traceback))
|
||||||
|
self.client.fail_ping(self.uuid, self.slug, data="\n".join(self.log_lines))
|
||||||
|
return self.suppress_exceptions
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "CheckTrap":
|
||||||
|
"""Enter the context manager.
|
||||||
|
|
||||||
|
Sends a start ping to the check represented by self.uuid or self.slug.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
WrongClientError: Raised when using an AsyncClient with this as a sync client manager
|
||||||
|
PingFailedError: When a ping fails for any reason not handled by a custom exception
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404 or response text has "not found" in it
|
||||||
|
BadAPIRequestError: Raised when status_code is 400, or if you pass a uuid and a slug, or if
|
||||||
|
pinging by a slug and do not have a ping key set
|
||||||
|
HCAPIRateLimitError: Raised when status code is 429 or response text has "rate limited" in it
|
||||||
|
NonUniqueSlugError: Raused when status code is 409.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CheckTrap: self
|
||||||
|
"""
|
||||||
|
if isinstance(self.client, Client):
|
||||||
|
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])
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(
|
||||||
|
self,
|
||||||
|
exc_type: Optional[Type[BaseException]],
|
||||||
|
exc: Optional[BaseException],
|
||||||
|
traceback: Optional[TracebackType],
|
||||||
|
) -> Optional[bool]:
|
||||||
|
"""Exit the context manager.
|
||||||
|
|
||||||
|
If there is an exception, add it to any log lines and send a fail ping.
|
||||||
|
Otherwise, send a success ping with any log lines appended.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exc_type (Optional[Type[BaseException]]): [description]
|
||||||
|
exc (Optional[BaseException]): [description]
|
||||||
|
traceback (Optional[TracebackType]): [description]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[bool]: self.suppress_exceptions, if true will not raise any exceptions
|
||||||
|
"""
|
||||||
|
if exc_type is None:
|
||||||
|
await 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))
|
||||||
|
await self.client.fail_ping(
|
||||||
|
self.uuid, self.slug, data="\n".join(self.log_lines)
|
||||||
|
)
|
||||||
|
return self.suppress_exceptions
|
||||||
@@ -35,3 +35,15 @@ class NonUniqueSlugError(HCAPIError):
|
|||||||
"""Thrown when the api returns a 409 when pinging."""
|
"""Thrown when the api returns a 409 when pinging."""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class WrongClientError(HCAPIError):
|
||||||
|
"""Thrown when trying to use a CheckTrap with the wrong client type."""
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class PingFailedError(HCAPIError):
|
||||||
|
"""Thrown when a ping fails."""
|
||||||
|
|
||||||
|
...
|
||||||
|
|||||||
@@ -324,7 +324,9 @@ class Client(AbstractClient):
|
|||||||
for key, item in response.json()["badges"].items()
|
for key, item in response.json()["badges"].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def success_ping(self, uuid: str = "", slug: 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.
|
"""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.
|
Can also be used to indicate a continuously running process is still running and healthy.
|
||||||
@@ -340,6 +342,7 @@ class Client(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to ""
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -354,10 +357,12 @@ class Client(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "")
|
ping_url = self._get_ping_url(uuid, slug, "")
|
||||||
response = self.check_ping_response(self._client.get(ping_url))
|
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
def start_ping(self, uuid: str = "", slug: 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.
|
"""Sends a "job has started!" message to Healthchecks.io.
|
||||||
|
|
||||||
Sending a "start" signal is optional, but it enables a few extra features:
|
Sending a "start" signal is optional, but it enables a few extra features:
|
||||||
@@ -375,6 +380,7 @@ class Client(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to ""
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -389,10 +395,12 @@ class Client(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "/start")
|
ping_url = self._get_ping_url(uuid, slug, "/start")
|
||||||
response = self.check_ping_response(self._client.get(ping_url))
|
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
def fail_ping(self, uuid: str = "", slug: 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.
|
"""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.
|
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||||
@@ -408,6 +416,7 @@ class Client(AbstractClient):
|
|||||||
Args:
|
Args:
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to ""
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -422,11 +431,11 @@ class Client(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, "/fail")
|
ping_url = self._get_ping_url(uuid, slug, "/fail")
|
||||||
response = self.check_ping_response(self._client.get(ping_url))
|
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|
||||||
def exit_code_ping(
|
def exit_code_ping(
|
||||||
self, exit_code: int, uuid: str = "", slug: str = ""
|
self, exit_code: int, uuid: str = "", slug: str = "", data: str = ""
|
||||||
) -> Tuple[bool, str]:
|
) -> Tuple[bool, str]:
|
||||||
"""Signals to Healthchecks.io that the job has failed.
|
"""Signals to Healthchecks.io that the job has failed.
|
||||||
|
|
||||||
@@ -444,6 +453,7 @@ class Client(AbstractClient):
|
|||||||
exit_code (int): Exit code to sent, int from 0 to 255
|
exit_code (int): Exit code to sent, int from 0 to 255
|
||||||
uuid (str): Check's UUID. Defaults to "".
|
uuid (str): Check's UUID. Defaults to "".
|
||||||
slug (str): Check's Slug. Defaults to "".
|
slug (str): Check's Slug. Defaults to "".
|
||||||
|
data (str): Text data to append to this check. Defaults to ""
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
@@ -458,5 +468,5 @@ class Client(AbstractClient):
|
|||||||
Tuple[bool, str]: success (true or false) and the response text
|
Tuple[bool, str]: success (true or false) and the response text
|
||||||
"""
|
"""
|
||||||
ping_url = self._get_ping_url(uuid, slug, f"/{exit_code}")
|
ping_url = self._get_ping_url(uuid, slug, f"/{exit_code}")
|
||||||
response = self.check_ping_response(self._client.get(ping_url))
|
response = self.check_ping_response(self._client.post(ping_url, content=data))
|
||||||
return (True if response.status_code == 200 else False, response.text)
|
return (True if response.status_code == 200 else False, response.text)
|
||||||
|
|||||||
@@ -393,9 +393,9 @@ ping_test_parameters = [
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"respx_mocker, tc, url, ping_method, method_kwargs", ping_test_parameters
|
"respx_mocker, tc, url, ping_method, method_kwargs", ping_test_parameters
|
||||||
)
|
)
|
||||||
async def test_success_ping(respx_mocker, tc, url, ping_method, method_kwargs):
|
async def test_asuccess_ping(respx_mocker, tc, url, ping_method, method_kwargs):
|
||||||
channels_url = urljoin(tc._ping_url, url)
|
channels_url = urljoin(tc._ping_url, url)
|
||||||
respx_mocker.get(channels_url).mock(
|
respx_mocker.post(channels_url).mock(
|
||||||
return_value=Response(status_code=200, text="OK")
|
return_value=Response(status_code=200, text="OK")
|
||||||
)
|
)
|
||||||
ping_method = getattr(tc, ping_method)
|
ping_method = getattr(tc, ping_method)
|
||||||
|
|||||||
97
tests/client/test_check_trap.py
Normal file
97
tests/client/test_check_trap.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import respx
|
||||||
|
from httpx import Client as HTTPXClient
|
||||||
|
from httpx import Response
|
||||||
|
|
||||||
|
from healthchecks_io import CheckCreate
|
||||||
|
from healthchecks_io import CheckTrap
|
||||||
|
from healthchecks_io import CheckUpdate
|
||||||
|
from healthchecks_io import PingFailedError
|
||||||
|
from healthchecks_io import WrongClientError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.respx
|
||||||
|
def test_check_trap_sync(respx_mock, test_client):
|
||||||
|
start_url = urljoin(test_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
success_url = urljoin(test_client._ping_url, "test")
|
||||||
|
respx_mock.post(success_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
|
||||||
|
with CheckTrap(test_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.respx
|
||||||
|
def test_check_trap_sync_failed_ping(respx_mock, test_client):
|
||||||
|
start_url = urljoin(test_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=444, text="OK"))
|
||||||
|
with pytest.raises(PingFailedError):
|
||||||
|
with CheckTrap(test_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.respx
|
||||||
|
def test_check_trap_sync_exception(respx_mock, test_client):
|
||||||
|
start_url = urljoin(test_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
fail_url = urljoin(test_client._ping_url, "test/fail")
|
||||||
|
respx_mock.post(fail_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
with CheckTrap(test_client, uuid="test") as ct:
|
||||||
|
raise Exception("Exception")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_check_trap_async(respx_mock, test_async_client):
|
||||||
|
start_url = urljoin(test_async_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
success_url = urljoin(test_async_client._ping_url, "test")
|
||||||
|
respx_mock.post(success_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
|
||||||
|
async with CheckTrap(test_async_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_check_trap_async_failed_ping(respx_mock, test_async_client):
|
||||||
|
start_url = urljoin(test_async_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=444, text="OK"))
|
||||||
|
with pytest.raises(PingFailedError):
|
||||||
|
async with CheckTrap(test_async_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_check_trap_async_exception(respx_mock, test_async_client):
|
||||||
|
start_url = urljoin(test_async_client._ping_url, "test/start")
|
||||||
|
respx_mock.post(start_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
fail_url = urljoin(test_async_client._ping_url, "test/fail")
|
||||||
|
respx_mock.post(fail_url).mock(return_value=Response(status_code=200, text="OK"))
|
||||||
|
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
async with CheckTrap(test_async_client, uuid="test") as ct:
|
||||||
|
raise Exception("Exception")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_trap_wrong_client_error(test_client, test_async_client):
|
||||||
|
|
||||||
|
with pytest.raises(WrongClientError):
|
||||||
|
async with CheckTrap(test_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(WrongClientError):
|
||||||
|
with CheckTrap(test_async_client, uuid="test") as ct:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_trap_no_uuid_or_slug(test_client):
|
||||||
|
with pytest.raises(Exception) as exc:
|
||||||
|
with CheckTrap(test_client):
|
||||||
|
pass
|
||||||
|
assert str(exc) == "Must pass a slug or an uuid"
|
||||||
@@ -358,7 +358,7 @@ ping_test_parameters = [
|
|||||||
)
|
)
|
||||||
def test_success_ping(respx_mocker, tc, url, ping_method, method_kwargs):
|
def test_success_ping(respx_mocker, tc, url, ping_method, method_kwargs):
|
||||||
channels_url = urljoin(tc._ping_url, url)
|
channels_url = urljoin(tc._ping_url, url)
|
||||||
respx_mocker.get(channels_url).mock(
|
respx_mocker.post(channels_url).mock(
|
||||||
return_value=Response(status_code=200, text="OK")
|
return_value=Response(status_code=200, text="OK")
|
||||||
)
|
)
|
||||||
ping_method = getattr(tc, ping_method)
|
ping_method = getattr(tc, ping_method)
|
||||||
|
|||||||
Reference in New Issue
Block a user