asyncio - removed deprecation (#372)

* improved connection error and termination handling
* removed keyboard termination: exception
* fixed test cases
* added python >= 3.10 compatibility

Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
This commit is contained in:
Jürgen Eckel 2023-04-02 23:36:37 +02:00 committed by GitHub
parent 08ce10ab1f
commit e69742808f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 292 additions and 304 deletions

View File

@ -69,5 +69,12 @@ class ABCI_RPC:
"params": [encode_transaction(tx_dict)], "params": [encode_transaction(tx_dict)],
"id": str(uuid4()), "id": str(uuid4()),
} }
# TODO: handle connection errors! try:
return requests.post(endpoint, json=payload) 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

View File

@ -10,7 +10,7 @@ import pymongo
from planetmint.config import Config from planetmint.config import Config
from planetmint.backend.exceptions import DuplicateKeyError, OperationError, ConnectionError from planetmint.backend.exceptions import DuplicateKeyError, OperationError, ConnectionError
from transactions.common.exceptions import ConfigurationError 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 from planetmint.backend.connection import DBConnection, _kwargs_parser
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -9,7 +9,7 @@ import tarantool
from planetmint.config import Config from planetmint.config import Config
from transactions.common.exceptions import ConfigurationError 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.connection import DBConnection
from planetmint.backend.exceptions import ConnectionError from planetmint.backend.exceptions import ConnectionError

View File

@ -3,8 +3,7 @@ import logging
import os import os
from decouple import config from decouple import config
from planetmint.utils import Singleton from planetmint.utils.singleton import Singleton
from planetmint.version import __version__
class Config(metaclass=Singleton): class Config(metaclass=Singleton):

View File

@ -1,9 +1,12 @@
from queue import Empty from queue import Empty
from collections import defaultdict from collections import defaultdict
import multiprocessing import multiprocessing
import logging
from planetmint.ipc.events import EventTypes, POISON_PILL from planetmint.ipc.events import EventTypes, POISON_PILL
logger = logging.getLogger(__name__)
class Exchange: class Exchange:
"""Dispatch events to subscribers.""" """Dispatch events to subscribers."""
@ -63,10 +66,14 @@ class Exchange:
def run(self): def run(self):
"""Start the exchange""" """Start the exchange"""
self.started_queue.put("STARTED") self.started_queue.put("STARTED")
try:
while True: while True:
event = self.publisher_queue.get() event = self.publisher_queue.get()
if event == POISON_PILL: if event == POISON_PILL:
return return
else: else:
self.dispatch(event) self.dispatch(event)
except KeyboardInterrupt:
return
except Exception as e:
logger.debug(f"Exchange Exception: {e}")

View File

@ -13,7 +13,7 @@ from planetmint.abci.parallel_validation import ParallelValidationApp
from planetmint.web import server, websocket_server from planetmint.web import server, websocket_server
from planetmint.ipc.events import EventTypes from planetmint.ipc.events import EventTypes
from planetmint.ipc.exchange import Exchange from planetmint.ipc.exchange import Exchange
from planetmint.utils import Process from planetmint.utils.processes import Process
from planetmint.version import __version__ from planetmint.version import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

44
planetmint/utils/lazy.py Normal file
View File

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

View File

@ -10,15 +10,6 @@ import multiprocessing
import setproctitle 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): class Process(multiprocessing.Process):
"""Wrapper around multiprocessing.Process that uses """Wrapper around multiprocessing.Process that uses
setproctitle to set the name of the process when running setproctitle to set the name of the process when running
@ -83,49 +74,3 @@ def pool(builder, size, timeout=None):
local_pool.put(instance) local_pool.put(instance)
return pooled 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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import gunicorn.app.base
from flask import Flask from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from planetmint import utils from planetmint.utils import processes
from planetmint.application.validator import Validator from planetmint.application.validator import Validator
from planetmint.web.routes import add_routes from planetmint.web.routes import add_routes
from planetmint.web.strip_content_type_middleware import StripContentTypeMiddleware 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.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) add_routes(app)

