From 7879a74dd888335151c162c008c0b2be23f2c57f Mon Sep 17 00:00:00 2001 From: Guy Khmelnitsky <3136012+GuyKh@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:16:01 +0300 Subject: [PATCH] Move to Async ClientSession and Ruff Checking (#45) * Move to async methods * ruff fixes * Fix Tests * Update minimum python version * Fix CI * Requirements * Fix Token usage * Fix commons import * Async tests * Async tests * Fix bad tests * Update README.md * Fix Requirements * Remove requests * Update Pipfile --- .github/workflows/ci.yml | 4 +- .ruff.toml | 32 ++ Pipfile | 6 +- Pipfile.lock | 657 +++++++++++++++++++++++---------- README.md | 6 +- env/pyvenv.cfg | 3 + ims_envista/__init__.py | 7 +- ims_envista/commons.py | 78 ++++ ims_envista/const.py | 8 +- ims_envista/ims_envista.py | 257 +++++++------ ims_envista/ims_variable.py | 6 +- ims_envista/meteo_data.py | 51 +-- ims_envista/station_data.py | 36 +- ims_envista/version.py | 15 +- requirements.txt | 3 +- setup.py | 33 +- tests/__init__.py | 3 +- tests/unit/__init__.py | 1 + tests/unit/test_ims_envista.py | 296 ++++++++------- tests/unit/test_version.py | 18 +- 20 files changed, 990 insertions(+), 530 deletions(-) create mode 100644 .ruff.toml create mode 100644 env/pyvenv.cfg create mode 100644 ims_envista/commons.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f039d0..e2bfddc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest environment: Unittest env: - IMS_TOKEN: '${{ secrets.IMS_TOKEN }}' + IMS_TOKEN: ${{ secrets.IMS_TOKEN }} # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -61,6 +61,8 @@ jobs: - name: Run UnitTests run: >- python3 -m unittest discover + env: + IMS_TOKEN: ${{ secrets.IMS_TOKEN }} #- name: Publish package to TestPyPI # uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..24625ee --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,32 @@ +# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml + +target-version = "py312" + +[lint] +select = [ + "ALL", +] + +ignore = [ + "ANN101", # Missing type annotation for `self` in method + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed + "D102", + "D103", # no docstrings on public methods + "D105", # no docstrings on magic methods + "D107", # no docstrings for methods + "D203", # no-blank-line-before-class (incompatible with formatter) + "D212", # multi-line-summary-first-line (incompatible with formatter) + "COM812", # incompatible with formatter + "ISC001", # incompatible with formatter + "S101", # Assert, + "E501" # Line too long +] + +[lint.flake8-pytest-style] +fixture-parentheses = false + +[lint.pyupgrade] +keep-runtime-typing = true + +[lint.mccabe] +max-complexity = 25 diff --git a/Pipfile b/Pipfile index ea80216..e6a2cbc 100644 --- a/Pipfile +++ b/Pipfile @@ -6,11 +6,11 @@ name = "pypi" [packages] pytest = "*" pytest-cov = "*" -requests = "*" loguru = "*" +aiohttp = "*" [dev-packages] [requires] -python_version = "3.9" -python_full_version = "3.9.6" +python_version = "3.12" +python_full_version = "3.12.0" diff --git a/Pipfile.lock b/Pipfile.lock index 866ff0b..2ec3364 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,12 +1,12 @@ { "_meta": { "hash": { - "sha256": "0cdf77c4f6db54f09fc9abbcb98e1cf2e12d6c985d42da6eec10d576aeef89ae" + "sha256": "d27c3a966a5d20ba22eaef1fbfd113fca97436d630de4ddf9cc2cfed459f8171" }, "pipfile-spec": 6, "requires": { - "python_full_version": "3.9.6", - "python_version": "3.9" + "python_full_version": "3.12.0", + "python_version": "3.12" }, "sources": [ { @@ -17,187 +17,299 @@ ] }, "default": { - "certifi": { + "aiohappyeyeballs": { "hashes": [ - "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", - "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" + "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", + "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.0" + }, + "aiohttp": { + "hashes": [ + "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", + "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1", + "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe", + "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb", + "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", + "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91", + "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", + "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a", + "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3", + "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", + "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", + "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b", + "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8", + "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", + "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", + "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", + "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511", + "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699", + "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", + "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", + "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", + "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db", + "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", + "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce", + "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", + "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", + "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", + "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", + "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", + "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", + "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf", + "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", + "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", + "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6", + "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", + "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3", + "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a", + "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", + "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088", + "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc", + "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f", + "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", + "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471", + "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e", + "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", + "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", + "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69", + "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3", + "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32", + "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589", + "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", + "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92", + "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", + "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", + "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", + "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857", + "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1", + "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6", + "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22", + "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", + "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b", + "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", + "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", + "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", + "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", + "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", + "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f", + "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", + "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", + "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae", + "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d", + "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b", + "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f", + "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862", + "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", + "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c", + "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683", + "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef", + "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f", + "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", + "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", + "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", + "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", + "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11", + "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", + "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", + "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", + "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172", + "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569", + "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", + "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==2024.7.4" + "markers": "python_version >= '3.8'", + "version": "==3.10.5" }, - "charset-normalizer": { + "aiosignal": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "attrs": { + "hashes": [ + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + ], + "markers": "python_version >= '3.7'", + "version": "==24.2.0" }, "coverage": { "extras": [ "toml" ], "hashes": [ - "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c", - "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63", - "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7", - "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f", - "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8", - "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf", - "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0", - "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384", - "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76", - "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7", - "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d", - "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70", - "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f", - "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818", - "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b", - "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d", - "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec", - "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083", - "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2", - "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9", - "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd", - "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade", - "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e", - "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a", - "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227", - "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87", - "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c", - "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e", - "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c", - "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e", - "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd", - "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec", - "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562", - "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8", - "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677", - "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357", - "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c", - "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd", - "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49", - "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286", - "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1", - "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf", - "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51", - "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409", - "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384", - "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e", - "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978", - "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57", - "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e", - "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2", - "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48", - "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4" + "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", + "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", + "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", + "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", + "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", + "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", + "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", + "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", + "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", + "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", + "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", + "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", + "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", + "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", + "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", + "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", + "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", + "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", + "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", + "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", + "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", + "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", + "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", + "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", + "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", + "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", + "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", + "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", + "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", + "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", + "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", + "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", + "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", + "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", + "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", + "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", + "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", + "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", + "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", + "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", + "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", + "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", + "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", + "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", + "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", + "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", + "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", + "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", + "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", + "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", + "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", + "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", + "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", + "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", + "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", + "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", + "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", + "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", + "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", + "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", + "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", + "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", + "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", + "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", + "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", + "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", + "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", + "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", + "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", + "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", + "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", + "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc" ], "markers": "python_version >= '3.8'", - "version": "==7.4.4" + "version": "==7.6.1" }, - "exceptiongroup": { + "frozenlist": { "hashes": [ - "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", - "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" + "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", + "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", + "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", + "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", + "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", + "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", + "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", + "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", + "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", + "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", + "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", + "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", + "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", + "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", + "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", + "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", + "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", + "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", + "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", + "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", + "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", + "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", + "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", + "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", + "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", + "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", + "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", + "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", + "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", + "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", + "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", + "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", + "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", + "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", + "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", + "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", + "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", + "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", + "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", + "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", + "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", + "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", + "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", + "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", + "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", + "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", + "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", + "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", + "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", + "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", + "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", + "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", + "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", + "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", + "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", + "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", + "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", + "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", + "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", + "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", + "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", + "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", + "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", + "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", + "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", + "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", + "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", + "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", + "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", + "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", + "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", + "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", + "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", + "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", + "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", + "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", + "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" ], - "markers": "python_version < '3.11'", - "version": "==1.2.2" + "markers": "python_version >= '3.8'", + "version": "==1.4.1" }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "iniconfig": { "hashes": [ @@ -216,6 +328,104 @@ "markers": "python_version >= '3.5'", "version": "==0.7.2" }, + "multidict": { + "hashes": [ + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" + ], + "markers": "python_version >= '3.8'", + "version": "==6.1.0" + }, "packaging": { "hashes": [ "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", @@ -234,12 +444,12 @@ }, "pytest": { "hashes": [ - "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", - "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.3.2" + "version": "==8.3.3" }, "pytest-cov": { "hashes": [ @@ -250,30 +460,103 @@ "markers": "python_version >= '3.8'", "version": "==5.0.0" }, - "requests": { - "hashes": [ - "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", - "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.32.3" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "urllib3": { + "yarl": { "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", + "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", + "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", + "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", + "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", + "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", + "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", + "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", + "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", + "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", + "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", + "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", + "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", + "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", + "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", + "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", + "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", + "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", + "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", + "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", + "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", + "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", + "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", + "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", + "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", + "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", + "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", + "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", + "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", + "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", + "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", + "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", + "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", + "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", + "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", + "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", + "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", + "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", + "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", + "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", + "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", + "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", + "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", + "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", + "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", + "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", + "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", + "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", + "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", + "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", + "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", + "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", + "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", + "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", + "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", + "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", + "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", + "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", + "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", + "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", + "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", + "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", + "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", + "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", + "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", + "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", + "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", + "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", + "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", + "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", + "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", + "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", + "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", + "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", + "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", + "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", + "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", + "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", + "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", + "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", + "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", + "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", + "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", + "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", + "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", + "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", + "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", + "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", + "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", + "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", + "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", + "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" ], "markers": "python_version >= '3.8'", - "version": "==2.2.1" + "version": "==1.11.1" } }, "develop": {} diff --git a/README.md b/README.md index 5683194..7806abc 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ ## Components and Frameworks used +* [aiohttp](https://pypi.org/project/aiohttp/) * [Loguru](https://pypi.org/project/loguru/) -* [Requests ](https://pypi.org/project/requests/) * [urllib3](https://pypi.org/project/urllib3/) ## Installing @@ -41,7 +41,7 @@ from ims_envista import IMSEnvista ims = IMSEnvista("2cc57fb1-cda5-4965-af12-b397e5b8eb32") # Get JERUSALEM stations for getting an id -[station for station in ims.get_all_stations_info() if station.name.startswith("JERUSALEM")] +[station for station in await ims.get_all_stations_info() if station.name.startswith("JERUSALEM")] > [JERUSALEM GIVAT RAM(22) - Location: [Lat - 31.771 / Long - 35.197], Active, Owner: ims, RegionId: 7, Monitors: [ Rain(mm), WSmax(m / sec), WDmax(deg), WS(m / sec), WD(deg), STDwd(deg), TD(degC), RH( %), TDmax(degC), TDmin( degC), Grad(w / m2), DiffR(w / m2), WS1mm(m / sec), Ws10mm(m / sec), Time(hhmm), NIP( @@ -66,7 +66,7 @@ RAM_1m(249) - Location: [Lat - 31.7704 / Long - 35.1973], Active, Owner: ims, Re mm)], StationTarget:] # Get latest data by a station id -ims.get_latest_station_data(23) +await ims.get_latest_station_data(23) > Station(23), Data: [Station: 23, Date: 2023 - 02 - 21 12: 00:00 + 02: 00, Readings: [(TD: 17.6°C), (TDmax: 17.8°C), (TDmin: 17.5°C), (RH: 58.0 %), (Rain: 0.0mm), (WS: 2.8m / s), (WSmax: 3.7m / s), (WD: 285.0deg), (WDmax: 289.0deg), (STDwd: 10.5deg), diff --git a/env/pyvenv.cfg b/env/pyvenv.cfg new file mode 100644 index 0000000..cf5b887 --- /dev/null +++ b/env/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.10.7 diff --git a/ims_envista/__init__.py b/ims_envista/__init__.py index 767cc7d..789b8a0 100644 --- a/ims_envista/__init__.py +++ b/ims_envista/__init__.py @@ -1,2 +1,7 @@ -"""Module providing IMS (Israel Meteorological Service) python API wrapper for Envista.""" +"""Module providing IMS (Israel Meteorological Service) API wrapper for Envista.""" +from .commons import IMSEnvistaError from .ims_envista import IMSEnvista + +__all__ = [ + "IMSEnvista", "IMSEnvistaError", +] diff --git a/ims_envista/commons.py b/ims_envista/commons.py new file mode 100644 index 0000000..db036b8 --- /dev/null +++ b/ims_envista/commons.py @@ -0,0 +1,78 @@ +"""IMS Envista Commons.""" + +import http +import logging +from json import JSONDecodeError +from typing import Any +from uuid import UUID + +from aiohttp import ( + ClientError, + ClientSession, + TraceRequestChunkSentParams, + TraceRequestEndParams, + TraceRequestStartParams, +) + +logger = logging.getLogger(__name__) + +class IMSEnvistaError(Exception): + """ + Exception raised for errors in the IMS Envista API. + + Attributes + ---------- + error -- description of the error + + """ + + def __init__(self, error: str) -> None: + self.error = error + super().__init__(f"{self.error}") + +async def on_request_start_debug(session: ClientSession, context,params: TraceRequestStartParams) -> None: # noqa: ANN001, ARG001 + logger.debug("HTTP %s: %s", params.method, params.url) + + +async def on_request_chunk_sent_debug( + session: ClientSession, context, params: TraceRequestChunkSentParams # noqa: ANN001, ARG001 +) -> None: + if (params.method in ("POST", "PUT")) and params.chunk: + logger.debug("HTTP Content %s: %s", params.method, params.chunk) + + +async def on_request_end_debug(session: ClientSession, context, params: TraceRequestEndParams) -> None: # noqa: ANN001, ARG001 + response_text = await params.response.text() + logger.debug("HTTP %s Response <%s>: %s", params.method, params.response.status, response_text) + + +def get_headers(token: UUID | str) -> dict[str, str]: + return { + "Accept": "application/vnd.github.v3.text-match+json", + "Authorization": f"ApiToken {token!s}" + } + +async def get( + session: ClientSession, url: str, token: UUID | str, headers: dict | None = None +) -> dict[str, Any]: + try: + if not headers: + headers = get_headers(token) + + resp = await session.get(url=url, headers=headers) + json_resp: dict = await resp.json(content_type=None) + except TimeoutError as ex: + msg = f"Failed to communicate with IMS Envista API due to time out: ({ex!s})" + raise IMSEnvistaError(msg) from ex + except ClientError as ex: + msg = f"Failed to communicate with IMS Envistadue to ClientError: ({ex!s})" + raise IMSEnvistaError(msg) from ex + except JSONDecodeError as ex: + msg = f"Received invalid response from IMS Envista API: {ex!s}" + raise IMSEnvistaError(msg) from ex + + if resp.status != http.HTTPStatus.OK: + msg = f"Received Error from IMS Envista API: {resp.status, resp.reason}" + raise IMSEnvistaError(msg) + + return json_resp diff --git a/ims_envista/const.py b/ims_envista/const.py index c25f0db..a35e6bb 100644 --- a/ims_envista/const.py +++ b/ims_envista/const.py @@ -1,4 +1,4 @@ -""" Constant for ims-envista """ +"""Constant for ims-envista.""" from .ims_variable import IMSVariable @@ -9,9 +9,9 @@ GET_ALL_REGIONS_DATA_URL = ENVISTA_REGIONS_URL GET_SPECIFIC_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}" GET_SPECIFIC_REGION_DATA_URL = ENVISTA_REGIONS_URL + "/{}" -GET_LATEST_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data{}/latest" -GET_EARLIEST_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data{}/earliest" -GET_DAILY_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data{}/daily" +GET_LATEST_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data/{}/latest" +GET_EARLIEST_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data/{}/earliest" +GET_DAILY_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data/{}/daily" GET_STATION_DATA_BY_DATE_URL = GET_DAILY_STATION_DATA_URL + "/{}/{}/{}" GET_MONTHLY_STATION_DATA_URL = ENVISTA_STATIONS_URL + "/{}/data{}/monthly" diff --git a/ims_envista/ims_envista.py b/ims_envista/ims_envista.py index 0691ba7..f7ce66e 100644 --- a/ims_envista/ims_envista.py +++ b/ims_envista/ims_envista.py @@ -1,150 +1,141 @@ """Module IMSEnvista getting IMS meteorological readings.""" from __future__ import annotations -import json -from typing import Optional, List -from datetime import date -import requests -from requests.adapters import HTTPAdapter -from requests.exceptions import HTTPError -from loguru import logger -from urllib3 import Retry +import asyncio +import atexit +from typing import TYPE_CHECKING +from aiohttp import ClientSession, TraceConfig + +from .commons import ( + get, + on_request_chunk_sent_debug, + on_request_end_debug, + on_request_start_debug, +) from .const import ( - GET_LATEST_STATION_DATA_URL, - GET_EARLIEST_STATION_DATA_URL, - GET_STATION_DATA_BY_DATE_URL, - GET_SPECIFIC_STATION_DATA_URL, - GET_ALL_STATIONS_DATA_URL, - GET_ALL_REGIONS_DATA_URL, - API_REGION_ID, API_NAME, + API_REGION_ID, API_STATIONS, - GET_SPECIFIC_REGION_DATA_URL, + GET_ALL_REGIONS_DATA_URL, + GET_ALL_STATIONS_DATA_URL, GET_DAILY_STATION_DATA_URL, - GET_MONTHLY_STATION_DATA_URL, + GET_EARLIEST_STATION_DATA_URL, + GET_LATEST_STATION_DATA_URL, GET_MONTHLY_STATION_DATA_BY_MONTH_URL, + GET_MONTHLY_STATION_DATA_URL, + GET_SPECIFIC_REGION_DATA_URL, + GET_SPECIFIC_STATION_DATA_URL, + GET_STATION_DATA_BY_DATE_URL, GET_STATION_DATA_BY_RANGE_URL, - VARIABLES + VARIABLES, ) -from .ims_variable import IMSVariable from .meteo_data import ( - station_meteo_data_from_json, StationMeteorologicalReadings, + station_meteo_data_from_json, ) -from .station_data import StationInfo, station_from_json, region_from_json, RegionInfo +from .station_data import RegionInfo, StationInfo, region_from_json, station_from_json -# ims.gov.il does not support ipv6 yet, `requests` use ipv6 by default -# and wait for timeout before trying ipv4, so we have to disable ipv6 -requests.packages.urllib3.util.connection.HAS_IPV6 = False +if TYPE_CHECKING: + from datetime import date + from uuid import UUID -def create_session(): - session = requests.Session() - retry = Retry(connect=3, backoff_factor=0.5) - adapter = HTTPAdapter(max_retries=retry) - session.mount('http://', adapter) - session.mount('https://', adapter) - return session + from .ims_variable import IMSVariable class IMSEnvista: - """API Wrapper to IMS Envista""" + """API Wrapper to IMS Envista.""" - def __init__(self, token: str): + def __init__(self, token: UUID | str, session: ClientSession | None = None) -> None: if not token: raise ValueError - self.token = token - self.session = create_session() + # Custom Logger to the session + trace_config = TraceConfig() + trace_config.on_request_start.append(on_request_start_debug) + trace_config.on_request_chunk_sent.append(on_request_chunk_sent_debug) + trace_config.on_request_end.append(on_request_end_debug) + trace_config.freeze() + + if not session: + session = ClientSession(trace_configs=[trace_config]) + atexit.register(self._shutdown) + else: + session.trace_configs.append(trace_config) + + self._session = session + self._token = token + + def _shutdown(self) -> None: + if not self._session.closed: + asyncio.run(self._session.close()) @staticmethod def _get_channel_id_url_part(channel_id: int) -> str: - """get specific Channel Id url param""" + """Get specific Channel Id url param.""" if channel_id: return "/" + str(channel_id) return "" - def _get_ims_url(self, url: str) -> Optional[dict]: - """Fetches data from IMS url - - Args: - url (str): IMS Station ID - - Returns: - data: Current station meteorological data - """ - logger.debug(f"Fetching data from: {url}") - try: - response = self.session.get( - url, - headers={ - "Accept": "application/vnd.github.v3.text-match+json", - "Authorization": f"ApiToken {self.token}", - }, - timeout=10, - ) - - # If the response was successful, no Exception will be raised - response.raise_for_status() - return json.loads(response.text) - except HTTPError as http_err: - logger.error(f"HTTP error occurred: {http_err}") # Python 3.6 - return None - except Exception as err: - logger.error(f"Other error occurred: {err}") # Python 3.6 - return None - - def close(self): - """ Close Requests Session """ - self.session.close() - - def get_latest_station_data( - self, station_id: int, channel_id: int = None + async def get_latest_station_data( + self, station_id: int, channel_id: int | None = None ) -> StationMeteorologicalReadings: - """Fetches the latest station data from IMS Envista API + """ + Fetch the latest station data from IMS Envista API. Args: + ---- station_id (int): IMS Station Id channel_id (int): [Optional] Specific Channel Id Returns: + ------- data: Current station meteorological data + """ get_url = GET_LATEST_STATION_DATA_URL.format( str(station_id), self._get_channel_id_url_part(channel_id) ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_earliest_station_data( - self, station_id: int, channel_id: int = None + async def get_earliest_station_data( + self, station_id: int, channel_id: int | None = None ) -> StationMeteorologicalReadings: - """Fetches the earliest station data from IMS Envista API + """ + Fetch the earliest station data from IMS Envista API. Args: + ---- station_id (int): IMS Station ID channel_id (int): [Optional] Specific Channel ID Returns: + ------- data: Current station meteorological data + """ get_url = GET_EARLIEST_STATION_DATA_URL.format( str(station_id), self._get_channel_id_url_part(channel_id) ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_station_data_from_date( - self, station_id: int, date_to_query: date, channel_id: int = None + async def get_station_data_from_date( + self, station_id: int, date_to_query: date, channel_id: int | None = None ) -> StationMeteorologicalReadings: - """Fetches latest station data from IMS Envista API by date + """ + Fetch latest station data from IMS Envista API by date. Args: + ---- station_id (int): IMS Station ID date_to_query (date): Selected date to query channel_id (int): [Optional] Specific Channel Id Returns: + ------- data: Current station meteorological data + """ get_url = GET_STATION_DATA_BY_DATE_URL.format( str(station_id), @@ -153,25 +144,29 @@ def get_station_data_from_date( str(date_to_query.month), str(date_to_query.day), ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_station_data_by_date_range( + async def get_station_data_by_date_range( self, station_id: int, from_date: date, to_date: date, - channel_id: int = None, + channel_id: int | None = None, ) -> StationMeteorologicalReadings: - """Fetches latest station data from IMS Envista API by date range + """ + Fetch latest station data from IMS Envista API by date range. Args: + ---- station_id (int): IMS Station ID from_date (date): From date to query to_date (date): to date to query channel_id (int): [Optional] Specific Channel Id Returns: + ------- data: Current station meteorological data + """ get_url = GET_STATION_DATA_BY_RANGE_URL.format( str(station_id), @@ -183,45 +178,52 @@ def get_station_data_by_date_range( str(to_date.strftime("%m")), str(to_date.strftime("%d")), ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_daily_station_data( - self, station_id: int, channel_id: int = None + async def get_daily_station_data( + self, station_id: int, channel_id: int | None = None ) -> StationMeteorologicalReadings: - """Fetches the daily station data from IMS Envista API + """ + Fetch the daily station data from IMS Envista API. Args: + ---- station_id (int): IMS Station ID channel_id (int): [Optional] Specific Channel Id Returns: + ------- data: Current station meteorological data + """ get_url = GET_DAILY_STATION_DATA_URL.format( str(station_id), self._get_channel_id_url_part(channel_id), ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_monthly_station_data( + async def get_monthly_station_data( self, station_id: int, - channel_id: int = None, - month: str = None, - year: str = None, + channel_id: int | None = None, + month: str | None = None, + year: str | None = None, ) -> StationMeteorologicalReadings: - """Fetches monthly station data from IMS Envista API + """ + Fetch monthly station data from IMS Envista API. Args: + ---- station_id (int): IMS Station ID channel_id (int): [Optional] Specific Channel Id month (str): [Optional] Specific Month in MM format (07) year (str): [Optional] Specific Year in YYYY format (2020) Returns: + ------- data: Current station meteorological data - """ + """ if not month or not year: get_url = GET_MONTHLY_STATION_DATA_URL.format( str(station_id), self._get_channel_id_url_part(channel_id) @@ -230,69 +232,80 @@ def get_monthly_station_data( get_url = GET_MONTHLY_STATION_DATA_BY_MONTH_URL.format( str(station_id), self._get_channel_id_url_part(channel_id), year, month ) - return station_meteo_data_from_json(self._get_ims_url(get_url)) + return station_meteo_data_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_all_stations_info(self) -> List[StationInfo]: - """Fetches all stations data from IMS Envista API + async def get_all_stations_info(self) -> list[StationInfo]: + """ + Fetch all stations data from IMS Envista API. - Returns: + Returns + ------- data: All stations data + """ get_url = GET_ALL_STATIONS_DATA_URL - response = self._get_ims_url(get_url) - stations = [] - for station in response: - stations.append(station_from_json(station)) - return stations + response = await get(session=self._session, url=get_url, token=self._token) + return [station_from_json(station) for station in response] - def get_station_info(self, station_id: int) -> StationInfo: - """Fetches station data from IMS Envista API + async def get_station_info(self, station_id: int) -> StationInfo: + """ + Fetch station data from IMS Envista API. Args: + ---- station_id (int): IMS Station ID Returns: + ------- data: Current station data + """ get_url = GET_SPECIFIC_STATION_DATA_URL.format(str(station_id)) - return station_from_json(self._get_ims_url(get_url)) + return station_from_json(await get(session=self._session, url=get_url, token=self._token)) - def get_all_regions_info(self) -> List[RegionInfo]: - """Fetches all regions data from IMS Envista API + async def get_all_regions_info(self) -> list[RegionInfo]: + """ + Fetch all regions data from IMS Envista API. - Returns: + Returns + ------- data: All stations data + """ get_url = GET_ALL_REGIONS_DATA_URL - response = self._get_ims_url(get_url) + response = await get(session=self._session, url=get_url, token=self._token) regions = [] for region in response: - stations = [] - for station in region[API_STATIONS]: - stations.append(station_from_json(station)) - + stations = [station_from_json(station) for station in region[API_STATIONS]] regions.append( RegionInfo(region[API_REGION_ID], region[API_NAME], stations) ) return regions - def get_region_info(self, region_id: int) -> RegionInfo: - """Fetches region data from IMS Envista API + async def get_region_info(self, region_id: int) -> RegionInfo: + """ + Fetch region data from IMS Envista API. Args: + ---- region_id (int): IMS Region ID Returns: + ------- data: region data + """ get_url = GET_SPECIFIC_REGION_DATA_URL.format(str(region_id)) - response = self._get_ims_url(get_url) + response = await get(session=self._session, url=get_url, token=self._token) return region_from_json(response) - def get_metrics_descriptions(self) -> List[IMSVariable]: - """Returns the descriptions of Meteorological Metrics collected by the stations. + def get_metrics_descriptions(self) -> list[IMSVariable]: + """ + Return the descriptions of Meteorological Metrics collected by the stations. - Returns: + Returns + ------- list of IMSVariable, containing description and measuring unit + """ return list(VARIABLES.values()) diff --git a/ims_envista/ims_variable.py b/ims_envista/ims_variable.py index 9d6fc49..e6b31e1 100644 --- a/ims_envista/ims_variable.py +++ b/ims_envista/ims_variable.py @@ -1,10 +1,14 @@ -""" Data Class for IMS Variable """ +"""Data Class for IMS Variable.""" from __future__ import annotations + from dataclasses import dataclass + @dataclass class IMSVariable: + """IMS Envista Variable.""" + variable_code: str unit: str description: str diff --git a/ims_envista/meteo_data.py b/ims_envista/meteo_data.py index 48868b4..5b29b77 100644 --- a/ims_envista/meteo_data.py +++ b/ims_envista/meteo_data.py @@ -1,39 +1,40 @@ -""" Data Class for IMS Meteorological Readings """ +"""Data Class for IMS Meteorological Readings.""" from __future__ import annotations + import textwrap -from typing import List from dataclasses import dataclass, field from datetime import datetime - from .const import ( + API_CHANNELS, + API_DATA, + API_DATETIME, + API_NAME, API_RAIN, - API_WS_MAX, - API_WD_MAX, - API_WS, - API_WD, + API_RH, + API_STATION_ID, + API_STATUS, API_STD_WD, API_TD, - API_RH, API_TD_MAX, API_TD_MIN, + API_VALID, + API_VALUE, + API_WD, + API_WD_MAX, + API_WS, API_WS_1MM, API_WS_10MM, + API_WS_MAX, VARIABLES, - API_DATETIME, - API_CHANNELS, - API_VALID, - API_STATUS, - API_NAME, - API_VALUE, - API_STATION_ID, - API_DATA, ) + @dataclass class MeteorologicalData: - """Meteorological Data""" + """Meteorological Data.""" + station_id: int """Station ID""" datetime: datetime @@ -62,7 +63,7 @@ class MeteorologicalData: """Maximum 1 minute wind speed in m/s""" ws_10mm: float """Maximum 10 minute wind speed in m/s""" - + def _pretty_print(self) -> str: return textwrap.dedent( @@ -106,9 +107,11 @@ def __repr__(self) -> str: @dataclass class StationMeteorologicalReadings: + """Station Meteorological Readings.""" + station_id: int """ Station Id""" - data: List['MeteorologicalData'] = field(default_factory=list) + data: list[MeteorologicalData] = field(default_factory=list) """ List of Meteorological Data """ def __repr__(self) -> str: @@ -118,7 +121,7 @@ def __repr__(self) -> str: def meteo_data_from_json(station_id: int, data: dict) -> MeteorologicalData: - """Create a MeteorologicalData object from a JSON object""" + """Create a MeteorologicalData object from a JSON object.""" dt = datetime.fromisoformat(data[API_DATETIME]) channel_value_dict = {} for channel_value in data[API_CHANNELS]: @@ -160,8 +163,8 @@ def meteo_data_from_json(station_id: int, data: dict) -> MeteorologicalData: def station_meteo_data_from_json(json: dict) -> StationMeteorologicalReadings: station_id = int(json[API_STATION_ID]) - data = json[API_DATA] - meteo_data = [] - for single_meteo_data in data: - meteo_data.append(meteo_data_from_json(station_id, single_meteo_data)) + data = json.get(API_DATA) + if not data: + return None + meteo_data = [meteo_data_from_json(station_id, single_meteo_data) for single_meteo_data in data] return StationMeteorologicalReadings(station_id, meteo_data) diff --git a/ims_envista/station_data.py b/ims_envista/station_data.py index 95af766..dbcd3b8 100644 --- a/ims_envista/station_data.py +++ b/ims_envista/station_data.py @@ -1,32 +1,34 @@ -""" Data Class for Station Data """ +"""Data Class for Station Data.""" from __future__ import annotations + import textwrap -from typing import List from dataclasses import dataclass, field @dataclass class Location: - """Location (Lat/Long)""" + """Location (Lat/Long).""" + latitude: float """Latitude""" longitude: float """Longitude""" - def __repr__(self): + def __repr__(self) -> str: return textwrap.dedent("""[Lat-{}/Long-{}]""").format( self.latitude, self.longitude ) def location_from_json(json: dict) -> Location: - """Converts a JSON object to a Location object.""" + """Convert a JSON object to a Location object.""" return Location(json["latitude"], json["longitude"]) @dataclass class Monitor: - """Monitor""" + """Monitor.""" + channel_id: int """Channel ID""" name: str @@ -43,12 +45,12 @@ class Monitor: """Monitored Condition Units""" description: str """Monitored Condition Description""" - def __repr__(self): + def __repr__(self) -> str: return textwrap.dedent("""{}({})""").format(self.name, self.units) def monitor_from_json(json: dict) -> Monitor: - """Converts a JSON object to a Monitor object.""" + """Convert a JSON object to a Monitor object.""" return Monitor( json["channelId"], json["name"], @@ -62,7 +64,8 @@ def monitor_from_json(json: dict) -> Monitor: @dataclass class StationInfo: - """Station Information""" + """Station Information.""" + station_id: int """Station ID""" name: str @@ -83,10 +86,10 @@ class StationInfo: """Region ID""" station_target: str """Station Target""" - monitors: List['Monitor'] = field(default_factory=list) + monitors: list[Monitor] = field(default_factory=list) """List of Monitored Conditions""" - def __repr__(self): + def __repr__(self) -> str: return textwrap.dedent( """{} ({}) - Location: {}, {}ctive, Owner: {}, RegionId: {}, Monitors: {}, StationTarget: {}""" ).format( @@ -102,7 +105,7 @@ def __repr__(self): def station_from_json(json: dict) -> StationInfo: - """Converts a JSON object to a Station object.""" + """Convert a JSON object to a Station object.""" return StationInfo( json["stationId"], json["name"], @@ -119,22 +122,23 @@ def station_from_json(json: dict) -> StationInfo: @dataclass class RegionInfo: - """Region Information""" + """Region Information.""" + region_id: int """Region ID""" name: str """Region Name""" - stations: List['StationInfo'] = field(default_factory=list) + stations: list[StationInfo] = field(default_factory=list) """List of Stations in the Region""" - def __repr__(self): + def __repr__(self) -> str: return textwrap.dedent("""{}({}), Stations: {}""").format( self.name, self.region_id, self.stations ) def region_from_json(json: dict) -> RegionInfo: - """Converts a JSON object to a Region object.""" + """Convert a JSON object to a Region object.""" return RegionInfo( json["regionId"], json["name"], diff --git a/ims_envista/version.py b/ims_envista/version.py index b3a5bb8..adf891f 100644 --- a/ims_envista/version.py +++ b/ims_envista/version.py @@ -1,11 +1,12 @@ -"""Version""" -class Version(object): - """Version of the package""" +"""Version.""" +class Version: + """Version of the package.""" - def __setattr__(self, *args): - raise TypeError("can't modify immutable instance") + def __setattr__(self, *args: dict) -> None: + msg = "can't modify immutable instance" + raise TypeError(msg) __delattr__ = __setattr__ - def __init__(self, num): - super(Version, self).__setattr__("number", num) + def __init__(self, num: str) -> None: + super().__setattr__("number", num) diff --git a/requirements.txt b/requirements.txt index 5ff6f6b..8790885 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ pytest pytest-cov -requests +pytest-asyncio loguru +aiohttp>=3.10.5 urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/setup.py b/setup.py index 8eae2f8..da53d95 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,24 @@ -import setuptools -from ims_envista.version import Version +import setuptools # noqa: D100 -setuptools.setup(name='ims_envista', - version='0.0.0', +setuptools.setup(name="ims_envista", + version="0.0.0", long_description_content_type="text/markdown", - description='Israel Meteorological Service Envista API wrapper package', - long_description=open('README.md').read().strip(), - author='Guy Khmelnitsky', - author_email='guykhmel@gmail.com', - url='https://github.com/GuyKh/py-ims-envista', + description="Israel Meteorological Service Envista API wrapper package", + long_description="Israel Meteorological Service Envista API wrapper package", + author="Guy Khmelnitsky", + author_email="guykhmel@gmail.com", + url="https://github.com/GuyKh/py-ims-envista", packages=setuptools.find_packages(), - python_requires=">=3.6", - install_requires=["requests","urllib3","loguru"], - license='MIT License', + python_requires=">=3.10", + install_requires=["urllib3","loguru", "aiohttp"], + license="MIT License", zip_safe=False, - keywords=['ims','weatheril','Israel Meteorological Service','Meteorological Service','weather'], + keywords=["ims","weatheril","Israel Meteorological Service","Meteorological Service","weather"], classifiers=[ "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Natural Language :: English", - "Operating System :: OS Independent",]) + "Operating System :: OS Independent"]) diff --git a/tests/__init__.py b/tests/__init__.py index e3c9e9c..7e4dde5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,6 @@ +import unittest # noqa: D104 + import pytest -import unittest __all__ = [ "pytest", diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index e69de29..d4669fa 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ + # noqa: D104 diff --git a/tests/unit/test_ims_envista.py b/tests/unit/test_ims_envista.py index ea06786..2ddc364 100644 --- a/tests/unit/test_ims_envista.py +++ b/tests/unit/test_ims_envista.py @@ -1,199 +1,223 @@ -from datetime import date, datetime, timedelta +"""Test IMS Envista API.""" + import os -from ims_envista import IMSEnvista +import unittest +from datetime import date, datetime, timedelta +from zoneinfo import ZoneInfo -from tests import * +from aiohttp import ClientSession + +from ims_envista import IMSEnvista def to_date_time(d: date) -> datetime: - """Convert date to datetime""" + """Convert date to datetime.""" return datetime(d.year, d.month, d.day).astimezone() -class TestIMSEnvista(unittest.TestCase): - def setUp(self): - """Setup""" - # Load test data - self.ims = IMSEnvista(os.environ.get("IMS_TOKEN")) +class TestIMSEnvista(unittest.IsolatedAsyncioTestCase): + """Test IMS Envista API.""" + + async def asyncSetUp(self) -> None: + """Do Setup.""" + self.token = os.environ.get("IMS_TOKEN") self.station_id = 178 # TEL AVIV COAST station self.region_id = 13 self.channel_id = 7 # TD = Temperature Channel - def tearDown(self): - """Tear Down""" - self.ims.close() + # Initialize the session in an async context + self.session = ClientSession() + self.ims = IMSEnvista(self.token, session=self.session) + + async def asyncTearDown(self) -> None: + """Tear Down.""" + await self.session.close() + + + async def test_get_all_regions_info(self) -> None: + """Test get_all_regions_info endpoint.""" + regions = await self.ims.get_all_regions_info() + + assert regions is not None + assert len(regions) > 0 + + + async def test_get_region_info(self) -> None: + """Test get_regions_info endpoint.""" + region = await self.ims.get_region_info(self.region_id) + + assert region is not None + assert region.region_id == self.region_id - def test_get_all_regions_info(self): - """Test get_all_regions_info endpoint""" - regions = self.ims.get_all_regions_info() - self.assertIsNotNone(regions) - self.assertGreater(len(regions), 0) + async def test_get_all_stations_info(self) -> None: + """Test get_all_stations_info endpoint.""" + stations = await self.ims.get_all_stations_info() - def test_get_region_info(self): - """Test get_regions_info endpoint""" - region = self.ims.get_region_info(self.region_id) + assert stations is not None + assert len(stations) > 0 - self.assertIsNotNone(region) - self.assertEqual(region.region_id, self.region_id) - def test_get_all_stations_info(self): - """Test get_all_stations_info endpoint""" - stations = self.ims.get_all_stations_info() + async def test_get_station_info(self) -> None: + """Test get_region_info endpoint.""" + station = await self.ims.get_station_info(self.station_id) - self.assertIsNotNone(stations) - self.assertGreater(len(stations), 0) + assert station is not None + assert station.station_id == self.station_id - def test_get_station_info(self): - """Test get_region_info endpoint""" - station = self.ims.get_station_info(self.station_id) - self.assertIsNotNone(station) - self.assertEqual(station.station_id, self.station_id) + async def test_get_latest_station_data(self) -> None: + """Test get_latest_station endpoint.""" + station_data = await self.ims.get_latest_station_data(self.station_id) - def test_get_latest_station_data(self): - """Test get_latest_station endpoint""" - station_data = self.ims.get_latest_station_data(self.station_id) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 + assert station_data.data[0].td > 0 - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) - self.assertGreater(station_data.data[0].td, 0) - def test_get_latest_station_data_with_channel(self): - """Test get_latest_station_data endpoint with channel""" - station_data = self.ims.get_latest_station_data( + async def test_get_latest_station_data_with_channel(self) -> None: + """Test get_latest_station_data endpoint with channel.""" + station_data = await self.ims.get_latest_station_data( self.station_id, self.channel_id ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) - self.assertGreater(station_data.data[0].td, 0) - - def test_get_earliest_station_data(self): - """Test get_earliest_station_data endpoint""" - station_data = self.ims.get_earliest_station_data(self.station_id) - - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) - self.assertGreater(station_data.data[0].td, 0) - - def test_get_earliest_station_data_with_channel(self): - """Test get_earliest_station_data endpoint with channel""" - station_data = self.ims.get_earliest_station_data( + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 + assert station_data.data[0].td > 0 + + + async def test_get_earliest_station_data(self) -> None: + """Test get_earliest_station_data endpoint.""" + station_data = await self.ims.get_earliest_station_data(self.station_id) + + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 + assert station_data.data[0].td > 0 + + + async def test_get_earliest_station_data_with_channel(self) -> None: + """Test get_earliest_station_data endpoint with channel.""" + station_data = await self.ims.get_earliest_station_data( self.station_id, self.channel_id ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) - self.assertGreater(station_data.data[0].td, 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 + assert station_data.data[0].td > 0 + - def test_get_station_data_from_date(self): - """Test get_station_data_from_date endpoint""" - station_data = self.ims.get_station_data_from_date( - self.station_id, date.today() + async def test_get_station_data_from_date(self) -> None: + """Test get_station_data_from_date endpoint.""" + station_data = await self.ims.get_station_data_from_date( + self.station_id, datetime.now(tz=ZoneInfo("Asia/Jerusalem")) ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertEqual(station_reading.datetime.date(), date.today()) + assert station_reading.datetime.date() == datetime.now(tz=ZoneInfo("Asia/Jerusalem")).date() + - def test_get_station_data_from_date_with_channel(self): - """Test get_station_data_from_date endpoint with channel""" - station_data = self.ims.get_station_data_from_date( - self.station_id, date.today(), self.channel_id + async def test_get_station_data_from_date_with_channel(self) -> None: + """Test get_station_data_from_date endpoint with channel.""" + station_data = await self.ims.get_station_data_from_date( + self.station_id, datetime.now(tz=ZoneInfo("Asia/Jerusalem")), self.channel_id ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertEqual(station_reading.datetime.date(), date.today()) + assert station_reading.datetime.date() == datetime.now(tz=ZoneInfo("Asia/Jerusalem")).date() - def test_get_station_data_by_date_range(self): - """Test get_station_data_by_date_range endpoint""" - today = date.today() + + async def test_get_station_data_by_date_range(self) -> None: + """Test get_station_data_by_date_range endpoint.""" + today = datetime.now(tz=ZoneInfo("Asia/Jerusalem")) yesterday = today - timedelta(days=1) - station_data = self.ims.get_station_data_by_date_range( + station_data = await self.ims.get_station_data_by_date_range( self.station_id, from_date=yesterday, to_date=today ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertGreaterEqual(station_reading.datetime, to_date_time(yesterday)) - self.assertLess(station_reading.datetime, to_date_time(today)) - self.assertGreater(station_reading.td, 0) + assert station_reading.datetime >= to_date_time(yesterday) + assert station_reading.datetime < to_date_time(today) + assert station_reading.td > 0 + - def test_get_station_data_by_date_range_with_channel(self): - """Test get_station_data_by_date_range endpoint with channel""" - today = date.today() + async def test_get_station_data_by_date_range_with_channel(self) -> None: + """Test get_station_data_by_date_range endpoint with channel.""" + today = datetime.now(tz=ZoneInfo("Asia/Jerusalem")) yesterday = today - timedelta(days=1) - station_data = self.ims.get_station_data_by_date_range( + station_data = await self.ims.get_station_data_by_date_range( self.station_id, from_date=yesterday, to_date=today, channel_id=self.channel_id, ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertGreaterEqual(station_reading.datetime, to_date_time(yesterday)) - self.assertLess(station_reading.datetime, to_date_time(today)) - self.assertGreater(station_reading.td, 0) - - def test_get_monthly_station_data(self): - """Test get_monthly_station_data endpoint""" - year = date.today().strftime("%Y") - month = date.today().strftime("%m") - station_data = self.ims.get_monthly_station_data( + assert station_reading.datetime >= to_date_time(yesterday) + assert station_reading.datetime < to_date_time(today) + assert station_reading.td > 0 + + + async def test_get_monthly_station_data(self) -> None: + """Test get_monthly_station_data endpoint.""" + year = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%Y") + month = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%m") + station_data = await self.ims.get_monthly_station_data( self.station_id, month=month, year=year ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertEqual(station_reading.datetime.date().strftime("%Y"), year) - self.assertEqual(station_reading.datetime.date().strftime("%m"), month) - self.assertGreater(station_reading.td, 0) - - def test_get_monthly_station_data_with_channel(self): - """Test get_monthly_station_data endpoint with channel""" - year = date.today().strftime("%Y") - month = date.today().strftime("%m") - station_data = self.ims.get_monthly_station_data( + assert station_reading.datetime.date().strftime("%Y") == year + assert station_reading.datetime.date().strftime("%m") == month + assert station_reading.td > 0 + + + async def test_get_monthly_station_data_with_channel(self) -> None: + """Test get_monthly_station_data endpoint with channel.""" + year = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%Y") + month = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%m") + station_data = await self.ims.get_monthly_station_data( self.station_id, channel_id=self.channel_id, month=month, year=year ) - self.assertIsNotNone(station_data) - self.assertEqual(station_data.station_id, self.station_id) - self.assertIsNotNone(station_data.data) - self.assertGreater(len(station_data.data), 0) + assert station_data is not None + assert station_data.station_id == self.station_id + assert station_data.data is not None + assert len(station_data.data) > 0 for station_reading in station_data.data: - self.assertEqual(station_reading.datetime.date().strftime("%m"), month) - self.assertEqual(station_reading.datetime.date().strftime("%Y"), year) - self.assertGreater(station_reading.td, 0) + assert station_reading.datetime.date().strftime("%m") == month + assert station_reading.datetime.date().strftime("%Y") == year + assert station_reading.td > 0 - def test_get_metrics_descriptions(self): + def test_get_metrics_descriptions(self) -> None: metrics = self.ims.get_metrics_descriptions() - self.assertIsNotNone(metrics) - self.assertGreater(len(metrics), 0) + assert metrics is not None + assert len(metrics) > 0 diff --git a/tests/unit/test_version.py b/tests/unit/test_version.py index f62c8aa..7fe68b9 100644 --- a/tests/unit/test_version.py +++ b/tests/unit/test_version.py @@ -1,13 +1,21 @@ +"""Test Version.""" +import unittest + +import pytest + from ims_envista.version import Version -from tests import * class TestVersion(unittest.TestCase): - def test_set_version(self): + """Test Version.""" + + def test_set_version(self) -> None: ver = Version("1.0.0") - self.assertEqual(ver.number, "1.0.0") + if ver.number != "1.0.0": + msg = "Expected Version 1.0.0" + raise ValueError(msg) - def test_version_immutable(self): + def test_version_immutable(self) -> None: ver = Version("1.0.0") - with pytest.raises(TypeError) as e: + with pytest.raises(TypeError): ver.number = "1.1.0"