mirror of
https://github.com/andrewthetechie/py-healthchecks.io.git
synced 2025-12-19 16:01:35 +01:00
more api methods
This commit is contained in:
@@ -15,7 +15,7 @@ from weakref import finalize
|
|||||||
from httpx import Client, Response
|
from httpx import Client, Response
|
||||||
|
|
||||||
from healthchecks_io.schemas import checks
|
from healthchecks_io.schemas import checks
|
||||||
from .exceptions import HCAPIAuthError, HCAPIError, CheckNotFoundError
|
from .exceptions import HCAPIAuthError, HCAPIError, CheckNotFoundError, BadAPIRequestError
|
||||||
|
|
||||||
|
|
||||||
class AbstractClient(ABC):
|
class AbstractClient(ABC):
|
||||||
@@ -90,6 +90,7 @@ class AbstractClient(ABC):
|
|||||||
Raises:
|
Raises:
|
||||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
HCAPIError: Raised when status_code is 5xx
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response: the passed in response object
|
Response: the passed in response object
|
||||||
@@ -103,6 +104,12 @@ class AbstractClient(ABC):
|
|||||||
f"Status Code {response.status_code}. Response {response.text}"
|
f"Status Code {response.status_code}. Response {response.text}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if response.status_code == 404:
|
||||||
|
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}")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ from typing import Optional
|
|||||||
from httpx import AsyncClient as HTTPXAsyncClient
|
from httpx import AsyncClient as HTTPXAsyncClient
|
||||||
|
|
||||||
from ._abstract import AbstractClient
|
from ._abstract import AbstractClient
|
||||||
from .exceptions import HCAPIAuthError, CheckNotFoundError
|
|
||||||
from .exceptions import HCAPIError
|
|
||||||
from healthchecks_io import VERSION
|
from healthchecks_io import VERSION
|
||||||
from healthchecks_io.schemas import checks
|
from healthchecks_io.schemas import checks
|
||||||
|
|
||||||
@@ -89,11 +87,119 @@ class AsyncClient(AbstractClient):
|
|||||||
checks.Check: the check
|
checks.Check: the check
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
CheckNotFoundError: when no check with check_id is found
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
|
||||||
"""
|
"""
|
||||||
request_url = self._get_api_request_url(f"checks/{check_id}")
|
request_url = self._get_api_request_url(f"checks/{check_id}")
|
||||||
response = self.check_response(await self._client.get(request_url))
|
response = self.check_response(await self._client.get(request_url))
|
||||||
if response.status_code == 404:
|
|
||||||
raise CheckNotFoundError(f"{check_id} not found at {request_url}")
|
|
||||||
return checks.Check.from_api_result(response.json())
|
return checks.Check.from_api_result(response.json())
|
||||||
|
|
||||||
|
async def pause_check(self, check_id: str) -> checks.Check:
|
||||||
|
"""Disables monitoring for a check without removing it.
|
||||||
|
|
||||||
|
The check goes into a "paused" state.
|
||||||
|
You can resume monitoring of the check by pinging it.
|
||||||
|
|
||||||
|
check_id must be a uuid, not a unique id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
check_id (str): check's uuid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
checks.Check: the check just paused
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
|
||||||
|
"""
|
||||||
|
request_url = self._get_api_request_url(f"checks/{check_id}/pause")
|
||||||
|
response = self.check_response(await self._client.post(request_url , data={}))
|
||||||
|
return checks.Check.from_api_result(response.json())
|
||||||
|
|
||||||
|
async def delete_check(self, check_id: str) -> checks.Check:
|
||||||
|
"""Permanently deletes the check from the user's account.
|
||||||
|
|
||||||
|
check_id must be a uuid, not a unique id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
check_id (str): check's uuid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
checks.Check: the check just deleted
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
|
||||||
|
"""
|
||||||
|
request_url = self._get_api_request_url(f"checks/{check_id}")
|
||||||
|
response = self.check_response(await self._client.delete(request_url))
|
||||||
|
return checks.Check.from_api_result(response.json())
|
||||||
|
|
||||||
|
async 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),
|
||||||
|
and the total number of returned pings depends on the account's
|
||||||
|
billing plan: 100 for free accounts, 1000 for paid accounts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
check_id (str): check's uuid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[checks.CheckPings]: list of pings this check has received
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
|
||||||
|
"""
|
||||||
|
request_url = self._get_api_request_url(f"checks/{check_id}/pings/")
|
||||||
|
response = self.check_response(await self._client.get(request_url))
|
||||||
|
return [
|
||||||
|
checks.CheckPings.from_api_result(check_data)
|
||||||
|
for check_data in response.json()["pings"]
|
||||||
|
]
|
||||||
|
|
||||||
|
async def get_check_flips(self, check_id: str, seconds: Optional[int] = None, start: Optional[int] = None, end: Optional[int] = 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").
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||||
|
HCAPIError: Raised when status_code is 5xx
|
||||||
|
CheckNotFoundError: Raised when status_code is 404
|
||||||
|
BadAPIRequestError: Raised when status_code is 400
|
||||||
|
|
||||||
|
Args:
|
||||||
|
check_id (str): check uuid
|
||||||
|
seconds (Optional[int], optional): Returns the flips from the last value seconds. Defaults to None.
|
||||||
|
start (Optional[int], optional): Returns flips that are newer than the specified UNIX timestamp.. Defaults to None.
|
||||||
|
end (Optional[int], optional): Returns flips that are older than the specified UNIX timestamp.. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[checks.CheckStatuses]: List of status flips for this check
|
||||||
|
|
||||||
|
"""
|
||||||
|
params = dict()
|
||||||
|
if seconds is not None and seconds >=0:
|
||||||
|
params['seconds'] = seconds
|
||||||
|
if start is not None and start >= 0:
|
||||||
|
params['start'] = start
|
||||||
|
if end is not None and end >= 0:
|
||||||
|
params['end'] = end
|
||||||
|
|
||||||
|
request_url = self._get_api_request_url(f"checks/{check_id}/flips/", params)
|
||||||
|
response = self.check_response(await self._client.get(request_url))
|
||||||
|
return [
|
||||||
|
checks.CheckStatuses(**status_data)
|
||||||
|
for status_data in response.json()
|
||||||
|
]
|
||||||
|
|||||||
@@ -16,3 +16,8 @@ class CheckNotFoundError(HCAPIError):
|
|||||||
"""Thrown when getting a check returns a 404."""
|
"""Thrown when getting a check returns a 404."""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class BadAPIRequestError(HCAPIError):
|
||||||
|
"""Thrown when an api request returns a 400."""
|
||||||
|
|
||||||
|
...
|
||||||
@@ -173,7 +173,7 @@ class CheckPings(BaseModel):
|
|||||||
remote_addr: str
|
remote_addr: str
|
||||||
method: str
|
method: str
|
||||||
user_agent: str
|
user_agent: str
|
||||||
duration: float
|
duration: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api_result(
|
def from_api_result(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from httpx import Response
|
|||||||
|
|
||||||
from healthchecks_io.client import AsyncClient
|
from healthchecks_io.client import AsyncClient
|
||||||
from healthchecks_io.client.exceptions import HCAPIAuthError
|
from healthchecks_io.client.exceptions import HCAPIAuthError
|
||||||
from healthchecks_io.client.exceptions import HCAPIError, CheckNotFoundError
|
from healthchecks_io.client.exceptions import HCAPIError, CheckNotFoundError, BadAPIRequestError
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -98,3 +98,101 @@ async def test_check_get_404(respx_mock, test_async_client):
|
|||||||
)
|
)
|
||||||
with pytest.raises(CheckNotFoundError):
|
with pytest.raises(CheckNotFoundError):
|
||||||
await test_async_client.get_check("test")
|
await test_async_client.get_check("test")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_pause_check_200(fake_check_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/pause")
|
||||||
|
respx_mock.post(checks_url).mock(
|
||||||
|
return_value=Response(status_code=200, json=fake_check_api_result)
|
||||||
|
)
|
||||||
|
check = await test_async_client.pause_check(check_id="test")
|
||||||
|
assert check.name == fake_check_api_result["name"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_check_pause_404(respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/pause")
|
||||||
|
respx_mock.post(checks_url).mock(
|
||||||
|
return_value=Response(status_code=404)
|
||||||
|
)
|
||||||
|
with pytest.raises(CheckNotFoundError):
|
||||||
|
await test_async_client.pause_check("test")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_delete_check_200(fake_check_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test")
|
||||||
|
respx_mock.delete(checks_url).mock(
|
||||||
|
return_value=Response(status_code=200, json=fake_check_api_result)
|
||||||
|
)
|
||||||
|
check = await test_async_client.delete_check(check_id="test")
|
||||||
|
assert check.name == fake_check_api_result["name"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_delete_pause404(respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test")
|
||||||
|
respx_mock.delete(checks_url).mock(
|
||||||
|
return_value=Response(status_code=404)
|
||||||
|
)
|
||||||
|
with pytest.raises(CheckNotFoundError):
|
||||||
|
await test_async_client.delete_check("test")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_get_check_pings_200(fake_check_pings_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/pings/")
|
||||||
|
respx_mock.get(checks_url).mock(
|
||||||
|
return_value=Response(status_code=200, json={"pings": fake_check_pings_api_result})
|
||||||
|
)
|
||||||
|
pings = await test_async_client.get_check_pings("test")
|
||||||
|
assert len(pings) == len(fake_check_pings_api_result)
|
||||||
|
assert pings[0].type == fake_check_pings_api_result[0]['type']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_get_check_flips_200(fake_check_flips_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/flips/")
|
||||||
|
respx_mock.get(checks_url).mock(
|
||||||
|
return_value=Response(status_code=200, json=fake_check_flips_api_result)
|
||||||
|
)
|
||||||
|
flips = await test_async_client.get_check_flips("test")
|
||||||
|
assert len(flips) == len(fake_check_flips_api_result)
|
||||||
|
assert flips[0].up == fake_check_flips_api_result[0]['up']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_get_check_flips_params_200(fake_check_flips_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/flips/?seconds=1&start=1&end=1")
|
||||||
|
respx_mock.get(checks_url).mock(
|
||||||
|
return_value=Response(status_code=200, json=fake_check_flips_api_result)
|
||||||
|
)
|
||||||
|
flips = await test_async_client.get_check_flips("test", seconds=1, start=1, end=1)
|
||||||
|
assert len(flips) == len(fake_check_flips_api_result)
|
||||||
|
assert flips[0].up == fake_check_flips_api_result[0]['up']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.respx
|
||||||
|
async def test_get_check_flips_400(fake_check_flips_api_result, respx_mock, test_async_client):
|
||||||
|
assert test_async_client._client is not None
|
||||||
|
checks_url = urljoin(test_async_client._api_url, "checks/test/flips/")
|
||||||
|
respx_mock.get(checks_url).mock(
|
||||||
|
return_value=Response(status_code=400)
|
||||||
|
)
|
||||||
|
with pytest.raises(BadAPIRequestError):
|
||||||
|
await test_async_client.get_check_flips("test")
|
||||||
@@ -86,3 +86,63 @@ def test_async_client():
|
|||||||
"""An AsyncClient for testing, set to a nonsense url so we aren't pinging healtchecks."""
|
"""An AsyncClient for testing, set to a nonsense url so we aren't pinging healtchecks."""
|
||||||
|
|
||||||
yield AsyncClient(api_key="test", api_url="https://localhost/api")
|
yield AsyncClient(api_key="test", api_url="https://localhost/api")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_check_pings_api_result():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"type": "success",
|
||||||
|
"date": "2020-06-09T14:51:06.113073+00:00",
|
||||||
|
"n": 4,
|
||||||
|
"scheme": "http",
|
||||||
|
"remote_addr": "192.0.2.0",
|
||||||
|
"method": "GET",
|
||||||
|
"ua": "curl/7.68.0",
|
||||||
|
"duration": 2.896736
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "start",
|
||||||
|
"date": "2020-06-09T14:51:03.216337+00:00",
|
||||||
|
"n": 3,
|
||||||
|
"scheme": "http",
|
||||||
|
"remote_addr": "192.0.2.0",
|
||||||
|
"method": "GET",
|
||||||
|
"ua": "curl/7.68.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "success",
|
||||||
|
"date": "2020-06-09T14:50:59.633577+00:00",
|
||||||
|
"n": 2,
|
||||||
|
"scheme": "http",
|
||||||
|
"remote_addr": "192.0.2.0",
|
||||||
|
"method": "GET",
|
||||||
|
"ua": "curl/7.68.0",
|
||||||
|
"duration": 2.997976
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "start",
|
||||||
|
"date": "2020-06-09T14:50:56.635601+00:00",
|
||||||
|
"n": 1,
|
||||||
|
"scheme": "http",
|
||||||
|
"remote_addr": "192.0.2.0",
|
||||||
|
"method": "GET",
|
||||||
|
"ua": "curl/7.68.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_check_flips_api_result():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"timestamp": "2020-03-23T10:18:23+00:00",
|
||||||
|
"up": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2020-03-23T10:17:15+00:00",
|
||||||
|
"up": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2020-03-23T10:16:18+00:00",
|
||||||
|
"up": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user