View File

@ -104,9 +104,14 @@ class TransactionListApi(Resource):
but this node only accepts transaction with higher \ but this node only accepts transaction with higher \
schema version number.", schema version number.",
) )
try:
status_code, message = ABCI_RPC().write_transaction( status_code, message = ABCI_RPC().write_transaction(
MODE_LIST, ABCI_RPC().tendermint_rpc_endpoint, MODE_COMMIT, tx_obj, mode 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: if status_code == 202:
response = jsonify(tx) response = jsonify(tx)

View File

@ -3,11 +3,15 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0 # Code is Apache-2.0 and docs are CC-BY-4.0
import json import json
import logging
import asyncio
from planetmint.ipc.events import EventTypes from planetmint.ipc.events import EventTypes
from planetmint.ipc.events import POISON_PILL from planetmint.ipc.events import POISON_PILL
from planetmint.utils.python import is_above_py39
logger = logging.getLogger(__name__)
class Dispatcher: class Dispatcher:
@ -16,15 +20,13 @@ class Dispatcher:
This class implements a simple publish/subscribe pattern. This class implements a simple publish/subscribe pattern.
""" """
def __init__(self, event_source, type="tx"): def __init__(self, type="tx"):
"""Create a new instance. """Create a new instance.
Args: Args:
event_source: a source of events. Elements in the queue type: a string identifier.
should be strings.
""" """
self.event_source = event_source
self.subscribers = {} self.subscribers = {}
self.type = type self.type = type
@ -54,6 +56,18 @@ class Dispatcher:
txids.append(tx.id) txids.append(tx.id)
return {"height": block["height"], "hash": block["hash"], "transaction_ids": txids} 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 @staticmethod
def eventify_block(block): def eventify_block(block):
for tx in block["transactions"]: for tx in block["transactions"]:
@ -67,16 +81,19 @@ class Dispatcher:
asset_ids = [tx.id] asset_ids = [tx.id]
yield {"height": block["height"], "asset_ids": asset_ids, "transaction_id": 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.""" """Publish new events to the subscribers."""
logger.debug(f"DISPATCHER CALLED : {self.type}")
while True: 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 = [] str_buffer = []
if event == POISON_PILL: if event == POISON_PILL:
return return
logger.debug(f"DISPATCHER ELEMENT : {event}")
if isinstance(event, str): if isinstance(event, str):
str_buffer.append(event) str_buffer.append(event)
elif event.type == EventTypes.BLOCK_VALID: elif event.type == EventTypes.BLOCK_VALID:

View File

@ -18,7 +18,6 @@
import asyncio import asyncio
import logging import logging
import threading
import aiohttp import aiohttp
from uuid import uuid4 from uuid import uuid4
@ -26,25 +25,34 @@ from concurrent.futures import CancelledError
from planetmint.config import Config from planetmint.config import Config
from planetmint.web.websocket_dispatcher import Dispatcher from planetmint.web.websocket_dispatcher import Dispatcher
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
EVENTS_ENDPOINT = "/api/v1/streams/valid_transactions" EVENTS_ENDPOINT = "/api/v1/streams/valid_transactions"
EVENTS_ENDPOINT_BLOCKS = "/api/v1/streams/valid_blocks" EVENTS_ENDPOINT_BLOCKS = "/api/v1/streams/valid_blocks"
def _multiprocessing_to_asyncio(in_queue, out_queue1, out_queue2, loop): async def access_queue(app):
"""Bridge between a synchronous multiprocessing queue if app["event_source"] == None:
and an asynchronous asyncio queue. return
in_queue = app["event_source"]
Args: tx_source = Dispatcher.get_queue_on_demand(app, "tx_source")
in_queue (multiprocessing.Queue): input queue blk_source = Dispatcher.get_queue_on_demand(app, "blk_source")
out_queue (asyncio.Queue): output queue logger.debug(f"REROUTING CALLED")
""" try:
while True: while True:
value = in_queue.get() try:
loop.call_soon_threadsafe(out_queue1.put_nowait, value) if not in_queue.empty():
loop.call_soon_threadsafe(out_queue2.put_nowait, value) 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): async def websocket_tx_handler(request):
@ -60,6 +68,7 @@ async def websocket_tx_handler(request):
# Consume input buffer # Consume input buffer
try: try:
msg = await websocket.receive() msg = await websocket.receive()
logger.debug(f"TX HANDLER: {msg}")
except RuntimeError as e: except RuntimeError as e:
logger.debug("Websocket exception: %s", str(e)) logger.debug("Websocket exception: %s", str(e))
break break
@ -90,6 +99,7 @@ async def websocket_blk_handler(request):
# Consume input buffer # Consume input buffer
try: try:
msg = await websocket.receive() msg = await websocket.receive()
logger.debug(f"BLK HANDLER: {msg}")
except RuntimeError as e: except RuntimeError as e:
logger.debug("Websocket exception: %s", str(e)) logger.debug("Websocket exception: %s", str(e))
break break
@ -107,43 +117,33 @@ async def websocket_blk_handler(request):
return websocket return websocket
def init_app(tx_source, blk_source, *, loop=None): async def start_background_tasks(app):
"""Init the application server. blk_dispatcher = app["blk_dispatcher"]
app["task1"] = asyncio.create_task(blk_dispatcher.publish(app), name="blk")
Return: tx_dispatcher = app["tx_dispatcher"]
An aiohttp application. app["task2"] = asyncio.create_task(tx_dispatcher.publish(app), name="tx")
"""
blk_dispatcher = Dispatcher(blk_source, "blk") app["task3"] = asyncio.create_task(access_queue(app), name="router")
tx_dispatcher = Dispatcher(tx_source, "tx")
# 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) def init_app(sync_event_source):
app["tx_dispatcher"] = tx_dispatcher """Create and start the WebSocket server."""
app["blk_dispatcher"] = blk_dispatcher 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, websocket_tx_handler)
app.router.add_get(EVENTS_ENDPOINT_BLOCKS, websocket_blk_handler) app.router.add_get(EVENTS_ENDPOINT_BLOCKS, websocket_blk_handler)
app.on_startup.append(start_background_tasks)
return app return app
def start(sync_event_source, loop=None): def start(sync_event_source):
"""Create and start the WebSocket server.""" app = init_app(sync_event_source)
aiohttp.web.run_app(app, host=Config().get()["wsserver"]["host"], port=Config().get()["wsserver"]["port"])
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
)

169
poetry.lock generated
View File

@ -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]] [[package]]
name = "aafigure" name = "aafigure"
@ -33,91 +33,106 @@ dev = ["black", "build", "pytest", "pytest-cov", "twine"]
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
version = "3.8.1" version = "3.8.4"
description = "Async http client/server framework (asyncio)" description = "Async http client/server framework (asyncio)"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"},
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"},
{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.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"},
{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.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"},
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"},
{file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"},
{file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"},
{file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"},
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"},
{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.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"},
{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.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, {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.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"},
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"},
{file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"},
{file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"},
{file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"},
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"},
{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.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"},
{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.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, {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.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"},
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"},
{file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"},
{file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"},
{file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"},
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"},
{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.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"},
{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.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"},
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"},
{file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"},
{file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"},
{file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"},
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"},
{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.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"},
{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.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"},
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"},
{file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"},
{file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"},
{file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, {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] [package.dependencies]
aiosignal = ">=1.1.2" aiosignal = ">=1.1.2"
async-timeout = ">=4.0.0a3,<5.0" async-timeout = ">=4.0.0a3,<5.0"
attrs = ">=17.3.0" attrs = ">=17.3.0"
charset-normalizer = ">=2.0,<3.0" charset-normalizer = ">=2.0,<4.0"
frozenlist = ">=1.1.1" frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0" multidict = ">=4.5,<7.0"
yarl = ">=1.0,<2.0" yarl = ">=1.0,<2.0"
@ -2311,7 +2326,7 @@ cffi = ">=1.4.1"
six = "*" six = "*"
[package.extras] [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)"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]] [[package]]
@ -3461,4 +3476,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "8b9c0f4ebc41bca0af100c124f3522e2e1052271ebde525b0b06b44496a2d105" content-hash = "ad5feda1537dc9cde8ae983dafedb9a38acc71acdbe425505027f77f05c04a98"

View File

@ -25,7 +25,7 @@ planetmint = "planetmint.commands.planetmint:main"
python = "^3.9" python = "^3.9"
chardet = "3.0.4" chardet = "3.0.4"
base58 = "2.1.1" base58 = "2.1.1"
aiohttp = "3.8.1" aiohttp = "^3.8.4"
flask-cors = "3.0.10" flask-cors = "3.0.10"
flask-restful = "0.3.9" flask-restful = "0.3.9"
flask = "2.1.2" flask = "2.1.2"

View File

@ -445,22 +445,13 @@ def abci_http(_setup_database, _configure_planetmint, abci_server, tendermint_ho
return False 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") @pytest.fixture(scope="session")
def abci_server(): def abci_server():
from abci.server import ABCIServer from abci.server import ABCIServer
# from tendermint.abci import types_pb2 as types_v0_34_11 # from tendermint.abci import types_pb2 as types_v0_34_11
from planetmint.abci.application_logic import ApplicationLogic from planetmint.abci.application_logic import ApplicationLogic
from planetmint.utils import Process from planetmint.utils.processes import Process
app = ABCIServer(app=ApplicationLogic()) app = ABCIServer(app=ApplicationLogic())
abci_proxy = Process(name="ABCI", target=app.run) abci_proxy = Process(name="ABCI", target=app.run)

View File

@ -7,6 +7,7 @@ import queue
import pytest import pytest
from unittest.mock import patch, call from unittest.mock import patch, call
from planetmint.utils import processes
@pytest.fixture @pytest.fixture
@ -32,9 +33,7 @@ def mock_queue(monkeypatch):
def test_empty_pool_is_populated_with_instances(mock_queue): def test_empty_pool_is_populated_with_instances(mock_queue):
from planetmint import utils pool = processes.pool(lambda: "hello", 4)
pool = utils.pool(lambda: "hello", 4)
assert len(mock_queue.items) == 0 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): def test_pool_blocks_if_no_instances_available(mock_queue):
from planetmint import utils pool = processes.pool(lambda: "hello", 4)
pool = utils.pool(lambda: "hello", 4)
assert len(mock_queue.items) == 0 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): def test_pool_raises_empty_exception_when_timeout(mock_queue):
from planetmint import utils pool = processes.pool(lambda: "hello", 1, timeout=1)
pool = utils.pool(lambda: "hello", 1, timeout=1)
assert len(mock_queue.items) == 0 assert len(mock_queue.items) == 0
@ -141,7 +136,7 @@ def test_process_group_instantiates_and_start_processes(mock_process):
def test_lazy_execution(): def test_lazy_execution():
from planetmint.utils import Lazy from planetmint.utils.lazy import Lazy
lz = Lazy() lz = Lazy()
lz.split(",")[1].split(" ").pop(1).strip() lz.split(",")[1].split(" ").pop(1).strip()
@ -164,7 +159,7 @@ def test_process_set_title():
from uuid import uuid4 from uuid import uuid4
from multiprocessing import Queue from multiprocessing import Queue
from setproctitle import getproctitle from setproctitle import getproctitle
from planetmint.utils import Process from planetmint.utils.processes import Process
queue = Queue() queue = Queue()
uuid = str(uuid4()) uuid = str(uuid4())

View File

@ -2,7 +2,7 @@
# Planetmint and IPDB software contributors. # Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0 # Code is Apache-2.0 and docs are CC-BY-4.0
import aiohttp
import asyncio import asyncio
import json import json
import queue import queue
@ -16,6 +16,7 @@ from transactions.common import crypto
from planetmint.ipc import events from planetmint.ipc import events
from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT, EVENTS_ENDPOINT_BLOCKS from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT, EVENTS_ENDPOINT_BLOCKS
from ipld import multihash, marshal from ipld import multihash, marshal
from planetmint.web.websocket_dispatcher import Dispatcher
class MockWebSocket: class MockWebSocket:
@ -76,75 +77,12 @@ def test_simplified_block_works():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_bridge_sync_async_queue(event_loop): async def test_websocket_block_event(aiohttp_client):
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):
user_priv, user_pub = crypto.generate_key_pair() user_priv, user_pub = crypto.generate_key_pair()
tx = Create.generate([user_pub], [([user_pub], 1)]) tx = Create.generate([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
blk_source = asyncio.Queue(loop=event_loop) app = init_app(None)
tx_source = asyncio.Queue(loop=event_loop)
app = init_app(tx_source, blk_source, loop=event_loop)
client = await aiohttp_client(app) client = await aiohttp_client(app)
ws = await client.ws_connect(EVENTS_ENDPOINT_BLOCKS) ws = await client.ws_connect(EVENTS_ENDPOINT_BLOCKS)
block = { block = {
@ -153,7 +91,8 @@ async def test_websocket_block_event(aiohttp_client, event_loop):
"transactions": [tx], "transactions": [tx],
} }
block_event = events.Event(events.EventTypes.BLOCK_VALID, block) 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) await blk_source.put(block_event)
result = await ws.receive() 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 assert json_result["transaction_ids"][0] == tx.id
await blk_source.put(events.POISON_PILL) await blk_source.put(events.POISON_PILL)
await tx_source.put(events.POISON_PILL)
@pytest.mark.asyncio @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() user_priv, user_pub = crypto.generate_key_pair()
tx = Create.generate([user_pub], [([user_pub], 1)]) tx = Create.generate([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
blk_source = asyncio.Queue(loop=event_loop) app = init_app(None)
tx_source = asyncio.Queue(loop=event_loop)
app = init_app(tx_source, blk_source, loop=event_loop)
client = await aiohttp_client(app) client = await aiohttp_client(app)
ws = await client.ws_connect(EVENTS_ENDPOINT) ws = await client.ws_connect(EVENTS_ENDPOINT)
block = {"height": 1, "transactions": [tx]} 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) block_event = events.Event(events.EventTypes.BLOCK_VALID, block)
await tx_source.put(block_event) 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["asset_ids"] == [tx.id]
assert json_result["height"] == block["height"] assert json_result["height"] == block["height"]
await blk_source.put(events.POISON_PILL)
await tx_source.put(events.POISON_PILL) await tx_source.put(events.POISON_PILL)
@pytest.mark.asyncio @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.ipc.events import POISON_PILL
from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT from planetmint.web.websocket_server import init_app, EVENTS_ENDPOINT
blk_source = asyncio.Queue(loop=event_loop) app = init_app(None)
tx_source = asyncio.Queue(loop=event_loop)
app = init_app(tx_source, blk_source, loop=event_loop)
client = await aiohttp_client(app) client = await aiohttp_client(app)
ws = await client.ws_connect(EVENTS_ENDPOINT) 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("hack")
await tx_source.put("the") await tx_source.put("the")
await tx_source.put("planet!") await tx_source.put("planet!")
@ -217,7 +159,8 @@ async def test_websocket_string_event(aiohttp_client, event_loop):
result = await ws.receive() result = await ws.receive()
assert result.data == "planet!" 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") @pytest.mark.skip("Processes are not stopping properly, and the whole test suite would hang")