diff --git a/planetmint/abci/rpc.py b/planetmint/abci/rpc.py index 70be5da..0ab8356 100644 --- a/planetmint/abci/rpc.py +++ b/planetmint/abci/rpc.py @@ -69,5 +69,12 @@ class ABCI_RPC: "params": [encode_transaction(tx_dict)], "id": str(uuid4()), } - # TODO: handle connection errors! - return requests.post(endpoint, json=payload) + try: + response = requests.post(endpoint, json=payload) + except requests.exceptions.ConnectionError as e: + logger.error(f"Tendermint RCP Connection issue: {e}") + raise e + except Exception as e: + logger.error(f"Tendermint RCP Connection issue: {e}") + raise e + return response diff --git a/planetmint/backend/localmongodb/connection.py b/planetmint/backend/localmongodb/connection.py index f910f8d..01b1447 100644 --- a/planetmint/backend/localmongodb/connection.py +++ b/planetmint/backend/localmongodb/connection.py @@ -10,7 +10,7 @@ import pymongo from planetmint.config import Config from planetmint.backend.exceptions import DuplicateKeyError, OperationError, ConnectionError from transactions.common.exceptions import ConfigurationError -from planetmint.utils import Lazy +from planetmint.utils.lazy import Lazy from planetmint.backend.connection import DBConnection, _kwargs_parser logger = logging.getLogger(__name__) diff --git a/planetmint/backend/tarantool/sync_io/connection.py b/planetmint/backend/tarantool/sync_io/connection.py index 6e2e76a..bed79c8 100644 --- a/planetmint/backend/tarantool/sync_io/connection.py +++ b/planetmint/backend/tarantool/sync_io/connection.py @@ -9,7 +9,7 @@ import tarantool from planetmint.config import Config from transactions.common.exceptions import ConfigurationError -from planetmint.utils import Lazy +from planetmint.utils.lazy import Lazy from planetmint.backend.connection import DBConnection from planetmint.backend.exceptions import ConnectionError diff --git a/planetmint/config.py b/planetmint/config.py index f2e3dc5..1a54658 100644 --- a/planetmint/config.py +++ b/planetmint/config.py @@ -3,8 +3,7 @@ import logging import os from decouple import config -from planetmint.utils import Singleton -from planetmint.version import __version__ +from planetmint.utils.singleton import Singleton class Config(metaclass=Singleton): diff --git a/planetmint/ipc/exchange.py b/planetmint/ipc/exchange.py index e62a6cb..08e71ed 100644 --- a/planetmint/ipc/exchange.py +++ b/planetmint/ipc/exchange.py @@ -1,9 +1,12 @@ from queue import Empty from collections import defaultdict import multiprocessing +import logging from planetmint.ipc.events import EventTypes, POISON_PILL +logger = logging.getLogger(__name__) + class Exchange: """Dispatch events to subscribers.""" @@ -63,10 +66,14 @@ class Exchange: def run(self): """Start the exchange""" self.started_queue.put("STARTED") - - while True: - event = self.publisher_queue.get() - if event == POISON_PILL: - return - else: - self.dispatch(event) + try: + while True: + event = self.publisher_queue.get() + if event == POISON_PILL: + return + else: + self.dispatch(event) + except KeyboardInterrupt: + return + except Exception as e: + logger.debug(f"Exchange Exception: {e}") diff --git a/planetmint/start.py b/planetmint/start.py index 441da8a..2bf702c 100644 --- a/planetmint/start.py +++ b/planetmint/start.py @@ -13,7 +13,7 @@ from planetmint.abci.parallel_validation import ParallelValidationApp from planetmint.web import server, websocket_server from planetmint.ipc.events import EventTypes from planetmint.ipc.exchange import Exchange -from planetmint.utils import Process +from planetmint.utils.processes import Process from planetmint.version import __version__ logger = logging.getLogger(__name__) diff --git a/planetmint/utils/__init__.py b/planetmint/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/utils/lazy.py b/planetmint/utils/lazy.py new file mode 100644 index 0000000..0490cc9 --- /dev/null +++ b/planetmint/utils/lazy.py @@ -0,0 +1,44 @@ +class Lazy: + """Lazy objects are useful to create chains of methods to + execute later. + + A lazy object records the methods that has been called, and + replay them when the :py:meth:`run` method is called. Note that + :py:meth:`run` needs an object `instance` to replay all the + methods that have been recorded. + """ + + def __init__(self): + """Instantiate a new Lazy object.""" + self.stack = [] + + def __getattr__(self, name): + self.stack.append(name) + return self + + def __call__(self, *args, **kwargs): + self.stack.append((args, kwargs)) + return self + + def __getitem__(self, key): + self.stack.append("__getitem__") + self.stack.append(([key], {})) + return self + + def run(self, instance): + """Run the recorded chain of methods on `instance`. + + Args: + instance: an object. + """ + + last = instance + + for item in self.stack: + if isinstance(item, str): + last = getattr(last, item) + else: + last = last(*item[0], **item[1]) + + self.stack = [] + return last diff --git a/planetmint/utils.py b/planetmint/utils/processes.py similarity index 61% rename from planetmint/utils.py rename to planetmint/utils/processes.py index a1b4687..26ed9d0 100644 --- a/planetmint/utils.py +++ b/planetmint/utils/processes.py @@ -10,15 +10,6 @@ import multiprocessing import setproctitle -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] - - class Process(multiprocessing.Process): """Wrapper around multiprocessing.Process that uses setproctitle to set the name of the process when running @@ -83,49 +74,3 @@ def pool(builder, size, timeout=None): local_pool.put(instance) return pooled - - -class Lazy: - """Lazy objects are useful to create chains of methods to - execute later. - - A lazy object records the methods that has been called, and - replay them when the :py:meth:`run` method is called. Note that - :py:meth:`run` needs an object `instance` to replay all the - methods that have been recorded. - """ - - def __init__(self): - """Instantiate a new Lazy object.""" - self.stack = [] - - def __getattr__(self, name): - self.stack.append(name) - return self - - def __call__(self, *args, **kwargs): - self.stack.append((args, kwargs)) - return self - - def __getitem__(self, key): - self.stack.append("__getitem__") - self.stack.append(([key], {})) - return self - - def run(self, instance): - """Run the recorded chain of methods on `instance`. - - Args: - instance: an object. - """ - - last = instance - - for item in self.stack: - if isinstance(item, str): - last = getattr(last, item) - else: - last = last(*item[0], **item[1]) - - self.stack = [] - return last diff --git a/planetmint/utils/python.py b/planetmint/utils/python.py new file mode 100644 index 0000000..754d669 --- /dev/null +++ b/planetmint/utils/python.py @@ -0,0 +1,13 @@ +import sys + + +def is_above_py39(): + if sys.version_info.major == 3: + if sys.version_info.minor < 10: + return False + else: + return True + elif sys.version_info.major > 3: + return True + else: + return False diff --git a/planetmint/utils/singleton.py b/planetmint/utils/singleton.py new file mode 100644 index 0000000..3776cb9 --- /dev/null +++ b/planetmint/utils/singleton.py @@ -0,0 +1,7 @@ +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/planetmint/web/server.py b/planetmint/web/server.py index f5ab15e..9a2c460 100644 --- a/planetmint/web/server.py +++ b/planetmint/web/server.py @@ -14,7 +14,7 @@ import gunicorn.app.base from flask import Flask from flask_cors import CORS -from planetmint import utils +from planetmint.utils import processes from planetmint.application.validator import Validator from planetmint.web.routes import add_routes from planetmint.web.strip_content_type_middleware import StripContentTypeMiddleware @@ -81,7 +81,7 @@ def create_app(*, debug=False, threads=1, planetmint_factory=None): app.debug = debug - app.config["validator_class_name"] = utils.pool(planetmint_factory, size=threads) + app.config["validator_class_name"] = processes.pool(planetmint_factory, size=threads) add_routes(app) diff --git a/planetmint/web/views/transactions.py b/planetmint/web/views/transactions.py index b406879..cd2ae31 100644 --- a/planetmint/web/views/transactions.py +++ b/planetmint/web/views/transactions.py @@ -104,9 +104,14 @@ class TransactionListApi(Resource): but this node only accepts transaction with higher \ schema version number.", ) - status_code, message = ABCI_RPC().write_transaction( - MODE_LIST, ABCI_RPC().tendermint_rpc_endpoint, MODE_COMMIT, tx_obj, mode - ) + try: + status_code, message = ABCI_RPC().write_transaction( + MODE_LIST, ABCI_RPC().tendermint_rpc_endpoint, MODE_COMMIT, tx_obj, mode + ) + except Exception as e: + logger.error(f"Tendermint RPC connection issue: {e}") + status_code = 500 + message = {"detail": "Tendermint RPC connection error"} if status_code == 202: response = jsonify(tx) diff --git a/planetmint/web/websocket_dispatcher.py b/planetmint/web/websocket_dispatcher.py index 8342b04..7435371 100644 --- a/planetmint/web/websocket_dispatcher.py +++ b/planetmint/web/websocket_dispatcher.py @@ -3,11 +3,15 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 - import json +import logging +import asyncio from planetmint.ipc.events import EventTypes from planetmint.ipc.events import POISON_PILL +from planetmint.utils.python import is_above_py39 + +logger = logging.getLogger(__name__) class Dispatcher: @@ -16,15 +20,13 @@ class Dispatcher: This class implements a simple publish/subscribe pattern. """ - def __init__(self, event_source, type="tx"): + def __init__(self, type="tx"): """Create a new instance. Args: - event_source: a source of events. Elements in the queue - should be strings. + type: a string identifier. """ - self.event_source = event_source self.subscribers = {} self.type = type @@ -54,6 +56,18 @@ class Dispatcher: txids.append(tx.id) return {"height": block["height"], "hash": block["hash"], "transaction_ids": txids} + @staticmethod + def get_queue_on_demand(app, queue_name: str): + if queue_name not in app: + logging.debug(f"creating queue: {queue_name}") + if is_above_py39(): + app[queue_name] = asyncio.Queue() + else: + get_loop = asyncio.get_event_loop() + app[queue_name] = asyncio.Queue(loop=get_loop) + + return app[queue_name] + @staticmethod def eventify_block(block): for tx in block["transactions"]: @@ -67,16 +81,19 @@ class Dispatcher: asset_ids = [tx.id] yield {"height": block["height"], "asset_ids": asset_ids, "transaction_id": tx.id} - async def publish(self): + async def publish(self, app): """Publish new events to the subscribers.""" - + logger.debug(f"DISPATCHER CALLED : {self.type}") while True: - event = await self.event_source.get() + if self.type == "tx": + event = await Dispatcher.get_queue_on_demand(app, "tx_source").get() + elif self.type == "blk": + event = await Dispatcher.get_queue_on_demand(app, "blk_source").get() str_buffer = [] if event == POISON_PILL: return - + logger.debug(f"DISPATCHER ELEMENT : {event}") if isinstance(event, str): str_buffer.append(event) elif event.type == EventTypes.BLOCK_VALID: diff --git a/planetmint/web/websocket_server.py b/planetmint/web/websocket_server.py index 0aceff8..f43b814 100644 --- a/planetmint/web/websocket_server.py +++ b/planetmint/web/websocket_server.py @@ -18,7 +18,6 @@ import asyncio import logging -import threading import aiohttp from uuid import uuid4 @@ -26,25 +25,34 @@ from concurrent.futures import CancelledError from planetmint.config import Config from planetmint.web.websocket_dispatcher import Dispatcher - logger = logging.getLogger(__name__) EVENTS_ENDPOINT = "/api/v1/streams/valid_transactions" EVENTS_ENDPOINT_BLOCKS = "/api/v1/streams/valid_blocks" -def _multiprocessing_to_asyncio(in_queue, out_queue1, out_queue2, loop): - """Bridge between a synchronous multiprocessing queue - and an asynchronous asyncio queue. - - Args: - in_queue (multiprocessing.Queue): input queue - out_queue (asyncio.Queue): output queue - """ - - while True: - value = in_queue.get() - loop.call_soon_threadsafe(out_queue1.put_nowait, value) - loop.call_soon_threadsafe(out_queue2.put_nowait, value) +async def access_queue(app): + if app["event_source"] == None: + return + in_queue = app["event_source"] + tx_source = Dispatcher.get_queue_on_demand(app, "tx_source") + blk_source = Dispatcher.get_queue_on_demand(app, "blk_source") + logger.debug(f"REROUTING CALLED") + try: + while True: + try: + if not in_queue.empty(): + item = in_queue.get_nowait() + logger.debug(f"REROUTING: {item}") + await tx_source.put(item) + await blk_source.put(item) + else: + await asyncio.sleep(1) + except Exception as e: + logger.debug(f"REROUTING wait exception : {e}") + raise e # await asyncio.sleep(1) + except asyncio.CancelledError as e: + logger.debug(f"REROUTING Cancelled : {e}") + pass async def websocket_tx_handler(request): @@ -60,6 +68,7 @@ async def websocket_tx_handler(request): # Consume input buffer try: msg = await websocket.receive() + logger.debug(f"TX HANDLER: {msg}") except RuntimeError as e: logger.debug("Websocket exception: %s", str(e)) break @@ -90,6 +99,7 @@ async def websocket_blk_handler(request): # Consume input buffer try: msg = await websocket.receive() + logger.debug(f"BLK HANDLER: {msg}") except RuntimeError as e: logger.debug("Websocket exception: %s", str(e)) break @@ -107,43 +117,33 @@ async def websocket_blk_handler(request): return websocket -def init_app(tx_source, blk_source, *, loop=None): - """Init the application server. +async def start_background_tasks(app): + blk_dispatcher = app["blk_dispatcher"] + app["task1"] = asyncio.create_task(blk_dispatcher.publish(app), name="blk") - Return: - An aiohttp application. - """ + tx_dispatcher = app["tx_dispatcher"] + app["task2"] = asyncio.create_task(tx_dispatcher.publish(app), name="tx") - blk_dispatcher = Dispatcher(blk_source, "blk") - tx_dispatcher = Dispatcher(tx_source, "tx") + app["task3"] = asyncio.create_task(access_queue(app), name="router") - # Schedule the dispatcher - loop.create_task(blk_dispatcher.publish(), name="blk") - loop.create_task(tx_dispatcher.publish(), name="tx") - app = aiohttp.web.Application(loop=loop) - app["tx_dispatcher"] = tx_dispatcher - app["blk_dispatcher"] = blk_dispatcher +def init_app(sync_event_source): + """Create and start the WebSocket server.""" + app = aiohttp.web.Application() + app["event_source"] = sync_event_source + + # dispatchers + app["tx_dispatcher"] = Dispatcher("tx") + app["blk_dispatcher"] = Dispatcher("blk") + + # routes app.router.add_get(EVENTS_ENDPOINT, websocket_tx_handler) app.router.add_get(EVENTS_ENDPOINT_BLOCKS, websocket_blk_handler) + + app.on_startup.append(start_background_tasks) return app -def start(sync_event_source, loop=None): - """Create and start the WebSocket server.""" - - if not loop: - loop = asyncio.get_event_loop() - - tx_source = asyncio.Queue(loop=loop) - blk_source = asyncio.Queue(loop=loop) - - bridge = threading.Thread( - target=_multiprocessing_to_asyncio, args=(sync_event_source, tx_source, blk_source, loop), daemon=True - ) - bridge.start() - - app = init_app(tx_source, blk_source, loop=loop) - aiohttp.web.run_app( - app, host=Config().get()["wsserver"]["host"], port=Config().get()["wsserver"]["port"], loop=loop - ) +def start(sync_event_source): + app = init_app(sync_event_source) + aiohttp.web.run_app(app, host=Config().get()["wsserver"]["host"], port=Config().get()["wsserver"]["port"]) diff --git a/poetry.lock b/poetry.lock index f2ca464..22099d1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "aafigure" @@ -33,91 +33,106 @@ dev = ["black", "build", "pytest", "pytest-cov", "twine"] [[package]] name = "aiohttp" -version = "3.8.1" +version = "3.8.4" description = "Async http client/server framework (asyncio)" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, - {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, - {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, - {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, - {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, - {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, - {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, - {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, - {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, - {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, - {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, - {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, - {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, - {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, - {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, - {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, - {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, - {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, - {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, - {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, - {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, - {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, - {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, - {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, - {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, - {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, - {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, - {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, - {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, - {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, - {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, - {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, - {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, - {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, - {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, - {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, - {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, - {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, - {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, - {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, - {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, - {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, - {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, - {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, - {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, - {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, - {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, - {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, - {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, - {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, - {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, - {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, - {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, - {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, - {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, - {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, - {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, - {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, - {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, - {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, - {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, - {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, - {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, - {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, - {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, - {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, - {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, - {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, - {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, - {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, - {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, - {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"}, + {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"}, + {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"}, + {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"}, + {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"}, + {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"}, + {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"}, + {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"}, + {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"}, + {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"}, + {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"}, + {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"}, ] [package.dependencies] aiosignal = ">=1.1.2" async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<3.0" +charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" @@ -2311,7 +2326,7 @@ cffi = ">=1.4.1" six = "*" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] @@ -3461,4 +3476,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8b9c0f4ebc41bca0af100c124f3522e2e1052271ebde525b0b06b44496a2d105" +content-hash = "ad5feda1537dc9cde8ae983dafedb9a38acc71acdbe425505027f77f05c04a98" diff --git a/pyproject.toml b/pyproject.toml index 77e100d..26fd47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ planetmint = "planetmint.commands.planetmint:main" python = "^3.9" chardet = "3.0.4" base58 = "2.1.1" -aiohttp = "3.8.1" +aiohttp = "^3.8.4" flask-cors = "3.0.10" flask-restful = "0.3.9" flask = "2.1.2" diff --git a/tests/conftest.py b/tests/conftest.py index a194a1c..9f99c3e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -445,22 +445,13 @@ def abci_http(_setup_database, _configure_planetmint, abci_server, tendermint_ho return False -@pytest.fixture(scope="session") -def event_loop(): - import asyncio - - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - - @pytest.fixture(scope="session") def abci_server(): from abci.server import ABCIServer # from tendermint.abci import types_pb2 as types_v0_34_11 from planetmint.abci.application_logic import ApplicationLogic - from planetmint.utils import Process + from planetmint.utils.processes import Process app = ABCIServer(app=ApplicationLogic()) abci_proxy = Process(name="ABCI", target=app.run) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5db3177..b3fb424 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,6 +7,7 @@ import queue import pytest from unittest.mock import patch, call +from planetmint.utils import processes @pytest.fixture @@ -32,9 +33,7 @@ def mock_queue(monkeypatch): def test_empty_pool_is_populated_with_instances(mock_queue): - from planetmint import utils - - pool = utils.pool(lambda: "hello", 4) + pool = processes.pool(lambda: "hello", 4) assert len(mock_queue.items) == 0 @@ -60,9 +59,7 @@ def test_empty_pool_is_populated_with_instances(mock_queue): def test_pool_blocks_if_no_instances_available(mock_queue): - from planetmint import utils - - pool = utils.pool(lambda: "hello", 4) + pool = processes.pool(lambda: "hello", 4) assert len(mock_queue.items) == 0 @@ -98,9 +95,7 @@ def test_pool_blocks_if_no_instances_available(mock_queue): def test_pool_raises_empty_exception_when_timeout(mock_queue): - from planetmint import utils - - pool = utils.pool(lambda: "hello", 1, timeout=1) + pool = processes.pool(lambda: "hello", 1, timeout=1) assert len(mock_queue.items) == 0 @@ -141,7 +136,7 @@ def test_process_group_instantiates_and_start_processes(mock_process): def test_lazy_execution(): - from planetmint.utils import Lazy + from planetmint.utils.lazy import Lazy lz = Lazy() lz.split(",")[1].split(" ").pop(1).strip() @@ -164,7 +159,7 @@ def test_process_set_title(): from uuid import uuid4 from multiprocessing import Queue from setproctitle import getproctitle - from planetmint.utils import Process + from planetmint.utils.processes import Process queue = Queue() uuid = str(uuid4()) diff --git a/tests/web/test_websocket_server.py b/tests/web/test_websocket_server.py index 26f9323..f769ee1 100644 --- a/tests/web/test_websocket_server.py +++ b/tests/web/test_websocket_server.py @@ -2,7 +2,7 @@ # Planetmint and IPDB software contributors. # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 - +import aiohttp import asyncio import json import queue @@ -16,6 +16,7 @@ from transactions.common import crypto from planetmint.ipc import events from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT, EVENTS_ENDPOINT_BLOCKS from ipld import multihash, marshal +from planetmint.web.websocket_dispatcher import Dispatcher class MockWebSocket: @@ -76,75 +77,12 @@ def test_simplified_block_works(): @pytest.mark.asyncio -async def test_bridge_sync_async_queue(event_loop): - from planetmint.web.websocket_server import _multiprocessing_to_asyncio - - sync_queue = queue.Queue() - async_queue = asyncio.Queue(loop=event_loop) - async_queue2 = asyncio.Queue(loop=event_loop) - - bridge = threading.Thread( - target=_multiprocessing_to_asyncio, args=(sync_queue, async_queue, async_queue2, event_loop), daemon=True - ) - bridge.start() - - sync_queue.put("fahren") - sync_queue.put("auf") - sync_queue.put("der") - sync_queue.put("Autobahn") - - result = await async_queue.get() - assert result == "fahren" - - result = await async_queue.get() - assert result == "auf" - - result = await async_queue.get() - assert result == "der" - - result = await async_queue.get() - assert result == "Autobahn" - - print(f" queue ({async_queue.qsize()}): {async_queue} ") - assert async_queue.qsize() == 0 - - -# TODO: fix the test and uncomment it -# @patch('threading.Thread') -# @patch('aiohttp.web.run_app') -# @patch('planetmint.web.websocket_server.init_app') -# @patch('asyncio.get_event_loop', return_value='event-loop') -# @patch('asyncio.Queue', return_value='event-queue') -# def test_start_creates_an_event_loop(queue_mock, get_event_loop_mock, -# init_app_mock, run_app_mock, -# thread_mock): -# from planetmint import config -# from planetmint.web.websocket_server import start, _multiprocessing_to_asyncio -# -# start(None) -# #thread_mock.assert_called_once_with( -# # target=_multiprocessing_to_asyncio, -# # args=(None, queue_mock.return_value, queue_mock.return_value, get_event_loop_mock.return_value), -# # daemon=True, -# #) -# thread_mock.return_value.start.assert_called_once_with() -# init_app_mock.assert_called_with('event-queue', 'event-queue', loop='event-loop') -# run_app_mock.assert_called_once_with( -# init_app_mock.return_value, -# host=config['wsserver']['host'], -# port=config['wsserver']['port'], -# ) - - -@pytest.mark.asyncio -async def test_websocket_block_event(aiohttp_client, event_loop): +async def test_websocket_block_event(aiohttp_client): user_priv, user_pub = crypto.generate_key_pair() tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) - blk_source = asyncio.Queue(loop=event_loop) - tx_source = asyncio.Queue(loop=event_loop) - app = init_app(tx_source, blk_source, loop=event_loop) + app = init_app(None) client = await aiohttp_client(app) ws = await client.ws_connect(EVENTS_ENDPOINT_BLOCKS) block = { @@ -153,7 +91,8 @@ async def test_websocket_block_event(aiohttp_client, event_loop): "transactions": [tx], } block_event = events.Event(events.EventTypes.BLOCK_VALID, block) - + blk_source = Dispatcher.get_queue_on_demand(app, "blk_source") + tx_source = Dispatcher.get_queue_on_demand(app, "tx_source") await blk_source.put(block_event) result = await ws.receive() @@ -164,20 +103,21 @@ async def test_websocket_block_event(aiohttp_client, event_loop): assert json_result["transaction_ids"][0] == tx.id await blk_source.put(events.POISON_PILL) + await tx_source.put(events.POISON_PILL) @pytest.mark.asyncio -async def test_websocket_transaction_event(aiohttp_client, event_loop): +async def test_websocket_transaction_event(aiohttp_client): user_priv, user_pub = crypto.generate_key_pair() tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) - blk_source = asyncio.Queue(loop=event_loop) - tx_source = asyncio.Queue(loop=event_loop) - app = init_app(tx_source, blk_source, loop=event_loop) + app = init_app(None) client = await aiohttp_client(app) ws = await client.ws_connect(EVENTS_ENDPOINT) block = {"height": 1, "transactions": [tx]} + blk_source = Dispatcher.get_queue_on_demand(app, "blk_source") + tx_source = Dispatcher.get_queue_on_demand(app, "tx_source") block_event = events.Event(events.EventTypes.BLOCK_VALID, block) await tx_source.put(block_event) @@ -190,20 +130,22 @@ async def test_websocket_transaction_event(aiohttp_client, event_loop): assert json_result["asset_ids"] == [tx.id] assert json_result["height"] == block["height"] + await blk_source.put(events.POISON_PILL) await tx_source.put(events.POISON_PILL) @pytest.mark.asyncio -async def test_websocket_string_event(aiohttp_client, event_loop): +async def test_websocket_string_event(aiohttp_client): from planetmint.ipc.events import POISON_PILL from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT - blk_source = asyncio.Queue(loop=event_loop) - tx_source = asyncio.Queue(loop=event_loop) - app = init_app(tx_source, blk_source, loop=event_loop) + app = init_app(None) client = await aiohttp_client(app) ws = await client.ws_connect(EVENTS_ENDPOINT) + blk_source = Dispatcher.get_queue_on_demand(app, "blk_source") + tx_source = Dispatcher.get_queue_on_demand(app, "tx_source") + await tx_source.put("hack") await tx_source.put("the") await tx_source.put("planet!") @@ -217,7 +159,8 @@ async def test_websocket_string_event(aiohttp_client, event_loop): result = await ws.receive() assert result.data == "planet!" - await tx_source.put(POISON_PILL) + await blk_source.put(events.POISON_PILL) + await tx_source.put(events.POISON_PILL) @pytest.mark.skip("Processes are not stopping properly, and the whole test suite would hang")