pax_global_header 0000666 0000000 0000000 00000000064 14737314232 0014520 g ustar 00root root 0000000 0000000 52 comment=40996b578546e18a0dff313dda8e9a7d6b586525
python-aiormq-6.8.1/ 0000775 0000000 0000000 00000000000 14737314232 0014343 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/.coveragerc 0000664 0000000 0000000 00000000100 14737314232 0016453 0 ustar 00root root 0000000 0000000 [run]
branch = True
omit =
*/env/*
*/tests/*
*/.*/*
python-aiormq-6.8.1/.editorconfig 0000664 0000000 0000000 00000000435 14737314232 0017022 0 ustar 00root root 0000000 0000000 root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{py,yml}]
indent_style = space
[*.py]
indent_size = 4
[docs/**.py]
max_line_length = 80
[*.rst]
indent_size = 4
[Makefile]
indent_style = tab
[*.yml]
indent_size = 2
python-aiormq-6.8.1/.github/ 0000775 0000000 0000000 00000000000 14737314232 0015703 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14737314232 0017740 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/.github/workflows/tests.yml 0000664 0000000 0000000 00000006471 14737314232 0021635 0 ustar 00root root 0000000 0000000 name: tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
pylama:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup python3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Cache virtualenv
id: venv-cache
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ github.job }}-${{ github.ref }}
- run: python -m pip install poetry
- run: poetry install
- run: poetry run pylama
env:
FORCE_COLOR: 1
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup python3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Cache virtualenv
id: venv-cache
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ github.job }}-${{ github.ref }}
- run: python -m pip install poetry
- run: poetry install
- run: poetry run mypy
env:
FORCE_COLOR: 1
docs:
runs-on: ubuntu-latest
services:
rabbitmq:
image: docker://mosquito/aiormq-rabbitmq
ports:
- 5672:5672
- 5671:5671
steps:
- uses: actions/checkout@v2
- name: Setup python3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Cache virtualenv
id: venv-cache
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ github.job }}-${{ github.ref }}
- run: python -m pip install poetry
- run: poetry install
- run: poetry run pytest -svv README.rst
env:
FORCE_COLOR: 1
tests:
runs-on: ubuntu-latest
services:
rabbitmq:
image: docker://mosquito/aiormq-rabbitmq
ports:
- 5672:5672
- 5671:5671
strategy:
fail-fast: false
matrix:
python:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
steps:
- uses: actions/checkout@v2
- name: Setup python${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: "${{ matrix.python }}"
- name: Cache virtualenv
id: venv-cache
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ github.job }}-${{ github.ref }}-${{ matrix.python }}
- run: python -m pip install poetry
- run: poetry install --with=uvloop
- name: pytest
run: >-
poetry run pytest \
-vv \
--cov=aiormq \
--cov-report=term-missing \
--doctest-modules \
--aiomisc-test-timeout=120 \
tests
env:
FORCE_COLOR: 1
- run: poetry run coveralls
env:
COVERALLS_PARALLEL: 'true'
COVERALLS_SERVICE_NAME: github
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
finish:
needs:
- tests
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
python-aiormq-6.8.1/.gitignore 0000664 0000000 0000000 00000003737 14737314232 0016345 0 ustar 00root root 0000000 0000000 # Created by .ignore support plugin (hsz.mobi)
### VirtualEnv template
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
.Python
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
.venv
pip-selfcheck.json
### IPythonNotebook template
# Temporary data
.ipynb_checkpoints/
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
docs/source/apidoc
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# pytest
.pytest_cache
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
/htmlcov
/temp
.DS_Store
.*cache
python-aiormq-6.8.1/COPYING 0000664 0000000 0000000 00000024330 14737314232 0015400 0 ustar 00root root 0000000 0000000 Apache License
==============
_Version 2.0, January 2004_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright 2023 Dmitry Orlov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
python-aiormq-6.8.1/Makefile 0000664 0000000 0000000 00000001150 14737314232 0016000 0 ustar 00root root 0000000 0000000 all: clean test
NAME:=$(shell poetry version -n | awk '{print $1}')
VERSION:=$(shell poetry version -s)
RABBITMQ_CONTAINER_NAME:=aiormq_rabbitmq
RABBITMQ_IMAGE:=mosquito/aiormq-rabbitmq
rabbitmq:
docker kill $(RABBITMQ_CONTAINER_NAME) || true
docker run --pull=always --rm -d \
--name $(RABBITMQ_CONTAINER_NAME) \
-p 5671:5671 \
-p 5672:5672 \
-p 15671:15671 \
-p 15672:15672 \
$(RABBITMQ_IMAGE)
upload:
poetry publish --build --skip-existing
test:
poetry run pytest -vvx --cov=aiormq \
--cov-report=term-missing tests README.rst
clean:
rm -fr *.egg-info .tox
develop: clean
poetry install
python-aiormq-6.8.1/README.rst 0000664 0000000 0000000 00000042022 14737314232 0016032 0 ustar 00root root 0000000 0000000 ======
AIORMQ
======
.. image:: https://coveralls.io/repos/github/mosquito/aiormq/badge.svg?branch=master
:target: https://coveralls.io/github/mosquito/aiormq?branch=master
:alt: Coveralls
.. image:: https://img.shields.io/pypi/status/aiormq.svg
:target: https://github.com/mosquito/aiormq
:alt: Status
.. image:: https://github.com/mosquito/aiormq/workflows/tests/badge.svg
:target: https://github.com/mosquito/aiormq/actions?query=workflow%3Atests
:alt: Build status
.. image:: https://img.shields.io/pypi/v/aiormq.svg
:target: https://pypi.python.org/pypi/aiormq/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/wheel/aiormq.svg
:target: https://pypi.python.org/pypi/aiormq/
.. image:: https://img.shields.io/pypi/pyversions/aiormq.svg
:target: https://pypi.python.org/pypi/aiormq/
.. image:: https://img.shields.io/pypi/l/aiormq.svg
:target: https://github.com/mosquito/aiormq/blob/master/LICENSE.md
aiormq is a pure python AMQP client library.
.. contents:: Table of contents
Status
======
* 3.x.x branch - Production/Stable
* 4.x.x branch - Unstable (Experimental)
* 5.x.x and greater is only Production/Stable releases.
Features
========
* Connecting by URL
* amqp example: **amqp://user:password@server.host/vhost**
* secure amqp example: **amqps://user:password@server.host/vhost?cafile=ca.pem&keyfile=key.pem&certfile=cert.pem&no_verify_ssl=0**
* Buffered queue for received frames
* Only `PLAIN`_ auth mechanism support
* `Publisher confirms`_ support
* `Transactions`_ support
* Channel based asynchronous locks
.. note::
AMQP 0.9.1 requires serialize sending for some frame types
on the channel. e.g. Content body must be following after
content header. But frames might be sent asynchronously
on another channels.
* Tracking unroutable messages
(Use **connection.channel(on_return_raises=False)** for disabling)
* Full SSL/TLS support, using your choice of:
* ``amqps://`` url query parameters:
* ``cafile=`` - string contains path to ca certificate file
* ``capath=`` - string contains path to ca certificates
* ``cadata=`` - base64 encoded ca certificate data
* ``keyfile=`` - string contains path to key file
* ``certfile=`` - string contains path to certificate file
* ``no_verify_ssl`` - boolean disables certificates validation
* ``context=`` `SSLContext`_ keyword argument to ``connect()``.
* Python `type hints`_
* Uses `pamqp`_ as an AMQP 0.9.1 frame encoder/decoder
.. _Publisher confirms: https://www.rabbitmq.com/confirms.html
.. _Transactions: https://www.rabbitmq.com/semantics.html
.. _PLAIN: https://www.rabbitmq.com/authentication.html
.. _type hints: https://docs.python.org/3/library/typing.html
.. _pamqp: https://pypi.org/project/pamqp/
.. _SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext
Tutorial
========
Introduction
------------
Simple consumer
***************
.. code-block:: python
import asyncio
import aiormq
async def on_message(message):
"""
on_message doesn't necessarily have to be defined as async.
Here it is to show that it's possible.
"""
print(f" [x] Received message {message!r}")
print(f"Message body is: {message.body!r}")
print("Before sleep!")
await asyncio.sleep(5) # Represents async I/O operations
print("After sleep!")
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
# Declaring queue
declare_ok = await channel.queue_declare('helo')
consume_ok = await channel.basic_consume(
declare_ok.queue, on_message, no_ack=True
)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
Simple publisher
****************
.. code-block:: python
:name: test_simple_publisher
import asyncio
from typing import Optional
import aiormq
from aiormq.abc import DeliveredMessage
MESSAGE: Optional[DeliveredMessage] = None
async def main():
global MESSAGE
body = b'Hello World!'
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost//")
# Creating a channel
channel = await connection.channel()
declare_ok = await channel.queue_declare("hello", auto_delete=True)
# Sending the message
await channel.basic_publish(body, routing_key='hello')
print(f" [x] Sent {body}")
MESSAGE = await channel.basic_get(declare_ok.queue)
print(f" [x] Received message from {declare_ok.queue!r}")
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
assert MESSAGE is not None
assert MESSAGE.routing_key == "hello"
assert MESSAGE.body == b'Hello World!'
Work Queues
-----------
Create new task
***************
.. code-block:: python
import sys
import asyncio
import aiormq
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
body = b' '.join(sys.argv[1:]) or b"Hello World!"
# Sending the message
await channel.basic_publish(
body,
routing_key='task_queue',
properties=aiormq.spec.Basic.Properties(
delivery_mode=1,
)
)
print(f" [x] Sent {body!r}")
await connection.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Simple worker
*************
.. code-block:: python
import asyncio
import aiormq
import aiormq.abc
async def on_message(message: aiormq.abc.DeliveredMessage):
print(f" [x] Received message {message!r}")
print(f" Message body is: {message.body!r}")
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
await channel.basic_qos(prefetch_count=1)
# Declaring queue
declare_ok = await channel.queue_declare('task_queue', durable=True)
# Start listening the queue with name 'task_queue'
await channel.basic_consume(declare_ok.queue, on_message, no_ack=True)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# we enter a never-ending loop that waits for data and runs
# callbacks whenever necessary.
print(" [*] Waiting for messages. To exit press CTRL+C")
loop.run_forever()
Publish Subscribe
-----------------
Publisher
*********
.. code-block:: python
import sys
import asyncio
import aiormq
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
await channel.exchange_declare(
exchange='logs', exchange_type='fanout'
)
body = b' '.join(sys.argv[1:]) or b"Hello World!"
# Sending the message
await channel.basic_publish(
body, routing_key='info', exchange='logs'
)
print(f" [x] Sent {body!r}")
await connection.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Subscriber
**********
.. code-block:: python
import asyncio
import aiormq
import aiormq.abc
async def on_message(message: aiormq.abc.DeliveredMessage):
print(f"[x] {message.body!r}")
await message.channel.basic_ack(
message.delivery.delivery_tag
)
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
await channel.basic_qos(prefetch_count=1)
await channel.exchange_declare(
exchange='logs', exchange_type='fanout'
)
# Declaring queue
declare_ok = await channel.queue_declare(exclusive=True)
# Binding the queue to the exchange
await channel.queue_bind(declare_ok.queue, 'logs')
# Start listening the queue with name 'task_queue'
await channel.basic_consume(declare_ok.queue, on_message)
loop = asyncio.get_event_loop()
loop.create_task(main())
# we enter a never-ending loop that waits for data
# and runs callbacks whenever necessary.
print(' [*] Waiting for logs. To exit press CTRL+C')
loop.run_forever()
Routing
-------
Direct consumer
***************
.. code-block:: python
import sys
import asyncio
import aiormq
import aiormq.abc
async def on_message(message: aiormq.abc.DeliveredMessage):
print(f" [x] {message.delivery.routing_key!r}:{message.body!r}"
await message.channel.basic_ack(
message.delivery.delivery_tag
)
async def main():
# Perform connection
connection = aiormq.Connection("amqp://guest:guest@localhost/")
await connection.connect()
# Creating a channel
channel = await connection.channel()
await channel.basic_qos(prefetch_count=1)
severities = sys.argv[1:]
if not severities:
sys.stderr.write(f"Usage: {sys.argv[0]} [info] [warning] [error]\n")
sys.exit(1)
# Declare an exchange
await channel.exchange_declare(
exchange='logs', exchange_type='direct'
)
# Declaring random queue
declare_ok = await channel.queue_declare(durable=True, auto_delete=True)
for severity in severities:
await channel.queue_bind(
declare_ok.queue, 'logs', routing_key=severity
)
# Start listening the random queue
await channel.basic_consume(declare_ok.queue, on_message)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# we enter a never-ending loop that waits for data
# and runs callbacks whenever necessary.
print(" [*] Waiting for messages. To exit press CTRL+C")
loop.run_forever()
Emitter
*******
.. code-block:: python
import sys
import asyncio
import aiormq
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
await channel.exchange_declare(
exchange='logs', exchange_type='direct'
)
body = (
b' '.join(arg.encode() for arg in sys.argv[2:])
or
b"Hello World!"
)
# Sending the message
routing_key = sys.argv[1] if len(sys.argv) > 2 else 'info'
await channel.basic_publish(
body, exchange='logs', routing_key=routing_key,
properties=aiormq.spec.Basic.Properties(
delivery_mode=1
)
)
print(f" [x] Sent {body!r}")
await connection.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Topics
------
Publisher
*********
.. code-block:: python
import sys
import asyncio
import aiormq
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
await channel.exchange_declare('topic_logs', exchange_type='topic')
routing_key = (
sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
)
body = (
b' '.join(arg.encode() for arg in sys.argv[2:])
or
b"Hello World!"
)
# Sending the message
await channel.basic_publish(
body, exchange='topic_logs', routing_key=routing_key,
properties=aiormq.spec.Basic.Properties(
delivery_mode=1
)
)
print(f" [x] Sent {body!r}")
await connection.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Consumer
********
.. code-block:: python
import asyncio
import sys
import aiormq
import aiormq.abc
async def on_message(message: aiormq.abc.DeliveredMessage):
print(f" [x] {message.delivery.routing_key!r}:{message.body!r}")
await message.channel.basic_ack(
message.delivery.delivery_tag
)
async def main():
# Perform connection
connection = await aiormq.connect(
"amqp://guest:guest@localhost/", loop=loop
)
# Creating a channel
channel = await connection.channel()
await channel.basic_qos(prefetch_count=1)
# Declare an exchange
await channel.exchange_declare('topic_logs', exchange_type='topic')
# Declaring queue
declare_ok = await channel.queue_declare('task_queue', durable=True)
binding_keys = sys.argv[1:]
if not binding_keys:
sys.stderr.write(
f"Usage: {sys.argv[0]} [binding_key]...\n"
)
sys.exit(1)
for binding_key in binding_keys:
await channel.queue_bind(
declare_ok.queue, 'topic_logs', routing_key=binding_key
)
# Start listening the queue with name 'task_queue'
await channel.basic_consume(declare_ok.queue, on_message)
loop = asyncio.get_event_loop()
loop.create_task(main())
# we enter a never-ending loop that waits for
# data and runs callbacks whenever necessary.
print(" [*] Waiting for messages. To exit press CTRL+C")
loop.run_forever()
Remote procedure call (RPC)
---------------------------
RPC server
**********
.. code-block:: python
import asyncio
import aiormq
import aiormq.abc
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
async def on_message(message:aiormq.abc.DeliveredMessage):
n = int(message.body.decode())
print(f" [.] fib({n})")
response = str(fib(n)).encode()
await message.channel.basic_publish(
response, routing_key=message.header.properties.reply_to,
properties=aiormq.spec.Basic.Properties(
correlation_id=message.header.properties.correlation_id
),
)
await message.channel.basic_ack(message.delivery.delivery_tag)
print('Request complete')
async def main():
# Perform connection
connection = await aiormq.connect("amqp://guest:guest@localhost/")
# Creating a channel
channel = await connection.channel()
# Declaring queue
declare_ok = await channel.queue_declare('rpc_queue')
# Start listening the queue with name 'hello'
await channel.basic_consume(declare_ok.queue, on_message)
loop = asyncio.get_event_loop()
loop.create_task(main())
# we enter a never-ending loop that waits for data
# and runs callbacks whenever necessary.
print(" [x] Awaiting RPC requests")
loop.run_forever()
RPC client
**********
.. code-block:: python
import asyncio
import uuid
import aiormq
import aiormq.abc
class FibonacciRpcClient:
def __init__(self):
self.connection = None # type: aiormq.Connection
self.channel = None # type: aiormq.Channel
self.callback_queue = ''
self.futures = {}
self.loop = loop
async def connect(self):
self.connection = await aiormq.connect("amqp://guest:guest@localhost/")
self.channel = await self.connection.channel()
declare_ok = await self.channel.queue_declare(
exclusive=True, auto_delete=True
)
await self.channel.basic_consume(declare_ok.queue, self.on_response)
self.callback_queue = declare_ok.queue
return self
async def on_response(self, message: aiormq.abc.DeliveredMessage):
future = self.futures.pop(message.header.properties.correlation_id)
future.set_result(message.body)
async def call(self, n):
correlation_id = str(uuid.uuid4())
future = loop.create_future()
self.futures[correlation_id] = future
await self.channel.basic_publish(
str(n).encode(), routing_key='rpc_queue',
properties=aiormq.spec.Basic.Properties(
content_type='text/plain',
correlation_id=correlation_id,
reply_to=self.callback_queue,
)
)
return int(await future)
async def main():
fibonacci_rpc = await FibonacciRpcClient().connect()
print(" [x] Requesting fib(30)")
response = await fibonacci_rpc.call(30)
print(r" [.] Got {response!r}")
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
python-aiormq-6.8.1/aiormq/ 0000775 0000000 0000000 00000000000 14737314232 0015633 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/aiormq/__init__.py 0000664 0000000 0000000 00000003262 14737314232 0017747 0 ustar 00root root 0000000 0000000 from pamqp import commands as spec
from . import abc
from .channel import Channel
from .connection import Connection, connect
from .exceptions import (
AMQPChannelError, AMQPConnectionError, AMQPError, AMQPException,
AuthenticationError, ChannelAccessRefused, ChannelClosed,
ChannelInvalidStateError, ChannelLockedResource, ChannelNotFoundEntity,
ChannelPreconditionFailed, ConnectionChannelError, ConnectionClosed,
ConnectionCommandInvalid, ConnectionFrameError, ConnectionInternalError,
ConnectionNotAllowed, ConnectionNotImplemented, ConnectionResourceError,
ConnectionSyntaxError, ConnectionUnexpectedFrame, DeliveryError,
DuplicateConsumerTag, IncompatibleProtocolError, InvalidFrameError,
MethodNotImplemented, ProbableAuthenticationError, ProtocolSyntaxError,
PublishError,
)
__all__ = (
"AMQPChannelError",
"AMQPConnectionError",
"AMQPError",
"AMQPException",
"AuthenticationError",
"Channel",
"ChannelAccessRefused",
"ChannelClosed",
"ChannelInvalidStateError",
"ChannelLockedResource",
"ChannelNotFoundEntity",
"ChannelPreconditionFailed",
"Connection",
"ConnectionChannelError",
"ConnectionClosed",
"ConnectionCommandInvalid",
"ConnectionFrameError",
"ConnectionInternalError",
"ConnectionNotAllowed",
"ConnectionNotImplemented",
"ConnectionResourceError",
"ConnectionSyntaxError",
"ConnectionUnexpectedFrame",
"DeliveryError",
"DuplicateConsumerTag",
"IncompatibleProtocolError",
"InvalidFrameError",
"MethodNotImplemented",
"ProbableAuthenticationError",
"ProtocolSyntaxError",
"PublishError",
"abc",
"connect",
"spec",
)
python-aiormq-6.8.1/aiormq/abc.py 0000664 0000000 0000000 00000041661 14737314232 0016742 0 ustar 00root root 0000000 0000000 import asyncio
import dataclasses
import io
import logging
from abc import ABC, abstractmethod, abstractproperty
from types import TracebackType
from typing import (
Any, Awaitable, Callable, Coroutine, Dict, Iterable, Optional, Set, Tuple,
Type, Union,
)
import pamqp
from pamqp import commands as spec
from pamqp.base import Frame
from pamqp.body import ContentBody
from pamqp.commands import Basic, Channel, Confirm, Exchange, Queue, Tx
from pamqp.common import FieldArray, FieldTable, FieldValue
from pamqp.constants import REPLY_SUCCESS
from pamqp.header import ContentHeader
from pamqp.heartbeat import Heartbeat
from yarl import URL
ExceptionType = Union[BaseException, Type[BaseException]]
# noinspection PyShadowingNames
class TaskWrapper:
__slots__ = "_exception", "task"
_exception: Union[BaseException, Type[BaseException]]
task: asyncio.Task
def __init__(self, task: asyncio.Task):
self.task = task
self._exception = asyncio.CancelledError
def throw(self, exception: ExceptionType) -> None:
self._exception = exception
self.task.cancel()
async def __inner(self) -> Any:
try:
return await self.task
except asyncio.CancelledError as e:
raise self._exception from e
def __await__(self, *args: Any, **kwargs: Any) -> Any:
return self.__inner().__await__()
def cancel(self) -> None:
return self.throw(asyncio.CancelledError())
def __getattr__(self, item: str) -> Any:
return getattr(self.task, item)
def __repr__(self) -> str:
return "<%s: %s>" % (self.__class__.__name__, repr(self.task))
TaskType = Union[asyncio.Task, TaskWrapper]
CoroutineType = Coroutine[Any, None, Any]
GetResultType = Union[Basic.GetEmpty, Basic.GetOk]
@dataclasses.dataclass(frozen=True)
class DeliveredMessage:
delivery: Union[spec.Basic.Deliver, spec.Basic.Return, GetResultType]
header: ContentHeader
body: bytes
channel: "AbstractChannel"
@property
def routing_key(self) -> Optional[str]:
if isinstance(
self.delivery, (
spec.Basic.Return,
spec.Basic.GetOk,
spec.Basic.Deliver,
),
):
return self.delivery.routing_key
return None
@property
def exchange(self) -> Optional[str]:
if isinstance(
self.delivery, (
spec.Basic.Return,
spec.Basic.GetOk,
spec.Basic.Deliver,
),
):
return self.delivery.exchange
return None
@property
def delivery_tag(self) -> Optional[int]:
if isinstance(
self.delivery, (
spec.Basic.GetOk,
spec.Basic.Deliver,
),
):
return self.delivery.delivery_tag
return None
@property
def redelivered(self) -> Optional[bool]:
if isinstance(
self.delivery, (
spec.Basic.GetOk,
spec.Basic.Deliver,
),
):
return self.delivery.redelivered
return None
@property
def consumer_tag(self) -> Optional[str]:
if isinstance(self.delivery, spec.Basic.Deliver):
return self.delivery.consumer_tag
return None
@property
def message_count(self) -> Optional[int]:
if isinstance(self.delivery, spec.Basic.GetOk):
return self.delivery.message_count
return None
ChannelRType = Tuple[int, Channel.OpenOk]
CallbackCoro = Coroutine[Any, Any, Any]
ConsumerCallback = Callable[[DeliveredMessage], CallbackCoro]
ReturnCallback = Callable[[DeliveredMessage], Any]
ArgumentsType = FieldTable
ConfirmationFrameType = Union[
Basic.Ack, Basic.Nack, Basic.Reject,
]
@dataclasses.dataclass(frozen=True)
class SSLCerts:
cert: Optional[str]
key: Optional[str]
capath: Optional[str]
cafile: Optional[str]
cadata: Optional[bytes]
verify: bool
@dataclasses.dataclass(frozen=True)
class FrameReceived:
channel: int
frame: str
URLorStr = Union[URL, str]
DrainResult = Awaitable[None]
TimeoutType = Optional[Union[float, int]]
FrameType = Union[Frame, ContentHeader, ContentBody]
RpcReturnType = Optional[
Union[
Basic.CancelOk,
Basic.ConsumeOk,
Basic.GetOk,
Basic.QosOk,
Basic.RecoverOk,
Channel.CloseOk,
Channel.FlowOk,
Channel.OpenOk,
Confirm.SelectOk,
Exchange.BindOk,
Exchange.DeclareOk,
Exchange.DeleteOk,
Exchange.UnbindOk,
Queue.BindOk,
Queue.DeleteOk,
Queue.DeleteOk,
Queue.PurgeOk,
Queue.UnbindOk,
Tx.CommitOk,
Tx.RollbackOk,
Tx.SelectOk,
]
]
@dataclasses.dataclass(frozen=True)
class ChannelFrame:
payload: bytes
should_close: bool
drain_future: Optional[asyncio.Future] = None
def drain(self) -> None:
if not self.should_drain:
return
if self.drain_future is not None and not self.drain_future.done():
self.drain_future.set_result(None)
@property
def should_drain(self) -> bool:
return self.drain_future is not None and not self.drain_future.done()
@classmethod
def marshall(
cls, channel_number: int,
frames: Iterable[Union[FrameType, Heartbeat, ContentBody]],
drain_future: Optional[asyncio.Future] = None,
) -> "ChannelFrame":
should_close = False
with io.BytesIO() as fp:
for frame in frames:
if should_close:
logger = logging.getLogger(
"aiormq.connection",
).getChild(
"marshall",
)
logger.warning(
"It looks like you are going to send a frame %r after "
"the connection is closed, it's pointless, "
"the frame is dropped.", frame,
)
continue
if isinstance(frame, spec.Connection.CloseOk):
should_close = True
fp.write(pamqp.frame.marshal(frame, channel_number))
return cls(
payload=fp.getvalue(),
drain_future=drain_future,
should_close=should_close,
)
class AbstractFutureStore:
futures: Set[Union[asyncio.Future, TaskType]]
loop: asyncio.AbstractEventLoop
@abstractmethod
def add(self, future: Union[asyncio.Future, TaskWrapper]) -> None:
raise NotImplementedError
@abstractmethod
def reject_all(self, exception: Optional[ExceptionType]) -> Any:
raise NotImplementedError
@abstractmethod
def create_task(self, coro: CoroutineType) -> TaskType:
raise NotImplementedError
@abstractmethod
def create_future(self) -> asyncio.Future:
raise NotImplementedError
@abstractmethod
def get_child(self) -> "AbstractFutureStore":
raise NotImplementedError
class AbstractBase(ABC):
loop: asyncio.AbstractEventLoop
@abstractmethod
def _future_store_child(self) -> AbstractFutureStore:
raise NotImplementedError
@abstractmethod
def create_task(self, coro: CoroutineType) -> TaskType:
raise NotImplementedError
def create_future(self) -> asyncio.Future:
raise NotImplementedError
@abstractmethod
async def _on_close(self, exc: Optional[Exception] = None) -> None:
raise NotImplementedError
@abstractmethod
async def close(
self, exc: Optional[ExceptionType] = asyncio.CancelledError(),
) -> None:
raise NotImplementedError
@abstractmethod
def __str__(self) -> str:
raise NotImplementedError
@abstractproperty
def is_closed(self) -> bool:
raise NotImplementedError
class AbstractChannel(AbstractBase):
frames: asyncio.Queue
connection: "AbstractConnection"
number: int
on_return_callbacks: Set[ReturnCallback]
closing: asyncio.Future
@abstractmethod
async def open(self) -> spec.Channel.OpenOk:
pass
@abstractmethod
async def basic_get(
self, queue: str = "", no_ack: bool = False,
timeout: TimeoutType = None,
) -> DeliveredMessage:
raise NotImplementedError
@abstractmethod
async def basic_cancel(
self, consumer_tag: str, *, nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.CancelOk:
raise NotImplementedError
@abstractmethod
async def basic_consume(
self,
queue: str,
consumer_callback: ConsumerCallback,
*,
no_ack: bool = False,
exclusive: bool = False,
arguments: Optional[ArgumentsType] = None,
consumer_tag: Optional[str] = None,
timeout: TimeoutType = None,
) -> spec.Basic.ConsumeOk:
raise NotImplementedError
@abstractmethod
def basic_ack(
self, delivery_tag: int, multiple: bool = False, wait: bool = True,
) -> DrainResult:
raise NotImplementedError
@abstractmethod
def basic_nack(
self,
delivery_tag: int,
multiple: bool = False,
requeue: bool = True,
wait: bool = True,
) -> DrainResult:
raise NotImplementedError
@abstractmethod
def basic_reject(
self, delivery_tag: int, *, requeue: bool = True, wait: bool = True,
) -> DrainResult:
raise NotImplementedError
@abstractmethod
async def basic_publish(
self,
body: bytes,
*,
exchange: str = "",
routing_key: str = "",
properties: Optional[spec.Basic.Properties] = None,
mandatory: bool = False,
immediate: bool = False,
timeout: TimeoutType = None,
) -> Optional[ConfirmationFrameType]:
raise NotImplementedError
@abstractmethod
async def basic_qos(
self,
*,
prefetch_size: Optional[int] = None,
prefetch_count: Optional[int] = None,
global_: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.QosOk:
raise NotImplementedError
@abstractmethod
async def basic_recover(
self, *, nowait: bool = False, requeue: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.RecoverOk:
raise NotImplementedError
@abstractmethod
async def exchange_declare(
self,
exchange: str = "",
*,
exchange_type: str = "direct",
passive: bool = False,
durable: bool = False,
auto_delete: bool = False,
internal: bool = False,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.DeclareOk:
raise NotImplementedError
@abstractmethod
async def exchange_delete(
self,
exchange: str = "",
*,
if_unused: bool = False,
nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Exchange.DeleteOk:
raise NotImplementedError
@abstractmethod
async def exchange_bind(
self,
destination: str = "",
source: str = "",
routing_key: str = "",
*,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.BindOk:
raise NotImplementedError
@abstractmethod
async def exchange_unbind(
self,
destination: str = "",
source: str = "",
routing_key: str = "",
*,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.UnbindOk:
raise NotImplementedError
@abstractmethod
async def flow(
self, active: bool,
timeout: TimeoutType = None,
) -> spec.Channel.FlowOk:
raise NotImplementedError
@abstractmethod
async def queue_bind(
self,
queue: str,
exchange: str,
routing_key: str = "",
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.BindOk:
raise NotImplementedError
@abstractmethod
async def queue_declare(
self,
queue: str = "",
*,
passive: bool = False,
durable: bool = False,
exclusive: bool = False,
auto_delete: bool = False,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.DeclareOk:
raise NotImplementedError
@abstractmethod
async def queue_delete(
self,
queue: str = "",
if_unused: bool = False,
if_empty: bool = False,
nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Queue.DeleteOk:
raise NotImplementedError
@abstractmethod
async def queue_purge(
self, queue: str = "", nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Queue.PurgeOk:
raise NotImplementedError
@abstractmethod
async def queue_unbind(
self,
queue: str = "",
exchange: str = "",
routing_key: str = "",
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.UnbindOk:
raise NotImplementedError
@abstractmethod
async def tx_commit(
self, timeout: TimeoutType = None,
) -> spec.Tx.CommitOk:
raise NotImplementedError
@abstractmethod
async def tx_rollback(
self, timeout: TimeoutType = None,
) -> spec.Tx.RollbackOk:
raise NotImplementedError
@abstractmethod
async def tx_select(self, timeout: TimeoutType = None) -> spec.Tx.SelectOk:
raise NotImplementedError
@abstractmethod
async def confirm_delivery(
self, nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Confirm.SelectOk:
raise NotImplementedError
class AbstractConnection(AbstractBase):
FRAME_BUFFER_SIZE: int = 10
# Interval between sending heartbeats based on the heartbeat(timeout)
HEARTBEAT_INTERVAL_MULTIPLIER: TimeoutType
# Allow three missed heartbeats (based on heartbeat(timeout)
HEARTBEAT_GRACE_MULTIPLIER: int
server_properties: ArgumentsType
connection_tune: spec.Connection.Tune
channels: Dict[int, Optional[AbstractChannel]]
write_queue: asyncio.Queue
url: URL
closing: asyncio.Future
@abstractmethod
def set_close_reason(
self, reply_code: int = REPLY_SUCCESS,
reply_text: str = "normally closed",
class_id: int = 0, method_id: int = 0,
) -> None:
raise NotImplementedError
@abstractproperty
def is_opened(self) -> bool:
raise NotImplementedError
@abstractmethod
def __str__(self) -> str:
raise NotImplementedError
@abstractmethod
async def connect(
self, client_properties: Optional[FieldTable] = None,
) -> bool:
raise NotImplementedError
@abstractproperty
def server_capabilities(self) -> ArgumentsType:
raise NotImplementedError
@abstractproperty
def basic_nack(self) -> bool:
raise NotImplementedError
@abstractproperty
def consumer_cancel_notify(self) -> bool:
raise NotImplementedError
@abstractproperty
def exchange_exchange_bindings(self) -> bool:
raise NotImplementedError
@abstractproperty
def publisher_confirms(self) -> Optional[bool]:
raise NotImplementedError
async def channel(
self,
channel_number: Optional[int] = None,
publisher_confirms: bool = True,
frame_buffer_size: int = FRAME_BUFFER_SIZE,
timeout: TimeoutType = None,
**kwargs: Any,
) -> AbstractChannel:
raise NotImplementedError
@abstractmethod
async def __aenter__(self) -> "AbstractConnection":
raise NotImplementedError
@abstractmethod
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
raise NotImplementedError
@abstractmethod
async def ready(self) -> None:
raise NotImplementedError
@abstractmethod
async def update_secret(
self, new_secret: str, *,
reason: str = "", timeout: TimeoutType = None,
) -> spec.Connection.UpdateSecretOk:
raise NotImplementedError
__all__ = (
"AbstractBase", "AbstractChannel", "AbstractConnection",
"AbstractFutureStore", "ArgumentsType", "CallbackCoro", "ChannelFrame",
"ChannelRType", "ConfirmationFrameType", "ConsumerCallback",
"CoroutineType", "DeliveredMessage", "DrainResult", "ExceptionType",
"FieldArray", "FieldTable", "FieldValue", "FrameReceived", "FrameType",
"GetResultType", "ReturnCallback", "RpcReturnType", "SSLCerts",
"TaskType", "TaskWrapper", "TimeoutType", "URLorStr",
)
python-aiormq-6.8.1/aiormq/auth.py 0000664 0000000 0000000 00000001536 14737314232 0017153 0 ustar 00root root 0000000 0000000 import abc
from enum import Enum
from typing import Optional
from .abc import AbstractConnection
class AuthBase:
value: Optional[str]
def __init__(self, connector: AbstractConnection):
self.connector = connector
self.value = None
@abc.abstractmethod
def encode(self) -> str:
raise NotImplementedError
def marshal(self) -> str:
if self.value is None:
self.value = self.encode()
return self.value
class PlainAuth(AuthBase):
def encode(self) -> str:
return (
"\x00"
+ (self.connector.url.user or "guest")
+ "\x00"
+ (self.connector.url.password or "guest")
)
class ExternalAuth(AuthBase):
def encode(self) -> str:
return ""
class AuthMechanism(Enum):
PLAIN = PlainAuth
EXTERNAL = ExternalAuth
python-aiormq-6.8.1/aiormq/base.py 0000664 0000000 0000000 00000011300 14737314232 0017112 0 ustar 00root root 0000000 0000000 import abc
import asyncio
from contextlib import suppress
from functools import wraps
from typing import Any, Callable, Coroutine, Optional, Set, TypeVar, Union
from weakref import WeakSet
from .abc import (
AbstractBase, AbstractFutureStore, CoroutineType, ExceptionType, TaskType,
TaskWrapper, TimeoutType,
)
from .tools import Countdown, shield
T = TypeVar("T")
class FutureStore(AbstractFutureStore):
__slots__ = "futures", "loop", "parent"
futures: Set[Union[asyncio.Future, TaskType]]
weak_futures: WeakSet
loop: asyncio.AbstractEventLoop
def __init__(self, loop: asyncio.AbstractEventLoop):
self.futures = set()
self.loop = loop
self.parent: Optional[FutureStore] = None
def __on_task_done(
self, future: Union[asyncio.Future, TaskWrapper],
) -> Callable[..., Any]:
def remover(*_: Any) -> None:
nonlocal future
if future in self.futures:
self.futures.remove(future)
return remover
def add(self, future: Union[asyncio.Future, TaskWrapper]) -> None:
self.futures.add(future)
future.add_done_callback(self.__on_task_done(future))
if self.parent:
self.parent.add(future)
@shield
async def reject_all(self, exception: Optional[ExceptionType]) -> None:
tasks = []
while self.futures:
future: Union[TaskType, asyncio.Future] = self.futures.pop()
if future.done():
continue
if isinstance(future, TaskWrapper):
future.throw(exception or Exception)
tasks.append(future)
elif isinstance(future, asyncio.Future):
future.set_exception(exception or Exception)
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
def create_task(self, coro: CoroutineType) -> TaskType:
task: TaskWrapper = TaskWrapper(self.loop.create_task(coro))
self.add(task)
return task
def create_future(self, weak: bool = False) -> asyncio.Future:
future = self.loop.create_future()
self.add(future)
return future
def get_child(self) -> "FutureStore":
store = FutureStore(self.loop)
store.parent = self
return store
class Base(AbstractBase):
__slots__ = "loop", "__future_store", "closing"
def __init__(
self, *, loop: asyncio.AbstractEventLoop,
parent: Optional[AbstractBase] = None,
):
self.loop: asyncio.AbstractEventLoop = loop
if parent:
self.__future_store = parent._future_store_child()
else:
self.__future_store = FutureStore(loop=self.loop)
self.closing = self._create_closing_future()
def _create_closing_future(self) -> asyncio.Future:
future = self.__future_store.create_future()
future.add_done_callback(lambda x: x.exception())
return future
def _cancel_tasks(
self, exc: Optional[ExceptionType] = None,
) -> Coroutine[Any, Any, None]:
return self.__future_store.reject_all(exc)
def _future_store_child(self) -> AbstractFutureStore:
return self.__future_store.get_child()
def create_task(self, coro: CoroutineType) -> TaskType:
return self.__future_store.create_task(coro)
def create_future(self) -> asyncio.Future:
return self.__future_store.create_future()
@abc.abstractmethod
async def _on_close(
self, exc: Optional[ExceptionType] = None,
) -> None: # pragma: no cover
return
async def __closer(self, exc: Optional[ExceptionType]) -> None:
if self.is_closed: # pragma: no cover
return
with suppress(Exception):
await self._on_close(exc)
with suppress(Exception):
await self._cancel_tasks(exc)
async def close(
self, exc: Optional[ExceptionType] = asyncio.CancelledError,
timeout: TimeoutType = None,
) -> None:
if self.is_closed:
return None
countdown = Countdown(timeout)
await countdown(self.__closer(exc))
def __repr__(self) -> str:
cls_name = self.__class__.__name__
return '<{0}: "{1}" at 0x{2:02x}>'.format(
cls_name, str(self), id(self),
)
@abc.abstractmethod
def __str__(self) -> str: # pragma: no cover
raise NotImplementedError
@property
def is_closed(self) -> bool:
return self.closing.done()
TaskFunctionType = Callable[..., T]
def task(func: TaskFunctionType) -> TaskFunctionType:
@wraps(func)
async def wrap(self: Base, *args: Any, **kwargs: Any) -> Any:
return await self.create_task(func(self, *args, **kwargs))
return wrap
python-aiormq-6.8.1/aiormq/channel.py 0000664 0000000 0000000 00000071505 14737314232 0017625 0 ustar 00root root 0000000 0000000 import asyncio
import io
import logging
from collections import OrderedDict
from contextlib import suppress
from functools import partial
from io import BytesIO
from random import getrandbits
from types import MappingProxyType
from typing import (
Any, Awaitable, Callable, Dict, List, Mapping, Optional, Set, Tuple, Type,
Union,
)
from uuid import UUID
import pamqp.frame
from pamqp import commands as spec
from pamqp.base import Frame
from pamqp.body import ContentBody
from pamqp.constants import REPLY_SUCCESS
from pamqp.exceptions import AMQPFrameError
from pamqp.header import ContentHeader
from aiormq.tools import Countdown, awaitable
from .abc import (
AbstractChannel, AbstractConnection, ArgumentsType, ChannelFrame,
ConfirmationFrameType, ConsumerCallback, DeliveredMessage, ExceptionType,
FrameType, GetResultType, ReturnCallback, RpcReturnType, TimeoutType,
)
from .base import Base, task
from .exceptions import (
AMQPChannelError, AMQPError, ChannelAccessRefused, ChannelClosed,
ChannelInvalidStateError, ChannelLockedResource, ChannelNotFoundEntity,
ChannelPreconditionFailed, DeliveryError, DuplicateConsumerTag,
InvalidFrameError, MethodNotImplemented, PublishError,
)
log = logging.getLogger(__name__)
EXCEPTION_MAPPING: Mapping[int, Type[AMQPChannelError]] = MappingProxyType({
403: ChannelAccessRefused,
404: ChannelNotFoundEntity,
405: ChannelLockedResource,
406: ChannelPreconditionFailed,
})
def exception_by_code(frame: spec.Channel.Close) -> AMQPError:
if frame.reply_code is None:
return ChannelClosed(frame.reply_code, frame.reply_text)
exception_class = EXCEPTION_MAPPING.get(frame.reply_code)
if exception_class is None:
return ChannelClosed(frame.reply_code, frame.reply_text)
return exception_class(frame.reply_text)
def _check_routing_key(key: str) -> None:
if len(key) > 255:
raise ValueError("Routing key too long (max 255 bytes)")
class Returning(asyncio.Future):
pass
ConfirmationType = Union[asyncio.Future, Returning]
class Channel(Base, AbstractChannel):
# noinspection PyTypeChecker
CONTENT_FRAME_SIZE = len(pamqp.frame.marshal(ContentBody(b""), 0))
CHANNEL_CLOSE_TIMEOUT = 10
confirmations: Dict[int, ConfirmationType]
def __init__(
self,
connector: AbstractConnection,
number: int,
publisher_confirms: bool = True,
frame_buffer: Optional[int] = None,
on_return_raises: bool = True,
):
super().__init__(loop=connector.loop, parent=connector)
self.connection = connector
if (
publisher_confirms and not connector.publisher_confirms
): # pragma: no cover
raise ValueError("Server doesn't support publisher confirms")
self.consumers: Dict[str, ConsumerCallback] = {}
self.confirmations = OrderedDict()
self.message_id_delivery_tag: Dict[str, int] = dict()
self.delivery_tag = 0
self.getter: Optional[asyncio.Future] = None
self.getter_lock = asyncio.Lock()
self.frames: asyncio.Queue = asyncio.Queue(maxsize=frame_buffer or 0)
self.max_content_size = (
connector.connection_tune.frame_max - self.CONTENT_FRAME_SIZE
)
self.__lock = asyncio.Lock()
self.number: int = number
self.publisher_confirms = publisher_confirms
self.rpc_frames: asyncio.Queue = asyncio.Queue(
maxsize=frame_buffer or 0,
)
self.write_queue = connector.write_queue
self.on_return_raises = on_return_raises
self.on_return_callbacks: Set[ReturnCallback] = set()
self._close_exception = None
self.create_task(self._reader())
self.__close_reply_code: int = REPLY_SUCCESS
self.__close_reply_text: str = ""
self.__close_class_id: int = 0
self.__close_method_id: int = 0
self.__close_event: asyncio.Event = asyncio.Event()
def set_close_reason(
self, reply_code: int = REPLY_SUCCESS,
reply_text: str = "", class_id: int = 0, method_id: int = 0,
) -> None:
self.__close_reply_code = reply_code
self.__close_reply_text = reply_text
self.__close_class_id = class_id
self.__close_method_id = method_id
@property
def lock(self) -> asyncio.Lock:
if self.is_closed:
raise ChannelInvalidStateError("%r closed" % self)
return self.__lock
async def _get_frame(self) -> FrameType:
weight, frame = await self.frames.get()
self.frames.task_done()
return frame
def __str__(self) -> str:
return str(self.number)
@task
async def rpc(
self, frame: Frame, timeout: TimeoutType = None,
) -> RpcReturnType:
if self.__close_event.is_set():
raise ChannelInvalidStateError("Channel closed by RPC timeout")
countdown = Countdown(timeout)
lock = self.lock
async with countdown.enter_context(lock):
try:
await countdown(
self.write_queue.put(
ChannelFrame.marshall(
channel_number=self.number,
frames=[frame],
),
),
)
if not (frame.synchronous or getattr(frame, "nowait", False)):
return None
result = await countdown(self.rpc_frames.get())
self.rpc_frames.task_done()
if result.name not in frame.valid_responses: # pragma: no cover
raise InvalidFrameError(frame)
return result
except (asyncio.CancelledError, asyncio.TimeoutError):
if self.is_closed:
raise
log.warning(
"Closing channel %r because RPC call %s cancelled",
self, frame,
)
self.__close_event.set()
await self.write_queue.put(
ChannelFrame.marshall(
channel_number=self.number,
frames=[
spec.Channel.Close(
class_id=0,
method_id=0,
reply_code=504,
reply_text=(
"RPC timeout on frame {!s}".format(frame)
),
),
],
),
)
raise
async def open(self, timeout: TimeoutType = None) -> spec.Channel.OpenOk:
frame: spec.Channel.OpenOk = await self.rpc(
spec.Channel.Open(), timeout=timeout,
)
if self.publisher_confirms:
await self.rpc(spec.Confirm.Select())
if frame is None: # pragma: no cover
raise AMQPFrameError(frame)
return frame
async def __get_content_frame(self) -> ContentBody:
content_frame = await self._get_frame()
if not isinstance(content_frame, ContentBody):
raise ValueError(
"Unexpected frame {!r}".format(content_frame),
content_frame,
)
return content_frame
async def _read_content(
self,
frame: Union[spec.Basic.Deliver, spec.Basic.Return, GetResultType],
header: ContentHeader,
) -> DeliveredMessage:
with BytesIO() as body:
content: Optional[ContentBody] = None
if header.body_size:
content = await self.__get_content_frame()
while content and body.tell() < header.body_size:
body.write(content.value)
if body.tell() < header.body_size:
content = await self.__get_content_frame()
return DeliveredMessage(
delivery=frame,
header=header,
body=body.getvalue(),
channel=self,
)
async def __get_content_header(self) -> ContentHeader:
frame: FrameType = await self._get_frame()
if not isinstance(frame, ContentHeader):
raise ValueError(
"Unexpected frame {!r} instead of ContentHeader".format(frame),
frame,
)
return frame
async def _on_deliver_frame(self, frame: spec.Basic.Deliver) -> None:
header: ContentHeader = await self.__get_content_header()
message = await self._read_content(frame, header)
if frame.consumer_tag is None:
log.warning("Frame %r has no consumer tag", frame)
return
consumer = self.consumers.get(frame.consumer_tag)
if consumer is not None:
# noinspection PyAsyncCall
self.create_task(consumer(message))
async def _on_get_frame(
self, frame: Union[spec.Basic.GetOk, spec.Basic.GetEmpty],
) -> None:
message = None
if isinstance(frame, spec.Basic.GetOk):
header: ContentHeader = await self.__get_content_header()
message = await self._read_content(frame, header)
if isinstance(frame, spec.Basic.GetEmpty):
message = DeliveredMessage(
delivery=frame,
header=ContentHeader(),
body=b"",
channel=self,
)
getter = getattr(self, "getter", None)
if getter is None:
raise RuntimeError("Getter is None")
if getter.done():
log.error("Got message but no active getter")
return
getter.set_result((frame, message))
return
async def _on_return_frame(self, frame: spec.Basic.Return) -> None:
header: ContentHeader = await self.__get_content_header()
message = await self._read_content(frame, header)
message_id = message.header.properties.message_id
if message_id is None:
log.error("message_id if None on returned message %r", message)
return
delivery_tag = self.message_id_delivery_tag.get(message_id)
if delivery_tag is None: # pragma: nocover
log.error("Unhandled message %r returning", message)
return
confirmation = self.confirmations.pop(delivery_tag, None)
if confirmation is None: # pragma: nocover
return
self.confirmations[delivery_tag] = Returning()
if self.on_return_raises:
confirmation.set_exception(PublishError(message, frame))
return
for cb in self.on_return_callbacks:
# noinspection PyBroadException
try:
cb(message)
except Exception:
log.exception("Unhandled return callback exception")
confirmation.set_result(message)
def _confirm_delivery(
self, delivery_tag: Optional[int],
frame: ConfirmationFrameType,
) -> None:
if delivery_tag not in self.confirmations:
return
confirmation = self.confirmations.pop(delivery_tag)
if isinstance(confirmation, Returning):
return
elif confirmation.done(): # pragma: nocover
log.warning(
"Delivery tag %r confirmed %r was ignored", delivery_tag, frame,
)
return
elif isinstance(frame, spec.Basic.Ack):
confirmation.set_result(frame)
return
confirmation.set_exception(
DeliveryError(None, frame),
) # pragma: nocover
async def _on_confirm_frame(self, frame: ConfirmationFrameType) -> None:
if not self.publisher_confirms: # pragma: nocover
return
if frame.delivery_tag not in self.confirmations:
log.error("Unexpected confirmation frame %r from broker", frame)
return
multiple = getattr(frame, "multiple", False)
if multiple:
for delivery_tag in self.confirmations.keys():
if frame.delivery_tag >= delivery_tag:
# Should be called later to avoid keys copying
self.loop.call_soon(
self._confirm_delivery, delivery_tag, frame,
)
else:
self._confirm_delivery(frame.delivery_tag, frame)
async def _on_cancel_frame(
self,
frame: Union[spec.Basic.CancelOk, spec.Basic.Cancel],
) -> None:
if frame.consumer_tag is not None:
self.consumers.pop(frame.consumer_tag, None)
async def _on_close_frame(self, frame: spec.Channel.Close) -> None:
exc: BaseException = exception_by_code(frame)
with suppress(asyncio.QueueFull):
self.write_queue.put_nowait(
ChannelFrame.marshall(
channel_number=self.number,
frames=[spec.Channel.CloseOk()],
),
)
self.connection.channels.pop(self.number, None)
self.__close_event.set()
raise exc
async def _on_close_ok_frame(self, _: spec.Channel.CloseOk) -> None:
self.connection.channels.pop(self.number, None)
self.__close_event.set()
raise ChannelClosed(None, None)
async def _reader(self) -> None:
hooks: Mapping[Any, Tuple[bool, Callable[[Any], Awaitable[None]]]]
hooks = {
spec.Basic.Deliver: (False, self._on_deliver_frame),
spec.Basic.GetOk: (True, self._on_get_frame),
spec.Basic.GetEmpty: (True, self._on_get_frame),
spec.Basic.Return: (False, self._on_return_frame),
spec.Basic.Cancel: (False, self._on_cancel_frame),
spec.Basic.CancelOk: (True, self._on_cancel_frame),
spec.Channel.Close: (False, self._on_close_frame),
spec.Channel.CloseOk: (False, self._on_close_ok_frame),
spec.Basic.Ack: (False, self._on_confirm_frame),
spec.Basic.Nack: (False, self._on_confirm_frame),
}
last_exception: Optional[BaseException] = None
try:
while True:
frame = await self._get_frame()
should_add_to_rpc, hook = hooks.get(type(frame), (True, None))
if hook is not None:
await hook(frame)
if should_add_to_rpc:
await self.rpc_frames.put(frame)
except asyncio.CancelledError as e:
self.__close_event.set()
last_exception = e
return
except Exception as e:
last_exception = e
raise
finally:
await self.close(
last_exception, timeout=self.CHANNEL_CLOSE_TIMEOUT,
)
@task
async def _on_close(self, exc: Optional[ExceptionType] = None) -> None:
if not self.connection.is_opened or self.__close_event.is_set():
return
await self.rpc(
spec.Channel.Close(
reply_code=self.__close_reply_code,
class_id=self.__close_class_id,
method_id=self.__close_method_id,
),
timeout=self.connection.connection_tune.heartbeat or None,
)
await self.__close_event.wait()
async def basic_get(
self, queue: str = "", no_ack: bool = False,
timeout: TimeoutType = None,
) -> DeliveredMessage:
countdown = Countdown(timeout)
async with countdown.enter_context(self.getter_lock):
self.getter = self.create_future()
await self.rpc(
spec.Basic.Get(queue=queue, no_ack=no_ack),
timeout=countdown.get_timeout(),
)
frame: Union[spec.Basic.GetEmpty, spec.Basic.GetOk]
message: DeliveredMessage
frame, message = await countdown(self.getter)
del self.getter
return message
async def basic_cancel(
self, consumer_tag: str, *, nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.CancelOk:
return await self.rpc(
spec.Basic.Cancel(consumer_tag=consumer_tag, nowait=nowait),
timeout=timeout,
)
async def basic_consume(
self,
queue: str,
consumer_callback: ConsumerCallback,
*,
no_ack: bool = False,
exclusive: bool = False,
arguments: Optional[ArgumentsType] = None,
consumer_tag: Optional[str] = None,
timeout: TimeoutType = None,
) -> spec.Basic.ConsumeOk:
consumer_tag = consumer_tag or "ctag%i.%s" % (
self.number,
UUID(int=getrandbits(128), version=4).hex,
)
if consumer_tag in self.consumers:
raise DuplicateConsumerTag(self.number)
self.consumers[consumer_tag] = awaitable(consumer_callback)
return await self.rpc(
spec.Basic.Consume(
queue=queue,
no_ack=no_ack,
exclusive=exclusive,
consumer_tag=consumer_tag,
arguments=arguments,
),
timeout=timeout,
)
async def basic_ack(
self, delivery_tag: int, multiple: bool = False, wait: bool = True,
) -> None:
drain_future = self.create_future() if wait else None
await self.write_queue.put(
ChannelFrame.marshall(
frames=[
spec.Basic.Ack(
delivery_tag=delivery_tag,
multiple=multiple,
),
],
channel_number=self.number,
drain_future=drain_future,
),
)
if drain_future is not None:
await drain_future
async def basic_nack(
self,
delivery_tag: int,
multiple: bool = False,
requeue: bool = True,
wait: bool = True,
) -> None:
if not self.connection.basic_nack:
raise MethodNotImplemented
drain_future = self.create_future() if wait else None
await self.write_queue.put(
ChannelFrame.marshall(
frames=[
spec.Basic.Nack(
delivery_tag=delivery_tag,
multiple=multiple,
requeue=requeue,
),
],
channel_number=self.number,
drain_future=drain_future,
),
)
if drain_future is not None:
await drain_future
async def basic_reject(
self, delivery_tag: int, *, requeue: bool = True, wait: bool = True,
) -> None:
drain_future = self.create_future()
await self.write_queue.put(
ChannelFrame.marshall(
channel_number=self.number,
frames=[
spec.Basic.Reject(
delivery_tag=delivery_tag,
requeue=requeue,
),
],
drain_future=drain_future,
),
)
if drain_future is not None:
await drain_future
def _split_body(self, body: bytes) -> List[ContentBody]:
if not body:
return []
if len(body) < self.max_content_size:
return [ContentBody(body)]
with io.BytesIO(body) as fp:
reader = partial(fp.read, self.max_content_size)
return list(map(ContentBody, iter(reader, b"")))
async def basic_publish(
self,
body: bytes,
*,
exchange: str = "",
routing_key: str = "",
properties: Optional[spec.Basic.Properties] = None,
mandatory: bool = False,
immediate: bool = False,
timeout: TimeoutType = None,
wait: bool = True,
) -> Optional[ConfirmationFrameType]:
_check_routing_key(routing_key)
countdown = Countdown(timeout=timeout)
publish_frame = spec.Basic.Publish(
exchange=exchange,
routing_key=routing_key,
mandatory=mandatory,
immediate=immediate,
)
content_header = ContentHeader(
properties=properties or spec.Basic.Properties(delivery_mode=1),
body_size=len(body),
)
if not content_header.properties.message_id:
# UUID compatible random bytes
rnd_uuid = UUID(int=getrandbits(128), version=4)
content_header.properties.message_id = rnd_uuid.hex
confirmation: Optional[ConfirmationType] = None
async with countdown.enter_context(self.lock):
self.delivery_tag += 1
if self.publisher_confirms:
message_id = content_header.properties.message_id
if self.delivery_tag not in self.confirmations:
self.confirmations[
self.delivery_tag
] = self.create_future()
confirmation = self.confirmations[self.delivery_tag]
self.message_id_delivery_tag[message_id] = self.delivery_tag
if confirmation is None:
return
confirmation.add_done_callback(
lambda _: self.message_id_delivery_tag.pop(
message_id, None,
),
)
body_frames: List[Union[FrameType, ContentBody]]
body_frames = [publish_frame, content_header]
body_frames += self._split_body(body)
drain_future = self.create_future() if wait else None
await countdown(
self.write_queue.put(
ChannelFrame.marshall(
frames=body_frames,
channel_number=self.number,
drain_future=drain_future,
),
),
)
if drain_future:
await drain_future
if not self.publisher_confirms:
return None
if confirmation is None:
return None
return await countdown(confirmation)
async def basic_qos(
self,
*,
prefetch_size: Optional[int] = None,
prefetch_count: Optional[int] = None,
global_: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.QosOk:
return await self.rpc(
spec.Basic.Qos(
prefetch_size=prefetch_size or 0,
prefetch_count=prefetch_count or 0,
global_=global_,
),
timeout=timeout,
)
async def basic_recover(
self, *, nowait: bool = False, requeue: bool = False,
timeout: TimeoutType = None,
) -> spec.Basic.RecoverOk:
frame: Union[spec.Basic.RecoverAsync, spec.Basic.Recover]
if nowait:
frame = spec.Basic.RecoverAsync(requeue=requeue)
else:
frame = spec.Basic.Recover(requeue=requeue)
return await self.rpc(frame, timeout=timeout)
async def exchange_declare(
self,
exchange: str = "",
*,
exchange_type: str = "direct",
passive: bool = False,
durable: bool = False,
auto_delete: bool = False,
internal: bool = False,
nowait: bool = False,
arguments: Optional[Dict[str, Any]] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.DeclareOk:
return await self.rpc(
spec.Exchange.Declare(
exchange=str(exchange),
exchange_type=str(exchange_type),
passive=bool(passive),
durable=bool(durable),
auto_delete=bool(auto_delete),
internal=bool(internal),
nowait=bool(nowait),
arguments=arguments,
),
timeout=timeout,
)
async def exchange_delete(
self,
exchange: str = "",
*,
if_unused: bool = False,
nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Exchange.DeleteOk:
return await self.rpc(
spec.Exchange.Delete(
exchange=exchange, nowait=nowait, if_unused=if_unused,
),
timeout=timeout,
)
async def exchange_bind(
self,
destination: str = "",
source: str = "",
routing_key: str = "",
*,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.BindOk:
_check_routing_key(routing_key)
return await self.rpc(
spec.Exchange.Bind(
destination=destination,
source=source,
routing_key=routing_key,
nowait=nowait,
arguments=arguments,
),
timeout=timeout,
)
async def exchange_unbind(
self,
destination: str = "",
source: str = "",
routing_key: str = "",
*,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Exchange.UnbindOk:
_check_routing_key(routing_key)
return await self.rpc(
spec.Exchange.Unbind(
destination=destination,
source=source,
routing_key=routing_key,
nowait=nowait,
arguments=arguments,
),
timeout=timeout,
)
async def flow(
self, active: bool,
timeout: TimeoutType = None,
) -> spec.Channel.FlowOk:
return await self.rpc(
spec.Channel.Flow(active=active),
timeout=timeout,
)
async def queue_bind(
self,
queue: str,
exchange: str,
routing_key: str = "",
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.BindOk:
_check_routing_key(routing_key)
return await self.rpc(
spec.Queue.Bind(
queue=queue,
exchange=exchange,
routing_key=routing_key,
nowait=nowait,
arguments=arguments,
),
timeout=timeout,
)
async def queue_declare(
self,
queue: str = "",
*,
passive: bool = False,
durable: bool = False,
exclusive: bool = False,
auto_delete: bool = False,
nowait: bool = False,
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.DeclareOk:
return await self.rpc(
spec.Queue.Declare(
queue=queue,
passive=bool(passive),
durable=bool(durable),
exclusive=bool(exclusive),
auto_delete=bool(auto_delete),
nowait=bool(nowait),
arguments=arguments,
),
timeout=timeout,
)
async def queue_delete(
self,
queue: str = "",
if_unused: bool = False,
if_empty: bool = False,
nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Queue.DeleteOk:
return await self.rpc(
spec.Queue.Delete(
queue=queue,
if_unused=if_unused,
if_empty=if_empty,
nowait=nowait,
),
timeout=timeout,
)
async def queue_purge(
self, queue: str = "", nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Queue.PurgeOk:
return await self.rpc(
spec.Queue.Purge(queue=queue, nowait=nowait),
timeout=timeout,
)
async def queue_unbind(
self,
queue: str = "",
exchange: str = "",
routing_key: str = "",
arguments: Optional[ArgumentsType] = None,
timeout: TimeoutType = None,
) -> spec.Queue.UnbindOk:
_check_routing_key(routing_key)
return await self.rpc(
spec.Queue.Unbind(
routing_key=routing_key,
arguments=arguments,
queue=queue,
exchange=exchange,
),
timeout=timeout,
)
async def tx_commit(
self, timeout: TimeoutType = None,
) -> spec.Tx.CommitOk:
return await self.rpc(spec.Tx.Commit(), timeout=timeout)
async def tx_rollback(
self, timeout: TimeoutType = None,
) -> spec.Tx.RollbackOk:
return await self.rpc(spec.Tx.Rollback(), timeout=timeout)
async def tx_select(self, timeout: TimeoutType = None) -> spec.Tx.SelectOk:
return await self.rpc(spec.Tx.Select(), timeout=timeout)
async def confirm_delivery(
self, nowait: bool = False,
timeout: TimeoutType = None,
) -> spec.Confirm.SelectOk:
return await self.rpc(
spec.Confirm.Select(nowait=nowait),
timeout=timeout,
)
python-aiormq-6.8.1/aiormq/connection.py 0000664 0000000 0000000 00000073065 14737314232 0020357 0 ustar 00root root 0000000 0000000 import asyncio
import logging
import platform
import ssl
import sys
from base64 import b64decode
from collections.abc import AsyncIterable
from contextlib import suppress
from io import BytesIO
from types import MappingProxyType, TracebackType
from typing import (
Any, Awaitable, Callable, Dict, Mapping, Optional, Tuple, Type, Union,
)
import pamqp.frame
from pamqp import commands as spec
from pamqp.base import Frame
from pamqp.common import FieldTable
from pamqp.constants import REPLY_SUCCESS
from pamqp.exceptions import AMQPFrameError, AMQPInternalError, AMQPSyntaxError
from pamqp.frame import FrameTypes
from pamqp.header import ProtocolHeader
from pamqp.heartbeat import Heartbeat
from yarl import URL
from .abc import (
AbstractChannel, AbstractConnection, ArgumentsType, ChannelFrame,
ExceptionType, SSLCerts, TaskType, URLorStr,
)
from .auth import AuthMechanism
from .base import Base, task
from .channel import Channel
from .exceptions import (
AMQPConnectionError, AMQPError, AuthenticationError, ConnectionChannelError,
ConnectionClosed, ConnectionCommandInvalid, ConnectionFrameError,
ConnectionInternalError, ConnectionNotAllowed, ConnectionNotImplemented,
ConnectionResourceError, ConnectionSyntaxError, ConnectionUnexpectedFrame,
IncompatibleProtocolError, ProbableAuthenticationError,
)
from .tools import Countdown, censor_url
# noinspection PyUnresolvedReferences
try:
from importlib.metadata import Distribution
__version__ = Distribution.from_name("aiormq").version
except ImportError:
import pkg_resources
__version__ = pkg_resources.get_distribution("aiormq").version
log = logging.getLogger(__name__)
CHANNEL_CLOSE_RESPONSES = (spec.Channel.Close, spec.Channel.CloseOk)
DEFAULT_PORTS = {
"amqp": 5672,
"amqps": 5671,
}
PRODUCT = "aiormq"
PLATFORM = "{} {} ({} build {})".format(
platform.python_implementation(),
platform.python_version(),
*platform.python_build(),
)
TimeType = Union[float, int]
TimeoutType = Optional[TimeType]
ReceivedFrame = Tuple[int, int, FrameTypes]
EXCEPTION_MAPPING = MappingProxyType({
501: ConnectionFrameError,
502: ConnectionSyntaxError,
503: ConnectionCommandInvalid,
504: ConnectionChannelError,
505: ConnectionUnexpectedFrame,
506: ConnectionResourceError,
530: ConnectionNotAllowed,
540: ConnectionNotImplemented,
541: ConnectionInternalError,
})
def exception_by_code(frame: spec.Connection.Close) -> AMQPError:
if frame.reply_code is None:
return ConnectionClosed(frame.reply_code, frame.reply_text)
exc_class = EXCEPTION_MAPPING.get(frame.reply_code)
if exc_class is None:
return ConnectionClosed(frame.reply_code, frame.reply_text)
return exc_class(frame.reply_text)
def parse_bool(v: Any) -> bool:
if isinstance(v, bool):
return v
v = str(v)
return v.lower() in (
"true", "yes", "y", "enable", "on", "enabled", "1"
)
def parse_int(v: Any) -> int:
if isinstance(v, int):
return v
v = str(v)
try:
return int(v)
except ValueError:
return 0
def parse_timeout(v: Any) -> TimeoutType:
if isinstance(v, float):
if v.is_integer():
return int(v)
return v
if isinstance(v, int):
return v
v = str(v)
try:
if "." in v:
result = float(v)
if result.is_integer():
return int(result)
return result
return int(v)
except ValueError:
return 0
def parse_heartbeat(v: str) -> int:
result = parse_int(v)
return result if 0 <= result < 65535 else 0
def parse_connection_name(connection_name: Optional[str]) -> Dict[str, str]:
if not connection_name or not isinstance(connection_name, str):
return {}
return dict(connection_name=connection_name)
class FrameReceiver(AsyncIterable):
_loop: asyncio.AbstractEventLoop
def __init__(
self, reader: asyncio.StreamReader,
):
self.reader: asyncio.StreamReader = reader
self.started: bool = False
self.lock = asyncio.Lock()
@property
def loop(self) -> asyncio.AbstractEventLoop:
if not hasattr(self, "_loop"):
self._loop = asyncio.get_event_loop()
return self._loop
def __aiter__(self) -> "FrameReceiver":
return self
async def get_frame(self) -> ReceivedFrame:
if self.reader.at_eof():
del self.reader
raise StopAsyncIteration
with BytesIO() as fp:
async with self.lock:
try:
fp.write(await self.reader.readexactly(1))
if fp.getvalue() == b"\0x00":
fp.write(await self.reader.read())
raise AMQPFrameError(fp.getvalue())
if self.reader is None:
raise AMQPConnectionError()
fp.write(await self.reader.readexactly(6))
if not self.started and fp.getvalue().startswith(b"AMQP"):
raise AMQPSyntaxError
else:
self.started = True
frame_type, _, frame_length = pamqp.frame.frame_parts(
fp.getvalue(),
)
if frame_length is None:
raise AMQPInternalError("No frame length", None)
fp.write(await self.reader.readexactly(frame_length + 1))
except asyncio.IncompleteReadError as e:
raise AMQPConnectionError(
"Server connection unexpectedly closed. "
f"Read {len(e.partial)} bytes but {e.expected} "
"bytes expected",
) from e
except ConnectionRefusedError as e:
raise AMQPConnectionError(
f"Server connection refused: {e!r}",
) from e
except ConnectionResetError as e:
raise AMQPConnectionError(
f"Server connection reset: {e!r}",
) from e
except ConnectionError as e:
raise AMQPConnectionError(
f"Server connection error: {e!r}",
) from e
except OSError as e:
raise AMQPConnectionError(
f"Server communication error: {e!r}",
) from e
return pamqp.frame.unmarshal(fp.getvalue())
async def __anext__(self) -> ReceivedFrame:
return await self.get_frame()
class FrameGenerator(AsyncIterable):
def __init__(self, queue: asyncio.Queue):
self.queue: asyncio.Queue = queue
self.close_event: asyncio.Event = asyncio.Event()
def __aiter__(self) -> "FrameGenerator":
return self
async def __anext__(self) -> ChannelFrame:
if self.close_event.is_set():
raise StopAsyncIteration
frame: ChannelFrame = await self.queue.get()
self.queue.task_done()
return frame
class Connection(Base, AbstractConnection):
FRAME_BUFFER_SIZE = 10
# Interval between sending heartbeats based on the heartbeat(timeout)
HEARTBEAT_INTERVAL_MULTIPLIER = 0.5
# Allow three missed heartbeats (based on heartbeat(timeout)
HEARTBEAT_GRACE_MULTIPLIER = 3
READER_CLOSE_TIMEOUT = 2
_reader_task: TaskType
_writer_task: TaskType
__create_connection_kwargs: Mapping[str, Any]
write_queue: asyncio.Queue
server_properties: ArgumentsType
connection_tune: spec.Connection.Tune
channels: Dict[int, Optional[AbstractChannel]]
@staticmethod
def _parse_ca_data(data: Optional[str]) -> Optional[bytes]:
return b64decode(data) if data else None
def __init__(
self,
url: URLorStr,
*,
loop: Optional[asyncio.AbstractEventLoop] = None,
context: Optional[ssl.SSLContext] = None,
**create_connection_kwargs: Any,
):
super().__init__(loop=loop or asyncio.get_event_loop(), parent=None)
self.url = URL(url)
if self.url.is_absolute() and not self.url.port:
self.url = self.url.with_port(DEFAULT_PORTS[self.url.scheme])
if self.url.path == "/" or not self.url.path:
self.vhost = "/"
else:
quoted_vhost = self.url.path[1:]
# yarl>=1.9.5 skips unquoting backslashes in path
self.vhost = quoted_vhost.replace("%2F", "/")
self.ssl_context = context
self.ssl_certs = SSLCerts(
cafile=self.url.query.get("cafile"),
capath=self.url.query.get("capath"),
cadata=self._parse_ca_data(self.url.query.get("cadata")),
key=self.url.query.get("keyfile"),
cert=self.url.query.get("certfile"),
verify=self.url.query.get("no_verify_ssl", "0") == "0",
)
self.started = False
self.channels = {}
self.write_queue = asyncio.Queue(
maxsize=self.FRAME_BUFFER_SIZE,
)
self.last_channel = 1
self.timeout = parse_int(self.url.query.get("timeout", "60"))
self.heartbeat_timeout = parse_heartbeat(
self.url.query.get("heartbeat", "60"),
)
self.last_channel_lock = asyncio.Lock()
self.connected = asyncio.Event()
self.connection_name = self.url.query.get("name")
self.__close_reply_code: int = REPLY_SUCCESS
self.__close_reply_text: str = "normally closed"
self.__close_class_id: int = 0
self.__close_method_id: int = 0
self.__update_secret_lock: asyncio.Lock = asyncio.Lock()
self.__update_secret_future: Optional[asyncio.Future] = None
self.__connection_unblocked: asyncio.Event = asyncio.Event()
self.__heartbeat_grace_timeout = (
(self.heartbeat_timeout + 1) * self.HEARTBEAT_GRACE_MULTIPLIER
)
self.__last_frame_time: float = self.loop.time()
self.__create_connection_kwargs = create_connection_kwargs
async def ready(self) -> None:
await self.connected.wait()
await self.__connection_unblocked.wait()
def set_close_reason(
self, reply_code: int = REPLY_SUCCESS,
reply_text: str = "normally closed",
class_id: int = 0, method_id: int = 0,
) -> None:
self.__close_reply_code = reply_code
self.__close_reply_text = reply_text
self.__close_class_id = class_id
self.__close_method_id = method_id
@property
def is_opened(self) -> bool:
is_reader_running = (
hasattr(self, "_reader_task") and not self._reader_task.done()
)
is_writer_running = (
hasattr(self, "_writer_task") and not self._writer_task.done()
)
return (
is_reader_running and
is_writer_running and
not self.is_closed
)
def __str__(self) -> str:
return str(censor_url(self.url))
def _get_ssl_context(self) -> ssl.SSLContext:
context = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH,
capath=self.ssl_certs.capath,
cafile=self.ssl_certs.cafile,
cadata=self.ssl_certs.cadata,
)
if self.ssl_certs.cert:
context.load_cert_chain(self.ssl_certs.cert, self.ssl_certs.key)
if not self.ssl_certs.verify:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
return context
def _client_properties(self, **kwargs: Any) -> Dict[str, Any]:
properties = {
"platform": PLATFORM,
"version": __version__,
"product": PRODUCT,
"capabilities": {
"authentication_failure_close": True,
"basic.nack": True,
"connection.blocked": True,
"consumer_cancel_notify": True,
"publisher_confirms": True,
},
"information": "See https://github.com/mosquito/aiormq/",
}
properties.update(
parse_connection_name(self.connection_name),
)
properties.update(kwargs)
return properties
def _credentials_class(
self,
start_frame: spec.Connection.Start,
) -> AuthMechanism:
auth_requested = self.url.query.get("auth", "plain").upper()
auth_available = start_frame.mechanisms.split()
if auth_requested in auth_available:
with suppress(KeyError):
return AuthMechanism[auth_requested]
raise AuthenticationError(
start_frame.mechanisms, [m.name for m in AuthMechanism],
)
@staticmethod
async def _rpc(
request: Frame, writer: asyncio.StreamWriter,
frame_receiver: FrameReceiver,
wait_response: bool = True,
) -> Optional[FrameTypes]:
writer.write(pamqp.frame.marshal(request, 0))
await writer.drain()
if not wait_response:
return None
_, _, frame = await frame_receiver.get_frame()
if request.synchronous and frame.name not in request.valid_responses:
raise AMQPInternalError(
"one of {!r}".format(request.valid_responses), frame,
)
elif isinstance(frame, spec.Connection.Close):
if frame.reply_code == 403:
raise ProbableAuthenticationError(frame.reply_text)
raise ConnectionClosed(frame.reply_code, frame.reply_text)
return frame
@task
async def connect(
self, client_properties: Optional[FieldTable] = None,
) -> bool:
if self.is_opened:
raise RuntimeError("Connection already opened")
ssl_context = self.ssl_context
if ssl_context is None and self.url.scheme == "amqps":
ssl_context = await self.loop.run_in_executor(
None, self._get_ssl_context,
)
self.ssl_context = ssl_context
log.debug("Connecting to: %s", self)
try:
reader, writer = await asyncio.open_connection(
self.url.host, self.url.port, ssl=ssl_context,
**self.__create_connection_kwargs,
)
frame_receiver = FrameReceiver(reader)
except OSError as e:
raise AMQPConnectionError(*e.args) from e
frame: Optional[FrameTypes]
try:
protocol_header = ProtocolHeader()
writer.write(protocol_header.marshal())
_, _, frame = await frame_receiver.get_frame()
except EOFError as e:
raise IncompatibleProtocolError(*e.args) from e
if not isinstance(frame, spec.Connection.Start):
raise AMQPInternalError("Connection.StartOk", frame)
credentials = self._credentials_class(frame)
server_properties: ArgumentsType = frame.server_properties
try:
frame = await self._rpc(
spec.Connection.StartOk(
client_properties=self._client_properties(
**(client_properties or {}),
),
mechanism=credentials.name,
response=credentials.value(self).marshal(),
),
writer=writer,
frame_receiver=frame_receiver,
)
if not isinstance(frame, spec.Connection.Tune):
raise AMQPInternalError("Connection.Tune", frame)
connection_tune: spec.Connection.Tune = frame
connection_tune.heartbeat = self.heartbeat_timeout
await self._rpc(
spec.Connection.TuneOk(
channel_max=connection_tune.channel_max,
frame_max=connection_tune.frame_max,
heartbeat=connection_tune.heartbeat,
),
writer=writer,
frame_receiver=frame_receiver,
wait_response=False,
)
frame = await self._rpc(
spec.Connection.Open(virtual_host=self.vhost),
writer=writer,
frame_receiver=frame_receiver,
)
if not isinstance(frame, spec.Connection.OpenOk):
raise AMQPInternalError("Connection.OpenOk", frame)
except BaseException as e:
await self.__close_writer(writer)
await self.close(e)
raise
# noinspection PyAsyncCall
self._reader_task = self.create_task(self.__reader(frame_receiver))
self._reader_task.add_done_callback(self._on_reader_done)
# noinspection PyAsyncCall
self._writer_task = self.create_task(self.__writer(writer))
self.connection_tune = connection_tune
self.server_properties = server_properties
return True
def _on_reader_done(self, task: asyncio.Task) -> None:
log.debug("Reader exited for %r", self)
if not task.cancelled() and task.exception() is not None:
log.debug("Cancelling cause reader exited abnormally")
self.set_close_reason(
reply_code=500, reply_text="reader unexpected closed",
)
async def close_writer_task() -> None:
if not self._writer_task.done():
self._writer_task.cancel()
await asyncio.gather(self._writer_task, return_exceptions=True)
try:
exc = task.exception()
except asyncio.CancelledError as e:
exc = e
await self.close(exc)
self.loop.create_task(close_writer_task())
async def __handle_close_ok(self, _: spec.Connection.CloseOk) -> None:
return
async def __handle_heartbeat(self, _: Heartbeat) -> None:
return
async def __handle_close(self, frame: spec.Connection.Close) -> None:
log.exception(
"Unexpected connection close from remote \"%s\", "
"Connection.Close(reply_code=%r, reply_text=%r)",
self, frame.reply_code, frame.reply_text,
)
with suppress(asyncio.QueueFull):
self.write_queue.put_nowait(
ChannelFrame.marshall(
channel_number=0,
frames=[spec.Connection.CloseOk()],
),
)
exception = exception_by_code(frame)
if (
self.__update_secret_future is not None and
not self.__update_secret_future.done()
):
self.__update_secret_future.set_exception(exception)
raise exception
async def __handle_channel_close_ok(
self, _: spec.Channel.CloseOk,
) -> None:
self.channels.pop(0, None)
async def __handle_channel_update_secret_ok(
self, frame: spec.Connection.UpdateSecretOk,
) -> None:
if (
self.__update_secret_future is not None and
not self.__update_secret_future.done()
):
self.__update_secret_future.set_result(frame)
return
log.warning("Got unexpected UpdateSecretOk frame")
async def __handle_connection_blocked(
self, frame: spec.Connection.Blocked,
) -> None:
log.warning("Connection %r was blocked by: %r", self, frame.reason)
self.__connection_unblocked.clear()
async def __handle_connection_unblocked(
self, _: spec.Connection.Unblocked,
) -> None:
log.warning("Connection %r was unblocked", self)
self.__connection_unblocked.set()
async def __reader(self, frame_receiver: FrameReceiver) -> None:
self.__connection_unblocked.set()
self.connected.set()
# Not very optimal, but avoid creating a task for each frame sending
# noinspection PyAsyncCall
if self.heartbeat_timeout > 0:
self.create_task(self.__heartbeat())
channel_frame_handlers: Mapping[Any, Callable[[Any], Awaitable[None]]]
channel_frame_handlers = {
spec.Connection.CloseOk: self.__handle_close_ok,
spec.Connection.Close: self.__handle_close,
Heartbeat: self.__handle_heartbeat,
spec.Channel.CloseOk: self.__handle_channel_close_ok,
spec.Connection.UpdateSecretOk: (
self.__handle_channel_update_secret_ok
),
spec.Connection.Blocked: self.__handle_connection_blocked,
spec.Connection.Unblocked: self.__handle_connection_unblocked,
}
try:
async for weight, channel, frame in frame_receiver:
self.__last_frame_time = self.loop.time()
log.debug(
"Received frame %r in channel #%d weight=%s on %r",
frame, channel, weight, self,
)
if channel == 0:
handler = channel_frame_handlers.get(type(frame))
if handler is None:
log.error("Unexpected frame %r", frame)
continue
await handler(frame)
continue
ch: Optional[AbstractChannel] = self.channels.get(channel)
if ch is None:
log.error(
"Got frame for closed channel %d: %r", channel, frame,
)
continue
if isinstance(frame, CHANNEL_CLOSE_RESPONSES):
self.channels[channel] = None
await ch.frames.put((weight, frame))
except asyncio.CancelledError:
if self.is_connection_was_stuck:
log.warning(
"Server connection %r was stuck. No frames were received "
"in %d seconds.", self, self.__heartbeat_grace_timeout,
)
self._writer_task.cancel()
raise
@property
def is_connection_was_stuck(self) -> bool:
delay = self.loop.time() - self.__last_frame_time
return delay > self.__heartbeat_grace_timeout
async def __heartbeat(self) -> None:
heartbeat_timeout = max(1, self.heartbeat_timeout // 2)
heartbeat = ChannelFrame.marshall(
frames=[Heartbeat()], channel_number=0,
)
while not self.closing.done():
if self.is_connection_was_stuck:
self._reader_task.cancel()
return
await asyncio.sleep(heartbeat_timeout)
try:
await asyncio.wait_for(
self.write_queue.put(heartbeat),
timeout=self.__heartbeat_grace_timeout,
)
except asyncio.TimeoutError:
self._reader_task.cancel()
return
async def __writer(self, writer: asyncio.StreamWriter) -> None:
channel_frame: ChannelFrame
try:
frame_iterator = FrameGenerator(self.write_queue)
self.closing.add_done_callback(
lambda _: frame_iterator.close_event.set(),
)
if not self.__connection_unblocked.is_set():
await self.__connection_unblocked.wait()
async for channel_frame in frame_iterator:
log.debug("Prepare to send %r", channel_frame)
writer.write(channel_frame.payload)
if channel_frame.should_close:
await writer.drain()
channel_frame.drain()
return
if channel_frame.should_drain:
await writer.drain()
channel_frame.drain()
except asyncio.CancelledError:
if not self.__check_writer(writer) or self.is_connection_was_stuck:
raise
frame = spec.Connection.Close(
reply_code=self.__close_reply_code,
reply_text=self.__close_reply_text,
class_id=self.__close_class_id,
method_id=self.__close_method_id,
)
writer.write(ChannelFrame.marshall(0, [frame]).payload)
log.debug("Sending %r to %r", frame, self)
try:
await asyncio.wait_for(
writer.drain(), timeout=self.__heartbeat_grace_timeout,
)
finally:
await self.__close_writer(writer)
raise
finally:
log.debug("Writer exited for %r", self)
if sys.version_info < (3, 7):
async def __close_writer(self, writer: asyncio.StreamWriter) -> None:
log.debug("Writer on connection %s closed", self)
writer.close()
else:
async def __close_writer(self, writer: asyncio.StreamWriter) -> None:
log.debug("Writer on connection %s closed", self)
with suppress(OSError, RuntimeError):
if writer.can_write_eof():
writer.write_eof()
writer.close()
await writer.wait_closed()
@staticmethod
def __check_writer(writer: asyncio.StreamWriter) -> bool:
if writer is None:
return False
if hasattr(writer, "is_closing"):
return not writer.is_closing()
if writer.transport:
return not writer.transport.is_closing()
return writer.can_write_eof()
async def _on_close(
self,
ex: Optional[ExceptionType] = ConnectionClosed(0, "normal closed"),
) -> None:
log.debug("Closing connection %r cause: %r", self, ex)
if not self._reader_task.done():
self._reader_task.cancel()
if not self._writer_task.done():
self._writer_task.cancel()
await asyncio.gather(
self._reader_task, self._writer_task, return_exceptions=True,
)
@property
def server_capabilities(self) -> ArgumentsType:
return self.server_properties["capabilities"] # type: ignore
@property
def basic_nack(self) -> bool:
return bool(self.server_capabilities.get("basic.nack"))
@property
def consumer_cancel_notify(self) -> bool:
return bool(self.server_capabilities.get("consumer_cancel_notify"))
@property
def exchange_exchange_bindings(self) -> bool:
return bool(self.server_capabilities.get("exchange_exchange_bindings"))
@property
def publisher_confirms(self) -> Optional[bool]:
publisher_confirms = self.server_capabilities.get("publisher_confirms")
if publisher_confirms is None:
return None
return bool(publisher_confirms)
async def channel(
self,
channel_number: Optional[int] = None,
publisher_confirms: bool = True,
frame_buffer_size: int = FRAME_BUFFER_SIZE,
timeout: TimeoutType = None,
**kwargs: Any,
) -> AbstractChannel:
await self.connected.wait()
if self.is_closed:
raise RuntimeError("%r closed" % self)
if not self.publisher_confirms and publisher_confirms:
raise ValueError("Server doesn't support publisher_confirms")
if channel_number is None:
async with self.last_channel_lock:
if self.channels:
self.last_channel = max(self.channels.keys())
while self.last_channel in self.channels.keys():
self.last_channel += 1
if self.last_channel > 65535:
log.warning("Resetting channel number for %r", self)
self.last_channel = 1
# switching context for prevent blocking event-loop
await asyncio.sleep(0)
channel_number = self.last_channel
elif channel_number in self.channels:
raise ValueError("Channel %d already used" % channel_number)
if channel_number < 0 or channel_number > 65535:
raise ValueError("Channel number too large")
channel = Channel(
self,
channel_number,
frame_buffer=frame_buffer_size,
publisher_confirms=publisher_confirms,
**kwargs,
)
self.channels[channel_number] = channel
try:
await channel.open(timeout=timeout)
except Exception:
self.channels[channel_number] = None
raise
return channel
async def update_secret(
self, new_secret: str, *,
reason: str = "", timeout: TimeoutType = None,
) -> spec.Connection.UpdateSecretOk:
channel_frame = ChannelFrame.marshall(
channel_number=0,
frames=[
spec.Connection.UpdateSecret(
new_secret=new_secret, reason=reason,
),
],
)
countdown = Countdown(timeout)
async with countdown.enter_context(self.__update_secret_lock):
self.__update_secret_future = self.loop.create_future()
await self.write_queue.put(channel_frame)
try:
response: spec.Connection.UpdateSecretOk = (
await countdown(self.__update_secret_future)
)
finally:
self.__update_secret_future = None
return response
async def __aenter__(self) -> AbstractConnection:
if not self.is_opened:
await self.connect()
return self
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
await self.close(exc_val)
async def connect(
url: URLorStr, *args: Any, client_properties: Optional[FieldTable] = None,
**kwargs: Any,
) -> AbstractConnection:
connection = Connection(url, *args, **kwargs)
await connection.connect(client_properties or {})
return connection
python-aiormq-6.8.1/aiormq/exceptions.py 0000664 0000000 0000000 00000013307 14737314232 0020372 0 ustar 00root root 0000000 0000000 from typing import Any, Optional
from pamqp.base import Frame
from pamqp.commands import Basic
from .abc import DeliveredMessage
class AMQPError(Exception):
reason = "An unspecified AMQP error has occurred: %s"
def __repr__(self) -> str:
try:
return "<%s: %s>" % (
self.__class__.__name__, self.reason % self.args,
)
except TypeError:
# FIXME: if you are here file an issue
return f"<{self.__class__.__name__}: {self.args!r}>"
# Backward compatibility
AMQPException = AMQPError
class AMQPConnectionError(AMQPError, ConnectionError):
reason = "Unexpected connection problem"
def __repr__(self) -> str:
if self.args:
return f"<{self.__class__.__name__}: {self.args!r}>"
return AMQPError.__repr__(self)
class IncompatibleProtocolError(AMQPConnectionError):
reason = "The protocol returned by the server is not supported"
class AuthenticationError(AMQPConnectionError):
reason = (
"Server and client could not negotiate use of the "
"authentication mechanisms. Server supports only %r, "
"but client supports only %r."
)
class ProbableAuthenticationError(AMQPConnectionError):
reason = (
"Client was disconnected at a connection stage indicating a "
"probable authentication error: %s"
)
class ConnectionClosed(AMQPConnectionError):
reason = "The AMQP connection was closed (%s) %s"
class ConnectionSyntaxError(ConnectionClosed):
reason = (
"The sender sent a frame that contained illegal values for "
"one or more fields. This strongly implies a programming error "
"in the sending peer: %r"
)
class ConnectionFrameError(ConnectionClosed):
reason = (
"The sender sent a malformed frame that the recipient could "
"not decode. This strongly implies a programming error "
"in the sending peer: %r"
)
class ConnectionCommandInvalid(ConnectionClosed):
reason = (
"The client sent an invalid sequence of frames, attempting to "
"perform an operation that was considered invalid by the server."
" This usually implies a programming error in the client: %r"
)
class ConnectionChannelError(ConnectionClosed):
reason = (
"The client attempted to work with a channel that had not been "
"correctly opened. This most likely indicates a fault in the "
"client layer: %r"
)
class ConnectionUnexpectedFrame(ConnectionClosed):
reason = (
"The peer sent a frame that was not expected, usually in the "
"context of a content header and body. This strongly indicates "
"a fault in the peer's content processing: %r"
)
class ConnectionResourceError(ConnectionClosed):
reason = (
"The server could not complete the method because it lacked "
"sufficient resources. This may be due to the client creating "
"too many of some type of entity: %r"
)
class ConnectionNotAllowed(ConnectionClosed):
reason = (
"The client tried to work with some entity in a manner that is "
"prohibited by the server, due to security settings or by "
"some other criteria: %r"
)
class ConnectionNotImplemented(ConnectionClosed):
reason = (
"The client tried to use functionality that is "
"not implemented in the server: %r"
)
class ConnectionInternalError(ConnectionClosed):
reason = (
" The server could not complete the method because of an "
"internal error. The server may require intervention by an "
"operator in order to resume normal operations: %r"
)
class AMQPChannelError(AMQPError):
reason = "An unspecified AMQP channel error has occurred"
class ChannelClosed(AMQPChannelError):
reason = "The channel was closed (%s) %s"
class ChannelAccessRefused(ChannelClosed):
reason = (
"The client attempted to work with a server entity to "
"which it has no access due to security settings: %r"
)
class ChannelNotFoundEntity(ChannelClosed):
reason = (
"The client attempted to work with a server "
"entity that does not exist: %r"
)
class ChannelLockedResource(ChannelClosed):
reason = (
"The client attempted to work with a server entity to "
"which it has no access because another client is working "
"with it: %r"
)
class ChannelPreconditionFailed(ChannelClosed):
reason = (
"The client requested a method that was not allowed because "
"some precondition failed: %r"
)
class DuplicateConsumerTag(ChannelClosed):
reason = "The consumer tag specified already exists for this channel: %s"
class ProtocolSyntaxError(AMQPError):
reason = "An unspecified protocol syntax error occurred"
class InvalidFrameError(ProtocolSyntaxError):
reason = "Invalid frame received: %r"
class MethodNotImplemented(AMQPError):
pass
class DeliveryError(AMQPError):
__slots__ = "message", "frame"
reason = "Error when delivery message %r, frame %r"
def __init__(
self, message: Optional[DeliveredMessage],
frame: Frame, *args: Any,
):
self.message = message
self.frame = frame
super().__init__(self.message, self.frame)
class PublishError(DeliveryError):
reason = "%r for routing key %r"
def __init__(self, message: DeliveredMessage, frame: Frame, *args: Any):
assert isinstance(message.delivery, Basic.Return)
self.message = message
self.frame = frame
super(DeliveryError, self).__init__(
message.delivery.reply_text, message.delivery.routing_key, *args,
)
class ChannelInvalidStateError(RuntimeError):
pass
python-aiormq-6.8.1/aiormq/py.typed 0000664 0000000 0000000 00000000000 14737314232 0017320 0 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/aiormq/tools.py 0000664 0000000 0000000 00000006324 14737314232 0017352 0 ustar 00root root 0000000 0000000 import asyncio
import platform
import time
from functools import wraps
from types import TracebackType
from typing import (
Any, AsyncContextManager, Awaitable, Callable, Coroutine, Optional, Type,
TypeVar, Union,
)
from yarl import URL
from aiormq.abc import TimeoutType
T = TypeVar("T")
def censor_url(url: URL) -> URL:
if url.password is not None:
return url.with_password("******")
return url
def shield(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
@wraps(func)
def wrap(*args: Any, **kwargs: Any) -> Awaitable[T]:
return asyncio.shield(func(*args, **kwargs))
return wrap
def awaitable(
func: Callable[..., Union[T, Awaitable[T]]],
) -> Callable[..., Coroutine[Any, Any, T]]:
# Avoid python 3.8+ warning
if asyncio.iscoroutinefunction(func):
return func # type: ignore
@wraps(func)
async def wrap(*args: Any, **kwargs: Any) -> T:
result = func(*args, **kwargs)
if hasattr(result, "__await__"):
return await result # type: ignore
if asyncio.iscoroutine(result) or asyncio.isfuture(result):
return await result
return result # type: ignore
return wrap
class Countdown:
__slots__ = "loop", "deadline"
if platform.system() == "Windows":
@staticmethod
def _now() -> float:
# windows monotonic timer resolution is not enough.
# Have to use time.time()
return time.time()
else:
@staticmethod
def _now() -> float:
return time.monotonic()
def __init__(self, timeout: TimeoutType = None):
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
self.deadline: TimeoutType = None
if timeout is not None:
self.deadline = self._now() + timeout
def get_timeout(self) -> TimeoutType:
if self.deadline is None:
return None
current = self._now()
if current >= self.deadline:
raise asyncio.TimeoutError
return self.deadline - current
async def __call__(self, coro: Awaitable[T]) -> T:
try:
timeout = self.get_timeout()
except asyncio.TimeoutError:
fut = asyncio.ensure_future(coro)
fut.cancel()
await asyncio.gather(fut, return_exceptions=True)
raise
if self.deadline is None and not timeout:
return await coro
return await asyncio.wait_for(coro, timeout=timeout)
def enter_context(
self, ctx: AsyncContextManager[T],
) -> AsyncContextManager[T]:
return CountdownContext(self, ctx)
class CountdownContext(AsyncContextManager):
def __init__(self, countdown: Countdown, ctx: AsyncContextManager):
self.countdown: Countdown = countdown
self.ctx: AsyncContextManager = ctx
async def __aenter__(self) -> T:
return await self.countdown(self.ctx.__aenter__())
async def __aexit__(
self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException], exc_tb: Optional[TracebackType],
) -> Any:
return await self.countdown(
self.ctx.__aexit__(exc_type, exc_val, exc_tb),
)
python-aiormq-6.8.1/aiormq/types.py 0000664 0000000 0000000 00000000347 14737314232 0017355 0 ustar 00root root 0000000 0000000 import warnings
from .abc import * # noqa
warnings.warn(
"aiormq.types was deprecated and will be removed in "
"one of next major releases. Use aiormq.abc instead.",
category=DeprecationWarning,
stacklevel=2,
)
python-aiormq-6.8.1/gray.conf 0000664 0000000 0000000 00000000127 14737314232 0016154 0 ustar 00root root 0000000 0000000 formatters = add-trailing-comma,isort,unify
min-python-version = 3.7
log-level = error
python-aiormq-6.8.1/poetry.lock 0000664 0000000 0000000 00000232260 14737314232 0016544 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "aiomisc"
version = "17.3.25"
description = "aiomisc - miscellaneous utils for asyncio"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "aiomisc-17.3.25-py3-none-any.whl", hash = "sha256:40c10d60149769ff6d2d36790986e2c5296414eb49c6107f361e77234aaa6fbc"},
{file = "aiomisc-17.3.25.tar.gz", hash = "sha256:10f11f04aff59dbc1c6a3ab2bf99455fed3658cbdbc859e28f6c3927dd7e50af"},
]
[package.dependencies]
colorlog = ">=6.0,<7.0"
logging-journald = {version = "*", markers = "sys_platform == \"linux\""}
typing_extensions = {version = "*", markers = "python_version < \"3.10\""}
[package.extras]
aiohttp = ["aiohttp (>3)"]
asgi = ["aiohttp-asgi (>=0.5.2,<0.6.0)"]
carbon = ["aiocarbon (>=0.15,<0.16)"]
cron = ["croniter (>=1.3.8,<2.0.0)"]
grpc = ["grpcio (>=1.56.0,<2.0.0)", "grpcio-tools (>=1.56.0,<2.0.0)"]
raven = ["aiohttp (>3)", "raven"]
rich = ["rich"]
uvloop = ["uvloop (>=0.14,<1)"]
[[package]]
name = "aiomisc-pytest"
version = "1.1.1"
description = "pytest integration for aiomisc"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "aiomisc_pytest-1.1.1-py3-none-any.whl", hash = "sha256:c07251f79c936c85c7589429f43c728cf1a34b80c0089b268f2cfa6186e77020"},
{file = "aiomisc_pytest-1.1.1.tar.gz", hash = "sha256:2c378c41b078c0576027de6bf7fbc537a7e69285d23eaf4d45738d5d0de56dd3"},
]
[package.dependencies]
aiomisc = ">=17"
pytest = ">=7.2.1,<8.0.0"
[[package]]
name = "certifi"
version = "2023.11.17"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "collective-checkdocs"
version = "0.2"
description = "Distutils command to view and validate restructured text in package's long_description"
optional = false
python-versions = "*"
files = [
{file = "collective.checkdocs-0.2.zip", hash = "sha256:3a5328257c5224bc72753820c182910d7fb336bc1dba5e09113d48566655e46e"},
]
[package.dependencies]
docutils = "*"
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "colorlog"
version = "6.8.0"
description = "Add colours to the output of Python's logging module."
optional = false
python-versions = ">=3.6"
files = [
{file = "colorlog-6.8.0-py3-none-any.whl", hash = "sha256:4ed23b05a1154294ac99f511fabe8c1d6d4364ec1f7fc989c7fb515ccc29d375"},
{file = "colorlog-6.8.0.tar.gz", hash = "sha256:fbb6fdf9d5685f2517f388fb29bb27d54e8654dd31f58bc2a3b217e967a95ca6"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
[[package]]
name = "coverage"
version = "6.5.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
{file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
{file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
{file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
{file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
{file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
{file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
{file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
{file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
{file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
{file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
{file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
{file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
{file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
{file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
{file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
{file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
{file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
{file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
{file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]]
name = "coveralls"
version = "3.3.1"
description = "Show coverage stats online via coveralls.io"
optional = false
python-versions = ">= 3.5"
files = [
{file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"},
{file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"},
]
[package.dependencies]
coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0"
docopt = ">=0.6.1"
requests = ">=1.0.0"
[package.extras]
yaml = ["PyYAML (>=3.10)"]
[[package]]
name = "docopt"
version = "0.6.2"
description = "Pythonic argument parser, that will make you smile"
optional = false
python-versions = "*"
files = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
[[package]]
name = "docutils"
version = "0.20.1"
description = "Docutils -- Python Documentation Utilities"
optional = false
python-versions = ">=3.7"
files = [
{file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"},
{file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "idna"
version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "logging-journald"
version = "0.6.7"
description = "Pure python logging handler for writing logs to the journald using native protocol"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "logging_journald-0.6.7-py3-none-any.whl", hash = "sha256:ef9333a84fd64fbe1e18ca6f22624af4fc5d92d519a2e2652aa43358548898eb"},
{file = "logging_journald-0.6.7.tar.gz", hash = "sha256:5fdb576fff2ff82e98be1c7b4f0cbd87f061de5dbed38030f388dd4ba2d52e7d"},
]
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "multidict"
version = "6.0.4"
description = "multidict implementation"
optional = false
python-versions = ">=3.7"
files = [
{file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
{file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
{file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
{file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
{file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
{file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
{file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
{file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
{file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
{file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
{file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
{file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
{file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
{file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
{file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
{file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
{file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
{file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
{file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
{file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
{file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
{file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
{file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
{file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
{file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
{file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
{file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
{file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
{file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
{file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
{file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
{file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
{file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
]
[[package]]
name = "mypy"
version = "0.991"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"},
{file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"},
{file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"},
{file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"},
{file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"},
{file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"},
{file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"},
{file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"},
{file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"},
{file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"},
{file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"},
{file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"},
{file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"},
{file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"},
{file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"},
{file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"},
{file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"},
{file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"},
{file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"},
{file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"},
{file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"},
{file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"},
{file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"},
{file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"},
{file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"},
{file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"},
{file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"},
{file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"},
{file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"},
{file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"},
]
[package.dependencies]
mypy-extensions = ">=0.4.3"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=3.10"
[package.extras]
dmypy = ["psutil (>=4.0)"]
install-types = ["pip"]
python2 = ["typed-ast (>=1.4.0,<2)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
name = "pamqp"
version = "3.3.0"
description = "RabbitMQ Focused AMQP low-level library"
optional = false
python-versions = ">=3.7"
files = [
{file = "pamqp-3.3.0-py2.py3-none-any.whl", hash = "sha256:c901a684794157ae39b52cbf700db8c9aae7a470f13528b9d7b4e5f7202f8eb0"},
{file = "pamqp-3.3.0.tar.gz", hash = "sha256:40b8795bd4efcf2b0f8821c1de83d12ca16d5760f4507836267fd7a02b06763b"},
]
[package.extras]
codegen = ["lxml", "requests", "yapf"]
testing = ["coverage", "flake8", "flake8-comprehensions", "flake8-deprecated", "flake8-import-order", "flake8-print", "flake8-quotes", "flake8-rst-docstrings", "flake8-tuple", "yapf"]
[[package]]
name = "pluggy"
version = "1.2.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"},
{file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
[[package]]
name = "pycodestyle"
version = "2.10.0"
description = "Python style guide checker"
optional = false
python-versions = ">=3.6"
files = [
{file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"},
{file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"},
]
[[package]]
name = "pydocstyle"
version = "6.3.0"
description = "Python docstring style checker"
optional = false
python-versions = ">=3.6"
files = [
{file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"},
{file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"},
]
[package.dependencies]
snowballstemmer = ">=2.2.0"
[package.extras]
toml = ["tomli (>=1.2.3)"]
[[package]]
name = "pyflakes"
version = "3.0.1"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.6"
files = [
{file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"},
{file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"},
]
[[package]]
name = "pygments"
version = "2.17.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.7"
files = [
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
]
[package.extras]
plugins = ["importlib-metadata"]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pylama"
version = "8.4.1"
description = "Code audit tool for python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pylama-8.4.1-py3-none-any.whl", hash = "sha256:5bbdbf5b620aba7206d688ed9fc917ecd3d73e15ec1a89647037a09fa3a86e60"},
{file = "pylama-8.4.1.tar.gz", hash = "sha256:2d4f7aecfb5b7466216d48610c7d6bad1c3990c29cdd392ad08259b161e486f6"},
]
[package.dependencies]
mccabe = ">=0.7.0"
pycodestyle = ">=2.9.1"
pydocstyle = ">=6.1.1"
pyflakes = ">=2.5.0"
[package.extras]
all = ["eradicate", "mypy", "pylint", "radon", "vulture"]
eradicate = ["eradicate"]
mypy = ["mypy"]
pylint = ["pylint"]
radon = ["radon"]
tests = ["eradicate (>=2.0.0)", "mypy", "pylama-quotes", "pylint (>=2.11.1)", "pytest (>=7.1.2)", "pytest-mypy", "radon (>=5.1.0)", "toml", "types-setuptools", "types-toml", "vulture"]
toml = ["toml (>=0.10.2)"]
vulture = ["vulture"]
[[package]]
name = "pytest"
version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "4.1.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
]
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
[[package]]
name = "pytest-rst"
version = "0.0.7"
description = "Test code from RST documents with pytest"
optional = false
python-versions = "*"
files = [
{file = "pytest-rst-0.0.7.tar.gz", hash = "sha256:2b34aa9d41ce8dce3e685e6f40dff432804962ffec49d3d37565f2dbad4014d5"},
]
[package.dependencies]
docutils = "*"
py = "*"
pygments = "*"
pytest = "*"
[[package]]
name = "requests"
version = "2.32.0"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"},
{file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "setuptools"
version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
optional = false
python-versions = "*"
files = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "types-docutils"
version = "0.20.0.3"
description = "Typing stubs for docutils"
optional = false
python-versions = "*"
files = [
{file = "types-docutils-0.20.0.3.tar.gz", hash = "sha256:4928e790f42b99d5833990f99c8dd9fa9f16825f6ed30380ca981846d36870cd"},
{file = "types_docutils-0.20.0.3-py3-none-any.whl", hash = "sha256:a930150d8e01a9170f9bca489f46808ddebccdd8bc1e47c07968a77e49fb9321"},
]
[[package]]
name = "types-setuptools"
version = "65.7.0.4"
description = "Typing stubs for setuptools"
optional = false
python-versions = "*"
files = [
{file = "types-setuptools-65.7.0.4.tar.gz", hash = "sha256:147809433301fe7e0f4ef5c0782f9a0453788960575e1efb6da5fe8cb2493c9f"},
{file = "types_setuptools-65.7.0.4-py3-none-any.whl", hash = "sha256:522067dfd8e1771f8d7e047e451de2740dc4e0c9f48a22302a6cc96e6c964a13"},
]
[package.dependencies]
types-docutils = "*"
[[package]]
name = "typing-extensions"
version = "4.7.1"
description = "Backported and Experimental Type Hints for Python 3.7+"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
{file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
]
[[package]]
name = "urllib3"
version = "2.0.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.7"
files = [
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "uvloop"
version = "0.18.0"
description = "Fast implementation of asyncio event loop on top of libuv"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "uvloop-0.18.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f354d669586fca96a9a688c585b6257706d216177ac457c92e15709acaece10"},
{file = "uvloop-0.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:280904236a5b333a273292b3bcdcbfe173690f69901365b973fa35be302d7781"},
{file = "uvloop-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad79cd30c7e7484bdf6e315f3296f564b3ee2f453134a23ffc80d00e63b3b59e"},
{file = "uvloop-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99deae0504547d04990cc5acf631d9f490108c3709479d90c1dcd14d6e7af24d"},
{file = "uvloop-0.18.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:edbb4de38535f42f020da1e3ae7c60f2f65402d027a08a8c60dc8569464873a6"},
{file = "uvloop-0.18.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:54b211c46facb466726b227f350792770fc96593c4ecdfaafe20dc00f3209aef"},
{file = "uvloop-0.18.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:25b714f07c68dcdaad6994414f6ec0f2a3b9565524fba181dcbfd7d9598a3e73"},
{file = "uvloop-0.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1121087dfeb46e9e65920b20d1f46322ba299b8d93f7cb61d76c94b5a1adc20c"},
{file = "uvloop-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74020ef8061678e01a40c49f1716b4f4d1cc71190d40633f08a5ef8a7448a5c6"},
{file = "uvloop-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f4a549cd747e6f4f8446f4b4c8cb79504a8372d5d3a9b4fc20e25daf8e76c05"},
{file = "uvloop-0.18.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6132318e1ab84a626639b252137aa8d031a6c0550250460644c32ed997604088"},
{file = "uvloop-0.18.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:585b7281f9ea25c4a5fa993b1acca4ad3d8bc3f3fe2e393f0ef51b6c1bcd2fe6"},
{file = "uvloop-0.18.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:61151cc207cf5fc88863e50de3d04f64ee0fdbb979d0b97caf21cae29130ed78"},
{file = "uvloop-0.18.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c65585ae03571b73907b8089473419d8c0aff1e3826b3bce153776de56cbc687"},
{file = "uvloop-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3d301e23984dcbc92d0e42253e0e0571915f0763f1eeaf68631348745f2dccc"},
{file = "uvloop-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:680da98f12a7587f76f6f639a8aa7708936a5d17c5e7db0bf9c9d9cbcb616593"},
{file = "uvloop-0.18.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:75baba0bfdd385c886804970ae03f0172e0d51e51ebd191e4df09b929771b71e"},
{file = "uvloop-0.18.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ed3c28337d2fefc0bac5705b9c66b2702dc392f2e9a69badb1d606e7e7f773bb"},
{file = "uvloop-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8849b8ef861431543c07112ad8436903e243cdfa783290cbee3df4ce86d8dd48"},
{file = "uvloop-0.18.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:211ce38d84118ae282a91408f61b85cf28e2e65a0a8966b9a97e0e9d67c48722"},
{file = "uvloop-0.18.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0a8f706b943c198dcedf1f2fb84899002c195c24745e47eeb8f2fb340f7dfc3"},
{file = "uvloop-0.18.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:58e44650cbc8607a218caeece5a689f0a2d10be084a69fc32f7db2e8f364927c"},
{file = "uvloop-0.18.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b8b7cf7806bdc745917f84d833f2144fabcc38e9cd854e6bc49755e3af2b53e"},
{file = "uvloop-0.18.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:56c1026a6b0d12b378425e16250acb7d453abaefe7a2f5977143898db6cfe5bd"},
{file = "uvloop-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:12af0d2e1b16780051d27c12de7e419b9daeb3516c503ab3e98d364cc55303bb"},
{file = "uvloop-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b028776faf9b7a6d0a325664f899e4c670b2ae430265189eb8d76bd4a57d8a6e"},
{file = "uvloop-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53aca21735eee3859e8c11265445925911ffe410974f13304edb0447f9f58420"},
{file = "uvloop-0.18.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:847f2ed0887047c63da9ad788d54755579fa23f0784db7e752c7cf14cf2e7506"},
{file = "uvloop-0.18.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6e20bb765fcac07879cd6767b6dca58127ba5a456149717e0e3b1f00d8eab51c"},
{file = "uvloop-0.18.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e14de8800765b9916d051707f62e18a304cde661fa2b98a58816ca38d2b94029"},
{file = "uvloop-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f3b18663efe0012bc4c315f1b64020e44596f5fabc281f5b0d9bc9465288559c"},
{file = "uvloop-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6d341bc109fb8ea69025b3ec281fcb155d6824a8ebf5486c989ff7748351a37"},
{file = "uvloop-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:895a1e3aca2504638a802d0bec2759acc2f43a0291a1dff886d69f8b7baff399"},
{file = "uvloop-0.18.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d90858f32a852988d33987d608bcfba92a1874eb9f183995def59a34229f30d"},
{file = "uvloop-0.18.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db1fcbad5deb9551e011ca589c5e7258b5afa78598174ac37a5f15ddcfb4ac7b"},
{file = "uvloop-0.18.0.tar.gz", hash = "sha256:d5d1135beffe9cd95d0350f19e2716bc38be47d5df296d7cc46e3b7557c0d1ff"},
]
[package.extras]
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
[[package]]
name = "yarl"
version = "1.9.4"
description = "Yet another URL library"
optional = false
python-versions = ">=3.7"
files = [
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
{file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
{file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
{file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
{file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
{file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
{file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
{file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
{file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
{file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
{file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
{file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
{file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
{file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
{file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
{file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
]
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "a6ddd156362ba6dedf5f83ded8b3a4fb566c06bdf0a7fbf83e8bc122ed71a45f"
python-aiormq-6.8.1/poetry.toml 0000664 0000000 0000000 00000000105 14737314232 0016556 0 ustar 00root root 0000000 0000000 cache-dir = ".cache"
[virtualenvs]
path = ".venv"
in-project = true
python-aiormq-6.8.1/pylama.ini 0000664 0000000 0000000 00000000270 14737314232 0016326 0 ustar 00root root 0000000 0000000 [pylama]
linters = mccabe,pycodestyle,pyflakes
skip = *env*,.tox*,*build*,.*,env/*,.venv/*
ignore = C901
[pylama:pycodestyle]
max_line_length = 80
show-pep8 = True
show-source = True
python-aiormq-6.8.1/pyproject.toml 0000664 0000000 0000000 00000004651 14737314232 0017265 0 ustar 00root root 0000000 0000000 [tool.poetry]
name = "aiormq"
version = "6.8.1"
description = "Pure python AMQP asynchronous client library"
authors = ["Dmitry Orlov "]
readme = "README.rst"
license = "Apache-2.0"
keywords=["rabbitmq", "asyncio", "amqp", "amqp 0.9.1", "driver", "pamqp"]
homepage = "https://github.com/mosquito/aiormq"
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: Apache Software License",
"Topic :: Internet",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: System :: Clustering",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: MacOS",
"Operating System :: POSIX",
"Operating System :: Microsoft",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: Implementation :: CPython",
]
packages = [{ include = "aiormq" }]
[tool.poetry.urls]
"Source" = "https://github.com/mosquito/aiormq"
"Tracker" = "https://github.com/mosquito/aiormq/issues"
"Documentation" = "https://github.com/mosquito/aiormq/blob/master/README.rst"
[tool.poetry.dependencies]
python = "^3.8"
pamqp = "3.3.0"
setuptools = [{ version = '*', python = "< 3.8" }]
yarl = [{ version = '*'}]
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.4"
coverage = "^6.5.0"
coveralls = "^3.3.1"
pylama = "^8.4.1"
pytest-cov = "^4.0.0"
collective-checkdocs = "^0.2"
mypy = "^0.991"
pytest-rst = "^0.0.7"
types-setuptools = "^65.6.0.2"
aiomisc-pytest = "^1.1.1"
setuptools = "^69.0.3"
[tool.poetry.group.uvloop.dependencies]
uvloop = ["^0.18"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = false
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
follow_imports = "silent"
no_implicit_reexport = true
strict_optional = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = false
files = "aiormq"
python-aiormq-6.8.1/tests/ 0000775 0000000 0000000 00000000000 14737314232 0015505 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/tests/__init__.py 0000664 0000000 0000000 00000000000 14737314232 0017604 0 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/tests/certs/ 0000775 0000000 0000000 00000000000 14737314232 0016625 5 ustar 00root root 0000000 0000000 python-aiormq-6.8.1/tests/certs/Dockerfile 0000664 0000000 0000000 00000000622 14737314232 0020617 0 ustar 00root root 0000000 0000000 FROM rabbitmq:3.8-management-alpine
RUN mkdir -p /certs/
COPY tests/certs/ca.pem /certs/
COPY tests/certs/server.key /certs/
COPY tests/certs/server.pem /certs/
ENV RABBITMQ_SSL_CERTFILE=/certs/server.pem
ENV RABBITMQ_SSL_KEYFILE=/certs/server.key
ENV RABBITMQ_SSL_CACERTFILE=/certs/ca.pem
ENV RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=false
ENV RABBITMQ_DEFAULT_USER=guest
ENV RABBITMQ_DEFAULT_PASS=guest
python-aiormq-6.8.1/tests/certs/ca.pem 0000664 0000000 0000000 00000004122 14737314232 0017712 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIF+DCCA+CgAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBgzELMAkGA1UEBhMCUlUx
DzANBgNVBAgTBk1vc2NvdzEPMA0GA1UEBxMGTW9zY293MQ0wCwYDVQQKEwRIb21l
MRAwDgYDVQQLEwdJVCBDcmV3MRAwDgYDVQQDEwdUZXN0IENBMR8wHQYJKoZIhvcN
AQkBFhByb290QGV4YW1wbGUuY29tMCAXDTE4MTIxNTExMzEwMFoYDzIyMTgxMjE1
MTEzMTAwWjCBgzELMAkGA1UEBhMCUlUxDzANBgNVBAgTBk1vc2NvdzEPMA0GA1UE
BxMGTW9zY293MQ0wCwYDVQQKEwRIb21lMRAwDgYDVQQLEwdJVCBDcmV3MRAwDgYD
VQQDEwdUZXN0IENBMR8wHQYJKoZIhvcNAQkBFhByb290QGV4YW1wbGUuY29tMIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2PtC5aCUO3dj1H6rK3pXwUFY
msIMB6uI3cKqx4U3thyL3orTAYu51Ax/nG8iVi9X3CY0v0Bfwrq004oqCFuyygl0
yNRKomx9prpunCRv+vW6ojpif+iMOJmyGKQ8vhMSCbUgbk2Z53U1FKYWybPk9bXA
fpI1KdyT2iVB0wDKKLkbLTtMuSOiFDCYZK5+yVBhbxIBQtoldhejbgHh0z7r78Bz
v5vRwTyL73xHJydj+7yxJp4BgcptGYMJO6pb+c8vbLBdQy38i1vAAQa1XQzh8jUU
KMXsO6LssMAdVMpA53uscQNz8j7g+cGwnWPe2t1f8I81tbI/oZ3MAf42zSAfbvde
x9LbaXmmYAcifqdkkHaaaTKr8jVZyxK/CKMUzsL7JckDxXMAQgoKofWespyi2cqF
/XISEnFaqOFh5brxwZmIKy2/GAqpyIv2BPWefgEhw1+d6GZMysPiSZKOdGUtoqlW
1Ni55z/gKLYNcTRyah7cGP6tGasSFjZAvMmgW6I+Q559pUXfsCSI+MGzGgeBNiPX
460j4qfyM/2kvR2vIjtY5choV2utDk3XMr5jSPatFKPX0K136TTMqEgBHL3SQ228
apR18MrNYwnlOSxXsr/85s/Hf7dtk7OPRIxJPiewUnUK2UrLQ8eLYdSMjJgJxfmc
2a2DQwq/0MwQomcJbtECAwEAAaNzMHEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUcylnYt/dChvaK5Ndc/Ko25dXrfswCwYDVR0PBAQDAgEGMBEGCWCGSAGG+EIB
AQQEAwIABzAfBglghkgBhvhCAQ0EEhYQdGVzdCBjZXJ0aWZpY2F0ZTANBgkqhkiG
9w0BAQ0FAAOCAgEATHN9WJedxwW3bDr5yO1BgOVGYaGpbg2DheHnL61smQwimfZm
T8z/1mn9iKBJd++lh/lfCTINlX4N3ViCTgDx+dEilQ80RQlbpi5fcrsU0xbuBHBX
Pn/I61CuGebfFMQLD8qazkuZ7SDZMA5LdmPL7FnIJpHdl7DnnghyLHuakLn/7Qlu
UDh8WBjWHGIwIuS9g5gB9cwVPV1tTPz+PrBUdvQKUWUgBuS5MbdpPzNKJq9Q6qTB
khnso6s0+CQ7oIR30El3vxSueS7T6wfIFp/PL1jwesJ2AmBWz84I5n8dTwchFrXD
dDMsAgx1Ea2QFbXSRnsjdY8Mufkt31SIG4e5xSERoQ2STkfsDiqWWhEywI2hOc3x
E8+QhXyjwKw6W6d4Nt5tg5sFB2+4CJ4jKxzyE5jZHSNrf8365dicz9YEGSPuDUs7
oZcErGlbP+ixR0G/C0R0FG3+8XnhUJBJDd6wGiivM/y2ajfvIoEtfL0vg5pia2Bh
hCUI8+jXg6TDvrGfGYAghDXNzY02KHP/sfGcDtQzc2qFuhlikdfowhT8+djJrVtm
YNQeKie9XE8wYVUiuQE0ZRUFv2bY02Ur2WMzF+v7np+XcM5SMhGAWsV5xOQBv+6W
msfhw6XNx2U5lGDtmI+r9l8dJgLGYIXKTJSfSGn1t8cLBbu/4OmpsP9UkWk=
-----END CERTIFICATE-----
python-aiormq-6.8.1/tests/certs/client.key 0000664 0000000 0000000 00000006247 14737314232 0020626 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
python-aiormq-6.8.1/tests/certs/client.pem 0000664 0000000 0000000 00000010224 14737314232 0020605 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
python-aiormq-6.8.1/tests/certs/server.key 0000664 0000000 0000000 00000006253 14737314232 0020653 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
python-aiormq-6.8.1/tests/certs/server.pem 0000664 0000000 0000000 00000010301 14737314232 0020631 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIGDjCCA/agAwIBAgIBBTANBgkqhkiG9w0BAQ0FADCBgzELMAkGA1UEBhMCUlUx
DzANBgNVBAgTBk1vc2NvdzEPMA0GA1UEBxMGTW9zY293MQ0wCwYDVQQKEwRIb21l
MRAwDgYDVQQLEwdJVCBDcmV3MRAwDgYDVQQDEwdUZXN0IENBMR8wHQYJKoZIhvcN
AQkBFhByb290QGV4YW1wbGUuY29tMCAXDTE4MTIxNTExMzMwMFoYDzIyMTcxMjE1
MTEzMzAwWjCBhTELMAkGA1UEBhMCUlUxDzANBgNVBAgTBk1vc2NvdzEPMA0GA1UE
BxMGTW9zY293MQ0wCwYDVQQKEwRIb21lMRAwDgYDVQQLEwdJVCBDcmV3MRIwEAYD
VQQDEwlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20w
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDQchaeDZkDZoH6sNXitp2L
+NXDJWUZlcDkWbUvcthzv/5CQwdHVIY90gvfjEAkndTNMlPlkXfGCYf+LKisOI6d
HfPV0l+rwayZaSx/T/bSaLop/V3SsV5q17JZj4/6Uxu+Nc60TvFJr78EC7euREzS
fphtnyAc0S/4TfUvXiScMfEXPCph4koK+cDplrUJ7B4a+ojnCEUgVRiNS6mLTb/6
NlOn3s3TTAytANcNiKLP7NyvKodIfI5EnD9+A+nn+A2//MS35atiAya7BWVJwr/z
C2QtzkeY1FG5bR5NFdqD3E0xUyqZK3X/xO2Si3n/zPWOSVRVXomqAQXqOMGQHic+
dr3Exclx/09UgC6HhrvhebFEZJVoXq7VK/JnsNZGFsBNufyVbszVeCWt3q0r8huO
205aci3WQsyVt3F1CD/Jnnwp29cToEIcmCLYqjBshCw9VO/fcFN3d6p/Cp+Q5Ogx
eXPiOcrWaChOR3J7SE4ehn3WQEJ7lMiErx8xEA9Fi1NRb3tLG50drFV6e3wg16rP
kw8cdl2h9oFUKmKFcc9+emEblFy/KMZrzAYdoiYQrzV5+CbwTeMXuRbYWfWZXUhE
279C/INvOADotEcJdXWeOHVqjMbssOKenkTNU1MBIAj5ifQp1ThXf9+5rJcEWr/j
TuDPY5CsbeUH0n944iWX6wIDAQABo4GGMIGDMAwGA1UdEwEB/wQCMAAwHQYDVR0O
BBYEFIxbKKeY60LCPZxa/3cRQvvCnjCdMAsGA1UdDwQEAwIF4DAaBgNVHREEEzAR
hwR/AAABgglsb2NhbGhvc3QwEQYJYIZIAYb4QgEBBAQDAgZAMBgGCWCGSAGG+EIB
DQQLFglsb2NhbGhvc3QwDQYJKoZIhvcNAQENBQADggIBAEmG6ijYL0NUfkH3BTd+
tSmLzetOOmec7YrIVTA0VNUgXaQLiQ814qr1Bc14Q0f5jDMb+Nq+t0iGwGHQpSwr
2es5jU5hAggPfvnT2htT4aSz6qvv9LVurdaeSuP5kLRhKNmHbfts75Mpvaxk0NH1
n5ScQId/7Heg0s0nvZn8MYoLDSI0WoZLewRNia2igmcB2r5/YYWDzjakt7zmQzsH
P4jjvdSrmJgcpe8F3tZLPcNk/3ib330kt9GM8BzPlIMNzpXrbslWWcurzIrC25dJ
5gYyOxkjCZ9dgvjMFov8tKgszSVVj7jI4AbDMlPdCNhY+h2VwYP67QJgX2lSkGVm
WkgTv2aJ5/ElV7CqcEl4/tT4ncfjIKDy6F9rUXcR6qVF4UHXFpilgKo+tJMpRlxq
f6Qbzkda83tU3LaGcc1PeY/e13eEH4I23ms4ffieUGbhx4OJ1RYAukoiapQWbCJl
9zLIkRHXlmWspMvJHsTvESANx1ImCK4HySqbheaq8K1N1dH+7uHRbSF+QK14yKTj
u7a3cw3tqlg7SfBSywBEbkv83KiBLGWcTvo8yS9xcaIRoqnqakrgRe5pAYRnu/zh
IW4GrF45UB6Meqc9Cb+6yCHp4OmkfJZQoEdqhaVv8JP+D+Q4tF0UaLmqP2ybtk6L
MvekAEBEOasl6ZnGM/QQBN1o
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
python-aiormq-6.8.1/tests/conftest.py 0000664 0000000 0000000 00000007113 14737314232 0017706 0 ustar 00root root 0000000 0000000 import asyncio
import gc
import logging
import os
import tracemalloc
import pamqp
import pytest
from aiomisc_pytest import TCPProxy
from yarl import URL
from aiormq import Connection
def cert_path(*args):
return os.path.join(
os.path.abspath(os.path.dirname(__file__)), "certs", *args,
)
AMQP_URL = URL(os.getenv("AMQP_URL", "amqp://guest:guest@localhost/"))
amqp_urls = {
"amqp": AMQP_URL,
"amqp-named": AMQP_URL.update_query(name="pytest"),
"amqps": AMQP_URL.with_scheme("amqps").with_query(
{"cafile": cert_path("ca.pem"), "no_verify_ssl": 1},
),
"amqps-client": AMQP_URL.with_scheme("amqps").with_query(
{
"cafile": cert_path("ca.pem"),
"keyfile": cert_path("client.key"),
"certfile": cert_path("client.pem"),
"no_verify_ssl": 1,
},
),
}
amqp_url_list, amqp_url_ids = [], []
for name, url in amqp_urls.items():
amqp_url_list.append(url)
amqp_url_ids.append(name)
@pytest.fixture(params=amqp_url_list, ids=amqp_url_ids)
async def amqp_url(request):
return request.param
@pytest.fixture
async def amqp_connection(amqp_url, loop):
connection = Connection(amqp_url, loop=loop)
async with connection:
yield connection
channel_params = [
dict(channel_number=None, frame_buffer_size=10, publisher_confirms=True),
dict(channel_number=None, frame_buffer_size=1, publisher_confirms=True),
dict(channel_number=None, frame_buffer_size=10, publisher_confirms=False),
dict(channel_number=None, frame_buffer_size=1, publisher_confirms=False),
]
@pytest.fixture(params=channel_params)
async def amqp_channel(request, amqp_connection):
try:
yield await amqp_connection.channel(**request.param)
finally:
await amqp_connection.close()
skip_when_quick_test = pytest.mark.skipif(
os.getenv("TEST_QUICK") is not None, reason="quick test",
)
@pytest.fixture(autouse=True)
def memory_tracer():
tracemalloc.start()
tracemalloc.clear_traces()
filters = (
tracemalloc.Filter(True, pamqp.__file__),
tracemalloc.Filter(True, asyncio.__file__),
)
snapshot_before = tracemalloc.take_snapshot().filter_traces(filters)
def format_stat(stats):
items = [
"TOP STATS:",
"%-90s %6s %6s %6s" % ("Traceback", "line", "size", "count"),
]
for stat in stats:
fname = stat.traceback[0].filename
lineno = stat.traceback[0].lineno
items.append(
"%-90s %6s %6s %6s"
% (fname, lineno, stat.size_diff, stat.count_diff),
)
return "\n".join(items)
try:
yield
gc.collect()
snapshot_after = tracemalloc.take_snapshot().filter_traces(filters)
top_stats = snapshot_after.compare_to(
snapshot_before, "lineno", cumulative=True,
)
if top_stats:
logging.error(format_stat(top_stats))
raise AssertionError("Possible memory leak")
finally:
tracemalloc.stop()
@pytest.fixture()
async def proxy(tcp_proxy, localhost, amqp_url: URL):
port = amqp_url.port or 5672 if amqp_url.scheme == "amqp" else 5671
async with tcp_proxy(amqp_url.host, port) as proxy:
yield proxy
@pytest.fixture
async def proxy_connection(proxy: TCPProxy, amqp_url: URL, loop):
url = amqp_url.with_host(
"localhost",
).with_port(
proxy.proxy_port,
)
connection = Connection(url, loop=loop)
await connection.connect()
try:
yield connection
finally:
await connection.close()
python-aiormq-6.8.1/tests/test_channel.py 0000664 0000000 0000000 00000020461 14737314232 0020531 0 ustar 00root root 0000000 0000000 import asyncio
import uuid
from os import urandom
import pytest
from aiomisc_pytest import TCPProxy
import aiormq
from aiormq.abc import DeliveredMessage
async def test_simple(amqp_channel: aiormq.Channel):
await amqp_channel.basic_qos(prefetch_count=1)
assert amqp_channel.number
queue = asyncio.Queue()
deaclare_ok = await amqp_channel.queue_declare(auto_delete=True)
consume_ok = await amqp_channel.basic_consume(deaclare_ok.queue, queue.put)
await amqp_channel.basic_publish(
b"foo",
routing_key=deaclare_ok.queue,
properties=aiormq.spec.Basic.Properties(message_id="123"),
)
message: DeliveredMessage = await queue.get()
assert message.body == b"foo"
cancel_ok = await amqp_channel.basic_cancel(consume_ok.consumer_tag)
assert cancel_ok.consumer_tag == consume_ok.consumer_tag
assert cancel_ok.consumer_tag not in amqp_channel.consumers
await amqp_channel.queue_delete(deaclare_ok.queue)
deaclare_ok = await amqp_channel.queue_declare(auto_delete=True)
await amqp_channel.basic_publish(b"foo bar", routing_key=deaclare_ok.queue)
message = await amqp_channel.basic_get(deaclare_ok.queue, no_ack=True)
assert message.body == b"foo bar"
async def test_blank_body(amqp_channel: aiormq.Channel):
await amqp_channel.basic_qos(prefetch_count=1)
assert amqp_channel.number
queue = asyncio.Queue()
deaclare_ok = await amqp_channel.queue_declare(auto_delete=True)
consume_ok = await amqp_channel.basic_consume(deaclare_ok.queue, queue.put)
await amqp_channel.basic_publish(
b"",
routing_key=deaclare_ok.queue,
properties=aiormq.spec.Basic.Properties(message_id="123"),
)
message: DeliveredMessage = await queue.get()
assert message.body == b""
cancel_ok = await amqp_channel.basic_cancel(consume_ok.consumer_tag)
assert cancel_ok.consumer_tag == consume_ok.consumer_tag
assert cancel_ok.consumer_tag not in amqp_channel.consumers
await amqp_channel.queue_delete(deaclare_ok.queue)
deaclare_ok = await amqp_channel.queue_declare(auto_delete=True)
await amqp_channel.basic_publish(b"foo bar", routing_key=deaclare_ok.queue)
message = await amqp_channel.basic_get(deaclare_ok.queue, no_ack=True)
assert message.body == b"foo bar"
async def test_bad_consumer(amqp_channel: aiormq.Channel, loop):
channel: aiormq.Channel = amqp_channel
await channel.basic_qos(prefetch_count=1)
declare_ok = await channel.queue_declare()
future = loop.create_future()
await channel.basic_publish(b"urgent", routing_key=declare_ok.queue)
consumer_tag = loop.create_future()
async def bad_consumer(message):
await channel.basic_cancel(await consumer_tag)
future.set_result(message)
raise Exception
consume_ok = await channel.basic_consume(
declare_ok.queue, bad_consumer, no_ack=False,
)
consumer_tag.set_result(consume_ok.consumer_tag)
message = await future
await channel.basic_reject(message.delivery.delivery_tag, requeue=True)
assert message.body == b"urgent"
future = loop.create_future()
await channel.basic_consume(
declare_ok.queue, future.set_result, no_ack=True,
)
message = await future
assert message.body == b"urgent"
assert message.delivery_tag is not None
assert message.exchange is not None
assert message.redelivered
async def test_ack_nack_reject(amqp_channel: aiormq.Channel):
channel: aiormq.Channel = amqp_channel
await channel.basic_qos(prefetch_count=1)
declare_ok = await channel.queue_declare(auto_delete=True)
queue = asyncio.Queue()
await channel.basic_consume(declare_ok.queue, queue.put, no_ack=False)
await channel.basic_publish(b"rejected", routing_key=declare_ok.queue)
message: DeliveredMessage = await queue.get()
assert message.body == b"rejected"
assert message.delivery_tag is not None
assert message.exchange is not None
assert not message.redelivered
await channel.basic_reject(message.delivery.delivery_tag, requeue=False)
await channel.basic_publish(b"nacked", routing_key=declare_ok.queue)
message = await queue.get()
assert message.body == b"nacked"
await channel.basic_nack(message.delivery.delivery_tag, requeue=False)
await channel.basic_publish(b"acked", routing_key=declare_ok.queue)
message = await queue.get()
assert message.body == b"acked"
await channel.basic_ack(message.delivery.delivery_tag)
async def test_confirm_multiple(amqp_channel: aiormq.Channel):
"""
RabbitMQ has been observed to send confirmations in a strange pattern
when publishing simultaneously where only some messages are delivered
to a queue. It sends acks like this 1 2 4 5(multiple, confirming also 3).
This test is probably inconsequential without publisher_confirms
This is a regression for https://github.com/mosquito/aiormq/issues/10
"""
channel: aiormq.Channel = amqp_channel
exchange = uuid.uuid4().hex
await channel.exchange_declare(exchange, exchange_type="topic")
try:
declare_ok = await channel.queue_declare(exclusive=True)
await channel.queue_bind(
declare_ok.queue, exchange, routing_key="test.5",
)
for i in range(10):
messages = [
asyncio.ensure_future(
channel.basic_publish(
b"test", exchange=exchange,
routing_key="test.{}".format(i),
),
)
for i in range(10)
]
_, pending = await asyncio.wait(messages, timeout=0.2)
assert not pending, "not all publishes were completed (confirmed)"
await asyncio.sleep(0.05)
finally:
await channel.exchange_delete(exchange)
async def test_exclusive_queue_locked(amqp_connection):
channel0 = await amqp_connection.channel()
channel1 = await amqp_connection.channel()
qname = str(uuid.uuid4())
await channel0.queue_declare(qname, exclusive=True)
try:
await channel0.basic_consume(qname, print, exclusive=True)
with pytest.raises(aiormq.exceptions.ChannelLockedResource):
await channel1.queue_declare(qname)
await channel1.basic_consume(qname, print, exclusive=True)
finally:
await channel0.queue_delete(qname)
async def test_remove_writer_when_closed(amqp_channel: aiormq.Channel):
with pytest.raises(aiormq.exceptions.ChannelClosed):
await amqp_channel.queue_declare(
"amq.forbidden_queue_name", auto_delete=True,
)
with pytest.raises(aiormq.exceptions.ChannelInvalidStateError):
await amqp_channel.queue_delete("amq.forbidden_queue_name")
async def test_proxy_connection(proxy_connection, proxy: TCPProxy):
channel: aiormq.Channel = await proxy_connection.channel()
await channel.queue_declare(auto_delete=True)
async def test_declare_queue_timeout(proxy_connection, proxy: TCPProxy):
for _ in range(3):
channel: aiormq.Channel = await proxy_connection.channel()
qname = str(uuid.uuid4())
with proxy.slowdown(read_delay=5, write_delay=0):
with pytest.raises(asyncio.TimeoutError):
await channel.queue_declare(
qname, auto_delete=True, timeout=0.5,
)
async def test_big_message(amqp_channel: aiormq.Channel):
size = 20 * 1024 * 1024
message = urandom(size)
await amqp_channel.basic_publish(message)
async def test_routing_key_too_large(amqp_channel: aiormq.Channel):
routing_key = "x" * 256
with pytest.raises(ValueError):
await amqp_channel.basic_publish(b"foo bar", routing_key=routing_key)
exchange = uuid.uuid4().hex
await amqp_channel.exchange_declare(exchange, exchange_type="topic")
with pytest.raises(ValueError):
await amqp_channel.exchange_bind(exchange, exchange, routing_key)
with pytest.raises(ValueError):
await amqp_channel.exchange_unbind(exchange, exchange, routing_key)
queue = await amqp_channel.queue_declare(exclusive=True)
with pytest.raises(ValueError):
await amqp_channel.queue_bind(queue.queue, exchange, routing_key)
with pytest.raises(ValueError):
await amqp_channel.queue_unbind(queue.queue, exchange, routing_key)
await amqp_channel.exchange_delete(exchange)
python-aiormq-6.8.1/tests/test_connection.py 0000664 0000000 0000000 00000037001 14737314232 0021256 0 ustar 00root root 0000000 0000000 import asyncio
import itertools
import os
import ssl
import uuid
from binascii import hexlify
from typing import Optional
import aiomisc
import pytest
from pamqp.commands import Basic
from yarl import URL
import aiormq
from aiormq.abc import DeliveredMessage
from aiormq.auth import AuthBase, ExternalAuth, PlainAuth
from aiormq.connection import parse_int, parse_timeout, parse_bool
from .conftest import AMQP_URL, cert_path, skip_when_quick_test
CERT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "certs"))
async def test_simple(amqp_connection: aiormq.Connection):
channel1 = await amqp_connection.channel()
await channel1.basic_qos(prefetch_count=1)
assert channel1.number
channel2 = await amqp_connection.channel(11)
assert channel2.number
await channel1.close()
await channel2.close()
channel = await amqp_connection.channel()
queue = asyncio.Queue()
deaclare_ok = await channel.queue_declare(auto_delete=True)
consume_ok = await channel.basic_consume(deaclare_ok.queue, queue.put)
await channel.basic_publish(b"foo", routing_key=deaclare_ok.queue)
message: DeliveredMessage = await queue.get()
assert message.body == b"foo"
with pytest.raises(aiormq.exceptions.PublishError) as e:
await channel.basic_publish(
b"bar", routing_key=deaclare_ok.queue + "foo", mandatory=True,
)
message = e.value.message
assert message.delivery.routing_key == deaclare_ok.queue + "foo"
assert message.body == b"bar"
assert "'NO_ROUTE' for routing key" in repr(e.value)
cancel_ok = await channel.basic_cancel(consume_ok.consumer_tag)
assert cancel_ok.consumer_tag == consume_ok.consumer_tag
await channel.queue_delete(deaclare_ok.queue)
deaclare_ok = await channel.queue_declare(auto_delete=True)
await channel.basic_publish(b"foo bar", routing_key=deaclare_ok.queue)
message = await channel.basic_get(deaclare_ok.queue, no_ack=True)
assert message.body == b"foo bar"
await amqp_connection.close()
with pytest.raises(RuntimeError):
await channel.basic_get(deaclare_ok.queue)
with pytest.raises(RuntimeError):
await amqp_connection.channel()
async def test_channel_reuse(amqp_connection: aiormq.Connection):
for _ in range(10):
channel = await amqp_connection.channel(channel_number=1)
await channel.basic_qos(prefetch_count=1)
await channel.close()
del channel
async def test_channel_closed_reuse(amqp_connection: aiormq.Connection):
for _ in range(10):
channel = await amqp_connection.channel(channel_number=1)
with pytest.raises(aiormq.ChannelAccessRefused):
await channel.exchange_declare("", passive=True, auto_delete=True)
async def test_bad_channel(amqp_connection: aiormq.Connection):
with pytest.raises(ValueError):
await amqp_connection.channel(65536)
with pytest.raises(ValueError):
await amqp_connection.channel(-1)
channel = await amqp_connection.channel(65535)
with pytest.raises(aiormq.exceptions.ChannelNotFoundEntity):
await channel.queue_bind(uuid.uuid4().hex, uuid.uuid4().hex)
async def test_properties(amqp_connection):
assert amqp_connection.connection_tune.channel_max > 0
assert amqp_connection.connection_tune.heartbeat > 1
assert amqp_connection.connection_tune.frame_max > 1024
await amqp_connection.close()
async def test_open(amqp_connection):
with pytest.raises(RuntimeError):
await amqp_connection.connect()
channel = await amqp_connection.channel()
await channel.close()
await amqp_connection.close()
async def test_channel_close(amqp_connection):
channel = await amqp_connection.channel()
assert channel.number in amqp_connection.channels
await channel.close()
assert channel.number not in amqp_connection.channels
async def test_conncetion_reject(loop):
with pytest.raises(ConnectionError):
await aiormq.connect(
"amqp://guest:guest@127.0.0.1:59999/", loop=loop,
)
connection = aiormq.Connection(
"amqp://guest:guest@127.0.0.1:59999/", loop=loop,
)
with pytest.raises(ConnectionError):
await loop.create_task(connection.connect())
async def test_auth_base(amqp_connection):
with pytest.raises(NotImplementedError):
AuthBase(amqp_connection).marshal()
async def test_auth_plain(amqp_connection, loop):
auth = PlainAuth(amqp_connection).marshal()
assert auth == PlainAuth(amqp_connection).marshal()
auth_parts = auth.split("\x00")
assert auth_parts == ["", "guest", "guest"]
connection = aiormq.Connection(
amqp_connection.url.with_user("foo").with_password("bar"),
loop=loop,
)
auth = PlainAuth(connection).marshal()
auth_parts = auth.split("\x00")
assert auth_parts == ["", "foo", "bar"]
auth = PlainAuth(connection)
auth.value = "boo"
assert auth.marshal() == "boo"
async def test_auth_external(loop):
url = AMQP_URL.with_scheme("amqps")
url.update_query(auth="external")
connection = aiormq.Connection
auth = ExternalAuth(connection).marshal()
auth = ExternalAuth(connection)
auth.value = ""
assert auth.marshal() == ""
async def test_channel_closed(amqp_connection):
for i in range(10):
channel = await amqp_connection.channel()
with pytest.raises(aiormq.exceptions.ChannelNotFoundEntity):
await channel.basic_consume("foo", lambda x: None)
channel = await amqp_connection.channel()
with pytest.raises(aiormq.exceptions.ChannelNotFoundEntity):
await channel.queue_declare(
"foo_%s" % i, auto_delete=True, passive=True,
)
await amqp_connection.close()
async def test_timeout_default(loop):
connection = aiormq.Connection(AMQP_URL, loop=loop)
await connection.connect()
assert connection.timeout == 60
await connection.close()
async def test_timeout_1000(loop):
url = AMQP_URL.update_query(timeout=1000)
connection = aiormq.Connection(url, loop=loop)
await connection.connect()
assert connection.timeout
await connection.close()
async def test_heartbeat_0(loop):
url = AMQP_URL.update_query(heartbeat=0)
connection = aiormq.Connection(url, loop=loop)
await connection.connect()
assert connection.connection_tune.heartbeat == 0
await connection.close()
async def test_heartbeat_default(loop):
connection = aiormq.Connection(AMQP_URL, loop=loop)
await connection.connect()
assert connection.connection_tune.heartbeat == 60
await connection.close()
async def test_heartbeat_above_range(loop):
url = AMQP_URL.update_query(heartbeat=70000)
connection = aiormq.Connection(url, loop=loop)
await connection.connect()
assert connection.connection_tune.heartbeat == 0
await connection.close()
async def test_heartbeat_under_range(loop):
url = AMQP_URL.update_query(heartbeat=-1)
connection = aiormq.Connection(url, loop=loop)
await connection.connect()
assert connection.connection_tune.heartbeat == 0
await connection.close()
async def test_heartbeat_not_int(loop):
url = AMQP_URL.update_query(heartbeat="None")
connection = aiormq.Connection(url, loop=loop)
await connection.connect()
assert connection.connection_tune.heartbeat == 0
await connection.close()
async def test_bad_credentials(amqp_url: URL):
with pytest.raises(aiormq.exceptions.ProbableAuthenticationError):
await aiormq.connect(amqp_url.with_password(uuid.uuid4().hex))
async def test_non_publisher_confirms(amqp_connection):
amqp_connection.server_capabilities["publisher_confirms"] = False
with pytest.raises(ValueError):
await amqp_connection.channel(publisher_confirms=True)
await amqp_connection.channel(publisher_confirms=False)
@skip_when_quick_test
async def test_no_free_channels(amqp_connection: aiormq.Connection):
await asyncio.wait_for(
asyncio.gather(
*[
amqp_connection.channel(n + 1)
for n in range(amqp_connection.connection_tune.channel_max)
],
),
timeout=120,
)
with pytest.raises(aiormq.exceptions.ConnectionNotAllowed):
await asyncio.wait_for(amqp_connection.channel(), timeout=5)
async def test_huge_message(amqp_connection: aiormq.Connection):
conn: aiormq.Connection = amqp_connection
body = os.urandom(int(conn.connection_tune.frame_max * 2.5))
channel: aiormq.Channel = await conn.channel()
queue = asyncio.Queue()
deaclare_ok = await channel.queue_declare(auto_delete=True)
await channel.basic_consume(deaclare_ok.queue, queue.put)
await channel.basic_publish(body, routing_key=deaclare_ok.queue)
message: DeliveredMessage = await queue.get()
assert message.body == body
async def test_return_message(amqp_connection: aiormq.Connection):
conn: aiormq.Connection = amqp_connection
body = os.urandom(512)
routing_key = hexlify(os.urandom(16)).decode()
channel: aiormq.Channel = await conn.channel(
on_return_raises=False,
)
result: Optional[Basic.Return] = await channel.basic_publish(
body, routing_key=routing_key, mandatory=True,
)
assert result is not None
assert result.delivery.name == "Basic.Return"
assert result.delivery.routing_key == routing_key
async def test_cancel_on_queue_deleted(amqp_connection, loop):
conn: aiormq.Connection = amqp_connection
channel: aiormq.Channel = await conn.channel()
deaclare_ok = await channel.queue_declare(auto_delete=True)
consume_ok = await channel.basic_consume(deaclare_ok.queue, print)
assert consume_ok.consumer_tag in channel.consumers
with pytest.raises(aiormq.DuplicateConsumerTag):
await channel.basic_consume(
deaclare_ok.queue, print, consumer_tag=consume_ok.consumer_tag,
)
await channel.queue_delete(deaclare_ok.queue)
await asyncio.sleep(0.1)
assert consume_ok.consumer_tag not in channel.consumers
URL_VHOSTS = [
("amqp:///", "/"),
("amqp:////", "/"),
("amqp:///test", "test"),
("amqp:////test", "/test"),
("amqp://localhost/test", "test"),
("amqp://localhost//test", "/test"),
("amqps://localhost:5678//test", "/test"),
("amqps://localhost:5678/-test", "-test"),
("amqp://guest:guest@localhost/@test", "@test"),
("amqp://guest:guest@localhost//@test", "/@test"),
]
async def test_ssl_verification_fails_without_trusted_ca():
url = AMQP_URL.with_scheme("amqps")
with pytest.raises(ConnectionError, match=".*CERTIFICATE_VERIFY_FAILED.*"):
connection = aiormq.Connection(url)
await connection.connect()
async def test_ssl_context():
url = AMQP_URL.with_scheme("amqps")
context = ssl.create_default_context(
purpose=ssl.Purpose.SERVER_AUTH,
cafile=cert_path("ca.pem"),
)
context.load_cert_chain(cert_path("client.pem"), cert_path("client.key"))
context.check_hostname = False
connection = aiormq.Connection(url, context=context)
await connection.connect()
await connection.close()
@pytest.mark.parametrize("url,vhost", URL_VHOSTS)
async def test_connection_urls_vhosts(url, vhost, loop):
assert aiormq.Connection(url, loop=loop).vhost == vhost
async def test_update_secret(amqp_connection, amqp_url: URL):
respone = await amqp_connection.update_secret(
amqp_url.password, timeout=1,
)
assert isinstance(respone, aiormq.spec.Connection.UpdateSecretOk)
@aiomisc.timeout(20)
async def test_connection_stuck(proxy, amqp_url: URL):
url = amqp_url.with_host(
proxy.proxy_host,
).with_port(
proxy.proxy_port,
).update_query(heartbeat="1")
connection = await aiormq.connect(url)
async with connection:
# delay the delivery of each packet by 5 seconds, which
# is more than the heartbeat
with proxy.slowdown(50, 50):
while connection.is_opened:
await asyncio.sleep(1)
writer_task: asyncio.Task = connection._writer_task # type: ignore
assert writer_task.done()
with pytest.raises(asyncio.CancelledError):
assert writer_task.result()
reader_task: asyncio.Task = connection._reader_task # type: ignore
assert reader_task.done()
with pytest.raises(asyncio.CancelledError):
assert reader_task.result()
class BadNetwork:
def __init__(self, proxy, stair: int, disconnect_time: float):
self.proxy = proxy
self.stair = stair
self.disconnect_time = disconnect_time
self.num_bytes = 0
self.loop = asyncio.get_event_loop()
self.lock = asyncio.Lock()
proxy.set_content_processors(
self.client_to_server,
self.server_to_client,
)
async def disconnect(self):
async with self.lock:
await asyncio.sleep(self.disconnect_time)
await self.proxy.disconnect_all()
self.stair *= 2
self.num_bytes = 0
async def server_to_client(self, chunk: bytes) -> bytes:
async with self.lock:
self.num_bytes += len(chunk)
if self.num_bytes < self.stair:
return chunk
self.loop.create_task(self.disconnect())
return chunk
@staticmethod
def client_to_server(chunk: bytes) -> bytes:
return chunk
DISCONNECT_OFFSETS = [2 << i for i in range(1, 10)]
STAIR_STEPS = list(
itertools.product([0.0, 0.005, 0.05, 0.1], DISCONNECT_OFFSETS),
)
STAIR_STEPS_IDS = [
f"[{i // len(DISCONNECT_OFFSETS)}] {t}-{s}"
for i, (t, s) in enumerate(STAIR_STEPS)
]
@aiomisc.timeout(30)
@pytest.mark.parametrize(
"disconnect_time,stair", STAIR_STEPS,
ids=STAIR_STEPS_IDS,
)
async def test_connection_close_stairway(
disconnect_time: float, stair: int, proxy, amqp_url: URL,
):
url = amqp_url.with_host(
proxy.proxy_host,
).with_port(
proxy.proxy_port,
).update_query(heartbeat="1")
BadNetwork(proxy, stair, disconnect_time)
async def run():
connection = await aiormq.connect(url)
queue = asyncio.Queue()
channel = await connection.channel()
declare_ok = await channel.queue_declare(auto_delete=True)
await channel.basic_consume(
declare_ok.queue, queue.put, no_ack=True,
)
while True:
await channel.basic_publish(
b"test", routing_key=declare_ok.queue,
)
message: DeliveredMessage = await queue.get()
assert message.body == b"test"
for _ in range(5):
with pytest.raises(aiormq.AMQPError):
await run()
PARSE_INT_PARAMS = (
(1, 1),
("1", 1),
("0.1", 0),
("-1", -1),
)
@pytest.mark.parametrize("value,expected", PARSE_INT_PARAMS)
def test_parse_int(value, expected):
assert parse_int(value) == expected
PARSE_TIMEOUT_PARAMS = (
(1, 1),
(1.0, 1),
("0", 0),
("0.0", 0),
("0.111", 0.111),
)
@pytest.mark.parametrize("value,expected", PARSE_TIMEOUT_PARAMS)
def test_parse_timeout(value, expected):
assert parse_timeout(value) == expected
PARSE_BOOL_PARAMS = (
("no", False),
("nope", False),
("do not do it bro", False),
("YES", True),
("yes", True),
("yeS", True),
("yEs", True),
("True", True),
("true", True),
("TRUE", True),
("1", True),
("ENABLE", True),
("ENAbled", True),
("y", True),
("Y", True),
)
@pytest.mark.parametrize("value,expected", PARSE_BOOL_PARAMS)
def test_parse_bool(value, expected):
assert parse_bool(value) == expected
python-aiormq-6.8.1/tests/test_future_store.py 0000664 0000000 0000000 00000004536 14737314232 0021654 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
from aiormq.abc import TaskWrapper
from aiormq.base import FutureStore
@pytest.fixture
def root_store(loop):
store = FutureStore(loop=loop)
try:
yield store
finally:
loop.run_until_complete(
store.reject_all(Exception("Cancelling")),
)
@pytest.fixture
def child_store(loop, root_store):
store = root_store.get_child()
try:
yield store
finally:
loop.run_until_complete(
store.reject_all(Exception("Cancelling")),
)
async def test_reject_all(
loop, root_store: FutureStore, child_store: FutureStore,
):
future1 = root_store.create_future()
future2 = child_store.create_future()
assert root_store.futures
assert child_store.futures
await root_store.reject_all(RuntimeError)
await asyncio.sleep(0.1)
assert isinstance(future1.exception(), RuntimeError)
assert isinstance(future2.exception(), RuntimeError)
assert not root_store.futures
assert not child_store.futures
async def test_result(
loop, root_store: FutureStore, child_store: FutureStore,
):
async def result():
await asyncio.sleep(0.1)
return "result"
assert await child_store.create_task(result()) == "result"
async def test_siblings(
loop, root_store: FutureStore, child_store: FutureStore,
):
async def coro(store):
await asyncio.sleep(0.1)
await store.reject_all(RuntimeError)
task1 = child_store.create_task(coro(child_store))
assert root_store.futures
assert child_store.futures
with pytest.raises(RuntimeError):
await task1
await asyncio.sleep(0.1)
assert not root_store.futures
assert not child_store.futures
child = child_store.get_child().get_child().get_child()
task = child.create_task(coro(child))
assert root_store.futures
assert child_store.futures
assert child.futures
with pytest.raises(RuntimeError):
await task
await asyncio.sleep(0.1)
assert not root_store.futures
assert not child_store.futures
assert not child.futures
async def test_task_wrapper(loop):
future = loop.create_future()
wrapped = TaskWrapper(future)
wrapped.throw(RuntimeError())
with pytest.raises(asyncio.CancelledError):
await future
with pytest.raises(RuntimeError):
await wrapped
python-aiormq-6.8.1/tests/test_tools.py 0000664 0000000 0000000 00000002174 14737314232 0020262 0 ustar 00root root 0000000 0000000 import asyncio
import pytest
from aiormq.tools import Countdown, awaitable
def simple_func():
return 1
def await_result_func():
return asyncio.sleep(0)
async def await_func():
await asyncio.sleep(0)
return 2
def return_future():
loop = asyncio.get_event_loop()
f = loop.create_future()
loop.call_soon(f.set_result, 5)
return f
async def await_future():
return (await return_future()) + 1
def return_coroutine():
return await_future()
AWAITABLE_FUNCS = [
(simple_func, 1),
(await_result_func, None),
(await_func, 2),
(return_future, 5),
(await_future, 6),
(return_coroutine, 6),
]
@pytest.mark.parametrize("func,result", AWAITABLE_FUNCS)
async def test_awaitable(func, result, loop):
assert await awaitable(func)() == result
async def test_countdown(loop):
countdown = Countdown(timeout=0.1)
await countdown(asyncio.sleep(0))
# waiting for the countdown exceeded
await asyncio.sleep(0.2)
task = asyncio.create_task(asyncio.sleep(0))
with pytest.raises(asyncio.TimeoutError):
await countdown(task)
assert task.cancelled()