Compare commits

...

33 Commits

Author SHA1 Message Date
Andrew
67cbd2963f Merge pull request #34 from andrewthetechie/fix-schedule
default schedule to None
2022-01-07 20:53:17 -06:00
Andrew Herrington
960d78bc10 default schedule to None 2022-01-07 20:47:21 -06:00
Andrew
17c612c29f Merge pull request #33 from andrewthetechie/fix-optional-timeout
make timeout optional
2022-01-07 20:40:45 -06:00
Andrew Herrington
79ae404e63 make timeout optional` 2022-01-07 20:36:52 -06:00
Andrew
f13236d1a5 Merge pull request #32 from andrewthetechie/fix-check-create-optional-fields
update CheckCreate optionals
2022-01-07 20:26:04 -06:00
Andrew Herrington
4233abdf4b update CheckCreate optionals
These optionals need default values or they fail to validate
2022-01-07 20:22:01 -06:00
Andrew
bbebf174b9 Merge pull request #30 from andrewthetechie/dependabot/pip/pygments-2.11.2
Bump pygments from 2.11.0 to 2.11.2
2022-01-07 20:21:08 -06:00
dependabot[bot]
d9a1553e88 Bump pygments from 2.11.0 to 2.11.2
Bumps [pygments](https://github.com/pygments/pygments) from 2.11.0 to 2.11.2.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.11.0...2.11.2)

---
updated-dependencies:
- dependency-name: pygments
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-08 02:20:25 +00:00
Andrew
ca62cdc282 Merge pull request #31 from andrewthetechie/dependabot/pip/httpx-0.21.3
Bump httpx from 0.21.1 to 0.21.3
2022-01-07 20:18:18 -06:00
Andrew
b5bffa2dc0 Merge pull request #27 from andrewthetechie/dependabot/pip/furo-2022.1.2
Bump furo from 2021.11.23 to 2022.1.2
2022-01-07 20:17:50 -06:00
Andrew
78bfa7b0fc Merge pull request #26 from andrewthetechie/dependabot/pip/pyupgrade-2.31.0
Bump pyupgrade from 2.30.1 to 2.31.0
2022-01-07 20:17:39 -06:00
Andrew
1c5f8f5f1f Merge pull request #24 from andrewthetechie/dependabot/pip/docs/furo-2022.1.2
Bump furo from 2021.11.23 to 2022.1.2 in /docs
2022-01-07 20:17:28 -06:00
Andrew
17d431bf42 Merge pull request #23 from andrewthetechie/dependabot/pip/dot-github/workflows/virtualenv-20.13.0
Bump virtualenv from 20.11.2 to 20.13.0 in /.github/workflows
2022-01-07 20:17:15 -06:00
Andrew
5b1cc5aafd Merge pull request #25 from andrewthetechie/dependabot/pip/pydantic-1.9.0
Bump pydantic from 1.8.2 to 1.9.0
2022-01-07 20:17:02 -06:00
dependabot[bot]
bc7a85e523 Bump httpx from 0.21.1 to 0.21.3
Bumps [httpx](https://github.com/encode/httpx) from 0.21.1 to 0.21.3.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.21.1...0.21.3)

---
updated-dependencies:
- dependency-name: httpx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-07 01:29:58 +00:00
dependabot[bot]
c0431ca182 Bump furo from 2021.11.23 to 2022.1.2
Bumps [furo](https://github.com/pradyunsg/furo) from 2021.11.23 to 2022.1.2.
- [Release notes](https://github.com/pradyunsg/furo/releases)
- [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md)
- [Commits](https://github.com/pradyunsg/furo/compare/2021.11.23...2022.01.02)

---
updated-dependencies:
- dependency-name: furo
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 01:41:10 +00:00
dependabot[bot]
112a80b24e Bump pyupgrade from 2.30.1 to 2.31.0
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 2.30.1 to 2.31.0.
- [Release notes](https://github.com/asottile/pyupgrade/releases)
- [Commits](https://github.com/asottile/pyupgrade/compare/v2.30.1...v2.31.0)

---
updated-dependencies:
- dependency-name: pyupgrade
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 01:40:43 +00:00
dependabot[bot]
ee2cb27a2e Bump pydantic from 1.8.2 to 1.9.0
Bumps [pydantic](https://github.com/samuelcolvin/pydantic) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/samuelcolvin/pydantic/releases)
- [Changelog](https://github.com/samuelcolvin/pydantic/blob/master/HISTORY.md)
- [Commits](https://github.com/samuelcolvin/pydantic/compare/v1.8.2...v1.9.0)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 01:40:12 +00:00
dependabot[bot]
e04b50b565 Bump furo from 2021.11.23 to 2022.1.2 in /docs
Bumps [furo](https://github.com/pradyunsg/furo) from 2021.11.23 to 2022.1.2.
- [Release notes](https://github.com/pradyunsg/furo/releases)
- [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md)
- [Commits](https://github.com/pradyunsg/furo/compare/2021.11.23...2022.01.02)

---
updated-dependencies:
- dependency-name: furo
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 01:36:07 +00:00
dependabot[bot]
3480060739 Bump virtualenv from 20.11.2 to 20.13.0 in /.github/workflows
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.11.2 to 20.13.0.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.11.2...20.13.0)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 01:35:29 +00:00
Andrew Herrington
c103d5f281 poetry update 2021-12-30 23:43:15 -06:00
Andrew
8f2a17cb26 Merge pull request #20 from andrewthetechie/dependabot/pip/pygments-2.11.0
Bump pygments from 2.10.0 to 2.11.0
2021-12-30 23:39:33 -06:00
Andrew
0ad674fc74 Merge pull request #22 from andrewthetechie/context-manager
Add Context Managers
2021-12-30 23:39:18 -06:00
Andrew Herrington
86c2348172 fixes for mypy and xdoctest 2021-12-30 23:34:23 -06:00
Andrew Herrington
0606e83f19 add context manager features 2021-12-30 23:15:08 -06:00
dependabot[bot]
6b46571dd4 Bump pygments from 2.10.0 to 2.11.0
Bumps [pygments](https://github.com/pygments/pygments) from 2.10.0 to 2.11.0.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.10.0...2.11.0)

---
updated-dependencies:
- dependency-name: pygments
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-31 01:27:59 +00:00
Andrew Herrington
9f139177dc add docs on using the context manager 2021-12-30 18:28:08 -06:00
Andrew Herrington
885651c666 add context manager and tests 2021-12-30 18:24:57 -06:00
Andrew Herrington
7bc06275d3 update dependencies 2021-12-30 17:57:55 -06:00
Andrew
31efa7962a Merge pull request #19 from andrewthetechie/dependabot/pip/dot-github/workflows/virtualenv-20.11.2
Bump virtualenv from 20.10.0 to 20.11.2 in /.github/workflows
2021-12-30 17:54:54 -06:00
Andrew
5b5c919995 Merge pull request #16 from andrewthetechie/dependabot/pip/pyupgrade-2.30.0
Bump pyupgrade from 2.29.1 to 2.30.0
2021-12-30 17:54:14 -06:00
dependabot[bot]
0256482f70 Bump virtualenv from 20.10.0 to 20.11.2 in /.github/workflows
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.10.0 to 20.11.2.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.10.0...20.11.2)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-30 01:24:44 +00:00
dependabot[bot]
47a09d7a5f Bump pyupgrade from 2.29.1 to 2.30.0
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 2.29.1 to 2.30.0.
- [Release notes](https://github.com/asottile/pyupgrade/releases)
- [Commits](https://github.com/asottile/pyupgrade/compare/v2.29.1...v2.30.0)

---
updated-dependencies:
- dependency-name: pyupgrade
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 01:25:23 +00:00
15 changed files with 592 additions and 116 deletions

View File

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

View File

@@ -1,3 +1,3 @@
furo==2021.11.23
furo==2022.1.2
sphinx==4.3.2
sphinx-click==3.0.2

View File

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

171
poetry.lock generated
View File

@@ -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)"]
@@ -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,7 +903,7 @@ idna2008 = ["idna"]
[[package]]
name = "ruamel.yaml"
version = "0.17.18"
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
@@ -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 = "7332e1d577ec21c002b08f7c7e9e7dea5dadfa9aa21fe138a5576fb8c39a69cc"
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"},
@@ -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,10 +1766,14 @@ rfc3986 = [
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
]
"ruamel.yaml" = [
{file = "ruamel.yaml-0.17.18-py3-none-any.whl", hash = "sha256:9c648677803a2e9570c1116d15ba4fd89198c8966171868044bee2181cae8ab3"},
{file = "ruamel.yaml-0.17.18.tar.gz", hash = "sha256:92b85e64a1d75adc29f941960f5a88dcf3d233a0ba0c3d0a864ca9645a9b7271"},
{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"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
@@ -1955,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"},
@@ -1971,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"},
]

View File

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

View File

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

View File

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

View File

@@ -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())
@@ -322,7 +341,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 +359,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 +374,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 +399,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 +414,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 +437,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 +452,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 +476,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 +491,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)

View 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

View File

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

View File

@@ -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()
@@ -305,7 +324,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 +342,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 +357,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 +380,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 +395,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 +416,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 +431,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 +453,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 +468,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)

View File

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

View File

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

View 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"

View File

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