diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml index 4adf813..7715205 100644 --- a/.github/workflows/pypi_publish.yml +++ b/.github/workflows/pypi_publish.yml @@ -12,14 +12,14 @@ jobs: - name: Build and publish to pypi (3.10) uses: JRubics/poetry-publish@v1.10 with: - python_version: "3.10.2" + python_version: "3.10.4" ignore_dev_requirements: "yes" pypi_token: ${{ secrets.PYPI_TOKEN }} extra_build_dependency_packages: "capnproto libzmq3-dev" - name: Build and publish to pypi (3.9) uses: JRubics/poetry-publish@v1.10 with: - python_version: "3.9.10" + python_version: "3.9.13" build_format: wheel ignore_dev_requirements: "yes" pypi_token: ${{ secrets.PYPI_TOKEN }} @@ -27,7 +27,7 @@ jobs: - name: Build and publish to pypi (3.8) uses: JRubics/poetry-publish@v1.10 with: - python_version: "3.8.12" + python_version: "3.8.13" build_format: wheel ignore_dev_requirements: "yes" pypi_token: ${{ secrets.PYPI_TOKEN }} diff --git a/Dockerfile b/Dockerfile index dfe83e6..73b5823 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/pypy:3.8-bullseye AS compile +FROM docker.io/python:3.10-bullseye AS compile ENV PYTHONFAULTHANDLER=1 \ PYTHONHASHSEED=random \ @@ -27,7 +27,7 @@ RUN pip install --upgrade pip \ && poetry config virtualenvs.in-project true \ && poetry install --no-root --no-dev --no-interaction --no-ansi -FROM docker.io/pypy:3.8-slim-bullseye AS app +FROM docker.io/python:3.10-slim-bullseye AS app ENV PYTHONFAULTHANDLER=1 \ PYTHONHASHSEED=random \ diff --git a/examples/memory_profile/long_running_zmq.py b/examples/memory_profile/long_running_zmq.py new file mode 100644 index 0000000..7d6e31f --- /dev/null +++ b/examples/memory_profile/long_running_zmq.py @@ -0,0 +1,107 @@ +import asyncio +import linecache +import logging +import os + +try: + import tracemalloc +except ModuleNotFoundError: + tracemalloc = False +import uuid +from random import randint +from platform import python_version as pv, python_implementation as pi +from timeit import default_timer as timer + +import zmq +import zmq.asyncio + +from podping_hivewriter.constants import LIVETEST_OPERATION_ID +from podping_hivewriter.models.medium import Medium +from podping_hivewriter.models.reason import Reason +from podping_hivewriter.podping_hivewriter import PodpingHivewriter +from podping_hivewriter.podping_settings_manager import PodpingSettingsManager + + +def display_top(snapshot, key_type="lineno", limit=3): + snapshot = snapshot.filter_traces( + ( + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + ) + ) + top_stats = snapshot.statistics(key_type) + + logging.info("Top %s lines" % limit) + for index, stat in enumerate(top_stats[:limit], 1): + frame = stat.traceback[0] + # replace "/path/to/module/file.py" with "module/file.py" + filename = os.sep.join(frame.filename.split(os.sep)[-2:]) + logging.info( + "#%s: %s:%s: %.1f KiB" % (index, filename, frame.lineno, stat.size / 1024) + ) + line = linecache.getline(frame.filename, frame.lineno).strip() + if line: + logging.info(" %s" % line) + + other = top_stats[limit:] + if other: + size = sum(stat.size for stat in other) + logging.info("%s other: %.1f KiB" % (len(other), size / 1024)) + total = sum(stat.size for stat in top_stats) + logging.info("Total allocated size: %.1f KiB" % (total / 1024)) + + +async def endless_send_loop(event_loop): + context = zmq.asyncio.Context() + socket = context.socket(zmq.REQ, io_loop=event_loop) + socket.connect(f"tcp://{host}:{port}") + + test_name = "long_running_zmq" + python_version = pv() + python_implementation = pi() + start_time = timer() + + while True: + session_uuid = uuid.uuid4() + session_uuid_str = str(session_uuid) + + num_iris = randint(1, 10) + + for i in range(num_iris): + await socket.send_string( + f"https://example.com?t={test_name}&i={i}&v={python_version}&pi={python_implementation}&s={session_uuid_str}" + ) + response = await socket.recv_string() + assert response == "OK" + + if tracemalloc and (timer() - start_time) >= 60: + snapshot = tracemalloc.take_snapshot() + display_top(snapshot) + start_time = timer() + await asyncio.sleep(3) + + +if __name__ == "__main__": + if tracemalloc: + tracemalloc.start() + loop = asyncio.get_event_loop() + logging.getLogger().setLevel(level=logging.INFO) + settings_manager = PodpingSettingsManager() + + host = "127.0.0.1" + port = 9979 + podping_hivewriter = PodpingHivewriter( + os.environ["PODPING_HIVE_ACCOUNT"], + [os.environ["PODPING_HIVE_POSTING_KEY"]], + settings_manager, + medium=Medium.podcast, + reason=Reason.update, + listen_ip=host, + listen_port=port, + resource_test=True, + operation_id=LIVETEST_OPERATION_ID, + ) + loop.run_until_complete(podping_hivewriter.wait_startup()) + loop.run_until_complete(endless_send_loop(loop)) + + podping_hivewriter.close() diff --git a/install-packages.sh b/install-packages.sh index 35d9f91..b6f2460 100755 --- a/install-packages.sh +++ b/install-packages.sh @@ -17,7 +17,7 @@ apt-get update apt-get -y upgrade # Install application dependencies -apt-get -y install --no-install-recommends capnproto libffi7 libssl1.1 libzmq5 zlib1g +apt-get -y install --no-install-recommends capnproto libffi7 libssl1.1 libzmq5 zlib1g gcc libstdc++-10-dev # Delete cached files we don't need anymore (note that if you're # using official Docker images for Debian or Ubuntu, this happens diff --git a/poetry.lock b/poetry.lock index 8d08fde..1dd3ea9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,7 +28,7 @@ tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false @@ -36,17 +36,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.4.0" +version = "22.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "backoff" @@ -125,7 +125,7 @@ python-versions = ">=3.6" [[package]] name = "cffi" -version = "1.15.0" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -136,11 +136,11 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] @@ -163,7 +163,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.1" +version = "6.4.3" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -177,7 +177,7 @@ toml = ["tomli"] [[package]] name = "cython" -version = "0.29.30" +version = "0.29.32" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false @@ -205,7 +205,7 @@ python-versions = "*" [[package]] name = "ecdsa" -version = "0.17.0" +version = "0.18.0" description = "ECDSA cryptographic signature library (pure python)" category = "main" optional = false @@ -392,6 +392,17 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "patch-env" +version = "1.0.0" +description = "Patch os.environ with dynamic values when the interpreter starts" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["check-manifest"] + [[package]] name = "pathspec" version = "0.9.0" @@ -551,6 +562,20 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.8.2" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "tox", "pytest-asyncio"] + [[package]] name = "pytest-timeout" version = "2.1.0" @@ -603,7 +628,7 @@ py = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "requests" -version = "2.28.0" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -611,13 +636,13 @@ python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2.0.0,<2.1.0" +charset-normalizer = ">=2,<3" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rfc3986" @@ -643,11 +668,11 @@ python-versions = "*" [[package]] name = "shellingham" -version = "1.4.0" +version = "1.5.0" description = "Tool to Detect Surrounding Shell" category = "main" optional = false -python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" +python-versions = ">=3.4" [[package]] name = "single-source" @@ -683,11 +708,11 @@ python-versions = ">=3.5" [[package]] name = "stevedore" -version = "3.5.0" +version = "4.0.0" description = "Manage dynamic plugins for Python applications" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" [package.dependencies] pbr = ">=2.0.0,<2.1.0 || >2.1.0" @@ -734,7 +759,7 @@ typer = ">=0.3.0,<0.4.0" [[package]] name = "typing-extensions" -version = "4.2.0" +version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -742,11 +767,11 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.11" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] @@ -759,7 +784,7 @@ server = ["pyzmq"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "1c7c6b32d5d981d1d9ff3544437f81f519680c700c9bb9f26e011dc30f5e19f7" +content-hash = "fd2f37d4193c47c118356e52a310dd58f9e637edc5b02c5d7a111cfcc8cc21c5" [metadata.files] anyio = [ @@ -770,14 +795,8 @@ asgiref = [ {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] +atomicwrites = [] +attrs = [] backoff = [ {file = "backoff-2.1.2-py3-none-any.whl", hash = "sha256:b135e6d7c7513ba2bfd6895bc32bc8c66c6f3b0279b4c6cd866053cfd7d3126b"}, {file = "backoff-2.1.2.tar.gz", hash = "sha256:407f1bc0f22723648a8880821b935ce5df8475cf04f7b6b5017ae264d30f6069"}, @@ -802,62 +821,8 @@ certifi = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, ] -cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, -] +cffi = [] +charset-normalizer = [] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -866,91 +831,8 @@ colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -coverage = [ - {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, - {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, - {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, - {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, - {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, - {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, - {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, - {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, - {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, - {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, - {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, - {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, - {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, -] -cython = [ - {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5cb144728a335d7a7fd0a61dff6abb7a9aeff9acd46d50b886b7d9a95bb7311"}, - {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d52d5733dcb144deca8985f0a197c19cf71e6bd6bd9d8034f3f67b2dea68d12b"}, - {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0cd6c932e945af15ae4ddcf8fdc0532bda48784c92ed0a53cf4fae897067ccd1"}, - {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a30092c6e2d24255fbfe0525f9a750554f96a263ed986d12ac3c9f7d9a85a424"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:abcaf99f90cddc0f53600613eaafc81d27c4ac0671f0df8bce5466d4e86d54a1"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9826981308802c61a76f967875b31b7c683b7fc369eabaa6cbc22efeb12c90e8"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d166d9f853db436f5e10733a9bd615699ddb4238feadcbdf5ae50dc0b18b18f5"}, - {file = "Cython-0.29.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0b83a342a071c4f14e7410568e0c0bd95e2f20c0b32944e3a721649a1357fda4"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ffa8c09617833ff0824aa7926fa4fa9d2ec3929c67168e89105f276b7f36a63e"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6b389a94b42909ff56d3491fde7c44802053a103701a7d210dcdd449a5b4f7b4"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7eff71c39b98078deaad1d1bdbf10864d234e2ab5d5257e980a6926a8523f697"}, - {file = "Cython-0.29.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8e08f18d249b9b65e272a5a60f3360a8922c4c149036b98fc821fe1afad5bdae"}, - {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3993aafd68a7311ef94e00e44a137f6a50a69af0575ebcc8a0a074ad4152a2b2"}, - {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5c7cfd908efc77306ddd41ef07f5a7a352c9205ced5c1e00a0e5ece4391707c4"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e605635a92ae862cb46d84d1d6883324518f9aaff4a71cede6d61df20b6a410c"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:786ee7b0cdb508b6de64c0f1f9c74f207186dfafad1ef938f25b7494cc481a80"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1e078943bbde703ca08d43e719480eb8b187d9023cbd91798619f5b5e18d0d71"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5183356c756b56c2df12d96300d602e47ffb89943c5a0bded66faca5d3da7be0"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e36755e71fd20eceb410cc441b7f2586654c2edb013f4663842fdaf60b96c1ca"}, - {file = "Cython-0.29.30-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e29d3487f357108b711f2f29319811d92166643d29aec1b8e063aad46a346775"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5a8a3709ad9343a1dc02b8ec9cf6bb284be248d2c64af85464d9c3525eec74a5"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b17639b6a155abaa61a89f6f1323fb57b138d0529911ca03978d594945d062ba"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9462e9cf284d9b1d2c5b53d62188e3c09cc5c7a0018ba349d99b73cf930238de"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:58d2b734250c1093bc69c1c3a6f5736493b9f8b34eb765f0a28a4a09468c0b00"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28db751e2d8365b39664d9cb62dc1668688b8fcc5b954e9ca9d20e0b8e03d8b0"}, - {file = "Cython-0.29.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f2dae7dd56860018d5fd5032a71f11fdc224020932b463d0511a1536f27df85"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d0859a958e0155b6ae4dee04170ccfac2c3d613a7e3bee8749614530b9e3b4a4"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d0f34b44078e3e0b2f1be2b99044619b37127128e7d55c54bbd2438adcaf31d3"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:80a7255ad84620f53235c0720cdee2bc7431d9e3db7b3742823a606c329eb539"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0239c7a22a0f3fb1deec75cab0078eba4dd17868aa992a54a178851e0c8684"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c299c5b250ae9f81c38200441b6f1d023aeee9d8e7f61c04001c7437181ccb06"}, - {file = "Cython-0.29.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:019d330ac580b2ca4a457c464ac0b8c35009d820ef5d09f328d6e31a10e1ce89"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71fd1d910aced510c001936667fc7f2901c49b2ca7a2ad67358979c94a7f42ac"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:60d370c33d56077d30e5f425026e58c2559e93b4784106f61581cf54071f6270"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:20778297c8bcba201ca122a2f792a9899d6e64c68a92363dd7eb24306d54d7ce"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f1fe924c920b699af27aefebd722df4cfbb85206291623cd37d1a7ddfd57792"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c79685dd4631a188e2385dc6a232896c7b67ea2e3e5f8b5555b4b743f475d6d7"}, - {file = "Cython-0.29.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88c5e2f92f16cd999ddfc43d572639679e8a057587088e627e98118e46a803e6"}, - {file = "Cython-0.29.30-py2.py3-none-any.whl", hash = "sha256:acb72e0b42079862cf2f894964b41f261e941e75677e902c5f4304b3eb00af33"}, - {file = "Cython-0.29.30.tar.gz", hash = "sha256:2235b62da8fe6fa8b99422c8e583f2fb95e143867d337b5c75e4b9a1a865f9e3"}, -] +coverage = [] +cython = [] dateutils = [ {file = "dateutils-0.6.12-py2.py3-none-any.whl", hash = "sha256:f33b6ab430fa4166e7e9cb8b21ee9f6c9843c48df1a964466f52c79b2a8d53b3"}, {file = "dateutils-0.6.12.tar.gz", hash = "sha256:03dd90bcb21541bd4eb4b013637e4f1b5f944881c46cc6e4b67a6059e370e3f1"}, @@ -958,10 +840,7 @@ dateutils = [ docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] -ecdsa = [ - {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, - {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, -] +ecdsa = [] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, @@ -1038,6 +917,7 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] +patch-env = [] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, @@ -1127,6 +1007,7 @@ pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] +pytest-mock = [] pytest-timeout = [ {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"}, {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"}, @@ -1223,10 +1104,7 @@ pyzmq = [ {file = "pyzmq-22.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d6157793719de168b199194f6b6173f0ccd3bf3499e6870fac17086072e39115"}, {file = "pyzmq-22.3.0.tar.gz", hash = "sha256:8eddc033e716f8c91c6a2112f0a8ebc5e00532b4a6ae1eb0ccc48e027f9c671c"}, ] -requests = [ - {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, - {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, -] +requests = [] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -1235,10 +1113,7 @@ rfc3987 = [ {file = "rfc3987-1.3.8-py2.py3-none-any.whl", hash = "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53"}, {file = "rfc3987-1.3.8.tar.gz", hash = "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733"}, ] -shellingham = [ - {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, - {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, -] +shellingham = [] single-source = [ {file = "single-source-0.3.0.tar.gz", hash = "sha256:b12705af958ca99d56ea9ce40bd9cc749378f4fe7ad03b1f9067e29daceef27d"}, {file = "single_source-0.3.0-py3-none-any.whl", hash = "sha256:7bc87168ced50f638b6ab0cda4cc1ce9e80ee0e1220014397050d336d021a597"}, @@ -1255,10 +1130,7 @@ sniffio = [ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] -stevedore = [ - {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, - {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, -] +stevedore = [] tomli = [ {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, @@ -1271,11 +1143,5 @@ typer-cli = [ {file = "typer-cli-0.0.12.tar.gz", hash = "sha256:d2c4a7a5c0326c20fb0970eed3c2173f76ba6b8b33d9bbece3a3dd91d673f096"}, {file = "typer_cli-0.0.12-py3-none-any.whl", hash = "sha256:f9b810d4fbdb750b28ceaa5fd8f737db596570418ae092e6d54a64d378e843ca"}, ] -typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, -] -urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, -] +typing-extensions = [] +urllib3 = [] diff --git a/pyproject.toml b/pyproject.toml index be249ce..d850582 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "podping-hivewriter" -version = "1.2.4" +version = "1.2.5" license = "MIT" authors = ["Alecks Gates ", "Brian of London "] maintainers = ["Alecks Gates ", "Brian of London "] @@ -43,6 +43,8 @@ pytest-timeout = "^2.0.2" typer-cli = "^0.0.12" flake8 = "^4.0.1" mypy = "^0.950" +patch-env = "^1.0.0" +pytest-mock = "^3.8.2" [tool.poetry.extras] server = ["pyzmq"] diff --git a/setup.py b/setup.py index 88460b4..38c55b5 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup_kwargs = { "name": "podping-hivewriter", - "version": "1.2.4", + "version": "1.2.5", "description": "This is a tool used to submit RFC 3987-compliant International Resource Identifiers as a Podping notification on the Hive blockchain.", "long_description": "# podping-hivewriter\nThe Hive writer component of Podping. You will need a Hive account, see section [Hive account and Authorization](#hive-account) below.\n\n## What is Podping?\n\nPodping is a mechanism of using decentralized communication to relay notification of updates of RSS feeds that use The Podcast Namespace. It does so by supplying minimum relevant metadata to consumers to be able to make efficient and actionable decisions, allowing them to decide what to do with given RSS feeds without parsing them ahead of time.\n\n*This* project provides a standardized way of posting a \"podping\" specifically to the Hive blockcahin.\n\n## Running podping-hivewriter\n\nThe project has two modes of running: `write` mode and `server` mode.\n\n`write` mode is primarily useful for people with a very small number of feeds to publish updates for relatively infrequently (i.e. a few times a day or less).\n\n`server` mode is for hosts (or other services like the Podcast Index's [podping.cloud](https://podping.cloud/)) who publish updates for a significant amount of feeds on a regular basis. Not that the average small-time podcast can't run it, but it's overkill. This mode is for efficiency only, as the `server` will batch process feeds as they come in to make the most use of the Hive blockchain.\n\nSee the dedicated [CLI docs](CLI.md) for more information on configuration options, including environment variables.\n\n### Container\n\nThe container images are hosted on [Docker Hub](https://hub.docker.com/r/podcastindexorg/podping-hivewriter). Images are currently based on Debian bullseye-based PyPy 3.8 with the following architectures: `amd64`\n\nThese images can be run in either `write` or `server` mode and is likely the easiest option for users who do not have experience installing Python packages.\n\n#### Command Line\n\nRunning in `write` mode with command line options, like `--dry-run` for example, add them with the full podping command.\nSettings can also be passed with the `-e` option for Docker. Note, we leave out `-p 9999:9999` here because we're not running the server.\n\n```shell\ndocker run --rm \\\n -e PODPING_HIVE_ACCOUNT= \\\n -e PODPING_HIVE_POSTING_KEY= \\\n docker.io/podcastindexorg/podping-hivewriter \\\n --dry-run write https://www.example.com/feed.xml\n```\n\nRun in `server` mode, passing local port 9999 to port 9999 in the container.\nENV variables can be passed to docker with `--env-file` option after modifying the `.env.EXAMPLE` file and renaming it to `.env`\n\n```shell\ndocker run --rm -p 9999:9999 --env-file .env --name podping docker.io/podcastindexorg/podping-hivewriter\n```\n\nAs another example for running in `server` mode, to run in *detached* mode, note the `-d` in the `docker run` options. Also note that `write` or `server` must come *after* the command line options for `podping`:\n```shell\ndocker run --rm -d \\\n -p 9999:9999 --env-file .env \\\n --name podping \\\n docker.io/podcastindexorg/podping-hivewriter \\\n --livetest server\n```\n\nOne running you can view and follow the live output with:\n```shell\ndocker logs podping -f\n```\n\nSee the [CLI docs](https://github.com/Podcastindex-org/podping-hivewriter/blob/main/CLI.md) for default values.\n\n\n#### docker-compose\n\n```yaml\nversion: '2.0'\nservices:\n podping-hivewriter:\n image: docker.io/podcastindexorg/podping-hivewriter\n restart: always\n ports:\n - \"9999:9999\"\n environment:\n - PODPING_HIVE_ACCOUNT=\n - PODPING_HIVE_POSTING_KEY=\n - PODPING_LISTEN_IP=0.0.0.0\n - PODPING_LISTEN_PORT=9999\n - PODPING_LIVETEST=false\n - PODPING_DRY_RUN=false\n - PODPING_STATUS=true\n - PODPING_IGNORE_CONFIG_UPDATES=false\n - PODPING_I_KNOW_WHAT_IM_DOING=false\n - PODPING_DEBUG=false\n```\n\nAssuming you just copy-pasted without reading, the above will fail at first. As noted in the [server command documentation](https://github.com/Podcastindex-org/podping-hivewriter/blob/main/CLI.md#podping-server):\n\n>WARNING: DO NOT run this on a publicly accessible host. There currently is NO authentication required to submit to the server. Set to * or 0.0.0.0 for all interfaces.\n\nAs all Docker installations vary, we set `0.0.0.0` as the listen IP for connectivity. This doesn't affect the IP address docker listens on when we tell it to pass port `9999` through to the container. If you understand the consequences of this, set `PODPING_I_KNOW_WHAT_IM_DOING` to `true`.\n\nThis is a temporary measure to limit potential misconfiguration until we fully bundle the `podping.cloud` HTTP front end. Then again, if you're running this, you're probably Dave.\n\n\n### CLI Install\n\nThe following have been tested on Linux and macOS. However, Windows should work also. If you have issues on Windows we highly recommend the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) and/or Docker.\n\n#### Using [pipx](https://pypa.github.io/pipx/) (preferred over pip)\n```shell\npipx install podping-hivewriter\n```\n\n#### Using pip\n```shell\npip install --user podping-hivewriter\n```\n\n#### Installing the server\n\nIf you'd like to install the server component, it's hidden behind the extra flag `server`. This is to make it easier to install only the `write` CLI component `podping-hivewriter` on non-standard systems without a configured development enviornment.\n\n```shell\npipx install podping-hivewriter[server]\n```\n\nMake sure you have `~/.local/bin/` on your `PATH`.\n\nSee the dedicated [CLI docs](CLI.md) for more information.\n\n## Podping reasons\n\nPodping accepts various different \"reasons\" for publishing updates to RSS feeds:\n\n* `update` -- A general indication that an RSS feed has been updated\n* `live` -- An indication that an RSS feed has been updated and a contained [``](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#live-item) tag's status attribute has been changed to live.\n* `liveEnd` -- An indication that an RSS feed has been updated and either the status attribute of an existing [``](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#live-item) has been changed from live to ended or a [``](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#live-item) that previously had a status attribute of live has been removed from the feed entirely.\n\nThe canonical list of reasons within the scope of this project is [maintained in this schema](https://github.com/Podcastindex-org/podping-hivewriter/blob/main/src/podping_hivewriter/schema/reason.capnp).\n\n## Mediums\n\nPodping accepts various different \"mediums\" for identifying types of RSS feeds using the Podcast Namespace. Please check the [``](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#medium) specification for the full list.\n\n`podping-hivewriter` *may* lag behind the specification, and if it does, please let us know or submit a pull request.\n\nThe canonical list of mediums within the scope of this project is [maintained in this schema](https://github.com/Podcastindex-org/podping-hivewriter/blob/main/src/podping_hivewriter/schema/medium.capnp).\n\n## Development\n\nYou'll need a few extras:\n\n1. [capnproto](https://capnproto.org/). Linux: `capnproto` package in your package manager. On a Mac: `brew instal capnp`\n2. [Poetry](https://python-poetry.org/docs/)\n\n\nWe use [poetry](https://python-poetry.org/) for dependency management. Once you have it, clone this repo and run:\n\n```shell\npoetry install\n```\n\nThen to switch to the virtual environment, use:\n\n```shell\npoetry shell\n```\nMake sure you have a the environment variables `PODPING_HIVE_ACCOUNT` and `PODPING_HIVE_POSTING_KEY` set.\n\nAfter that you should be able to run the `podping` command or run the tests:\n\n```shell\npytest\n```\n\nTo run all tests, make sure to set the necessary environment variables for your Hive account. This will take many minutes:\n\n```shell\npytest --runslow\n```\n\n### Building the image locally with Docker\n\nLocally build the podping-hivewriter container with a \"develop\" tag\n\n```shell\ndocker build -t podping-hivewriter:develop .\n```\n\nSee above for more details on running the docker CLI.\n\n## Hive account\n\nIf you need a Hive account, please download the [Hive Keychain extension for your browser](https://hive-keychain.com/) then use this link to get your account from [https://HiveOnboard.com?ref=podping](https://hiveonboard.com?ref=podping). You will need at least 20 Hive Power \"powered up\" to get started (worth around $10). Please contact [@brianoflondon](https://peakd.com/@brianoflondon) brian@podping.org if you need assistance getting set up.\n\nIf you use the [Hiveonboard]((https://hiveonboard.com?ref=podping)) link `podping` will **delegate** enough Hive Power to get you started. If, for any reason, Hiveonboard is not giving out free accounts, please contact [@brianoflondon](https://peakd.com/@brianoflondon) either on [PodcastIndex Social](https://podcastindex.social/invite/U2m6FY3T) or [Telegram](https://t.me/brianoflondon).\n\n### Permissions and Authorization\n\nYou don't need permission, but you do need to tell `podping` that you want to send valid `podpings`:\n\n- Hive is a so-called \"permissionless\" blockchain. Once you have a Hive Account and a minimal amount of Hive Power, that account can post to Hive, including sending `podpings`.\n- Nobody can block any valid Hive Account from sending and nobody can help you if you lose your keys.\n- Whilst anyone can post `podpings` to Hive, there is a need to register your Hive Accountname for those `podpings` to be recognized by all clients. This is merely a spam-prevention measure and clients may choose to ignore it.\n- Please contact new@podping.org or send a Hive Transfer to [@podping](https://peakd.com/@podping) to have your account validated.\n- Side note on keys: `podping` uses the `posting-key` which is the lowest value of the four Hive keys (`owner`, `active`, `memo`, `posting` and there is usually a `master password` which can generate all the keys). That is not to say that losing control of it is a good idea, but that key is not authorized to make financially important transfers. It can, however, post public information so should be treated carefully and kept secure.\n\nFor a [comprehensive explanation of Hive and Podping, please see this post](https://peakd.com/podping/@brianoflondon/podping-and-podcasting-20-funding-to-put-hive-at-the-center-of-global-podcasting-infrastructure).", "author": "Alecks Gates", diff --git a/src/podping_hivewriter/podping_hivewriter.py b/src/podping_hivewriter/podping_hivewriter.py index 799e3c4..86f5491 100644 --- a/src/podping_hivewriter/podping_hivewriter.py +++ b/src/podping_hivewriter/podping_hivewriter.py @@ -1,4 +1,5 @@ import asyncio +import itertools import json import logging import re @@ -404,17 +405,17 @@ async def send_notification( except RPCNodeException as ex: logging.error(f"send_notification error: {ex}") - if ( - ex.raw_body - and "error" in ex.raw_body - and "message" in ex.raw_body["error"] - and re.match( - r"plugin exception.*custom json.*", ex.raw_body["error"]["message"] - ) - ): - self.lighthive_client.next_node() - raise TooManyCustomJsonsPerBlock() - raise ex + try: + if re.match( + r"plugin exception.*custom json.*", + ex.raw_body["error"]["message"], + ): + self.lighthive_client.next_node() + raise TooManyCustomJsonsPerBlock() + else: + raise ex + except (KeyError, AttributeError): + raise ex except PodpingCustomJsonPayloadExceeded as ex: raise ex @@ -465,7 +466,7 @@ async def failure_retry( await self.wait_startup() failure_count = 0 - while True: + for _ in itertools.repeat(None): # Sleep a maximum of 5 minutes, 3 additional seconds for every retry sleep_time = min(failure_count * 3, 300) if failure_count > 0: @@ -491,6 +492,8 @@ async def failure_retry( except RPCNodeException as ex: logging.exception(f"Failed to send {len(iri_set)} IRIs") try: + # Test if we have a well-formed Hive error message + logging.exception(ex) if ( ex.raw_body["error"]["data"]["name"] == "tx_missing_posting_auth" @@ -503,6 +506,10 @@ async def failure_retry( f"{STARTUP_FAILED_INVALID_POSTING_KEY_EXIT_CODE}" ) sys.exit(STARTUP_FAILED_INVALID_POSTING_KEY_EXIT_CODE) + except (KeyError, AttributeError): + logging.warning( + f"Malformed error response from node: {self.lighthive_client.current_node}" + ) except Exception: logging.info(f"Current node: {self.lighthive_client.current_node}") logging.info(self.lighthive_client.nodes) @@ -510,6 +517,7 @@ async def failure_retry( sys.exit(STARTUP_FAILED_UNKNOWN_EXIT_CODE) except Exception as ex: + logging.exception(ex) logging.exception(f"Failed to send {len(iri_set)} IRIs") if logging.DEBUG >= logging.root.level: for iri in iri_set: @@ -518,3 +526,5 @@ async def failure_retry( finally: self.lighthive_client.next_node() failure_count += 1 + + return failure_count diff --git a/tests/integration/test_write_cli_multiple.py b/tests/integration/test_write_cli_multiple.py index 679adea..afa34e5 100644 --- a/tests/integration/test_write_cli_multiple.py +++ b/tests/integration/test_write_cli_multiple.py @@ -35,8 +35,8 @@ async def test_write_cli_multiple(): for i in range(num_iris) } - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] default_hive_operation_id = HiveOperationId(LIVETEST_OPERATION_ID, medium, reason) default_hive_operation_id_str = str(default_hive_operation_id) diff --git a/tests/integration/test_write_cli_single.py b/tests/integration/test_write_cli_single.py index 0f0b73f..4aa5c84 100644 --- a/tests/integration/test_write_cli_single.py +++ b/tests/integration/test_write_cli_single.py @@ -28,8 +28,8 @@ async def test_write_cli_single(): test_name = "cli_single" iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] default_hive_operation_id = HiveOperationId(LIVETEST_OPERATION_ID, medium, reason) default_hive_operation_id_str = str(default_hive_operation_id) diff --git a/tests/integration/test_write_cli_single_simulcast.py b/tests/integration/test_write_cli_single_simulcast.py index 27edae1..3c5b35f 100644 --- a/tests/integration/test_write_cli_single_simulcast.py +++ b/tests/integration/test_write_cli_single_simulcast.py @@ -53,8 +53,8 @@ async def get_iri_from_blockchain(start_block: int): for n in range(7) } for iri in test_iris: - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] args = [ "--medium", str(medium), diff --git a/tests/integration/test_write_zmq_multiple.py b/tests/integration/test_write_zmq_multiple.py index 103c8fb..8e3880b 100644 --- a/tests/integration/test_write_zmq_multiple.py +++ b/tests/integration/test_write_zmq_multiple.py @@ -39,8 +39,8 @@ async def test_write_zmq_multiple(event_loop): for i in range(num_iris) } - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] default_hive_operation_id = HiveOperationId(LIVETEST_OPERATION_ID, medium, reason) default_hive_operation_id_str = str(default_hive_operation_id) diff --git a/tests/integration/test_write_zmq_single.py b/tests/integration/test_write_zmq_single.py index c340c93..38b0e08 100644 --- a/tests/integration/test_write_zmq_single.py +++ b/tests/integration/test_write_zmq_single.py @@ -32,8 +32,8 @@ async def test_write_zmq_single(event_loop): test_name = "zmq_single" iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] default_hive_operation_id = HiveOperationId(LIVETEST_OPERATION_ID, medium, reason) default_hive_operation_id_str = str(default_hive_operation_id) diff --git a/tests/regression/test_#48_send_notification_raises_rpcexception.py b/tests/regression/test_#48_send_notification_raises_rpcexception.py new file mode 100644 index 0000000..68fb5cb --- /dev/null +++ b/tests/regression/test_#48_send_notification_raises_rpcexception.py @@ -0,0 +1,144 @@ +import os +import random +import uuid +from platform import python_version as pv + +import lighthive +import pytest + +from lighthive.client import Client +from lighthive.exceptions import RPCNodeException + +from podping_hivewriter.constants import LIVETEST_OPERATION_ID +from podping_hivewriter.exceptions import TooManyCustomJsonsPerBlock +from podping_hivewriter.models.medium import str_medium_map, mediums +from podping_hivewriter.models.reason import str_reason_map, reasons +from podping_hivewriter.podping_hivewriter import PodpingHivewriter +from podping_hivewriter.podping_settings_manager import PodpingSettingsManager + + +@pytest.mark.asyncio +async def test_send_notification_raises_rpcexception_invalid_body( + event_loop, monkeypatch +): + settings_manager = PodpingSettingsManager(ignore_updates=True) + + def mock_broadcast(*args, **kwargs): + raise RPCNodeException( + "mock_broadcast exception", code=42, raw_body={"foo": "bar"} + ) + + monkeypatch.setattr(lighthive.client.Client, "broadcast", mock_broadcast) + + session_uuid = uuid.uuid4() + session_uuid_str = str(session_uuid) + + test_name = "test_send_notification_raises_rpcexception_invalid_body" + iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" + + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] + + podping_hivewriter = PodpingHivewriter( + os.environ["PODPING_HIVE_ACCOUNT"], + [os.environ["PODPING_HIVE_POSTING_KEY"]], + settings_manager, + medium=medium, + reason=reason, + daemon=False, + resource_test=False, + operation_id=LIVETEST_OPERATION_ID, + ) + + await podping_hivewriter.wait_startup() + + with pytest.raises(RPCNodeException): + await podping_hivewriter.send_notification_iri(iri, medium, reason) + + podping_hivewriter.close() + + +@pytest.mark.asyncio +async def test_send_notification_raises_rpcexception_valid_body( + event_loop, monkeypatch +): + settings_manager = PodpingSettingsManager(ignore_updates=True) + + def mock_broadcast(*args, **kwargs): + raise RPCNodeException( + "mock_broadcast exception", + code=42, + raw_body={"error": {"message": "nonsense"}}, + ) + + monkeypatch.setattr(lighthive.client.Client, "broadcast", mock_broadcast) + + session_uuid = uuid.uuid4() + session_uuid_str = str(session_uuid) + + test_name = "test_send_notification_raises_rpcexception_valid_body" + iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" + + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] + + podping_hivewriter = PodpingHivewriter( + os.environ["PODPING_HIVE_ACCOUNT"], + [os.environ["PODPING_HIVE_POSTING_KEY"]], + settings_manager, + medium=medium, + reason=reason, + daemon=False, + resource_test=False, + operation_id=LIVETEST_OPERATION_ID, + ) + + await podping_hivewriter.wait_startup() + + with pytest.raises(RPCNodeException): + await podping_hivewriter.send_notification_iri(iri, medium, reason) + + podping_hivewriter.close() + + +@pytest.mark.asyncio +async def test_send_notification_raises_too_many_custom_jsons_per_block( + event_loop, monkeypatch +): + settings_manager = PodpingSettingsManager(ignore_updates=True) + + def mock_broadcast(*args, **kwargs): + raise RPCNodeException( + "mock_broadcast exception", + code=42, + raw_body={"error": {"message": "plugin exception foobar custom json bizz"}}, + ) + + monkeypatch.setattr(lighthive.client.Client, "broadcast", mock_broadcast) + + session_uuid = uuid.uuid4() + session_uuid_str = str(session_uuid) + + test_name = "test_send_notification_raises_too_many_custom_jsons_per_block" + iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" + + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] + + podping_hivewriter = PodpingHivewriter( + os.environ["PODPING_HIVE_ACCOUNT"], + [os.environ["PODPING_HIVE_POSTING_KEY"]], + settings_manager, + medium=medium, + reason=reason, + daemon=False, + resource_test=False, + operation_id=LIVETEST_OPERATION_ID, + ) + + await podping_hivewriter.wait_startup() + + with pytest.raises(TooManyCustomJsonsPerBlock): + await podping_hivewriter.send_notification_iri(iri, medium, reason) + + podping_hivewriter.close() diff --git a/tests/regression/test_#48_failure_retry_handles_invalid_error_response.py b/tests/regression/test_#51_failure_retry_handles_invalid_error_response.py similarity index 53% rename from tests/regression/test_#48_failure_retry_handles_invalid_error_response.py rename to tests/regression/test_#51_failure_retry_handles_invalid_error_response.py index 4ae20ed..600166e 100644 --- a/tests/regression/test_#48_failure_retry_handles_invalid_error_response.py +++ b/tests/regression/test_#51_failure_retry_handles_invalid_error_response.py @@ -9,6 +9,7 @@ from lighthive.client import Client from lighthive.exceptions import RPCNodeException +from podping_hivewriter import podping_hivewriter from podping_hivewriter.constants import LIVETEST_OPERATION_ID from podping_hivewriter.models.medium import str_medium_map, mediums from podping_hivewriter.models.reason import str_reason_map, reasons @@ -17,15 +18,24 @@ @pytest.mark.asyncio -async def test_failure_retry_handles_invalid_error_response(event_loop, monkeypatch): +async def test_failure_retry_handles_invalid_error_response( + event_loop, mocker, monkeypatch +): settings_manager = PodpingSettingsManager(ignore_updates=True) + logging_warning_stub = mocker.stub(name="logging_warning_stub") + logging_exception_stub = mocker.stub(name="logging_exception_stub") + def mock_broadcast(*args, **kwargs): raise RPCNodeException( "mock_broadcast exception", code=42, raw_body={"foo": "bar"} ) + mocker.patch.object(podping_hivewriter.itertools, "repeat", return_value=range(1)) + monkeypatch.setattr(podping_hivewriter.logging, "warning", logging_warning_stub) + monkeypatch.setattr(podping_hivewriter.logging, "exception", logging_exception_stub) monkeypatch.setattr(lighthive.client.Client, "broadcast", mock_broadcast) + lighthive_client_next_node_spy = mocker.spy(lighthive.client.Client, "next_node") session_uuid = uuid.uuid4() session_uuid_str = str(session_uuid) @@ -33,26 +43,29 @@ def mock_broadcast(*args, **kwargs): test_name = "failure_retry_handles_invalid_error_response" iri = f"https://example.com?t={test_name}&v={pv()}&s={session_uuid_str}" - medium = str_medium_map[random.sample(mediums, 1)[0]] - reason = str_reason_map[random.sample(reasons, 1)[0]] + medium = str_medium_map[random.sample(sorted(mediums), 1)[0]] + reason = str_reason_map[random.sample(sorted(reasons), 1)[0]] - host = "127.0.0.1" - port = 9979 - podping_hivewriter = PodpingHivewriter( + writer = PodpingHivewriter( os.environ["PODPING_HIVE_ACCOUNT"], [os.environ["PODPING_HIVE_POSTING_KEY"]], settings_manager, medium=medium, reason=reason, - listen_ip=host, - listen_port=port, + daemon=False, resource_test=False, operation_id=LIVETEST_OPERATION_ID, ) - await podping_hivewriter.wait_startup() + await writer.wait_startup() + + lighthive_client_next_node_spy.reset_mock() + + failure_count = await writer.failure_retry({iri}, medium, reason) - with pytest.raises(RPCNodeException): - await podping_hivewriter.send_notification_iri(iri, medium, reason) + writer.close() - podping_hivewriter.close() + logging_warning_stub.assert_called_once() + lighthive_client_next_node_spy.assert_called_once() + assert logging_exception_stub.call_count == 2 + assert failure_count == 1