mirror of
https://github.com/andrewthetechie/py-healthchecks.io.git
synced 2025-12-05 17:18:16 +01:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
818dd54f2f | ||
|
|
376578ac36 | ||
|
|
67cbd2963f | ||
|
|
960d78bc10 | ||
|
|
17c612c29f | ||
|
|
79ae404e63 | ||
|
|
f13236d1a5 | ||
|
|
4233abdf4b | ||
|
|
bbebf174b9 | ||
|
|
d9a1553e88 | ||
|
|
ca62cdc282 | ||
|
|
b5bffa2dc0 | ||
|
|
78bfa7b0fc | ||
|
|
1c5f8f5f1f | ||
|
|
17d431bf42 | ||
|
|
5b1cc5aafd | ||
|
|
bc7a85e523 | ||
|
|
c0431ca182 | ||
|
|
112a80b24e | ||
|
|
ee2cb27a2e | ||
|
|
e04b50b565 | ||
|
|
3480060739 | ||
|
|
c103d5f281 | ||
|
|
8f2a17cb26 | ||
|
|
0ad674fc74 | ||
|
|
86c2348172 | ||
|
|
0606e83f19 | ||
|
|
6b46571dd4 | ||
|
|
9f139177dc | ||
|
|
885651c666 | ||
|
|
7bc06275d3 | ||
|
|
31efa7962a | ||
|
|
5b5c919995 | ||
|
|
0256482f70 | ||
|
|
47a09d7a5f | ||
|
|
f15615a09f | ||
|
|
819693cd1a | ||
|
|
a83fd4ac77 | ||
|
|
93bd42756a | ||
|
|
b96a02d711 | ||
|
|
3f5662670a | ||
|
|
063a5e9709 | ||
|
|
379782f86a | ||
|
|
fb84f469ac | ||
|
|
3f4aaa6be3 | ||
|
|
b0187f44e1 | ||
|
|
1abc3ce7e2 |
2
.github/workflows/constraints.txt
vendored
2
.github/workflows/constraints.txt
vendored
@@ -2,5 +2,5 @@ pip==21.3.1
|
||||
nox==2021.10.1
|
||||
nox-poetry==0.9.0
|
||||
poetry==1.1.12
|
||||
virtualenv==20.10.0
|
||||
virtualenv==20.13.0
|
||||
poetry-dynamic-versioning==0.13.1
|
||||
|
||||
10
.github/workflows/pre-release.yml
vendored
10
.github/workflows/pre-release.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Release
|
||||
name: Pre-release to pypi
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -40,11 +40,3 @@ jobs:
|
||||
user: __token__
|
||||
password: ${{ secrets.TEST_PYPI_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
|
||||
- name: Publish the release notes
|
||||
uses: release-drafter/release-drafter@v5.15.0
|
||||
with:
|
||||
publish: ${{ steps.check-version.outputs.tag != '' }}
|
||||
tag: ${{ steps.check-version.outputs.tag }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
40
.github/workflows/release.yml
vendored
Normal file
40
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Release to pypi
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v2.4.0
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2.3.1
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Upgrade pip
|
||||
run: |
|
||||
pip install --constraint=.github/workflows/constraints.txt pip
|
||||
pip --version
|
||||
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
pip install --constraint=.github/workflows/constraints.txt poetry poetry-dynamic-versioning
|
||||
poetry --version
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
poetry build --ansi
|
||||
|
||||
- name: Publish package on PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.4.2
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
16
README.rst
16
README.rst
@@ -7,16 +7,16 @@ Py Healthchecks.Io
|
||||
|
||||
|pre-commit| |Black|
|
||||
|
||||
.. |PyPI| image:: https://img.shields.io/pypi/v/py-healthchecksio.svg
|
||||
:target: https://pypi.org/project/py-healthchecksio/
|
||||
.. |PyPI| image:: https://img.shields.io/pypi/v/healthchecks-io.svg
|
||||
:target: https://pypi.org/project/healthchecks-io/
|
||||
:alt: PyPI
|
||||
.. |Status| image:: https://img.shields.io/pypi/status/py-healthchecksio.svg
|
||||
:target: https://pypi.org/project/py-healthchecksio/
|
||||
.. |Status| image:: https://img.shields.io/pypi/status/healthchecks-io.svg
|
||||
:target: https://pypi.org/project/healthchecks-io/
|
||||
:alt: Status
|
||||
.. |Python Version| image:: https://img.shields.io/pypi/pyversions/py-healthchecksio
|
||||
:target: https://pypi.org/project/py-healthchecksio
|
||||
.. |Python Version| image:: https://img.shields.io/pypi/pyversions/healthchecks-io
|
||||
:target: https://pypi.org/project/healthchecks-io
|
||||
:alt: Python Version
|
||||
.. |License| image:: https://img.shields.io/pypi/l/py-healthchecks.io
|
||||
.. |License| image:: https://img.shields.io/pypi/l/healthchecks-io
|
||||
:target: https://opensource.org/licenses/MIT
|
||||
:alt: License
|
||||
.. |Read the Docs| image:: https://img.shields.io/readthedocs/py-healthchecksio/latest.svg?label=Read%20the%20Docs
|
||||
@@ -60,7 +60,7 @@ You can install *Py Healthchecks.Io* via pip_ from PyPI_:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ pip install py-healthchecks.io
|
||||
$ pip install healthchecks-io
|
||||
|
||||
|
||||
Usage
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
furo==2021.11.23
|
||||
sphinx==4.3.1
|
||||
furo==2022.1.2
|
||||
sphinx==4.3.2
|
||||
sphinx-click==3.0.2
|
||||
|
||||
@@ -3,6 +3,31 @@ Usage
|
||||
|
||||
This package implements the Healthchecks.io Management and Ping APIs as documented here https://healthchecks.io/docs/api/.
|
||||
|
||||
Context Manager
|
||||
---------------
|
||||
|
||||
Either the Client or AsyncClient can be used as a ContextManager (or Async Context Manager)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from healthchecks_io import AsyncClient, CheckCreate
|
||||
|
||||
async with AsyncClient(api_key="myapikey") as client:
|
||||
check = await client.create_check(CreateCheck(name="New Check", tags="tag1 tag2")
|
||||
print(check)
|
||||
|
||||
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.
|
||||
|
||||
.. 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
|
||||
----
|
||||
|
||||
@@ -57,6 +82,7 @@ Pinging a Check
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from healthchecks_io import Client
|
||||
|
||||
client = Client(api_key="myapikey")
|
||||
@@ -78,3 +104,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")
|
||||
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
|
||||
|
||||
189
poetry.lock
generated
189
poetry.lock
generated
@@ -45,17 +45,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.2.0"
|
||||
version = "21.4.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
@@ -68,21 +68,6 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
[package.dependencies]
|
||||
pytz = ">=2015.7"
|
||||
|
||||
[[package]]
|
||||
name = "backports.entry-points-selectable"
|
||||
version = "1.1.1"
|
||||
description = "Compatibility shim providing selectable entry points for older implementations"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.7.1"
|
||||
@@ -261,11 +246,11 @@ pipenv = ["pipenv"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.4.0"
|
||||
version = "3.4.2"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
@@ -352,7 +337,7 @@ restructuredtext-lint = "*"
|
||||
|
||||
[[package]]
|
||||
name = "furo"
|
||||
version = "2021.11.23"
|
||||
version = "2022.1.2"
|
||||
description = "A clean customisable Sphinx documentation theme."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -417,7 +402,7 @@ http2 = ["h2 (>=3,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.21.1"
|
||||
version = "0.21.3"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -437,7 +422,7 @@ http2 = ["h2 (>=3,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -603,11 +588,11 @@ flake8-polyfill = ">=1.0.2,<2"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
@@ -647,7 +632,7 @@ virtualenv = ">=20.0.8"
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit-hooks"
|
||||
version = "4.0.1"
|
||||
version = "4.1.0"
|
||||
description = "Some out-of-the-box hooks for pre-commit."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -675,7 +660,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.8.2"
|
||||
version = "1.9.0"
|
||||
description = "Data validation and settings management using python 3.6 type hinting"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -712,7 +697,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.10.0"
|
||||
version = "2.11.2"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -834,7 +819,7 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyupgrade"
|
||||
version = "2.29.1"
|
||||
version = "2.31.0"
|
||||
description = "A tool to automatically upgrade syntax for newer versions."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -918,14 +903,14 @@ idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "ruamel.yaml"
|
||||
version = "0.17.17"
|
||||
version = "0.17.19"
|
||||
description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3"
|
||||
|
||||
[package.dependencies]
|
||||
"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""}
|
||||
"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["ryd"]
|
||||
@@ -995,7 +980,7 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "4.3.1"
|
||||
version = "4.3.2"
|
||||
description = "Python documentation generator"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -1021,7 +1006,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinxcontrib-websupport"]
|
||||
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"]
|
||||
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"]
|
||||
test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
|
||||
|
||||
[[package]]
|
||||
@@ -1154,7 +1139,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -1190,7 +1175,7 @@ test = ["pytest", "typing-extensions", "mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "types-croniter"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
description = "Typing stubs for croniter"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -1227,14 +1212,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.10.0"
|
||||
version = "20.11.2"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
"backports.entry-points-selectable" = ">=1.0.4"
|
||||
distlib = ">=0.3.1,<1"
|
||||
filelock = ">=3.2,<4"
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
@@ -1267,20 +1251,20 @@ tests = ["codecov", "scikit-build", "cmake", "ninja", "pybind11", "pytest", "pyt
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "7aa92cbd452389c4c3bf0af735ac63ed73601cf81cde06a32e0485c2a0be7a6e"
|
||||
content-hash = "85bd6d26ad35f2ca03ad6ad4cf5534beed5c9abebbde910e2c0e16409dd6048e"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
@@ -1300,17 +1284,13 @@ atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
||||
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
babel = [
|
||||
{file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"},
|
||||
{file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
|
||||
]
|
||||
"backports.entry-points-selectable" = [
|
||||
{file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"},
|
||||
{file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"},
|
||||
]
|
||||
bandit = [
|
||||
{file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"},
|
||||
{file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"},
|
||||
@@ -1417,8 +1397,8 @@ dparse = [
|
||||
{file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.4.0-py3-none-any.whl", hash = "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8"},
|
||||
{file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"},
|
||||
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
|
||||
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
|
||||
@@ -1444,8 +1424,8 @@ flake8-rst-docstrings = [
|
||||
{file = "flake8_rst_docstrings-0.2.5-py3-none-any.whl", hash = "sha256:b99d9041b769b857efe45a448dc8c71b1bb311f9cacbdac5de82f96498105082"},
|
||||
]
|
||||
furo = [
|
||||
{file = "furo-2021.11.23-py3-none-any.whl", hash = "sha256:6d396451ad1aadce380c662fca9362cb10f4fd85f296d74fe3ca32006eb641d7"},
|
||||
{file = "furo-2021.11.23.tar.gz", hash = "sha256:54cecac5f3b688b5c7370d72ecdf1cd91a6c53f0f42751f4a719184b562cde70"},
|
||||
{file = "furo-2022.1.2-py3-none-any.whl", hash = "sha256:958016bfe1387c1e8ddf5b9d71696b69c4eaa5cd8afc9492abfb008aba2d300c"},
|
||||
{file = "furo-2022.1.2.tar.gz", hash = "sha256:b217f218cbcd423ffbfe69baa79389d4ecebf2d86f0d593c44ef31da7b5aed30"},
|
||||
]
|
||||
gitdb = [
|
||||
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
|
||||
@@ -1464,12 +1444,12 @@ httpcore = [
|
||||
{file = "httpcore-0.14.3.tar.gz", hash = "sha256:d10162a63265a0228d5807964bd964478cbdb5178f9a2eedfebb2faba27eef5d"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.21.1-py3-none-any.whl", hash = "sha256:208e5ef2ad4d105213463cfd541898ed9d11851b346473539a8425e644bb7c66"},
|
||||
{file = "httpx-0.21.1.tar.gz", hash = "sha256:02af20df486b78892a614a7ccd4e4e86a5409ec4981ab0e422c579a887acad83"},
|
||||
{file = "httpx-0.21.3-py3-none-any.whl", hash = "sha256:df9a0fd43fa79dbab411d83eb1ea6f7a525c96ad92e60c2d7f40388971b25777"},
|
||||
{file = "httpx-0.21.3.tar.gz", hash = "sha256:7a3eb67ef0b8abbd6d9402248ef2f84a76080fa1c839f8662e6eb385640e445a"},
|
||||
]
|
||||
identify = [
|
||||
{file = "identify-2.4.0-py2.py3-none-any.whl", hash = "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc"},
|
||||
{file = "identify-2.4.0.tar.gz", hash = "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107"},
|
||||
{file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"},
|
||||
{file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
@@ -1619,8 +1599,8 @@ pep8-naming = [
|
||||
{file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
{file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
|
||||
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
|
||||
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
@@ -1631,8 +1611,8 @@ pre-commit = [
|
||||
{file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"},
|
||||
]
|
||||
pre-commit-hooks = [
|
||||
{file = "pre_commit_hooks-4.0.1-py2.py3-none-any.whl", hash = "sha256:6efe92c7613c311abc7dd06817fc016f222d9289fe24b261e64412b0af96c662"},
|
||||
{file = "pre_commit_hooks-4.0.1.tar.gz", hash = "sha256:99f1b9fc00a82e6588990b6b92edcdf4bec9c3d65c6272b8867be389055ce05e"},
|
||||
{file = "pre_commit_hooks-4.1.0-py2.py3-none-any.whl", hash = "sha256:ba95316b79038e56ce998cdacb1ce922831ac0e41744c77bcc2b9677bf183206"},
|
||||
{file = "pre_commit_hooks-4.1.0.tar.gz", hash = "sha256:b6361865d1877c5da5ac3a944aab19ce6bd749a534d2ede28e683d07194a57e1"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
@@ -1643,28 +1623,41 @@ pycodestyle = [
|
||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
|
||||
{file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
|
||||
{file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
|
||||
{file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
|
||||
{file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
|
||||
{file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
|
||||
{file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"},
|
||||
{file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"},
|
||||
{file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"},
|
||||
{file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"},
|
||||
{file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"},
|
||||
{file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"},
|
||||
{file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"},
|
||||
{file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"},
|
||||
]
|
||||
pydocstyle = [
|
||||
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
|
||||
@@ -1675,8 +1668,8 @@ pyflakes = [
|
||||
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
|
||||
{file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
|
||||
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
|
||||
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
|
||||
@@ -1715,8 +1708,8 @@ pytz = [
|
||||
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
|
||||
]
|
||||
pyupgrade = [
|
||||
{file = "pyupgrade-2.29.1-py2.py3-none-any.whl", hash = "sha256:1d3f03d14652caacd6f3d464696e103ef33578531a5c1b995be3821b14b811b7"},
|
||||
{file = "pyupgrade-2.29.1.tar.gz", hash = "sha256:737e02bed0de70247df5d2a0c3edba6b4529adf44f5ef588ecda4a69f14bf694"},
|
||||
{file = "pyupgrade-2.31.0-py2.py3-none-any.whl", hash = "sha256:0a62c5055f854d7f36e155b7ee8920561bf0399c53edd975cf02436eef8937fc"},
|
||||
{file = "pyupgrade-2.31.0.tar.gz", hash = "sha256:80e2308cae2b11c3fdd091137495d99abf7e0cd98b501aa5758974991497c24c"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
@@ -1773,8 +1766,8 @@ rfc3986 = [
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
"ruamel.yaml" = [
|
||||
{file = "ruamel.yaml-0.17.17-py3-none-any.whl", hash = "sha256:9af3ec5d7f8065582f3aa841305465025d0afd26c5fb54e15b964e11838fc74f"},
|
||||
{file = "ruamel.yaml-0.17.17.tar.gz", hash = "sha256:9751de4cbb57d4bfbf8fc394e125ed4a2f170fbff3dc3d78abf50be85924f8be"},
|
||||
{file = "ruamel.yaml-0.17.19-py3-none-any.whl", hash = "sha256:92ac00b312c9a83ff3253a8f7b86dfe6f9996b4082b103af84b8df99175945bc"},
|
||||
{file = "ruamel.yaml-0.17.19.tar.gz", hash = "sha256:b9ce9a925d0f0c35a1dbba56b40f253c53cd526b0fa81cf7b1d24996f28fb1d7"},
|
||||
]
|
||||
"ruamel.yaml.clib" = [
|
||||
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
|
||||
@@ -1828,8 +1821,8 @@ soupsieve = [
|
||||
{file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"},
|
||||
]
|
||||
sphinx = [
|
||||
{file = "Sphinx-4.3.1-py3-none-any.whl", hash = "sha256:048dac56039a5713f47a554589dc98a442b39226a2b9ed7f82797fcb2fe9253f"},
|
||||
{file = "Sphinx-4.3.1.tar.gz", hash = "sha256:32a5b3e9a1b176cc25ed048557d4d3d01af635e6b76c5bc7a43b0a34447fbd45"},
|
||||
{file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"},
|
||||
{file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"},
|
||||
]
|
||||
sphinx-autobuild = [
|
||||
{file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"},
|
||||
@@ -1876,8 +1869,8 @@ toml = [
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"},
|
||||
{file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"},
|
||||
{file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
|
||||
{file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
|
||||
]
|
||||
tornado = [
|
||||
{file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
|
||||
@@ -1959,8 +1952,8 @@ typeguard = [
|
||||
{file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"},
|
||||
]
|
||||
types-croniter = [
|
||||
{file = "types-croniter-1.0.3.tar.gz", hash = "sha256:955d9ca3efaf99f0f76d6607a1f5b8a9cc4051e26dc34b4a3d69dfa29571758e"},
|
||||
{file = "types_croniter-1.0.3-py3-none-any.whl", hash = "sha256:45f5ca35db9c964c986ecbceea0a779219638b38a01f5346242da44a0013c3e1"},
|
||||
{file = "types-croniter-1.0.4.tar.gz", hash = "sha256:d366652365d034bf9c778dcdd7f0f2a550f60a680f5ea44609419deb2e8555e8"},
|
||||
{file = "types_croniter-1.0.4-py3-none-any.whl", hash = "sha256:d2ab4b182b10db03152d6056e239323c9254a92d4b0b810156b11795b3070713"},
|
||||
]
|
||||
types-pytz = [
|
||||
{file = "types-pytz-2021.3.3.tar.gz", hash = "sha256:f6d21d6687935a1615db464b1e1df800d19502c36bc0486f43be7dfd2c404947"},
|
||||
@@ -1975,14 +1968,14 @@ urllib3 = [
|
||||
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
|
||||
]
|
||||
virtualenv = [
|
||||
{file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"},
|
||||
{file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"},
|
||||
{file = "virtualenv-20.11.2-py2.py3-none-any.whl", hash = "sha256:efd556cec612fd826dc7ef8ce26a6e4ba2395f494244919acd135fb5ceffa809"},
|
||||
{file = "virtualenv-20.11.2.tar.gz", hash = "sha256:7f9e9c2e878d92a434e760058780b8d67a7c5ec016a66784fe4b0d5e50a4eb5c"},
|
||||
]
|
||||
xdoctest = [
|
||||
{file = "xdoctest-0.15.10-py3-none-any.whl", hash = "sha256:7666bd0511df59275dfe94ef94b0fde9654afd14f00bf88902fdc9bcee77d527"},
|
||||
{file = "xdoctest-0.15.10.tar.gz", hash = "sha256:5f16438f2b203860e75ec594dbc38020df7524db0b41bb88467ea0a6030e6685"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
|
||||
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
|
||||
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
|
||||
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
|
||||
]
|
||||
|
||||
@@ -30,7 +30,7 @@ safety = "^1.10.3"
|
||||
mypy = "^0.910"
|
||||
typeguard = "^2.13.2"
|
||||
xdoctest = {extras = ["colors"], version = "^0.15.10"}
|
||||
sphinx = "^4.3.1"
|
||||
sphinx = "^4.3.2"
|
||||
sphinx-autobuild = ">=2021.3.14"
|
||||
pre-commit = "^2.16.0"
|
||||
flake8 = "^4.0.1"
|
||||
@@ -44,8 +44,8 @@ darglint = "^1.8.1"
|
||||
reorder-python-imports = "^2.6.0"
|
||||
pre-commit-hooks = "^4.0.1"
|
||||
sphinx-click = "^3.0.2"
|
||||
Pygments = "^2.10.0"
|
||||
pyupgrade = "^2.29.1"
|
||||
Pygments = "^2.11.2"
|
||||
pyupgrade = "^2.31.0"
|
||||
furo = ">=2021.11.12"
|
||||
pytest-cov = "^3.0.0"
|
||||
types-croniter = "^1.0.3"
|
||||
|
||||
@@ -4,18 +4,22 @@ __version__ = "0.0.0" # noqa: E402
|
||||
|
||||
from .client import AsyncClient # 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 CheckNotFoundError # noqa: F401, E402
|
||||
from .client.exceptions import HCAPIAuthError # noqa: F401, E402
|
||||
from .client.exceptions import HCAPIError # noqa: F401, E402
|
||||
from .client.exceptions import HCAPIRateLimitError # 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 Integration, Badges, CheckUpdate # noqa: F401, E402
|
||||
|
||||
__all__ = [
|
||||
"AsyncClient",
|
||||
"Client",
|
||||
"CheckTrap",
|
||||
"BadAPIRequestError",
|
||||
"CheckNotFoundError",
|
||||
"HCAPIAuthError",
|
||||
@@ -23,6 +27,8 @@ __all__ = [
|
||||
"CheckNotFoundError",
|
||||
"HCAPIRateLimitError",
|
||||
"NonUniqueSlugError",
|
||||
"WrongClientError",
|
||||
"PingFailedError",
|
||||
"Check",
|
||||
"CheckCreate",
|
||||
"CheckUpdate",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""healthchecks_io clients."""
|
||||
from .async_client import AsyncClient # noqa: F401
|
||||
from .check_trap import CheckTrap # noqa: F401
|
||||
from .sync_client import Client # noqa: F401
|
||||
|
||||
__all__ = ["AsyncClient", "Client"]
|
||||
__all__ = ["AsyncClient", "Client", "CheckTrap"]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""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
|
||||
|
||||
@@ -56,6 +58,23 @@ class AsyncClient(AbstractClient):
|
||||
] = f"py-healthchecks.io-async/{client_version}"
|
||||
self._client.headers["Content-type"] = "application/json"
|
||||
|
||||
async def __aenter__(self) -> "AsyncClient":
|
||||
"""Context manager entrance.
|
||||
|
||||
Returns:
|
||||
AsyncClient: returns this client as a context manager
|
||||
"""
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> None:
|
||||
"""Context manager exit."""
|
||||
await self._afinalizer_method()
|
||||
|
||||
def _finalizer_method(self) -> None:
|
||||
"""Calls _afinalizer_method from a sync context to work with weakref.finalizer."""
|
||||
asyncio.run(self._afinalizer_method())
|
||||
@@ -79,7 +98,7 @@ class AsyncClient(AbstractClient):
|
||||
"""
|
||||
request_url = self._get_api_request_url("checks/")
|
||||
response = self.check_response(
|
||||
await self._client.post(request_url, json=new_check.dict())
|
||||
await self._client.post(request_url, json=new_check.dict(exclude_none=True))
|
||||
)
|
||||
return Check.from_api_result(response.json())
|
||||
|
||||
@@ -99,7 +118,8 @@ class AsyncClient(AbstractClient):
|
||||
request_url = self._get_api_request_url(f"checks/{uuid}")
|
||||
response = self.check_response(
|
||||
await self._client.post(
|
||||
request_url, json=update_check.dict(exclude_unset=True)
|
||||
request_url,
|
||||
json=update_check.dict(exclude_unset=True, exclude_none=True),
|
||||
)
|
||||
)
|
||||
return Check.from_api_result(response.json())
|
||||
@@ -322,7 +342,9 @@ class AsyncClient(AbstractClient):
|
||||
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.
|
||||
|
||||
Can also be used to indicate a continuously running process is still running and healthy.
|
||||
@@ -338,6 +360,7 @@ class AsyncClient(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to "".
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -352,10 +375,14 @@ class AsyncClient(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Sending a "start" signal is optional, but it enables a few extra features:
|
||||
@@ -373,6 +400,7 @@ class AsyncClient(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to "".
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -387,10 +415,14 @@ class AsyncClient(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
@@ -406,6 +438,7 @@ class AsyncClient(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to "".
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -420,11 +453,13 @@ class AsyncClient(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
@@ -442,6 +477,7 @@ class AsyncClient(AbstractClient):
|
||||
exit_code (int): Exit code to sent, int from 0 to 255
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to "".
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -456,5 +492,7 @@ class AsyncClient(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
166
src/healthchecks_io/client/check_trap.py
Normal file
166
src/healthchecks_io/client/check_trap.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""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:
|
||||
# ignore typing, if we've gotten here we know its an async client
|
||||
await self.client.success_ping( # type: ignore
|
||||
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( # type: ignore
|
||||
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."""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class WrongClientError(HCAPIError):
|
||||
"""Thrown when trying to use a CheckTrap with the wrong client type."""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class PingFailedError(HCAPIError):
|
||||
"""Thrown when a ping fails."""
|
||||
|
||||
...
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""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
|
||||
|
||||
@@ -50,6 +52,23 @@ class Client(AbstractClient):
|
||||
self._client.headers["user-agent"] = f"py-healthchecks.io/{client_version}"
|
||||
self._client.headers["Content-type"] = "application/json"
|
||||
|
||||
def __enter__(self) -> "Client":
|
||||
"""Context manager entrance.
|
||||
|
||||
Returns:
|
||||
Client: returns this client as a context manager
|
||||
"""
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> None:
|
||||
"""Context manager exit."""
|
||||
self._finalizer_method()
|
||||
|
||||
def _finalizer_method(self) -> None:
|
||||
"""Closes the httpx client."""
|
||||
self._client.close()
|
||||
@@ -119,7 +138,7 @@ class Client(AbstractClient):
|
||||
"""
|
||||
request_url = self._get_api_request_url("checks/")
|
||||
response = self.check_response(
|
||||
self._client.post(request_url, json=new_check.dict())
|
||||
self._client.post(request_url, json=new_check.dict(exclude_none=True))
|
||||
)
|
||||
return Check.from_api_result(response.json())
|
||||
|
||||
@@ -144,7 +163,10 @@ class Client(AbstractClient):
|
||||
"""
|
||||
request_url = self._get_api_request_url(f"checks/{uuid}")
|
||||
response = self.check_response(
|
||||
self._client.post(request_url, json=update_check.dict(exclude_unset=True))
|
||||
self._client.post(
|
||||
request_url,
|
||||
json=update_check.dict(exclude_unset=True, exclude_none=True),
|
||||
)
|
||||
)
|
||||
return Check.from_api_result(response.json())
|
||||
|
||||
@@ -305,7 +327,9 @@ class Client(AbstractClient):
|
||||
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.
|
||||
|
||||
Can also be used to indicate a continuously running process is still running and healthy.
|
||||
@@ -321,6 +345,7 @@ class Client(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to ""
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -335,10 +360,12 @@ class Client(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Sending a "start" signal is optional, but it enables a few extra features:
|
||||
@@ -356,6 +383,7 @@ class Client(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to ""
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -370,10 +398,12 @@ class Client(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
Actively signaling a failure minimizes the delay from your monitored service failing to you receiving an alert.
|
||||
@@ -389,6 +419,7 @@ class Client(AbstractClient):
|
||||
Args:
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to ""
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -403,11 +434,11 @@ class Client(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
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]:
|
||||
"""Signals to Healthchecks.io that the job has failed.
|
||||
|
||||
@@ -425,6 +456,7 @@ class Client(AbstractClient):
|
||||
exit_code (int): Exit code to sent, int from 0 to 255
|
||||
uuid (str): Check's UUID. Defaults to "".
|
||||
slug (str): Check's Slug. Defaults to "".
|
||||
data (str): Text data to append to this check. Defaults to ""
|
||||
|
||||
Raises:
|
||||
HCAPIAuthError: Raised when status_code == 401 or 403
|
||||
@@ -439,5 +471,5 @@ class Client(AbstractClient):
|
||||
Tuple[bool, str]: success (true or false) and the response text
|
||||
"""
|
||||
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)
|
||||
|
||||
@@ -38,7 +38,7 @@ class Check(BaseModel):
|
||||
update_url: Optional[str]
|
||||
pause_url: Optional[str]
|
||||
channels: Optional[str]
|
||||
timeout: int
|
||||
timeout: Optional[int]
|
||||
uuid: Optional[str]
|
||||
|
||||
@validator("uuid", always=True)
|
||||
@@ -65,11 +65,11 @@ class Check(BaseModel):
|
||||
class CheckCreate(BaseModel):
|
||||
"""Pydantic object for creating a check."""
|
||||
|
||||
name: Optional[str] = Field(..., description="Name of the check")
|
||||
name: Optional[str] = Field("", description="Name of the check")
|
||||
tags: Optional[str] = Field(
|
||||
..., description="String separated list of tags to apply"
|
||||
"", description="String separated list of tags to apply"
|
||||
)
|
||||
desc: Optional[str] = Field(..., description="Description of the check")
|
||||
desc: Optional[str] = Field("", description="Description of the check")
|
||||
timeout: Optional[int] = Field(
|
||||
86400,
|
||||
description="The expected period of this check in seconds.",
|
||||
@@ -83,7 +83,7 @@ class CheckCreate(BaseModel):
|
||||
lte=31536000,
|
||||
)
|
||||
schedule: Optional[str] = Field(
|
||||
"* * * * *",
|
||||
None,
|
||||
description="A cron expression defining this check's schedule. "
|
||||
"If you specify both timeout and schedule parameters, "
|
||||
"Healthchecks.io will create a Cron check and ignore the "
|
||||
|
||||
@@ -14,6 +14,42 @@ from healthchecks_io.client.exceptions import HCAPIAuthError
|
||||
from healthchecks_io.client.exceptions import HCAPIError
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.respx
|
||||
async def test_acreate_check_200_context_manager(
|
||||
fake_check_api_result, respx_mock, test_async_client
|
||||
):
|
||||
checks_url = urljoin(test_async_client._api_url, "checks/")
|
||||
respx_mock.post(checks_url).mock(
|
||||
return_value=Response(
|
||||
status_code=200,
|
||||
json={
|
||||
"channels": "",
|
||||
"desc": "",
|
||||
"grace": 60,
|
||||
"last_ping": None,
|
||||
"n_pings": 0,
|
||||
"name": "Backups",
|
||||
"slug": "backups",
|
||||
"next_ping": None,
|
||||
"manual_resume": False,
|
||||
"methods": "",
|
||||
"pause_url": "https://healthchecks.io/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
|
||||
"ping_url": "https://hc-ping.com/f618072a-7bde-4eee-af63-71a77c5723bc",
|
||||
"status": "new",
|
||||
"tags": "prod www",
|
||||
"timeout": 3600,
|
||||
"update_url": "https://healthchecks.io/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc",
|
||||
},
|
||||
)
|
||||
)
|
||||
async with test_async_client as test_client:
|
||||
check = await test_client.create_check(
|
||||
CheckCreate(name="test", tags="test", desc="test")
|
||||
)
|
||||
assert check.name == "Backups"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.respx
|
||||
async def test_acreate_check_200(fake_check_api_result, respx_mock, test_async_client):
|
||||
@@ -357,9 +393,9 @@ ping_test_parameters = [
|
||||
@pytest.mark.parametrize(
|
||||
"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)
|
||||
respx_mocker.get(channels_url).mock(
|
||||
respx_mocker.post(channels_url).mock(
|
||||
return_value=Response(status_code=200, text="OK")
|
||||
)
|
||||
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"
|
||||
@@ -14,6 +14,39 @@ from healthchecks_io.client.exceptions import HCAPIAuthError
|
||||
from healthchecks_io.client.exceptions import HCAPIError
|
||||
|
||||
|
||||
@pytest.mark.respx
|
||||
def test_create_check_200_context_manager(
|
||||
fake_check_api_result, respx_mock, test_client
|
||||
):
|
||||
checks_url = urljoin(test_client._api_url, "checks/")
|
||||
respx_mock.post(checks_url).mock(
|
||||
return_value=Response(
|
||||
status_code=200,
|
||||
json={
|
||||
"channels": "",
|
||||
"desc": "",
|
||||
"grace": 60,
|
||||
"last_ping": None,
|
||||
"n_pings": 0,
|
||||
"name": "Backups",
|
||||
"slug": "backups",
|
||||
"next_ping": None,
|
||||
"manual_resume": False,
|
||||
"methods": "",
|
||||
"pause_url": "https://healthchecks.io/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
|
||||
"ping_url": "https://hc-ping.com/f618072a-7bde-4eee-af63-71a77c5723bc",
|
||||
"status": "new",
|
||||
"tags": "prod www",
|
||||
"timeout": 3600,
|
||||
"update_url": "https://healthchecks.io/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc",
|
||||
},
|
||||
)
|
||||
)
|
||||
with test_client as tc:
|
||||
check = tc.create_check(CheckCreate(name="test", tags="test", desc="test"))
|
||||
assert check.name == "Backups"
|
||||
|
||||
|
||||
@pytest.mark.respx
|
||||
def test_create_check_200(fake_check_api_result, respx_mock, test_client):
|
||||
checks_url = urljoin(test_client._api_url, "checks/")
|
||||
@@ -325,7 +358,7 @@ ping_test_parameters = [
|
||||
)
|
||||
def test_success_ping(respx_mocker, tc, url, ping_method, method_kwargs):
|
||||
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")
|
||||
)
|
||||
ping_method = getattr(tc, ping_method)
|
||||
|
||||
Reference in New Issue
Block a user