pax_global_header00006660000000000000000000000064143100607400014505gustar00rootroot0000000000000052 comment=9696466a5cdaa73ace6d41153bc2a6c79cb3ceae aiormq-6.4.2/000077500000000000000000000000001431006074000130065ustar00rootroot00000000000000aiormq-6.4.2/.coveragerc000066400000000000000000000001001431006074000151160ustar00rootroot00000000000000[run] branch = True omit = */env/* */tests/* */.*/* aiormq-6.4.2/.drone.yml000066400000000000000000000102171431006074000147170ustar00rootroot00000000000000--- kind: pipeline name: default steps: - name: linter image: snakepacker/python:pylava pull: always commands: - pylava -o pylava.ini . - name: prepare toxenv image: snakepacker/python:all group: tests pull: always commands: - tox --notest volumes: - name: cache path: /drone/src/.tox - name: mypy image: snakepacker/python:all group: tests pull: always commands: - tox environment: TOXENV: mypy volumes: - name: cache path: /drone/src/.tox - name: checkdoc image: snakepacker/python:all group: tests pull: always commands: - tox environment: TOXENV: checkdoc volumes: - name: cache path: /drone/src/.tox - name: python 3.8 image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py38 COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.7 image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py37 COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.6 image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py36 COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.5 image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py35 COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.8 uvloop image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py38-uvloop COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.7 uvloop image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py37-uvloop COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.6 uvloop image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py36-uvloop COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: python 3.5 uvloop image: snakepacker/python:all pull: always commands: - wait-for-port rabbitmq:5672 rabbitmq:5671 - tox environment: AMQP_URL: amqp://guest:guest@rabbitmq TOXENV: py35-uvloop COVERALLS_REPO_TOKEN: from_secret: COVERALLS_TOKEN volumes: - name: cache path: /drone/src/.tox - name: notify image: drillster/drone-email settings: host: from_secret: SMTP_HOST username: from_secret: SMTP_USERNAME password: from_secret: SMTP_PASSWORD from: from_secret: SMTP_USERNAME when: status: - changed - failure volumes: - name: cache temp: {} services: - name: rabbitmq image: mosquito/aiormq-rabbitmq:latest pull: always --- kind: signature hmac: db78b836fb377a913c4eed3e22177b502268738102c19982dd2c3302510cd7f1 ... aiormq-6.4.2/.editorconfig000066400000000000000000000004351431006074000154650ustar00rootroot00000000000000root = 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 aiormq-6.4.2/.github/000077500000000000000000000000001431006074000143465ustar00rootroot00000000000000aiormq-6.4.2/.github/workflows/000077500000000000000000000000001431006074000164035ustar00rootroot00000000000000aiormq-6.4.2/.github/workflows/tox.yml000066400000000000000000000026501431006074000177430ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: tox on: push: branches: [ master ] pull_request: branches: [ master ] jobs: lint: runs-on: ubuntu-latest strategy: matrix: linter: - checkdoc - pylava - mypy steps: - uses: actions/checkout@v2 - name: tox ${{ matrix.linter }} uses: docker://snakepacker/python:all env: TOXENV: ${{ matrix.linter }} with: args: tox build: runs-on: ubuntu-latest services: rabbitmq: image: docker://mosquito/aiormq-rabbitmq ports: - 5672:5672 strategy: matrix: toxenv: - py37 - py38 - py39 - py310 - py37-uvloop - py38-uvloop - py39-uvloop - py310-uvloop steps: - uses: actions/checkout@v2 - name: tox ${{ matrix.toxenv }} uses: docker://snakepacker/python:all env: TOXENV: ${{ matrix.toxenv }} FORCE_COLOR: yes AMQP_URL: amqp://guest:guest@rabbitmq COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: args: /bin/bash -c "wait-for-port rabbitmq:5672 && tox" aiormq-6.4.2/.gitignore000066400000000000000000000037371431006074000150100ustar00rootroot00000000000000# 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 aiormq-6.4.2/LICENSE000077700000000000000000000000001431006074000154122LICENSE.mdustar00rootroot00000000000000aiormq-6.4.2/LICENSE.md000066400000000000000000000243301431006074000144140ustar00rootroot00000000000000Apache 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 2021 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. aiormq-6.4.2/MANIFEST.in000066400000000000000000000001611431006074000145420ustar00rootroot00000000000000recursive-exclude tests * recursive-exclude __pycache__ * exclude .* include README.rst include aiormq/py.typed aiormq-6.4.2/Makefile000066400000000000000000000014411431006074000144460ustar00rootroot00000000000000all: clean sdist test upload NAME:=$(shell python3 setup.py --name) VERSION:=$(shell python3 setup.py --version | sed 's/+/-/g') 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) sdist: python3 setup.py sdist bdist_wheel upload: sdist twine upload dist/*$(VERSION)* quick-test: TEST_QUICK='1' env/bin/pytest -vvx --cov=aiormq \ --cov-report=term-missing tests test: quick-test env/bin/tox clean: rm -fr *.egg-info .tox develop: clean virtualenv -p python3.6 env env/bin/pip install -Ue '.' env/bin/pip install -Ue '.[develop]' aiormq-6.4.2/README.rst000066400000000000000000000410401431006074000144740ustar00rootroot00000000000000====== 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/tox/badge.svg :target: https://github.com/mosquito/aiormq/actions?query=workflow%3Atox :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) 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(" [x] Received message %r" % message) print("Message body is: %r" % message.body) 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 import asyncio import aiormq async def main(): # Perform connection connection = await aiormq.connect("amqp://guest:guest@localhost//") # Creating a channel channel = await connection.channel() # Sending the message await channel.basic_publish(b'Hello World!', routing_key='hello') print(" [x] Sent 'Hello World!'") loop = asyncio.get_event_loop() loop.run_until_complete(main()) 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(" [x] Sent %r" % body) await connection.close() loop = asyncio.get_event_loop() loop.run_until_complete(main()) Simple worker ************* .. code-block:: python import asyncio import aiormq import aiormq.types async def on_message(message: aiormq.types.DeliveredMessage): print(" [x] Received message %r" % (message,)) print(" Message body is: %r" % (message.body,)) 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(" [x] Sent %r" % (body,)) await connection.close() loop = asyncio.get_event_loop() loop.run_until_complete(main()) Subscriber ********** .. code-block:: python import asyncio import aiormq import aiormq.types async def on_message(message: aiormq.types.DeliveredMessage): print("[x] %r" % (message.body,)) 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.types async def on_message(message: aiormq.types.DeliveredMessage): print(" [x] %r:%r" % (message.delivery.routing_key, message.body)) 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( "Usage: %s [info] [warning] [error]\n" % sys.argv[0] ) 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(" [x] Sent %r" % body) 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(" [x] Sent %r" % (body,)) 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.types async def on_message(message: aiormq.types.DeliveredMessage): print(" [x] %r:%r" % (message.delivery.routing_key, message.body)) 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( "Usage: %s [binding_key]...\n" % sys.argv[0] ) 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.types 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.types.DeliveredMessage): n = int(message.body.decode()) print(" [.] fib(%d)" % 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.types 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.types.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(" [.] Got %r" % response) loop = asyncio.get_event_loop() loop.run_until_complete(main()) aiormq-6.4.2/aiormq/000077500000000000000000000000001431006074000142765ustar00rootroot00000000000000aiormq-6.4.2/aiormq/__init__.py000066400000000000000000000036731431006074000164200ustar00rootroot00000000000000from 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, ) from .version import ( __author__, __version__, author_info, package_info, package_license, team_email, version_info, ) __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", "__author__", "__version__", "abc", "author_info", "connect", "package_info", "package_license", "spec", "team_email", "version_info", ) aiormq-6.4.2/aiormq/abc.py000066400000000000000000000363721431006074000154100ustar00rootroot00000000000000import asyncio from abc import ABC, abstractmethod, abstractproperty from types import TracebackType from typing import ( Any, Awaitable, Callable, Coroutine, Dict, Iterable, NamedTuple, Optional, Set, Tuple, Type, Union, ) 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] class DeliveredMessage(NamedTuple): 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 = Dict[ str, Union[str, int, bool, Dict[str, Union[str, int, bool]]], ] ConfirmationFrameType = Union[ Basic.Ack, Basic.Nack, Basic.Reject, ] class SSLCerts(NamedTuple): cert: Optional[str] key: Optional[str] capath: Optional[str] cafile: Optional[str] cadata: Optional[bytes] verify: bool class FrameReceived(NamedTuple): 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, ] ] class ChannelFrame(NamedTuple): channel_number: int frames: Iterable[Union[FrameType, Heartbeat, ContentBody]] drain_future: Optional[asyncio.Future] = None 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: ArgumentsType = None, consumer_tag: 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: 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: int = None, prefetch_count: 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[Dict[str, Any]] = 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: dict = 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: dict = 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: dict = 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: dict = 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: dict = 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: Dict[str, Any] = 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: 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", ) aiormq-6.4.2/aiormq/auth.py000066400000000000000000000015361431006074000156160ustar00rootroot00000000000000import 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 aiormq-6.4.2/aiormq/base.py000066400000000000000000000113251431006074000155640ustar00rootroot00000000000000import 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 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: 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 await asyncio.wait_for( self.loop.create_task(self.__closer(exc)), timeout=timeout, ) 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 aiormq-6.4.2/aiormq/channel.py000066400000000000000000000710621431006074000162660ustar00rootroot00000000000000import 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, Dict, List, Mapping, Optional, Set, 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.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)) 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 does'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( 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() self.write_queue.put_nowait( ChannelFrame( 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 spec.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(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( 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(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(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 _reader(self) -> None: while True: try: frame = await self._get_frame() if isinstance(frame, spec.Basic.Deliver): with suppress(Exception): await self._on_deliver(frame) continue elif isinstance( frame, (spec.Basic.GetOk, spec.Basic.GetEmpty), ): with suppress(Exception): await self._on_get(frame) elif isinstance(frame, spec.Basic.Return): with suppress(Exception): await self._on_return(frame) continue elif isinstance(frame, spec.Basic.Cancel): if frame.consumer_tag is None: continue self.consumers.pop(frame.consumer_tag, None) continue elif isinstance(frame, spec.Basic.CancelOk): if frame.consumer_tag is not None: self.consumers.pop(frame.consumer_tag, None) elif isinstance(frame, (spec.Basic.Ack, spec.Basic.Nack)): with suppress(Exception): await self._on_confirm(frame) continue elif isinstance(frame, spec.Channel.Close): exc: BaseException = exception_by_code(frame) self.write_queue.put_nowait( ChannelFrame( channel_number=self.number, frames=[spec.Channel.CloseOk()], ), ) self.connection.channels.pop(self.number, None) await self._cancel_tasks(exc) return elif isinstance(frame, spec.Channel.CloseOk): if self.__close_event.is_set(): await self._cancel_tasks(asyncio.TimeoutError()) self.connection.channels.pop(self.number, None) return await self.rpc_frames.put(frame) except asyncio.CancelledError: return except Exception as e: # pragma: nocover log.debug("Channel reader exception %r", exc_info=e) await self._cancel_tasks(e) raise @task async def _on_close(self, exc: Optional[ExceptionType] = None) -> None: if self.connection.is_opened and not self.__close_event.is_set(): 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, ) self.connection.channels.pop(self.number, None) 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: ArgumentsType = None, consumer_tag: 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( 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( 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( 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: spec.Basic.Properties = None, mandatory: bool = False, immediate: bool = False, timeout: TimeoutType = None, wait: bool = True, ) -> Optional[ConfirmationFrameType]: _check_routing_key(routing_key) drain_future = self.create_future() if wait else None 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) await countdown( self.write_queue.put( ChannelFrame( 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: int = None, prefetch_count: 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: dict = 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: dict = 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: dict = 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: dict = 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: dict = 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, ) aiormq-6.4.2/aiormq/connection.py000066400000000000000000000650031431006074000170130ustar00rootroot00000000000000import 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, Dict, Optional, Tuple, Type, Union import pamqp.frame from pamqp import commands as spec from pamqp.base import Frame 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 ( AMQPError, AuthenticationError, ConnectionChannelError, ConnectionClosed, ConnectionCommandInvalid, ConnectionFrameError, ConnectionInternalError, ConnectionNotAllowed, ConnectionNotImplemented, ConnectionResourceError, ConnectionSyntaxError, ConnectionUnexpectedFrame, IncompatibleProtocolError, ProbableAuthenticationError, ) from .tools import Countdown, censor_url from .version import __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: str) -> bool: return v == "1" or v.lower() in ("true", "yes", "y", "enable", "on") def parse_int(v: str) -> int: try: return int(v) except ValueError: return 0 def parse_timeout(v: str) -> TimeoutType: try: if "." in v: return float(v) 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, receive_timeout: TimeType, ): self.reader: asyncio.StreamReader = reader self.timeout: TimeType = receive_timeout 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 countdown = Countdown(self.timeout) async with countdown.enter_context(self.lock): try: frame_header = await countdown(self.reader.readexactly(1)) if frame_header == b"\0x00": raise AMQPFrameError( await countdown(self.reader.read()), ) if self.reader is None: raise ConnectionError frame_header += await countdown(self.reader.readexactly(6)) if not self.started and frame_header.startswith(b"AMQP"): raise AMQPSyntaxError else: self.started = True frame_type, _, frame_length = pamqp.frame.frame_parts( frame_header, ) if frame_length is None: raise AMQPInternalError("No frame length", None) frame_payload = await countdown( self.reader.readexactly(frame_length + 1), ) except asyncio.IncompleteReadError as e: raise AMQPFrameError( "Server connection unexpectedly closed", ) from e except asyncio.TimeoutError as e: raise asyncio.TimeoutError( "Server connection was stucked. " "No frames were received in {} seconds.".format( self.timeout, ), ) from e return pamqp.frame.unmarshal(frame_header + frame_payload) 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 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: asyncio.AbstractEventLoop = None, context: ssl.SSLContext = None ): 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: self.vhost = self.url.path[1:] 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() 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)) 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: dict = 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, ) frame_receiver = FrameReceiver( reader, (self.timeout + 1) * self.HEARTBEAT_GRACE_MULTIPLIER, ) except OSError as e: raise ConnectionError(*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 Exception 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)) # Not very optimal, but avoid creating a task for each frame sending # noinspection PyAsyncCall self.create_task(self.__heartbeat()) self.connection_tune = connection_tune self.server_properties = server_properties self.__connection_unblocked.set() 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) await self.close(task.exception()) self.loop.create_task(close_writer_task()) async def __reader(self, frame_receiver: FrameReceiver) -> None: self.connected.set() async for weight, channel, frame in frame_receiver: log.debug( "Received frame %r in channel #%d weight=%s on %r", frame, channel, weight, self, ) if channel == 0: if isinstance(frame, spec.Connection.CloseOk): return if isinstance(frame, spec.Connection.Close): log.exception( "Unexpected connection close from remote \"%s\", " "Connection.Close(reply_code=%r, reply_text=%r)", self, frame.reply_code, frame.reply_text, ) self.write_queue.put_nowait( ChannelFrame( 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 elif isinstance(frame, Heartbeat): continue elif isinstance(frame, spec.Channel.CloseOk): self.channels.pop(channel, None) elif isinstance(frame, spec.Connection.UpdateSecretOk): if ( self.__update_secret_future is not None and not self.__update_secret_future.done() ): self.__update_secret_future.set_result(frame) else: log.warning("Got unexpected UpdateSecretOk frame") continue elif isinstance(frame, spec.Connection.Blocked): log.warning( "Connection %r was blocked by: %r", self, frame.reason, ) self.__connection_unblocked.clear() continue elif isinstance(frame, spec.Connection.Unblocked): log.warning("Connection %r was unblocked", self) self.__connection_unblocked.set() continue log.error("Unexpected frame %r", 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)) async def __heartbeat(self) -> None: heartbeat_timeout = max(1, self.heartbeat_timeout // 2) heartbeat = ChannelFrame(frames=[Heartbeat()], channel_number=0) while not self.closing.done(): await asyncio.sleep(heartbeat_timeout) await self.write_queue.put(heartbeat) 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) frame: FrameTypes with BytesIO() as fp: for frame in channel_frame.frames: fp.write( pamqp.frame.marshal( frame, channel_frame.channel_number, ), ) if isinstance(frame, spec.Connection.CloseOk): writer.write(fp.getvalue()) return writer.write(fp.getvalue()) if ( channel_frame.drain_future is not None and not channel_frame.drain_future.done() ): await writer.drain() channel_frame.drain_future.set_result(None) except asyncio.CancelledError: if not self.__check_writer(writer): 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(pamqp.frame.marshal(frame, 0)) log.debug("Sending %r to %r", frame, self) await asyncio.gather( writer.drain(), self.__close_writer(writer), return_exceptions=True, ) 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) 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: 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( channel_number=0, frames=[ spec.Connection.UpdateSecret( new_secret=new_secret, reason=reason, ), ], ) async with 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 asyncio.wait_for( self.__update_secret_future, timeout=timeout, ) ) 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: Dict[str, Any] = None, **kwargs: Any ) -> AbstractConnection: connection = Connection(url, *args, **kwargs) await connection.connect(client_properties or {}) return connection aiormq-6.4.2/aiormq/exceptions.py000066400000000000000000000125271431006074000170400ustar00rootroot00000000000000from 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: return "<%s: %s>" % (self.__class__.__name__, self.reason % self.args) # Backward compatibility AMQPException = AMQPError class AMQPConnectionError(AMQPError): reason = "Connection can not be opened" 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 aiormq-6.4.2/aiormq/py.typed000066400000000000000000000000001431006074000157630ustar00rootroot00000000000000aiormq-6.4.2/aiormq/tools.py000066400000000000000000000057771431006074000160300ustar00rootroot00000000000000import 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 def __call__(self, coro: Awaitable[T]) -> Awaitable[T]: timeout = self.get_timeout() if self.deadline is None and not timeout: return coro return 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), ) aiormq-6.4.2/aiormq/types.py000066400000000000000000000003471431006074000160200ustar00rootroot00000000000000import 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, ) aiormq-6.4.2/aiormq/version.py000066400000000000000000000005201431006074000163320ustar00rootroot00000000000000author_info = (("Dmitry Orlov", "me@mosquito.su"),) package_info = "Pure python AMQP asynchronous client library" package_license = "Apache Software License" team_email = "me@mosquito.su" version_info = (6, 4, 2) __author__ = ", ".join("{} <{}>".format(*info) for info in author_info) __version__ = ".".join(map(str, version_info)) aiormq-6.4.2/pylava.ini000066400000000000000000000001371431006074000150040ustar00rootroot00000000000000[pylava] ignore=C901,E252 skip = env*,.tox*,*build* [pylava:pycodestyle] max_line_length = 80 aiormq-6.4.2/pytest.ini000066400000000000000000000002551431006074000150410ustar00rootroot00000000000000[pytest] log_cli=true markers = no_catch_loop_exceptions: no catch unhandled exceptions from event loop allow_get_event_loop: allow to call asyncio.get_event_loop() aiormq-6.4.2/setup.cfg000066400000000000000000000006371431006074000146350ustar00rootroot00000000000000[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 = True [mypy-tests.*] ignore_errors = True aiormq-6.4.2/setup.py000066400000000000000000000037021431006074000145220ustar00rootroot00000000000000import os from importlib.machinery import SourceFileLoader from setuptools import find_packages, setup module = SourceFileLoader( "version", os.path.join("aiormq", "version.py"), ).load_module() setup( name="aiormq", version=module.__version__, packages=find_packages(exclude=["tests"]), license=module.package_license, description=module.package_info, long_description=open("README.rst").read(), url="https://github.com/mosquito/aiormq", author=module.__author__, author_email=module.team_email, install_requires=[ "pamqp==3.2.1", "yarl", ], keywords=["rabbitmq", "asyncio", "amqp", "amqp 0.9.1", "driver", "pamqp"], 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.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: CPython", ], python_requires=">=3.7", package_data={"aiormq": ["py.typed"]}, extras_require={ "develop": [ "aiomisc~=16.0", "coverage!=4.3", "coveralls", "pylava", "pytest", "pytest-cov", "tox>=2.4", ], }, ) aiormq-6.4.2/tests/000077500000000000000000000000001431006074000141505ustar00rootroot00000000000000aiormq-6.4.2/tests/__init__.py000066400000000000000000000000001431006074000162470ustar00rootroot00000000000000aiormq-6.4.2/tests/certs/000077500000000000000000000000001431006074000152705ustar00rootroot00000000000000aiormq-6.4.2/tests/certs/Dockerfile000066400000000000000000000006221431006074000172620ustar00rootroot00000000000000FROM 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 aiormq-6.4.2/tests/certs/ca.pem000066400000000000000000000041221431006074000163550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- aiormq-6.4.2/tests/certs/client.key000066400000000000000000000062471431006074000172710ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- aiormq-6.4.2/tests/certs/client.pem000066400000000000000000000102241431006074000172500ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- aiormq-6.4.2/tests/certs/server.key000066400000000000000000000062531431006074000173160ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- aiormq-6.4.2/tests/certs/server.pem000066400000000000000000000103011431006074000172740ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- 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 MvekAEBEOasl6ZnGM/QQBN1o -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- aiormq-6.4.2/tests/conftest.py000066400000000000000000000071311431006074000163510ustar00rootroot00000000000000import asyncio import gc import logging import os import tracemalloc import pamqp import pytest from aiomisc_pytest.pytest_plugin 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() aiormq-6.4.2/tests/test_channel.py000066400000000000000000000205451431006074000171770ustar00rootroot00000000000000import asyncio import uuid from os import urandom import pytest from aiomisc_pytest.pytest_plugin 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" @pytest.mark.no_catch_loop_exceptions 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) aiormq-6.4.2/tests/test_connection.py000066400000000000000000000265041431006074000177270ustar00rootroot00000000000000import asyncio import os import ssl import uuid from binascii import hexlify from typing import Optional 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 .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) aiormq-6.4.2/tests/test_future_store.py000066400000000000000000000040541431006074000203120ustar00rootroot00000000000000import asyncio import pytest 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 aiormq-6.4.2/tests/test_tools.py000066400000000000000000000014371431006074000167260ustar00rootroot00000000000000import asyncio import pytest from aiormq.tools import 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 aiormq-6.4.2/tox.ini000066400000000000000000000012411431006074000143170ustar00rootroot00000000000000[tox] envlist = checkdoc.pylava,mypy,py3{7-10},py3{7-10}-uvloop [testenv] passenv = COVERALLS_* AMQP_* TEST_* FORCE_COLOR deps = py37-uvloop: uvloop~=0.15.0 py38-uvloop: uvloop~=0.15.0 py39-uvloop: uvloop~=0.15.0 py310-uvloop: uvloop~=0.16.0 extras = develop commands= py.test -v --cov --cov-report=term-missing --doctest-modules --aiomisc-test-timeout=30 tests - coveralls [testenv:checkdoc] deps = collective.checkdocs pygments commands = python setup.py checkdocs [testenv:mypy] usedevelop = true deps = mypy commands = mypy aiormq [testenv:pylava] usedevelop = true deps = pyflakes~=2.4.0 pylava commands = pylava aiormq tests