pax_global_header00006660000000000000000000000064143354406160014520gustar00rootroot0000000000000052 comment=78fef8dbde4f63e31336f83f48aa6d21bcb6554c aio-pika-8.2.5/000077500000000000000000000000001433544061600132265ustar00rootroot00000000000000aio-pika-8.2.5/.coafile000066400000000000000000000001731433544061600146320ustar00rootroot00000000000000[Default] bears = PEP8Bear, PyUnusedCodeBear, FilenameBear, InvalidLinkBear files = aio_pika/**/*.py max_line_length = 120 aio-pika-8.2.5/.coveragerc000066400000000000000000000000561433544061600153500ustar00rootroot00000000000000[run] omit = aio_pika/compat.py branch = True aio-pika-8.2.5/.deepsource.toml000066400000000000000000000001341433544061600163350ustar00rootroot00000000000000version = 1 [[analyzers]] name = "python" enabled = true runtime_version = "3.x.x" aio-pika-8.2.5/.drone.yml000066400000000000000000000072231433544061600151420ustar00rootroot00000000000000--- kind: pipeline name: default steps: - name: prepare toxenv image: snakepacker/python:all group: tests pull: always commands: - tox --notest volumes: - name: cache path: /drone/src/.tox - name: linter image: snakepacker/python:all commands: - tox environment: TOXENV: lint 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 commands: - 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.8 uvloop image: snakepacker/python:all commands: - 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 image: snakepacker/python:all commands: - 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.7 uvloop image: snakepacker/python:all commands: - 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 image: snakepacker/python:all commands: - 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.6 uvloop image: snakepacker/python:all commands: - 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 image: snakepacker/python:all commands: - 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.5 uvloop image: snakepacker/python:all commands: - 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: rabbitmq:3-alpine --- kind: signature hmac: 32a7f019710b16f795a6531ef6fab89d2ab24f50aaee729c3a7379a0dda472b0 ... aio-pika-8.2.5/.editorconfig000066400000000000000000000004351433544061600157050ustar00rootroot00000000000000root = 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 = 3 [Makefile] indent_style = tab [*.yml] indent_size = 2 aio-pika-8.2.5/.github/000077500000000000000000000000001433544061600145665ustar00rootroot00000000000000aio-pika-8.2.5/.github/workflows/000077500000000000000000000000001433544061600166235ustar00rootroot00000000000000aio-pika-8.2.5/.github/workflows/tox.yml000066400000000000000000000026671433544061600201730ustar00rootroot00000000000000# 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: - lint - checkdoc - mypy steps: - uses: actions/checkout@v2 - name: tox ${{ matrix.linter }} uses: docker://snakepacker/python:all env: TOXENV: ${{ matrix.linter }} with: args: tox build: needs: lint runs-on: ubuntu-latest services: rabbitmq: image: docker://mosquito/aiormq-rabbitmq ports: - 5672:5672 strategy: fail-fast: false matrix: toxenv: - py37 - py38 - py39 - py310 # - py37-uvloop # - py38-uvloop # - py39-uvloop steps: - uses: actions/checkout@v2 - name: tox ${{ matrix.toxenv }} uses: docker://snakepacker/python:all env: FORCE_COLOR: 1 TOXENV: ${{ matrix.toxenv }} AMQP_URL: amqp://guest:guest@rabbitmq COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: args: /bin/bash -c "wait-for-port rabbitmq:5672 && tox" aio-pika-8.2.5/.gitignore000066400000000000000000000037471433544061600152310ustar00rootroot00000000000000# 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/ .vscode/ ## 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 aio-pika-8.2.5/CHANGELOG.md000066400000000000000000000361471433544061600150520ustar00rootroot000000000000008.2.4 ----- * Fix memory leaks around channel close callbacks #496 * Fastest way to reject all messages when queue iterator is closing #498 8.2.3 ----- * Fix memory leak when callback collections is chaining #495 8.2.2 ----- * Prevent "Task exception was never retrieved" on timeout #492 8.2.1 ----- * Fix memory leaks on channel close #491 8.2.0 ----- * allow passing ssl_context to the connection #474. A default parameter has been added to the public API, this does not break anything unless your code relies on the order of the arguments. 8.1.1 ----- * Generated anonymous queue name may conflict #486 * improve typing in multiple library actors #478 8.1.0 ----- * Bump `aiormq~=6.4.0` with `connection blocking` feature * `Connection.update_secret` method (#481) 8.0.3 ----- * cannot use client_properties issue #469 8.0.2 ----- * linter fixes in `aio_pika.rpc.__all__` 8.0.1 ----- * aio_pika.rpc fix for `TypeError: invalid exception object` for future 8.0.0 ----- ***Release notes*** In this release, there are many changes to the internal API and bug fixes related to sudden disconnection and correct recovery after reconnection. Unfortunately, the behavior that was in version 7.x was slightly affected. It's the reason the major version has been updated. The entire set of existing tests passes with minimal changes, therefore, except for some minor changes in behavior, the user code should work either without any modifications or with minimal changes, such as replacing removed deprecated functions with alternatives. This release has been already tested in a working environment, and now it seems that we have completely resolved all the known issues related to recovery after network failures. ***Changes***: * Added tests for unexpected network connection resets and fixed many related problems. * Added `UnderlayChannel` and `UnderlayConneciton`, this is `NamedTuple`s contains all connection and channel related properties. The `aiormq.Connection` and `aiormq.Channel` objects are now packaged in this `NamedTuple`s and can be atomically assigned to `aio_pika.Connection` and `aio_pika.Channel` objects. The main benefit is the not needed to add locks during the connection, in the best case, the container object is assigned to callee as usual, however, if something goes wrong during the connection, there is no need to clear something in `aio_pika.RobustConnection` or `aio_pika.RobustChannel`. * An `__init__` method is now a part of abstract classes for most `aio_pika` entities. * Removed explicit relations between `aio_pika.Channel` and `aio_pika.Connection`. Now you can't get a `aio_pika.Connection` instance from the `aio_pika.Channel` instance. * Fixed a bug that caused the whole connection was closed when a timeout occurred in one of the channels, in case the channel was waiting for a response frame to an amqp-rpc call. * Removed deprecated `add_close_callback` and `remove_close_callback` methods in `aio_pika.Channel`. Use `aio_pika.Channel.close_callbacks.add(callback, ...)` and `aio_pika.Channel.close_callbacks.remove(callback, ...)` instead. * Fixed a bug in `aio_pika.RobustChannel` that caused `default_exchane` broken after reconnecting. * The `publisher_confirms` property of `aio_pika.Channel` is public now. * Function `get_exchange_name` is public now. * Fixed an error in which the queue iterator could enter a deadlock state, with a sudden disconnection. * The new entity `OneShotCallback` helps, for example, to call all the closing callbacks at the channel if the `Connection` was unexpectedly closed, and the channel closing frame did not come explicitly. 7.2.0 ----- * Make `aio_pika.patterns.rpc` more extendable. 7.1.0 ----- * Fixes in documentation 7.0.0 ----- This release brings support for a new version of `aiormq`, which is used as a low-level driver for working with AMQP. The release contains a huge number of changes in the internal structure of the library, mainly related to type inheritance and abstract types, as well as typehints checking via mypy. The biggest change to the user API is the violation of the inheritance order, due to the introduction of abstract types, so this release is a major one. ### Changes * There are a lot of changes in the structure of the library, due to the widespread use of typing. * `aio_pika.abc` module now contains all types and abstract class prototypes. * Modern `aiormq~=6.1.1` used. * Complete type checks coverage via mypy. * The interface of `aio_pika`'s classes has undergone minimal changes, but you should double-check your code before migrating, at least because almost all types are now in `aio_pika.abc`. Module `aio_pika.types` still exists, but will produce a `DeprecationWarning`. * Default value for argument `weak` is changed to `False` in `CallbackCollection.add(func, weak=False)`. ### Known 6.x to 7.x migration issues * `pamqp.specification` module didn't exist in `pamqp==3.0.1` so you have to change it: * `pamqp.commands` for AMPQ-RPC–relates classes * `pamqp.base` for `Frame` class * `pamqp.body` for `ContentBody` class * `pamqp.commands` for `Basic`, `Channel`, `Confirm`, `Exchange`, `Queue`, `Tx` classes. * `pamqp.common` for `FieldArray`, `FieldTable`, `FieldValue` classes * `pamqp.constants` for constants like `REPLY_SUCCESS`. * `pamqp.header` for `ContentHeader` class. * `pamqp.heartbeat` for `Heartbeat` class. * Type definitions related to imports from `aio_pika` might throw warnings like `'SomeType' is not declared in __all__ `. This is a normal situation, since now it is necessary to import types from `aio_pika.abc`. In this release, these are just warnings, but in the next major release, this will stop working, so you should take care of changes in your code. Just use `aio_pika.abc` in your imports. The list of deprecated imports: * `from aio_pika.message import ReturnCallback` * `from aio_pika.patterns.rpc import RPCMessageType` - renamed to `RPCMessageTypes` * `import aio_pika.types` - module deprecated use `aio_pika.abc` instead * `from aio_pika.connection import ConnectionType` 6.8.2 ----- * explicit `Channel.is_user_closed` property * user-friendly exception when channel has been closed * reopen channels which are closed from the broker side 6.8.1 ----- * Fix flapping test test_robust_duplicate_queue #424 * Fixed callback on_close for rpc #424 6.8.0 ----- * fix: master deserialize types #366 * fix: add missing type hint on exchange publish method #370 * Return self instead of select result in `__aenter__` #373 * fix: call remove_close_callback #374 6.7.1 ----- * Fix breaking change in callback definition #344 6.7.0 ----- * Reworked tests and finally applied PR #311 * Improve documentation examples and snippets #339 * Restore RobustChannel.default_exchange on reconnect #340 * Improve the docs a bit #335 6.6.1 ----- * Add generics to Pool and PoolItemContextManager #321 * Fix Docs for ``DeliveryError`` #322 6.6.0 ----- * message.reject called inside ProcessContext.__exit__ fails when channel is closed #302 6.5.3 ----- * Add docs and github links to setup.py #304 6.5.2 ----- * Type annotation fixes * Add documentation 6.5.1 ----- * Test fixes * Add reopen method for channel #263 6.5.0 ----- * Add get methods for exchange and queue #282 * fix type annotation and documentation for Connection.add_close_callback #290 6.4.3 ----- * log channel close status * add OSError to `CONNECTION_EXCEPTIONS` 6.4.2 ----- * [fix] heartbeat_last to heartbeat_last_received #274 * Fix memory leak #285 * Fix type hint #287 * Pass loop when connecting to aiormq #294 6.4.1 ----- * RobustConnection cleanup fixes #273 6.4.0 ----- * aiormq updates: * Fixes for python 3.8 [#69](https://github.com/mosquito/aiormq/pull/69) [#67](https://github.com/mosquito/aiormq/pull/67) * [passing ``name=`` query parameter](https://github.com/mosquito/aiormq/pull/69/commits/a967502e6dbdf5de422cfb183932bcec134250ad) from URL to user defined connection name (Rabbitmq 3.8+) * Fix connection drain [#68](https://github.com/mosquito/aiormq/pull/68) * Remove ``loop=`` argument from asyncio entities [#67](https://github.com/mosquito/aiormq/pull/67) * ChannelInvalidStateError exceptions instead of RuntimeError [#65](https://github.com/mosquito/aiormq/pull/65) * Update tests for python 3.8 * ``Pool.close()`` method and allow to use ``Pool`` as a context manager [#269](https://github.com/mosquito/aio-pika/pull/269) * Fix stuck of ``RobustConnection`` when exclusive queues still locked on server-side [#267](https://github.com/mosquito/aio-pika/pull/267) * Add ``global_`` parameter to ``Channel.set_qos`` method [#266](https://github.com/mosquito/aio-pika/pull/266) * Fix ``Connection.drain()`` is ``None`` [Fix connection drain](https://github.com/mosquito/aiormq/pull/68) 6.3.0 ----- * passing `client_properties` 6.2.0 ----- * Allow str as an exchange type #260 6.1.2 ----- * Added typing on process method #252 6.1.1 ----- * Documentation fixes * Missed timeout parameter on `connect()` #245 6.1.0 ----- * Unified `CallbackCollection`s for channels and connections * Make RobustConnection more robust * `JsonRPC` and `JsonMaster` adapters * Improve patterns documentation 6.0.1 ----- * Extended ExchangeType #237. Added `x-modulus-hash` exchange type. 6.0.0 ----- * `RobustConnection` logic changes (see #234). Thanks to @decaz for analysis and fixes. 5.6.3 ----- * add more type annotations * consistent setting headers for message #233 5.6.2 ----- * Fixes: set header value on HeaderProxy #232 5.5.3 ----- * Fixed #218. How to properly close RobustConnection? 5.5.2 ----- * Fixed #216. Exception in Queue.consume callback isn't propagated properly. 5.5.1 ----- * Allow to specify `requeue=` and `reject_on_redelivered=` in Master pattern #212 5.5.0 ----- * Fixed #209 int values for headers 5.4.1 ----- * update aiormq version * use `AMQPError` instead of `AMQPException`. `AMQPException` is now alias for `AMQPError` 5.4.0 ----- * Fix routing key handling (#206 @decaz) * Fix URL building (#207 @decaz) * Test suite for `connect` function 5.3.2 ----- * Fix tests for `Pool` 5.3.1 ----- * no duplicate call message when exception * add robust classes to apidoc 5.3.0 ----- * use None instead of Elipsis for initial state (@chibby0ne) * `Pool`: enable arguments for pool constructor (@chibby0ne) * Create py.typed (#176 @zarybnicky) * 5.2.4 ----- * Fix encode timestamp error on copy (#198 @tzoiker) * Bump `aiormq` 5.2.2 ----- * Fix HeaderProxy bug (#195 @tzoiker) 5.2.1 ----- * remove non-initialized channels when reconnect 5.2.0 ----- * robust connection close only when unclosed * `heartbeat_last` property 5.1.1 ----- * Simple test suite for testing robust connection via tcp proxy 5.0.1 ----- * robust connection initialization hotfix 5.0.0 ----- * Connector is now `aiormq` and not `pika` * Remove vendored `pika` * Compatibility changes: * **[HIGH]** Exceptions hierarchy completely changed: * ``UnroutableError`` removed. Use ``DeliveryError`` instead. * ``ConnectionRefusedError`` is now standard ``ConnectionError`` * Each error code has separate exception type. * **[LOW]** ``Connection.close`` method requires exception instead of ``code`` ``reason`` pair or ``None`` * **[MEDIUM]** ``IncomingMessage.ack`` ``IncomingMessage.nack`` ``IncomingMessage.reject`` returns coroutines. Old usage compatible but event loop might throw warnings. * **[HIGH]** ``Message.timestamp`` property is now ``datetime.datetime`` * **[LOW]** Tracking of ``publisher confirms`` removed, using similar feature from ``aiormq`` instead. * **[LOW]** non async context manager ``IncomingMessage.process()`` is deprecated. Use ``async with message.process():`` instead. 4.9.1 ----- * Fix race condition on callback timeout #180 4.9.0 ----- * Add abstract pool #174 * Fixed Deprecation Warnings in Python 3.7 #153 4.8.1 ----- * Migrate from travis to drone.io * Use pylava instead of pylama 4.8.0 ----- * save passive flag on reconnect #170 4.7.0 ----- * fixed inconsistent argument type for connection.connect #136 * fixed conditions for creating SSL connection. #135 4.6.4 ----- * Fix UnboundLocalError exception #163 4.6.3 ----- * RobustConnection fixes #162 * Fix code examples in the README.rst 4.6.1 ----- * Close connection in examples 4.6.0 ----- * Add content_type for all patterns 4.5.0 ----- * Add special exceptions for Worker 4.4.0 ----- * More extendable Master 4.3.0 ----- * Fix #112 * Fix #155 4.2.0 ----- * Add default params for RPC.cereate() 4.1.0 ----- * Fix InvalidStateError when connection lost 4.0.1 ----- * Fix: RPC stuck when response deserialization error 4.0.0 ----- * Drop python 3.4 support 2.9.0 ----- * prevent `set_results` on cancelled future #133 * Added asynchronous context manager support for channels #130 2.8.3 ----- * BUGFIX: ChannelClosed exception was never retrieved 2.8.2 ----- * BUGFIX: handle coroutine double wrapping for Python 3.4 2.8.1 ----- * added example for URL which contains ssl required options. 2.8.0 ----- * `ssl_options` for coonect and connect_robust * default ports for `amqp` and `amqps` 2.7.1 ----- * python 3.4 fix 2.7.0 ----- * Add `message_kwargs` for worker pattern 2.6.0 ----- * Added `timeout` parameter for `Exchange.declare` * QueueEmpty exception public added to the module `__all__` 2.5.0 ----- * Ability to reconnect on Channel.Close * Ability to reconnect on Channel.Cancel 2.4.0 ----- * Rollback to pika==0.10 because new one had issues. 2.3.0 ----- * Feature: abillity to use ExternalCredentials with blank login. 2.2.2 ----- * Bugfix: _on_getempty should delete _on_getok_callback #110. (thank's to @dhontecillas) 2.2.1 ----- * Fixes for pyflakes 2.2.0 ----- * Rework transactions 2.1.0 ----- * Use pika's asyncio adapter 2.0.0 ----- * Rework robust connector 1.9.0 ----- * Ability to disable robustness for single queue in `rubust_connect` mode. * Ability to pass exchage by name. 1.8.1 ----- * Added `python_requires=">3.4.*, <4",` instead of `if sys.version_info` in the `setup.py` 1.8.0 ----- * Change `TimeoutError` to the `asyncio.TimeoutError` * Allow to bind queue by exchange name * Added `extras_require = {':python_version': 'typing >= 3.5.3',` to the `setup.py` 1.7.0 ----- * `aio_pika.patterns` submodule * `aio_pika.patterns.RPC` - RPC pattern * `aio_pika.patterns.Master` - Master/Worker pattern 1.5.1 ----- * `passive` argument for excahnge 1.5.0 ----- * `Channel.is_closed` property * `Channel.close` just return `None` when channel already closed * `Connection` might be used in `async with` expression * `Queue` might be used in `async with` and returns `QueueIterator` * Changing examples * `Queue.iterator()` method * `QueueIterator.close()` returns `asyncio.Future` instead of `asyncio.Task` * Ability to use `QueueIterator` in `async for` expression * `connect_robust` is a `coroutine` instead of function which returns a coroutine (PyCharm type checking display warning instead) * add tests 1.4.2 ----- * Improve documentation. Add examples for connection and channel * `Conneciton.close` returns `asyncio.Task` instead coroutine. * `connect_robust` now is function instead of `partial`. aio-pika-8.2.5/LICENSE000066400000000000000000000261351433544061600142420ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ 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 {yyyy} {name of copyright owner} 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. aio-pika-8.2.5/MANIFEST.in000066400000000000000000000001311433544061600147570ustar00rootroot00000000000000recursive-exclude tests * recursive-exclude __pycache__ * exclude .* include README.rst aio-pika-8.2.5/Makefile000066400000000000000000000007531433544061600146730ustar00rootroot00000000000000all: test RABBITMQ_CONTAINER_NAME:=aio_pika_rabbitmq RABBITMQ_IMAGE:=mosquito/aiormq-rabbitmq test: find . -name "*.pyc" -type f -delete tox rabbitmq: docker pull $(RABBITMQ_IMAGE) docker kill $(RABBITMQ_CONTAINER_NAME) || true docker run --rm -d \ --name $(RABBITMQ_CONTAINER_NAME) \ -p 5671:5671 \ -p 5672:5672 \ -p 15671:15671 \ -p 15672:15672 \ $(RABBITMQ_IMAGE) upload: python3.7 setup.py sdist bdist_wheel twine upload dist/*$(shell python3 setup.py --version)* aio-pika-8.2.5/README.rst000066400000000000000000000166351433544061600147300ustar00rootroot00000000000000.. _documentation: https://aio-pika.readthedocs.org/ .. _adopted official RabbitMQ tutorial: https://aio-pika.readthedocs.io/en/latest/rabbitmq-tutorial/1-introduction.html aio-pika ======== .. image:: https://readthedocs.org/projects/aio-pika/badge/?version=latest :target: https://aio-pika.readthedocs.org/ :alt: ReadTheDocs .. image:: https://coveralls.io/repos/github/mosquito/aio-pika/badge.svg?branch=master :target: https://coveralls.io/github/mosquito/aio-pika :alt: Coveralls .. image:: https://github.com/mosquito/aio-pika/workflows/tox/badge.svg :target: https://github.com/mosquito/aio-pika/actions?query=workflow%3Atox :alt: Github Actions .. image:: https://img.shields.io/pypi/v/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ :alt: Latest Version .. image:: https://img.shields.io/pypi/wheel/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ .. image:: https://img.shields.io/pypi/pyversions/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ .. image:: https://img.shields.io/pypi/l/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ A wrapper around `aiormq`_ for asyncio and humans. Check out the examples and the tutorial in the `documentation`_. If you are a newcomer to RabbitMQ, please start with the `adopted official RabbitMQ tutorial`_. .. _aiormq: http://github.com/mosquito/aiormq/ .. note:: Since version ``5.0.0`` this library doesn't use ``pika`` as AMQP connector. Versions below ``5.0.0`` contains or requires ``pika``'s source code. .. note:: The version 7.0.0 has breaking API changes, see CHANGELOG.md for migration hints. Features -------- * Completely asynchronous API. * Object oriented API. * Transparent auto-reconnects with complete state recovery with `connect_robust` (e.g. declared queues or exchanges, consuming state and bindings). * Python 3.7+ compatible. * For python 3.5 users available `aio-pika<7` * Transparent `publisher confirms`_ support * `Transactions`_ support * Completely type-hints coverage. .. _Transactions: https://www.rabbitmq.com/semantics.html .. _publisher confirms: https://www.rabbitmq.com/confirms.html Installation ------------ .. code-block:: shell pip install aio-pika Usage example ------------- Simple consumer: .. code-block:: python import asyncio import aio_pika import aio_pika.abc async def main(loop): # Connect with the givien parameters is also valiable. # aio_pika.connect_robust(host="host", login="login", password="password") # You can only choose one option to create a connection, url or kw-based params. connection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", loop=loop ) async with connection: queue_name = "test_queue" # Creating channel channel: aio_pika.abc.AbstractChannel = await connection.channel() # Declaring queue queue: aio_pika.abc.AbstractQueue = await channel.declare_queue( queue_name, auto_delete=True ) async with queue.iterator() as queue_iter: # Cancel consuming after __aexit__ async for message in queue_iter: async with message.process(): print(message.body) if queue.name in message.body.decode(): break if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main(loop)) loop.close() Simple publisher: .. code-block:: python import asyncio import aio_pika import aio_pika.abc async def main(loop): # Explicit type annotation connection: aio_pika.RobustConnection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", loop=loop ) routing_key = "test_queue" channel: aio_pika.abc.AbstractChannel = await connection.channel() await channel.default_exchange.publish( aio_pika.Message( body='Hello {}'.format(routing_key).encode() ), routing_key=routing_key ) await connection.close() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main(loop)) loop.close() Get single message example: .. code-block:: python import asyncio from aio_pika import connect_robust, Message async def main(loop): connection = await connect_robust( "amqp://guest:guest@127.0.0.1/", loop=loop ) queue_name = "test_queue" routing_key = "test_queue" # Creating channel channel = await connection.channel() # Declaring exchange exchange = await channel.declare_exchange('direct', auto_delete=True) # Declaring queue queue = await channel.declare_queue(queue_name, auto_delete=True) # Binding queue await queue.bind(exchange, routing_key) await exchange.publish( Message( bytes('Hello', 'utf-8'), content_type='text/plain', headers={'foo': 'bar'} ), routing_key ) # Receiving message incoming_message = await queue.get(timeout=5) # Confirm message await incoming_message.ack() await queue.unbind(exchange, routing_key) await queue.delete() await connection.close() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main(loop)) There are more examples and the RabbitMQ tutorial in the `documentation`_. Versioning ========== This software follows `Semantic Versioning`_ For contributors ---------------- Setting up development environment __________________________________ Clone the project: .. code-block:: shell git clone https://github.com/mosquito/aio-pika.git cd aio-pika Create a new virtualenv for `aio-pika`_: .. code-block:: shell python3 -m venv env source env/bin/activate Install all requirements for `aio-pika`_: .. code-block:: shell pip install -e '.[develop]' Running Tests _____________ **NOTE: In order to run the tests locally you need to run a RabbitMQ instance with default user/password (guest/guest) and port (5672).** * ProTip: Use Docker for this: .. code-block:: bash docker run -d -p 5671:5671 -p 5672:5672 -p 15671:15671 -p 15672:15672 mosquito/aiormq-rabbitmq To test just run: .. code-block:: bash make test Editing Documentation _____________________ To iterate quickly on the documentation live in your browser, try: .. code-block:: bash nox -s docs -- serve Creating Pull Requests ______________________ Please feel free to create pull requests, but you should describe your use cases and add some examples. Changes should follow a few simple rules: * When your changes break the public API, you must increase the major version. * When your changes are safe for public API (e.g. added an argument with default value) * You have to add test cases (see `tests/` folder) * You must add docstrings * Feel free to add yourself to `"thank's to" section`_ .. _"thank's to" section: https://github.com/mosquito/aio-pika/blob/master/docs/source/index.rst#thanks-for-contributing .. _Semantic Versioning: http://semver.org/ .. _aio-pika: https://github.com/mosquito/aio-pika/ aio-pika-8.2.5/aio_pika/000077500000000000000000000000001433544061600150025ustar00rootroot00000000000000aio-pika-8.2.5/aio_pika/__init__.py000066400000000000000000000021631433544061600171150ustar00rootroot00000000000000from . import abc, patterns, pool from .abc import DeliveryMode from .channel import Channel from .connection import Connection, connect from .exceptions import AMQPException, MessageProcessError from .exchange import Exchange, ExchangeType from .log import logger from .message import IncomingMessage, Message from .queue import Queue from .robust_channel import RobustChannel from .robust_connection import RobustConnection, connect_robust from .robust_exchange import RobustExchange from .robust_queue import RobustQueue from .version import ( __author__, __version__, author_info, package_info, package_license, version_info, ) __all__ = ( "AMQPException", "Channel", "Connection", "DeliveryMode", "Exchange", "ExchangeType", "IncomingMessage", "Message", "MessageProcessError", "Queue", "RobustChannel", "RobustConnection", "RobustExchange", "RobustQueue", "__author__", "__version__", "abc", "author_info", "connect", "connect_robust", "logger", "package_info", "package_license", "patterns", "pool", "version_info", ) aio-pika-8.2.5/aio_pika/abc.py000066400000000000000000000567371433544061600161230ustar00rootroot00000000000000import asyncio from abc import ABC, abstractmethod from datetime import datetime, timedelta from enum import Enum, IntEnum, unique from functools import singledispatch from types import TracebackType from typing import ( Any, AsyncContextManager, AsyncIterable, Awaitable, Callable, Dict, FrozenSet, Generator, Iterator, MutableMapping, NamedTuple, Optional, Set, Tuple, Type, TypeVar, Union, ) try: from typing import TypedDict except ImportError: from typing_extensions import TypedDict import aiormq.abc from aiormq.abc import ExceptionType from pamqp.common import Arguments from yarl import URL from .pool import PoolInstance from .tools import ( CallbackCollection, CallbackSetType, CallbackType, OneShotCallback, ) TimeoutType = Optional[Union[int, float]] NoneType = type(None) DateType = Union[int, datetime, float, timedelta, None] ExchangeParamType = Union["AbstractExchange", str] ConsumerTag = str MILLISECONDS = 1000 ZERO_TIME = datetime.utcfromtimestamp(0) class SSLOptions(TypedDict, total=False): cafile: str capath: str cadata: str keyfile: str certfile: str no_verify_ssl: int @unique class ExchangeType(str, Enum): FANOUT = "fanout" DIRECT = "direct" TOPIC = "topic" HEADERS = "headers" X_DELAYED_MESSAGE = "x-delayed-message" X_CONSISTENT_HASH = "x-consistent-hash" X_MODULUS_HASH = "x-modulus-hash" @unique class DeliveryMode(IntEnum): NOT_PERSISTENT = 1 PERSISTENT = 2 @unique class TransactionState(str, Enum): CREATED = "created" COMMITED = "commited" ROLLED_BACK = "rolled back" STARTED = "started" class DeclarationResult(NamedTuple): message_count: int consumer_count: int class AbstractTransaction: state: TransactionState @property @abstractmethod def channel(self) -> "AbstractChannel": raise NotImplementedError @abstractmethod async def select( self, timeout: TimeoutType = None, ) -> aiormq.spec.Tx.SelectOk: raise NotImplementedError @abstractmethod async def rollback( self, timeout: TimeoutType = None, ) -> aiormq.spec.Tx.RollbackOk: raise NotImplementedError async def commit( self, timeout: TimeoutType = None, ) -> aiormq.spec.Tx.CommitOk: raise NotImplementedError async def __aenter__(self) -> "AbstractTransaction": raise NotImplementedError async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: raise NotImplementedError HeadersValue = Union[aiormq.abc.FieldValue, bytes] HeadersPythonValues = Union[ HeadersValue, Set[HeadersValue], Tuple[HeadersValue, ...], FrozenSet[HeadersValue], ] HeadersType = MutableMapping[str, HeadersPythonValues] class AbstractMessage(ABC): body: bytes body_size: int headers_raw: aiormq.abc.FieldTable content_type: Optional[str] content_encoding: Optional[str] delivery_mode: DeliveryMode priority: Optional[int] correlation_id: Optional[str] reply_to: Optional[str] expiration: Optional[DateType] message_id: Optional[str] timestamp: Optional[datetime] type: Optional[str] user_id: Optional[str] app_id: Optional[str] @property @abstractmethod def headers(self) -> HeadersType: raise NotImplementedError @headers.setter def headers(self, value: HeadersType) -> None: raise NotImplementedError @abstractmethod def info(self) -> Dict[str, HeadersValue]: raise NotImplementedError @property @abstractmethod def locked(self) -> bool: raise NotImplementedError @property @abstractmethod def properties(self) -> aiormq.spec.Basic.Properties: raise NotImplementedError @abstractmethod def __iter__(self) -> Iterator[int]: raise NotImplementedError @abstractmethod def lock(self) -> None: raise NotImplementedError def __copy__(self) -> "AbstractMessage": raise NotImplementedError class AbstractIncomingMessage(AbstractMessage, ABC): cluster_id: Optional[str] consumer_tag: Optional["ConsumerTag"] delivery_tag: Optional[int] redelivered: Optional[bool] message_count: Optional[int] routing_key: Optional[str] exchange: Optional[str] @property @abstractmethod def channel(self) -> aiormq.abc.AbstractChannel: raise NotImplementedError @abstractmethod def process( self, requeue: bool = False, reject_on_redelivered: bool = False, ignore_processed: bool = False, ) -> "AbstractProcessContext": raise NotImplementedError @abstractmethod async def ack(self, multiple: bool = False) -> None: raise NotImplementedError @abstractmethod async def reject(self, requeue: bool = False) -> None: raise NotImplementedError @abstractmethod async def nack(self, multiple: bool = False, requeue: bool = True) -> None: raise NotImplementedError def info(self) -> Dict[str, Any]: raise NotImplementedError @property @abstractmethod def processed(self) -> bool: raise NotImplementedError class AbstractProcessContext(AsyncContextManager): @abstractmethod async def __aenter__(self) -> AbstractIncomingMessage: raise NotImplementedError @abstractmethod async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: raise NotImplementedError class AbstractQueue: channel: aiormq.abc.AbstractChannel name: str durable: bool exclusive: bool auto_delete: bool arguments: Arguments passive: bool declaration_result: aiormq.spec.Queue.DeclareOk close_callbacks: CallbackCollection @abstractmethod def __init__( self, channel: aiormq.abc.AbstractChannel, name: Optional[str], durable: bool, exclusive: bool, auto_delete: bool, arguments: Arguments, passive: bool = False, ): raise NotImplementedError @abstractmethod async def declare( self, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.DeclareOk: raise NotImplementedError @abstractmethod async def bind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, *, arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.BindOk: raise NotImplementedError @abstractmethod async def unbind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.UnbindOk: raise NotImplementedError @abstractmethod async def consume( self, callback: Callable[[AbstractIncomingMessage], Any], no_ack: bool = False, exclusive: bool = False, arguments: Arguments = None, consumer_tag: Optional[ConsumerTag] = None, timeout: TimeoutType = None, ) -> ConsumerTag: raise NotImplementedError @abstractmethod async def cancel( self, consumer_tag: ConsumerTag, timeout: TimeoutType = None, nowait: bool = False, ) -> aiormq.spec.Basic.CancelOk: raise NotImplementedError @abstractmethod async def get( self, *, no_ack: bool = False, fail: bool = True, timeout: TimeoutType = 5, ) -> Optional[AbstractIncomingMessage]: raise NotImplementedError @abstractmethod async def purge( self, no_wait: bool = False, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.PurgeOk: raise NotImplementedError @abstractmethod async def delete( self, *, if_unused: bool = True, if_empty: bool = True, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.DeleteOk: raise NotImplementedError @abstractmethod def iterator(self, **kwargs: Any) -> "AbstractQueueIterator": raise NotImplementedError class AbstractQueueIterator(AsyncIterable): _amqp_queue: AbstractQueue _queue: asyncio.Queue _consumer_tag: ConsumerTag _consume_kwargs: Dict[str, Any] @abstractmethod def close(self, *_: Any) -> Awaitable[Any]: raise NotImplementedError @abstractmethod async def on_message(self, message: AbstractIncomingMessage) -> None: raise NotImplementedError @abstractmethod async def consume(self) -> None: raise NotImplementedError @abstractmethod def __aiter__(self) -> "AbstractQueueIterator": raise NotImplementedError @abstractmethod def __aenter__(self) -> Awaitable["AbstractQueueIterator"]: raise NotImplementedError @abstractmethod async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: raise NotImplementedError @abstractmethod async def __anext__(self) -> AbstractIncomingMessage: raise NotImplementedError class AbstractExchange(ABC): name: str @abstractmethod def __init__( self, channel: aiormq.abc.AbstractChannel, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, auto_delete: bool = False, durable: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None, ): raise NotImplementedError @abstractmethod async def declare( self, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.DeclareOk: raise NotImplementedError @abstractmethod async def bind( self, exchange: ExchangeParamType, routing_key: str = "", *, arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.BindOk: raise NotImplementedError @abstractmethod async def unbind( self, exchange: ExchangeParamType, routing_key: str = "", arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.UnbindOk: raise NotImplementedError @abstractmethod async def publish( self, message: "AbstractMessage", routing_key: str, *, mandatory: bool = True, immediate: bool = False, timeout: TimeoutType = None, ) -> Optional[aiormq.abc.ConfirmationFrameType]: raise NotImplementedError @abstractmethod async def delete( self, if_unused: bool = False, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.DeleteOk: raise NotImplementedError class UnderlayChannel(NamedTuple): channel: aiormq.abc.AbstractChannel close_callback: OneShotCallback @classmethod async def create( cls, connection: aiormq.abc.AbstractConnection, close_callback: Callable[..., Awaitable[Any]], **kwargs: Any, ) -> "UnderlayChannel": close_callback = OneShotCallback(close_callback) await connection.ready() connection.closing.add_done_callback(close_callback) channel = await connection.channel(**kwargs) channel.closing.add_done_callback(close_callback) return cls( channel=channel, close_callback=close_callback, ) async def close(self, exc: Optional[ExceptionType] = None) -> Any: if self.close_callback.finished.is_set(): return # close callbacks must be fired when closing # and should be deleted later to prevent memory leaks await self.channel.close(exc) await self.close_callback.wait() self.channel.closing.remove_done_callback(self.close_callback) self.channel.connection.closing.remove_done_callback( self.close_callback ) class AbstractChannel(PoolInstance, ABC): QUEUE_CLASS: Type[AbstractQueue] EXCHANGE_CLASS: Type[AbstractExchange] close_callbacks: CallbackCollection return_callbacks: CallbackCollection default_exchange: AbstractExchange publisher_confirms: bool @property @abstractmethod def is_initialized(self) -> bool: return hasattr(self, "_channel") @property @abstractmethod def is_closed(self) -> bool: raise NotImplementedError @abstractmethod def close(self, exc: Optional[ExceptionType] = None) -> Awaitable[None]: raise NotImplementedError @property @abstractmethod def channel(self) -> aiormq.abc.AbstractChannel: raise NotImplementedError @property @abstractmethod def number(self) -> Optional[int]: raise NotImplementedError @abstractmethod async def __aenter__(self) -> "AbstractChannel": raise NotImplementedError @abstractmethod def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Awaitable[None]: raise NotImplementedError @abstractmethod async def initialize(self, timeout: TimeoutType = None) -> None: raise NotImplementedError @abstractmethod def reopen(self) -> Awaitable[None]: raise NotImplementedError @abstractmethod async def declare_exchange( self, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, durable: bool = False, auto_delete: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None, timeout: TimeoutType = None, ) -> AbstractExchange: raise NotImplementedError @abstractmethod async def get_exchange( self, name: str, *, ensure: bool = True, ) -> AbstractExchange: raise NotImplementedError @abstractmethod async def declare_queue( self, name: Optional[str] = None, *, durable: bool = False, exclusive: bool = False, passive: bool = False, auto_delete: bool = False, arguments: Arguments = None, timeout: TimeoutType = None, ) -> AbstractQueue: raise NotImplementedError @abstractmethod async def get_queue( self, name: str, *, ensure: bool = True, ) -> AbstractQueue: raise NotImplementedError @abstractmethod async def set_qos( self, prefetch_count: int = 0, prefetch_size: int = 0, global_: bool = False, timeout: TimeoutType = None, all_channels: Optional[bool] = None, ) -> aiormq.spec.Basic.QosOk: raise NotImplementedError @abstractmethod async def queue_delete( self, queue_name: str, timeout: TimeoutType = None, if_unused: bool = False, if_empty: bool = False, nowait: bool = False, ) -> aiormq.spec.Queue.DeleteOk: raise NotImplementedError @abstractmethod async def exchange_delete( self, exchange_name: str, timeout: TimeoutType = None, if_unused: bool = False, nowait: bool = False, ) -> aiormq.spec.Exchange.DeleteOk: raise NotImplementedError @abstractmethod def transaction(self) -> AbstractTransaction: raise NotImplementedError @abstractmethod async def flow(self, active: bool = True) -> aiormq.spec.Channel.FlowOk: raise NotImplementedError @abstractmethod def __await__(self) -> Generator[Any, Any, "AbstractChannel"]: raise NotImplementedError class UnderlayConnection(NamedTuple): connection: aiormq.abc.AbstractConnection close_callback: OneShotCallback @classmethod async def make_connection( cls, url: URL, timeout: TimeoutType = None, **kwargs: Any, ) -> aiormq.abc.AbstractConnection: connection: aiormq.abc.AbstractConnection = await asyncio.wait_for( aiormq.connect(url, **kwargs), timeout=timeout, ) await connection.ready() return connection @classmethod async def connect( cls, url: URL, close_callback: Callable[..., Awaitable[Any]], timeout: TimeoutType = None, **kwargs: Any, ) -> "UnderlayConnection": try: connection = await cls.make_connection( url, timeout=timeout, **kwargs, ) close_callback = OneShotCallback(close_callback) connection.closing.add_done_callback(close_callback) except Exception as e: closing = asyncio.get_event_loop().create_future() closing.set_exception(e) await close_callback(closing) raise await connection.ready() return cls( connection=connection, close_callback=close_callback, ) def ready(self) -> Awaitable[Any]: return self.connection.ready() async def close(self, exc: Optional[aiormq.abc.ExceptionType]) -> Any: if self.close_callback.finished.is_set(): return try: return await self.connection.close(exc) except asyncio.CancelledError: raise finally: await self.close_callback.wait() class AbstractConnection(PoolInstance, ABC): close_callbacks: CallbackCollection connected: asyncio.Event transport: Optional[UnderlayConnection] @abstractmethod def __init__( self, url: URL, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any, ): raise NotImplementedError( f"Method not implemented, passed: url={url}, loop={loop!r}", ) @property @abstractmethod def is_closed(self) -> bool: raise NotImplementedError @abstractmethod async def close(self, exc: ExceptionType = asyncio.CancelledError) -> None: raise NotImplementedError @abstractmethod async def connect(self, timeout: TimeoutType = None) -> None: raise NotImplementedError @abstractmethod def channel( self, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ) -> AbstractChannel: raise NotImplementedError @abstractmethod async def ready(self) -> None: 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], ) -> None: raise NotImplementedError @abstractmethod async def update_secret( self, new_secret: str, *, reason: str = "", timeout: TimeoutType = None, ) -> aiormq.spec.Connection.UpdateSecretOk: raise NotImplementedError class AbstractRobustQueue(AbstractQueue): @abstractmethod def restore(self, channel: aiormq.abc.AbstractChannel) -> Awaitable[None]: raise NotImplementedError @abstractmethod async def bind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, *, arguments: Arguments = None, timeout: TimeoutType = None, robust: bool = True, ) -> aiormq.spec.Queue.BindOk: raise NotImplementedError @abstractmethod async def consume( self, callback: Callable[[AbstractIncomingMessage], Any], no_ack: bool = False, exclusive: bool = False, arguments: Arguments = None, consumer_tag: Optional[ConsumerTag] = None, timeout: TimeoutType = None, robust: bool = True, ) -> ConsumerTag: raise NotImplementedError class AbstractRobustExchange(AbstractExchange): @abstractmethod def restore(self, channel: aiormq.abc.AbstractChannel) -> Awaitable[None]: raise NotImplementedError @abstractmethod async def bind( self, exchange: ExchangeParamType, routing_key: str = "", *, arguments: Arguments = None, timeout: TimeoutType = None, robust: bool = True, ) -> aiormq.spec.Exchange.BindOk: raise NotImplementedError class AbstractRobustChannel(AbstractChannel): reopen_callbacks: CallbackCollection @abstractmethod def reopen(self) -> Awaitable[None]: raise NotImplementedError @abstractmethod async def restore(self, connection: aiormq.abc.AbstractConnection) -> None: raise NotImplementedError @abstractmethod async def declare_exchange( self, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, durable: bool = False, auto_delete: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None, timeout: TimeoutType = None, robust: bool = True, ) -> AbstractRobustExchange: raise NotImplementedError @abstractmethod async def declare_queue( self, name: Optional[str] = None, *, durable: bool = False, exclusive: bool = False, passive: bool = False, auto_delete: bool = False, arguments: Optional[Dict[str, Any]] = None, timeout: TimeoutType = None, robust: bool = True, ) -> AbstractRobustQueue: raise NotImplementedError class AbstractRobustConnection(AbstractConnection): reconnect_callbacks: CallbackCollection @property @abstractmethod def reconnecting(self) -> bool: raise NotImplementedError @abstractmethod def reconnect(self) -> Awaitable[None]: raise NotImplementedError @abstractmethod def channel( self, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ) -> AbstractRobustChannel: raise NotImplementedError ChannelCloseCallback = Callable[ [AbstractChannel, Optional[BaseException]], Any, ] ConnectionCloseCallback = Callable[ [AbstractConnection, Optional[BaseException]], Any, ] ConnectionType = TypeVar("ConnectionType", bound=AbstractConnection) @singledispatch def get_exchange_name(value: Any) -> str: raise ValueError( "exchange argument must be an exchange " f"instance or str not {value!r}", ) @get_exchange_name.register(AbstractExchange) def _get_exchange_name_from_exchnage(value: AbstractExchange) -> str: return value.name @get_exchange_name.register(str) def _get_exchange_name_from_str(value: str) -> str: return value __all__ = ( "AbstractChannel", "AbstractConnection", "AbstractExchange", "AbstractIncomingMessage", "AbstractMessage", "AbstractProcessContext", "AbstractQueue", "AbstractQueueIterator", "AbstractRobustChannel", "AbstractRobustConnection", "AbstractRobustExchange", "AbstractRobustQueue", "AbstractTransaction", "CallbackSetType", "CallbackType", "ChannelCloseCallback", "ConnectionCloseCallback", "ConsumerTag", "DateType", "DeclarationResult", "DeliveryMode", "ExchangeParamType", "ExchangeType", "HeadersPythonValues", "HeadersType", "HeadersValue", "MILLISECONDS", "SSLOptions", "TimeoutType", "TransactionState", "UnderlayChannel", "UnderlayConnection", "ZERO_TIME", "get_exchange_name", ) aio-pika-8.2.5/aio_pika/channel.py000066400000000000000000000327471433544061600170010ustar00rootroot00000000000000import asyncio from abc import ABC from types import TracebackType from typing import Any, AsyncContextManager, Generator, Optional, Type, Union from warnings import warn import aiormq import aiormq.abc from pamqp.common import Arguments from .abc import ( AbstractChannel, AbstractExchange, AbstractQueue, TimeoutType, UnderlayChannel, ) from .exchange import Exchange, ExchangeType from .log import get_logger from .message import IncomingMessage from .queue import Queue from .tools import CallbackCollection from .transaction import Transaction log = get_logger(__name__) class ChannelContext(AsyncContextManager, AbstractChannel, ABC): async def __aenter__(self) -> "AbstractChannel": if not self.is_initialized: await self.initialize() return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: return await self.close(exc_val) def __await__(self) -> Generator[Any, Any, AbstractChannel]: yield from self.initialize().__await__() return self class Channel(ChannelContext): """Channel abstraction""" QUEUE_CLASS = Queue EXCHANGE_CLASS = Exchange _channel: Optional[UnderlayChannel] def __init__( self, connection: aiormq.abc.AbstractConnection, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ): """ :param connection: :class:`aio_pika.adapter.AsyncioConnection` instance :param loop: Event loop (:func:`asyncio.get_event_loop()` when :class:`None`) :param future_store: :class:`aio_pika.common.FutureStore` instance :param publisher_confirms: False if you don't need delivery confirmations (in pursuit of performance) """ if not publisher_confirms and on_return_raises: raise RuntimeError( '"on_return_raises" not applicable ' 'without "publisher_confirms"', ) self._connection: aiormq.abc.AbstractConnection = connection # That's means user closed channel instance explicitly self._closed: bool = False self._channel = None self._channel_number = channel_number self.close_callbacks = CallbackCollection(self) self.return_callbacks = CallbackCollection(self) self.publisher_confirms = publisher_confirms self.on_return_raises = on_return_raises @property def is_initialized(self) -> bool: """Returns True when the channel has been opened and ready for interaction""" return self._channel is not None @property def is_closed(self) -> bool: """Returns True when the channel has been closed from the broker side or after the close() method has been called.""" if not self.is_initialized or self._closed: return True if not self._channel: return True return self._channel.channel.is_closed async def close( self, exc: Optional[aiormq.abc.ExceptionType] = None, ) -> None: if not self.is_initialized: log.warning("Channel not opened") return if not self._channel: log.warning("Transport is not ready") return log.debug("Closing channel %r", self) self._closed = True await self._channel.close() @property def channel(self) -> aiormq.abc.AbstractChannel: if not self.is_initialized or not self._channel: raise aiormq.exceptions.ChannelInvalidStateError( "Channel was not opened", ) if self.is_closed: raise aiormq.exceptions.ChannelInvalidStateError( "Channel has been closed", ) return self._channel.channel @property def number(self) -> Optional[int]: return ( self.channel.number if self.is_initialized else self._channel_number ) def __str__(self) -> str: return "{}".format(self.number or "Not initialized channel") async def _open(self) -> None: await self._connection.ready() channel = await UnderlayChannel.create( self._connection, self._on_close, publisher_confirms=self.publisher_confirms, on_return_raises=self.on_return_raises, channel_number=self._channel_number, ) await self._on_open(channel.channel) self._channel = channel self._closed = False async def initialize(self, timeout: TimeoutType = None) -> None: if self.is_initialized: raise RuntimeError("Already initialized") elif self._closed: raise RuntimeError("Can't initialize closed channel") await self._open() await self._on_initialized() async def _on_open(self, channel: aiormq.abc.AbstractChannel) -> None: self.default_exchange: Exchange = self.EXCHANGE_CLASS( channel=channel, arguments=None, auto_delete=False, durable=False, internal=False, name="", passive=False, type=ExchangeType.DIRECT, ) async def _on_close(self, closing: asyncio.Future) -> None: await self.close_callbacks(closing.exception()) if self._channel and self._channel.channel: self._channel.channel.on_return_callbacks.discard(self._on_return) async def _on_initialized(self) -> None: self.channel.on_return_callbacks.add(self._on_return) def _on_return(self, message: aiormq.abc.DeliveredMessage) -> None: self.return_callbacks(IncomingMessage(message, no_ack=True)) async def reopen(self) -> None: log.debug("Start reopening channel %r", self) await self._open() def __del__(self) -> None: self._closed = True self._channel = None async def declare_exchange( self, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, durable: bool = False, auto_delete: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None, timeout: TimeoutType = None, ) -> AbstractExchange: """ Declare an exchange. :param name: string with exchange name or :class:`aio_pika.exchange.Exchange` instance :param type: Exchange type. Enum ExchangeType value or string. String values must be one of 'fanout', 'direct', 'topic', 'headers', 'x-delayed-message', 'x-consistent-hash'. :param durable: Durability (exchange survive broker restart) :param auto_delete: Delete queue when channel will be closed. :param internal: Do not send it to broker just create an object :param passive: Do not fail when entity was declared previously but has another params. Raises :class:`aio_pika.exceptions.ChannelClosed` when exchange doesn't exist. :param arguments: additional arguments :param timeout: execution timeout :return: :class:`aio_pika.exchange.Exchange` instance """ if auto_delete and durable is None: durable = False exchange = self.EXCHANGE_CLASS( channel=self.channel, name=name, type=type, durable=durable, auto_delete=auto_delete, internal=internal, passive=passive, arguments=arguments, ) await exchange.declare(timeout=timeout) log.debug("Exchange declared %r", exchange) return exchange async def get_exchange( self, name: str, *, ensure: bool = True ) -> AbstractExchange: """ With ``ensure=True``, it's a shortcut for ``.declare_exchange(..., passive=True)``; otherwise, it returns an exchange instance without checking its existence. When the exchange does not exist, if ``ensure=True``, will raise :class:`aio_pika.exceptions.ChannelClosed`. Use this method in a separate channel (or as soon as channel created). This is only a way to get an exchange without declaring a new one. :param name: exchange name :param ensure: ensure that the exchange exists :return: :class:`aio_pika.exchange.Exchange` instance :raises: :class:`aio_pika.exceptions.ChannelClosed` instance """ if ensure: return await self.declare_exchange(name=name, passive=True) else: return self.EXCHANGE_CLASS( channel=self.channel, name=name, durable=False, auto_delete=False, internal=False, passive=True, arguments=None, ) async def declare_queue( self, name: Optional[str] = None, *, durable: bool = False, exclusive: bool = False, passive: bool = False, auto_delete: bool = False, arguments: Arguments = None, timeout: TimeoutType = None ) -> AbstractQueue: """ :param name: queue name :param durable: Durability (queue survive broker restart) :param exclusive: Makes this queue exclusive. Exclusive queues may only be accessed by the current connection, and are deleted when that connection closes. Passive declaration of an exclusive queue by other connections are not allowed. :param passive: Do not fail when entity was declared previously but has another params. Raises :class:`aio_pika.exceptions.ChannelClosed` when queue doesn't exist. :param auto_delete: Delete queue when channel will be closed. :param arguments: additional arguments :param timeout: execution timeout :return: :class:`aio_pika.queue.Queue` instance :raises: :class:`aio_pika.exceptions.ChannelClosed` instance """ queue: AbstractQueue = self.QUEUE_CLASS( channel=self.channel, name=name, durable=durable, exclusive=exclusive, auto_delete=auto_delete, arguments=arguments, passive=passive, ) await queue.declare(timeout=timeout) self.close_callbacks.add(queue.close_callbacks, weak=True) return queue async def get_queue( self, name: str, *, ensure: bool = True ) -> AbstractQueue: """ With ``ensure=True``, it's a shortcut for ``.declare_queue(..., passive=True)``; otherwise, it returns a queue instance without checking its existence. When the queue does not exist, if ``ensure=True``, will raise :class:`aio_pika.exceptions.ChannelClosed`. Use this method in a separate channel (or as soon as channel created). This is only a way to get a queue without declaring a new one. :param name: queue name :param ensure: ensure that the queue exists :return: :class:`aio_pika.queue.Queue` instance :raises: :class:`aio_pika.exceptions.ChannelClosed` instance """ if ensure: return await self.declare_queue(name=name, passive=True) else: return self.QUEUE_CLASS( channel=self.channel, name=name, durable=False, exclusive=False, auto_delete=False, arguments=None, passive=True, ) async def set_qos( self, prefetch_count: int = 0, prefetch_size: int = 0, global_: bool = False, timeout: TimeoutType = None, all_channels: Optional[bool] = None, ) -> aiormq.spec.Basic.QosOk: if all_channels is not None: warn('Use "global_" instead of "all_channels"', DeprecationWarning) global_ = all_channels return await self.channel.basic_qos( prefetch_count=prefetch_count, prefetch_size=prefetch_size, global_=global_, timeout=timeout, ) async def queue_delete( self, queue_name: str, timeout: TimeoutType = None, if_unused: bool = False, if_empty: bool = False, nowait: bool = False, ) -> aiormq.spec.Queue.DeleteOk: return await self.channel.queue_delete( queue=queue_name, if_unused=if_unused, if_empty=if_empty, nowait=nowait, timeout=timeout, ) async def exchange_delete( self, exchange_name: str, timeout: TimeoutType = None, if_unused: bool = False, nowait: bool = False, ) -> aiormq.spec.Exchange.DeleteOk: return await self.channel.exchange_delete( exchange=exchange_name, if_unused=if_unused, nowait=nowait, timeout=timeout, ) def transaction(self) -> Transaction: if self.publisher_confirms: raise RuntimeError( "Cannot create transaction when publisher " "confirms are enabled", ) return Transaction(self) async def flow(self, active: bool = True) -> aiormq.spec.Channel.FlowOk: return await self.channel.flow(active=active) __all__ = ("Channel",) aio-pika-8.2.5/aio_pika/connection.py000066400000000000000000000251051433544061600175160ustar00rootroot00000000000000import asyncio from ssl import SSLContext from types import TracebackType from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar, Union import aiormq.abc from aiormq.tools import censor_url from pamqp.common import FieldTable from yarl import URL from .abc import ( AbstractChannel, AbstractConnection, SSLOptions, TimeoutType, UnderlayConnection, ) from .channel import Channel from .log import get_logger from .tools import CallbackCollection log = get_logger(__name__) T = TypeVar("T") class Connection(AbstractConnection): """ Connection abstraction """ CHANNEL_CLASS: Type[Channel] = Channel KWARGS_TYPES: Tuple[Tuple[str, Callable[[str], Any], str], ...] = () _closed: bool @property def is_closed(self) -> bool: return self._closed async def close( self, exc: Optional[aiormq.abc.ExceptionType] = asyncio.CancelledError, ) -> None: transport, self.transport = self.transport, None self._close_called = True if not transport: return await transport.close(exc) self._closed = True @classmethod def _parse_kwargs(cls, kwargs: Dict[str, Any]) -> Dict[str, Any]: result = {} for key, parser, default in cls.KWARGS_TYPES: result[key] = parser(kwargs.get(key, default)) return result def __init__( self, url: URL, loop: Optional[asyncio.AbstractEventLoop] = None, ssl_context: Optional[SSLContext] = None, **kwargs: Any ): self.loop = loop or asyncio.get_event_loop() self.transport = None self._closed = False self._close_called = False self.url = URL(url) self.kwargs: Dict[str, Any] = self._parse_kwargs( kwargs or dict(self.url.query), ) self.kwargs["context"] = ssl_context self.close_callbacks = CallbackCollection(self) self.connected: asyncio.Event = asyncio.Event() def __str__(self) -> str: return str(censor_url(self.url)) def __repr__(self) -> str: return f'<{self.__class__.__name__}: "{self}">' async def _on_connection_close(self, closing: asyncio.Future) -> None: exc: Optional[BaseException] = closing.exception() self.connected.clear() await self.close_callbacks(exc) async def _on_connected(self, transport: UnderlayConnection) -> None: self.connected.set() async def connect(self, timeout: TimeoutType = None) -> None: """ Connect to AMQP server. This method should be called after :func:`aio_pika.connection.Connection.__init__` .. note:: This method is called by :func:`connect`. You shouldn't call it explicitly. """ transport = await UnderlayConnection.connect( self.url, self._on_connection_close, timeout=timeout, **self.kwargs ) await self._on_connected(transport) self.transport = transport def channel( self, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ) -> AbstractChannel: """ Coroutine which returns new instance of :class:`Channel`. Example: .. code-block:: python import aio_pika async def main(loop): connection = await aio_pika.connect( "amqp://guest:guest@127.0.0.1/" ) channel1 = connection.channel() await channel1.close() # Creates channel with specific channel number channel42 = connection.channel(42) await channel42.close() # For working with transactions channel_no_confirms = await connection.channel( publisher_confirms=False ) await channel_no_confirms.close() Also available as an asynchronous context manager: .. code-block:: python import aio_pika async def main(loop): connection = await aio_pika.connect( "amqp://guest:guest@127.0.0.1/" ) async with connection.channel() as channel: # channel is open and available # channel is now closed :param channel_number: specify the channel number explicit :param publisher_confirms: if `True` the :func:`aio_pika.Exchange.publish` method will be return :class:`bool` after publish is complete. Otherwise the :func:`aio_pika.Exchange.publish` method will be return :class:`None` :param on_return_raises: raise an :class:`aio_pika.exceptions.DeliveryError` when mandatory message will be returned """ if not self.transport: raise RuntimeError("Connection was not opened") log.debug("Creating AMQP channel for connection: %r", self) channel = self.CHANNEL_CLASS( connection=self.transport.connection, channel_number=channel_number, publisher_confirms=publisher_confirms, on_return_raises=on_return_raises, ) log.debug("Channel created: %r", channel) return channel async def ready(self) -> None: await self.connected.wait() def __del__(self) -> None: if ( self.is_closed or self.loop.is_closed() or not hasattr(self, "connection") ): return asyncio.ensure_future(self.close()) async def __aenter__(self) -> "Connection": return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: await self.close() async def update_secret( self, new_secret: str, *, reason: str = "", timeout: TimeoutType = None, ) -> aiormq.spec.Connection.UpdateSecretOk: if self.transport is None: raise RuntimeError("Connection is not ready") result = await self.transport.connection.update_secret( new_secret=new_secret, reason=reason, timeout=timeout, ) self.url = self.url.with_password(new_secret) return result def make_url( url: Union[str, URL, None] = None, *, host: str = "localhost", port: int = 5672, login: str = "guest", password: str = "guest", virtualhost: str = "/", ssl: bool = False, ssl_options: Optional[SSLOptions] = None, client_properties: Optional[FieldTable] = None, **kwargs: Any, ) -> URL: if url is not None: if not isinstance(url, URL): return URL(url) return url kw = kwargs kw.update(ssl_options or {}) kw.update(client_properties or {}) # sanitize keywords kw = {k: v for k, v in kw.items() if v is not None} return URL.build( scheme="amqps" if ssl else "amqp", host=host, port=port, user=login, password=password, # yarl >= 1.3.0 requires path beginning with slash path="/" + virtualhost, query=kw, ) async def connect( url: Union[str, URL, None] = None, *, host: str = "localhost", port: int = 5672, login: str = "guest", password: str = "guest", virtualhost: str = "/", ssl: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, ssl_options: Optional[SSLOptions] = None, ssl_context: Optional[SSLContext] = None, timeout: TimeoutType = None, client_properties: Optional[FieldTable] = None, connection_class: Type[AbstractConnection] = Connection, **kwargs: Any ) -> AbstractConnection: """ Make connection to the broker. Example: .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect( "amqp://guest:guest@127.0.0.1/" ) Connect to localhost with default credentials: .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect() .. note:: The available keys for ssl_options parameter are: * cert_reqs * certfile * keyfile * ssl_version For an information on what the ssl_options can be set to reference the `official Python documentation`_ . Set connection name for RabbitMQ admin panel: .. code-block:: python # As URL parameter method read_connection = await connect( "amqp://guest:guest@localhost/?name=Read%20connection" ) write_connection = await connect( client_properties={ 'connection_name': 'Write connection' } ) .. note: ``client_properties`` argument requires ``aiormq>=2.9`` URL string might be contain ssl parameters e.g. `amqps://user:pass@host//?ca_certs=ca.pem&certfile=crt.pem&keyfile=key.pem` :param client_properties: add custom client capability. :param url: RFC3986_ formatted broker address. When :class:`None` will be used keyword arguments. :param host: hostname of the broker :param port: broker port 5672 by default :param login: username string. `'guest'` by default. :param password: password string. `'guest'` by default. :param virtualhost: virtualhost parameter. `'/'` by default :param ssl: use SSL for connection. Should be used with addition kwargs. :param ssl_options: A dict of values for the SSL connection. :param timeout: connection timeout in seconds :param loop: Event loop (:func:`asyncio.get_event_loop()` when :class:`None`) :param ssl_context: ssl.SSLContext instance :param connection_class: Factory of a new connection :param kwargs: addition parameters which will be passed to the connection. :return: :class:`aio_pika.connection.Connection` .. _RFC3986: https://goo.gl/MzgYAs .. _official Python documentation: https://goo.gl/pty9xA """ connection: AbstractConnection = connection_class( make_url( url, host=host, port=port, login=login, password=password, virtualhost=virtualhost, ssl=ssl, ssl_options=ssl_options, client_properties=client_properties, **kwargs ), loop=loop, ssl_context=ssl_context, ) await connection.connect(timeout=timeout) return connection __all__ = ("connect", "Connection", "make_url") aio-pika-8.2.5/aio_pika/exceptions.py000066400000000000000000000024301433544061600175340ustar00rootroot00000000000000import asyncio import pamqp.exceptions from aiormq.exceptions import ( AMQPChannelError, AMQPConnectionError, AMQPError, AMQPException, AuthenticationError, ChannelClosed, ChannelInvalidStateError, ChannelNotFoundEntity, ChannelPreconditionFailed, ConnectionClosed, DeliveryError, DuplicateConsumerTag, IncompatibleProtocolError, InvalidFrameError, MethodNotImplemented, ProbableAuthenticationError, ProtocolSyntaxError, PublishError, ) CONNECTION_EXCEPTIONS = ( AMQPError, ConnectionError, OSError, RuntimeError, StopAsyncIteration, pamqp.exceptions.PAMQPException, ) class MessageProcessError(AMQPError): reason = "%s: %r" class QueueEmpty(AMQPError, asyncio.QueueEmpty): pass __all__ = ( "AMQPChannelError", "AMQPConnectionError", "AMQPError", "AMQPException", "AuthenticationError", "CONNECTION_EXCEPTIONS", "ChannelClosed", "ChannelInvalidStateError", "ChannelNotFoundEntity", "ChannelPreconditionFailed", "ConnectionClosed", "DeliveryError", "DuplicateConsumerTag", "IncompatibleProtocolError", "InvalidFrameError", "MessageProcessError", "MethodNotImplemented", "ProbableAuthenticationError", "ProtocolSyntaxError", "PublishError", "QueueEmpty", ) aio-pika-8.2.5/aio_pika/exchange.py000066400000000000000000000143621433544061600171440ustar00rootroot00000000000000from typing import Optional, Union import aiormq from pamqp.common import Arguments from .abc import ( AbstractExchange, AbstractMessage, ExchangeParamType, ExchangeType, TimeoutType, get_exchange_name, ) from .log import get_logger log = get_logger(__name__) class Exchange(AbstractExchange): """ Exchange abstraction """ channel: aiormq.abc.AbstractChannel def __init__( self, channel: aiormq.abc.AbstractChannel, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, auto_delete: bool = False, durable: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None ): self._type = type.value if isinstance(type, ExchangeType) else type self.channel = channel self.name = name self.auto_delete = auto_delete self.durable = durable self.internal = internal self.passive = passive self.arguments = arguments or {} def __str__(self) -> str: return self.name def __repr__(self) -> str: return "" % ( self, self.auto_delete, self.durable, self.arguments, ) async def declare( self, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.DeclareOk: return await self.channel.exchange_declare( self.name, exchange_type=self._type, durable=self.durable, auto_delete=self.auto_delete, internal=self.internal, passive=self.passive, arguments=self.arguments, timeout=timeout, ) async def bind( self, exchange: ExchangeParamType, routing_key: str = "", *, arguments: Arguments = None, timeout: TimeoutType = None ) -> aiormq.spec.Exchange.BindOk: """ A binding can also be a relationship between two exchanges. This can be simply read as: this exchange is interested in messages from another exchange. Bindings can take an extra routing_key parameter. To avoid the confusion with a basic_publish parameter we're going to call it a binding key. .. code-block:: python client = await connect() routing_key = 'simple_routing_key' src_exchange_name = "source_exchange" dest_exchange_name = "destination_exchange" channel = await client.channel() src_exchange = await channel.declare_exchange( src_exchange_name, auto_delete=True ) dest_exchange = await channel.declare_exchange( dest_exchange_name, auto_delete=True ) queue = await channel.declare_queue(auto_delete=True) await queue.bind(dest_exchange, routing_key) await dest_exchange.bind(src_exchange, routing_key) :param exchange: :class:`aio_pika.exchange.Exchange` instance :param routing_key: routing key :param arguments: additional arguments :param timeout: execution timeout :return: :class:`None` """ log.debug( "Binding exchange %r to exchange %r, routing_key=%r, arguments=%r", self, exchange, routing_key, arguments, ) return await self.channel.exchange_bind( arguments=arguments, destination=self.name, routing_key=routing_key, source=get_exchange_name(exchange), timeout=timeout, ) async def unbind( self, exchange: ExchangeParamType, routing_key: str = "", arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.UnbindOk: """ Remove exchange-to-exchange binding for this :class:`Exchange` instance :param exchange: :class:`aio_pika.exchange.Exchange` instance :param routing_key: routing key :param arguments: additional arguments :param timeout: execution timeout :return: :class:`None` """ log.debug( "Unbinding exchange %r from exchange %r, " "routing_key=%r, arguments=%r", self, exchange, routing_key, arguments, ) return await self.channel.exchange_unbind( arguments=arguments, destination=self.name, routing_key=routing_key, source=get_exchange_name(exchange), timeout=timeout, ) async def publish( self, message: AbstractMessage, routing_key: str, *, mandatory: bool = True, immediate: bool = False, timeout: TimeoutType = None ) -> Optional[aiormq.abc.ConfirmationFrameType]: """ Publish the message to the queue. `aio-pika` uses `publisher confirms`_ extension for message delivery. .. _publisher confirms: https://www.rabbitmq.com/confirms.html """ log.debug( "Publishing message with routing key %r via exchange %r: %r", routing_key, self, message, ) if self.internal: # Caught on the client side to prevent channel closure raise ValueError( f"Can not publish to internal exchange: '{self.name}'!", ) return await self.channel.basic_publish( exchange=self.name, routing_key=routing_key, body=message.body, properties=message.properties, mandatory=mandatory, immediate=immediate, timeout=timeout, ) async def delete( self, if_unused: bool = False, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.DeleteOk: """ Delete the queue :param timeout: operation timeout :param if_unused: perform deletion when queue has no bindings. """ log.info("Deleting %r", self) return await self.channel.exchange_delete( self.name, if_unused=if_unused, timeout=timeout, ) __all__ = ("Exchange", "ExchangeType", "ExchangeParamType") aio-pika-8.2.5/aio_pika/log.py000066400000000000000000000003661433544061600161420ustar00rootroot00000000000000import logging logger: logging.Logger = logging.getLogger("aio_pika") def get_logger(name: str) -> logging.Logger: package, module = name.split(".", 1) if package == logger.name: name = module return logger.getChild(name) aio-pika-8.2.5/aio_pika/message.py000066400000000000000000000533601433544061600170070ustar00rootroot00000000000000import json import time from datetime import datetime, timedelta from functools import singledispatch from pprint import pformat from types import TracebackType from typing import ( Any, Callable, Dict, Iterable, Iterator, List, MutableMapping, Optional, Type, TypeVar, Union, ) import aiormq from aiormq.abc import DeliveredMessage, FieldTable from pamqp.common import FieldValue from .abc import ( MILLISECONDS, ZERO_TIME, AbstractChannel, AbstractIncomingMessage, AbstractMessage, AbstractProcessContext, DateType, DeliveryMode, HeadersPythonValues, HeadersType, NoneType, ) from .exceptions import MessageProcessError from .log import get_logger log = get_logger(__name__) def to_milliseconds(seconds: Union[float, int]) -> int: return int(seconds * MILLISECONDS) @singledispatch def encode_expiration(value: Any) -> Optional[str]: raise ValueError("Invalid timestamp type: %r" % type(value), value) @encode_expiration.register(datetime) def encode_expiration_datetime(value: datetime) -> str: now = datetime.now(tz=value.tzinfo) return str(to_milliseconds((value - now).total_seconds())) @encode_expiration.register(int) @encode_expiration.register(float) def encode_expiration_number(value: Union[int, float]) -> str: return str(to_milliseconds(value)) @encode_expiration.register(timedelta) def encode_expiration_timedelta(value: timedelta) -> str: return str(int(value.total_seconds() * MILLISECONDS)) @encode_expiration.register(NoneType) # type: ignore def encode_expiration_none(_: Any) -> None: return None @singledispatch def decode_expiration(t: Any) -> Optional[float]: raise ValueError("Invalid expiration type: %r" % type(t), t) @decode_expiration.register(time.struct_time) def decode_expiration_struct_time(t: time.struct_time) -> float: return (datetime(*t[:7]) - ZERO_TIME).total_seconds() @decode_expiration.register(str) def decode_expiration_str(t: str) -> float: return float(t) @singledispatch def encode_timestamp(value: Any) -> Optional[datetime]: raise ValueError("Invalid timestamp type: %r" % type(value), value) @encode_timestamp.register(time.struct_time) def encode_timestamp_struct_time(value: time.struct_time) -> datetime: return datetime(*value[:6]) @encode_timestamp.register(datetime) def encode_timestamp_datetime(value: datetime) -> datetime: return value @encode_timestamp.register(float) @encode_timestamp.register(int) def encode_timestamp_number(value: Union[int, float]) -> datetime: return datetime.utcfromtimestamp(value) @encode_timestamp.register(timedelta) def encode_timestamp_timedelta(value: timedelta) -> datetime: return datetime.utcnow() + value @encode_timestamp.register(NoneType) # type: ignore def encode_timestamp_none(_: Any) -> None: return None @singledispatch def decode_timestamp(value: Any) -> Optional[datetime]: raise ValueError("Invalid timestamp type: %r" % type(value), value) @decode_timestamp.register(datetime) def decode_timestamp_datetime(value: datetime) -> datetime: return value @decode_timestamp.register(float) @decode_timestamp.register(int) def decode_timestamp_number(value: Union[float, int]) -> datetime: return datetime.utcfromtimestamp(value) @decode_timestamp.register(time.struct_time) def decode_timestamp_struct_time(value: time.struct_time) -> datetime: return datetime(*value[:6]) @decode_timestamp.register(NoneType) # type: ignore def decode_timestamp_none(_: Any) -> None: return None V = TypeVar("V") D = TypeVar("D") T = TypeVar("T") def optional( value: V, func: Union[Callable[[V], T], Type[T]], default: D = None, ) -> Union[T, D]: return func(value) if value else default # type: ignore class HeaderProxy(MutableMapping): def __init__(self, headers: FieldTable): self._headers: FieldTable = headers self._cache: Dict[str, Any] = {} def __getitem__(self, k: str) -> FieldValue: if k not in self._headers: raise KeyError(k) if k not in self._cache: value = self._headers[k] if isinstance(value, bytes): self._cache[k] = value.decode() else: self._cache[k] = value return self._cache[k] def __delitem__(self, key: str) -> None: del self._headers[key] def __setitem__(self, key: str, value: FieldValue) -> None: self._headers[key] = header_converter(value) self._cache.pop(key, None) def __len__(self) -> int: return len(self._headers) def __iter__(self) -> Iterator[str]: yield from self._headers @singledispatch def header_converter(value: Any) -> FieldValue: return bytearray( json.dumps( value, separators=(",", ":"), ensure_ascii=False, default=repr, ).encode(), ) @header_converter.register(NoneType) # type: ignore @header_converter.register(bytearray) @header_converter.register(str) @header_converter.register(datetime) @header_converter.register(time.struct_time) @header_converter.register(list) @header_converter.register(dict) @header_converter.register(int) def header_converter_native(v: T) -> T: return v @header_converter.register(bytes) def header_converter_bytes(v: bytes) -> bytearray: return bytearray(v) @header_converter.register(set) @header_converter.register(tuple) @header_converter.register(frozenset) def header_converter_iterable(v: Iterable[T]) -> List[T]: return header_converter(list(v)) # type: ignore def format_headers(d: Optional[HeadersType]) -> FieldTable: ret: FieldTable = {} if not d: return ret for key, value in d.items(): ret[key] = header_converter(value) return ret class Message(AbstractMessage): """ AMQP message abstraction """ __slots__ = ( "app_id", "body", "body_size", "content_encoding", "content_type", "correlation_id", "delivery_mode", "expiration", "_headers", "headers_raw", "message_id", "priority", "reply_to", "timestamp", "type", "user_id", "__lock", ) def __init__( self, body: bytes, *, headers: Optional[HeadersType] = None, content_type: Optional[str] = None, content_encoding: Optional[str] = None, delivery_mode: Union[DeliveryMode, int, None] = None, priority: Optional[int] = None, correlation_id: Optional[str] = None, reply_to: Optional[str] = None, expiration: Optional[DateType] = None, message_id: Optional[str] = None, timestamp: Optional[DateType] = None, type: Optional[str] = None, user_id: Optional[str] = None, app_id: Optional[str] = None ): """ Creates a new instance of Message :param body: message body :param headers: message headers :param content_type: content type :param content_encoding: content encoding :param delivery_mode: delivery mode :param priority: priority :param correlation_id: correlation id :param reply_to: reply to :param expiration: expiration in seconds (or datetime or timedelta) :param message_id: message id :param timestamp: timestamp :param type: type :param user_id: user id :param app_id: app id """ self.__lock = False self.body = body if isinstance(body, bytes) else bytes(body) self.body_size = len(self.body) if self.body else 0 self.headers_raw: FieldTable = format_headers(headers) self._headers: HeadersType = HeaderProxy(self.headers_raw) self.content_type = content_type self.content_encoding = content_encoding self.delivery_mode: DeliveryMode = DeliveryMode( optional( delivery_mode, int, DeliveryMode.NOT_PERSISTENT, ), ) self.priority = optional(priority, int, 0) self.correlation_id = optional(correlation_id, str) self.reply_to = optional(reply_to, str) self.expiration = expiration self.message_id = optional(message_id, str) self.timestamp = encode_timestamp(timestamp) self.type = optional(type, str) self.user_id = optional(user_id, str) self.app_id = optional(app_id, str) @property def headers(self) -> HeadersType: return self._headers @headers.setter def headers(self, value: Dict[str, HeadersPythonValues]) -> None: self.headers_raw = format_headers(value) @staticmethod def _as_bytes(value: Any) -> bytes: if isinstance(value, bytes): return value elif isinstance(value, str): return value.encode() elif value is None: return b"" else: return str(value).encode() def info(self) -> dict: """ Create a dict with message attributes :: { "body_size": 100, "headers": {}, "content_type": "text/plain", "content_encoding": "", "delivery_mode": DeliveryMode.NOT_PERSISTENT, "priority": 0, "correlation_id": "", "reply_to": "", "expiration": "", "message_id": "", "timestamp": "", "type": "", "user_id": "", "app_id": "", } """ return { "body_size": self.body_size, "headers": self.headers_raw, "content_type": self.content_type, "content_encoding": self.content_encoding, "delivery_mode": self.delivery_mode, "priority": self.priority, "correlation_id": self.correlation_id, "reply_to": self.reply_to, "expiration": self.expiration, "message_id": self.message_id, "timestamp": decode_timestamp(self.timestamp), "type": str(self.type), "user_id": self.user_id, "app_id": self.app_id, } @property def locked(self) -> bool: """ is message locked :return: :class:`bool` """ return bool(self.__lock) @property def properties(self) -> aiormq.spec.Basic.Properties: """ Build :class:`aiormq.spec.Basic.Properties` object """ return aiormq.spec.Basic.Properties( content_type=self.content_type, content_encoding=self.content_encoding, headers=self.headers_raw, delivery_mode=self.delivery_mode, priority=self.priority, correlation_id=self.correlation_id, reply_to=self.reply_to, expiration=encode_expiration(self.expiration), message_id=self.message_id, timestamp=self.timestamp, message_type=self.type, user_id=self.user_id, app_id=self.app_id, ) def __repr__(self) -> str: return "{name}:{repr}".format( name=self.__class__.__name__, repr=pformat(self.info()), ) def __setattr__(self, key: str, value: FieldValue) -> None: if not key.startswith("_") and self.locked: raise ValueError("Message is locked") return super().__setattr__(key, value) def __iter__(self) -> Iterator[int]: return iter(self.body) def lock(self) -> None: """ Set lock flag to `True`""" self.__lock = True def __copy__(self) -> "Message": return Message( body=self.body, headers=self.headers_raw, content_encoding=self.content_encoding, content_type=self.content_type, delivery_mode=self.delivery_mode, priority=self.priority, correlation_id=self.correlation_id, reply_to=self.reply_to, expiration=self.expiration, message_id=self.message_id, timestamp=self.timestamp, type=self.type, user_id=self.user_id, app_id=self.app_id, ) class IncomingMessage(Message, AbstractIncomingMessage): """ Incoming message is seems like Message but has additional methods for message acknowledgement. Depending on the acknowledgement mode used, RabbitMQ can consider a message to be successfully delivered either immediately after it is sent out (written to a TCP socket) or when an explicit ("manual") client acknowledgement is received. Manually sent acknowledgements can be positive or negative and use one of the following protocol methods: * basic.ack is used for positive acknowledgements * basic.nack is used for negative acknowledgements (note: this is a RabbitMQ extension to AMQP 0-9-1) * basic.reject is used for negative acknowledgements but has one limitations compared to basic.nack Positive acknowledgements simply instruct RabbitMQ to record a message as delivered. Negative acknowledgements with basic.reject have the same effect. The difference is primarily in the semantics: positive acknowledgements assume a message was successfully processed while their negative counterpart suggests that a delivery wasn't processed but still should be deleted. """ __slots__ = ( "_loop", "__channel", "cluster_id", "consumer_tag", "delivery_tag", "exchange", "routing_key", "redelivered", "__no_ack", "__processed", "message_count", ) def __init__(self, message: DeliveredMessage, no_ack: bool = False): """ Create an instance of :class:`IncomingMessage` """ self.__channel = message.channel self.__no_ack = no_ack self.__processed = False expiration = None if message.header.properties.expiration: expiration = decode_expiration( message.header.properties.expiration, ) super().__init__( body=message.body, content_type=message.header.properties.content_type, content_encoding=message.header.properties.content_encoding, headers=message.header.properties.headers, delivery_mode=message.header.properties.delivery_mode, priority=message.header.properties.priority, correlation_id=message.header.properties.correlation_id, reply_to=message.header.properties.reply_to, expiration=expiration / 1000.0 if expiration else None, message_id=message.header.properties.message_id, timestamp=decode_timestamp(message.header.properties.timestamp), type=message.header.properties.message_type, user_id=message.header.properties.user_id, app_id=message.header.properties.app_id, ) self.cluster_id = message.header.properties.cluster_id self.consumer_tag = message.consumer_tag self.delivery_tag = message.delivery_tag self.exchange = message.exchange self.message_count = message.message_count self.redelivered = message.redelivered self.routing_key = message.routing_key if no_ack or not self.delivery_tag: self.lock() self.__processed = True @property def channel(self) -> aiormq.abc.AbstractChannel: return self.__channel def process( self, requeue: bool = False, reject_on_redelivered: bool = False, ignore_processed: bool = False, ) -> AbstractProcessContext: """ Context manager for processing the message >>> async def on_message_received(message: IncomingMessage): ... async with message.process(): ... # When exception will be raised ... # the message will be rejected ... print(message.body) Example with ignore_processed=True >>> async def on_message_received(message: IncomingMessage): ... async with message.process(ignore_processed=True): ... # Now (with ignore_processed=True) you may reject ... # (or ack) message manually too ... if True: # some reasonable condition here ... await message.reject() ... print(message.body) :param requeue: Requeue message when exception. :param reject_on_redelivered: When True message will be rejected only when message was redelivered. :param ignore_processed: Do nothing if message already processed """ return ProcessContext( self, requeue=requeue, reject_on_redelivered=reject_on_redelivered, ignore_processed=ignore_processed, ) async def ack(self, multiple: bool = False) -> None: """ Send basic.ack is used for positive acknowledgements .. note:: This method looks like a blocking-method, but actually it just sends bytes to the socket and doesn't require any responses from the broker. :param multiple: If set to True, the message's delivery tag is treated as "up to and including", so that multiple messages can be acknowledged with a single method. If set to False, the ack refers to a single message. :return: None """ if self.__no_ack: raise TypeError('Can\'t ack message with "no_ack" flag') if self.__processed: raise MessageProcessError("Message already processed", self) if self.delivery_tag is not None: await self.__channel.basic_ack( delivery_tag=self.delivery_tag, multiple=multiple, ) self.__processed = True if not self.locked: self.lock() async def reject(self, requeue: bool = False) -> None: """ When `requeue=True` the message will be returned to queue. Otherwise message will be dropped. .. note:: This method looks like a blocking-method, but actually it just sends bytes to the socket and doesn't require any responses from the broker. :param requeue: bool """ if self.__no_ack: raise TypeError('This message has "no_ack" flag.') if self.__processed: raise MessageProcessError("Message already processed", self) if self.delivery_tag is not None: await self.__channel.basic_reject( delivery_tag=self.delivery_tag, requeue=requeue, ) self.__processed = True if not self.locked: self.lock() async def nack( self, multiple: bool = False, requeue: bool = True, ) -> None: if not self.channel.connection.basic_nack: raise RuntimeError("Method not supported on server") if self.__no_ack: raise TypeError('Can\'t nack message with "no_ack" flag') if self.__processed: raise MessageProcessError("Message already processed", self) if self.delivery_tag is not None: await self.__channel.basic_nack( delivery_tag=self.delivery_tag, multiple=multiple, requeue=requeue, ) self.__processed = True if not self.locked: self.lock() def info(self) -> dict: """ Method returns dict representation of the message """ info = super().info() info["cluster_id"] = self.cluster_id info["consumer_tag"] = self.consumer_tag info["delivery_tag"] = self.delivery_tag info["exchange"] = self.exchange info["redelivered"] = self.redelivered info["routing_key"] = self.routing_key return info @property def processed(self) -> bool: return self.__processed class ReturnedMessage(IncomingMessage): pass ReturnCallback = Callable[[AbstractChannel, ReturnedMessage], Any] class ProcessContext(AbstractProcessContext): def __init__( self, message: IncomingMessage, *, requeue: bool, reject_on_redelivered: bool, ignore_processed: bool ): self.message = message self.requeue = requeue self.reject_on_redelivered = reject_on_redelivered self.ignore_processed = ignore_processed async def __aenter__(self) -> IncomingMessage: return self.message async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: if not exc_type: if not self.ignore_processed or not self.message.processed: await self.message.ack() return if not self.ignore_processed or not self.message.processed: if self.reject_on_redelivered and self.message.redelivered: if not self.message.channel.is_closed: log.info( "Message %r was redelivered and will be rejected", self.message, ) await self.message.reject(requeue=False) return log.warning( "Message %r was redelivered and reject is not sent " "since channel is closed", self.message, ) else: if not self.message.channel.is_closed: await self.message.reject(requeue=self.requeue) return log.warning("Reject is not sent since channel is closed") __all__ = "Message", "IncomingMessage", "ReturnedMessage", aio-pika-8.2.5/aio_pika/patterns/000077500000000000000000000000001433544061600166425ustar00rootroot00000000000000aio-pika-8.2.5/aio_pika/patterns/__init__.py000066400000000000000000000003511433544061600207520ustar00rootroot00000000000000from .master import JsonMaster, Master, NackMessage, RejectMessage, Worker from .rpc import RPC, JsonRPC __all__ = ( "Master", "NackMessage", "RejectMessage", "RPC", "Worker", "JsonMaster", "JsonRPC", ) aio-pika-8.2.5/aio_pika/patterns/base.py000066400000000000000000000025011433544061600201240ustar00rootroot00000000000000import pickle from typing import Any, Callable class Method: __slots__ = ( "name", "func", ) def __init__(self, name: str, func: Callable[..., Any]): self.name = name self.func = func def __getattr__(self, item: str) -> "Method": return Method(".".join((self.name, item)), func=self.func) def __call__(self, **kwargs: Any) -> Any: return self.func(self.name, kwargs=kwargs) class Proxy: __slots__ = ("func",) def __init__(self, func: Callable[..., Any]): self.func = func def __getattr__(self, item: str) -> Method: return Method(item, self.func) class Base: SERIALIZER = pickle CONTENT_TYPE = "application/python-pickle" def serialize(self, data: Any) -> bytes: """ Serialize data to the bytes. Uses `pickle` by default. You should overlap this method when you want to change serializer :param data: Data which will be serialized """ return self.SERIALIZER.dumps(data) def deserialize(self, data: bytes) -> Any: """ Deserialize data from bytes. Uses `pickle` by default. You should overlap this method when you want to change serializer :param data: Data which will be deserialized """ return self.SERIALIZER.loads(data) aio-pika-8.2.5/aio_pika/patterns/master.py000066400000000000000000000141551433544061600205150ustar00rootroot00000000000000import asyncio import gzip import json import logging from functools import partial from types import MappingProxyType from typing import Any, Awaitable, Callable, Mapping, Optional, TypeVar import aiormq from aiormq.tools import awaitable from aio_pika.abc import ( AbstractChannel, AbstractExchange, AbstractIncomingMessage, AbstractQueue, ConsumerTag, DeliveryMode, ) from aio_pika.message import Message, ReturnedMessage from ..tools import create_task from .base import Base, Proxy log = logging.getLogger(__name__) T = TypeVar("T") class MessageProcessingError(Exception): pass class NackMessage(MessageProcessingError): def __init__(self, requeue: bool = False): self.requeue = requeue class RejectMessage(MessageProcessingError): def __init__(self, requeue: bool = False): self.requeue = requeue class Worker: __slots__ = ( "queue", "consumer_tag", "loop", ) def __init__( self, queue: AbstractQueue, consumer_tag: ConsumerTag, loop: asyncio.AbstractEventLoop, ): self.queue = queue self.consumer_tag = consumer_tag self.loop = loop def close(self) -> Awaitable[None]: """ Cancel subscription to the channel :return: :class:`asyncio.Task` """ async def closer() -> None: await self.queue.cancel(self.consumer_tag) return create_task(closer) class Master(Base): __slots__ = ( "channel", "loop", "proxy", ) DELIVERY_MODE = DeliveryMode.PERSISTENT __doc__ = """ Implements Master/Worker pattern. Usage example: `worker.py` :: master = Master(channel) worker = await master.create_worker('test_worker', lambda x: print(x)) `master.py` :: master = Master(channel) await master.proxy.test_worker('foo') """ def __init__( self, channel: AbstractChannel, requeue: bool = True, reject_on_redelivered: bool = False, ): """ Creates a new :class:`Master` instance. :param channel: Initialized instance of :class:`aio_pika.Channel` """ self.channel: AbstractChannel = channel self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() self.proxy = Proxy(self.create_task) self.channel.return_callbacks.add(self.on_message_returned) self._requeue = requeue self._reject_on_redelivered = reject_on_redelivered @property def exchange(self) -> AbstractExchange: return self.channel.default_exchange @staticmethod def on_message_returned( channel: AbstractChannel, message: ReturnedMessage, ) -> None: log.warning( "Message returned. Probably destination queue does not exists: %r", message, ) def serialize(self, data: Any) -> bytes: """ Serialize data to the bytes. Uses `pickle` by default. You should overlap this method when you want to change serializer :param data: Data which will be serialized :returns: bytes """ return super().serialize(data) def deserialize(self, data: bytes) -> Any: """ Deserialize data from bytes. Uses `pickle` by default. You should overlap this method when you want to change serializer :param data: Data which will be deserialized :returns: :class:`Any` """ return super().deserialize(data) @classmethod async def execute( cls, func: Callable[..., Awaitable[T]], kwargs: Any, ) -> T: kwargs = kwargs or {} if not isinstance(kwargs, dict): raise RejectMessage(requeue=False) return await func(**kwargs) async def on_message( self, func: Callable[..., Any], message: AbstractIncomingMessage, ) -> None: async with message.process( requeue=self._requeue, reject_on_redelivered=self._reject_on_redelivered, ignore_processed=True, ): try: await self.execute(func, self.deserialize(message.body)) except RejectMessage as e: await message.reject(requeue=e.requeue) except NackMessage as e: await message.nack(requeue=e.requeue) async def create_queue( self, channel_name: str, **kwargs: Any ) -> AbstractQueue: return await self.channel.declare_queue(channel_name, **kwargs) async def create_worker( self, channel_name: str, func: Callable[..., Any], **kwargs: Any ) -> Worker: """ Creates a new :class:`Worker` instance. """ queue = await self.create_queue(channel_name, **kwargs) if hasattr(func, "_is_coroutine"): fn = func else: fn = awaitable(func) consumer_tag = await queue.consume(partial(self.on_message, fn)) return Worker(queue, consumer_tag, self.loop) async def create_task( self, channel_name: str, kwargs: Mapping[str, Any] = MappingProxyType({}), **message_kwargs: Any ) -> Optional[aiormq.abc.ConfirmationFrameType]: """ Creates a new task for the worker """ message = Message( body=self.serialize(kwargs), content_type=self.CONTENT_TYPE, delivery_mode=self.DELIVERY_MODE, **message_kwargs ) return await self.exchange.publish( message, channel_name, mandatory=True, ) class JsonMaster(Master): SERIALIZER = json CONTENT_TYPE = "application/json" def serialize(self, data: Any) -> bytes: return self.SERIALIZER.dumps(data, ensure_ascii=False).encode() class CompressedJsonMaster(Master): SERIALIZER = json CONTENT_TYPE = "application/json;compression=gzip" COMPRESS_LEVEL = 6 def serialize(self, data: Any) -> bytes: return gzip.compress( self.SERIALIZER.dumps(data, ensure_ascii=False).encode(), compresslevel=self.COMPRESS_LEVEL, ) def deserialize(self, data: bytes) -> Any: return self.SERIALIZER.loads(gzip.decompress(data)) aio-pika-8.2.5/aio_pika/patterns/rpc.py000066400000000000000000000333511433544061600200050ustar00rootroot00000000000000import asyncio import json import logging import time import uuid from enum import Enum from functools import partial from typing import Any, Callable, Dict, Optional, Tuple, TypeVar from aiormq.abc import ExceptionType from aiormq.tools import awaitable from aio_pika.abc import ( AbstractChannel, AbstractExchange, AbstractIncomingMessage, AbstractQueue, ConsumerTag, DeliveryMode, ) from aio_pika.exceptions import MessageProcessError from aio_pika.exchange import ExchangeType from aio_pika.message import IncomingMessage, Message, ReturnedMessage from .base import Base, Proxy log = logging.getLogger(__name__) T = TypeVar("T") CallbackType = Callable[..., T] class RPCException(RuntimeError): pass class RPCMessageType(str, Enum): ERROR = "error" RESULT = "result" CALL = "call" # This needed only for migration from 6.x to 7.x # TODO: Remove this in 8.x release RPCMessageTypes = RPCMessageType # noqa class RPC(Base): __slots__ = ( "channel", "loop", "proxy", "result_queue", "result_consumer_tag", "routes", "consumer_tags", "dlx_exchange", ) DLX_NAME = "rpc.dlx" DELIVERY_MODE = DeliveryMode.NOT_PERSISTENT __doc__ = """ Remote Procedure Call helper. Create an instance :: rpc = await RPC.create(channel) Registering python function :: # RPC instance passes only keyword arguments def multiply(*, x, y): return x * y await rpc.register("multiply", multiply) Call function through proxy :: assert await rpc.proxy.multiply(x=2, y=3) == 6 Call function explicit :: assert await rpc.call('multiply', dict(x=2, y=3)) == 6 """ result_queue: AbstractQueue result_consumer_tag: ConsumerTag dlx_exchange: AbstractExchange def __init__(self, channel: AbstractChannel): self.channel = channel self.loop = asyncio.get_event_loop() self.proxy = Proxy(self.call) self.futures: Dict[str, asyncio.Future] = {} self.routes: Dict[str, Callable[..., Any]] = {} self.queues: Dict[Callable[..., Any], AbstractQueue] = {} self.consumer_tags: Dict[Callable[..., Any], ConsumerTag] = {} def __remove_future(self, future: asyncio.Future) -> None: log.debug("Remove done future %r", future) self.futures.pop(str(id(future)), None) def create_future(self) -> Tuple[asyncio.Future, str]: future = self.loop.create_future() log.debug("Create future for RPC call") correlation_id = str(uuid.uuid4()) self.futures[correlation_id] = future future.add_done_callback(self.__remove_future) return future, correlation_id async def close(self) -> None: if not hasattr(self, "result_queue"): log.warning("RPC already closed") return log.debug("Cancelling listening %r", self.result_queue) await self.result_queue.cancel(self.result_consumer_tag) del self.result_consumer_tag log.debug("Unbinding %r", self.result_queue) await self.result_queue.unbind( self.dlx_exchange, "", arguments={"From": self.result_queue.name, "x-match": "any"}, ) log.debug("Cancelling undone futures %r", self.futures) for future in self.futures.values(): if future.done(): continue future.set_exception(asyncio.CancelledError) log.debug("Deleting %r", self.result_queue) await self.result_queue.delete() del self.result_queue del self.dlx_exchange async def initialize( self, auto_delete: bool = True, durable: bool = False, **kwargs: Any ) -> None: if hasattr(self, "result_queue"): return self.result_queue = await self.channel.declare_queue( None, auto_delete=auto_delete, durable=durable, **kwargs ) self.dlx_exchange = await self.channel.declare_exchange( self.DLX_NAME, type=ExchangeType.HEADERS, auto_delete=True, ) await self.result_queue.bind( self.dlx_exchange, "", arguments={"From": self.result_queue.name, "x-match": "any"}, ) self.result_consumer_tag = await self.result_queue.consume( self.on_result_message, exclusive=True, no_ack=True, ) self.channel.close_callbacks.add(self.on_close) self.channel.return_callbacks.add(self.on_message_returned) def on_close( self, channel: AbstractChannel, exc: Optional[ExceptionType] = None, ) -> None: log.debug("Closing RPC futures because %r", exc) for future in self.futures.values(): if future.done(): continue future.set_exception(exc or Exception) @classmethod async def create(cls, channel: AbstractChannel, **kwargs: Any) -> "RPC": """ Creates a new instance of :class:`aio_pika.patterns.RPC`. You should use this method instead of :func:`__init__`, because :func:`create` returns coroutine and makes async initialize :param channel: initialized instance of :class:`aio_pika.Channel` :returns: :class:`RPC` """ rpc = cls(channel) await rpc.initialize(**kwargs) return rpc def on_message_returned( self, channel: AbstractChannel, message: ReturnedMessage, ) -> None: if message.correlation_id is None: log.warning( "Message without correlation_id was returned: %r", message, ) return future = self.futures.pop(message.correlation_id, None) if not future or future.done(): log.warning("Unknown message was returned: %r", message) return future.set_exception( MessageProcessError("Message has been returned", message), ) async def on_result_message(self, message: AbstractIncomingMessage) -> None: if message.correlation_id is None: log.warning( "Message without correlation_id was received: %r", message, ) return future = self.futures.pop(message.correlation_id, None) if future is None: log.warning("Unknown message: %r", message) return try: payload = await self.deserialize_message(message) except Exception as e: log.error("Failed to deserialize response on message: %r", message) future.set_exception(e) return if message.type == RPCMessageType.RESULT.value: future.set_result(payload) elif message.type == RPCMessageType.ERROR.value: if not isinstance(payload, Exception): payload = RPCException("Wrapped non-exception object", payload) future.set_exception(payload) elif message.type == RPCMessageType.CALL.value: future.set_exception( asyncio.TimeoutError("Message timed-out", message), ) else: future.set_exception( RuntimeError("Unknown message type %r" % message.type), ) async def on_call_message( self, method_name: str, message: IncomingMessage, ) -> None: if method_name not in self.routes: log.warning("Method %r not registered in %r", method_name, self) return try: payload = await self.deserialize_message(message) func = self.routes[method_name] result: Any = await self.execute(func, payload) message_type = RPCMessageType.RESULT except Exception as e: result = self.serialize_exception(e) message_type = RPCMessageType.ERROR if not message.reply_to: log.info( 'RPC message without "reply_to" header %r call result ' "will be lost", message, ) await message.ack() return result_message = await self.serialize_message( payload=result, message_type=message_type, correlation_id=message.correlation_id, delivery_mode=message.delivery_mode, ) try: await self.channel.default_exchange.publish( result_message, message.reply_to, mandatory=False, ) except Exception: log.exception("Failed to send reply %r", result_message) await message.reject(requeue=False) return if message_type == RPCMessageType.ERROR.value: await message.ack() return await message.ack() def serialize_exception(self, exception: Exception) -> Any: """ Make python exception serializable """ return exception async def execute(self, func: CallbackType, payload: Dict[str, Any]) -> T: """ Executes rpc call. Might be overlapped. """ return await func(**payload) async def deserialize_message( self, message: AbstractIncomingMessage, ) -> Any: return self.deserialize(message.body) async def serialize_message( self, payload: Any, message_type: RPCMessageType, correlation_id: Optional[str], delivery_mode: DeliveryMode, **kwargs: Any ) -> Message: return Message( self.serialize(payload), content_type=self.CONTENT_TYPE, correlation_id=correlation_id, delivery_mode=delivery_mode, timestamp=time.time(), type=message_type.value, **kwargs ) async def call( self, method_name: str, kwargs: Optional[Dict[str, Any]] = None, *, expiration: Optional[int] = None, priority: int = 5, delivery_mode: DeliveryMode = DELIVERY_MODE ) -> Any: """ Call remote method and awaiting result. :param method_name: Name of method :param kwargs: Methos kwargs :param expiration: If not `None` messages which staying in queue longer will be returned and :class:`asyncio.TimeoutError` will be raised. :param priority: Message priority :param delivery_mode: Call message delivery mode :raises asyncio.TimeoutError: when message expired :raises CancelledError: when called :func:`RPC.cancel` :raises RuntimeError: internal error """ future, correlation_id = self.create_future() message = await self.serialize_message( payload=kwargs or {}, message_type=RPCMessageType.CALL, correlation_id=correlation_id, delivery_mode=delivery_mode, reply_to=self.result_queue.name, headers={"From": self.result_queue.name}, priority=priority, ) if expiration is not None: message.expiration = expiration log.debug("Publishing calls for %s(%r)", method_name, kwargs) await self.channel.default_exchange.publish( message, routing_key=method_name, mandatory=True, ) log.debug("Waiting RPC result for %s(%r)", method_name, kwargs) return await future async def register( self, method_name: str, func: CallbackType, **kwargs: Any ) -> Any: """ Method creates a queue with name which equal of `method_name` argument. Then subscribes this queue. :param method_name: Method name :param func: target function. Function **MUST** accept only keyword arguments. :param kwargs: arguments which will be passed to `queue_declare` :raises RuntimeError: Function already registered in this :class:`RPC` instance or method_name already used. """ arguments = kwargs.pop("arguments", {}) arguments.update({"x-dead-letter-exchange": self.DLX_NAME}) kwargs["arguments"] = arguments queue = await self.channel.declare_queue(method_name, **kwargs) if func in self.consumer_tags: raise RuntimeError("Function already registered") if method_name in self.routes: raise RuntimeError( "Method name already used for %r" % self.routes[method_name], ) self.consumer_tags[func] = await queue.consume( partial(self.on_call_message, method_name), ) self.routes[method_name] = awaitable(func) self.queues[func] = queue async def unregister(self, func: CallbackType) -> None: """ Cancels subscription to the method-queue. :param func: Function """ if func not in self.consumer_tags: return consumer_tag = self.consumer_tags.pop(func) queue = self.queues.pop(func) await queue.cancel(consumer_tag) self.routes.pop(queue.name) class JsonRPCError(RuntimeError): pass class JsonRPC(RPC): SERIALIZER = json CONTENT_TYPE = "application/json" def serialize(self, data: Any) -> bytes: return self.SERIALIZER.dumps( data, ensure_ascii=False, default=repr, ).encode() def serialize_exception(self, exception: Exception) -> Any: return { "error": { "type": exception.__class__.__name__, "message": repr(exception), "args": exception.args, }, } async def deserialize_message( self, message: AbstractIncomingMessage, ) -> Any: payload = await super().deserialize_message(message) if message.type == RPCMessageType.ERROR: payload = JsonRPCError("RPC exception", payload) return payload __all__ = ( "CallbackType", "JsonRPC", "RPC", "RPCException", "RPCMessageType", ) aio-pika-8.2.5/aio_pika/pool.py000066400000000000000000000104001433544061600163200ustar00rootroot00000000000000import abc import asyncio from types import TracebackType from typing import ( Any, AsyncContextManager, Awaitable, Callable, Coroutine, Generic, Optional, Set, Tuple, Type, TypeVar, Union, ) from aiormq.tools import awaitable from aio_pika.log import get_logger from aio_pika.tools import create_task log = get_logger(__name__) class PoolInstance(abc.ABC): @abc.abstractmethod def close(self) -> Awaitable[None]: raise NotImplementedError T = TypeVar("T") ConstructorType = Callable[ ..., Union[ Awaitable[PoolInstance], PoolInstance, Coroutine[Any, Any, PoolInstance], ], ] class PoolInvalidStateError(RuntimeError): pass class Pool(Generic[T]): __slots__ = ( "loop", "__max_size", "__items", "__constructor", "__created", "__lock", "__constructor_args", "__item_set", "__closed", ) def __init__( self, constructor: ConstructorType, *args: Any, max_size: Optional[int] = None, loop: Optional[asyncio.AbstractEventLoop] = None, ): self.loop = loop or asyncio.get_event_loop() self.__closed = False self.__constructor: Callable[..., Awaitable[Any]] = awaitable( constructor, ) self.__constructor_args: Tuple[Any, ...] = args or () self.__created: int = 0 self.__item_set: Set[PoolInstance] = set() self.__items: asyncio.Queue = asyncio.Queue() self.__lock: asyncio.Lock = asyncio.Lock() self.__max_size: Optional[int] = max_size @property def is_closed(self) -> bool: return self.__closed def acquire(self) -> "PoolItemContextManager[T]": if self.__closed: raise PoolInvalidStateError("acquire operation on closed pool") return PoolItemContextManager[T](self) @property def _has_released(self) -> bool: return self.__items.qsize() > 0 @property def _is_overflow(self) -> bool: if self.__max_size: return self.__created >= self.__max_size or self._has_released return self._has_released async def _create_item(self) -> T: if self.__closed: raise PoolInvalidStateError("create item operation on closed pool") async with self.__lock: if self._is_overflow: return await self.__items.get() log.debug("Creating a new instance of %r", self.__constructor) item = await self.__constructor(*self.__constructor_args) self.__created += 1 self.__item_set.add(item) return item async def _get(self) -> T: if self.__closed: raise PoolInvalidStateError("get operation on closed pool") if self._is_overflow: return await self.__items.get() return await self._create_item() def put(self, item: T) -> None: if self.__closed: raise PoolInvalidStateError("put operation on closed pool") self.__items.put_nowait(item) async def close(self) -> None: async with self.__lock: self.__closed = True tasks = [] for item in self.__item_set: tasks.append(create_task(item.close)) if tasks: await asyncio.gather(*tasks, return_exceptions=True) async def __aenter__(self) -> "Pool": return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: if self.__closed: return await asyncio.ensure_future(self.close()) class PoolItemContextManager(Generic[T], AsyncContextManager): __slots__ = "pool", "item" def __init__(self, pool: Pool): self.pool = pool self.item: T async def __aenter__(self) -> T: # noinspection PyProtectedMember self.item = await self.pool._get() return self.item async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: if self.item is not None: self.pool.put(self.item) aio-pika-8.2.5/aio_pika/py.typed000066400000000000000000000000011433544061600164700ustar00rootroot00000000000000 aio-pika-8.2.5/aio_pika/queue.py000066400000000000000000000353611433544061600165100ustar00rootroot00000000000000import asyncio from functools import partial from types import TracebackType from typing import Any, Callable, Optional, Type import aiormq from aiormq.abc import DeliveredMessage from pamqp.common import Arguments from .abc import ( AbstractIncomingMessage, AbstractQueue, AbstractQueueIterator, ConsumerTag, TimeoutType, get_exchange_name, ) from .exceptions import QueueEmpty from .exchange import ExchangeParamType from .log import get_logger from .message import IncomingMessage from .tools import CallbackCollection, create_task log = get_logger(__name__) async def consumer( callback: Callable[[AbstractIncomingMessage], Any], msg: DeliveredMessage, *, no_ack: bool ) -> Any: message = IncomingMessage(msg, no_ack=no_ack) return await create_task(callback, message) class Queue(AbstractQueue): """ AMQP queue abstraction """ def __init__( self, channel: aiormq.abc.AbstractChannel, name: Optional[str], durable: bool, exclusive: bool, auto_delete: bool, arguments: Arguments, passive: bool = False, ): self.__get_lock = asyncio.Lock() self.close_callbacks = CallbackCollection(self) self.channel = channel self.name = name or "" self.durable = durable self.exclusive = exclusive self.auto_delete = auto_delete self.arguments = arguments self.passive = passive def __str__(self) -> str: return f"{self.name}" def __repr__(self) -> str: return ( f"<{self.__class__.__name__}({self}): " f"auto_delete={self.auto_delete}, " f"durable={self.durable}, " f"exclusive={self.exclusive}, " f"arguments={self.arguments!r}" ) async def declare( self, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.DeclareOk: """ Declare queue. :param timeout: execution timeout :param passive: Only check to see if the queue exists. :return: :class:`None` """ log.debug("Declaring queue: %r", self) self.declaration_result = await self.channel.queue_declare( queue=self.name, durable=self.durable, exclusive=self.exclusive, auto_delete=self.auto_delete, arguments=self.arguments, passive=self.passive, timeout=timeout, ) if self.declaration_result.queue is not None: self.name = self.declaration_result.queue else: self.name = "" return self.declaration_result async def bind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, *, arguments: Arguments = None, timeout: TimeoutType = None ) -> aiormq.spec.Queue.BindOk: """ A binding is a relationship between an exchange and a queue. This can be simply read as: the queue is interested in messages from this exchange. Bindings can take an extra routing_key parameter. To avoid the confusion with a basic_publish parameter we're going to call it a binding key. :param exchange: :class:`aio_pika.exchange.Exchange` instance :param routing_key: routing key :param arguments: additional arguments :param timeout: execution timeout :raises asyncio.TimeoutError: when the binding timeout period has elapsed. :return: :class:`None` """ if routing_key is None: routing_key = self.name log.debug( "Binding queue %r: exchange=%r, routing_key=%r, arguments=%r", self, exchange, routing_key, arguments, ) return await self.channel.queue_bind( self.name, exchange=get_exchange_name(exchange), routing_key=routing_key, arguments=arguments, timeout=timeout, ) async def unbind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.UnbindOk: """ Remove binding from exchange for this :class:`Queue` instance :param exchange: :class:`aio_pika.exchange.Exchange` instance :param routing_key: routing key :param arguments: additional arguments :param timeout: execution timeout :raises asyncio.TimeoutError: when the unbinding timeout period has elapsed. :return: :class:`None` """ if routing_key is None: routing_key = self.name log.debug( "Unbinding queue %r: exchange=%r, routing_key=%r, arguments=%r", self, exchange, routing_key, arguments, ) return await self.channel.queue_unbind( queue=self.name, exchange=get_exchange_name(exchange), routing_key=routing_key, arguments=arguments, timeout=timeout, ) async def consume( self, callback: Callable[[AbstractIncomingMessage], Any], no_ack: bool = False, exclusive: bool = False, arguments: Arguments = None, consumer_tag: Optional[ConsumerTag] = None, timeout: TimeoutType = None, ) -> ConsumerTag: """ Start to consuming the :class:`Queue`. :param timeout: :class:`asyncio.TimeoutError` will be raises when the Future was not finished after this time. :param callback: Consuming callback. Could be a coroutine. :param no_ack: if :class:`True` you don't need to call :func:`aio_pika.message.IncomingMessage.ack` :param exclusive: Makes this queue exclusive. Exclusive queues may only be accessed by the current connection, and are deleted when that connection closes. Passive declaration of an exclusive queue by other connections are not allowed. :param arguments: additional arguments :param consumer_tag: optional consumer tag :raises asyncio.TimeoutError: when the consuming timeout period has elapsed. :return str: consumer tag :class:`str` """ log.debug("Start to consuming queue: %r", self) consume_result = await self.channel.basic_consume( queue=self.name, consumer_callback=partial( consumer, callback, no_ack=no_ack, ), exclusive=exclusive, no_ack=no_ack, arguments=arguments, consumer_tag=consumer_tag, timeout=timeout, ) # consumer_tag property is Optional[str] in practice this check # should never take place, however, it protects against the case # if the `None` comes from pamqp if consume_result.consumer_tag is None: raise RuntimeError("Consumer tag is None") return consume_result.consumer_tag async def cancel( self, consumer_tag: ConsumerTag, timeout: TimeoutType = None, nowait: bool = False, ) -> aiormq.spec.Basic.CancelOk: """ This method cancels a consumer. This does not affect already delivered messages, but it does mean the server will not send any more messages for that consumer. The client may receive an arbitrary number of messages in between sending the cancel method and receiving the cancel-ok reply. It may also be sent from the server to the client in the event of the consumer being unexpectedly cancelled (i.e. cancelled for any reason other than the server receiving the corresponding basic.cancel from the client). This allows clients to be notified of the loss of consumers due to events such as queue deletion. :param consumer_tag: consumer tag returned by :func:`~aio_pika.Queue.consume` :param timeout: execution timeout :param bool nowait: Do not expect a Basic.CancelOk response :return: Basic.CancelOk when operation completed successfully """ return await self.channel.basic_cancel( consumer_tag=consumer_tag, nowait=nowait, timeout=timeout, ) async def get( self, *, no_ack: bool = False, fail: bool = True, timeout: TimeoutType = 5 ) -> Optional[IncomingMessage]: """ Get message from the queue. :param no_ack: if :class:`True` you don't need to call :func:`aio_pika.message.IncomingMessage.ack` :param timeout: execution timeout :param fail: Should return :class:`None` instead of raise an exception :class:`aio_pika.exceptions.QueueEmpty`. :return: :class:`aio_pika.message.IncomingMessage` """ msg: DeliveredMessage = await self.channel.basic_get( self.name, no_ack=no_ack, timeout=timeout, ) if isinstance(msg.delivery, aiormq.spec.Basic.GetEmpty): if fail: raise QueueEmpty return None return IncomingMessage(msg, no_ack=no_ack) async def purge( self, no_wait: bool = False, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.PurgeOk: """ Purge all messages from the queue. :param no_wait: no wait response :param timeout: execution timeout :return: :class:`None` """ log.info("Purging queue: %r", self) return await self.channel.queue_purge( self.name, nowait=no_wait, timeout=timeout, ) async def delete( self, *, if_unused: bool = True, if_empty: bool = True, timeout: TimeoutType = None ) -> aiormq.spec.Queue.DeleteOk: """ Delete the queue. :param if_unused: Perform delete only when unused :param if_empty: Perform delete only when empty :param timeout: execution timeout :return: :class:`None` """ log.info("Deleting %r", self) return await self.channel.queue_delete( self.name, if_unused=if_unused, if_empty=if_empty, timeout=timeout, ) def __aiter__(self) -> "AbstractQueueIterator": return self.iterator() def iterator(self, **kwargs: Any) -> "AbstractQueueIterator": """ Returns an iterator for async for expression. Full example: .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect() async with connection: channel = await connection.channel() queue = await channel.declare_queue('test') async with queue.iterator() as q: async for message in q: print(message.body) When your program runs with run_forever the iterator will be closed in background. In this case the context processor for iterator might be skipped and the queue might be used in the "async for" expression directly. .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect() async with connection: channel = await connection.channel() queue = await channel.declare_queue('test') async for message in queue: print(message.body) :return: QueueIterator """ return QueueIterator(self, **kwargs) class QueueIterator(AbstractQueueIterator): DEFAULT_CLOSE_TIMEOUT = 5 @property def consumer_tag(self) -> Optional[ConsumerTag]: return getattr(self, "_consumer_tag", None) async def close(self, *_: Any) -> Any: log.debug("Cancelling queue iterator %r", self) if not hasattr(self, "_consumer_tag"): log.debug("Queue iterator %r already cancelled", self) return if self._amqp_queue.channel.is_closed: log.debug("Queue iterator %r channel closed", self) return log.debug("Basic.cancel for %r", self.consumer_tag) consumer_tag = self._consumer_tag del self._consumer_tag self._amqp_queue.close_callbacks.remove(self.close) await self._amqp_queue.cancel(consumer_tag) log.debug("Queue iterator %r closed", self) # Reject all messages msg: Optional[IncomingMessage] = None try: while True: msg = self._queue.get_nowait() except asyncio.QueueEmpty: if msg is not None and not self._amqp_queue.channel.is_closed: await msg.nack(requeue=True, multiple=True) def __str__(self) -> str: return f"queue[{self._amqp_queue}](...)" def __repr__(self) -> str: return ( f"<{self.__class__.__name__}: " f"queue={self._amqp_queue.name!r} " f"ctag={self.consumer_tag!r}>" ) def __init__(self, queue: Queue, **kwargs: Any): self._consumer_tag: ConsumerTag self._amqp_queue: AbstractQueue = queue self._queue = asyncio.Queue() self._consume_kwargs = kwargs self._amqp_queue.close_callbacks.add(self.close) async def on_message(self, message: AbstractIncomingMessage) -> None: await self._queue.put(message) async def consume(self) -> None: self._consumer_tag = await self._amqp_queue.consume( self.on_message, **self._consume_kwargs ) def __aiter__(self) -> "AbstractQueueIterator": return self async def __aenter__(self) -> "AbstractQueueIterator": if not hasattr(self, "_consumer_tag"): await self.consume() return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: await self.close() async def __anext__(self) -> IncomingMessage: if not hasattr(self, "_consumer_tag"): await self.consume() try: return await asyncio.wait_for( self._queue.get(), timeout=self._consume_kwargs.get("timeout"), ) except asyncio.CancelledError: timeout = self._consume_kwargs.get( "timeout", self.DEFAULT_CLOSE_TIMEOUT, ) log.info( "%r closing with timeout %d seconds", self, timeout, ) await asyncio.wait_for(self.close(), timeout=timeout) raise __all__ = ("Queue", "QueueIterator", "ConsumerTag") aio-pika-8.2.5/aio_pika/robust_channel.py000066400000000000000000000155031433544061600203660ustar00rootroot00000000000000import asyncio from collections import defaultdict from itertools import chain from typing import Any, DefaultDict, Dict, Optional, Set, Type, Union from warnings import warn import aiormq from .abc import ( AbstractRobustChannel, AbstractRobustExchange, AbstractRobustQueue, TimeoutType, ) from .channel import Channel from .exchange import Exchange, ExchangeType from .log import get_logger from .queue import Queue from .robust_exchange import RobustExchange from .robust_queue import RobustQueue from .tools import CallbackCollection log = get_logger(__name__) class RobustChannel(Channel, AbstractRobustChannel): # type: ignore """ Channel abstraction """ QUEUE_CLASS: Type[Queue] = RobustQueue EXCHANGE_CLASS: Type[Exchange] = RobustExchange _exchanges: DefaultDict[str, Set[AbstractRobustExchange]] _queues: DefaultDict[str, Set[RobustQueue]] default_exchange: RobustExchange def __init__( self, connection: aiormq.abc.AbstractConnection, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ): """ :param connection: :class:`aio_pika.adapter.AsyncioConnection` instance :param loop: Event loop (:func:`asyncio.get_event_loop()` when :class:`None`) :param future_store: :class:`aio_pika.common.FutureStore` instance :param publisher_confirms: False if you don't need delivery confirmations (in pursuit of performance) """ super().__init__( connection=connection, channel_number=channel_number, publisher_confirms=publisher_confirms, on_return_raises=on_return_raises, ) self._exchanges = defaultdict(set) self._queues = defaultdict(set) self._prefetch_count: int = 0 self._prefetch_size: int = 0 self._global_qos: bool = False self.reopen_callbacks: CallbackCollection = CallbackCollection(self) self.close_callbacks.add(self.__close_callback) self.__restore_lock = asyncio.Lock() async def __close_callback(self, *_: Any) -> None: if self._closed or self._connection.is_closed: self.close_callbacks.discard(self.__close_callback) return await self.reopen() async def restore(self, connection: aiormq.abc.AbstractConnection) -> None: async with self.__restore_lock: self._connection = connection await self.reopen() async def _open(self) -> None: await super()._open() await self.reopen_callbacks() async def _on_open(self, channel: aiormq.abc.AbstractChannel) -> None: if not hasattr(self, "default_exchange"): await super()._on_open(channel) exchanges = tuple(chain(*self._exchanges.values())) queues = tuple(chain(*self._queues.values())) await channel.basic_qos( prefetch_count=self._prefetch_count, prefetch_size=self._prefetch_size, ) for exchange in exchanges: await exchange.restore(channel) for queue in queues: await queue.restore(channel) if hasattr(self, "default_exchange"): self.default_exchange.channel = channel async def set_qos( self, prefetch_count: int = 0, prefetch_size: int = 0, global_: bool = False, timeout: TimeoutType = None, all_channels: Optional[bool] = None, ) -> aiormq.spec.Basic.QosOk: if all_channels is not None: warn('Use "global_" instead of "all_channels"', DeprecationWarning) global_ = all_channels await self._connection.ready() self._prefetch_count = prefetch_count self._prefetch_size = prefetch_size self._global_qos = global_ return await super().set_qos( prefetch_count=prefetch_count, prefetch_size=prefetch_size, global_=global_, timeout=timeout, ) async def declare_exchange( self, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, durable: bool = False, auto_delete: bool = False, internal: bool = False, passive: bool = False, arguments: Optional[Dict[str, Any]] = None, timeout: TimeoutType = None, robust: bool = True, ) -> AbstractRobustExchange: await self._connection.ready() exchange = ( await super().declare_exchange( name=name, type=type, durable=durable, auto_delete=auto_delete, internal=internal, passive=passive, arguments=arguments, timeout=timeout, ) ) if not internal and robust: # noinspection PyTypeChecker self._exchanges[name].add(exchange) # type: ignore return exchange # type: ignore async def exchange_delete( self, exchange_name: str, timeout: TimeoutType = None, if_unused: bool = False, nowait: bool = False, ) -> aiormq.spec.Exchange.DeleteOk: await self._connection.ready() result = await super().exchange_delete( exchange_name=exchange_name, timeout=timeout, if_unused=if_unused, nowait=nowait, ) self._exchanges.pop(exchange_name, None) return result async def declare_queue( self, name: Optional[str] = None, *, durable: bool = False, exclusive: bool = False, passive: bool = False, auto_delete: bool = False, arguments: Optional[Dict[str, Any]] = None, timeout: TimeoutType = None, robust: bool = True ) -> AbstractRobustQueue: await self._connection.ready() queue: RobustQueue = await super().declare_queue( # type: ignore name=name, durable=durable, exclusive=exclusive, passive=passive, auto_delete=auto_delete, arguments=arguments, timeout=timeout, ) if robust: self._queues[queue.name].add(queue) return queue async def queue_delete( self, queue_name: str, timeout: TimeoutType = None, if_unused: bool = False, if_empty: bool = False, nowait: bool = False, ) -> aiormq.spec.Queue.DeleteOk: await self._connection.ready() result = await super().queue_delete( queue_name=queue_name, timeout=timeout, if_unused=if_unused, if_empty=if_empty, nowait=nowait, ) self._queues.pop(queue_name, None) return result __all__ = ("RobustChannel",) aio-pika-8.2.5/aio_pika/robust_connection.py000066400000000000000000000215531433544061600211170ustar00rootroot00000000000000import asyncio from ssl import SSLContext from typing import Any, Optional, Type, Union from weakref import WeakSet import aiormq.abc from aiormq.connection import parse_bool, parse_timeout from pamqp.common import FieldTable from yarl import URL from .abc import ( AbstractRobustChannel, AbstractRobustConnection, SSLOptions, TimeoutType, UnderlayConnection, ) from .connection import Connection, make_url from .exceptions import CONNECTION_EXCEPTIONS from .log import get_logger from .robust_channel import RobustChannel from .tools import CallbackCollection, OneShotCallback log = get_logger(__name__) class RobustConnection(Connection, AbstractRobustConnection): """Robust connection""" CHANNEL_REOPEN_PAUSE = 1 CHANNEL_CLASS: Type[RobustChannel] = RobustChannel KWARGS_TYPES = ( ("reconnect_interval", parse_timeout, "5"), ("fail_fast", parse_bool, "1"), ) def __init__( self, url: URL, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any, ): super().__init__(url=url, loop=loop, **kwargs) self.reconnect_interval = self.kwargs.pop("reconnect_interval") self.fail_fast = self.kwargs.pop("fail_fast") self.__channels: WeakSet[AbstractRobustChannel] = WeakSet() self._reconnect_lock = asyncio.Lock() self.reconnect_callbacks: CallbackCollection = CallbackCollection(self) @property def reconnecting(self) -> bool: return self._reconnect_lock.locked() def __repr__(self) -> str: return ( f'<{self.__class__.__name__}: "{self}" ' f"{len(self.__channels)} channels>" ) async def _on_connection_close(self, closing: asyncio.Future) -> None: await super()._on_connection_close(closing) if self._close_called: return log.info( "Connection to %s closed. Reconnecting after %r seconds.", self, self.reconnect_interval, ) await asyncio.sleep(self.reconnect_interval) await self.reconnect() async def __connection_attempt( self, timeout: TimeoutType = None, ) -> aiormq.abc.AbstractConnection: connection = await UnderlayConnection.make_connection( self.url, timeout=timeout, **self.kwargs ) try: for channel in self.__channels: try: await channel.restore(connection) except Exception: log.exception("Failed to reopen channel") raise except asyncio.CancelledError: # In python 3.7 asyncio.CancelledError inherited # from Exception and this needed for catch it first raise except Exception as e: closing = self.loop.create_future() closing.set_exception(e) await self.close_callbacks(closing) await asyncio.gather(connection.close(e), return_exceptions=True) raise return connection async def connect(self, timeout: TimeoutType = None) -> None: if self.is_closed: raise RuntimeError(f"{self!r} connection closed") if self.reconnecting: raise RuntimeError( ( "Connect method called but connection " f"{self!r} is reconnecting right now." ), self, ) async with self._reconnect_lock: self.transport = None self.connected.clear() while not self.is_closed: close_callback = OneShotCallback( self._on_connection_close, ) try: connection = await self.__connection_attempt(timeout) self.connected.set() connection.closing.add_done_callback(close_callback) self.transport = UnderlayConnection( connection=connection, close_callback=close_callback, ) self.fail_fast = False return except CONNECTION_EXCEPTIONS as e: self.transport = None if self.fail_fast: raise log.warning( 'Connection attempt to "%s" failed: %s. ' "Reconnecting after %r seconds.", self, e, self.reconnect_interval, ) log.info( "Reconnect attempt failed %s. Retrying after %r seconds.", self, self.reconnect_interval, ) await asyncio.sleep(self.reconnect_interval) async def reconnect(self) -> None: await self.connect() await self.reconnect_callbacks() def channel( self, channel_number: Optional[int] = None, publisher_confirms: bool = True, on_return_raises: bool = False, ) -> AbstractRobustChannel: channel: AbstractRobustChannel = super().channel( channel_number=channel_number, publisher_confirms=publisher_confirms, on_return_raises=on_return_raises, ) # type: ignore self.__channels.add(channel) return channel async def connect_robust( url: Union[str, URL, None] = None, *, host: str = "localhost", port: int = 5672, login: str = "guest", password: str = "guest", virtualhost: str = "/", ssl: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, ssl_options: Optional[SSLOptions] = None, ssl_context: Optional[SSLContext] = None, timeout: TimeoutType = None, client_properties: Optional[FieldTable] = None, connection_class: Type[AbstractRobustConnection] = RobustConnection, **kwargs: Any ) -> AbstractRobustConnection: """Make connection to the broker. Example: .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect( "amqp://guest:guest@127.0.0.1/" ) Connect to localhost with default credentials: .. code-block:: python import aio_pika async def main(): connection = await aio_pika.connect() .. note:: The available keys for ssl_options parameter are: * cert_reqs * certfile * keyfile * ssl_version For an information on what the ssl_options can be set to reference the `official Python documentation`_ . Set connection name for RabbitMQ admin panel: .. code-block:: python # As URL parameter method read_connection = await connect( "amqp://guest:guest@localhost/?name=Read%20connection" ) # keyword method write_connection = await connect( client_properties={ 'connection_name': 'Write connection' } ) .. note: ``client_properties`` argument requires ``aiormq>=2.9`` URL string might be contain ssl parameters e.g. `amqps://user:pass@host//?ca_certs=ca.pem&certfile=crt.pem&keyfile=key.pem` :param client_properties: add custom client capability. :param url: RFC3986_ formatted broker address. When :class:`None` will be used keyword arguments. :param host: hostname of the broker :param port: broker port 5672 by default :param login: username string. `'guest'` by default. :param password: password string. `'guest'` by default. :param virtualhost: virtualhost parameter. `'/'` by default :param ssl: use SSL for connection. Should be used with addition kwargs. :param ssl_options: A dict of values for the SSL connection. :param timeout: connection timeout in seconds :param loop: Event loop (:func:`asyncio.get_event_loop()` when :class:`None`) :param ssl_context: ssl.SSLContext instance :param connection_class: Factory of a new connection :param kwargs: addition parameters which will be passed to the connection. :return: :class:`aio_pika.connection.Connection` .. _RFC3986: https://goo.gl/MzgYAs .. _official Python documentation: https://goo.gl/pty9xA """ connection: AbstractRobustConnection = connection_class( make_url( url, host=host, port=port, login=login, password=password, virtualhost=virtualhost, ssl=ssl, ssl_options=ssl_options, client_properties=client_properties, **kwargs ), loop=loop, ssl_context=ssl_context, ) await connection.connect(timeout=timeout) return connection __all__ = ( "RobustConnection", "connect_robust", ) aio-pika-8.2.5/aio_pika/robust_exchange.py000066400000000000000000000052451433544061600205420ustar00rootroot00000000000000import asyncio from typing import Any, Dict, Union import aiormq from pamqp.common import Arguments from .abc import ( AbstractExchange, AbstractRobustExchange, ExchangeParamType, TimeoutType, ) from .exchange import Exchange, ExchangeType from .log import get_logger log = get_logger(__name__) class RobustExchange(Exchange, AbstractRobustExchange): """ Exchange abstraction """ _bindings: Dict[Union[AbstractExchange, str], Dict[str, Any]] def __init__( self, channel: aiormq.abc.AbstractChannel, name: str, type: Union[ExchangeType, str] = ExchangeType.DIRECT, *, auto_delete: bool = False, durable: bool = False, internal: bool = False, passive: bool = False, arguments: Arguments = None ): super().__init__( channel=channel, name=name, type=type, auto_delete=auto_delete, durable=durable, internal=internal, passive=passive, arguments=arguments, ) self._bindings = {} self.__restore_lock = asyncio.Lock() async def restore(self, channel: aiormq.abc.AbstractChannel) -> None: async with self.__restore_lock: try: self.channel = channel # special case for default exchange if self.name == "": return await self.declare() for exchange, kwargs in tuple(self._bindings.items()): await self.bind(exchange, **kwargs) except Exception: del self.channel raise async def bind( self, exchange: ExchangeParamType, routing_key: str = "", *, arguments: Arguments = None, timeout: TimeoutType = None, robust: bool = True ) -> aiormq.spec.Exchange.BindOk: result = await super().bind( exchange, routing_key=routing_key, arguments=arguments, timeout=timeout, ) if robust: self._bindings[exchange] = dict( routing_key=routing_key, arguments=arguments, ) return result async def unbind( self, exchange: ExchangeParamType, routing_key: str = "", arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Exchange.UnbindOk: result = await super().unbind( exchange, routing_key, arguments=arguments, timeout=timeout, ) self._bindings.pop(exchange, None) return result __all__ = ("RobustExchange",) aio-pika-8.2.5/aio_pika/robust_queue.py000066400000000000000000000110571433544061600201020ustar00rootroot00000000000000from random import Random from typing import Any, Callable, Dict, Optional, Tuple, Union import aiormq from aiormq import ChannelInvalidStateError from pamqp.common import Arguments from .abc import ( AbstractExchange, AbstractIncomingMessage, AbstractQueueIterator, AbstractRobustQueue, ConsumerTag, TimeoutType, ) from .exchange import ExchangeParamType from .log import get_logger from .queue import Queue, QueueIterator log = get_logger(__name__) class RobustQueue(Queue, AbstractRobustQueue): __slots__ = ("_consumers", "_bindings") _consumers: Dict[ConsumerTag, Dict[str, Any]] _bindings: Dict[Tuple[Union[AbstractExchange, str], str], Dict[str, Any]] _rnd_gen: Random = Random() @classmethod def _get_random_queue_name(cls) -> str: rnd = cls._rnd_gen.getrandbits(128) return "amq_%s" % hex(rnd).lower() def __init__( self, channel: aiormq.abc.AbstractChannel, name: Optional[str], durable: bool = False, exclusive: bool = False, auto_delete: bool = False, arguments: Arguments = None, passive: bool = False, ): super().__init__( channel=channel, name=name or self._get_random_queue_name(), durable=durable, exclusive=exclusive, auto_delete=auto_delete, arguments=arguments, passive=passive, ) self._consumers = {} self._bindings = {} async def restore(self, channel: aiormq.abc.AbstractChannel) -> None: self.channel = channel await self.declare() bindings = tuple(self._bindings.items()) consumers = tuple(self._consumers.items()) for (exchange, routing_key), kwargs in bindings: await self.bind(exchange, routing_key, **kwargs) for consumer_tag, kwargs in consumers: await self.consume(consumer_tag=consumer_tag, **kwargs) async def bind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, *, arguments: Arguments = None, timeout: TimeoutType = None, robust: bool = True ) -> aiormq.spec.Queue.BindOk: if routing_key is None: routing_key = self.name result = await super().bind( exchange=exchange, routing_key=routing_key, arguments=arguments, timeout=timeout, ) if robust: self._bindings[(exchange, routing_key)] = dict( arguments=arguments, ) return result async def unbind( self, exchange: ExchangeParamType, routing_key: Optional[str] = None, arguments: Arguments = None, timeout: TimeoutType = None, ) -> aiormq.spec.Queue.UnbindOk: if routing_key is None: routing_key = self.name result = await super().unbind( exchange, routing_key, arguments, timeout, ) self._bindings.pop((exchange, routing_key), None) return result async def consume( self, callback: Callable[[AbstractIncomingMessage], Any], no_ack: bool = False, exclusive: bool = False, arguments: Arguments = None, consumer_tag: Optional[ConsumerTag] = None, timeout: TimeoutType = None, robust: bool = True, ) -> ConsumerTag: consumer_tag = await super().consume( consumer_tag=consumer_tag, timeout=timeout, callback=callback, no_ack=no_ack, exclusive=exclusive, arguments=arguments, ) if robust: self._consumers[consumer_tag] = dict( callback=callback, no_ack=no_ack, exclusive=exclusive, arguments=arguments, ) return consumer_tag async def cancel( self, consumer_tag: ConsumerTag, timeout: TimeoutType = None, nowait: bool = False, ) -> aiormq.spec.Basic.CancelOk: result = await super().cancel(consumer_tag, timeout, nowait) self._consumers.pop(consumer_tag, None) return result def iterator(self, **kwargs: Any) -> AbstractQueueIterator: return RobustQueueIterator(self, **kwargs) class RobustQueueIterator(QueueIterator): async def consume(self) -> None: while True: try: return await super().consume() except ChannelInvalidStateError: await self._amqp_queue.channel.connection.ready() __all__ = ("RobustQueue",) aio-pika-8.2.5/aio_pika/tools.py000066400000000000000000000157711433544061600165270ustar00rootroot00000000000000import asyncio from itertools import chain from threading import Lock from typing import ( AbstractSet, Any, Awaitable, Callable, Coroutine, Generator, Iterator, List, MutableSet, Optional, TypeVar, Union, ) from weakref import ReferenceType, WeakSet, ref from aio_pika.log import get_logger log = get_logger(__name__) T = TypeVar("T") def iscoroutinepartial(fn: Callable[..., Any]) -> bool: """ Function returns True if function is a partial instance of coroutine. See additional information here_. :param fn: Function :return: bool .. _here: https://goo.gl/C0S4sQ """ while True: parent = fn fn = getattr(parent, "func", None) # type: ignore if fn is None: break return asyncio.iscoroutinefunction(parent) def _task_done(future: asyncio.Future) -> None: exc = future.exception() if exc is not None: raise exc def create_task( func: Callable[..., Union[Coroutine[Any, Any, T], Awaitable[T]]], *args: Any, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any ) -> Awaitable[T]: loop = loop or asyncio.get_event_loop() if iscoroutinepartial(func): task = loop.create_task(func(*args, **kwargs)) # type: ignore task.add_done_callback(_task_done) return task def run(future: asyncio.Future) -> Optional[asyncio.Future]: if future.done(): return None try: future.set_result(func(*args, **kwargs)) except Exception as e: future.set_exception(e) return future future = loop.create_future() future.add_done_callback(_task_done) loop.call_soon(run, future) return future CallbackType = Callable[..., Union[T, Awaitable[T]]] CallbackSetType = AbstractSet[CallbackType] class StubAwaitable: __slots__ = () def __await__(self) -> Generator[Any, Any, None]: yield STUB_AWAITABLE = StubAwaitable() class CallbackCollection(MutableSet): __slots__ = ( "__weakref__", "__sender", "__callbacks", "__weak_callbacks", "__lock", ) def __init__(self, sender: Union[T, ReferenceType]): self.__sender: ReferenceType if isinstance(sender, ReferenceType): self.__sender = sender else: self.__sender = ref(sender) self.__callbacks: CallbackSetType = set() self.__weak_callbacks: MutableSet[CallbackType] = WeakSet() self.__lock: Lock = Lock() def add(self, callback: CallbackType, weak: bool = False) -> None: if self.is_frozen: raise RuntimeError("Collection frozen") if not callable(callback): raise ValueError("Callback is not callable") with self.__lock: if weak: self.__weak_callbacks.add(callback) else: self.__callbacks.add(callback) # type: ignore def discard(self, callback: CallbackType) -> None: if self.is_frozen: raise RuntimeError("Collection frozen") with self.__lock: if callback in self.__callbacks: self.__callbacks.remove(callback) # type: ignore elif callback in self.__weak_callbacks: self.__weak_callbacks.remove(callback) def clear(self) -> None: if self.is_frozen: raise RuntimeError("Collection frozen") with self.__lock: self.__callbacks.clear() # type: ignore self.__weak_callbacks.clear() @property def is_frozen(self) -> bool: return isinstance(self.__callbacks, frozenset) def freeze(self) -> None: if self.is_frozen: raise RuntimeError("Collection already frozen") with self.__lock: self.__callbacks = frozenset(self.__callbacks) self.__weak_callbacks = WeakSet(self.__weak_callbacks) def unfreeze(self) -> None: if not self.is_frozen: raise RuntimeError("Collection is not frozen") with self.__lock: self.__callbacks = set(self.__callbacks) self.__weak_callbacks = WeakSet(self.__weak_callbacks) def __contains__(self, x: object) -> bool: return x in self.__callbacks or x in self.__weak_callbacks def __len__(self) -> int: return len(self.__callbacks) + len(self.__weak_callbacks) def __iter__(self) -> Iterator[CallbackType]: return iter(chain(self.__callbacks, self.__weak_callbacks)) def __bool__(self) -> bool: return bool(self.__callbacks) or bool(self.__weak_callbacks) def __copy__(self) -> "CallbackCollection": instance = self.__class__(self.__sender) with self.__lock: for cb in self.__callbacks: instance.add(cb, weak=False) for cb in self.__weak_callbacks: instance.add(cb, weak=True) if self.is_frozen: instance.freeze() return instance def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[Any]: futures: List[asyncio.Future] = [] with self.__lock: sender = self.__sender() for cb in self: try: result = cb(sender, *args, **kwargs) if hasattr(result, "__await__"): futures.append(asyncio.ensure_future(result)) except Exception: log.exception("Callback %r error", cb) if not futures: return STUB_AWAITABLE return asyncio.gather(*futures, return_exceptions=True) def __hash__(self) -> int: return id(self) class OneShotCallback: __slots__ = ("loop", "finished", "__lock", "callback", "__task") def __init__(self, callback: Callable[..., Awaitable[T]]): self.callback: Callable[..., Awaitable[T]] = callback self.loop = asyncio.get_event_loop() self.finished: asyncio.Event = asyncio.Event() self.__lock: asyncio.Lock = asyncio.Lock() self.__task: asyncio.Future def __repr__(self) -> str: return f"<{self.__class__.__name__}: cb={self.callback!r}>" def wait(self) -> Awaitable[Any]: try: return self.finished.wait() except asyncio.CancelledError: self.__task.cancel() raise async def __task_inner(self, *args: Any, **kwargs: Any) -> None: async with self.__lock: if self.finished.is_set(): return try: await self.callback(*args, **kwargs) finally: self.finished.set() del self.callback def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[Any]: if self.finished.is_set(): return STUB_AWAITABLE self.__task = self.loop.create_task( self.__task_inner(*args, **kwargs), ) return self.__task __all__ = ( "CallbackCollection", "CallbackType", "CallbackSetType", "OneShotCallback", "create_task", "iscoroutinepartial", ) aio-pika-8.2.5/aio_pika/transaction.py000066400000000000000000000036041433544061600177040ustar00rootroot00000000000000from types import TracebackType from typing import Optional, Type import aiormq from pamqp import commands from .abc import ( AbstractChannel, AbstractTransaction, TimeoutType, TransactionState, ) class Transaction(AbstractTransaction): def __repr__(self) -> str: return f"<{self.__class__.__name__} {self.state.value}>" def __str__(self) -> str: return self.state.value def __init__(self, channel: AbstractChannel): self.__channel = channel self.state: TransactionState = TransactionState.CREATED @property def channel(self) -> AbstractChannel: if self.__channel is None: raise RuntimeError("Channel not opened") if self.__channel.is_closed: raise RuntimeError("Closed channel") return self.__channel async def select( self, timeout: TimeoutType = None, ) -> aiormq.spec.Tx.SelectOk: result = await self.channel.channel.tx_select(timeout=timeout) self.state = TransactionState.STARTED return result async def rollback( self, timeout: TimeoutType = None, ) -> commands.Tx.RollbackOk: result = await self.channel.channel.tx_rollback(timeout=timeout) self.state = TransactionState.ROLLED_BACK return result async def commit( self, timeout: TimeoutType = None, ) -> commands.Tx.CommitOk: result = await self.channel.channel.tx_commit(timeout=timeout) self.state = TransactionState.COMMITED return result async def __aenter__(self) -> "Transaction": await self.select() return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: if exc_type: await self.rollback() else: await self.commit() aio-pika-8.2.5/aio_pika/version.py000066400000000000000000000005221433544061600170400ustar00rootroot00000000000000author_info = (("Dmitry Orlov", "me@mosquito.su"),) package_info = "Wrapper for the aiormq for asyncio and humans." package_license = "Apache Software License" team_email = "me@mosquito.su" version_info = (8, 2, 5) __author__ = ", ".join("{} <{}>".format(*info) for info in author_info) __version__ = ".".join(map(str, version_info)) aio-pika-8.2.5/docs/000077500000000000000000000000001433544061600141565ustar00rootroot00000000000000aio-pika-8.2.5/docs/Makefile000066400000000000000000000011411433544061600156130ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = aio-pika SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)aio-pika-8.2.5/docs/requirements.txt000066400000000000000000000000571433544061600174440ustar00rootroot00000000000000# ONLY FOR ReadTheDocs .[develop] autodoc furo aio-pika-8.2.5/docs/source/000077500000000000000000000000001433544061600154565ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/_static/000077500000000000000000000000001433544061600171045ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/_static/.DS_Store000066400000000000000000000240041433544061600205670ustar00rootroot00000000000000Bud1    conbwspbl  @€ @€ @€ @ faviconbwspblobÉbplist00×  ]ShowStatusBar[ShowSidebar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowPathbar  _{{164, 108}, {770, 436}}%1=I`myz{|}~™šfaviconlsvCblob¤bplist00Ú GHGI _viewOptionsVersion_showIconPreviewWcolumns_calculateAllSizes_scrollPositionYXtextSize_scrollPositionXZsortColumnXiconSize_useRelativeDates «!%*/49=BÔ  WvisibleUwidthYascendingZidentifier , TnameÔXubiquity#Ô  µ\dateModifiedÔ$[dateCreatedÔ ') aTsizeÔ , . s TkindÔ1 3d UlabelÔ6 8K WversionÔ < XcommentsÔ?AČ^dateLastOpenedÔCYdateAdded##@(#@0 2DL`r{Ťˇ´¶·ĂĚÔÚäďđóôů  )234@IJLMR[\^_dmnpqw€„Ś•–— ©Ş¬­ĽĹĎĐŃŇŰäíKîfaviconlsvpblob‰bplist00Ú FGFH _viewOptionsVersion_showIconPreviewWcolumns_calculateAllSizes_scrollPositionYXtextSize_scrollPositionXZsortColumnXiconSize_useRelativeDates Ů %)-27 K ÔB  ##@(#@0 2DL`r{Ťˇ´¶·ĘÓâďű !'-7?ADEFOQSTU^`abkmnoxz|}~‡‰‹ŚŤ–š›śĄ§©Ş«´¶·¸ąÂËÔJŐfaviconvSrnlongtutorialbwspblob¸bplist00Ö ]ShowStatusBar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar  _{{223, 59}, {1289, 758}} #/;R_klmnoŠ ‹tutorialdsclbooltutoriallsvCblobbplist00Ř VW YXiconSize_showIconPreviewWcolumns_calculateAllSizesXtextSizeZsortColumn_useRelativeDates_viewOptionsVersion#@0 ® "&+05:?CHLPÔ  ZidentifierUwidthYascendingWvisibleTnameA Ô WvisibleUwidthYascending#XubiquityÔ  \dateModifiedµ Ô #[dateCreatedÔ '( Tsizea Ô ,- Tkinds Ô 12 Ulabeld Ô 67 WversionK Ô ;< Xcomments, Ô @^dateLastOpenedÔ DEZshareOwnerČÔ IE_shareLastEditorÔ OYdateAddedÔ QR_invitationStatusŇ#@*Tkind "4<PYdwŚ•–Ą®ążÉŃÖŮÚŰäěňüý˙ !"#,89:CHJKLUZ\]^gmopqz‚„…†Ź›śť¦µ¶·ŔËÍÎĎŘęëěőö÷  !"+01ZBtutoriallsvpblobmbplist00Ř EF HXiconSize_showIconPreviewWcolumns_calculateAllSizesXtextSizeZsortColumn_useRelativeDates_viewOptionsVersion#@0 Ů #(-26;@XcommentsUlabelWversion[dateCreatedTsize\dateModifiedTkindTname^dateLastOpenedÔ WvisibleUwidthYascendingUindex, Ô "d Ô% 'K Ô*,µÔ /1 aÔ *5 Ô 8 : s Ô = ? A Ô*C#@*Tkind "4<PYdwŚ•–©˛¸ŔĚŃŢăč÷"#%./124=>@ACLMOPR[\^_ajklnwxz{}†‡Š‹Ť–—𛤩ŞI»tutorialvSrnlong E DSDB `€(0@€ @€ @ascendingWvisible, Ô!"ČÔ & µ Ô*&Ô./ a Ô34 d Ô89 s Ô=> K ÔB  ##@(#@0 2DL`r{Ťˇ´¶·ĘÓâďű !'-7?ADEFOQSTU^`abkmnoxz|}~‡‰‹ŚŤ–š›śĄ§©Ş«´¶·¸ąÂËÔJŐfaviconvSrnlongtutorialbwspblob¸bplist00Ö ]ShowStatusBar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar  _{{223, 59}, {1289, 758}} #/;R_klmnoŠ ‹tutorialdsclbooltutoriallsvCblobbplist00Ř VW YXiconSize_showIconPreviewWcolumns_calculateAllSizesXtextSizeZsortColumn_useRelativeDataio-pika-8.2.5/docs/source/_static/custom.css000066400000000000000000000002301433544061600211230ustar00rootroot00000000000000@import url('https://fonts.googleapis.com/css?family=Roboto|Roboto+Condensed|Roboto+Mono|Roboto+Slab'); h1.logo { text-align: center !important; } aio-pika-8.2.5/docs/source/_static/favicon/000077500000000000000000000000001433544061600205315ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/_static/favicon/android-icon-144x144.png000066400000000000000000000132431433544061600245370ustar00rootroot00000000000000‰PNG  IHDRĐŠPLTE˙˙˙¬r+W!ţţţüüüúúúřřřéĚ©öööôôôďďďńńńíííëëëçççééé‚V ¬q)ĺĺćóóóŕáá‚U­s+<(××ŘY;§o*ääĺŮŮŮ["ââáăăă¬q(«p'ŢŢŢÖÖÖ‚VÚÚÚÔÔÔÜÜÜęͨÍÍÍŢÝÝĎĎĎĐŃŃËËËĎĐŃßßßÓÓÓ„X!¬t/TŐŐÖÉÉÉŰŰŰÄĹĆĘËĚŇŇŇĆĆÇĂĂÄżżżĘĘĘČČÉ‹e7„Y$ÇÇČşşş…[(´´µ­w4;'ŔŔŔ˝˝˝ęΫ­y8W:¸¸¸°‚J‰b3ĆĹŬ¬¬ćËŞĎÎÎÂÂÂÉÂş±C_.:&ŢßŕÍÎĎĽ»ąŢÇ«»Ż Ż|>T ŔÁÂĽĽĽ±°°Ľł§·§““qGŚh=†]+wOqKââă¶łŻż¬”˛Ť`µ‹X­{=]=ŇŃŃŔľĽ˝ą´Ą—†ł–rą•i™€a°‡V•xU™yR®~BŹlAmHfCĐËĹĺÓżŃÉżĂÁżĂľ·ą·łä̱ŮÇŻ­®Żľ¶­Ľµ­čͬł®§·«›ł˘Ť»ˇ¦Źrł’j‘rMŹnEŽi;Ąm(ŐŇÎŕŇÂĚÇÁÝÎĽŕθŰË·Áş˛ŕÉ­Ż©ˇáÂś­˘”¨›‹­šˇŹy¸™rź„a–|[|Ra@E.@+ŘŐŇÎĚÉŐĎČÔÍĹËÉĹËŽ̾¬±«¤ÁłŁ®Ą™·Ł‹ĽĄ‰¶ź¬—}ťŤx·‘aś[˛‡Qźj'”b%ŰÔËĺŘÉČÇĆćÖÄĆÄÁÇľ´Ĺ»Żµ°©§¨¨Ąť”ŞžŹµ›|ž‰n­Śdއ\±Š[–vOŞ€Mµ„H®€Ge&Ž^#éăÚÝÚŘË·çеŐǵ˹ĄÉ¸Ł§Ą˘Â±śĂ¨‡˘iš„išeuUŞ„T¶OR7K1çÝŃáŃ˝ĎĂ´ÔŔ¨Ü˝—¦•‚ɦ}Ł’}—‡r͢o¶€>•k9Źe1O4áŰÓĺǣǰ•Ö°‚Ŕˇ{˝™m‘WśsAŘĎÄÜĩ٠śĽśvÄ•]Îm~^IDATxÚě•ËkQĆťi+Éd™;3ÁdĆÄ –DŤşĐ¤AW‰DĐŤˇjDABíFÁǢ.¤Ř•¦¶(ZZ7‚.\‰.«‹nü DWâ˝óđdÄÜq¦".ü %›ďđ;ß9çΚ˙úŻż¦!řńhhŘÖ­&pý1¨ŰW>ČC5­žfddd­+üÓ)Ů´z\7‹c)ř/#ő©ŐmŹgRúMĂ«@‚Ҥp‚ăx[—Pâ1é×0%\ď3­*R9®pĽ ‰H'B˘$p §:÷÷đĂ$"„ü&H6z< ‡it5·7Űëů¦Ş‹ďU§šZév:ť®a©jÚ3Q$ś;/!µ;żĽ¸Ňh4VżĽëĺšëŔ)@4Čô`a©555ŐZZxĐ©€)*)M:EăłË+cÓ”eŮ4ͱ'ˆ•–ř„SÜďSçńËcZYÓ˛Yü˙XkşžŰ”Á•‡—ô7ź_1¦ĚüÉě;qş˘ú‹ű›Đ›Ł-¶śd=%ËěĹk>S„9<㽯Ą ăW¦tnÎ("ŠűyD}öCMcýŇj3uĂSžµqNźhC8 ąý6e!Ü®wkĐDśÓ7/d^N:&Ř˝P#¦É{Ź*Č~ç<“4ţlżFJî?˛%ďvń{‚Úóct ¦tâ`*í,‘»w˘zhK:vuÔîbx8,ú.yĽc˝ęíłBÖµdОۅśëź’ĎîŢPt¶ÚÝétĺ†ô°`DLh~/ĂśŮöÓä±Ć@‡Â:8˛A:pµ`čŃ€&rĐůKwA‡\ĎÎ @Ůę•Íą uŰfŔČ^O´€î] ™Ö:ăśf¸wh„ÄźútęňFżâqbs®š ú¸}tSčo‡ł ą÷%†®±C;Ü»÷€ž`骽ؽĄ(ÂĂĺç‚–¨tt‡÷ 5Ő‚Včř.x*Â}\Ĺď¤ZďKa'B)ÉÚŞ‹#׺yóäşÝ\şąíî6ŰŹŘÍ -Á(t•˝Đ ˰´w feľ0“^ Ażč×›č‡e*ŇŰŚŇŢFţ AĎÝnĎ÷Ń(ŰîAÇÉ><źď÷óů<ßçvůÁĚţ˛ű8ÝÍ´ŔR¶Ş Q÷ŃŽśgUÓť«pfž ą˛B„–&CcÔ*Śu{…ĘÍŕůpVZV±ożm9ĺ22Ł]óv!{Ué2´LíčÇľ"1ąĽ˝UĎ63agő3WŻÎúÍ—ť»t@Zc˛HÁµ—–›MË‹űh+0fŚ3s_÷dz =v#bΞ*‘¨ @Ůč]45 Hź‰Ë)ďa™3±€»|ó¦ŇŇŤV‹ µ˝áQĽ)ĺ˝jŰč‡ÁZ8CRÄ}0ÉĄ©éR_!3N tąusŮÖZ— 5Uw;k:}űŘ*ZP; CśćU=Ž+ýIKuT :ŮŠĘZ‹K”ĎP§m±Ń‰°6Ý60 öQAH3M„;Î28צQŚôLLđ˘G㡆Óč± Dč€!ŰXiůîNbŔp;ÍĚ@ťž>>{âloo…ś'3Ńuř¨¶aH„@ŠTű OŚO¦@Č06ććîššŠLó“éĚÝÝ B’ůĽÜ  ă đ}~ţÎÜň)Ķí;V î®?ű5? ٵ§-¶aOŽł/{a33‡˝­2DÚŹá’ŽÜ‘yrÄ`ĽĎŘ×/ ’ĚkV¤ťę!»zD6^D;i~ŠDńO@°@„Â)”aĘT@-‡ëÍy¶ńľMŇ®ˇěŮJ‹Ę¬-o@ J ÉżKŹk áŃşÉă<“…ďĐů÷ Yđňňµđ{ő×ÖęKż+·VL<9d †L wŰ|\9ş&Óé ^súżW©Ô?4<żË9ď%‚(Ś[ţШ(‹ ‚ (H…ĂS9űŮ{oŘ»±·č{ʱ÷Ť=öŤ]c,‰˝Ť˝ĆcKô˝Ý…AapuŃřLL¸»0żĽťňÍ›oVf°ŽDĹ+ îď€ClBDÚáC Qřy´ĆGh&„Hť:8ŁDřŐ¤!@˘w4ţ`€hÇČŤ<Í·śţ,C9kŞSÇa–­Ü‚ĄÚWTžĽyńŹQ—jí7ÚŹD $ŞŰs„&"üćĽy ɉÂF›Ků! ČŔŰh ŔĂd— $¨ČŽnČX­-[ů/áŢxAuçČLůÖîa ŕŇ*¦$ëRe”„2rŞSmÔâç×ę=oGN>Ž"ĘěáWs嚼ŕ€E—6-ÖnľÚP2éG“Üţ4Ë6·š<śś+×ŐËĽŁ(.EÄp0 mtxý&żešc¤{LËŇn‹Éd9°p ćšő/rňzŢ â˘ÁÂQˇPčĐJlN:‰Sĺ‚ĐFŔácÜz«‚7´$ÜĄćă$ Ťo\®cU&µ@ň.ËŰ5߀´ňY)B@ŃŞ h”hđ¸“ę )‡_'8\Šx?‘Đ« ô WlÔxAš” DâCŤď™Ű<ĐĄ‘“KžĄ/*J• ČçóQSôęIŤ‡°ŕ@˘ w”ýŔ‚Q$z‚Tł÷Íή˘…W‡é)T#Š3~”ÓÜť×ţ šÜ4¤ë;jˇ€´”ş„.’5bÄę}* P8kMź5Ýä´}ZZC5WęŮLFÁYG±-ćguÎŔćńTă=ő81¬©Ą©U+k¶*!Px„FE=T|VËÎŃ~P8¶kŮ–°Ň3c~Öě-]ď "}Ą$HuaŤŁ–/!/KĂEź®>ŠčŕE•Şe8Lv-?/&Â^¦Úüz«©E™jGÇS»´Ę§áşN$ÇatóQ»őăIUŕ'ܢŔCĚ™q)â h=jg \í´'¦ĘQ+¤J”Ei•źÖo_©žËbĹĄôGrcvŢ|2…Gí.S} CÚ߇Zť¸Sg/FWPń[j7\ę9žäNńĽ(Ęü®¶˝ŠRgÁnµgÍľÄ}huî×#äÔ©hhűvťf˝ŔChś‚EŤcÍ=öÎ4˘}]×húdí§ {üVÓgD>7Îl_Íe±+ p@[<𩼠ţ1Ôu Űżpq˙lęÄč wíÚ•’ˇ8ÓÖ凖/OA*QÄŮŹ˛5żÁnr,–t#!bé ů?–T ¤›YŢ$ž(ˇ˘†ě€ňĄUí u@)ťâĹ•”hŰŐłYĚF=čdŘP$"Â×{ ¬Őlň:2ÚNéýŽWi,íôNÖs= ‰â€€çŇÁÇçÎť?ţÂk×nüb†rřÄ˝îąĺѵk ˇˇąs?÷é Kk^Ůaa.Q´Ç9F,MöŮűöŻîŤ¤|µ´F l†km$[@0®Ç9(ďîŠUŻłjÝ_»ví›>ÝÂ>q@źŢĹ4ô0M+ŘÄăËR#cĄÜǸµ•˛Ň®ËlÖ¬YffłĚu÷şúD݉mgľ‰“hń@ †čWF Ďţ7Í2 E˘Ůýn>1@ ŃH,ôÚy;s<ű0(VŃ•cźf…b"ó^śR¤ "”nUü¨Ń"&DČEQÓ€.®ËühťÉ_?•ˇĽ˛î#ˇă—†N¤ĘÁóńs RgKÄĆżŻzÄá6qÄâĹ‹[qń,»Ř ‘ J+[}áX 1n·›ć·÷ń•†Ëën:tdtż~-[Ží±˝7ó«}¨Ů˝°yş~ʱý[öë7zô‘MćđU¬§ęČÍ$»Îânč *ŰVŻ=ť1Ęî‘Q–™y_ĸ—®_ĄRą˛e2BĄmŽtS›đţ·¶â-5˝ŃjV«MŕűÉ(Űş·˘ #Öâ,ÄÍCµÄđÔ=l.(®¨Őş4»nć%\Ëx"îźÖ g=Vµ×Čš©»i„™Zž”‡8ŻpidőzBÁ]äW{ŞĘ'C,4QQ“ŁŞđľ «W‡‰JŞ…¶¶ yÍ,ÖŃd2¨fŇTuNˇ ’¨P¦)<&ÇÎ’âW{qQáyµ€7 ž^O-•UęĄYáň®PĆk†FÓ¦=EʼnŐCʡc+:Ó 01ąă®Óęř…jDa÷­S,Ą%˝Î[,QásUˇž)ŔňoNżY“IĽé‰F.áš9QÉ–Sü EVíŘY•IĽňój!î &®‹$‚bÚ5­dŞ ç•wWĎ€ĄťČ±_?J«¤?D¤UŢÝ’ëĐy‰űťk“vKhZUF:Pń.+ÚUtc‡y®H)áP+ÝłMQ©@ĘňŞ›_4­`$ĘmŰ^F|ćöjN3đŕÂŽ@żMÄMŽ1ÓK•pL^yđ”˛t>„GBެÚy¨cťěĚď—wP)ő ‰<„ ʶEký eÓŰ[ŰVÓ–%ńU‚çx&wĹÖ˝Şe~ H®¬)żÂÄČÇÇé˨°t,¬p&V9Z;ĹÄø··¬o$áâä~OŇŐŮĹĹÄÁÁÁŃÓÖ»»»¶µµËÍĎČÉË˝ŔĂ´ł±ş˛¨ÜßâÔŐ×ÎŃŐÂĹÇąąąŤh;­y8‹e7­v3_-ÂĆĘŔŔżíΨS7ŰÝßËÎŃ˝ĽĽîŃŻ°®¬Šb16$ ŮÜßŐ×ÚĹČÍÂĂÄżľ˝·¬ťž‡k­}DŽk@€T^>:&×ÚÜÇËÎĹÇĘĆĆÇş˝ÁŰÉ´éĚ«´™wâäçŕăçßŰŐ×ŐÓşĽľŕͶ¹­áɬ®|?ÎĐŇ·ş˝ŕĎ»ĆŔ¸łµ¸ľş´Čľ˛°°°Ş­°§Ş­¬Łś‚a°I±B†\)uMbAÓĎʰ˛µŇÂŻ±ˇŤ¦ŽphEÖűëĎ­Íľ­««Ş®§žŁ‘|ş—n´Ž_•yW‘qIÝŕäµ·şŔµ¨ŻŞ¤¶¦‘¦™Š¶ˇ•}`°‰Z°‡S’tQ°{:}SŃĚÇĆÂľşµŻ˝¦‰®š‚ş Ľťwh¨N±„M˛v,ˇk(ÔÓĐĚĘÇĺŇĽĄ§©Á˛ˇŰş‘°“o´’hµRŽnFŮÓËŇÉľˇš’©ž‘ “„ť‰r’oCŞp*‹\#xPoIËÇÂäÔŔÎÄ·«Ż´ńÔ±˝­™Ő±…Ş•{ťŚwµ•n¨‹gś[™e&ĺáŰč×ÁČą©Ą˘ź˝Ş’«†YŔŹTK2ĺâŢćǡăĹ ÇłśĂ­‘°Žc—vO”b%C-ęʢɧ}Éśf«y=ĺÜĐáŃżŁ{KMNt=÷IDATxÚěVKhQ5QŃ6“ĎĚDš“!¤‰qŇĆZѸq!(‰ ( ő U(QüB‹  ˘Pě¶Bˇ]*AŠ˙UÁŞ7nD´ qguí}“Iî<«yIgşz $ŮÜsĎ9÷Ţ™ËXĆ2–ń?b% 3Ë,=*\:ÚĘłĘ,5*Läo†{gT1ȻЂĆŢ(yÝ*K1żJ»FŤť’ŔnAFłWĄ]»víę*ŕ7j`rŘ=VÁ2Ő…0H¬Ń¶´¸\­*\--Ŕ®‘3ąő&¬&eŞUŞep —Â~ŤX9Îćqxl6D9H@n– Ş ­­ś­RĆe I`Ż h’,Ę٬(ňűćŮ­öB_8ŕ%Üu@,c“äbqčĘóŃ{˝ýý‡/üúú`8šďx‰]¦yŕř¸8´?1?ďÎt§ÓJ:ťČdŇă/GN:A ©˙aCĹąXžęŤĺr©Ŕ—ĘĄüž,„;yÉĂ™§™ˇâ›C,˝{q(“°Äăv»Ýř‘ÎXîŽ „:ů¸‡ ţîŐţ‘9w.ôűťÎ`Đé„o·/wtęc!$Č`DM‰ţ«ÄŮüŹéŚ·[ô°Ç-ĺÎú$ďe(Pűç% `аŠńţW·ÚĽŮ÷ÓÝ”ýěJâŮž!ČÍŁc„2j˙Ą>7Ő?żŻ˙ú)’Ë<Ä9Ě˙ÄŐÝ? »%ńť(@óţfřź˛Âđ×QpřÉÁ$©Í˘ń4ç’łč˙?2°Üß DlHŤh6x‹#A7ô_OAęÆŽďŕZĐĂ´zÄňË ô__Az|;E˝Ŕ†8ÓKćżľ‚Üą˝a™$i,dvqRéVÚna!žůŇC¨]HŤ¨”ťĘů­,cg÷ȡ†hBł Ŕ„˘ŃźT˛#n+N߇ ëB˘ÇhČló–~Ŕ°aďľ»o]'F€9Vć0?G`+HMîm$aóŔŔýžh ¶:ÖR0#ŘoBČŚŔŽŕˉua/D‡'H,á0;ŰÓFů°HhĚ2ž ć!şş«˝‹wPŢÁ7Y$ś žĐú§ôܵua‰dă JŽa (ŻO´ µ5¦'čĘĹ µÁęÝŢNfX.R~W°EĆ ˝¨ĚRă=Oů­ŤÁíz-ŠAč˛çŕá łďĐŕ&Í; ÖçČŹâ 0#ěé ‚\´#*–źá 0—`lăzôNż|Ű=®ëi|»‚4¶}w ĺĐÉÍôh9JÇý1ÜćďkW(ĆpyřÂŔĆAťwŁĆöŮŹ˛ŃÁ-!ŃŢ&(ß)ď0ÇłSŔ¶ąý›ş źˇĹ°'žŢÔĹ#5îđdĘŮD(Ŕ„^6! sůôúde‹WŞ/B!˙™:B¬41ü|şŰL^h\D(Ś6#ŕö>ł€%îű¦XˇG;6TÎĐ*UAő á1áľKlҲ‰áŹG”†ě<fC‡ ©wŘĽBű_ĂÜ7źîÇS`(ÂÝŮoÓL(G®ďÇ; Đ®hřŐцŻÓ÷öřVRÄč“XKżs·Ťř˝yµ+Ú7ěw7±ĂǶ¶ Ru ľĚ9„\6”ł§uűW;e}¨ţY+pisT»Ć_§|×úń´}q‚jŤ@ţAĂ;ŚŢ˝kńËżią–&˘(ęoˇŃ˙§J¬V§­­c‡ŇĚ´iÚ„¤m´µĹÖ€ ‰4MI\hQŠ7řÁ "Š ţŔ…ÁhLÝăon]ąđľéč›[jPgx+yÉ;3çÝsÎ}ópnĽZdř! @ţ˝-ěőy´‡µn‚=Ďţţ <Ż‚×O(4ÜMóäřoŕŐVIUÉ&iN4K-OssČc …®ůÜHŕ€Ží9źgňČČÍ xń䳨'ך)˙ġ0ŚbC€'~;ŻTŞcćŇď xťFSňr1ČŃ(qS%بpk–ađóĄo?ÂYż=áµ3Ęű§:ff*˛wu ôľĚS‰fRm›@9¤ÖÜžˇ¶¶¶ˇKŮ‘ć !E ‰Žá83oĘŔh[:Ý6\aĚËĹ ŘEH´Ň*nm묭­mm˙ŕŔRL•L­c‘X_§;ÉěöÎ †tŢ1´ŇÜęiëlŤ‡BńdË·0ú[}ŁëWš˘†đzĘĆľ4L†ŮÉ–á± ňaˇ‡‡:“ˇ×덝ę(F»řU2gꉎŃEŽĽk•'§B-/DXĹ™A¸§˘ťCŹ‹€đçtkČ+#Á¨wË6‡ZnËJ¦tV!Ţ١–y•ŁńĚd!Ö4P¸1hŔA Ň‹CͬWźß%SBŔî¶„Tpţĺh•›vVä“Đ1’Ç(*ZCű &{Ş}űA ňᤇ–˛¶'jÂßÚC^ŹŰʼn¶@Ő]°Ű”B/¶Ę™Ś "ˤvůzwą ŮDÎĺúş Őä2(׏AXË!˙h-ŹJ.†ĺ9÷Ţjw|ż3Ů4żůĐŘ@ďń}ĂłŚKŞŢnÍS3赩-;‹3ă\“`çX§…wŐ=:ëŘ)±ôtV¶Żßj'ňٲćŐ$ĹŤż¤F!ž< ,`K·dĄů›uv“ú0ËĄ–˛kCU'Żâäi"•b!n¶ŞµŞ˛¶NžšĎďą´1¨ŻIp9Qw[/Í}]cŔ ”#.ŢIx™ĆR¬¤b˛ď!Ćbk‡-`rň6Ě cĘbúČ´¦ŢŢu˘'Mv˘Ó•d„ˇ1Ş˘ŽÖ™ :lÄ®$0e1Ý8Ů޲éSâĐ‹Ş€\ ą’k€JŠ 1Đç%,fă~Ä ÂŢ„ŕR uY?Îö&°¤8şÄŽŘ–…bţ©I1iŹ’U‡”D ĹRm !0‘ lG—`DŚ1Hź:¤´7Y;˛¤†ő·Ş$‘gy±F­Ň†‚;UŠÂľUkw«Ďč—¬{íÜĎr›ęaŕ˝ĐWŃ(Ł7‡°%…N´ĎcăĆUó Č€ú»OČm čě˘b/ŘE·éü:Ä ýe¨§t´¤†¬ô±»8ÎUó^]…ÂöIЉ’Á >ÄĚ˝1Ş«PŹ_€y˘ŘŹsfŢe0˘ăDŤ–´Ů ¨7[Áж’´7ÎH%›źËýí§č>ďě›h+ëŔ5ô%±Ŕ ÝlDvŹŰ Ůx’•ŘÜîćHŠď7’HB>Â…ý ™űˇ5­–[Ż<­¤k¶Čę€Ú µk¨ó@,‘ö!)žĽĄ‘Č)|/cîĚŮKť™‚„8é$·»äŚě/(®Ú:;Źl„ľ"vâX¶ť8ęT&y˘¨<­Ż‡Hł|;ců^yíˇ-ČĘmʧŹ=Póoᮄ gQ˝„-é{´ ÂbŐAO čGRĽľĘąi|ÓNî¬ZŕÜsK˝‡Ť/[bŃHŔłő *˘ăŘťě8ű+ő‰ę`PđßARü0´±ćE«ćĂ ‡U Ěü¦‡F$ÄÝĺčëÇôłÜăŰívâh¶¤M©h´ÚŹBeř9.βnG,[ľŔlqťĽ„d ·;äŤV7îÇ  "0! Âv˘÷çN$c)o*q´hµZŕşcAđ &óÂ…fËI‡Ő‚µ¤p ;~ŇvÜ“;—égéĎ ÜábKž`SÝqŞATl˘jŢ&î•4Ć€1 –t<`Öęď¤q2VÄx-wřQV0PKúTJ˘Fnb(vŘ|‡C+\—F»!…âkń2ßp›8O̧FřČŻ5é•č÷!Äô̧Ëă;Ş/€öxĘ#fBś7ŕ¦ŇAźżrnţr3S#|ÝŐÔŢV/”QĺE€Łń&L“ pб±ůBC_ó +.WÁ+€K\ĘMPşjôĂż/ݬĐOb®<¶Ĺ8 ;’ą)zÓĐÎŚ•bް:V-–P6ÇwÇ [çŘ"3–­q íH:÷Wâ–0·Čüă;"ŽóWĎďó­?m}ßĎ'6Ďßmú<}ß÷wĽß÷>–Ś—m1÷o`ţ­ůvtŠ-\q›±€ď»VC‚ôyţň´¸ťŰ!˙Ą `C֤ǺÁ…q&%ĐDRaHw|¨řr˙¨âk=qL“.€]ɲî»çŚVSwŻ`ţ˙Ôńm)ţ›µxţ{®ŻgÍcI@{ľăW¸‚1ŕ—ó°ŕ©0ĐdáŚ#ŕőŔ“믑˙˙h­,(„‰#9?¸ßĄT@„­€çŹŻÔŻ«´uóÄř˙ďMŔ‹üŻlhÍëŘImUŕŻSä:Sö/¨Dĺćé¸}‘ű:mšt_!–ş˘­‹EßAëÚ‰ÖUĺ ŔZ´+…Ľ‚a1YôÖÔ2Đç¸xłˇ .ťĐÜň€“p,ą«YŤ‡AŁOĺ  ĂłzĹtUÇĹ/…źČ=Ż×aŇ^ô´Í7pňŕ%äÜYĐ-¶ßüËŕS5€mc:öë–r!'Hź„ŔeA×KěÎźç@%Ç«m¦¬*¤V”ü'}Šb+ťňíß.FB`‹*™swČĘ@»żS7ág9Ф*CÂ^ÁW<±…!qÚäŐ ŔóĘ´Ä0¨“Š^9…»ő±„ Řü +hŐ @źˇhLrĺdFŕëćřřđŕţR•Gá â Ę*šB"5ĐPQ¶4>,ě-Řh·ŰŤ"@FŔÁ÷6[ÁusÖŃ:,d•g«ŻÄŰ(˙+Ő™°·úpëĂ9;[€,Ó绝)c/E¤ (ý{˝’µ%őÜÝČTş…±× l¶Š úÂÜĂŚš,‡Ă‘Ő¤ş‘% śű`ąŚ9P|řAbţĘ9˝»¨­¬ŤŚ:ĐŃę”Á÷x ąoʰîöŁn‡Óéláö±-qÔŚw›yúcń¸JÝ8çžćjsPĐAłp‘€”?¦%$ďf‡Äv\ÄnD—6qóÜGĹ‹*-×0Ϥ»WŹ /7[ ęĺHŔô„äc 4i;-ś:·fÜN·˙śQD; Ë-V+l0PąuXBżNZ…ű˙ĂAŮf ¨děhć”đ,Ă~.ŕ& Ä  ü˙Qö^ŚGßc'ÇŞ;čČuž `x±ňŤ,˛éÍÉűţh†¸ú-_ąďV+;ë(!kâ ř>btĆĆÚü!°zÂD"ŔVđ«'*á_Ź9ů! vŁ”Ă;ň”1 3„eöń˙7TÉ-ÚNó÷Ó»d§é;!ůsşuŐb€‚şA ‚Şńü {t˛Z*u–hsě¬˙M@łíECŕír±Rĺü`E¶ÚWä˝dĺ ‹Ukî2¸÷Áö˙MŔăĺ'÷ęBzD¤ĎEÚŤT€©emΓ6iTZvFŞ«09ev¢Sţ[ =ľżjoÂě”äBWjFvÚ¨¤†Áî" Đ­ÓôůEŔ»” ''§$çó_§ĐÍ›Ő! ÉßJµ9]˛©¤$82^ĐzSŃ]LëľIڧ°á,ú¨Ýß±Żn/-µßÔČ4MŤżÓůč€Có’¨h˙ SEÚTaÜ'Y(5>=ČrĽv·đűjhţ.W¸+ Eî\ĘčŁđߤŃŘE[.›ßM‚›öę™îFé€Ç“îiô:PCz-``”ÜÉBPf¸Ó–DËĽ¦ĐzéEPúÁťžŢ¨ŹFĎkźd˛Ě­ˇâ ÉŕË@Tm ˝’Ü–.˝Ő'Ľ)úxú”ł°›[ÇRÍąš^BI‘ľH}.c  Şŕ¶Lbşďx!GS€€+RSČîO˙0énŤÔzş‹˘¨*€vU$ íąŹ”8ĹÇ4»ŻÁ˘EśŃŮEHË OŁßá¶Fr „ Č-î°Ó§ÜL©Ŕ°ľűvŠ„Ä"5űü`J¶źZY¶™0őU~s÷›×‹#DIÉ&‚-Ŕ{Ś—HŃń›đĽ–Äc*—_ĽŘp4JČ‘Âë]˘S Yjóńči%ÍĚp%§LęÝwř˘üü˘˝Ë7®ľ˙dPi«(„ĂuTŇQtץKkîo\ľ·(?Ńđľ˝'Ą$»2hwNđ0‡ť@©ĐGÇýhďZB›˘(µ ń­51żF4&Ť1“? #‘`ˇşhPŰL޶"•*ÔKˇ*tˇH)B-UÜV¨`µZęBÄť"ř»şt%¸ôĽyŻ>1Ź)3! ·Pş<çΛÎ{ďžsŻ/€µ'źi/ ą5Ó /ŢĂWL“˙ÍçGś:÷˘ď kĹPţ4?Ä}ňµť¶m›ţŢĽ‘÷č·“ł€ßĺ’čX€¦ CäfEĄÍ Ŕ8OńćűúđĂĄň 6•|2€*Żěrůc8ëˇ*ŰiĆ€ŽŘ°zlÄ|ĂłdJ÷ęý/Ź !ŕÇnčŔ™{N˝ˇO·ŽDóź ä `J‡z4ćř«Ép˘„ĆÂî%đaZ— t°Ő ô@&1Áć´@ä˘ÎXŮŤőńŁžy}px»üňes“łI•r†Ž#’•jM܆žę…C>¸\‹xŘÖÓË•HŞŤćôivlp8úëÄ•®BP- lP‘Ń’đZ…ëýĐ‹đPď·ě6ż/?µŞłý*+őÇ i_Äc§7Z@Ž(_p»ËŐvŕ€CŽ×ć:ţ¸v‡úć퓹LĘŻZ©VN$\Ô>Ć‚©éŘ I>ÁzmŔĎ”!‹ŚĂŕWŇűS`Ce`Ýé试^H™Ý7Jx˙čňŕ7ÓBśG|™7ˇZ(¶č ăC,4ŕW?BÄvíćíń}µĐĚ äšo›5¸WÍ/˛|ó†ůŞĹÖŢą\;—±79EDr“uŁ˝3ä–6ËĐA4Şî8ěčżq"d–r—ă׼Ŕ†ŮŃń"“E—LÔN+'ç€ß±Ó(#‡V°ž28˝`šz]ůĐMń«.đkĎ:`p,ó-dŽŔý¬\hČ?'6č˙ˇĎ ,F_ar”Ž«… Ö?ÇŹ0ĚĹ tÜ˙řM˘ÁX#Üex&wEa ­¸DÚđ™v¤řmó§Ž]ł„,`ő»·W墤Ťńhvý?-T€-.„Ú,FüV÷‡É®BÓ›ţ ¸‡V_Kóăb3Ŕö–fĺÜĂr=]aÓÔđ]żÁŽJś/9:4v,"‚ě+Ó•îŢ^TS©¦f-đ‘˙jS UAW<9: m °JM€ßűz WhÇńťjjsA Żą…ʡäxçŕÄŘřľ ôş ůkÜî“_r˘é8V?ŇżQ 2lŔ)»ý’ ěz˛°'qôhzĄĺŃçŁ>Y=V*i8­ 1«ÎV…Áf‹cS ‰Ăx,,őřč*Í=•™ëŮ(ô|Řă…mÉŘô‹]„‚׎Čńdçŕ~p8J´–żv€oU”–áĘd9{!šI¦Tř»µŁiµřM\G„‚U‚˘"ě<]ś˙qiO(jŰŰÚľj¬iv»wKĎěĂą#Y$?đáćM…Źô›_¬J# ÉJ.łSIp(í/>y·péÔľP(‘V;{8igEq»[§{^TžÎ•»JĄhĐ˰HY±x|VżxĘ3» V9ŕ9¤/_îËž-^ś{˙|aŠ4W9őuş÷dĎđŁŮ»_žN”»˛ĄR{G>HŃ /JżYŻm‘-q9Rdš°čëËvť-Z\\,»Ź˘ż4€#::ňitF—H‚ŢN'K×>|d7ŐGá9H°›ÉŽT<Dm$e%2ZĄv€— ţŮÁn›|Óţů}Ó|ęř&"“ň€-Ž :’JĹ©”N4ĐcŔÎÜŤLE׾Ů鏭g¶Sb~Ü a“`ÄC7ŰX żĂaI˛şŐűŰ_şŽËMNżăŔHjuÄ& /«Qě+ŕzÓł/^Hś%±RAleAţf5 ž ŻeňĹŔ‘ Ęř°×«A Ů­É˝±¶Ésŕ$xy„/Qě}$˙ścÂCSٍźÜW«+ -ôzË}š¨đUyÔë˛2a?uźň˙ń/Ç/éyŰN–R~”IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/android-icon-36x36.png000066400000000000000000000024451433544061600244010ustar00rootroot00000000000000‰PNG  IHDR$$ÖŢhŞPLTE˙˙˙W!¬r+ýýýůúú‚V ‚Uűüü«q*řřů¬q(«p(¨o*÷÷ř­s+Z"őőőöö÷űűűôôőóóó­u1ňňóŢŢŢÝÚŐ¬t/ěíîęëíééęäăá†['„Y$ďđńîîďíěëččéŕáá×ÓÍÍĘĆę́Ȱ”°~BŽj>„W!sL\=ăäĺááâçĺáčăÝŮŘ×ŐÖ×ŇÓŐçŰĚäŮËÝÓÇÉÄĽéͬĺË¬Ę¸Łł¦–˝Ł±›±™{¸—n˝™m—xR’rI®y8Žh8Šc3¬s,…\*¦n)źj'‡Y!V N4íęççććíçŕâŕÜăÜÓÔÓÓ×ŃÉÖÍÁÓÉľßËłÜɲĺͱĎÁ±ÉĽ®ż¶¬¶±ŞËĽ©¸¬ťľ­´ŁŽ˝§Ť»ĄŠČ«˛ź¬ś©„Ģx¦’xľžwżťt¦ŽrŁŤpť…gµŹ_ž_°Š[—|Z´‰S±†P´‡N±JŹmC°}?Şx<‰a0‡`0•c%{RxPiEőő÷ăáŢăÝ×ŐÔŇęÝÎÎÍĚŇÍÇĆĹÄçŐŔÍÇżŕŃľĆÂĽâĐ»ÚËşÇÁşÎĹąćǡäšIJśĘµ›Đ·™şŞ–¸˘Â§†Ł”‚¨–ÂŁ}Ŕ˘}Ó¬{ˇŽx¨Žn¸”hĽ•g …d·‘ažaş]š|XvL–tK»‡H«HťtBŻ{<Ž^$UI1G/9&8%ÄeŘěĘIDAT8Ë•ÔeWă@`’ÉLŚ”Ô…BŃwwwwww]–uwww߸Ó$K…OÜosňś÷Ü;’“ i-^€c„ŇĄ¶Ą( {1ĹÔĹ‚`“:±éZżß,x?hbF>U–™Ä@E·}`†×?Ź đ˘©8AË:Ô<‰‹Ń@Ő \h2šöGŃĄ"ů9+Ń˙$$ôçúˇ72â–ă4 fÉ裑őGQó ÚHŁ@ÄgD@/ZH H:ăÄBż[ÍąâY`rCbż«ŃpÚ:*{‡3 np]jşŁ •NGÂŞ-đGŔZĘ›f”đú>­ćv Z̶ˇ#¸›Ú|3W’1Ď›ó Tż >M_D¶‚p(Ó&ďşUČ‘Îhő§ŻW˘m».Ëc†#™ń¤đ"DLMp6™ >`uW·:ifVÇFË íIÚ°»TÄ;^˙µš$këńđî´Ö4lÜk†dđn…†a ¸ÔnŔčČŃ‚šbSžé±‘T‘ &‘ŤĚ€îBV«©´šô!ąŢĆGdfĆPrPK®Łw†ń•JF ‚Ĺ˝ŽĽsj ˇ¦kywľHčÔŹPF ‰§˝}»›wó¬ ! 4Ç;Ą†DOťxĺ¶ŮŤ:ZąßQq… )%…ů'đeşE`[2łç0RzŞJO˝ęĚJŐi¸+ŽÜŞ˙ł%6ţň4HŰ™\Ç «FBYÝ=*dŁŞküűç÷´çěéΖ/e‰K»¸Ě+Q žĆC%­]N7ĆŞ•÷Ę Q};ś’…ĎN Ş°Š¬Ćw€´%˙;¸ăpŻ )衳±C[ çCą‡Ťâ±źAtgá–™ç¸Úzm{6Gî&‰±ń÷JÖ]»•îňĄ{˛Aé&8L ON»t5Ç~Ţh5řÚ f€bE­VX“Ö?KPv€°ž¬IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/android-icon-48x48.png000066400000000000000000000034431433544061600244060ustar00rootroot00000000000000‰PNG  IHDR00`Ü µ”PLTE˙˙˙W!¬r+ýýýűűűůůú¬q)‚VřřřZ"őőöôôő¬q*§o)ö÷÷óóôńńň­s+íîîěěí¬s.…['ďďďćçčę̨„Y$‚Uňňóđđń;'éęëééęăăăčĚ«¬t0ĺĺĺÝŢßáßÝÝÝÝŘŘŘÚŐĎ­x6©p*ěěěááâÔŐ×ÔÓŇčͬ®€G°}?Żz:‹f8_-†]+„W!ččéëéçÖŐÔÝŘŃĎÍÉĎĘÄĆĂżÁş˛´§»¤”vRoG±AŤh<Źi;Šc4­u0śg'{RęëëčçćßÜŮŐŃÍÍÍÍÝÖĚĆÇÉĚÉĆŇĚĹĚÇŔĹÁĽËú̵µ±¬ëÍŞŔµ©Ăµ¦Âł˘Ő˝źĽ¬®Ł•şž}«“vµ‹Wš{U˛‡SµQ˛…N˛‚G¬y<®w3‰b1€UyPvNb@^>\=[<T8÷řřăäćčĺăĺâßßßßäŕÜŰŰÜęăŰŮÚÚÝŮÔÖÓĐáŐĆčŘĹÉĆĂÓĘżÁŔżÓČ»ĺĐ·ŢĚ·ÚČ´×ƲçαâĘ®˝¶®˝´Şą±¨Î»Ą¶«źĹ˛šż­—¨ž’·¦‘Ĺ­˛Ł¬źŹÇ«‹¨™…ÁĄ‚¶›yŁŽtş—kŁŠk™c¶‘b±‹\wN”sJ–sI•qFŽd0Ąm)™e&•b%‹]%G/îíëďëçâÜŐĺŰĎÚŇÉŕŐČŘÎÁćÔżÝĎżĐǾȿ´Ľ¸´ĘľŻČĽ®Ć»­Ńż¨Čą§ŰÁ˘ł«ˇÇ¶ şŻ Ę¶žÍ¸ťŕż–Úş“°žÖ˛‡Í­‡¬ś‡Ä¦‚®š‚¦“}«–|Ż{ŁŹvťŠs»›rżśq™†oÉśe˘†dź\ąŽY­…Tł„Jµ‚CŽk?–m;˛{7jFeCód%JIDATHÇĄ”ĺÓ@ÇIîrŇĐvm×–¶T¶UŚccĂÝ}ĚŤáîîîîîîîîňĎp%MŇĎ®{Ĺóâ^¤żďc÷ëµřĎP@ójČń“yNR(_x6Ă^?uNŽ·‡Ž˛\kđr¦‰€ÂÔ_Ş;Wîy éí9Ż—””Źöę9šÚiď»"$"¶r±ńßďP·đ¶al‹Ţ3Çé ĎY)PY@I†É˝Ř)^¸@čŰ…Óú,ŇÔŹnŇăŚfŘĐ­íĘĘ%vűHB®çgąÓúž“kÚ‘c«UsőđŢC Ŕl±đ   żoR€´<Ú@ V*PĐ-LC€>8a@Čěč⨡{U˘ŕŔ1äh{ĂĹÝŕL"ŤŽć¨˝rY;4fěŻäiá }D4Ăn|KNf»O¨{p\NĹšˇýFǰ¦ S´ĐĽ‰äßŘďjf2WYx BŻ2ĶžtĄ-ËvJ8ăX˘\“Ô±–Á…}čŽHO®Í&"M&˘ť> çúdcştôż";Ú§4ô´˘^5Bűü:>¶Ü†‹ĺ b. őî öÔż ˇŠdłĆ{Ŕ†‹ňWg2xo˛d jlă]⎝‹*J]0ĹŮ»ăÂE{Čä‰!ëQv TíĐý­Ů4bxA’{h1ĆĹů..2Ŕ»ß!r©"ą Ó‰ŕţ‹>sWq{D€s^'+•,ŐŞĄd"†iwGÚýXÁ=’l8Ŕ`Śß$Qsó„Úž›ÓY`j·L·s0(Jó§főL0Çn'Fp»ÇqFC‡Y4P±Ńü”%ç˛×KzP‰‚äľKRćeɆâ:¬°Z=Łä˙tT”´%ŁR¬Ö3äáaÂů•ëGVŠřăÝÄĐ˝N.˛{gĺĺéBěÜĹ·ż#)˙‰†‰Ý˘@ôő“&H5đĎQCrăꓨqůGŽ 5Ôr’4@ŢmźD”­jŚqLň¸ÇŹĹ‡ZŞ›€%=qx’EŻ… ¤?=#§w'¨CG íŰ32đ,/pFŢ+´Ď÷xRžteŐ8Ú˝ˇńĎxFŤč§)ĎĽŇĐ|Ńjöđ—z ńżŰ¨WőřČŁaVëĄX­ŘĎ.]¶üć7¤őA 1 8Ľvů˛Ą}ťRŔgg÷!C(qlâÉ“Ťu*Đz{żdż/K€ň–ťĂŐ»L-ßm|ظpŰ`×đŞűHđÎ!ťĂ‰p/e¬NÔAŐßĘußJCÝš±n¦Fx4îkU¬HEkf¤i)Â2 4ťEáfjß šn–ô4ˇ5ú†”¦Ć‰­ZbŚ™čâ]«˛-:HéSéśľĽĄűSÓjŽdü(ą.ßk–-‘ >4Xâ˛s”ß?y¦Ű©8®yň‚ÝlLča4č5ZĐrš!P0¤Í˙wüŠŃL>@–IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/android-icon-72x72.png000066400000000000000000000052611433544061600244000ustar00rootroot00000000000000‰PNG  IHDRHHb3CuýPLTE˙˙˙W!¬r+ýýýúúú‚Uüüü¬q)řřřöööéĚ©ôôôîîî§o*ńńńóóóZ";'đđđáââńňň«q(ííîěěěäää­r+ęęęčččĺĺćëëěÝŢŢÔÔŐéééççčÜÜÝĎĐĐćććŕŕŕ„W"Y;`/ßßßŰŰÜÚÚÚŘŘŮÖÖÖŇÓÓÍÎήz:­x6ââă××ŘÎÍ̬t.…[&ŃŃŃ”wSŮÔĎČÉÉÄĂÁćÍŻëÍŞłŤ_zVŹnF‡^,Ů×ÔĘËËĘČĹĆĹÄČÄŔÎĆĽäĐ·Ňŵļ´°°±ľŻś­–z·Ž]®J’oCŻ}?Ťi<Šc3U ~T {RuMcAĺâÝÝŮŐÔŃÍÎËČÁż˝Ŕ˝şËĂąľşµß˲ʿ˛Á¸®ął¬ç̪·©č̨±«˘ą§‘¶™v›\°„O—vNŽk@‰c5­u1¤l)W9ŮŮŘčÜĎĐĎÍÜÔĘ×ŃĘÇÇÇŇĚĆÂĂÄÍÉĂ˝ľżŮĚľ×ËĽČĂĽąşĽÎ²Űǯ¶¦¶ŻĄŻ§ž±Ą–«śŠ®š‚Ą”€ł›~Ŕ˘}ąť|¦’{ś‹vľ›p»n ‰nĄ‹lµ“j»•h›‚c˛‹\°X¶‹Wł‡R“tN˛„LqJ±F®D˛~>«z>°{:Źe2—d&xOjFgD_?M3ăâáéäŢßÝÜŢÜŮĺŢŐŐÓŃäÚĎÚŃĆç×ÄŕÓĂŢĐŔĺÓ˝ÖÉąÍĂ·Ĺż·âÍ´´ł±˝¸°Ć»®¶±«¸±©Çą¨ľ´¨ĺơşŞÇ±–Ůş”­“­˘“»ĄŠ±ź‰Ŕ§¸ˇ„»ťxŁŹvϦtˇŚrىiś…hź`Ä•Z¨R«M˛DŽg8Še8‹b1źi'śg'pJI0E.A+ěčâŢŰÖĆÁ˝×ìϿŞÖľ Ě·žĎ·›ŞŁ›Ä±šĄźŕľ–Îł’˝Ş’©śŤąźˇŹzŔźx»›s©‘s—…n¸“f­Ťe¸„Do=©x;‘`$Ž^$‰[#‰["}S mHŇípKoIDATXĂŐÓ@†i›6 )Ô©+P ×;îwwwwwwwwwwwwwwww‡awIZH Ă 3ü3w׹$_ŢżOşI‚˙C ¤H‡"ST* JĹ]ŔâŽDfsIJÇ0Śš$ JÁJ Žč(°"…‘ŚŃT©]Ć%&ł†‚çów (ŤsqĆöą|šŔÄBBmĽ~gĚäég& .č7ë@P‡ÔxómZęl‰;—Yi“ajgÁ iőz×ëŁgô2Ärč%Î+‘’5­ç3R€$Ρ̷¦ëĺ¬půĘ^<…Ş©tL¦”±R–.â7âë¤ é®g‡ÓˇX3Ľ1 ŐTTĆKy¶P ˇ7¶p„ÇlÓ˛§+HC­2!é„ĂI‰…¤ čn3qą@ú©Í¤ ÝÂ: ú╢v‘¶]ňáS;›Ô€4ąJ Żn7ĺ}®%Ĺlöi  óCgBoÇ@°b µix(m‘”ŕľ0ÖŢéÂ@cNR%‘u0 µ/•Ť-FYĘÂ@#Ą@®]a|É@ÖĘ0R˙X1kŔ€9˙L<4ÚăYĆtÚQa u€iŃôrăëýg‚ fqcâk{R^źxú1F{ŕ;gĆ©Ô$O5łß!šO9÷ť44˝¤č"­‹FןľW¸oq|ÖŢ +7 f ÷ÍÁ:ë˛ Ţk„ń»·˘=â; ‚˘ Ů5Š0t. AĂzÄÇDÎ2äők 3qo{äďńI +˛ž›&0ŇX+'¸<Ý“LžŕPŃ@ŕ ‚Ä˝ĺ/ ŤĎěÝ*2` s=XŹ9xbŰf‡ˇ•®#ĺ ykýZŐv¤Ş‘ň†©Ś$F9wĘĐĺQőĄˇłě|Č™„·4÷ˇ#ůAOlç©0¶©ÍäÚŻ——Ş»“őŹň˘ś‰{#-§"oyŁŞL„EY(L5ĘQ5}\vDÜ0RŘć)_@o1Űąłá¨´˝j’ńŤRÂTĹ·§€ÎŽçÖę0iŰZČa$2Ť† q™|4c 4† Ćńą‡*aöú9,×°˘ýVY*^ŁS#ř·IŐX“EŰvďR­kŃM#gŇŢü`¶Zôë+ź¶%_•¶Uj4cëq/šJ‡ o¤Ah(­‹†ˇĽ)<@Ĺ'/Q" +yňäs…î†ß,”ŮcG{CaéśvOŐqÂ*@CAó„Ţ2îäĐ)BÂHĆ’2_q(éěyóË·ä­•ť—|ţ!¨ÔƠߦ) ÔŃŢ”ÝC›µĺĹDÂĹ.S®lhónńřť4*má~ßękí©-*iŇďŮű,ű•R4ČXčĄÁ"ńI[´¦@ 5oOç#âi‰f—-›LŕŇv·WÍ  ,…;w®ŚÚ âÚńV˙§ĺśV—Ż,¨đĺgĐ»¨$a!q Ărâë‘oŹ IZ• •9U]łîéąaAYą:âSw•]-˙“»álʬHĂ׳«ş®-ŘłçžpĐ…,čJąpPΆµz,¸¶k% ĹYS‘SJGú`¦.Ů~ZíÄtů+ŕęqc|î`úÔnîQ =6:]&ęŢ1řO1%»Ta.×d|@ýr§ŇZ]fŽŕ{N_88ikęCżĘ˙§_dd‡”N ÍDčÔV@acę8á÷&ä¤j©,`@ýꉖÖVź¬Ź R6Űę°Š~I˘U7Úk5‰ü-’ŁOzT=Rµ6{őlr\”ˇNzvćK“üU†Dăâ eŠŁŐGŠdĐ:zgk˙¤L—ˇo¦TVŤ‡Ż)ŤËW$[ އ”ĘśŮ÷ĆGŮť:BŠĂO9µŃ«7dbtZ€Ř×)¦#ČŤ&Ď‘~?˘3šÜŽ<µ6ĘÖdZÉ÷łÎ•š’}d˙­™©í^E8‘QjÚ’Ćž**.ĎŤB…6Ő¨–9“ǰ›Ě´FǸÍeŇúín·ÝîÓZ-FšYaýv;š¦5ŚŽm…q ńg¬/·đĂöŐßľoCڤo×3…ţţ<ĄKIEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/android-icon-96x96.png000066400000000000000000000071411433544061600244130ustar00rootroot00000000000000‰PNG  IHDR``ŐF‡ PLTE˙˙˙W!¬r+ţţţüüüúúúéĚ©ôôôöööńńń¬q(íííůůůřřřďďďęęë‚V‚Uěěě§o*Z"¬q*ççç<(ááâX:óóóÜÜÝÔÔŐĺććäĺćäääŮŮÚééęččéăăăÝŢŢŘŘŘÍÍέs+„Y$ŕŕŕßßßŰŰŰĘËËÖÖ×ĐĐѬt.ÉĘĘ××ŘĚĚĚ­v3„W!ČČȇ^+âââŐŐŐŃŃŃŇŇÓĎĎĐÂÁÁ˝˝ľ¸´ŻŠc4;'ÓÓÓŔŔÁ†\(ĹĆĆč̬mB®|?~ToJŕŕáÜÚ×ĚĹĽ¶¦“®x7U ŘÖÔÄÄĹÎÉĂÄŔ»ľĽ¸ÉŔ·łł´čήćĚ­ëΫ©ś‹·ˇ‡łŹc{X°„MoF‰a0Şp*fCăÝÖ×ŇĚĂĂĂĽąµ˝·Ż±§›µšz”vQ­~EŤj?Ż{;‹g;{R`@ßÝŰćŕŮŰÖĐăŮÎąąşĚ·Áş±Ěľ­·±ŞĂ°šşŞ–Ą•¦“}·—q›‡o™d~^±‡R®J”rI­u1¤l(lG@*ÚŇÉČĆÄĹÄÂËĆÁŃČ˝ČĂ˝ŔľĽŕηŮČłŔµ§ćÉĄ˝±ŁÁŞŽ¸ĄŽ±ˇŤąťzź‰oµ”kź†f·`˛Ś^µŤ\°‰Y™yQ«x:ži']=éäŢŃÍÉÓÎČËĘÇÖĚÂĺԿƾ´¶µ´Ň°ǽ°ĂşŻ¬­Żăʫø©±­¨¶®¦É·ˇ¸­ ľŻśÉ´›« ”şĄŠ¨„Ż›‚ź’˝ˇ~ˇŽxŞ“wşśvżśr¸•i˛‘iź_¸ŚX•wSł†N˛‚GłD±~@Śf7Ťd1Ś\#vNJ1Ý×ÎÓŃÎŢÔÇ×ĐÇŰŃÄč×ÁÓĘżăŃşÜĚą±°°«§˘ŰŔžÝĽ•Łš‘Ř´‰Ą‰ÓŻ„Á¦„·ž€§’w­”v¦Źq¨Śiىi»”eĆ™a”|_ś€\¶ŠSżŽQ“sN§~M™e&P5F.âăăÎËĆĚş¤µŞťź–ŤË¬†É§~̢nˇzJ–l:’a%Ž^$tM«Ql IDAThŢíZgxGŤeË’Nĺ$%:„zÇŞ–dË˝Ű c;ˇ ¸`SCˇ&´ĐI¨Ič%B ô@€B'˝÷Ţ{ď{·w>K:±†üLćĂź>äŰ÷nŢÎĚÎîúş˙í_X|›]Ë#hô„„>iŕ3r<ú4>) Ŕ„R žű)|D(áCŠŽż>_*ÉÄJ%®Ë4`<ýPC>˘ŹRö-FÁË”–ôßޣ»Á" %ôp_"‰qcŹá۶ő¶ ś˘ď(>KŚßꋳçÎ"K®˝§=.Źqg€Ź ‘MöL·z´0Ů‘“0işńŁŁ'ZéYápD ş3•hôĚcł†0řCń©á Š‘ˇMŁąÓ÷ÔÖ6Ă)ÁÖz´J@@Ćه (ú›ŻÖÖ+f*Ę0Ŕb‚ŇČĚh„µľ6±('÷4Fý羥ĐĐóLţYmnNQ^©śQČM+„ÖŽ#ě¶WsüŽĐ:ÓQ™Ö.ĎË 9üŐĺ[ţT¨šh¤{Łbă#%™ľÉŤĘ4>_cyv°Ć}_FfSľVş/«2Ž\*´ëö¦PlÝ&{AÁć<«Ma7űuvÝŞ$:ËÂbĄQŐYZ÷E…¦@ TŞ^+t‘”/ék†ČaŘ˝¦ĚĎá|ŚÎËdBĆ‘Ţ;oÝ”,>±©Ě‡‰ 1^{ßóp fő l–!5˛íOˇ5ňč2¶ĎÁÚRY( ňÁ$ spµ#C·Š^.ŮJŠÖH–ľői¨ŃúEŮYžű1HF†‰¤-‘-Ë…ÁAPˇŇƬ°:„ŞG„ý8Lć^w{˛=kˇ^ ••ˇ@I'˛bv‘Ç×D'ňš;ˇa›"tëâÚÝ 2Ś(ńřOBofřµf‘@dÖÂD–Ď,ň§Cü´f‡ ¤9ź­ŮĎB]°şę’Đ{ô˘ŮÇkË”Vď::ĎŠB9°}Q|µLg„ uP#ŤĹM—‹”i9 +!Áú\‡ŰŞ·şiÝSoÍmhNŁË„źĘr­¨ç`ą8=1·'ťĘöńLv_ťČoOĚ]'§—şĺ` ].2črŃşďÁćX»?±ző‰÷§?–#óÍÚ}#éénŕ(čr1®8G÷îŐ¶ôĎ»ˇ’YŽÓ~Ţs,v™@—‹îUuĐ…Ż?je»€ëo`łŃź †-ăČ<ÇjŹlQu«1şˇŔx‘ůś™CčmŽÁ»˘M.ÖŇ÷SN€ŢŠ­şeuŠ*´Ż4Ćč`Ű©C<é€ŮV±vnÇ<(žť— rî“iîxę>Á>°ű”9*šÚŕ…N\#ĄFCHu4@ť:€C €ż˙Ćë0<ż§NŤť8€ă8W ·î"±ś:hî<Ô âŁ ĂU€A$hȱ<—myqSm0ă8á:k)’€eH[Ąs¸Ň¬OöâÍfY»m{ŰÖ—x ±ëť {»mZ÷”QA­Ň,“Íć~ć安/Ű,dPEÔO|X×ÄD@±#č¨ËYÇ ŕ˘Öä>'˛Đ 7ŐaŐ 8@Y×ĺ5kU<§ÍÚąĽ+ĸłÁ$›ą7ßHŰ[)Ľk"HűĄŤĐő˝LéÁ–n‚ż˝ëËHş€©•_ßnz%ÚPź]Ă_!)úE9PńR٦H‚1ó‡ţ]éÂ;¤ /ŻĘráa”@F‘6łjůɉźF:Úąs硛ş„Ś ľ,ŹÜă¬đ»ú–dj öčuAF¸tţĄ»^?« ×gŢĐÎŔRĂRËÉďĆ–EŇŻOi,ňg¸XúÂHsş=XÔs.Ź‹ I¬říÚ\‡=—ÁCÄčL\íőżµ-#±ľ 'ł|9?*fNôŇéČ(¤ŕ¬ŃŕvĚŔ˘Błr~ůź‘“|ičŘůaŔt6zqÄ)*Ä—5"±9ÝäqD=GVrť·äMV3``5bk©’°şěŮ ÷±ČD‹ň ´Nv—•PŠ0eŹož0áąv,™t˙şt©żš›@±aď¤%;^xnÂBĎŮJ±…©źÖÇ„çmşX6ż|Ll‚Ęő4ČŤĂĚ0•ŮV‘)Eý~SĹÂßT~yA§NťľŰ<&–ňÇűŃĹbĽ•Ü´·ďׇ1ď$ÇŔź·ą  °‚NßĆr"éW†`8(܉oŚRq”ujłˇÜ.Č/Ľ‘ČIŔJ‹)7Ay_°™› ň÷»‚ńéa%Eú _ZR1©fçä1$şað <Ć$¬izhgͤŠ%/-g}R|Xęm¬l‡§¤hj7nę/-(łPP°`l%·BçÁŃF‰Ç‘]°éĹL"°M®·Ş]ZSVh5ŹŰę˙úíĺď.Ă*Íe3sCYv­KmŐăÔŤFT©- N¨Kë°*©ć}SVÇĄěâ='h]ČćŠăN4e +#=pKJĚ\»B©(îňZAŤŤť$ňâ RŔ¶TwĎöëŁďŸ^­µë|ÁТľŐ“›ňň«sJüźÎ®UpŹPqł/„Qms›:`Ţ€ÉmS śęŕę (hj©°zŃh4č uŐOŁ#ŕŃ€ÚŔ“»lhYôíp•€D"‘‚úL#ţšŃc®ŚÁš˙±˘ć[íŠ^Ř_IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-114x114.png000066400000000000000000000104321433544061600242070ustar00rootroot00000000000000‰PNG  IHDRrr¸uOýPLTE˙˙˙W!¬r+üüüţţţůůů÷÷÷éĚ©ôôôűűűíîîńńńďďď‚Uččé;'ööö­r+¬q(óóó×ŘŘV §o*ęęęççç["ăääććć¬q*ěěěĺĺĺĐĐŃY;ââăŕŕŕ«p(ÚÚÚëëëŢÝÝáááŢŢßÇČÉâăăßßßŇŇŇę̨ÜÜÝĆĆÇŰŰŰŐŐŐ…[(ŇÓÔĘËËÄÄĹ…Z%ÔÔÔÍÍÎÉÉÉ­v2­t/ŮŮÚĚĚÍ®y8ÖÖÖÎĎĎĂĂÄ®w4ÎÎÍćË«ŔÁÁ˝˝˝ş»»čĚ«ĎĎĐ_.€U W:±±˛ëΫľ˛¤´Ž`°}@Šc4‰a0ŃĎÍĂÂÁÂľş®{=Śf7^,bA˝Ľ»¸¸ąÇżµÜĘłŐĂ®Á¸®ćĚ­­ˇŽwŚi=|RËĂą»ą·ŘƱ¶Ż¨µ­¤»­›®˘”ł˘Ś¶ťµ–q›f–{Yš{U”wS“rIyPmH[<M3ŮŘŘÔÓŇĚÇŔżżżáζş·łčÎ®ą©–ŞžŹĄ•ś‰q´‘g`±‰W®F j(iFŐÖÖÜŮŐÍĚĘŕŐÇÉČĆÇĆĹÓËÁÇÄÁżş´Ĺş­Ş««ĂµŁ®Ąšż«‘Ĺ­Źą¤‰¨šŔĄ„Ľˇ „b±Š\°…Q±‚HnEŹmBĄm(Ť]#qKćáŰŕÜ×ŐŇÍĺÚĚŰÔĚ×ĎĹÎÉÄÚĎÁćÓ˝áĎ»µ¶¶ł´µĐĂ´Éľ°¸ł®®®®˝´©ĘąĄćČŁł«ˇ±© ¦›Ž©“wĽ›sźk­Ťeµ‹XŞ„VsQ´‡P”uN°L^>?*ęĺŕáŘĎŃÎČčбŇŔ«Í˝©Â¶©»°ŁČ´›Á°›´¨›Č±•·ĄŹŔ¨Šž”°ť‡¸ˇ…ŇŻ‚Ł’}µuĄŽrŞpł”lą‘aś[«z=›g'R7çŢÔŮŐŃËÄĽł°­áÂťĄ ™ÝąŹÔ´Ť®›…ť‘ɧ}·šwЧvµ”l»—jľŚNˇxGł€@–c%‡X¨¦ĄŻź‹Çźo•€gŔV”k8wOIJ Ę ŘIDAThŢíšw|eÇMčĚ8Kr!1ëš4ÍhBF“6MÓ=)´”(e´ě˝AA†lD‚ČA¦€‚Čp‚ ¸÷Ţ{ď˝őăóŢČîRî¨˙éďŻöóy?ď7Ďó>ă˝çî’˙ő_Q¨)řCčJPx°Cj"R*µYÜ•ť•©ěJńBű$&&%Ą Ą%!,lĹżá’ŇŇČ…ś•b€°KŠ\!CRČŐ@E[ń­L…•jve ¬¦h"Sä˛LĚ ·X,złN+“ĂV\fB've–%‹])ŠöISË´űţ§6lŢĽářęYV=&“§ˇźĎ»Rg?P?qذ‰ősÚ¬LGL‘gę®;ţýOQ©1CmxçŮ˝N˝V‘B3YoĐ+ź»ą9W˘ÔHr›ßż×­×ĘÉ•˘Ľš˘ĐZżÍPI‘}đ8nĹX&űŰcí´\ĄRBJ©ĚťvŚ])Iîă~ˇ_†´Ť2Š^šíÖÁNěy&P+í{Ó<šÚű•ŮnÄěZŔ4ąÖ˝!ß(=GĆčě”FîÄ®ÄěĂr5’s¤ÉL9¬ŚLRgZž-c¬˘/…ÝZyă°X)Ď´Ľ#˛Ě‰+µR°[ «TRŽTýÖâY2ä°ŘJ™ˇĄQ)áHŮ<¤@O®h¤\kΑ‡Ůk…Ł~<łŇv3y7—ŰÉ•Bp> Ýá"Ł”Oe7˛fÂI‚‘kűKř¤é~Ś^)ĚŻ&Ëf^#!l'× O#Ő™Ö·ŔH^3ßô8µę$H9vÝqŞłk đíWlöĺń—Ż´éŔł‚I Ý꣔_ECä/ú Łzkř‘šĆp˝"-U@j&¤&)̇ˤqďqgRG„âU_ź+‰ŁěqÔŹ‚L“éźĘʇ4ľZ‚b–Ff:&Jâ"ź:L˙2ăŤM‡Ôˇ;ďW¶´Š@.Š‹\âÂÍ ˛ôčجä¨űXŹ+ߦŠT]Ý·Š gćŔ´xHĺ‚g‰v*~'#r[S˘ęŠ3}źíq‘SKY2a‹üĺ}É/|ú ńPGÔ ącNs\Çľ"O@X)PkťwĹ=̲z‚Ę”#––îńҲű=y~mÁĎŃĘżňÇ×صjčÓäşúlIĽâ3`XX-(Řl<‡e˙Ý~=…ÔZ°fĺfK4m¬&-´ya޶ž}ďÍM›NE™Ăś˛ĂRŤ9żd(ą§żzyq3Ëě~oČf ŽR g­ŕŮńľuuuGO2!;9…ťDĘ1¬Lýőh]Ý}ŤÖŻ%ĐHhż ÉL…ß3ňäŃC«ŞĆő3Ň]ÚGFb'Ůt%ĐôţěÎꪦÄâw¸/ ~EHážmĄawVů\ľ·¨ÚÔŘ ŽÜŔTĺöOŞr\ľĘEĚIöľŐE˛É‰đl/ڳƷĺäĺ>úŐŮ&ę÷Ł(ës9ŤśJ­®ˇý:˝J„_éŐÜÄŢ-Ł]DńěĽo(¤±*d©ČýFĐČ)sCÁâŕŞéŚcŃń HážµŹbÚtŃ3‘ Ž{6ŇȢcdůI…J5ŞQC§DN9>ëÚ!˝éÇr®Şg ł“\3V—=9N"M–Ř]k“ŹŔńk_‰Ő׹L*ňślĎEcÍŁÇ7Ň=4ş•ź¤DÔ ™;Aöô«Ę™¶’»ÉULŐˇ˘»ďśm*Ú•ő…a<Ľ¶(V~ŕ^H5źXAuyŠżBß:Rč§ú¸`Q%;üëY"liPŃĺ‡<§Ä$5ć~EIźÝ'."ěĆřuJ_ŹUŚ_™¦i°+ém*$5`4[~ŇQňŢŻ¤Íšç"<+Ď(cn&«…H$¸Í˝â!Ú̲ń®ň’UWÓČ%>¸-¦$ÁŤ˛ŕŤĽ|´ŻĽć™ţ´‘ŰG“®˘ˇţ›…oˇÓÄ8ą4T¸ŞŤÜZ¶ä)j™>đ.Ťś^í+ ˝©”0#đnÇ˝řď*˘ëę˛yPňľË`ÜLŘ1™B†Yűśˇ‘K«s"M·Ň M§nô"‘lŃ‹ľX™Sú†Šá‡p¦Ĺ,¶5 ĺDu_ß„ÜsŠť(ż˛í„-z'«çVNbn?cKKp§Ő]๡™FTUąôśb'ŇŻl;ů¸ÁHcĆUWOdKÖWç¬čă!ň\ă›ĎW‡ŞÇ2řfŞŘ±DqE/8Yë`wN`ĘĎ©“˝zmť6íÝ)S™âórݡי¤\ZÉ;±ž='5O­;ş0źq3hÇ/J„A®[wšIĘ…®b=Ü_Y¤¸˘g?KÍűßZ&eŐąËe’6ńÇb&)oťâ;1Ť:Ȥ¦4*UĹG*5Ě3źfqe §9‹Şł§ůďĐ€Śsc^_ŠękGk¶ŞÄ •§ëJÖĚ‹˛2z&ڏ‚}߇\ZW µIř)™ŤŁż’RdfŰÇßĺ‹r¬${鑞eR#&%jËř@z46QçĽë!ŁT´ýÂo–Ą•-Y&g^ B#yř ög2¤˘‘ĘƉ„×LrzK€Ć™l«ĺ0G#y$ëüĘTRŃH`ö˙˛0ŐhŘÄ?!"`Őé0­I!—+LűÄC˛łQÂkÎDŰŔČ]÷¨ćŢÉ ¦ÝOÜńßéĐë0]–÷ ŠD˛ĚߛÀaf‹ý±;žŘc0!fOšďHNîvŐţٸßëÇŹ÷S‰G˛sççK6»Öľ«ş%'ôšejxVăq9¶g~r2‚®^‘—×r–%ŠGB©=),ß@ĐĐ}vjXÍ™‚‘”şµŽjZb”v)Ń,=Ş€¤âÔ8ă×Ý`$Ą®Ý^Ë—v )ÉŢ@Ɔ}“šy»ćî2:ŠTţx7kÁőŹ•ŕŘÇ®ęÖ•\Óóµ(ŸŁKg^dŹ|ĚÜŻ{RŔŠÖýłxćě(G0k`ukE×8FvľćŠĺË%]¸Č» X>’ßL3*vrŢ`0/śsĆT$÷üś'%w̨MŻMďaä g¤§§×ŕ1´˙=»V n™ Ú ČH25 jś/Y5jpĹďF®‘;g¦\Óů<äȇӑţć Úď+žR]šWě4@Z’Frđ.Éâ÷¸Ş†<¤â"/#w~řýó‘WŔOíâpŤ««ö q_C*´z?áűOđüڬ©]ľă|dŹ8ó .&3r[–VÁ˙Đ±Ô ĚçŤçKĘŚ‘éµµďä†Ď®™µµé34|©y_U¨Ř^f2oPąÄµB›e#n’ň©ËÎ+Fţlä Áµ3–ďâMɢĘ ‘•Éu«Zďe!‚^5ň§?HP)`G‡sCĹnV¦Ps;fy&­ÁâÄ ×¤ř]Rą¸:§·;ĚIA†lÂy)bzt÷žë>ÚűřśľßüKH͉yMsßűŃu{vsŇ„|řáóç:´˘bč·J»\#éqać ŘsţüŰtp˘‘ě«ĎŰ’AT…í| wÎůÁ•WÎä •]{ĘúUˇc‘PďÚ#ľ?ŕ‘KI=ňAŰlä«x1äí~٤Y+Qľ>™Ń§?Ű!^S{iLWŽlď(˙ú49†d›4‰ôgF;Č‘€Ši@{{*ą ’}–g{}WF=ŰŤźĺm‘éíFOĎŘ–·ŰĚŚcŮđŃ~xđ`kë1cüBŃżae|63¦µőŞ}VöŃí–:7N„\9Ą•Uę¶Ş:~–ĘÓëę`PRšă Ő;©ŽÉé–» ‰ňHé‚üG,őA•+ó` ¸ŔëĐQĄ€[đĐ÷3·ŻÉéĄj?/kŻ„ĽśŃ^^*§Ę)żÖďvXô,SÁéd#Aß)d¦L,Ë|š}ÉĎ[ŕąŐ‡ű˝Ň˘Óšd ęű$ ň~5•– Ědö[¤íë‚5vqeąMÖĄ¤‘ßVqź1I?f¶ŕÚ 5ÓG˘‡iôŮÎd ¤¨®bÁ[¶uä1hĸHŔ‘ yÁ  &¬T‹T6NđyÜd&Ƈq®î:gđą~Ć‹Cjć^ŞŽ‹ůěžúe÷ŕŢ<ˇ´M(€(‰fŻçé[Tâ‘ĘíësJlĐ8DÍó驏=ز$އÔäNë#üz5ů7Ű"źQVnlP‰Aj_ńxÍ,Q¤ťÜ0‡,ÉW E*ł§Ţ[ZpĂEG$‘ýÂN µoĺř«*©Ě=łp ‹€Ë˛‚!Šg¦ÉM¨Ą­Y°µLĄj©Töźľ^Đ;ÍäÄE<‘}0ÂôökkVŐO^*?R_ޞ~ //ě·płÄŐ>0T†ey‹‰HÓ‚·Ď–I3€K#šRŇ˙Ö) ŹôŤ”P‡@RY˘h&óÉ*¦w„k"MőßľzYjßFçŁ6Ü}Ä™)‹î]Éóŕv Ć÷™ŞxC)(<­Řń0Qčę{ñń/ľ:éŤÉ'_´i¸yss"…%ÜkŐS@޸ď]€*L0sÚđ°§&Ż0qą|.W$TWC„qŚý ń# żOҲQeZĚśeuÚý6śÔ¬›×iµˇőŹlü3‘űýtа&-†é f$Ě"á˘AŽp);äÁ©ď»ŐjłR’Ë–B}Ă-(&|I.€Y%M®#ë§"±_ă‹ć‰ç˛Ú˙âŃ?Łqíç ô—IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-120x120.png000066400000000000000000000111271433544061600242030ustar00rootroot00000000000000‰PNG  IHDRxxşĆŕ÷PLTE˙˙˙W!¬r+ýýý÷÷÷üüüéĚ©őőőűűűđđđóóóůůůúúú‚Uíííëëë;'¬q(ňňň­r+éééďďďââăčččV Y:§o*["ëęęäääççčááâĺĺć«p(ßßßáŕŕŢŢŢçććÜÜÝÎÎĎÓÓÓĐŃŃ××ׄY$ÚÚÚÖÖ×ÝÝŢŮŮÚŘŘŘÔÔŐĘĘËŐŐŐÇÇČ­t/„X!ŇŇҬs-…['ĐĐĎČÉĘÄĹĹľľżĺĘŞŠc4ŰŰŰęĚ©ę́Ż|>ËĚÍľ¸Ż­x5UÍÍ·]+ÂÂÂÁĽ¶ćβăĚŻëΫÍĚĚËËËČČČĆĆÇŔŔżćж`0yPŘÔĎĘÉÉĽ˝˝ľĽş°…PŻ~BŤi<ŰŘÖŐŐÖŐŇÎĎËĆČĆĂŔŔÁĂÁľąşşáÉ­™}Zµ‹W–vOl@¬v2}S\=ŢÝÚŘÖÔÎĎĐÄżą»ą¶ą¶˛·ł­Ă·¨µŻ§˝˛Ąą ‚®€G±FŠe9tMiEĎÍĘĚĆżÉĂĽµµµčĚ«č˧˛¬Ą¨Ą˘©›‹˘“‚ Ťu·™sś„h•yW¬z<Ťg9ĺÚÍÂĂÄĆĂŔČż´˛˛˛ŻŻŻ«©¨Ŕ­« ”ą¦Ž˝śtˇŠm¸•i´Źcś€]°X”sK¶…JŽmDnIŇŃŃËÉĹÔÉ»··¸ăÍłĚŔ±»µ®ŢÇ«ż´§˛Ş ĽŻźĽ­šąŞ®¤˛˘Ž§š‰¨—¶ś|żˇ{źŹz …e¸’cšcłŚ\qJ«}E­y9›g'•b%_?ŇÎÉÝŃÂçԽļ˛Çş«Ç˛×¶Ź˛ Š¸Ł‰©–ł•p‘tPŚa-¤m)Łk)Ž^#cBJ1äßÚŐĐĘŐÍĹËąŁĹµ˘§Łž§ ÝĽ“ż«’Ó«zČŁw˛’j®Š_¨…Z«‚Q±z7b@T8P5B,ŰŇČÚÍ˝ĎÁŻäĹ áĂźĄť”µ¦“ƬŤÁŞŤľ¦Żś…Đ­„®™«–z¨‘s˝‘[źwF’nCĐÄ·¶§”ž—ŽżŤR—n<®ŮfIDAThŢí›g\SWŔ e%¤‰Y¶Yd'$! ((DT† QPëŢ{oëj«ÖU«VëUëlŐZ­UëčŢ{ď˝űˇ÷ľ—ó}Ix/|íů"ČýÝÎą÷žsî='wü/˙Ëť­…ŃXŽcc°ÄĆÂtá†ŇĆF$4)1†”ĤbľŘ6c“xÄX4SŕÚčü€S* Ćáň5sľ4‘÷>€>ĄŹe®°DU; Ű™NŢ<;Ď Áj€ÂńŮ€t.Öy뛆ĎMdŞ2ˇ°ôŰčŕ’ľn ŽT(dF—«f˝ĂXe¬„ŞöŃäŕ`áéÇlXe?8!)<”śË»oSÉÇc™ZšŻy5=:„M˛«Ř~ÔŘzËChśrÄ…ÍÓľ­Áz íP.S-1Ţ2`iéŕŇU>a2Ę­‚‘őĆ NŢÜÓfŕ űÁXljâŕ­M©I|ű‹ ˦zyş0XŘwŤ]J,9ÖŕÎ î_n…±Lö–DóJ~hpżr3,2ËWę,Ď-(ĺs™ů<™f'¦ËSű,zŢ1xoˇ%F{+4xŹ HĆ`ĺ…éˇÁéÇâC$#ho©÷eF…153×X˘Á~+”d<_`Sá­Š}«H¬ý&%4xë25Zc†»Úć8Es§­JnęźäˇÁËÓěŚ79ŰĺŚ\á»iV%‰`ŰČ NYěvÂqbč@ćô ˝­Og{H5Uł%ôiĘťX’‡·c—)P{O&‡>ČCđŽ!Á|ĺŽ0Çřţš,­‚qxÂz(SßĘhK~śVîŐâ3B€ĄZĹbOŢĆŇGÓ<°ÄŚyn›“üÔŮł¨±˘Đ˘Çz¶‘ÎÍłY–xăj<•Ą!°]*˘bżĎâ1םö‹Hו—?ńá]o¨ózĚGŔŇĄ‡}ç±"·Đ϶>=¤Ŕ»ŁNö%(ůÁ`…n_&ujë,Ţʡ đÖ†’ć×&zb9 ¶îŰćö= yYšK­ŕ8ięĎ– ®vŻÎ &\^"°u*8«č˘C«««ĄCÖU–•§đ˝2®ĚĹ9ŮiűKĺŕĆŇh 5K· *_«/+;P>´ľÎĺj”Z{Oż‡”—®ŻŻY˝±µ»ÔĐÜ%‹tŔĽAůÎÄúśÉŕş>WVŕ˛ó즲á~pď×›ë'@$_ŽÜĄÜ%K0y”'QŃ˙Ę®]“Áuť=9X>‘e¨ $BU!TBRđEDm$h5€˙RŻUٵ¨Î#V( ęĆMÂHŔ Ľ-Mv­F,PČ4ŇŃ«z­Ňĺédň…˙₸îĎÔZóô:­ŃĽlÁ «\6-šiŔ3Ýăô­DdâhÓ_ĺWÍŹ‹ëWńů‹×;ö»ôŽ‚3ʦ•xÍŤŰ-.nţgFúk=Ľ˙öB°TělĘž’/ě(—»pÓÎ rήŹ{Č=ř* -»Ń-ŻŔh¬pF0îŤs·ž9sfqH•1¶eçl»š,BŃ‹k2íŔŮ;[0zÁßÉA¸OĎŐů|—d:Xľ}TçÎő ŞňŚÝ»{Ďě ~ôŕËĺ´ö’¦?ZâzĽůa°5ÝŢɨsťţ î2sţËí :Ë_x®Â6”őt94TąŠN–©S-UCvď}'ˇĎˇé‘ôˇk<‹řè>ÁÖyĂŢKę«MV­A Â5t ¦W˝%b©ŃěΙ0LŘq0ś¨µÖWYlR_ MűÉĄŃ›¶§_0đMrţ{‚šřĂů‘ÁbĹŚEÍŮY6TÄŮ`€ÁńHgµÓ4Ą(:Üú+|7]ăâYXá™QÁ¤ôŤ·]'ñ Ŕt…ăq©_jł á=ž–ŹĽEr ä™Ű·÷ őH?¸’ŚŹń1AR" ŕ˘(¬Đh­cáĚÜDY^ęŢěöč5b>Rz AÍR‡3Ë÷<ťÉŢe‚<Ůśm˛q36÷ČDj§0čFÓ8»iČÔö pwbžĐĐ4»qĚ€Ń/)i©YčżŘIEEEKËŹťčşŐoÜ\tűqâxËŢ4+šű˘R gčlz!ď×­k×n=žKo}nä¬ówľ=SŢ:óÇÝâФČsvmŕ Ü6  yĚ`^űÓ»ů´3ÝkĐ}&)ÝŁ@E…ŔĂp?ľ‹’Ă“y˙|GwŕT€ÖŔ ~ Žž…‰ ťĂj\üÁ \±Ă9HëĆĐÇaą7?i ~qd8p— ČŇu™µĐ#Ńúb¨ě5ţ˝~ żČ7a‰ď™˙řč]i›ĽTűň 9sÇŤW^81lŞ×ifkđů°K|}áÄÂňňqăćÎtˇVOK`¬Rç ´ ÜUŮ믤‡ßü(Ŕý8¬Ą3—<ŘŚ^üÜ•&‹Ë©E§ Ú„*#2rZcžÍę2Ő5„WůFźO>%í|{d—p źy•fĚÖÔ<Ł^§űC2˝ůŤŹ.:*Rmô”ŢoÝĽŐgűůŰŰ6¬ÂO v›m:©F%#zţ€Ű†śŕď2”đ*ťµç¦ö\& íyLůĐ™| ŃąOv9†ěÁDaJ¬ł?0˝˝«DűWţ ÓĚzÜ粯:I‘$ „@"Ó»Öu0,ĘKg[ŚxC!$’đŻ d?&ĘtŤYSÓ;NYZSbS’Ź?¤0xŕB× [岎O5ô´JŰFd‘Bę|lSFŔ§Ćű<:1ô÷±čDě±eé‚ĺ)ËÇű\Z®ą˛ëŹ$ÉuS‹„‘€yĄKę\¤‡Ś #T¦łV®›&d–Ż|"ÇŤ¸ţŤĹö%錮RĎnNgű–™r˙ÂěJ;˛3pŮ“ŇyuOł1ĐĚV {Y&µ™Ý…ËňŁ…íyQąË÷öe9uD?t,+.˝_[ŁuZę §ö!†/ dn[:yp•ÉŽšˇ‘™Ůť#z‡:ľÇ)őVoÁצ!¶0XŽ[Şď[ľ~|¶Űä1JeDű7k.ýÉÁh§ŮT5îŔ±ÓÓŠ2˘…Xđ†‡EžRÚ˙ţĄ‡'”ĄUZ¬F)jx§µĎGŠć"´Fm´šÇşłË§<|ěÝGúNë—ż"7we˙3ĂO-]ĽBMµ»Äb·i•2ÜgOS7b{ă-)uF§Ë2¶ŔW]VSŽĘ{“'NßPłşÚWP’e¶ćiĄ°Űq2Łqf$ĂY™Íę1{-cM„dYĚkާSi×ţ˘áű(/BY™RŞÖiő‡ŃáĐkµj©Ň S05h–ŃqtÁćY™@!Ëd21zĎđů(ŁbEeĎ&ŕč©„‹DDúeTÚqjű_đIJJDý“„™ĘËž y!dp졑ă˙ňż0B'Šó(Ů‹IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-144x144.png000066400000000000000000000132431433544061600242200ustar00rootroot00000000000000‰PNG  IHDRĐŠPLTE˙˙˙¬r+W!ţţţüüüúúúřřřéĚ©öööôôôďďďńńńíííëëëçççééé‚V ¬q)ĺĺćóóóŕáá‚U­s+<(××ŘY;§o*ääĺŮŮŮ["ââáăăă¬q(«p'ŢŢŢÖÖÖ‚VÚÚÚÔÔÔÜÜÜęͨÍÍÍŢÝÝĎĎĎĐŃŃËËËĎĐŃßßßÓÓÓ„X!¬t/TŐŐÖÉÉÉŰŰŰÄĹĆĘËĚŇŇŇĆĆÇĂĂÄżżżĘĘĘČČÉ‹e7„Y$ÇÇČşşş…[(´´µ­w4;'ŔŔŔ˝˝˝ęΫ­y8W:¸¸¸°‚J‰b3ĆĹŬ¬¬ćËŞĎÎÎÂÂÂÉÂş±C_.:&ŢßŕÍÎĎĽ»ąŢÇ«»Ż Ż|>T ŔÁÂĽĽĽ±°°Ľł§·§““qGŚh=†]+wOqKââă¶łŻż¬”˛Ť`µ‹X­{=]=ŇŃŃŔľĽ˝ą´Ą—†ł–rą•i™€a°‡V•xU™yR®~BŹlAmHfCĐËĹĺÓżŃÉżĂÁżĂľ·ą·łä̱ŮÇŻ­®Żľ¶­Ľµ­čͬł®§·«›ł˘Ť»ˇ¦Źrł’j‘rMŹnEŽi;Ąm(ŐŇÎŕŇÂĚÇÁÝÎĽŕθŰË·Áş˛ŕÉ­Ż©ˇáÂś­˘”¨›‹­šˇŹy¸™rź„a–|[|Ra@E.@+ŘŐŇÎĚÉŐĎČÔÍĹËÉĹËŽ̾¬±«¤ÁłŁ®Ą™·Ł‹ĽĄ‰¶ź¬—}ťŤx·‘aś[˛‡Qźj'”b%ŰÔËĺŘÉČÇĆćÖÄĆÄÁÇľ´Ĺ»Żµ°©§¨¨Ąť”ŞžŹµ›|ž‰n­Śdއ\±Š[–vOŞ€Mµ„H®€Ge&Ž^#éăÚÝÚŘË·çеŐǵ˹ĄÉ¸Ł§Ą˘Â±śĂ¨‡˘iš„išeuUŞ„T¶OR7K1çÝŃáŃ˝ĎĂ´ÔŔ¨Ü˝—¦•‚ɦ}Ł’}—‡r͢o¶€>•k9Źe1O4áŰÓĺǣǰ•Ö°‚Ŕˇ{˝™m‘WśsAŘĎÄÜĩ٠śĽśvÄ•]Îm~^IDATxÚě•ËkQĆťi+Éd™;3ÁdĆÄ –DŤşĐ¤AW‰DĐŤˇjDABíFÁǢ.¤Ř•¦¶(ZZ7‚.\‰.«‹nü DWâ˝óđdÄÜq¦".ü %›ďđ;ß9çΚ˙úŻż¦!řńhhŘÖ­&pý1¨ŰW>ČC5­žfddd­+üÓ)Ů´z\7‹c)ř/#ő©ŐmŹgRúMĂ«@‚Ҥp‚ăx[—Pâ1é×0%\ď3­*R9®pĽ ‰H'B˘$p §:÷÷đĂ$"„ü&H6z< ‡it5·7Űëů¦Ş‹ďU§šZév:ť®a©jÚ3Q$ś;/!µ;żĽ¸Ňh4VżĽëĺšëŔ)@4Čô`a©555ŐZZxĐ©€)*)M:EăłË+cÓ”eŮ4ͱ'ˆ•–ř„SÜďSçńËcZYÓ˛Yü˙XkşžŰ”Á•‡—ô7ź_1¦ĚüÉě;qş˘ú‹ű›Đ›Ł-¶śd=%ËěĹk>S„9<㽯Ą ăW¦tnÎ("ŠűyD}öCMcýŇj3uĂSžµqNźhC8 ąý6e!Ü®wkĐDśÓ7/d^N:&Ř˝P#¦É{Ź*Č~ç<“4ţlżFJî?˛%ďvń{‚Úóct ¦tâ`*í,‘»w˘zhK:vuÔîbx8,ú.yĽc˝ęíłBÖµdОۅśëź’ĎîŢPt¶ÚÝétĺ†ô°`DLh~/ĂśŮöÓä±Ć@‡Â:8˛A:pµ`čŃ€&rĐůKwA‡\ĎÎ @Ůę•Íą uŰfŔČ^O´€î] ™Ö:ăśf¸wh„ÄźútęňFżâqbs®š ú¸}tSčo‡ł ą÷%†®±C;Ü»÷€ž`骽ؽĄ(ÂĂĺç‚–¨tt‡÷ 5Ő‚Včř.x*Â}\Ĺď¤ZďKa'B)ÉÚŞ‹#׺yóäşÝ\şąíî6ŰŹŘÍ -Á(t•˝Đ ˰´w feľ0“^ Ażč×›č‡e*ŇŰŚŇŢFţ AĎÝnĎ÷Ń(ŰîAÇÉ><źď÷óů<ßçvůÁĚţ˛ű8ÝÍ´ŔR¶Ş Q÷ŃŽśgUÓť«pfž ą˛B„–&CcÔ*Śu{…ĘÍŕůpVZV±ożm9ĺ22Ł]óv!{Ué2´LíčÇľ"1ąĽ˝UĎ63agő3WŻÎúÍ—ť»t@Zc˛HÁµ—–›MË‹űh+0fŚ3s_÷dz =v#bΞ*‘¨ @Ůč]45 Hź‰Ë)ďa™3±€»|ó¦ŇŇŤV‹ µ˝áQĽ)ĺ˝jŰč‡ÁZ8CRÄ}0ÉĄ©éR_!3N tąusŮÖZ— 5Uw;k:}űŘ*ZP; CśćU=Ž+ýIKuT :ŮŠĘZ‹K”ĎP§m±Ń‰°6Ý60 öQAH3M„;Î28צQŚôLLđ˘G㡆Óč± Dč€!ŰXiůîNbŔp;ÍĚ@ťž>>{âloo…ś'3Ńuř¨¶aH„@ŠTű OŚO¦@Č06ććîššŠLó“éĚÝÝ B’ůĽÜ  ă đ}~ţÎÜň)Ķí;V î®?ű5? ٵ§-¶aOŽł/{a33‡˝­2DÚŹá’ŽÜ‘yrÄ`ĽĎŘ×/ ’ĚkV¤ťę!»zD6^D;i~ŠDńO@°@„Â)”aĘT@-‡ëÍy¶ńľMŇ®ˇěŮJ‹Ę¬-o@ J ÉżKŹk áŃşÉă<“…ďĐů÷ Yđňňµđ{ő×ÖęKż+·VL<9d †L wŰ|\9ş&Óé ^súżW©Ô?4<żË9ď%‚(Ś[ţШ(‹ ‚ (H…ĂS9űŮ{oŘ»±·č{ʱ÷Ť=öŤ]c,‰˝Ť˝ĆcKô˝Ý…AapuŃřLL¸»0żĽťňÍ›oVf°ŽDĹ+ îď€ClBDÚáC Qřy´ĆGh&„Hť:8ŁDřŐ¤!@˘w4ţ`€hÇČŤ<Í·śţ,C9kŞSÇa–­Ü‚ĄÚWTžĽyńŹQ—jí7ÚŹD $ŞŰs„&"üćĽy ɉÂF›Ků! ČŔŰh ŔĂd— $¨ČŽnČX­-[ů/áŢxAuçČLůÖîa ŕŇ*¦$ëRe”„2rŞSmÔâç×ę=oGN>Ž"ĘěáWs嚼ŕ€E—6-ÖnľÚP2éG“Üţ4Ë6·š<śś+×ŐËĽŁ(.EÄp0 mtxý&żešc¤{LËŇn‹Éd9°p ćšő/rňzŢ â˘ÁÂQˇPčĐJlN:‰Sĺ‚ĐFŔácÜz«‚7´$ÜĄćă$ Ťo\®cU&µ@ň.ËŰ5߀´ňY)B@ŃŞ h”hđ¸“ę )‡_'8\Šx?‘Đ« ô WlÔxAš” DâCŤď™Ű<ĐĄ‘“KžĄ/*J• ČçóQSôęIŤ‡°ŕ@˘ w”ýŔ‚Q$z‚Tł÷Íή˘…W‡é)T#Š3~”ÓÜť×ţ šÜ4¤ë;jˇ€´”ş„.’5bÄę}* P8kMź5Ýä´}ZZC5WęŮLFÁYG±-ćguÎŔćńTă=ő81¬©Ą©U+k¶*!Px„FE=T|VËÎŃ~P8¶kŮ–°Ň3c~Öě-]ď "}Ą$HuaŤŁ–/!/KĂEź®>ŠčŕE•Şe8Lv-?/&Â^¦Úüz«©E™jGÇS»´Ę§áşN$ÇatóQ»őăIUŕ'ܢŔCĚ™q)â h=jg \í´'¦ĘQ+¤J”Ei•źÖo_©žËbĹĄôGrcvŢ|2…Gí.S} CÚ߇Zť¸Sg/FWPń[j7\ę9žäNńĽ(Ęü®¶˝ŠRgÁnµgÍľÄ}huî×#äÔ©hhűvťf˝ŔChś‚EŤcÍ=öÎ4˘}]×húdí§ {üVÓgD>7Îl_Íe±+ p@[<𩼠ţ1Ôu Űżpq˙lęÄč wíÚ•’ˇ8ÓÖ凖/OA*QÄŮŹ˛5żÁnr,–t#!bé ů?–T ¤›YŢ$ž(ˇ˘†ě€ňĄUí u@)ťâĹ•”hŰŐłYĚF=čdŘP$"Â×{ ¬Őlň:2ÚNéýŽWi,íôNÖs= ‰â€€çŇÁÇçÎť?ţÂk×nüb†rřÄ˝îąĺѵk ˇˇąs?÷é Kk^Ůaa.Q´Ç9F,MöŮűöŻîŤ¤|µ´F l†km$[@0®Ç9(ďîŠUŻłjÝ_»ví›>ÝÂ>q@źŢĹ4ô0M+ŘÄăËR#cĄÜǸµ•˛Ň®ËlÖ¬YffłĚu÷şúD݉mgľ‰“hń@ †čWF Ďţ7Í2 E˘Ůýn>1@ ŃH,ôÚy;s<ű0(VŃ•cźf…b"ó^śR¤ "”nUü¨Ń"&DČEQÓ€.®ËühťÉ_?•ˇĽ˛î#ˇă—†N¤ĘÁóńs RgKÄĆżŻzÄá6qÄâĹ‹[qń,»Ř ‘ J+[}áX 1n·›ć·÷ń•†Ëën:tdtż~-[Ží±˝7ó«}¨Ů˝°yş~ʱý[öë7zô‘MćđU¬§ęČÍ$»Îânč *ŰVŻ=ť1Ęî‘Q–™y_ĸ—®_ĄRą˛e2BĄmŽtS›đţ·¶â-5˝ŃjV«MŕűÉ(Űş·˘ #Öâ,ÄÍCµÄđÔ=l.(®¨Őş4»nć%\Ëx"îźÖ g=Vµ×Čš©»i„™Zž”‡8ŻpidőzBÁ]äW{ŞĘ'C,4QQ“ŁŞđľ «W‡‰JŞ…¶¶ yÍ,ÖŃd2¨fŇTuNˇ ’¨P¦)<&ÇÎ’âW{qQáyµ€7 ž^O-•UęĄYáň®PĆk†FÓ¦=EʼnŐCʡc+:Ó 01ąă®Óęř…jDa÷­S,Ą%˝Î[,QásUˇž)ŔňoNżY“IĽé‰F.áš9QÉ–Sü EVíŘY•IĽňój!î &®‹$‚bÚ5­dŞ ç•wWĎ€ĄťČ±_?J«¤?D¤UŢÝ’ëĐy‰űťk“vKhZUF:Pń.+ÚUtc‡y®H)áP+ÝłMQ©@ĘňŞ›_4­`$ĘmŰ^F|ćöjN3đŕÂŽ@żMÄMŽ1ÓK•pL^yđ”˛t>„GBެÚy¨cťěĚď—wP)ő ‰<„ ʶEký eÓŰ[ŰVÓ–%ńU‚çx&wĹÖ˝Şe~ H®¬˝ľľ±±˛‹e7†[(ĹÇČÜƬµ¶·ĺϵáÉ«ëͨź‹sŻH­v2ąşşă̱äË®ľ¶¬»«™´”l‡]+ÚŰÜęÍ©·©™¨š‰·›wrLnE­y:‡_.T ]=:&ÄÄĬ­­ŞŞŞĽł§ł­¦·Ą™_bAA+;'ŮŐŃĎĚČłł´˝¸˛çΰ¶ł®ěϬŔ­–´d±Ś`“xW˛Il@‰b3|RlGŢßáČĂ˝ĂŔ˝çӻ½·ą¸·Đô»ł¸¶łŕÉŻł°«ĽŻ ±©ž­¤ˇ{˝šq´T–vNŠa.ÓŃÎËĆŔżľ˝čĚ©¸˘‡·ž€°V°…P°€ExPßŢÜÓÔŐŐÔÓËÇĂĎÉÂżĽ·Ć˝°ÎżŻĆş«Áµ¦¬¦źł§—« ’¦—„˝˘̢ptMgEäâßĘÉÇĹĂÁÝϾô ßżŞťŽŻ›ą”hšfž„eť]­}CĄm)äŕÚÚŮ×ÔĐËÖĎÇĘûǿ¶§§§Łť•ľŞ‘± ŠŁ‹nş—l›…k¬Ťhˇ‡e“|`¶ŤZ¶‡N”qF°y6 i'™f&oIJ1ßŰÖäŰŃÚÓËŕÔĹÖÉşĐŸćČŁĘąŁŁšŽÓ±‡¨‘u­”t©‰a}\zS¨P”b%Ž^#R6ćŮɸł¬§ˇ™Ü¸ŚÁ¨‰Ş•|»[¶?”k7ŐƵԼˇÇł›©—€ÄĄ}źxGĆn¦”IDATxÚíśwxSUŔMpŃ6Ż„ĽQÍlł›Ů$MÝ…nK-CĘj™˛‘%2ŮČFADd#  "¸÷Ţ{ď˝çąď˝ä&döĄ|źß§ç?Jnî/çś{îąçž÷Îű_ţ—˙%®t öă:ęüAa1®sáoďÚőZşvíĘLŇŃq~¸Îâ‚o/ďÖíBżŔ,q~˙8<¬[7 Ăhťß~ńĹ))©”¤¤\ŚŘşĆśĆÁ°đqÝđ¸$­Č`Ą¤¦ńŇÓĄ é鼴T™"Ö8D•e\—¤ŐX)©Ľô ™HŃ»7Iöî­¤Ľ4 ŠČ"suĄĆĄˇqĂ—™˙gĂ×ËDäĂŹ®řđŹ?>Ü»kçëI…,=-…ůńQ¸Đ8)ŚÓďÚł¦µuÍľÍ;U0.GŹĂd,ąŕg–ÄřţŹ÷ĎŮŻgĎ~ŻÎ˙ůÇ˝;UY’ ^*üxD™ Ɖ$žÇOž^pcCnnĂŤ NźÜŁÇă’ Ł¸.NáeHt+6ÔpärąĂáĺrNÍýűôF(Ý?CŚKEăöĽPĎÍÉá—ŕçäpë_¨ ÇJüß/SĽó×H`Âpݶď˛Ü.sâÂÇ=úéŤ|‚‹čnüäŽqlÄ˙ýäű/ 9 łmܧ5*¤iđŰCWeG'"÷lâbnąÄ¦6Ł"ťÇŇŕ'Ŕµwľś.ům÷îđ ş]€ÝŹ‘űËř K΂5đ‹ŔšÝĘXůݬ÷Šůr'"YÍţ žsQăöŐGäâŠůeŕh0Ž2&[CJŹn®Č"ź˙¸V%âĄ`Łŕq’ÍŻ ®ČdŻ,ĂăŘ)Ś'zx¸˘pÎQ[VFŁ2<L˙ć ##(Ö¤h 0äÁi˝?,áÄžO™ÔŠtć§c…ĄÉČa 7Ş*Ő¤˛Ž{ٰi?cÇŹäf·.ޡcT¤°tÉM›rÄÜč’óĹ1-R5xYÇÁ@aŇ8 ´§”í-0PXďÖ\n,!ŘŞ Y$%MäŮžSesŹę ŇÔŔO‡q h‰útNf,0qÎn- şîĎL.y…ŠRłR饥řmÉXrs=ö°(!c™K%cáţÔŇŢ{űa„hî_­QĐ6 ˛äš\nifRCÄčžŕú§ VÄa{ˇž”¦2`]aMŠt'!VÄ”LâĎjµžŮ€‰TwɻǛcvéŔ&ČY.fy!.XÎ3ůzp˛ŽA4âI†l¶m‰É#bś…q±Ţ7}ÁçĆ{ŔîĚ’RŢßQ0´¸žŤÖwe5µđ1”ĽcD\0ţäWVK0…:ŤŐLđiHĆY×$ŻO@c,Ť%Ř|¬¤5_Ë8 pQ®yýţQD\°»’Ř`JŐlŽ nĽTč4Hý`”¨ĐFOÚ¨aŚÍŞÜS‰+·Ř]h»Ä`Š!'ăb×ę©ßĂ*Ž©¦ż,ŹČ™TLĽ ÷ …ĺ“x.&ćŹYhŁ\“MäĎČrnsâ2‡×#âˇxÁ¤¤ó…śxăźrX- ůY핤fvĎx`ňm[¨x“’Ą_Ä#rŰĚ^# ßÄĎGn<ä±Ű⨠R˛Ý>*ó°ó)=¦ż’#ŽŁ°˛‰Śg"0ŢoôŢ/C‚˙%<30ˇvŘÇ„€‰Ĺâ0°ĎË‹µxGbádÚp[ A‚˙9rB!=&Ň­ ‰Żź¶rUXiűłŁú±Ű„gÔ/?űěLđúµÂÂSMĆęç˝8ü¬€KÔO,PŞD,RkI‘lůĺGOŚŕůžÁ¶ă  mýĆaąöňź„Ť8Ű’n-“,±8&Á9_z–-…gž?®nĆŚŐĎGŘF%ăÇL|}:aŢăęęęf|_Ƶ¤ĎŘůYŮ’´„ŘRđÉřş¦ŞňŞţGâeŮnöGX*#-?|źőQÝŚţĺĺý›ZÄÁ–\mVRCX‚!‡ńž ţ%muËó*‹*óVÝ* € Şôiˇ‹í 6b|SącěXGíťŘďÄüɵŚ%.Öĺ˝ý8ěDSy‘;żŘś7pýĄ<«‡Nß©Ŕopť0aŢŚň±vř|Ĺş ˙Ď=Á¬I Ćb]Z6ߊUÖóäň<»UiĘolŔnÝâi0pËŇMěT‡»}ľ-—(lÁ,Ú’lJř`a4µË±ó?żĽ˛X©Ő{ó·rü`Âľ»ó5hŰ űtÓŔ¸ëk‹¬N­łÚüv~ţsa–dcď-âDú» źSŁqVĎŔj2‰€Aŕ×-ÔźÄâ6GI«Ń*—ŤŔ~7řΊŇĐčĘ2Ćޱ«¬ß¤Š|§Ć˘WîÇžW˛¦x0‘jĺŤwjXWévi,6ePęČ1«Ŕ•„%ń~©Ľ+hgä(¦fÚSa[í;©śď‚ ŔöŐ„†@\¸n-R°Eď ,T–Ľb[Ř>Éâ8n°ě‰Án›é6i-×âŰŢĎ™dweŃ`tbÍő[’¨źUdŐk4úĹ ř8´­+*őŕC8k[ÂYiúKAërR#LesÝkŐ{‹]©“€ŃźźČS—•ęmúĂrqľÎGŘ’,ÉPθăGP‹źbŽ–ĄVyô% ÖŢH…~¸ZCńuČOlS•-ĘŇ řX¨ŽâŔyŹ515Ź×•wÚKťNSá aAč÷,Ďř)Ä9“kÍV—Są¬ŚŔn7>ťś%qµ«ô~lKÁÖĆb“Ň›ßú+‘Ó€¨źˇ=ă?—g†p¬|[‹}VhbÁz[ҧ¹[ ¬^kţVĽ ¶mˇv¸'˝âŢM_ěF¬Ď3űĽŢ‚Ó|ĽµUú,¸Ě“ś-IÍc¸€'¬Ycv[}…Oˇ?ŕĐO˘ű!Tß×MÇuôÜCyćb«uB=ěV›MFv)bř™ŇĹv!Že·8ĚĹĹö58ž™Y¨5dŔEd ÔŃU›±C5śČ+rţ‰cq ¶#CR–Äébč¶$o›9¶Ŕ^0oT#'Ř!QćÁĺ2/˛ě:‡¬Zřč’M!Ű2{˛–Ä)lK8ëćk.X¨6– +˛jHY:Ź'•mFěvÝr‡ą`-ÎxřĂW¸t8ELΖx[bČ>­Ę«tĚě+6P‡Űĺ!EpǬł)6`‡šź¬|&(«nq¶# –̶¤˝;ű¬ň<Ča`‚“UÇňť•Ng´yóŰr5óăú—ç­‚íďťf¨'ŕd?)0ş¶hť´-=ÝżŞjŐ6ü‡O›j}J­Í¶Ăän\OŕÝg\S˙ÚAAl^U3‹. %F×}´ł8>ĚťŃÔ4c.^¨ź­;8¬uöěI[»v2ö¨yptišP8÷P%}jÇ`ɇ2śNŔ¶t ®nÜó¬ßȸƿô2¸’uÝ`^„łŰz⬠ĆĂ×ÉŰR¤öÂí >ĹŤ7>řh)D—^Wňź?®%Č’§jízŞTť4®áévgŘ_>yčłN¨tżô2îYrÝĽőO'¸89úKg![j7eŘ=Gö ĂÁü ¸ž’{]±YE^OgYÇXß-A¨­ >d_Áj©Â·(ťŔĺĎ}LűkB” ÎÄľŻ…Ś'ŁÁ. ŔŞ—ĚM ˘ň}|ś4ÚǡşbmÜ*HLÜ2ŁŞŔ…»ŘöH2˙ň·_déóÎIlŢŞ"ŻšnÁdxľÄşéOÓ\ 0…Ú4{>†`RÖ–W¨×Q*Cd´ŕéâô×aˇFŃ\Ý#w•“#·,©Ödi4YŘl±ąB[0»Ň\×ďzVŔ$ &&2'ŻTjČ † φѢöďAc!Őä¤Í…úˇ@Ř‚a^(Ą‘ľźž®Ý&ëB¦3%ŃÁżŇ€kĹËrN’`¸¤2Ȩ¦;ЉšŚj©ŚJĆpAŢžîďŔFž¸ć Iá˛"“Q_źJwŠBK% Y0Ü_Ĺ{hé·H:0!‰O—‘ş÷_®äÁđĹŰ@Ą:Ń·KE’Ţ}»ômQ:/•îC‹’ß ‡Zš}Qźw§i<·‰"kČ.\RIŚ©h,3ŮŚ¤B$! ƇßíssöŇŰR¦90˛ÂPăßŰ˝.pü&—Ö˘Vk´ÓďÇúJ W­ŹY]6µG­™öî5h¶^ď)˛0•áţŞ ĂRř …öŢtkµ×Z|ĆJ Ł­Ż´űŞ«9>€™¬ŹŢ(ááĽH}Y 0Z˛/şá˝Í•­#9ťFÜxgžăŽ÷Ŕ$´Üü¨†ŚÖčFéďA óŁÝ|ĂŠ·Ę; gWÜps6žŞŹ^'Łm%QeĐÚÝNçá‰(•˝iv˛p°oKŻű~‘ Î˙ë}˝B4pÜě!X&řgôş[(,333Ůś^7c¬+ŹOÓˇ.4 ćüŞiÇŻĚÎ(,:絯^°{ °ŃŰ·/Śá˙•eg7˙¶Ëdń;—ČáBJŞ•Óßk¦ Úëîč\.ś2fĚ‘ŻŁ‚‡§ôč1fŃč¨=4Ü ×˘9†^ÔĽbI‘U«Ă™m”fZY¸ů·f1ô°˝ClďdĚ×Ý#eC}ŕHT·ă˙ýÁP„µwÔiő*ܵťô8}ćĹ+š/şö‡WŁ*ě+ɢ#Ť}Qr83j,űřZŔšYĺ°{m*‰KЏ'1]ř •Íkw,ŰŰĂ’Ł™yÇĽŢ=Xćv§ÉąQĺŮć}łŕú3ßeÉĄűłÚhç!D&•dY”nG˙ď6 ŁzţÂx`=â‚ń77ŁĚh$1W4°®@†ž¨0X”vÇÄ3¨»Ś™÷ČkÝŮ™Ŕ꟬«Ş°jU Hc=ŕB= UDdY¶RskIt°ŃórŁ­Ę7č˙ź2:3úŃ䣦,Ł«Q"ęěëŃÜŻ8ÝŁi,“ŘţƢàŻčB´TŤµÚŔńyĚcTÉBžŘ’Š j“˝]“Śő–„ďĆAczp1Š yY$2Š i °d Gď+?uŤ ?‘mĘł›4:RhiČĎ"2ć'ôČ–„Ô Ń8­ćĽsöb4?xőŁ”Ŕ±‰9(u ß łľçˇ·Ţš6í‘G¦ß±x÷ !§CŇA0.ň¬™ËďšţČ;Ó¦˝őÖ=°8i˛HŹ®,˝É•Hš›ďN »h ĽŞ#`sš››Ż¤&]zO¤Sl’ôɆ4„‘kďîçőĚíoL™˛h;‘8±áÚěˇŮ~éź$ Oďéśľ~Ü=qďšňć%SŻš:ő’«ży#Q4"÷ŻkoĆÓő™fá[’î0 űáUab\Ż-şúŞKąęę #ăŁôË€›<,Ľ{źłţ„¸ś‚°°ôťĐ˘ĚˇsX ¦F·Ä‰€}đsb`Ł/9KŽ$¤˛ś/!S +µ$™ubQb VmÍosłgiKbŤ),˘ŹIłúd z%Á&äů=ΛúfČ#˝‚–ˇC‡f_1`§1˘Ź1çđ>ÜpĂ —ÓňűË y˙‘łÁz$äűŻü~9#0ă€ÇF÷ýĂE ”TŢYzÔ|lÉ–-ĺUËWŐ}šŹ}}őÔ®«ßHČ’ĎŤ_]·jůî-[–{´ř&›NÄıđVIt1jśĐíá¶9j«&Ö$¤˛…(Z`®)1yđN]SyŢXs»ŘWjŇ[t’O©úóý ŘÂ=›Mď2YÝcÝÇąß\rŐUSjęU—Ľy$ˇş"qjF‡9ß«tjmµR ü\o$˛4T}T¤Ag´8KíĎY"!vá7Żyó›# …W1żţ@ye±I«Veˇ˛ŞH†r2:ďŹxFB‰OŞŤÂăđ¤Îbrß"Hp żôµŻ.;|x!‘ŕ6.†ěŐ§÷˘ T†t,ęúAďB`ĘÉ"(,Ţh}ŚÎ.®Ź­® ’W^ZZ*ŞŽ_ýŐ]Bßút2HŤňŢ‘ç #Fťp:Uŕîôm@á÷MDo|Đx˛,›őśÔ`[j ”čÁgJO”ÄľN·aŕpTUJ§?:§ó«Öó–›˝jRŠŽąřŽ $‘ëJ:°ĄK<ÎÇ7vvťÄÄ źÍÁ„-żtěE¨^¶nl:Ś_¶¶˛ą¶D\ě.éÓ`öß&$ †ąÖ9ÜNş¬\ěoée[őţľňäÁ0—]I˝‹‡yvoI€KőŔŤťu_9b-p©©‚+……ľBg«^9§3nxáÂ~˘ĂMs±r°Đ€ŠR¦cíťp'ŢđÜŞ (<vüdČxPČ3Ů[ű …ö`b‚(k¦ÓČ\˛ Ż«]ľ•Jä;01—?xňD‡ąT«Âď-®$ÉR`Ű4ę«Í­ŰB01?wx[˙ ¨íd‰č|s±”Ŕr$Yeţ’­}9ÂŽńąeëg9Ě>§.ĂS;… ¨ŕvÜŁŻvŻĽĄ/j$ &FMde-«ˇ·JaT9żćę2şĚ®vYí+·n+Ah‚ř`bp-ţŕáëWCgłWk$e”{…s%˙Â#…NŁôą—´ęŰSl±Á€ ”uęÎUł»TŻ6¤PŽłŮż"ŠTi\Ő…E3' ęŰO(†‚…>UI4”Mn[]^QPěŐ«©7/QfdÍë •ŇšZoňŮ+v·¶Ďí;˛'G.‚ÓŐ‰„ČU6ŻĺÎYyĐbŻ´y €VLV]ŃŻM*­ť>}…ŤŽ™k¶¶ĎÝÖ·†éç6 Uż`řç-‡ÖÍ*Ż4•Ö˘"),ĘŠ«łŃr;4Šhô¦R_ˇ˝(o÷Ě k†Mš´őéźµťX·vâwËkEŤvwľ×Ç38ĄłŔbůň6xMšHaP©á`Śč íöĆF3HccAÝ]o-…¬ĹŁŁ”Ş”3X«Ó•†o) YH‚ĆjŤM»Ó©Tš(qąśz­FíQe‘čĚHY`±ÔÍFÁeP÷¬,ťŠťŽÖ …H–‘žî§˘±ÎFcŘŽ>¸źL&˘D&cŽÖ SťC(ŚĆ°QpĚÁ‚»âş!*¶ĘbŻ6.prgź¬Š UňpđBĄ+fb •<‹$]XRu>–S°üë€ţkňvhU9’9IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-180x180.png000066400000000000000000000174441433544061600242270ustar00rootroot00000000000000‰PNG  IHDR´´ öPLTE˙˙˙W!¬r+üüüůůůöööéĚ©űűűôôôńńńďďďřřříííëëëééé¬r,ççç‚V <(óóóW"ăââÖÖÖ­s+„X#‚U¬q)ĺĺĺY;ăääáááçććßßßŮŮŮTáŕŕŘŘŘŃŃщ["ÝÝÝÍÍͦo*«p'ÜÜÜŢŢŢŰŰŰŐÔÔčĚ©«o%ÓÓÓËËËĎĎĎÇÇČŰÚÚŐŐÖĐĐĐĹĹĆę̧ŇŇÓÂĂĂÂÁÁÄÄÄ­s.¬p'ČČČżŔÁ€RÓÔŐËĘĘÉÉĘW:ÍĚËáâăżżż9&˝˝ľěͨÂĂĹ­u2†]*«q(ÍĎĐ»»»¸¸¸ę́ŢŕâĚÍÍş¬ś®y8‹e7Šc3ÇĆŵ¶¶¸µ±®®ŻčĚ«Ťh;`/ÍĘĹŻ~BŻ}?ÄĆČŞn$ĘËÎĽ´«Ľ±¤…Z%T »Ľ˝˛˛˛´Ż©±KŹnEtMŘŮܸş»Ş«¬ Śu­w4ÇÉĚłłµŕĘŻëΫÝǫ¶¨©ťŽ¬zÔÍĹĂŔĽáηľą´ÖŲξ«ĄˇśŻ¦›¬š„Ł’|—j¸“e´Ť]{X¬€I…[(ŐÔÓŕ×ÍŰŃĹćÓ»ČÁ¸Áş°ćȤ­©¤Ŕ±ž«ˇ•şź~Ź_$U8N4F.ŰÜß¶˛­˛Ł´ť€´–rą—nˇ†ešb°Śa”xWµŠT˛‡R’qHŻt,©p*ŮÖÓŰĘ·ćеĚÁł¤ś“· „Ą–ť‡lłeŚj?¦n)ˇk(ăÚĎŐĚÁÇ»«É¶ Â­“°źŠľĄ…Ŕˇ{«•y¨t±“nś€]wO•sJĺâŢćŢÔŃÇą¦¦§Ţľ–ĄŚn¨Qµ=Ú¸ŽÓ±‡Í©|ł™z·švŽpL›g&–c%ÍÇż°Ą–Ą‰›Žś‰rÇťjĂ”Ye1‹]$ŢÜŘćŐÁżśr~^©†[•k9Ȳ™wKNßIDATxÚě—KhQ†MҢ©y’LšqČdň(‘dRFK+D­’ Ś›š.b"ÄT[uÔŤÖ]kń…Ą"!»¨7Z´b„"ęB«­b ‚;î]čą7sőjĘxí¤®úSÚlrţďžűźséş5­iMkZ“Ž,ř!‚Ď«[Á¸×z˘¸b`¨@DĐ Ĺ®®®nŁ&ř¶śµBŔV‘ű[}ý˘úúzđ%Ř Čx™ ëq…ŐB;«ŐÚ ÉjőlWŇîżUŔÄžĺ+¬2řťÍnw8\ ‡Ăn·5Xˇ]`ú—ViÁŘÄU€{5°‰!ňs§Š Č .‡Ý†LqHô˘AWpˇ Ş*F"\¸1v-#bi†ŕ§ć¤ÖÁ1Đ`4“%p슧6f®TTUJŽľ†| Y–Ĺ€ËakĐ^»FcCO916·P*ű®ËĄů7cŃd,p١Uŕ Ô:}F§¶CyčĚÍ™ĹŃ|~téăű®áćdLâĽVK„6źŤ»Ö®(ńl6Ź+ńŽâÂě–P,"€§5ľ)tjAmy˙ôv8ćy7ŽçgŽ …:¸¨k• 32 Ť—:úăćvgEŤŤfEé~8Űś”9ěą<µ3WN›úÜ››ü Żý6ńéŰ]ŰB11H¨khŤ9ňzáT?›)9łĘáůB4!ęęNfGP|—Oó@JÉëw§÷}žň%%ÁŃ`­58s@/+Y‚La;ăÎŇl[k: ˇvŻŤőŔ,_gLaż—ŕRŘáĄc[B2¦Ćç6hŤy®OqRČö&Ąü&úI˝ÜHxóČbÚ ]®–ß›î™Naj®`[s Hs‡ăąŠÚ¬RiÝP˝pĽě‚:˛”öŇm¦ĺ ÷N§˘˛@Îm@”ăxĹ\Ť­<™j— Bůg@đUŮę×Ĺ4Ťjęž®LT"ç6ÚhG0đş ĚzŠ— ™$G®÷Ź«˛Ú\ÜäLőY‡úéť6tn\Áb¬Ń8Ďľéă„8ç;}˛€…-é9†«â¤iż u5łŰ—8P@ @[P›p8ćşuŤ§±o$Ť˘ZM7ZÎó~}d/h:ĺ“ b¨‚ŃD;Ą¸ůorîú˛2†[MŹ?$*p±Y_áÇ×ŰPÄpŞ AĂ ĺHŁuµéđťTT„TS· ô¨ŃAu¸ÇMĐt[}µÚc Ő¤MÂł‡¤Ńú±ľŰ9)ĂřcKzí\î& ´®ÂŹdí˛VچČcs>'uöɦVív©cĂ «“‹ é€ňęh&ĘEKĺnsß;˛,Đćî3ç|t»4ŞďJ/01äăö»NścĐp·ĐBṲ̈ö+t>Čî€t¨—Ź›Ä>źI• ^Ľ;úťLĐ›śn"‘¤+÷>¸˝,ĚţôÄő$Śl  WćĐĄŽ•űŰŮ oíl A$ih´Kô}d…~zo #“çPýTd….^čü=ÔčAµ#CKnôčŃmP&q…/ąEłĚÍ^SŮ űŽîo–6+ mµ ąá<#t8‘{%ĐÄňź »»¶ G±bI ąÜ™Ţ˙M^3!ÇPÇĺ¦$šD°¤ć0×µŹ9÷kĐi@ʬĐÎç§[*Ď ˛$/ŞtďĽ,ŚxĐТ&b07+ŹúÁ‘Ídř ´Í%ťxďeö†'.îôz´="Íđ¸0ęĺöÍŕIríÔ’oĆmb˙bGKTti§6°§#'ŢžŠ3ľ.W÷î'·űk’ĺÔĎÖh~ßÍ#-!c{šĽg°>ˇ‹7`çi·K6žTX⽌ËăŇémIô"®ü_r 6ĺ…’bf]ÔôÎ#oj”71)úţЧO‡îiĺ ě°`“)«RĎ\RuúW~¨ľÝ|ŐŃ‘.>I”PcuáúaX|ďRaNJr‚CĂétzřŞVý;|&d¶d}ĹcT¨ @ůdŚ űVË4ôV |WÂv±ăčÖpĐŚš"ü˙B4^[ŽŽšş6; ‚0v?ŚDĐMŮuNřp´ {EĐäŃŰ.«ĹHÇGHĂ5d{˝‡ĄáYP‚x@EwźĆ ˝»wnă>ŢÔ}2@Ż­ĺ —šĎĶÔ!Đ [Ăh8"aÇg’áEI>;p«7kˇíA—ęDÖbŚFg÷–&¤.‰ÎpńĂâq“yŻ gÉÂŰX“ŃĬoPŁ"ÝpŔ°-šŘˇ?*Шőň·.'mÝX]Ŕ% qŢx-8DiK?€všěö'¤±Ł7î± äĹ(Ő[Nąš‹˙}ž _Ąď=벆,슾(63uý.3’)´d·z{‰źĘđĚź„Fo•ç.¦ú37Ż.“&ÂÍCη^#ň’GR.«™ ­zŤŘˇŐŤI‡5J=Š|«ç<‰[=jO˝k›Ůlv\D ˝WŞÍ\Áĺ.FN‚ŃŘüí—ôc»°eç^lµš·bs¨ČBń§mK‹4H¸SĘĺëÇÂŞُ~¨"* Cy a‘zmTňö¶‰µă?ćrýU·T’ť‚ ĘeÖhÔ¤4HĎqćPyJcQ„ţt˛W°ĐźŤqă†hĎĂĽ@é˙ zß]ÉŁřŻ ń"ݦ[v†˙§âÁ÷Lśa¨A’¨O]1‡E ÇĺA˙¸R“čdnfó±‡oÇć#&&nşřfÓÄ·Fý§÷®ĐŻÜŃę4ž¸9–ţrvîwĎůç4ßľ2%rĐP÷¶ÔZôŃ ń &će¨´âH&ľ_9‡\ä q&żşÄ’cUĆš"Ś@Üťt„ťĚf2ařšµš÷©Ç&’ĐÔOóťG¤?La ?éfš•L`v NS}ÄüçěĚé1‘†őĽ-f˘Ć|}Íł äý0~‡3a4KĘDś`V|@Ě‘†5®,“ˇ AŘ4kwŕ/0Ζ€©''ťĘf2AÎFÓµšnJs ‰šůZEr ¦yV}í§ăg Ş~íß­Ć0*͒¦ĆÉŐ×}0;%1&:ĐÔÜVťŁÁT"üÄ>/5ýíîţ_÷ŇL(ť*śś™N‰¸}uŽÎţµ;4l&3=U•š®4^÷ţ¨^ŁM…ú :¦vU*hëÚţNGÝÚ Ói<“¨yS|§ÄííŻ’UUŐ­čëˇÓ` Ö`ÔéŢ{ŻîčAznM…]ŹSgŻŰŃ·˘®ŞJćXçšz^x)3Âwwg•LFŘ{Ů Ě6[ÁŚu/ă5EhXň'µó=ú…bFC&„ş>Ť/KN č G«ű2ýĄ–=Ĺ7CĺśóblJ”ˇˇNnYśÖł‡™ÁŐ&6{GńÁIäŕč:üŃęZ>u9Ľifl´ˇń0ľĺőş>m©“}|ĺ%”ňFâöÂŃsȲŰÖ-y mhP?Ľz][vŕłÉŐĎĹiNm2ulU˛#ęĐńń[Z :ŘŐ&9Ő5\h9:Ř:żüŽŽ>4JĆźľjőá8@”ˇËŢăÄ*p÷kpn0óŔŹ™Ńö´hÉKFQß×g=GĽăNţŇw_vvkXG'&ŇŻÉC'ŤvuËÎE C:gB‘R}şŇ-ÚŮr8´Ń™|ä‘Á)I‡v%†®÷µ·7ׇső2Ѳ[VÝc)Đhăxł~8§éI”m—}Ú’íđ;z˙W&ň·vĄ•–¦5LäBű軺›ăůă|Ů* ·ÝµÜYbWdĄ¦ŤC5{»(ŤŠŠ’{V ŘÂď`φ4؆ö‘Dt}wi¬´§™˙†™ň›ĽŘë,¶+ ršë漅„I%7*ô–üĺ«Ú˛­?ň=Ňfâ©çA·— ßŐ]Ďwőç­@î]‰N8ObńȧޓgižÉČs-˝­s?CľŁEś.„uh8ß!şš˙(~ŮŮö^í% ®Â ł•6h€9\Bí§NךöbgĂĘމüăç4@“uŹl íëI Đ\ěonmĽôݎa Ú6N%j*]”…ľŘuý5[Qʆő4Y×ßÓ|č@?Ůg7Üâu[ô:\ŘŻÂń´¸5a;·ĐµqYč]bx4óNŹĂßŐíă;mż×@uŇëhщ¸ ‡\ĘuőÉ‚ŻMć ÷Uü‚'^|©p.â=>ń¤=>\ ó(ÚX3lĆĂĚ€†ń5;ćjÚŞ#ĎRäćŐĐeŹz—/m¬űgţËĄą§–ÖÎ=<čşëŤĘ˘˝NŤx‚[H?‹;©¨WCm|`YĆ`s{{ýÁpŻq_{wwŢ-aí­ë]ěv]€©y~&äĆŚsOg/y3ü¬BäĚí1Č=âë'”4ÝX h ;í5°jţZ+a+•ÖdËČ[/!+ĺ@KÉOżŻÄĺşÂ¨va%pS1fxÄ YžE'ő7?ôš[fáÄSŐŔ ěă…µCśQX¦TL:łÝâönţ /­D3»Ý¬ě˘LgŘ !•0“Řźş7ëÍŽŽŽEžËîY~çfÎ‰ÇµŁ†N^siíťËç\ćYÔѱCó‹zŻŠ},BBeÓ'íí vł–––¶¶${š^î۶m?CĆŐő>náĂ 0űüĽjĐľ*G•ă°ť×z E:ň¶]#‡}Íľú)IŇ©ă?j=ďĽó ˛µJĘôŐăęx'˘ď”,[ÖůŐt©Ě۶ňum*EÎ/;é@g6,PŹŻ5P=ŽřW^‚rľ»nT%>{¦$äŘÄCíC§ž+Ř©Ă=ÍRCzިŠ<»/DĹEž&yií(Ýa˙ÓĄ„FĚŔzŘÎ=u¨]4 ®ýťGB?˘0"ÓřýSĎ ŤÂeşčÁ!B–BMĄ‹l´Ůčżg×Ó=”َ«~¸2VóHGQů$Aż“= zʏJküăiŘWÓS$@˙<<úT)®Žżú€l ´ ç…Žé:|[Ő‘‚0ačz0ʎî’ŇŮÁ&«{ÄfH§}zTŃů|ŘZ?">& Č1ÖuščĄÓs8Ş ¬ Ű{BśÓqű~í_±sçÎU°»őö~„{ĺ‰?‡ă„ÇpłáôęŻ{{{ď"c @Y±sNçżJFBj·äĺ;‹ĘĽ ĎĽĺšŐOť5qč3»ĆB§ŐKČ–Ţ}Mp—T^ßĐPVärćVë¸ :+BďMÔuf}®ÇR\xÇ·Ë;ë’ŹĄ<‰ÓÎťĚáżČ)ucβؓ‘«7댸^®ĺBoŐTÉŐF«NaĂ\‡=Ă’ç,ŞAOŠęî#ˇ7ř¤¤ĄhF­,sz0ěc¶)tVdŐŤ§!ói˙2^­q&ŤÂ¬÷ä•mN‘’zśź6 “LJşę“$ř9~Í5 . gšVŁ;u•ňtĺÓT»đ6Ĺw̸ň”˵Ť-wţUč=’äíôuő”±| ČR=wµ—Ífb'-ÚŻQ·±|W# ·éţëéT&4•¬§Dö¶Äí#SJA¦îűYNČ4”‡´ä™ÝéV.óUę[.ÖăäĹíë–fJNŞ©VĽ$ٞŇŐá¬"V‹e-" ;ÉŚúą+$0»¤ß#^ĽĐ™g÷«4Ŕe­íár3püçWő¨—±ţü¨ßŘ"8 ‡1ĺ_–@!®·—Đxn˘VZgĚy|fÔˇź¬Í·°Ü”b%;şĹÓ§¨´ş÷ľ’ĺ.„¦űËK ĐDĚ€ťôÖŰ,Ĺí·>”Ő~Źą«ťwŘ5ZÎŢ[É+d ¶ zŁť„‡ă1BSá$™«vŤćęçn›hb.ĂChs.g%RÓ5ť)ÇBľŽ<41{Ý(…ÖŽ´óŇèŃW?÷ĐY±‘‡F<ŁőŰ“cbĚÔů!ę“uAő­Ż¤ÄÄF:ąiu™;#‡“6eXSy`Í©ľ÷ťé)‘…ŽOz˛±Sű~féËÉůťođµé™ ÷úŮpv¤ OĂôő÷µ.ŚŐ®f#c"µŇhÎ-©y%Ř‘€FGoRÓÂ'Óý9×É“ó5Ą|úůĎnľm&°' /'ĎŰŇXä¶Đť!§gpRľ>‘.Í *ňk–žź‰ń–I@ÓŔËÓw/śUŽp¦ŽÁČúYLYĹźË`2Űç/~sŮôiŔćCs‘OOjzlŁÓ]’k3jUÄéA†Ŕí ťľâćg7]KŘ’ˇśä‡oĽżŚíEĐe‰•+ů9C#lćl…ľ:Á¦eçOË<š‰˘äÓŻnşń~oyľ˘uMŇ‘F&j ŠlsîüügÜ:;“ą;v˘Đö‹?;yŢKo5zËóŠsmVrłŘŁ! ŃŮ‘śÜęĽĹ5ßŔÝŕ†ńˇ b U>Ýôöóµ·äš5ţć…ż±‚a+ ¸h¬.q®|`óµ7eN; ŕ°ńˇÁ›„8>;ů‚¦5 gąň =öÝS9?r'ÂŘt« u! Ť AąVÖlÚzůů™Ó\0@O~Î B‚Ť­>=÷Ąźß8Ë'g(¬„,üpŁč"ă—P9¶\m‚•íf‹ÔlzgŮĺ7ĹĚ$vŞqĚŘ2Úä)\ôę–Çžo¬,sşóЎéLYr†L‘AnŽ&5Evşź2Ëʸ-…yĺ.?°éă­K—]{ůWÜöÓĽ‹ć>üęÝżżýŘÂŐŤ•Ţ"'DE C?Rů‘ą‘•ĐNÜ­Ĺ=ş-be V3”;‹Ľw®¬]°`Á’%Ťk+ˇŘ:ËËÝsî(öä˘ ťSŮxEĺ(ÚÔÄ|$6´3ř› ­ć;IÄ…y›sG!I¶9$BQT ú!s"#ňÔ˘*"p B«ÁhŐ(F\ ×ëí0ü/‡[ DP§ĆZ8äżÓDl‘›”V+•Đ!›LV«żˇŘú'fŕaDLČQĹćsźŠSA$†JL& ¶Ś×LÄr‰ůŘ"÷ń$´Š"1đÉđA°ĆŔH˘“ vE™~ (­DNěÁ& ¶ 8@| XĐÔ#ŘÇ[(€Ä „ ĐÇá3ANô~%Ř#^/ǦoĂŮżŔ·˙ŰŃţmźhŘŁ_IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-57x57.png000066400000000000000000000042171433544061600240670ustar00rootroot00000000000000‰PNG  IHDR99»Ćs·ŮPLTE˙˙˙¬r+W!ýýýűűűůůů‚U÷÷÷¬q(ďďđV éĚ©‚W!öööóóóőőöôôô•c%ňňó«p)ĺĺćęęë†\(ííí­r+¬s-đńńéééçččáââŢŢŢëěěŚg;çççŐŐŐW"=)ŰÜÝ­v2­u0…Z%„X#âăăŕßŢŘŮÚŘŘŮľ¸±çÍ®Ż~A®z;‹e6}SäääŃŃŇÍÍÎÉĘĘÍĆľ´˘Ś“tM°€F®x6‰b2‡^,hEbAW:;'ńńňîîďĺäâŕŕáŢßŕÝÜŰÚÚÚŇÓÓŇĐĎ×ÓÎŃĘÁŔŔÁÄÁľĹŔşĘ¸ëΫ´ĄŻ ŽĽ¦Ś¤xťZ­|@Žk@­w5ăäĺëčäâáŕäßŘáÝŘÖ××ŐÖÖĎĎÎčÜÍÍĚĘČÇĆÓÍÄ×ÎĂĺÓĽŕηý´ĹĽ°ĺÍŻçĚ«ćË«µ¬ ®§žË¶ťş¬šł¨šĂŻ–»©”Ŕ¨‹ŞśŠ¦–€¸›xˇŚr¸p¶•kş“d˛Ś]ł‹YzX±J_,¤m)ˇk(Ť]#€U zQoJ[<çâÜÚŘÖŮÖŃÝÖĎĘËĚŰŃÇŃÍÇÍÉÄÄÄĂŕŃżÉÄżŃÇĽÎĆ»¶¶¶Óųɾ±ĎÁ°Ľµ®Ç»¬ăɩ˹ŁĆµźľ°źĆ˛šŕż™°Ł“Ĺ¬Žą¦ŤĹŞ˝˘«–|Ƥz«”włuť‡k˘hÉśe´Ž`ś`łŤ^—}^˛T’vT´…K“pE®w3§n*—e(ťh'”b%vN^>L2G/ěęéďëččĺâÝŘŇÚŐĎĎÍĘÔĎÉŕÔĹčŘÄäÓŔąąąÄľ¶čĐłÝɲŮĆ®¶˛®ĚĽ¨·®ŁÁ˛ §ź•µ¦’±˘ĽŁ…ˇ”…şˇ®šÁŁş Ó¬}ˇ|ľźx¨Źq‡q˝™m«Žk—‚g´d™_ź‚]¶Ť[ކZŔ‘X¬„R¦€RxPŞ|D‘m@´~<‘g4oIP6×ü?qIDATHÇĹ–usÔ`ĆąxIŽÓöĽw=(ŠTˇĄ´¸»»»»»»»»»»»»»;ź€Í‘&wĺ 1ĂÎ4™ŮÉďöÉłűn“ë˙Ž8Žk$µ9’Ö ‘&đśIAN˘A‚.UfÚüůíbÜ ÔdŰ ޸ä$í ‡ΗżđëMXW’‹ß¦[,éőjĹ@IbÄę4ŁÂÖ·/&"Ľ(#­ËŁ0Ěro^wbűßČ*-Ée>^LľT~áľßFćTĆy®%Ď,FIbťz(‰”;š˝g†Á}PKŇŽŤF«Ö˘şîŰ“ě4š¤íKĄÁ­ăő6ę×ŢŇ$oÚÄ›äŘŰ@ô*ż,ánĺ=ŚŽŮŃÚ;ȡ‰nZlRČÍI•¤[j "Ž&qŃąNędÇÄuđ»ZŰyˇüg@ę&U‡®rͤIş¦A?ÖL­#ť–Ů 6ke!_˙ä;ŕp‰n.y€Prm“Ţ1 łjźü’S±&gu‡Eő«:\ň©Ť‰×^DĽiu8ČíÖ«! Ѧ:kRë–€b·ş§sPą…•Ô&Iki_n”<×ňĄĺů+ĽôEĺ”™Eş[a°yôóćů=čđ–ÔNE,ş&ëŻ#S*ů›~2µ‘HŕČŐŽĂE(ł˘¸Iú<µ›»éŔCđĽ˘őzž¦ eÖîcĐ$FYÖOró°î‘TQ‚?ŢŞi‚ŮÜd-tCĸ¨őq·cb«“6WOI«Üńł*Wě˝O§MÂľ­ť8yV|îs&=ˇŚzăËąsç"UÁžeťĄçšÁŞWČö] µUÝ™Ł‚Č± Ď퀧şt"I6¦Ý•.+żepŃ Ě(“y~”Ě›M~_ŮőjŠ7B$Ô“i-[iî†ěN†YrtŘo˛č‘Ě ă”®nęئąÉ@âjWxÖ™4É&GgdŽß/×[2łdQĺMwVŤsHW@RĎš*mSý98f´ňžEŹS=Ú=Óo.Ĺ+ %蛝NŮĄ’aaaŇ%Ż ¨d±ŽgO5m rł :ßuÉŮ Ą0—‘1.ůiI×řř `‘LšŰŰ5·†acŽFFFJn« Ov±x0ů8<4NČ2ëgČ0DmČA˛M§U™SŁVý±ă#ł˛˛"Ź„Śn‘ž5ćÎi7Ei(N—6ą*x“„¨u8óŘQhH0ąąjbśż¬Rş" 6ktĘ&ŐŤÂÁĆ-kg zş|˛ICô˛´˝9şÂ•XŐÂĚ#>ăŕu]}2ÚçÓŇżĄI$46_ůŢ…4wB‘~-Ä&’ßµxX8 )_±nM‚ě e٧ß-®crć©[-Ρ'ţ¶6 ŤG#ň1řPŚă(ĘŇ nÁŠe­Ľ¨ž· 3nZ(-_ţQ–žá{U«Xά~˘jó¬ĂŰrv§ZË{öş±¨zkŻ?Á,Đ9 ˘YŘľ¬9˘‘łś«\Ůh“ÝjIQÉÂTŃĽ^˝ČÓZ6-˙ËěÇ/2FE9\CIEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-60x60.png000066400000000000000000000045021433544061600240500ustar00rootroot00000000000000‰PNG  IHDR<< ")@ńPLTE˙˙˙W!¬r+ýýý¬q)ůůůüüü‚V÷÷÷őőőűűűôôôV §o*óóóčéé["ę́V đńńíěí¬r*îîďęęë­s+«q(<(ňňóëěě„X!đđđďďďççčćććŃŃŃăăäŮŮÚÓÔÔ­u0…Z&ääĺâăäáááŐŐÖęĚ©±EŤh:ĺĺćŕáá××ŘÍÎĎËËÍčĚŞµ­Ł®|>­x7‰b3_.¬s-‡]+Ąm(~T áââßßŕŢŢßÜÜÝŰÜŰĘÇÄçԽ¼µćÍŻłŠW˛„K’rJ˛AŽkAl?\=îííÝÝŢŕÝŰŰŰÚ×ŇĚĺŮËÎĚĘÔĎÉÉÉÉÚĐĹĚ޽¸Çż¶˝ł¨ż±źĆ±™˛ź‡¶–nś†iš}[®z;Śf7­w4g3śg'zQiEęééĺăáâŕŢÝŮŐŘŐÓŇŇÓÔŃÍËËĘĎĘÄÇĆÄĹĂŔĚĆżÁŔżĆÁ»ÜĚşŃòǽ±äĚŻŢČ®şµ®Ç»­äĘ«ęÍŞęͩĸ©Ľ°ˇ˛©ź˝®›±Ą—Ľ©’»˘‚¤‘y«’s»šrž‰oµ‘e·Ž[›{T’vT•vP’oFŻ~D¬|@‰d8©p*ˇj(uMqKnHeCY;X:?*áÜŐčŢÓÜŐÎŢ×ÍÄÄĹÔÍĂŢŃÂÖËżŕĐ˝˝Ľ»ÂżşÎĸ×ȶÔĆ´ĘŔłçϱŔąŻĎŔ­Ęş¨°¬¨·ŻĄčɤ«¤ś®Ą™Řą“ɱ“° ‹˝¦ŠŞ›Ä¨…¦”}Ż{Ľźzą›w§Žo»–j·`˛‹]¬‡Z–yUµ‰S«N°‚IŞx<˛{8Šc3“a%‹\#Z!`?C,ňńńăââćŕŮÔÍĹĐČľş»Ľą»ĽÉĂ»ÓČşă϶Ľ·˛µ´±®ŞĄÁ´¤äÄťŕÁś¤ť•ŢĽ“­ˇ“Đ´‘­ °™ĽťwżťsˇŚrˇ‹q͢n…n­Źkľ—gś„eˇ†d±‹]łR±†Q˝‹M xF•pBµ=Śa-Š\#H0_ÚM; IDATHÇÍ–epQÇINrR’¶‘¦´MęZŞ)î®-îîîîîîîîîîîîđ‰}i{—tîÂđ6“ËĚÎý˛˙·owßË÷߆a$IÂSÜ+Ž2Ͳ4EX/ÍyĹX’2[/XPɤe Ă{Ő­«TáĽÂ,Cëjî«z>°{:Źe2—d&xOjFgD_?M3ăâáéäŢßÝÜŢÜŮĺŢŐŐÓŃäÚĎÚŃĆç×ÄŕÓĂŢĐŔĺÓ˝ÖÉąÍĂ·Ĺż·âÍ´´ł±˝¸°Ć»®¶±«¸±©Çą¨ľ´¨ĺơşŞÇ±–Ůş”­“­˘“»ĄŠ±ź‰Ŕ§¸ˇ„»ťxŁŹvϦtˇŚrىiś…hź`Ä•Z¨R«M˛DŽg8Še8‹b1źi'śg'pJI0E.A+ěčâŢŰÖĆÁ˝×ìϿŞÖľ Ě·žĎ·›ŞŁ›Ä±šĄźŕľ–Îł’˝Ş’©śŤąźˇŹzŔźx»›s©‘s—…n¸“f­Ťe¸„Do=©x;‘`$Ž^$‰[#‰["}S mHŇípKoIDATXĂŐÓ@†i›6 )Ô©+P ×;îwwwwwwwwwwwwwwww‡awIZH Ă 3ü3w׹$_ŢżOşI‚˙C ¤H‡"ST* JĹ]ŔâŽDfsIJÇ0Śš$ JÁJ Žč(°"…‘ŚŃT©]Ć%&ł†‚çów (ŤsqĆöą|šŔÄBBmĽ~gĚäég& .č7ë@P‡ÔxómZęl‰;—Yi“ajgÁ iőz×ëŁgô2Ärč%Î+‘’5­ç3R€$Ρ̷¦ëĺ¬půĘ^<…Ş©tL¦”±R–.â7âë¤ é®g‡ÓˇX3Ľ1 ŐTTĆKy¶P ˇ7¶p„ÇlÓ˛§+HC­2!é„ĂI‰…¤ čn3qą@ú©Í¤ ÝÂ: ú╢v‘¶]ňáS;›Ô€4ąJ Żn7ĺ}®%Ĺlöi  óCgBoÇ@°b µix(m‘”ŕľ0ÖŢéÂ@cNR%‘u0 µ/•Ť-FYĘÂ@#Ą@®]a|É@ÖĘ0R˙X1kŔ€9˙L<4ÚăYĆtÚQa u€iŃôrăëýg‚ fqcâk{R^źxú1F{ŕ;gĆ©Ô$O5łß!šO9÷ť44˝¤č"­‹FןľW¸oq|ÖŢ +7 f ÷ÍÁ:ë˛ Ţk„ń»·˘=â; ‚˘ Ů5Š0t. AĂzÄÇDÎ2äők 3qo{äďńI +˛ž›&0ŇX+'¸<Ý“LžŕPŃ@ŕ ‚Ä˝ĺ/ ŤĎěÝ*2` s=XŹ9xbŰf‡ˇ•®#ĺ ykýZŐv¤Ş‘ň†©Ś$F9wĘĐĺQőĄˇłě|Č™„·4÷ˇ#ůAOlç©0¶©ÍäÚŻ——Ş»“őŹň˘ś‰{#-§"oyŁŞL„EY(L5ĘQ5}\vDÜ0RŘć)_@o1Űąłá¨´˝j’ńŤRÂTĹ·§€ÎŽçÖę0iŰZČa$2Ť† q™|4c 4† Ćńą‡*aöú9,×°˘ýVY*^ŁS#ř·IŐX“EŰvďR­kŃM#gŇŢü`¶Zôë+ź¶%_•¶Uj4cëq/šJ‡ o¤Ah(­‹†ˇĽ)<@Ĺ'/Q" +yňäs…î†ß,”ŮcG{CaéśvOŐqÂ*@CAó„Ţ2îäĐ)BÂHĆ’2_q(éěyóË·ä­•ť—|ţ!¨ÔƠߦ) ÔŃŢ”ÝC›µĺĹDÂĹ.S®lhónńřť4*má~ßękí©-*iŇďŮű,ű•R4ČXčĄÁ"ńI[´¦@ 5oOç#âi‰f—-›LŕŇv·WÍ  ,…;w®ŚÚ âÚńV˙§ĺśV—Ż,¨đĺgĐ»¨$a!q Ărâë‘oŹ IZ• •9U]łîéąaAYą:âSw•]-˙“»álʬHĂ׳«ş®-ŘłçžpĐ…,čJąpPΆµz,¸¶k% ĹYS‘SJGú`¦.Ů~ZíÄtů+ŕęqc|î`úÔnîQ =6:]&ęŢ1řO1%»Ta.×d|@ýr§ŇZ]fŽŕ{N_88ikęCżĘ˙§_dd‡”N ÍDčÔV@acę8á÷&ä¤j©,`@ýꉖÖVź¬Ź R6Űę°Š~I˘U7Úk5‰ü-’ŁOzT=Rµ6{őlr\”ˇNzvćK“üU†Dăâ eŠŁŐGŠdĐ:zgk˙¤L—ˇo¦TVŤ‡Ż)ŤËW$[ އ”ĘśŮ÷ĆGŮť:BŠĂO9µŃ«7dbtZ€Ř×)¦#ČŤ&Ď‘~?˘3šÜŽ<µ6ĘÖdZÉ÷łÎ•š’}d˙­™©í^E8‘QjÚ’Ćž**.ĎŤB…6Ő¨–9“ǰ›Ě´FǸÍeŇúín·ÝîÓZ-FšYaýv;š¦5ŚŽm…q ńg¬/·đĂöŐßľoCڤo×3…ţţ<ĄKIEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-76x76.png000066400000000000000000000055271433544061600240760ustar00rootroot00000000000000‰PNG  IHDRLLđIˇýPLTE˙˙˙W!¬r+ýýýúúúüüüřřř¬q)óóóöööę́‚Vđđđőőőîîîěěí§o*Z"‚Uňňňääĺććć«q(ééęęęë­s+ččé<(ëëëX:ççčââăăăäßßßŮŮŮÔŐÖŃŇŇćĚ­ááâÝÝŢĐĐŃŕŕáŰŰÜÓÓÔËËĚĆĆĆ­t/¬s,ÎĎĎÍÍÍ­u2…Y%ÚÚŰÖÖׄX#X"ŢßßÜÜÝ×ŘŮÉÉĘËČÄęÍŞ®|>­w5…[(;'ĘĘĘÁÂÂľ»·čϱ宯Il@®z;‡_.vOgDéâÚŕÝŮ×××ŰÖĐŃÎÉÇČČĚÉĆŃĚĹĹĂÁľ˝˝ŇƸĚÁ˛čÍ®ęĚ©ľ° °¦šĽ¬§†˝ŁˇŚsźmť„eµŤ]ť€\’rKłJ‹f8Ży7Śe6‰b3`/†]*U }SrLbA^>çäŕĺâŕŢŰŘ×ŐÓ×ÔĐŐŇÍÍĚÉĹż¸·µłĺÍ°Ż¬©Ę¸¤´­¤­˘”Ŕ«’Ľźz«•zµ•nź‚_”vPŻ}A’a$zQßŢÝÜŮŐŰŘÔÓÓÓŕŮĐÓĎĘČÄŔÍĂ·˝¸˛Ŕ·­ş´­ŢȬȻ«Â¶¨®«¦Ŕ´¤©¦˘´¬ Ă˛žÇ°“¬źŹÖµŤ»Ą‹¸¤Š´ §•Żš€ÁŁ~¶™v¦Žq˝™mĽ—i·‘b®‹_™^­†X¶ŠW–yU˛†Q«~FŹnFŚjAŤi>‹`+›f'lHF.B,?)ěęćŮ×Ô×ĐČŰĎŔĎČŔáŃľÁŔľ×ËĽş»»ŢÍąçŃ·ÉŔ¶ş¸¶â˰ɽ°Ľ´«č˧ş°ĄăÄźŔ°ś© •·§“ÄŞŠ«š†·ś{»›s¸•iŁfłe—aÄ•[š|X°‡SŞS¬‚MŽqM–rE´A§m(ˇj(ži(™d&‰["P5ĺÖĹÜŃĹăӿüł°°±©Łť·ŞšÍµ™Ţ»’Ý»‘Č©„ˇ“‚ąź¤“~¦”}̡l´“l­Žh»•eş”dŞ„U·‡L’oD•k9¨s2ËQ €IDATXĂŐepAÇK€î¸kJ)–Đ4uwwwwwwwwwwwwwwwwźîîµ˝ă(Ű~čt¦ďË%yłżĽ˙>ą˝Mđż™Ö÷ç$µZĄR©Őˇk”?|Ą"h-EQZšPÁ%r%óáQË›őv‘gµ„Zů ź^âĂłhÖXĽËěyófwŮě6±´Jä3·­ÔóĚ™ž•†ÚL:ŕĂĂTZ®čäşÉ‹«{Ľ­+D_ëéůÓFK›†ŕĂłřâóăG ##”¶s4R#řÚőNÇ(1ik—ŇCF$͵~ŹP‚%®UBĎh§•jšT;‘\mäĂFŽGYŹŚF­J‰|:Ă,EőŚ2_XÁmľFJYdÖ=náß ·6`¤,¦A[Đ­ir„ĚŞEYh5ôQć ™ťđóŔVĄ}~â`YËk TJŕc}˝#Y‘µ›µaR]"‡ĺËlc!Śŕ6]•Ăę´×SŞp0‚m˝„”Á˛vHĎFóĄ0rX)˝N>˛ d‘Edëç ĐŢ´iOA… ¶´=f›-‡%Ż2`ZăřT2SÝ…‘ ʬKJy:§Ą1Ňjಓ'3Ueč WgjÚ8´.ůc·Háq2ĆŞ0ť~V¤ó#°UąSó¦hů §ľëLą|y‚őňB-*Ö=ÁŇŐ[™ý;¬fN‹©tö.É…}ź9nÜ»8Thńv€Ę,Ýą ť_Tv‹A%V§Ö\é$?,›kTVüt1ł›U© .Ş„5śPvűÎĄĚ•4`atú{$†°…;3Ą(ł Âň s°€µűĚŠ˝™RA,S¨„átŽI aËs{Ó¤XU­‡#T fë1°´rąŇ Ë#k\Żél·Ś„ű_śщ’Q¤ź0ËđF°Nk–Lu(»ĆXŻ !O5%1¶Xšj[ ,RiŤyŁ:ŤMăŇZ=ŢŤTbuęË×G Ţa°ł;Ěk\•Ś&š Ě•PTŤufF* Nt˘‘3Đmj%Fmďš„Ęd:XEP†cB9ÄşCjdź2„ŠĹëôˇ€Čłř±ŮP ¸ô:-gź%ěz¦ŘšHĺĚv^%š¨†­}IĹ%GvĎaáŚ~´SŤşFÔÔŁŠiĽJ4ë-Q§ˇÎ¸ąF ęÂ\Nź=˝5@˝ÎąN¤…‰XTŇĂáTŠsíą¸cÇ|vyâŘ1yGŞkve玨RM§V§Ö*´T¶nť/ĂgňlŮľ®nÜ(•Â&-źůsŁ÷ Ţ„–JŤZ*âŇÇĂ-a˘ďs'ű•+ ÔJ™˘J|>ył‰M€)ľ€˘¤ŠąÄ˛,}ŕ˘Ŕ … ÄSذGhđˇ˘ĚmşgŤŔÁ «2X´*´š…—đAYőHIâaŠě5JąAÇ ËT';/jŃá’¦,Ĺç#­vf7Ż%СTv†„gÂ"EôVžăĚĹ?h2űLg±)ba!Mú.o™§Â¶â©Ó§o3Od`aŠšíS;ăřŕUżf…ÖHĐżpáBÁ]u4“ßĚ!TĄ–ĺ­g•¸ PÉ&,µŞ%‹ŮŚśŽ–Đ”µ±UŃAíJMaAĐ„·ĂŔfn/Őv`Ń–śXŕŔşqw… ŃŃŃS“˙uëvłBM~Kő<X…Ý­ÄÚŻqˇ˛<­˙ Vżë6lX§Ů/` ĐŕşN…â`l«NXĚ‡Ż‘!¬5wr$–c]ˇĐřrX€µ1H`şV 2h ›ä°&€…¬p:9¬É%aU§MV Ś2ٲŁÜđňĺ«Ĺ…Ş\/Đ64 ĄëążüđrĺvÜdžX˙ÖôĹ\)b#ň…ŤţëräČ‘t}óP•«Ć•-ďŤÉč= ”¤SÇ­z·ßő«â¸Őżp‹ć…~QŻ=6˝ŐÄIűI ;LsÖâ.Q‹ ÍgP´éBëSÖ©çYt-XňËBË;ö÷%˙l82őşşě îRÄŮ÷ľÉ?IiXXÁ)ń¸ďŻ Ęš±{ň÷0¦`Őś0źčĐŕ”,v*ĹĂS2ĄFeŹ1aćć]Aâau&ĆűŤ¨p†jÎźůeV’ cVĎíőµ…ĄéLľĽ˝ @\(Śaťëš)ŤŤÎ-żŁÁ;}oŢ9ůR’$)Â){ţ]13+ť‡xˇăő\ĂŽĚYV y ^OÉ(˛7¬SŁ[îX§ÇŔ’ÇłdďŢŕre~Piúś·g^XTłzŐĘŁĘÄÇDů¬˛7Čďič c2ř2dt‚3g,hhg”Çm°°aPXşdy‹Ů ·ŮÜ6;¸Îłp,E†… O¸~ÔéXťŽ˘hŘŃxž‡€`¨ˇ•éÖöŰ7’_·Úű~•IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon-precomposed.png000066400000000000000000000200741433544061600256070ustar00rootroot00000000000000‰PNG  IHDRŔŔeś5PLTE˙˙˙¬r+W!ýýýúúúóóóéĚ©÷÷÷őőőđđđíííéééëëë¬s,ććć<(„X"ďďď‚V čččŕßßÝÜÜáááăăăŇŃчZ"ÔÔÔR„Y$ŢŢŢX"Y;äääŮŮŮâââ­r+ŰŰܨp*ćĺĺĐĐĐÖÖÖ‚TÍĚĚ­t.ĘÉɬq)«n$ÓÓÓŰÚÚŘ××ĂĂÂŘŘŮÎÎÍ«m"ËËËę̦ĎĎĎČČČP€Q«o&Şl ĆĆĹľľżěͨŔĂĆ>)żÂÄČÇÇé˨°t,¬p&V9Z;ĹÄø··¬o$áâä~OŇŐŮĹĹÄÁÁÁŃÓÖ»»»¶µµËÍĎČÉË˝ŔĂ´ł±ş˛¨ÜßâÔŐ×ÎŃŐÂĹÇąąąŤh;­y8‹e7­v3_-ÂĆĘŔŔżíΨS7ŰÝßËÎŃ˝ĽĽîŃŻ°®¬Šb16$ ŮÜßŐ×ÚĹČÍÂĂÄżľ˝·¬ťž‡k­}DŽk@€T^>:&×ÚÜÇËÎĹÇĘĆĆÇş˝ÁŰÉ´éĚ«´™wâäçŕăçßŰŐ×ŐÓşĽľŕͶ¹­áɬ®|?ÎĐŇ·ş˝ŕĎ»ĆŔ¸łµ¸ľş´Čľ˛°°°Ş­°§Ş­¬Łś‚a°I±B†\)uMbAÓĎʰ˛µŇÂŻ±ˇŤ¦ŽphEÖűëĎ­Íľ­««Ş®§žŁ‘|ş—n´Ž_•yW‘qIÝŕäµ·şŔµ¨ŻŞ¤¶¦‘¦™Š¶ˇ•}`°‰Z°‡S’tQ°{:}SŃĚÇĆÂľşµŻ˝¦‰®š‚ş Ľťwh¨N±„M˛v,ˇk(ÔÓĐĚĘÇĺŇĽĄ§©Á˛ˇŰş‘°“o´’hµRŽnFŮÓËŇÉľˇš’©ž‘ “„ť‰r’oCŞp*‹\#xPoIËÇÂäÔŔÎÄ·«Ż´ńÔ±˝­™Ő±…Ş•{ťŚwµ•n¨‹gś[™e&ĺáŰč×ÁČą©Ą˘ź˝Ş’«†YŔŹTK2ĺâŢćǡăĹ ÇłśĂ­‘°Žc—vO”b%C-ęʢɧ}Éśf«y=ĺÜĐáŃżŁ{KMNt=÷IDATxÚěVKhQ5QŃ6“ĎĚDš“!¤‰qŇĆZѸq!(‰ ( ő U(QüB‹  ˘Pě¶Bˇ]*AŠ˙UÁŞ7nD´ qguí}“Iî<«yIgşz $ŮÜsĎ9÷Ţ™ËXĆ2–ń?b% 3Ë,=*\:ÚĘłĘ,5*Läo†{gT1ȻЂĆŢ(yÝ*K1żJ»FŤť’ŔnAFłWĄ]»víę*ŕ7j`rŘ=VÁ2Ő…0H¬Ń¶´¸\­*\--Ŕ®‘3ąő&¬&eŞUŞep —Â~ŤX9Îćqxl6D9H@n– Ş ­­ś­RĆe I`Ż h’,Ę٬(ňűćŮ­öB_8ŕ%Üu@,c“äbqčĘóŃ{˝ýý‡/üúú`8šďx‰]¦yŕř¸8´?1?ďÎt§ÓJ:ťČdŇă/GN:A ©˙aCĹąXžęŤĺr©Ŕ—ĘĄüž,„;yÉĂ™§™ˇâ›C,˝{q(“°Äăv»Ýř‘ÎXîŽ „:ů¸‡ ţîŐţ‘9w.ôűťÎ`Đé„o·/wtęc!$Č`DM‰ţ«ÄŮüŹéŚ·[ô°Ç-ĺÎú$ďe(Pűç% `аŠńţW·ÚĽŮ÷ÓÝ”ýěJâŮž!ČÍŁc„2j˙Ą>7Ő?żŻ˙ú)’Ë<Ä9Ě˙ÄŐÝ? »%ńť(@óţfřź˛Âđ×QpřÉÁ$©Í˘ń4ç’łč˙?2°Üß DlHŤh6x‹#A7ô_OAęÆŽďŕZĐĂ´zÄňË ô__Az|;E˝Ŕ†8ÓKćżľ‚Üą˝a™$i,dvqRéVÚna!žůŇC¨]HŤ¨”ťĘů­,cg÷ȡ†hBł Ŕ„˘ŃźT˛#n+N߇ ëB˘ÇhČló–~Ŕ°aďľ»o]'F€9Vć0?G`+HMîm$aóŔŔýžh ¶:ÖR0#ŘoBČŚŔŽŕˉua/D‡'H,á0;ŰÓFů°HhĚ2ž ć!şş«˝‹wPŢÁ7Y$ś žĐú§ôܵua‰dă JŽa (ŻO´ µ5¦'čĘĹ µÁęÝŢNfX.R~W°EĆ ˝¨ĚRă=Oů­ŤÁíz-ŠAč˛çŕá łďĐŕ&Í; ÖçČŹâ 0#ěé ‚\´#*–źá 0—`lăzôNż|Ű=®ëi|»‚4¶}w ĺĐÉÍôh9JÇý1ÜćďkW(ĆpyřÂŔĆAťwŁĆöŮŹ˛ŃÁ-!ŃŢ&(ß)ď0ÇłSŔ¶ąý›ş źˇĹ°'žŢÔĹ#5îđdĘŮD(Ŕ„^6! sůôúde‹WŞ/B!˙™:B¬41ü|şŰL^h\D(Ś6#ŕö>ł€%îű¦XˇG;6TÎĐ*UAő á1áľKlҲ‰áŹG”†ě<fC‡ ©wŘĽBű_ĂÜ7źîÇS`(ÂÝŮoÓL(G®ďÇ; Đ®hřŐцŻÓ÷öřVRÄč“XKżs·Ťř˝yµ+Ú7ěw7±ĂǶ¶ Ru ľĚ9„\6”ł§uűW;e}¨ţY+pisT»Ć_§|×úń´}q‚jŤ@ţAĂ;ŚŢ˝kńËżią–&˘(ęoˇŃ˙§J¬V§­­c‡ŇĚ´iÚ„¤m´µĹÖ€ ‰4MI\hQŠ7řÁ "Š ţŔ…ÁhLÝăon]ąđľéč›[jPgx+yÉ;3çÝsÎ}ópnĽZdř! @ţ˝-ěőy´‡µn‚=Ďţţ <Ż‚×O(4ÜMóäřoŕŐVIUÉ&iN4K-OssČc …®ůÜHŕ€Ží9źgňČČÍ xń䳨'ך)˙ġ0ŚbC€'~;ŻTŞcćŇď xťFSňr1ČŃ(qS%بpk–ađóĄo?ÂYż=áµ3Ęű§:ff*˛wu ôľĚS‰fRm›@9¤ÖÜžˇ¶¶¶ˇKŮ‘ć !E ‰Žá83oĘŔh[:Ý6\aĚËĹ ŘEH´Ň*nm묭­mm˙ŕŔRL•L­c‘X_§;ÉěöÎ †tŢ1´ŇÜęiëlŤ‡BńdË·0ú[}ŁëWš˘†đzĘĆľ4L†ŮÉ–á± ňaˇ‡‡:“ˇ×덝ę(F»řU2gꉎŃEŽĽk•'§B-/DXĹ™A¸§˘ťCŹ‹€đçtkČ+#Á¨wË6‡ZnËJ¦tV!Ţ١–y•ŁńĚd!Ö4P¸1hŔA Ň‹CͬWźß%SBŔî¶„Tpţĺh•›vVä“Đ1’Ç(*ZCű &{Ş}űA ňᤇ–˛¶'jÂßÚC^ŹŰʼn¶@Ő]°Ű”B/¶Ę™Ś "ˤvůzwą ŮDÎĺúş Őä2(׏AXË!˙h-ŹJ.†ĺ9÷Ţjw|ż3Ů4żůĐŘ@ďń}ĂłŚKŞŢnÍS3赩-;‹3ă\“`çX§…wŐ=:ëŘ)±ôtV¶Żßj'ňٲćŐ$ĹŤż¤F!ž< ,`K·dĄů›uv“ú0ËĄ–˛kCU'Żâäi"•b!n¶ŞµŞ˛¶NžšĎďą´1¨ŻIp9Qw[/Í}]cŔ ”#.ŢIx™ĆR¬¤b˛ď!Ćbk‡-`rň6Ě cĘbúČ´¦ŢŢu˘'Mv˘Ó•d„ˇ1Ş˘ŽÖ™ :lÄ®$0e1Ý8Ů޲éSâĐ‹Ş€\ ą’k€JŠ 1Đç%,fă~Ä ÂŢ„ŕR uY?Îö&°¤8şÄŽŘ–…bţ©I1iŹ’U‡”D ĹRm !0‘ lG—`DŚ1Hź:¤´7Y;˛¤†ő·Ş$‘gy±F­Ň†‚;UŠÂľUkw«Ďč—¬{íÜĎr›ęaŕ˝ĐWŃ(Ł7‡°%…N´ĎcăĆUó Č€ú»OČm čě˘b/ŘE·éü:Ä ýe¨§t´¤†¬ô±»8ÎUó^]…ÂöIЉ’Á >ÄĚ˝1Ş«PŹ_€y˘ŘŹsfŢe0˘ăDŤ–´Ů ¨7[Áж’´7ÎH%›źËýí§č>ďě›h+ëŔ5ô%±Ŕ ÝlDvŹŰ Ůx’•ŘÜîćHŠď7’HB>Â…ý ™űˇ5­–[Ż<­¤k¶Čę€Ú µk¨ó@,‘ö!)žĽĄ‘Č)|/cîĚŮKť™‚„8é$·»äŚě/(®Ú:;Źl„ľ"vâX¶ť8ęT&y˘¨<­Ż‡Hł|;ců^yíˇ-ČĘmʧŹ=Póoᮄ gQ˝„-é{´ ÂbŐAO čGRĽľĘąi|ÓNî¬ZŕÜsK˝‡Ť/[bŃHŔłő *˘ăŘťě8ű+ő‰ę`PđßARü0´±ćE«ćĂ ‡U Ěü¦‡F$ÄÝĺčëÇôłÜăŰívâh¶¤M©h´ÚŹBeř9.βnG,[ľŔlqťĽ„d ·;äŤV7îÇ  "0! Âv˘÷çN$c)o*q´hµZŕşcAđ &óÂ…fËI‡Ő‚µ¤p ;~ŇvÜ“;—égéĎ ÜábKž`SÝqŞATl˘jŢ&î•4Ć€1 –t<`Öęď¤q2VÄx-wřQV0PKúTJ˘Fnb(vŘ|‡C+\—F»!…âkń2ßp›8O̧FřČŻ5é•č÷!Äô̧Ëă;Ş/€öxĘ#fBś7ŕ¦ŇAźżrnţr3S#|ÝŐÔŢV/”QĺE€Łń&L“ pб±ůBC_ó +.WÁ+€K\ĘMPşjôĂż/ݬĐOb®<¶Ĺ8 ;’ą)zÓĐÎŚ•bް:V-–P6ÇwÇ [çŘ"3–­q íH:÷Wâ–0·Čüă;"ŽóWĎďó­?m}ßĎ'6Ďßmú<}ß÷wĽß÷>–Ś—m1÷o`ţ­ůvtŠ-\q›±€ď»VC‚ôyţň´¸ťŰ!˙Ą `C֤ǺÁ…q&%ĐDRaHw|¨řr˙¨âk=qL“.€]ɲî»çŚVSwŻ`ţ˙Ôńm)ţ›µxţ{®ŻgÍcI@{ľăW¸‚1ŕ—ó°ŕ©0ĐdáŚ#ŕőŔ“믑˙˙h­,(„‰#9?¸ßĄT@„­€çŹŻÔŻ«´uóÄř˙ďMŔ‹üŻlhÍëŘImUŕŻSä:Sö/¨Dĺćé¸}‘ű:mšt_!–ş˘­‹EßAëÚ‰ÖUĺ ŔZ´+…Ľ‚a1YôÖÔ2Đç¸xłˇ .ťĐÜň€“p,ą«YŤ‡AŁOĺ  ĂłzĹtUÇĹ/…źČ=Ż×aŇ^ô´Í7pňŕ%äÜYĐ-¶ßüËŕS5€mc:öë–r!'Hź„ŔeA×KěÎźç@%Ç«m¦¬*¤V”ü'}Šb+ťňíß.FB`‹*™swČĘ@»żS7ág9Ф*CÂ^ÁW<±…!qÚäŐ ŔóĘ´Ä0¨“Š^9…»ő±„ Řü +hŐ @źˇhLrĺdFŕëćřřđŕţR•Gá â Ę*šB"5ĐPQ¶4>,ě-Řh·ŰŤ"@FŔÁ÷6[ÁusÖŃ:,d•g«ŻÄŰ(˙+Ő™°·úpëĂ9;[€,Ó绝)c/E¤ (ý{˝’µ%őÜÝČTş…±× l¶Š úÂÜĂŚš,‡Ă‘Ő¤ş‘% śű`ąŚ9P|řAbţĘ9˝»¨­¬ŤŚ:ĐŃę”Á÷x ąoʰîöŁn‡Óéláö±-qÔŚw›yúcń¸JÝ8çžćjsPĐAłp‘€”?¦%$ďf‡Äv\ÄnD—6qóÜGĹ‹*-×0Ϥ»WŹ /7[ ęĺHŔô„äc 4i;-ś:·fÜN·˙śQD; Ë-V+l0PąuXBżNZ…ű˙ĂAŮf ¨děhć”đ,Ă~.ŕ& Ä  ü˙Qö^ŚGßc'ÇŞ;čČuž `x±ňŤ,˛éÍÉűţh†¸ú-_ąďV+;ë(!kâ ř>btĆĆÚü!°zÂD"ŔVđ«'*á_Ź9ů! vŁ”Ă;ň”1 3„eöń˙7TÉ-ÚNó÷Ó»d§é;!ůsşuŐb€‚şA ‚Şńü {t˛Z*u–hsě¬˙M@łíECŕír±Rĺü`E¶ÚWä˝dĺ ‹Ukî2¸÷Áö˙MŔăĺ'÷ęBzD¤ĎEÚŤT€©emΓ6iTZvFŞ«09ev¢Sţ[ =ľżjoÂě”äBWjFvÚ¨¤†Áî" Đ­ÓôůEŔ»” ''§$çó_§ĐÍ›Ő! ÉßJµ9]˛©¤$82^ĐzSŃ]LëľIڧ°á,ú¨Ýß±Żn/-µßÔČ4MŤżÓůč€Có’¨h˙ SEÚTaÜ'Y(5>=ČrĽv·đűjhţ.W¸+ Eî\ĘčŁđߤŃŘE[.›ßM‚›öę™îFé€Ç“îiô:PCz-``”ÜÉBPf¸Ó–DËĽ¦ĐzéEPúÁťžŢ¨ŹFĎkźd˛Ě­ˇâ ÉŕË@Tm ˝’Ü–.˝Ő'Ľ)úxú”ł°›[ÇRÍąš^BI‘ľH}.c  Şŕ¶Lbşďx!GS€€+RSČîO˙0énŤÔzş‹˘¨*€vU$ íąŹ”8ĹÇ4»ŻÁ˘EśŃŮEHË OŁßá¶Fr „ Č-î°Ó§ÜL©Ŕ°ľűvŠ„Ä"5űü`J¶źZY¶™0őU~s÷›×‹#DIÉ&‚-Ŕ{Ś—HŃń›đĽ–Äc*—_ĽŘp4JČ‘Âë]˘S Yjóńči%ÍĚp%§LęÝwř˘üü˘˝Ë7®ľ˙dPi«(„ĂuTŇQtץKkîo\ľ·(?Ńđľ˝'Ą$»2hwNđ0‡ť@©ĐGÇýhďZB›˘(µ ń­51żF4&Ť1“? #‘`ˇşhPŰL޶"•*ÔKˇ*tˇH)B-UÜV¨`µZęBÄť"ř»şt%¸ôĽyŻ>1Ź)3! ·Pş<çΛÎ{ďžsŻ/€µ'źi/ ą5Ó /ŢĂWL“˙ÍçGś:÷˘ď kĹPţ4?Ä}ňµť¶m›ţŢĽ‘÷č·“ł€ßĺ’čX€¦ CäfEĄÍ Ŕ8OńćűúđĂĄň 6•|2€*Żěrůc8ëˇ*ŰiĆ€ŽŘ°zlÄ|ĂłdJ÷ęý/Ź !ŕÇnčŔ™{N˝ˇO·ŽDóź ä `J‡z4ćř«Ép˘„ĆÂî%đaZ— t°Ő ô@&1Áć´@ä˘ÎXŮŤőńŁžy}px»üňes“łI•r†Ž#’•jM܆žę…C>¸\‹xŘÖÓË•HŞŤćôivlp8úëÄ•®BP- lP‘Ń’đZ…ëýĐ‹đPď·ě6ż/?µŞłý*+őÇ i_Äc§7Z@Ž(_p»ËŐvŕ€CŽ×ć:ţ¸v‡úć퓹LĘŻZ©VN$\Ô>Ć‚©éŘ I>ÁzmŔĎ”!‹ŚĂŕWŇűS`Ce`Ýé试^H™Ý7Jx˙čňŕ7ÓBśG|™7ˇZ(¶č ăC,4ŕW?BÄvíćíń}µĐĚ äšo›5¸WÍ/˛|ó†ůŞĹÖŢą\;—±79EDr“uŁ˝3ä–6ËĐA4Şî8ěčżq"d–r—ă׼Ŕ†ŮŃń"“E—LÔN+'ç€ß±Ó(#‡V°ž28˝`šz]ůĐMń«.đkĎ:`p,ó-dŽŔý¬\hČ?'6č˙ˇĎ ,F_ar”Ž«… Ö?ÇŹ0ĚĹ tÜ˙řM˘ÁX#Üex&wEa ­¸DÚđ™v¤řmó§Ž]ł„,`ő»·W墤Ťńhvý?-T€-.„Ú,FüV÷‡É®BÓ›ţ ¸‡V_Kóăb3Ŕö–fĺÜĂr=]aÓÔđ]żÁŽJś/9:4v,"‚ě+Ó•îŢ^TS©¦f-đ‘˙jS UAW<9: m °JM€ßűz WhÇńťjjsA Żą…ʡäxçŕÄŘřľ ôş ůkÜî“_r˘é8V?ŇżQ 2lŔ)»ý’ ěz˛°'qôhzĄĺŃçŁ>Y=V*i8­ 1«ÎV…Áf‹cS ‰Ăx,,őřč*Í=•™ëŮ(ô|Řă…mÉŘô‹]„‚׎Čńdçŕ~p8J´–żv€oU”–áĘd9{!šI¦Tř»µŁiµřM\G„‚U‚˘"ě<]ś˙qiO(jŰŰÚľj¬iv»wKĎěĂą#Y$?đáćM…Źô›_¬J# ÉJ.łSIp(í/>y·péÔľP(‘V;{8igEq»[§{^TžÎ•»JĄhĐ˰HY±x|VżxĘ3» V9ŕ9¤/_îËž-^ś{˙|aŠ4W9őuş÷dĎđŁŮ»_žN”»˛ĄR{G>HŃ /JżYŻm‘-q9Rdš°čëËvť-Z\\,»Ź˘ż4€#::ňitF—H‚ŢN'K×>|d7ŐGá9H°›ÉŽT<Dm$e%2ZĄv€— ţŮÁn›|Óţů}Ó|ęř&"“ň€-Ž :’JĹ©”N4ĐcŔÎÜŤLE׾Ů鏭g¶Sb~Ü a“`ÄC7ŰX żĂaI˛şŐűŰ_şŽËMNżăŔHjuÄ& /«Qě+ŕzÓł/^Hś%±RAleAţf5 ž ŻeňĹŔ‘ Ęř°×«A Ů­É˝±¶Ésŕ$xy„/Qě}$˙ścÂCSٍźÜW«+ -ôzË}š¨đUyÔë˛2a?uźň˙ń/Ç/éyŰN–R~”IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/apple-icon.png000066400000000000000000000200741433544061600232710ustar00rootroot00000000000000‰PNG  IHDRŔŔeś5PLTE˙˙˙¬r+W!ýýýúúúóóóéĚ©÷÷÷őőőđđđíííéééëëë¬s,ććć<(„X"ďďď‚V čččŕßßÝÜÜáááăăăŇŃчZ"ÔÔÔR„Y$ŢŢŢX"Y;äääŮŮŮâââ­r+ŰŰܨp*ćĺĺĐĐĐÖÖÖ‚TÍĚĚ­t.ĘÉɬq)«n$ÓÓÓŰÚÚŘ××ĂĂÂŘŘŮÎÎÍ«m"ËËËę̦ĎĎĎČČČP€Q«o&Şl ĆĆĹľľżěͨŔĂĆ>)żÂÄČÇÇé˨°t,¬p&V9Z;ĹÄø··¬o$áâä~OŇŐŮĹĹÄÁÁÁŃÓÖ»»»¶µµËÍĎČÉË˝ŔĂ´ł±ş˛¨ÜßâÔŐ×ÎŃŐÂĹÇąąąŤh;­y8‹e7­v3_-ÂĆĘŔŔżíΨS7ŰÝßËÎŃ˝ĽĽîŃŻ°®¬Šb16$ ŮÜßŐ×ÚĹČÍÂĂÄżľ˝·¬ťž‡k­}DŽk@€T^>:&×ÚÜÇËÎĹÇĘĆĆÇş˝ÁŰÉ´éĚ«´™wâäçŕăçßŰŐ×ŐÓşĽľŕͶ¹­áɬ®|?ÎĐŇ·ş˝ŕĎ»ĆŔ¸łµ¸ľş´Čľ˛°°°Ş­°§Ş­¬Łś‚a°I±B†\)uMbAÓĎʰ˛µŇÂŻ±ˇŤ¦ŽphEÖűëĎ­Íľ­««Ş®§žŁ‘|ş—n´Ž_•yW‘qIÝŕäµ·şŔµ¨ŻŞ¤¶¦‘¦™Š¶ˇ•}`°‰Z°‡S’tQ°{:}SŃĚÇĆÂľşµŻ˝¦‰®š‚ş Ľťwh¨N±„M˛v,ˇk(ÔÓĐĚĘÇĺŇĽĄ§©Á˛ˇŰş‘°“o´’hµRŽnFŮÓËŇÉľˇš’©ž‘ “„ť‰r’oCŞp*‹\#xPoIËÇÂäÔŔÎÄ·«Ż´ńÔ±˝­™Ő±…Ş•{ťŚwµ•n¨‹gś[™e&ĺáŰč×ÁČą©Ą˘ź˝Ş’«†YŔŹTK2ĺâŢćǡăĹ ÇłśĂ­‘°Žc—vO”b%C-ęʢɧ}Éśf«y=ĺÜĐáŃżŁ{KMNt=÷IDATxÚěVKhQ5QŃ6“ĎĚDš“!¤‰qŇĆZѸq!(‰ ( ő U(QüB‹  ˘Pě¶Bˇ]*AŠ˙UÁŞ7nD´ qguí}“Iî<«yIgşz $ŮÜsĎ9÷Ţ™ËXĆ2–ń?b% 3Ë,=*\:ÚĘłĘ,5*Läo†{gT1ȻЂĆŢ(yÝ*K1żJ»FŤť’ŔnAFłWĄ]»víę*ŕ7j`rŘ=VÁ2Ő…0H¬Ń¶´¸\­*\--Ŕ®‘3ąő&¬&eŞUŞep —Â~ŤX9Îćqxl6D9H@n– Ş ­­ś­RĆe I`Ż h’,Ę٬(ňűćŮ­öB_8ŕ%Üu@,c“äbqčĘóŃ{˝ýý‡/üúú`8šďx‰]¦yŕř¸8´?1?ďÎt§ÓJ:ťČdŇă/GN:A ©˙aCĹąXžęŤĺr©Ŕ—ĘĄüž,„;yÉĂ™§™ˇâ›C,˝{q(“°Äăv»Ýř‘ÎXîŽ „:ů¸‡ ţîŐţ‘9w.ôűťÎ`Đé„o·/wtęc!$Č`DM‰ţ«ÄŮüŹéŚ·[ô°Ç-ĺÎú$ďe(Pűç% `аŠńţW·ÚĽŮ÷ÓÝ”ýěJâŮž!ČÍŁc„2j˙Ą>7Ő?żŻ˙ú)’Ë<Ä9Ě˙ÄŐÝ? »%ńť(@óţfřź˛Âđ×QpřÉÁ$©Í˘ń4ç’łč˙?2°Üß DlHŤh6x‹#A7ô_OAęÆŽďŕZĐĂ´zÄňË ô__Az|;E˝Ŕ†8ÓKćżľ‚Üą˝a™$i,dvqRéVÚna!žůŇC¨]HŤ¨”ťĘů­,cg÷ȡ†hBł Ŕ„˘ŃźT˛#n+N߇ ëB˘ÇhČló–~Ŕ°aďľ»o]'F€9Vć0?G`+HMîm$aóŔŔýžh ¶:ÖR0#ŘoBČŚŔŽŕˉua/D‡'H,á0;ŰÓFů°HhĚ2ž ć!şş«˝‹wPŢÁ7Y$ś žĐú§ôܵua‰dă JŽa (ŻO´ µ5¦'čĘĹ µÁęÝŢNfX.R~W°EĆ ˝¨ĚRă=Oů­ŤÁíz-ŠAč˛çŕá łďĐŕ&Í; ÖçČŹâ 0#ěé ‚\´#*–źá 0—`lăzôNż|Ű=®ëi|»‚4¶}w ĺĐÉÍôh9JÇý1ÜćďkW(ĆpyřÂŔĆAťwŁĆöŮŹ˛ŃÁ-!ŃŢ&(ß)ď0ÇłSŔ¶ąý›ş źˇĹ°'žŢÔĹ#5îđdĘŮD(Ŕ„^6! sůôúde‹WŞ/B!˙™:B¬41ü|şŰL^h\D(Ś6#ŕö>ł€%îű¦XˇG;6TÎĐ*UAő á1áľKlҲ‰áŹG”†ě<fC‡ ©wŘĽBű_ĂÜ7źîÇS`(ÂÝŮoÓL(G®ďÇ; Đ®hřŐцŻÓ÷öřVRÄč“XKżs·Ťř˝yµ+Ú7ěw7±ĂǶ¶ Ru ľĚ9„\6”ł§uűW;e}¨ţY+pisT»Ć_§|×úń´}q‚jŤ@ţAĂ;ŚŢ˝kńËżią–&˘(ęoˇŃ˙§J¬V§­­c‡ŇĚ´iÚ„¤m´µĹÖ€ ‰4MI\hQŠ7řÁ "Š ţŔ…ÁhLÝăon]ąđľéč›[jPgx+yÉ;3çÝsÎ}ópnĽZdř! @ţ˝-ěőy´‡µn‚=Ďţţ <Ż‚×O(4ÜMóäřoŕŐVIUÉ&iN4K-OssČc …®ůÜHŕ€Ží9źgňČČÍ xń䳨'ך)˙ġ0ŚbC€'~;ŻTŞcćŇď xťFSňr1ČŃ(qS%بpk–ađóĄo?ÂYż=áµ3Ęű§:ff*˛wu ôľĚS‰fRm›@9¤ÖÜžˇ¶¶¶ˇKŮ‘ć !E ‰Žá83oĘŔh[:Ý6\aĚËĹ ŘEH´Ň*nm묭­mm˙ŕŔRL•L­c‘X_§;ÉěöÎ †tŢ1´ŇÜęiëlŤ‡BńdË·0ú[}ŁëWš˘†đzĘĆľ4L†ŮÉ–á± ňaˇ‡‡:“ˇ×덝ę(F»řU2gꉎŃEŽĽk•'§B-/DXĹ™A¸§˘ťCŹ‹€đçtkČ+#Á¨wË6‡ZnËJ¦tV!Ţ١–y•ŁńĚd!Ö4P¸1hŔA Ň‹CͬWźß%SBŔî¶„Tpţĺh•›vVä“Đ1’Ç(*ZCű &{Ş}űA ňᤇ–˛¶'jÂßÚC^ŹŰʼn¶@Ő]°Ű”B/¶Ę™Ś "ˤvůzwą ŮDÎĺúş Őä2(׏AXË!˙h-ŹJ.†ĺ9÷Ţjw|ż3Ů4żůĐŘ@ďń}ĂłŚKŞŢnÍS3赩-;‹3ă\“`çX§…wŐ=:ëŘ)±ôtV¶Żßj'ňٲćŐ$ĹŤż¤F!ž< ,`K·dĄů›uv“ú0ËĄ–˛kCU'Żâäi"•b!n¶ŞµŞ˛¶NžšĎďą´1¨ŻIp9Qw[/Í}]cŔ ”#.ŢIx™ĆR¬¤b˛ď!Ćbk‡-`rň6Ě cĘbúČ´¦ŢŢu˘'Mv˘Ó•d„ˇ1Ş˘ŽÖ™ :lÄ®$0e1Ý8Ů޲éSâĐ‹Ş€\ ą’k€JŠ 1Đç%,fă~Ä ÂŢ„ŕR uY?Îö&°¤8şÄŽŘ–…bţ©I1iŹ’U‡”D ĹRm !0‘ lG—`DŚ1Hź:¤´7Y;˛¤†ő·Ş$‘gy±F­Ň†‚;UŠÂľUkw«Ďč—¬{íÜĎr›ęaŕ˝ĐWŃ(Ł7‡°%…N´ĎcăĆUó Č€ú»OČm čě˘b/ŘE·éü:Ä ýe¨§t´¤†¬ô±»8ÎUó^]…ÂöIЉ’Á >ÄĚ˝1Ş«PŹ_€y˘ŘŹsfŢe0˘ăDŤ–´Ů ¨7[Áж’´7ÎH%›źËýí§č>ďě›h+ëŔ5ô%±Ŕ ÝlDvŹŰ Ůx’•ŘÜîćHŠď7’HB>Â…ý ™űˇ5­–[Ż<­¤k¶Čę€Ú µk¨ó@,‘ö!)žĽĄ‘Č)|/cîĚŮKť™‚„8é$·»äŚě/(®Ú:;Źl„ľ"vâX¶ť8ęT&y˘¨<­Ż‡Hł|;ců^yíˇ-ČĘmʧŹ=Póoᮄ gQ˝„-é{´ ÂbŐAO čGRĽľĘąi|ÓNî¬ZŕÜsK˝‡Ť/[bŃHŔłő *˘ăŘťě8ű+ő‰ę`PđßARü0´±ćE«ćĂ ‡U Ěü¦‡F$ÄÝĺčëÇôłÜăŰívâh¶¤M©h´ÚŹBeř9.βnG,[ľŔlqťĽ„d ·;äŤV7îÇ  "0! Âv˘÷çN$c)o*q´hµZŕşcAđ &óÂ…fËI‡Ő‚µ¤p ;~ŇvÜ“;—égéĎ ÜábKž`SÝqŞATl˘jŢ&î•4Ć€1 –t<`Öęď¤q2VÄx-wřQV0PKúTJ˘Fnb(vŘ|‡C+\—F»!…âkń2ßp›8O̧FřČŻ5é•č÷!Äô̧Ëă;Ş/€öxĘ#fBś7ŕ¦ŇAźżrnţr3S#|ÝŐÔŢV/”QĺE€Łń&L“ pб±ůBC_ó +.WÁ+€K\ĘMPşjôĂż/ݬĐOb®<¶Ĺ8 ;’ą)zÓĐÎŚ•bް:V-–P6ÇwÇ [çŘ"3–­q íH:÷Wâ–0·Čüă;"ŽóWĎďó­?m}ßĎ'6Ďßmú<}ß÷wĽß÷>–Ś—m1÷o`ţ­ůvtŠ-\q›±€ď»VC‚ôyţň´¸ťŰ!˙Ą `C֤ǺÁ…q&%ĐDRaHw|¨řr˙¨âk=qL“.€]ɲî»çŚVSwŻ`ţ˙Ôńm)ţ›µxţ{®ŻgÍcI@{ľăW¸‚1ŕ—ó°ŕ©0ĐdáŚ#ŕőŔ“믑˙˙h­,(„‰#9?¸ßĄT@„­€çŹŻÔŻ«´uóÄř˙ďMŔ‹üŻlhÍëŘImUŕŻSä:Sö/¨Dĺćé¸}‘ű:mšt_!–ş˘­‹EßAëÚ‰ÖUĺ ŔZ´+…Ľ‚a1YôÖÔ2Đç¸xłˇ .ťĐÜň€“p,ą«YŤ‡AŁOĺ  ĂłzĹtUÇĹ/…źČ=Ż×aŇ^ô´Í7pňŕ%äÜYĐ-¶ßüËŕS5€mc:öë–r!'Hź„ŔeA×KěÎźç@%Ç«m¦¬*¤V”ü'}Šb+ťňíß.FB`‹*™swČĘ@»żS7ág9Ф*CÂ^ÁW<±…!qÚäŐ ŔóĘ´Ä0¨“Š^9…»ő±„ Řü +hŐ @źˇhLrĺdFŕëćřřđŕţR•Gá â Ę*šB"5ĐPQ¶4>,ě-Řh·ŰŤ"@FŔÁ÷6[ÁusÖŃ:,d•g«ŻÄŰ(˙+Ő™°·úpëĂ9;[€,Ó绝)c/E¤ (ý{˝’µ%őÜÝČTş…±× l¶Š úÂÜĂŚš,‡Ă‘Ő¤ş‘% śű`ąŚ9P|řAbţĘ9˝»¨­¬ŤŚ:ĐŃę”Á÷x ąoʰîöŁn‡Óéláö±-qÔŚw›yúcń¸JÝ8çžćjsPĐAłp‘€”?¦%$ďf‡Äv\ÄnD—6qóÜGĹ‹*-×0Ϥ»WŹ /7[ ęĺHŔô„äc 4i;-ś:·fÜN·˙śQD; Ë-V+l0PąuXBżNZ…ű˙ĂAŮf ¨děhć”đ,Ă~.ŕ& Ä  ü˙Qö^ŚGßc'ÇŞ;čČuž `x±ňŤ,˛éÍÉűţh†¸ú-_ąďV+;ë(!kâ ř>btĆĆÚü!°zÂD"ŔVđ«'*á_Ź9ů! vŁ”Ă;ň”1 3„eöń˙7TÉ-ÚNó÷Ó»d§é;!ůsşuŐb€‚şA ‚Şńü {t˛Z*u–hsě¬˙M@łíECŕír±Rĺü`E¶ÚWä˝dĺ ‹Ukî2¸÷Áö˙MŔăĺ'÷ęBzD¤ĎEÚŤT€©emΓ6iTZvFŞ«09ev¢Sţ[ =ľżjoÂě”äBWjFvÚ¨¤†Áî" Đ­ÓôůEŔ»” ''§$çó_§ĐÍ›Ő! ÉßJµ9]˛©¤$82^ĐzSŃ]LëľIڧ°á,ú¨Ýß±Żn/-µßÔČ4MŤżÓůč€Có’¨h˙ SEÚTaÜ'Y(5>=ČrĽv·đűjhţ.W¸+ Eî\ĘčŁđߤŃŘE[.›ßM‚›öę™îFé€Ç“îiô:PCz-``”ÜÉBPf¸Ó–DËĽ¦ĐzéEPúÁťžŢ¨ŹFĎkźd˛Ě­ˇâ ÉŕË@Tm ˝’Ü–.˝Ő'Ľ)úxú”ł°›[ÇRÍąš^BI‘ľH}.c  Şŕ¶Lbşďx!GS€€+RSČîO˙0énŤÔzş‹˘¨*€vU$ íąŹ”8ĹÇ4»ŻÁ˘EśŃŮEHË OŁßá¶Fr „ Č-î°Ó§ÜL©Ŕ°ľűvŠ„Ä"5űü`J¶źZY¶™0őU~s÷›×‹#DIÉ&‚-Ŕ{Ś—HŃń›đĽ–Äc*—_ĽŘp4JČ‘Âë]˘S Yjóńči%ÍĚp%§LęÝwř˘üü˘˝Ë7®ľ˙dPi«(„ĂuTŇQtץKkîo\ľ·(?Ńđľ˝'Ą$»2hwNđ0‡ť@©ĐGÇýhďZB›˘(µ ń­51żF4&Ť1“? #‘`ˇşhPŰL޶"•*ÔKˇ*tˇH)B-UÜV¨`µZęBÄť"ř»şt%¸ôĽyŻ>1Ź)3! ·Pş<çΛÎ{ďžsŻ/€µ'źi/ ą5Ó /ŢĂWL“˙ÍçGś:÷˘ď kĹPţ4?Ä}ňµť¶m›ţŢĽ‘÷č·“ł€ßĺ’čX€¦ CäfEĄÍ Ŕ8OńćűúđĂĄň 6•|2€*Żěrůc8ëˇ*ŰiĆ€ŽŘ°zlÄ|ĂłdJ÷ęý/Ź !ŕÇnčŔ™{N˝ˇO·ŽDóź ä `J‡z4ćř«Ép˘„ĆÂî%đaZ— t°Ő ô@&1Áć´@ä˘ÎXŮŤőńŁžy}px»üňes“łI•r†Ž#’•jM܆žę…C>¸\‹xŘÖÓË•HŞŤćôivlp8úëÄ•®BP- lP‘Ń’đZ…ëýĐ‹đPď·ě6ż/?µŞłý*+őÇ i_Äc§7Z@Ž(_p»ËŐvŕ€CŽ×ć:ţ¸v‡úć퓹LĘŻZ©VN$\Ô>Ć‚©éŘ I>ÁzmŔĎ”!‹ŚĂŕWŇűS`Ce`Ýé试^H™Ý7Jx˙čňŕ7ÓBśG|™7ˇZ(¶č ăC,4ŕW?BÄvíćíń}µĐĚ äšo›5¸WÍ/˛|ó†ůŞĹÖŢą\;—±79EDr“uŁ˝3ä–6ËĐA4Şî8ěčżq"d–r—ă׼Ŕ†ŮŃń"“E—LÔN+'ç€ß±Ó(#‡V°ž28˝`šz]ůĐMń«.đkĎ:`p,ó-dŽŔý¬\hČ?'6č˙ˇĎ ,F_ar”Ž«… Ö?ÇŹ0ĚĹ tÜ˙řM˘ÁX#Üex&wEa ­¸DÚđ™v¤řmó§Ž]ł„,`ő»·W墤Ťńhvý?-T€-.„Ú,FüV÷‡É®BÓ›ţ ¸‡V_Kóăb3Ŕö–fĺÜĂr=]aÓÔđ]żÁŽJś/9:4v,"‚ě+Ó•îŢ^TS©¦f-đ‘˙jS UAW<9: m °JM€ßűz WhÇńťjjsA Żą…ʡäxçŕÄŘřľ ôş ůkÜî“_r˘é8V?ŇżQ 2lŔ)»ý’ ěz˛°'qôhzĄĺŃçŁ>Y=V*i8­ 1«ÎV…Áf‹cS ‰Ăx,,őřč*Í=•™ëŮ(ô|Řă…mÉŘô‹]„‚׎Čńdçŕ~p8J´–żv€oU”–áĘd9{!šI¦Tř»µŁiµřM\G„‚U‚˘"ě<]ś˙qiO(jŰŰÚľj¬iv»wKĎěĂą#Y$?đáćM…Źô›_¬J# ÉJ.łSIp(í/>y·péÔľP(‘V;{8igEq»[§{^TžÎ•»JĄhĐ˰HY±x|VżxĘ3» V9ŕ9¤/_îËž-^ś{˙|aŠ4W9őuş÷dĎđŁŮ»_žN”»˛ĄR{G>HŃ /JżYŻm‘-q9Rdš°čëËvť-Z\\,»Ź˘ż4€#::ňitF—H‚ŢN'K×>|d7ŐGá9H°›ÉŽT<Dm$e%2ZĄv€— ţŮÁn›|Óţů}Ó|ęř&"“ň€-Ž :’JĹ©”N4ĐcŔÎÜŤLE׾Ů鏭g¶Sb~Ü a“`ÄC7ŰX żĂaI˛şŐűŰ_şŽËMNżăŔHjuÄ& /«Qě+ŕzÓł/^Hś%±RAleAţf5 ž ŻeňĹŔ‘ Ęř°×«A Ů­É˝±¶Ésŕ$xy„/Qě}$˙ścÂCSٍźÜW«+ -ôzË}š¨đUyÔë˛2a?uźň˙ń/Ç/éyŰN–R~”IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/browserconfig.xml000066400000000000000000000004311433544061600241220ustar00rootroot00000000000000 #ffffffaio-pika-8.2.5/docs/source/_static/favicon/favicon-16x16.png000066400000000000000000000010211433544061600234410ustar00rootroot00000000000000‰PNG  IHDR(-SPLTE˙˙˙ţţţüüü¬r*V ôóň«p(‚U­v3¦n(óńîŹj=řůůďçŢÚ̻ɭ‰®~¦Śm»’`°}?Śe5Z!vNůůúöőňęčćěčăçäŕĺŕŮäÜŇ͵ƧávšzS°G†]*…[)Şo&—c&T €SőőőööôđďíďîíńíçăŕÜěâÖčÝŃŢ×ĎßŐÉŕÔĹŐËżĎĆşŐǵßɰŮŬȻ¬ŮŔˇŇ»žÁ­–ł¤’ş¨‘Ĺ®łśÁˇz±™zą–k´’hĄ‰f˛Žcś‚b˘„_™~]ą‰Nł‚GštG±|;©s2_,‹`+Łl)Ť^#…X!iFY;Ąôg<ĘIDATÓ5ŽĺŇÂ0DbMK UÜősÁÝÝíý_…BĂůqgvgÎÎ|>qj •€ 1Ć˝J †L–ĺ˛ţö,ÂĄf<¦0–ůđŇÜü˙łVą ßs*f7«U•žUٞ1ÍúĘ"qp¤ý1źFXŐ6q3%©ĘnńŐXS^ä¶SbŰf@ęâĹÓĹ)áCK†ţ´wŃë clu~Ý÷ß##ŠĐŮÁé1K4-Qnłśţ DKMfÁä'/`żďó^§Č‹IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/favicon-32x32.png000066400000000000000000000021541433544061600234450ustar00rootroot00000000000000‰PNG  IHDR D¤ŠĆŕPLTE˙˙˙W!¬r+ýýţ‚Uúúű¬q)üüüV őőö§o)«p'Z"ňóó¬s,÷÷÷řřůőö÷óôőđńńÜŘÓ­v2„Y#úűüďďďíîďččéĺĺäĆą©†])ôóóŕßßÚŐĎéͬտĄł‰U®z:_.čäßÎËÇÖĚÂÁ·¬Ç˛ą§’®›„»ž{ť_•uN˛E‘oDŻ}?Śh;®x6Śe5‰d5¬s.Şr,tMS_?÷řřęęëîëçÝÝÜâÝ××××ćŢÖÔŇĎŮÓĘĚÇÂčÔ˝ŐÉĽĚÄşĆŔąŔ»µŢʲβçαż±č˧õŁŃĽˇÁ±ťĂ¬‘´ŁŤ©śŚĽŁ…Ş”yżťuĽ–fą“e±Ť`¶ŤZ”wS®‚K·†IŹj>©p*‰](™e&”a%yQ‚TnIlGcAćććčćăíçáăáŢěăŘäŰĐÓŃĎâŮÎÝÔÉçŘĹŰĐÄŕÓÂÜĐÂĆĂżâĐ»ĐĆ»ß͸ÎõŰ絯§·ŻĄË¸˘Č¶ ł©ś¸©—˛˘ŹĆ­ŽÚ·ŚŔ©‹¸¤‹Đ°‰Ć©†Ä¨†Ć§±›Ƣw¤w¤Źt©q˘‹n˘…bš|W’sN±„M«}F”qFśsB¬z=…[&‘`$V9S79&7%ŇGŃfGIDAT8ËŤ“UsŰ@…ł(©ŞĚvĚ1†c¦ŘiĂĚĚŘ6I™™™™ůŻVŇşcŮ“‡ś§ťąßś˝çŢÝŞý ˇęjĎ;—Őő]É f˘žĄž© ‹Ä•Ő…Ŕ:„‰GN™@ö§iÄ'ĚZŹę+ Bj˝+ˇ*ĂÔ Nč5€ńĄe‰K.9>lY¸Ů®±T€®Y:ý«Ŕü! <§rŇP˝óĄyžW€ô§ üňťZGŚÜuźIŕi0iäZCwš˛ş{&â¶Ůfogđj§ASz!ZÝ!:m64áđPC7Ë-hDęCt®.&>qݶ“WçqxĚT ç’ ŠsNű»||(šŹ´“ňY ŤĐÚ´,.»Ýó«łđe(‰řbt-łą-źŮŔÉ^ˇb›KRáđ<ţT§iµyŤm‹Y Čń zŻžAäLßyß"…ZŕH÷Ůľ.A%„‹ăţ×9ĄşYĽBEZüă—ŮľČÉK·ÖeÝ‘ťŁ:h=~L±řčŻaČxÂm… °«€ç~ŤÄ±×vŞŁQÇ€ 0ësĆRëE pxÔ˙Ř ™l…_»€ië™´–¨Ŕµá@Ś…ČţţůÍł“Ă7 wÖt¶SţÜ)´2ţÍąv——5ÉŁeETÝć˙9Dë$¡ҳŻRÍ x´—ŻŰÔł˘”‡ß÷:Hĺ×rŤ´|N)1=ń· lĺ"&§ĺfăô̱n—™ =˙/1Jfł$JÍíˇŠâ?‚îWÜ{Ő§IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/favicon-96x96.png000066400000000000000000000071411433544061600234720ustar00rootroot00000000000000‰PNG  IHDR``ŐF‡ PLTE˙˙˙W!¬r+ţţţüüüúúúéĚ©ôôôöööńńń¬q(íííůůůřřřďďďęęë‚V‚Uěěě§o*Z"¬q*ççç<(ááâX:óóóÜÜÝÔÔŐĺććäĺćäääŮŮÚééęččéăăăÝŢŢŘŘŘÍÍέs+„Y$ŕŕŕßßßŰŰŰĘËËÖÖ×ĐĐѬt.ÉĘĘ××ŘĚĚĚ­v3„W!ČČȇ^+âââŐŐŐŃŃŃŇŇÓĎĎĐÂÁÁ˝˝ľ¸´ŻŠc4;'ÓÓÓŔŔÁ†\(ĹĆĆč̬mB®|?~ToJŕŕáÜÚ×ĚĹĽ¶¦“®x7U ŘÖÔÄÄĹÎÉĂÄŔ»ľĽ¸ÉŔ·łł´čήćĚ­ëΫ©ś‹·ˇ‡łŹc{X°„MoF‰a0Şp*fCăÝÖ×ŇĚĂĂĂĽąµ˝·Ż±§›µšz”vQ­~EŤj?Ż{;‹g;{R`@ßÝŰćŕŮŰÖĐăŮÎąąşĚ·Áş±Ěľ­·±ŞĂ°šşŞ–Ą•¦“}·—q›‡o™d~^±‡R®J”rI­u1¤l(lG@*ÚŇÉČĆÄĹÄÂËĆÁŃČ˝ČĂ˝ŔľĽŕηŮČłŔµ§ćÉĄ˝±ŁÁŞŽ¸ĄŽ±ˇŤąťzź‰oµ”kź†f·`˛Ś^µŤ\°‰Y™yQ«x:ži']=éäŢŃÍÉÓÎČËĘÇÖĚÂĺԿƾ´¶µ´Ň°ǽ°ĂşŻ¬­Żăʫø©±­¨¶®¦É·ˇ¸­ ľŻśÉ´›« ”şĄŠ¨„Ż›‚ź’˝ˇ~ˇŽxŞ“wşśvżśr¸•i˛‘iź_¸ŚX•wSł†N˛‚GłD±~@Śf7Ťd1Ś\#vNJ1Ý×ÎÓŃÎŢÔÇ×ĐÇŰŃÄč×ÁÓĘżăŃşÜĚą±°°«§˘ŰŔžÝĽ•Łš‘Ř´‰Ą‰ÓŻ„Á¦„·ž€§’w­”v¦Źq¨Śiىi»”eĆ™a”|_ś€\¶ŠSżŽQ“sN§~M™e&P5F.âăăÎËĆĚş¤µŞťź–ŤË¬†É§~̢nˇzJ–l:’a%Ž^$tM«Ql IDAThŢíZgxGŤeË’Nĺ$%:„zÇŞ–dË˝Ű c;ˇ ¸`SCˇ&´ĐI¨Ič%B ô@€B'˝÷Ţ{ď{·w>K:±†üLćĂź>äŰ÷nŢÎĚÎîúş˙í_X|›]Ë#hô„„>iŕ3r<ú4>) Ŕ„R žű)|D(áCŠŽż>_*ÉÄJ%®Ë4`<ýPC>˘ŹRö-FÁË”–ôßޣ»Á" %ôp_"‰qcŹá۶ő¶ ś˘ď(>KŚßꋳçÎ"K®˝§=.Źqg€Ź ‘MöL·z´0Ů‘“0işńŁŁ'ZéYápD ş3•hôĚcł†0řCń©á Š‘ˇMŁąÓ÷ÔÖ6Ă)ÁÖz´J@@Ćه (ú›ŻÖÖ+f*Ę0Ŕb‚ŇČĚh„µľ6±('÷4Fý羥ĐĐóLţYmnNQ^©śQČM+„ÖŽ#ě¶WsüŽĐ:ÓQ™Ö.ĎË 9üŐĺ[ţT¨šh¤{Łbă#%™ľÉŤĘ4>_cyv°Ć}_FfSľVş/«2Ž\*´ëö¦PlÝ&{AÁć<«Ma7űuvÝŞ$:ËÂbĄQŐYZ÷E…¦@ TŞ^+t‘”/ék†ČaŘ˝¦ĚĎá|ŚÎËdBĆ‘Ţ;oÝ”,>±©Ě‡‰ 1^{ßóp fő l–!5˛íOˇ5ňč2¶ĎÁÚRY( ňÁ$ spµ#C·Š^.ŮJŠÖH–ľői¨ŃúEŮYžű1HF†‰¤-‘-Ë…ÁAPˇŇƬ°:„ŞG„ý8Lć^w{˛=kˇ^ ••ˇ@I'˛bv‘Ç×D'ňš;ˇa›"tëâÚÝ 2Ś(ńřOBofřµf‘@dÖÂD–Ď,ň§Cü´f‡ ¤9ź­ŮĎB]°şę’Đ{ô˘ŮÇkË”Vď::ĎŠB9°}Q|µLg„ uP#ŤĹM—‹”i9 +!Áú\‡ŰŞ·şiÝSoÍmhNŁË„źĘr­¨ç`ą8=1·'ťĘöńLv_ťČoOĚ]'§—şĺ` ].2črŃşďÁćX»?±ző‰÷§?–#óÍÚ}#éénŕ(čr1®8G÷îŐ¶ôĎ»ˇ’YŽÓ~Ţs,v™@—‹îUuĐ…Ż?je»€ëo`łŃź †-ăČ<ÇjŹlQu«1şˇŔx‘ůś™CčmŽÁ»˘M.ÖŇ÷SN€ŢŠ­şeuŠ*´Ż4Ćč`Ű©C<é€ŮV±vnÇ<(žť— rî“iîxę>Á>°ű”9*šÚŕ…N\#ĄFCHu4@ť:€C €ż˙Ćë0<ż§NŤť8€ă8W ·î"±ś:hî<Ô âŁ ĂU€A$hȱ<—myqSm0ă8á:k)’€eH[Ąs¸Ň¬OöâÍfY»m{ŰÖ—x ±ëť {»mZ÷”QA­Ň,“Íć~ć安/Ű,dPEÔO|X×ÄD@±#č¨ËYÇ ŕ˘Öä>'˛Đ 7ŐaŐ 8@Y×ĺ5kU<§ÍÚąĽ+ĸłÁ$›ą7ßHŰ[)Ľk"HűĄŤĐő˝LéÁ–n‚ż˝ëËHş€©•_ßnz%ÚPź]Ă_!)úE9PńR٦H‚1ó‡ţ]éÂ;¤ /ŻĘráa”@F‘6łjůɉźF:Úąs硛ş„Ś ľ,ŹÜă¬đ»ú–dj öčuAF¸tţĄ»^?« ×gŢĐÎŔRĂRËÉďĆ–EŇŻOi,ňg¸XúÂHsş=XÔs.Ź‹ I¬říÚ\‡=—ÁCÄčL\íőżµ-#±ľ 'ł|9?*fNôŇéČ(¤ŕ¬ŃŕvĚŔ˘Błr~ůź‘“|ičŘůaŔt6zqÄ)*Ä—5"±9ÝäqD=GVrť·äMV3``5bk©’°şěŮ ÷±ČD‹ň ´Nv—•PŠ0eŹož0áąv,™t˙şt©żš›@±aď¤%;^xnÂBĎŮJ±…©źÖÇ„çmşX6ż|Ll‚Ęő4ČŤĂĚ0•ŮV‘)Eý~SĹÂßT~yA§NťľŰ<&–ňÇűŃĹbĽ•Ü´·ďׇ1ď$ÇŔź·ą  °‚NßĆr"éW†`8(܉oŚRq”ujłˇÜ.Č/Ľ‘ČIŔJ‹)7Ay_°™› ň÷»‚ńéa%Eú _ZR1©fçä1$şað <Ć$¬izhgͤŠ%/-g}R|Xęm¬l‡§¤hj7nę/-(łPP°`l%·BçÁŃF‰Ç‘]°éĹL"°M®·Ş]ZSVh5ŹŰę˙úíĺď.Ă*Íe3sCYv­KmŐăÔŤFT©- N¨Kë°*©ć}SVÇĄěâ='h]ČćŠăN4e +#=pKJĚ\»B©(îňZAŤŤť$ňâ RŔ¶TwĎöëŁďŸ^­µë|ÁТľŐ“›ňň«sJüźÎ®UpŹPqł/„Qms›:`Ţ€ÉmS śęŕę (hj©°zŃh4č uŐOŁ#ŕŃ€ÚŔ“»lhYôíp•€D"‘‚úL#ţšŃc®ŚÁš˙±˘ć[íŠ^Ř_IEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/favicon.ico000066400000000000000000000021761433544061600226600ustar00rootroot00000000000000 h(    ˙˙˙˙˙˙˙˙˙˙˙˙ţţ˙˙ţ˙˙˙˙˙˙˙˙˙ý˙˙üř˙˙ýú˙˙˙˙˙˙˙˙˙ţ˙˙˙ţţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţţţ˙˙˙˙˙üúř˙ÁÎŮ˙‚ĄÂ˙f’¶˙`ź˙{–«˙»ĹÍ˙ůřö˙˙˙˙˙ţţţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţţţ˙˙˙˙˙ńńń˙n»˙!k§˙j©˙ m¬˙S…˙L{˙O}˙cť˙ëëę˙˙˙˙˙ţţţ˙˙˙˙˙˙˙˙˙őö÷˙ńîë˙tś˝˙f§˙.t­˙/u®˙&_Ť˙Mr˙$Z…˙$Y…˙Jz˙h‡ ˙íëé˙óôô˙˙˙˙˙˙˙ţ˙Ňßę˙›˝Ů˙0u­˙+r¬˙,s­˙-wł˙=[˙4N˙"Z˙"X„˙!W˙&[†˙šşŐ˙ĚÚć˙˙ýű˙ţ˙˙˙úúů˙˝ĘŐ˙+q«˙-tŻ˙)lŁ˙$`‘˙+s®˙"Z˙Mt˙ T˙#Y…˙ V‚˙łľÇ˙ř÷÷˙ţţţ˙˙˙˙˙çěđ˙ŻÉß˙B´˙!lŞ˙,nĄ˙%a‘˙+q«˙"Y†˙Mu˙#V˙O~˙:k“˙¬ÇÝ˙áćę˙˙˙˙˙˙˙˙˙íňö˙áéđ˙ĆÓŢ˙:zŻ˙(q­˙,v˛˙*p©˙!Y†˙!X…˙V˙/`˙ŔĘŇ˙ăěó˙ęďó˙˙˙˙˙˙˙˙˙˙˙˙˙üý˙˙˙˙˙˙‘°É˙%l¦˙5w­˙*p¨˙!X…˙+]…˙R~˙„›®˙˙˙˙˙ýţţ˙˙˙˙˙˙˙˙˙˙˙˙˙ţţţ˙ţ˙˙˙ţú÷˙L†µ˙k“´˙C}¬˙šµĘ˙śŻľ˙9fŠ˙g…ž˙?kŹ˙÷ôň˙˙˙˙˙ţţţ˙˙˙˙˙˙˙˙˙ýţţ˙˙˙˙˙ÍŘâ˙+p¨˙˛ľČ˙4u«˙ČÔŢ˙Ď×Ý˙)[„˙°şÁ˙"V˙ÁĘŃ˙˙˙˙˙ýţţ˙˙˙˙˙˙˙˙˙ýţ˙˙˙˙˙˙«Č˙&m¨˙o•ł˙+p¨˙ŐÝă˙Üáć˙"W‚˙i†ť˙T˙{•«˙˙˙˙˙ýţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙ţüú˙Sж˙%n«˙%o¬˙2tŞ˙äçę˙ěîđ˙+]‡˙R˙S˙Gp’˙řöô˙˙˙˙˙ţţţ˙˙˙˙˙ţţ˙˙˙˙ţ˙lšż˙c¤˙g¦˙Sł˙ú÷ő˙˙˙ţ˙Px™˙ Hx˙ Fw˙`ź˙˙ţü˙ţ˙˙˙˙˙˙˙˙˙˙˙ţţ˙˙˙˙˙˙éîń˙¦Ç˙tźÂ˙Űâč˙˙˙˙˙˙˙˙˙Ţäč˙p‘«˙w•®˙ĺčë˙˙˙˙˙ţţţ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙aio-pika-8.2.5/docs/source/_static/favicon/manifest.json000066400000000000000000000013201433544061600232260ustar00rootroot00000000000000{ "name": "App", "icons": [ { "src": "\/android-icon-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { "src": "\/android-icon-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { "src": "\/android-icon-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { "src": "\/android-icon-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { "src": "\/android-icon-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { "src": "\/android-icon-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" } ] }aio-pika-8.2.5/docs/source/_static/favicon/ms-icon-144x144.png000066400000000000000000000132431433544061600235360ustar00rootroot00000000000000‰PNG  IHDRĐŠPLTE˙˙˙¬r+W!ţţţüüüúúúřřřéĚ©öööôôôďďďńńńíííëëëçççééé‚V ¬q)ĺĺćóóóŕáá‚U­s+<(××ŘY;§o*ääĺŮŮŮ["ââáăăă¬q(«p'ŢŢŢÖÖÖ‚VÚÚÚÔÔÔÜÜÜęͨÍÍÍŢÝÝĎĎĎĐŃŃËËËĎĐŃßßßÓÓÓ„X!¬t/TŐŐÖÉÉÉŰŰŰÄĹĆĘËĚŇŇŇĆĆÇĂĂÄżżżĘĘĘČČÉ‹e7„Y$ÇÇČşşş…[(´´µ­w4;'ŔŔŔ˝˝˝ęΫ­y8W:¸¸¸°‚J‰b3ĆĹŬ¬¬ćËŞĎÎÎÂÂÂÉÂş±C_.:&ŢßŕÍÎĎĽ»ąŢÇ«»Ż Ż|>T ŔÁÂĽĽĽ±°°Ľł§·§““qGŚh=†]+wOqKââă¶łŻż¬”˛Ť`µ‹X­{=]=ŇŃŃŔľĽ˝ą´Ą—†ł–rą•i™€a°‡V•xU™yR®~BŹlAmHfCĐËĹĺÓżŃÉżĂÁżĂľ·ą·łä̱ŮÇŻ­®Żľ¶­Ľµ­čͬł®§·«›ł˘Ť»ˇ¦Źrł’j‘rMŹnEŽi;Ąm(ŐŇÎŕŇÂĚÇÁÝÎĽŕθŰË·Áş˛ŕÉ­Ż©ˇáÂś­˘”¨›‹­šˇŹy¸™rź„a–|[|Ra@E.@+ŘŐŇÎĚÉŐĎČÔÍĹËÉĹËŽ̾¬±«¤ÁłŁ®Ą™·Ł‹ĽĄ‰¶ź¬—}ťŤx·‘aś[˛‡Qźj'”b%ŰÔËĺŘÉČÇĆćÖÄĆÄÁÇľ´Ĺ»Żµ°©§¨¨Ąť”ŞžŹµ›|ž‰n­Śdއ\±Š[–vOŞ€Mµ„H®€Ge&Ž^#éăÚÝÚŘË·çеŐǵ˹ĄÉ¸Ł§Ą˘Â±śĂ¨‡˘iš„išeuUŞ„T¶OR7K1çÝŃáŃ˝ĎĂ´ÔŔ¨Ü˝—¦•‚ɦ}Ł’}—‡r͢o¶€>•k9Źe1O4áŰÓĺǣǰ•Ö°‚Ŕˇ{˝™m‘WśsAŘĎÄÜĩ٠śĽśvÄ•]Îm~^IDATxÚě•ËkQĆťi+Éd™;3ÁdĆÄ –DŤşĐ¤AW‰DĐŤˇjDABíFÁǢ.¤Ř•¦¶(ZZ7‚.\‰.«‹nü DWâ˝óđdÄÜq¦".ü %›ďđ;ß9çΚ˙úŻż¦!řńhhŘÖ­&pý1¨ŰW>ČC5­žfddd­+üÓ)Ů´z\7‹c)ř/#ő©ŐmŹgRúMĂ«@‚Ҥp‚ăx[—Pâ1é×0%\ď3­*R9®pĽ ‰H'B˘$p §:÷÷đĂ$"„ü&H6z< ‡it5·7Űëů¦Ş‹ďU§šZév:ť®a©jÚ3Q$ś;/!µ;żĽ¸Ňh4VżĽëĺšëŔ)@4Čô`a©555ŐZZxĐ©€)*)M:EăłË+cÓ”eŮ4ͱ'ˆ•–ř„SÜďSçńËcZYÓ˛Yü˙XkşžŰ”Á•‡—ô7ź_1¦ĚüÉě;qş˘ú‹ű›Đ›Ł-¶śd=%ËěĹk>S„9<㽯Ą ăW¦tnÎ("ŠűyD}öCMcýŇj3uĂSžµqNźhC8 ąý6e!Ü®wkĐDśÓ7/d^N:&Ř˝P#¦É{Ź*Č~ç<“4ţlżFJî?˛%ďvń{‚Úóct ¦tâ`*í,‘»w˘zhK:vuÔîbx8,ú.yĽc˝ęíłBÖµdОۅśëź’ĎîŢPt¶ÚÝétĺ†ô°`DLh~/ĂśŮöÓä±Ć@‡Â:8˛A:pµ`čŃ€&rĐůKwA‡\ĎÎ @Ůę•Íą uŰfŔČ^O´€î] ™Ö:ăśf¸wh„ÄźútęňFżâqbs®š ú¸}tSčo‡ł ą÷%†®±C;Ü»÷€ž`骽ؽĄ(ÂĂĺç‚–¨tt‡÷ 5Ő‚Včř.x*Â}\Ĺď¤ZďKa'B)ÉÚŞ‹#׺yóäşÝ\şąíî6ŰŹŘÍ -Á(t•˝Đ ˰´w feľ0“^ Ażč×›č‡e*ŇŰŚŇŢFţ AĎÝnĎ÷Ń(ŰîAÇÉ><źď÷óů<ßçvůÁĚţ˛ű8ÝÍ´ŔR¶Ş Q÷ŃŽśgUÓť«pfž ą˛B„–&CcÔ*Śu{…ĘÍŕůpVZV±ożm9ĺ22Ł]óv!{Ué2´LíčÇľ"1ąĽ˝UĎ63agő3WŻÎúÍ—ť»t@Zc˛HÁµ—–›MË‹űh+0fŚ3s_÷dz =v#bΞ*‘¨ @Ůč]45 Hź‰Ë)ďa™3±€»|ó¦ŇŇŤV‹ µ˝áQĽ)ĺ˝jŰč‡ÁZ8CRÄ}0ÉĄ©éR_!3N tąusŮÖZ— 5Uw;k:}űŘ*ZP; CśćU=Ž+ýIKuT :ŮŠĘZ‹K”ĎP§m±Ń‰°6Ý60 öQAH3M„;Î28צQŚôLLđ˘G㡆Óč± Dč€!ŰXiůîNbŔp;ÍĚ@ťž>>{âloo…ś'3Ńuř¨¶aH„@ŠTű OŚO¦@Č06ććîššŠLó“éĚÝÝ B’ůĽÜ  ă đ}~ţÎÜň)Ķí;V î®?ű5? ٵ§-¶aOŽł/{a33‡˝­2DÚŹá’ŽÜ‘yrÄ`ĽĎŘ×/ ’ĚkV¤ťę!»zD6^D;i~ŠDńO@°@„Â)”aĘT@-‡ëÍy¶ńľMŇ®ˇěŮJ‹Ę¬-o@ J ÉżKŹk áŃşÉă<“…ďĐů÷ Yđňňµđ{ő×ÖęKż+·VL<9d †L wŰ|\9ş&Óé ^súżW©Ô?4<żË9ď%‚(Ś[ţШ(‹ ‚ (H…ĂS9űŮ{oŘ»±·č{ʱ÷Ť=öŤ]c,‰˝Ť˝ĆcKô˝Ý…AapuŃřLL¸»0żĽťňÍ›oVf°ŽDĹ+ îď€ClBDÚáC Qřy´ĆGh&„Hť:8ŁDřŐ¤!@˘w4ţ`€hÇČŤ<Í·śţ,C9kŞSÇa–­Ü‚ĄÚWTžĽyńŹQ—jí7ÚŹD $ŞŰs„&"üćĽy ɉÂF›Ků! ČŔŰh ŔĂd— $¨ČŽnČX­-[ů/áŢxAuçČLůÖîa ŕŇ*¦$ëRe”„2rŞSmÔâç×ę=oGN>Ž"ĘěáWs嚼ŕ€E—6-ÖnľÚP2éG“Üţ4Ë6·š<śś+×ŐËĽŁ(.EÄp0 mtxý&żešc¤{LËŇn‹Éd9°p ćšő/rňzŢ â˘ÁÂQˇPčĐJlN:‰Sĺ‚ĐFŔácÜz«‚7´$ÜĄćă$ Ťo\®cU&µ@ň.ËŰ5߀´ňY)B@ŃŞ h”hđ¸“ę )‡_'8\Šx?‘Đ« ô WlÔxAš” DâCŤď™Ű<ĐĄ‘“KžĄ/*J• ČçóQSôęIŤ‡°ŕ@˘ w”ýŔ‚Q$z‚Tł÷Íή˘…W‡é)T#Š3~”ÓÜť×ţ šÜ4¤ë;jˇ€´”ş„.’5bÄę}* P8kMź5Ýä´}ZZC5WęŮLFÁYG±-ćguÎŔćńTă=ő81¬©Ą©U+k¶*!Px„FE=T|VËÎŃ~P8¶kŮ–°Ň3c~Öě-]ď "}Ą$HuaŤŁ–/!/KĂEź®>ŠčŕE•Şe8Lv-?/&Â^¦Úüz«©E™jGÇS»´Ę§áşN$ÇatóQ»őăIUŕ'ܢŔCĚ™q)â h=jg \í´'¦ĘQ+¤J”Ei•źÖo_©žËbĹĄôGrcvŢ|2…Gí.S} CÚ߇Zť¸Sg/FWPń[j7\ę9žäNńĽ(Ęü®¶˝ŠRgÁnµgÍľÄ}huî×#äÔ©hhűvťf˝ŔChś‚EŤcÍ=öÎ4˘}]×húdí§ {üVÓgD>7Îl_Íe±+ p@[<𩼠ţ1Ôu Űżpq˙lęÄč wíÚ•’ˇ8ÓÖ凖/OA*QÄŮŹ˛5żÁnr,–t#!bé ů?–T ¤›YŢ$ž(ˇ˘†ě€ňĄUí u@)ťâĹ•”hŰŐłYĚF=čdŘP$"Â×{ ¬Őlň:2ÚNéýŽWi,íôNÖs= ‰â€€çŇÁÇçÎť?ţÂk×nüb†rřÄ˝îąĺѵk ˇˇąs?÷é Kk^Ůaa.Q´Ç9F,MöŮűöŻîŤ¤|µ´F l†km$[@0®Ç9(ďîŠUŻłjÝ_»ví›>ÝÂ>q@źŢĹ4ô0M+ŘÄăËR#cĄÜǸµ•˛Ň®ËlÖ¬YffłĚu÷şúD݉mgľ‰“hń@ †čWF Ďţ7Í2 E˘Ůýn>1@ ŃH,ôÚy;s<ű0(VŃ•cźf…b"ó^śR¤ "”nUü¨Ń"&DČEQÓ€.®ËühťÉ_?•ˇĽ˛î#ˇă—†N¤ĘÁóńs RgKÄĆżŻzÄá6qÄâĹ‹[qń,»Ř ‘ J+[}áX 1n·›ć·÷ń•†Ëën:tdtż~-[Ží±˝7ó«}¨Ů˝°yş~ʱý[öë7zô‘MćđU¬§ęČÍ$»Îânč *ŰVŻ=ť1Ęî‘Q–™y_ĸ—®_ĄRą˛e2BĄmŽtS›đţ·¶â-5˝ŃjV«MŕűÉ(Űş·˘ #Öâ,ÄÍCµÄđÔ=l.(®¨Őş4»nć%\Ëx"îźÖ g=Vµ×Čš©»i„™Zž”‡8ŻpidőzBÁ]äW{ŞĘ'C,4QQ“ŁŞđľ «W‡‰JŞ…¶¶ yÍ,ÖŃd2¨fŇTuNˇ ’¨P¦)<&ÇÎ’âW{qQáyµ€7 ž^O-•UęĄYáň®PĆk†FÓ¦=EʼnŐCʡc+:Ó 01ąă®Óęř…jDa÷­S,Ą%˝Î[,QásUˇž)ŔňoNżY“IĽé‰F.áš9QÉ–Sü EVíŘY•IĽňój!î &®‹$‚bÚ5­dŞ ç•wWĎ€ĄťČ±_?J«¤?D¤UŢÝ’ëĐy‰űťk“vKhZUF:Pń.+ÚUtc‡y®H)áP+ÝłMQ©@ĘňŞ›_4­`$ĘmŰ^F|ćöjN3đŕÂŽ@żMÄMŽ1ÓK•pL^yđ”˛t>„GBެÚy¨cťěĚď—wP)ő ‰<„ ʶEký eÓŰ[ŰVÓ–%ńU‚çx&wĹÖ˝Şe~ H®¬ôˇ/kŚ ő·†Ąźz-ylţŃ—ÖŤq9Ć‚5äĹ·ÓyÂF‘ňŔę Äm•e=)¦ž—ÂvXŃwWSvę˘á!Ąď?DŘbĺ+_‹)…śµĂ´ţĚ®¨-VčőĂţlśtî"2QîŃŠ™÷{ˇÓCNQŃÚr=ŰĐ­ÇXdľăhAr’Şňém+ÁE”’Ů2‘±÷\ś\Ź\tş‹(#,ŹŮC„•<°¦ČFJšŃâĺô«tüBŻŔ9—µç1˝ţĄlŹEĐ//fJ‹ÂRja4Š11zţr*s ÔK•2ÁľżŁG¶°P™V=/á°?X‘gŢěŹÇ" .ϨŢ–‘xćÔŃĐĹŞlŮkëc3ó‹Afâań&v€uűSűB\ŰĽŚů KNś c±Žďsefw<8ťŢYlîTcőěH‹őb]ÁUäယ®a©čs čť7±`ŐĚž¨§§;űR çřëXČŽĎ{iÖő-;˛˘…Ö'±X»żő‚ăçÔŘ)2ž¸sçbůŕ>x!x#%f"ĎŚ–pž¸ĽÖ\źjˇ··Štĺţň´ŽľŠ(ńP¤0Ő]X¬ß¬šÉOQÇ# Ć˝­6u©+hk™-]¦Ó©m± $ĐB-­ŤAˇ\nnÁDŃ’qAML\ŕ÷ĺ 11Ô`\NĆÝ»o†éű ăˇi‡w2¤ßľďű}~Ë›ű¬ýS©š÷cĹEňy$ź‰Ńˇz\˘,[ĎZó™8ÍřôWÝŚ—>WkäBBú[ć€Kpľ˛j ÚWůfmţĆ™!“°ÄĄÂz@Ö"‘h4"Ő9%‚k¦HSýŤ€J"*Ít˛˘ů~ŘM®…ş¦` .4=ŹMĄçßîż©š35%§©z×ď‘‘÷»Ô2>€i.®76o›rčGߥűÇF%Ű·ź±V"!YâżHizžľoěˇĚCŚŕB$A!Č»(­ćŁéTg"‘?},G<Ő dägZłŮUż/Cä¤<é$Q/ZŠćîBfh~Ľ!' ý´—ńyVçÖÝZhő(đbń¤Ďťbi—Ýd¶ě‰¸łr G˛xÔU>kÓćdÇki+˙řpRRjí<„ARK$ę¦_zâe¶š &ë—şęÜźb7jćgMvÔýÉ\ÔéZ÷ťfŇŕ°ÜŐAŢa%,Uŕâáí4:Wk\&ą©yŰF9ćKgÍáiŠ /9ÜŚĎn íÍŔĂěő K_Ľ‡ť±‹DćEú ăĐąr,UÄ|éśIYGT0j3^Ö0dIíĂ8íh“—U:nÚJŔĹZd i¦vć¤ÚŞ÷7ąxf ˛–¬­:‹N;ŕĺ\vŇî’x¨îj±URŕbyĄ®ŹhŢ\Î|ăB“#ÄË Ąb?«ä[Ţ2ňĐ´#Ěąvóĺ] Ű˸*}‹Ý, Uů$ťFěfĺqxQ`Ě×Nbľ´Aţ˘ ŻşĂrłŮú˘|Ńâôd‹öď1ŹŽŽ±´ĹăjĽ©Ń`̇ĚŁK3ňf yµúh-çó¸¨-Z,4pÔO‰5vń˛ÄVĆĐü¨ľî$ă´ř÷E°ŻŹ¶:ć‘,á;<˝€ENłei<±Y‡mÝܧĺ)`ž¤·â“T˘±Ý ŇUßż]RÍ;Oç˘ËÂ٦—6c$ÇYĆI5Bý…N*Č)Ĺ $ ÍĆ–^Ćít=1˝ćF!żxötiµ\ĄM•s¬mo=Ę˝‚ŽŁýGŞRć!ŚP× !Ă~Žć¶Öá Ś ×x*Đ} şŇś_Vµ'‰·ćx×O×_kŐIăą–RQ" nľíő3Lď¶j Ć|2”-[2oş~2 K źęő3\7x¨Â‰âPI2=;‹AâÓŽpOOď9Ľ¶3,íŃŻ]·lŐň…&+>Ijíů®ëßz^ Ĺęő E–x” ke˘: ă_„ĽŢPK®%Ň”ś ůť#ş/® ]őÝ*uNÖ®¸—Ď‚‡&ŹBˇ1C (B@JÄ㡗ĺ9p÷:C e5‘¤ĂE¤pßŐ•‡¶ UZµ˛ÄŁ]W•ct˝Ů‹ÚŚýÔ¬cĂCýýýű††®]k€–şŁ/•h€PËC %Ĺ›3m˝ĺëĎľTę¬ R4ş=‹6#m€ŇG#PVđ«dőşŹÖꤲŇéPÄ'Oe‰Gą‹äĄ×‹Ń‰‰»%]-˙ăŇŐkfČÖ®­iňĘŁUR;ۢ™ů<É€kŞd«Z^‡Óă! I6ń]؆$ć•…¤čŕÇěë±é’…*3×"Áĺ—…TË],eđ T\x"Y5·"% dŢ'XźqĺŠEÓB¶FĂ4w˛n٦hYÚăď:ĂĽ‰đBGQ/‚*áš×hé9%Šß-Uöw yxqBţiů_ç%¨âďÄ+íWŽĹ%&Ş MÚ.áʼn˙VŢ·…5EŐżrÎ3ČĄ(Šă:AÄF–EI^¤WŮldµ¶zď˝î®˛«˝[˝uÔÁč˝÷öÁ(ŃËŕ:˝Žs_®˝BîóxaĚ8|1;öýćÜóî=çÜ˙yjťţtP Ŕ‚ý˘ç9FĄă"ă”k!m Ů˙S »–@,Hlîů˝9â‚_ŹE¬`K1řUÎGô…đ •fďĆz@%K¦ěşĹĎrˇđŐľ Őaű‚E¬Ő˛WD$±H–’özbŞ|Šëb¬/,T(_¬B«Ű÷ ' ±Ŕc­–y  xCżäŚ ±řşşp,‹Ěő5ĽĹOč şDąZ¬P(@é¶÷BélEc®­N/Łń"(,µ*Ť §*^8U R‘‰o4ΑăVĘ>˝Ë¦łX,:ůÚÜ…c‘uë/k˛iT*Ť”Y’r+GŽĆKtęÂ4ŇW9XJd3R}M ad479Ô1&ŠX`ĘÝŁýŻŢd2ď›P`ŹL ¤JXxŐś…Á†Ö†ęyÄĺĘ٢†E’gg‚}ę ü¨Ć}\¬kŠś…*,¬ł0Ř´ĺ‰SÚlj2řk{·ň˧b(Ö]ńčP˘`ˇł9‹ŘŞľsá%Ś:díVáG`w1rĽŠ‘Ó*ä,b#_ńü ,ĺ®##ĂôČĄRp`]ś#ĚžS„auÎ-(=8°Ä7RľYňQG8śU Ś KF—ţě:2ę› NícS¤•!夊é“:#ë9Ęł§dÉ=%ŠQ±$é‹§K8Ü5Šđ˛úz,,ŠtŇä:Çăá+´ě˝ŘŐô丸äô«Ĺ(Xi5ăâjf¤Ń¶ú ďc¨v“«ŘămrXC„1gwiĄ&·ży_6ň5}O‹‹‹§{"cI2âXË żŚOQĐżl×¶|;:$i*gśVë\V{™É6ňM©6g-ęzlňžb‘°dé5ńĎÓe4wÍ™ăĺn ˇAl¶©Ä±gu.ÄÚjŮáLlŰŽđĹîăÇÖĽß&"ÖŻ?BÝ#Ţ9]g8t Ęš¤rD…ťEp€´˛ŁŰY­ŃčzÔ€˙‰Uě'XÔ·±ëů–- ˙ŮśÓŠšp!.ȱ¤F_ąj›PĽs/bŻE”ELĆ‹!ćI›©Ń¤/cŃ=7 z ĺîŔ%VŮâ’Ćq¤%k˛ÎŃ6šź/ćŘé絨ZÎgŇg}…±čSFĘBÜKŤő—ĎçČJ”ĚHNî%+AĂ’,N®Y3y±„K© uŁ•A‰Ş¨˙ˇ"HßĹ°ŠŽÉÜj¤E{ö,âÚĺÓ† IăV%ŐIô[ –â…c ‰¨cId^fs´C|ýۧ<śg`›6ś‡ś>?ÉşúĂM.Ľ†ĹŘÓ¦đkČÎY¤ŚŮśYb+úG5ąL8ŰĄĎZ1.2€+⦫@L&s“ŠÎ¤:‹Żý*V§łĂŞVIpxQŐEFD.v‹/¤¸1aBJĘĉ©©S§őýtŮó‹XĽpHεnD»ľSSS'Nś’2AĄŔĹĎŹí˘Ř덿Éjŕ ćŤaVb‘$ Ĺ_«đůđ¨oňÓŢÚ‹źěxŢŠ1bŻŢ–â ĺŮ“Ţ+îáµkz ‘Čřb˝{ő-Ö^eÂ30 g5á™üOŞ= Öb­ŕG8nx.âĄoźu°CňÓp,\Ť‘$gřá!đdÚµĹ|óć‘ßb-5˛5Y$,yÖ“×őxůjŃC€"VëZ:/u}ú$ ËŚ&™pp‘‡`‘ěá5¤ŕwÖ‹–?‹L$q-"ŘŰR<ŞDO/pV»đŘżdŢá‡E¤`ŊñF>ÍƧ˘ĆXÄâř`e}ö„#äĂŇTR&˘ť´o»víFŚč·®.ŹJżŤäĹwX<QŮyô¬~#FŚ€őí;mjęÄ%:¨}~ŘćńPˇ…1»kTIJ¬Vµá°éícxĽW“ĂCţaźía\ę5ŞZ>1 nłýő›Ŕ,e¤ÚçkĎM%ué˝V·Łb‚}G[óáZ”\đ°é|śŐě8\ oăđ•µĆaňTA&OĂV1ÄĄ†qc›1ăÝörSÚWć•;¤=xű)°˝¸–‘F¨¸.€ZT 8VłÉĹ`¸Ď“Ú'śKšÎÖĘUö>|żž[›«÷‡dô‚Ô~17ąŇS>`·š ukŐlٶPL±Ď\ 4Ę˝V>v•u" /°6Ů®–ä}RK¶5 ŘÍ‹€ňŁÁymvW¸U.BäĹ5®˛7w–*őÄÚłTΦĄ˘ĚoĐkŚěřbčPÁ¨3şď´Ž‰vv*i6'ŃďµiqÉ“ţâk JťH®aP ’f©Ţ~±t´±d­¦€ĘDĹľzyř}ŔâÇ›±ćă˘Üi–lďVĆaŇŕfź›»ű>ÓdĹ|O‘(b]X'`×gîéż<^€ÇXMîD|$KŮŁRBüoÎU“¬˘¸Ćčîw ¸„a޵Iîr|,˙ţĚ>„˝ăöüč`)ôOň[ą‚2łĎťĺš<®tŚđkN‰l{DEíýńŻ—oůĹŽĄŠĂ’mÖŞ[¨,™×ŐY„qéLnçúS1°”MÇ@ÍöIEąP‘;F8†¬µoŹ«çů}¬˘]çőO,WŃl“+„R‰-ĐÂ)łľCéĘ$ŹćŹ%ËZTÖsLĂ€Óm”jQ´c*ˇ\˘X¶źZ»íĹŮHčóĆRfߪ(c­Ś¦xţhP‘šVkŻ#Řo`Ť_ĂR*; Z[ľŠß§·AëŹ4oŁŔ…ăŕ0“Ő^nĺĹSŐ+Ăg>řa-Z©i«ţ;â3[ĘŮS…_ů«-6˝Űl»ˇ}ÝŇ@VşD”˛hÖÎ;ĆÖI¬â¬hEÝ÷ü… ^¤`có‰Ľ(S¨5}Y{pŇšąóë–.…śFĂ’¦›×6L*ăŻč5IIcŤP ĂC`6ŁŐQ»ÜäŐĆíěXÚSą2’+’ěd‹H·(ëŇsŰŘţ Ë8|f‚‚‹ A®âüB„Ú"u™ËV¬¬ŇvÍěí;Ô¨[Ż:¬gL‰’JHć+uí2ľÇĽVcÖv«V1yŤ…JJ^,8Â0T› †ŠüÁ‰mg8şaî¦OśÔjóŘ1w×ÖéV>©L9§ßîö©E+fˇ„Ż÷‡ľ 6 •“Ś1Ţę«hŻ 6hP%”i ýP•şAé`&ÔŢqB '#÷±¨Q®ŇI .ŁŮk-ëö9`˘ľĂáóą­^łŢÄؤHĂÖ6”/›E‘ Ç&+,ŻY4:© ępĆĺr1ŚÁŔ~8LĄEH,tPÂŔ°ô,$sĘĎŠŠŠ«µZ9kZ5ÉlQ H‰źŁ„áĹDhŔ*ñ±52.Jsb&ľPÂѰÓpĄ V ý+¤âCL ŠTŕ ®0g¦ĺe‘&AlajH„<H8‘p8b˙Q8űçßáůŹě ćĂ= żŁIEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/ms-icon-310x310.png000066400000000000000000000422531433544061600235270ustar00rootroot00000000000000‰PNG  IHDR66ÜbN›PLTE˙˙˙¬r+W!ýýýűűűřřřôôôéĚ©ňňňďďďíííëëëččč‚V ¬s,<(ńńń„X!ÜÜÜßßŕ¬q)ęęęÖÖ×ÚÚŰY;çççääĺÔÔÓâââććçäăă­s+ááá‚Ućĺĺ„X#ŮŮŮŘŘŘŢŢެp'ÖŐŐĐĐĐĎĎĎ«o%čĚŞŃŃѢl(ŇŇŇĂĂÄSĚËËŤ^$ĹĆĆĘĘĘÎÎĎ˝˝˝ëͨÎÍÍĚĚĚę̧ÉÉÉÇÇÇTČČČ«n#ĹĹÄ­t/W:­v3€Qč˨»»ş…['ÁÁŔżżżĂÂÂČĘĚąą¸=)ŔÂĂ®x79&ĹÇʱ†RŚg:ĂĹÇ·¶µ_-ŕßކ]*×ŘÚÝŢ߉b2ŰÜŢ“tO°t,yPÁĂĹÓŇŃĘĚÎëά\=ŇÓŐ”xUÎĐŇľŔÂâăć···Ä»±±±±Ţŕâ´ŠX‹e6­}A­z<Žk@sL´ł´Ľ±Ą´d´Ť]­®Ż~S©Ş¬µ°«·¨–nEÔÔŐĽľżŔ˝¸Çż¶ĚŔ˛­¬«˛„JÔÖذC¸şĽŰĆ­¶¤Ž¶ „™€bĆÁ»äͰ´¤ą¬ś–|\©p*·´°Ľ´Ş°© ®Ą›áŮĎŐ͸«µšy®€GĽ¶ŻŹpJ”rHÎÇľçĎ´±«Ą°ź‰a@C,ăиľŻž§™›„i±}=ÉĹżŁ’~ź‹rł“l¬‰]š|X®‚NfCÎĸʺ¦Şź’»™pS7ĐŇÔÝČŻł–rą”hmHËČÄâʬ§§¦¸®˘˝ |˘Šlź„bżľĽ«˘—jFÔĎÉŕËłîŃŻ¨śŤ˝¤†Ł•„«€§sľş´ż¬–˛µ¸Ż›ćŢŐŘĂŞ™f&ĐËĹçÔ˝ŐĘĽäơ©“yˇŹxťh'ĺŘČŮŃÇČ´śŢ˝–ŞUL2ÝѤ ś˝¨ŤÝÔʰ˘‘ Ź›Ž}şťz”b%Š["ŰŘÓ®±´š‡pĹ–]Ţ̶ٷŤŇ®‚«ŤgčίĤ}ťvEčäŢŘÓÎÄ®’Ă©Ľ‰KÎŁpÖŲ”j6Óľ¤Dz[-AfIDATxÚě•Ńj@E6AŁR a%Š,í?J ţ˙cgtěÝ]´­›MŮ“‡ŇR8Ţ™;q@ B4ţă?(oÍűÜŇ-ŹWŔŻ„Pú58¬N’•·Ăů!ś2 fĺüąÖ@ÓĚçŹůCđO$q/gĹ}3BGéŐˇZ×2‰<Ď“/ňpb,,ÜpźX”0’ťŤŚ±+oŢÎuúüřGŽŇçX·~Ü'f˙`„Ň‹ľI]Đ4I@Ň4=eŕDżSÉá6n®’0Ö4a$%ĎŽK'Fď:9Ü'"p‚îůUĄµ.´V*ë‚Čä–¬J¬IfFFĄ*B‹µ.uĺ÷đ®oÔH0#ô3Sş¬/ Ó¶q˝6Í…‚¨,ë:'űź˙u#BĆă`$%Ó 6VlLŤÁ-©řf]“o$‘ĺŃľ=ź_‰—ó{ŰQ4M­©ĂZ?3CiMŚşü$ĆüA›Ă0îÝ$Ú ä.ćčĺ’ćrI.zH‰ C¬ˇ…“:t¨J,¨‹#© ”tphAčP† ”1C†Ň ‚¸d„®ÓÉ÷kNß^Hę5âůŕĐZáń{Ţßű§ĐR)Žőú|vpLĚĂ@tswÁŃ©9b©LبĆX#ˇiOp )w‚Ý˙±Z|ů˛Ri·+•ĘËb«1ăóN'8€NĐžŻ0Z|ý.8ćčÄh´–çÖň ÇksŮíÖŚťg®ŹŁéUĎ> 6âqą3ocł}XŰzšľťÝN?Ýú~Ř~ŮrđÓ Ö·t*d}*޵.jżple7Ö›őňýÂ"¨pż\oî­Ěm;řÄ4ËjŽçp6ü7ްňXxhgj~µŇ©mĄsĄčd8lˇ(K8<-ĺŇ[µNĄ8ÉuĂW oFYCG§Çé}­µ×_ë3±ř”LÓ´,OĹc™…űőćúFö"źG78"p˙‘7=k¤[F=.1á]­n墶 Ş*GRUňť-šŰÚŕě^ŢωžŃ ú¶!2Ć:Ž€#çmĺ÷ę‹1&‰D‚Á …Žľab ĺŻËÁ‘ĺDatä<8ţ_ŢŽ±¦ŢíÉ®¶«·KŔŤú-[÷űpévµStLó¬ßíę§çÍ(kĐ ŕHVŰZi2q+ىÖ_JCO% ő×Ů™i°äú;š&d S "ۨ¤hŞŤłtMމ˛tT)]Ë÷xö®ËÓmT]ő˙I ëˇ5ň»ĺ dI´UY­™Dľ¦%€NJŢo®,Ďđŕ(z´FEÂÍŚ KŻĄ *óěćazRQ)QÁߏAtJ4˝łyůžŹ‡yŁ{ĹÉG(˛ö«Np¦ńĹőrL 24‰«ź¬2Í„BńÂîÜe2ÜŚt47„ YÂĎ˙hWsaE±QZŹČŐÎUŰ­%ŢËşŘoşWśÚ}jŁ.§űÇƧŸ’čÁ‚4áLÝ©?{żsTóÔËšË SíitBĹĚhlŚšPŁO:Ĺq;Émr;«»CNtÄÔŃßZ©Ç‚F–é?Ebľ-p007sÇ–žl4’šÇ»ŕPŁ ÉBp;ŮŮ%Ë‘W`n^ˇ»«Ď’ÔDaľ¸^’h#‚–ä…f–ŚťŁÉ¸éßŕÜěćAŽR•1‹ÁŘTK®–˝´äŐş_A4x( cŠ-îä d•Ťĺ&é…ćÚEŤ7t47üĚC«ĽKdż” Í@pŻjŮń€Źuką!nđgŔ>@G7WÜ[€í)ÓŤĘdšs—ěH¸ŮĽa‹’ë ¦Lbµ–Ł R3ž›bÜ.CźíSm-ŕ°é5ÄÁ—‡KH÷îČ!úT’č;źČB%„kަ]!XzmŚxDnµ“S ¦†KUUr;ďǰ<äz?©řşÁFçë…`ŹĽI »/áT q3'6Rzl§‹ŰďäÂءĆű”Ę}~?k÷úEç4Mß+-±EᮞY)Đ §“’÷¶Wꬉ]އ ZTLíWžXlČšńÜ&&źn\›µó~—ÇľBďCFiľbj†ĹHrůŮöR€Tę´©i¸aéµ܉ÍjiS3.‹Í­fŻ=÷ńú¦Ax9¦řb3&I2}jÉL$YűpÜ®w4!6,}·aR\ŁSÂ_ÚOÉ› ămÖk‹ŹŻŕ(¦Č`c¬ô0bĚ.ś˝>–ŕf^›âĄ«í´_yB©–ac›L篓â»FĎhśl]Ľůµr<ČČô0’tůŮ•çp$ŰTřżľKďąF­dS,ÔR˘pő>·ĂŚÖlúÜÖdy¸Ř’!zHÉ’”üôâŇ8qAGhĂĄ'[*PIۨˇeQ©ôç+ł/çÂY=Ó61ĹŻ”é L+F*<» Ž~ 7“–BĎśůRĂ}0DlŠň¨ş|ŐĹ×Ďť_ŢÍŚ:6«5’üđîcnćЦÁsfş’«Ű0Jço|ÜÄ­m›†·o­Śá[pŞđíÖ帣I¸Á#´Ž!Ţ$‡%8[˙FŻŢd?n=—Ô±:á "µ´ÖďDčż‘Ä$żľ¸Jć):ţëÔŽw YŁ?‰9“&Ł Žkő¤¶ŰĐ(¨Ô–––Ú¦KJh MI ´‘tˇR(‰ –-,FAE ‚QŤDĹ…¸Äh ŠĹőŕ†D˘xĐ‹'=9_©­Ż˘~ĺó]ôĐdňýŢĽ™˙Ě›ÇţŰ<_Í›×ÔŁÍç† w4b1Ţ_uß“ĹŇăÖuW]F°íE>1€-ék54ké`ă@ iĆ(‹‹˘[P}zO ±ý·dŞ8ů*ł¬$QLC‹Ź ĹŔ\]őč^G2xťĄÂÇî´˛ůuúw°?zUĆŢ$=l5ű*RćŰa YuU“YBt.WßL‚z‡”Ůŕ?čŢëÖńŰ&„ô¨±bŘ‚‚ňţR)X¤r)SŘđ#Ż91!ü»i~ˇşY’–ŮÁ˝ŻţY}`ă­µE/Ý‹$­¸ŘP Ŕ]UGňRÚŘ’Çžäţ<3?%H{SQaSÂö¸Î“ ŔF—›ëLwE¦Nn(=)1°Ýě[]‡>¶ŽÁî YŇnţD›Al`qËöGOëłt±Ĺ(6ŢŇş[űT€  bCďóuSĐ§ŹŤÍj}ť®™»ż`¤ňŁD¤Ć ¸|ďţ¦‡hÂíWl b˙ťWő´±Ĺ°˛<]n•ú‡ÜYL±K®Ö‰EŢ 3QÁvë„Cř’Pl¸G kü¦\Qđ¶,×čn·Q‰7eLy&7ľW×C¨4°Ő]ęw¨±ăFÄćë~V“ŢŤŤ=|ÝâN'ßË’Wôobj÷íĽ…GÜ8<0mt¨  Ő.`«ş_´6áäŃ‚ľÓ9*TmLd nÁę:ń ňŢoˇ{^ŕŻ3H ‹„ÍÜ|׍ş±áqů0XďQD"Ă(` ěB›@ŕşż•*€i¶‘Ű‘Wú˘yw Ô!6™Í&ü/6“ł°…ę]4hRň˝ýĺÖHO†€–ĐZ`µ ~—P<˘uë©3ş‘‚4Ľ 2wrĽ7«n~‡Ťe„ąvúňĺËÓµ0 GĆ&1Ťµl5†Ĺ›Đ‚8ÚxRh c¬5Ăĺ}ĺĂőěßëąG:śQf@Č­Ę5Ţ=wfđńŃ×Ěc_žMŽ<™|vyČLţĄdéPçfýĽ2Ý ±íé­gČ´ŔUţyvňÇŮĎ}­,ÁďBt(7•?Î(Ď9Ă,ü$iŹÓô›B—7=ún|µĆNŤ™"°­í| ×ĄŠÂDZÚŔI[\D"l–µţó0A­włĹžß¸›@Řz]›Ků5$ =ü¦>uěÁ¶ŰuěĂyä]ˇ¨í¤VűŔÓił„XË×ĆF%b›W$pEĘ35Ķ‘Ŕ:< &ÚwÖř‡>O$‹a+<}[ĺL–H-¬P¸đä@Dlóô$@kßąZź^ž~:Äáçžź«JSbĂŽEí6ďťWE¤¸ĹşfßÁ–Ě™hhŘ9Y\`„-Ëőć;Ď(Ř–„ĺReOµ$˘·ŐŽľj;ňó+óóó^žţR?&ŕěWýrĂŚŘÖ'ř˝ÝS©W[Ô÷öLP6ŔĨK)´ŠŻŰÝTe®D ŽĽC.Mi»ÝŁđn÷ôőqř¤üJ»–Ý~¨áŚ“ĐˇăHL·›ŚĘoAlÁtk [Ű3«"†D‡gÇ? űś‰Ęʆ‡ĺŘ;ďuťÖŞÔxF‘á,ćNéĺčqĎ“ëľŔůO˛hs¨ĄÝkř‘®J/5ĘĂú»Ô±]®·j‚tŰ'ö}h˙ÔPiŃj,öCíçjČŹ› ,×űfԺ̼&%çRŃÁŠłÍÄ©Jťš§¨is˛sĺrynvŽv÷-(Ć~ĹĆkę…ćČظÜÄţűl…P,ťßAQ ”‰Ëކ7­Ä)|xĄVüxŻśa­‹ŘÂďÁ¶Źyä'cOw'hłĺ™*˝^݂ݞ®]JÂÖÜ’ ý]Ľ ÁÍŇÝ.bæcŃĹvŠÚfyŔBfnÎ6űäplžs›UĚk]L xJŢw^ I"ak™oŃćfŐJĄ2]_Ő9ÄA™‡Řś0Q™2 ߨ‰ ăZ®°„őŁ;(_Ë4¦«•j°˝Ő2BĆ×Ę“G¶ęÖş-LńĆĘŽW›  B<¤í/)g3ŞÓ4MšÚ¨r·8ń*ţ’ç<śCa“°­¤°Ąvžß*„­#•vŠšR§Ł,čĺnËh+ü’PLX‹ŹjĺŽ!­K– ?[ănśYAH µ_Ú_Úµą*µN&MI‘éÔFÇŮfsŰŘá%Î3„a[2陓Ő_?dɑ땰  z•ý˘‹”ŕ™|×îÍňyy”1gĂśÚ=šq&K8$ry >ʨ”ĄÄĹĹĄJÓԶƦ<6ÉĐ ` ¶Ĺ‰ŘtOë ż`cě¸W› ű"M )˛4ŁęD—GŔ&ýuÖŃJ7Ă=ŁČŠ—+úÎÜŮ…´u†qc[]Śě+h—ÉŚ™Qň1ĹâG”&:ť © •šČbˢĆb[ń«`Ő‚^Xçüşč‹hm{c?¦˝lŻ+v˝¨7…]ď˙žsôhÎăz±aň°Â.ä„óăyĎűľĎ˙˙Ľo•nân‰-nuż#ČjÖ*5Ť2Mg©›XPŘ`) üŘ>cŘž» lç«çÇĂ…N‡E—Ćý‚6łÎ°U‹ –’¬Ffy$cô ±‰s)żâM[¸G,+j˙łPŘ•mĘÔ+ {+_ókż›\]˛ôűń˛¸ŰĆvµ]E`;i,ŕ~@_P굾łméäZ·b1„şîá<úŘqfqÔĺÉĎĄJóîUň¨¸™·]ĆĚ4Ť‡}k.h}Ź®“ŘÚ§<&4'íc‡Ř„eÎÂ6NčI`sżąśFť’˙4˝ojµs„tνťW`c~06FĎđ€#1"çŇú‰»„đ‡–îśĺŽ0°é¶”sȆµ…—“69Ż \YüŘA*'ćťÁű•*©¸|k —ÁfŃ*A Ü€mxßdé‰ #ă.C'‘•Ď6ŰD/ň׬ Âĺĺř{–Çc;—°Ą­mě^Ĺ_JŻ. ¬ş]wńC i^ˇÚ­ŇŰvʽٳ^ŁHá~@ošm#ĽČËÖĹ—ÓGĎ´ftş‹÷K¬xY„ŢĚŻOŘL6Ľ–Fą¶1T¦&ţ0ľl¸@TNbc˝}&V’‰˝júN1`ŕřţů]ÚÁ§tYnz§Ücł(ąyô şoßUçĚ Ú+wQˇ¸-?(µšÓłaÍ8q)ŽúĂU¸gŽĎr'6 źţ¨Ö4dďŐŽç›VÉaSh®Y‚+2Ő)B_ű účŮʼnŽV1=˘„?4Ů®ö˙^ŠA$ĽWך±ą=‡¬žě8éžy_ÄźđŹşá[éT%¨a, k]`ă?Uł·FÝd• Bź Jq®>Ăý(=)°Qę›C'… ‚»ÇfD+)6YÂČxžÇÁžť1J+?á¬n÷¤‚Ĺ|ŕĄV WĂ^ľnîRÜ÷Rl9a::®&°dĹk‚Řiľ'ĹÖ°“ĺeu©4<É6\ˤTJpLmâĆ*J«EtDťëŇí–±C˛)gîT©µť4–ľşˇ%[Y‡ÇjÝ3|=‘‰Is ůT¶ˇHyMŻŻ|âflĄ•óéŰYN›ĺ«‚ŠvÍ(d!üˇ Âůź)Żä–'ĂjÖéÓŇôŔ6±O`‹_íó:Dl7Á$śTŐq+™Ŕ–<0Ć µ°Gkuk7îWŘŕr抚(?Nß`]˙9•ŞUćLö±Âx¦NŻ×šíf®ôۦfMĄ6 g:ŠÄ†žHe_'qJ`:Üja°eBGĐf÷;ÉŰŇáröňćpQôŹB˛K7ĽšÂÓÖýUu°3v`‘ĹŹhމ+(XGŁÔř¨L­Ž§dćţ<¤L¦dZLŹ~‘N8xµ E˘Ç?‘ŰĆ“jµ¦Şţ5=ž=ŘlôeĎŢ‚HC`k{ŃäeĹqŚFăÓFĎĄÜŇmčnU=R–¦Ľ›Éh±@›wľÄî•,hŠ„”`é†î˙řˇF±đĹGj§9ʰ™,xpf÷v«]Z3J°ż7rGRüÖ<*úźĹQĘ+XôYcĂ™ÁÍh49 »“qDşĺ,M9‹řVl&'€®ęă®OűˇF3¸ťŻ"Š”îŰLóÇs¶­Ú|Rčă+ÉĆ*:ą&¦›¸Áâ,Ş8žęďźr˛­&l†á«ńjb”¶ а¶âŻ·…»4ˇö)»·©1šÜ¶S lV“ŐX×=ë¶źŞXŰXEýÓ†8ątÓÔ7K,¶NK[ŤÍauŘ28ţ R,?ŞĚÁ¤Rc}íC@śŮ6‡ÍZş_‹ĺŻ„§¤złMzˇ[(úó¨äÄS,ŚÉŔÍňBfsËČ6x‚—J(le}yYîü8Fí ÔňUĆá{$¶d™˝˘aĽ7ŹwĎeĂuú¸śÔĎ)Żmě×J&Édf—ç°á©ĄÎĐ)Ę*BGŠUô׺"¸ă:ł˘ËH!P°fË‘op˘zo,ˇ“Ašm«ĎFťL)NÁ•ż¸ţ3\čjI}}6ô([őŔX/®N§ł´!Łěş(dB±ŇvôEĺ"Rg^jĎ!,˙z( oAAˇwjÝO`KťĽ9.„ Ă4·3ů%nô5ĹłŻŕI'í|ëüL/ěÔxhéč6ŐŁ »nĂŘuůuŤ0FŁ˙i‹¬Ť'AÁşW’JőFć–u`@13wăčMV>§Ŕöf9ˇr]PśKJLü‘”˘PęLuÝ/Üç lî™^x)ńT×XC%•mé‹3.aŃCcôÄu_0Ë4w•TÝăüłŁČ DăčłI [ŕ-rç©]Đď’’’x…Đd+šÚnĄ°µ-öôňîýâŰî÷dT?VŰN“K°ëFżřqş‘WłÖÜ^’Jíçs—ĆšzYĎExtv5•Ä6¶×”WŔJLZ€cŃ…ťvÜĐ|5-˝íŻž˝źšŠ‹ĂY3‹•T‹Ś˝˛aŚ[´Ą #‹6Iş K·{“©jŞ ˛<ű†Za€ ťĄR‹ŰňřŢ^oŘĺ±Y±É4›Í¨eÖŐex=ˇ éŤ`2Ő4ĂVŢÔîÝi +Uş{q”_´l“ÔĆ«Śs—Č2ܿ޲ÇZŻz[öËäl¬­mgslf+ ±c˙w ŤŢi¨°K±É¦˙ü†5[őţDşśŃâ]»#,ÚŽ,ő±0ŹŠűůŁĄ[•öĘo9€Búź[~Běµě/“{y˙ň[t9_,ăbőď[ÚÚ†•‘ůw>i7zµů¦…q©$}Fůó豲rŠU,M’Ú8S°LÜ >”k¶çM âňĂe˛ ś””äćäÄłäűö»6ÎĐ_QÝZŤ.x[Ă«ÍˬąŻg§SE.ÚÜż6d‹¶ŮĹźf>ęZş”KJ1§ăođš—{Ćilr5B.—łňoż“©„°Űí¤Ż#ŘXłęŢřAuştĎ-Ú ¨´ń†śJ6`‹¨ŤĂ|ô ąźÇÇëçžžËo.oľÍ‘“ÜXıqŔ0řŹ…JFbËG')˘çÍ+,ÚČ{çfĽGěTĂ˙]ÁÚ»Ş&ďň?Gvln>Ľý‡Ť>fĺ+~&pŻźŚÂ™mÓ˝úsŹüËMŢG¤j»=Ę)V±°‹?íHŚŇĚćvb?ŹČ}űçćź;Ďß®âŢ Ű»Ź¬î9xŚGTâOi«Ą“UĂ1ŹĆÜ=yCćRĺÚÂk© ¦í§o˙ äĆ˙?Ř’ó«ÝÓóťÔĚ 6`=^ Ś!j’Ö5M˝ĺ´ŁhJüţą:U-˙ŻŘÄ#zd­nČ3ô3ĹBkÖş±öi;qŹ·âĹ(…đNâKöŃŹű°–|Ú3ăy®lˡË9ĆĆ(˛-r ˘[xé§§J5 ý˙€ŤH9•ŚÚúk‹đicBô…ľwnçuEC„\@Ćdž “múŚÓzdj‹ˇťO-˛č¦ŃúšQ‰Ś&¶®ô6ţnžX•%É&ĹVĐÇ©ŃÄfďüµŘLÚSčoŹíęΦ©( ĂqÖDklqV´«VAEÜĎM\8©ZpŻXnqoq Ć8b{ďQ÷Š{"c‰#Žŕž˙ąďŐkۧń=)GŃĚ…óůź{ß˝÷śóz†˛˝¬ĂF;ŻÝ¦W—R`ó™ópÉx[?Ţe+3Ű C2˰UÁĆkÔ°~tŐ'f•HË>r¶ëŇó;+f®Ó®űü ëf¶˛8üxňâj{Ö«-HÚ’JQę zËĹg6JáŁŐ ‰lşbÖ †Y¤ÔÔmŇ‘i(Mn\n˙CoąĽĚű‹Ś[G™Ř*ő [´|{Cxź…ĎmM†®?5¬złĐpŠR—Üäőćíâż3#=1㟸ʩѩ8Ž"ć\Ů2­~á¬Ĺ¦m2xÁ…1PĂ.`Jpnça2NÁţ-˝ż˙Ĺç5)ńInć “bŹP1«±EEuě¶ ­‰)«DLb*Äąq/~ďă?‡gîź–ËۤBÇÍ}xN Ol¦Îťvľm#^ř6±˙ó¸K‹jí+ićÜňĘ8áí @ŐZŔ‡úđB—‡k o«5'Ľť’Ł.Ô|[ŮśUr¶˝4ĽNĄ¸˘ňÜ82rŁSő\ü‹~ŮÜ$tÜx„4'ěŮf5ßQ›ŘűÄđ:űJrnŢ>¸ŮŻÜ@BŤÖ¤1óÁąMął UÄ5ďB÷»r9| [Yęŕck‡LĺÜÜŕţ’üSĚŤkŤSŁ!)mÉŰ0647Wí€_ç»ö´.Wę[ŘXůPÇŮóëýÔ«áĆ‘ř`ţqn Ă”kŤ)DĚň3cě8:©â"Z+ąkDkÖŃÂç°ŃuC‡mcëíovq+ä'"WĚ92 ś n\k4.F-ňľDţ@ţ6†gčȤҡ‘ ­ů 6Äi•ŽłÇÖfë‚’÷ #& ˘s/ŕ=ž‰ékŰ ď…䏸jżž~ý}iˇ4>Jˇ#ŁoY „ÁŚ5ô xď‹Řm}¶­˝/¤(ö †tDаx÷> n˛Ży>+8ă–¨Q ^OÉ\ť’~=ÚĎo źÉäďBc•č ´ëŕşőUl´Eí2{~xď8pó+€ź??LDf@FúŔčëé)«3S®—TÎŤaS¦6‘Ĺ`új«ŐpĎ‘†¬Gî»Éż4ˇ#+eđGŰsÂ1¬Ć‚ľ‹M‡ő´ĂńE‘•B¨ęÁTe–Ą Hâď¬×?OJYm¬«ÓŁý]ł°)á&Q“΂@ízŠEłd&?ř`.Ú)čüťX8¬ě´ 9…Ť>­6–rürĎ •ĚČŢg˙óúůŃ™ĄXŕóGÉsE“źëŇ®ßýtNÁ‚ŕ:A+ťľZŔV«Ć27ůÁÝ%‘ýŐ“éQ8ĐięŞOS ăyÍ·±a]Đ˝¸˘gş-R˝ź_t°ľk± çŹ$ĎÍÔV8)d>xŢ»~ÜxŢU!˘Ö7)ĹN#9Á2÷ŐŇ3ĺ+š_Q YşĹŐü۬ˇľŤŤőżXą®9uż źŞ‚ĐńíĂ](MpyhI~ÔI/ť–(şYużQ)Q::=Só‹Üí35#z‡”$‹ ©>o$ë(îóŘč(˝7뉞Ó=Đ9h|\\Ź3^͵X4Üć> ÉŻ4żłaÜ-/›))…ŹIˇj%p·PőAe5kŐZvpŐ´dleŃÝxöšP*>ŻQvf) ÁĄ_=Lľkî¤7”ĘŤÔĆĹÖ3Fu3îpť:‘‘‘(î?‹őYČŘ`Ú.hoF?ý­Ű€¦ńtnîS‹3ą6Ąj“.˘’’-žŘ 8‹óŐíUőB[µę~r&µË.ŘЦˇŰ…–}&ôiŐ|AłX˝5qöĚx* vËPÖžľ€3›@Ły“s~Ý9çdłĆGGbw}°a·ĐkIżĆ'çěüę´Xkhd r ŇGÜóţ›4µ’R 6ył8g$Ěľ|í˛ 6XÔŕÍ‹'&šĽWŰ«»E‹ES!Ű…ôßvŕaĄé«5‚üđ5jh,űżžß^?›a«Ňdܵ„ý ¤&Ź răŇ™JH˙‚c+Ĺ6ňf±vkĆKńe†Ůňî·fhě‚đ{Ď’? ´ŤZ©TŰűôL Qy„s©_>—c›Şl„ ×€ďö¤ž„ßAŁĹôygZÔŞŤ°1nňfÉxŮşb¶Ă†>÷_Ű5ż7k&°ý“Ú’R25ż'Ří©o6HIEŮťő~{›j· ň̬R`ĂţJŐÜĆŠ ˘“Ň“Ůs´,¶×/V4Ş‚bŚŤ˙€M'š*˝5jr˙Ť 6 –9+=‘ŢýlEݤ<ŁÔ¤§óŮŮÓ.|™–C-6ńϬQ›.>ç·=‚]ţˇj˙í[â°›7°JĘżOĘ÷ĘŤ4cçćäë5§–ńRÝ|LLbĺ´›7Ó*'ĆĨÆ,…x,^«Ó©[Lďg€›ŕ!5ŤsĆÎU‡kˇK°ŰŠűl|+ŹLÜ®ťâÎ,˝}ÖÉĆĺ!Ş9—úćsĹÂj”–VMKN笖–h4ŞŔ¦ÓÚ‡NĂ9ljŔˇÍÖ»·©ç›„ ľŃăűםk·@^+áSŰ_© ŕx”˘źęj#Ş®ş˝ßɸql/QlkTŽ-ń©mGlqXěéř§ŕ¦›Övz-F€­=mSÉôž§‘?±YprDJKÔďCó1­KŃ9Ą¤6ž‹k·đĂÎRä¤ă)űëĎôŽ&ĄĐb*Ws¬-Ţ€YńµŽj•c”bÓĹ;»8BěGĽN…ÚšĽ{c×€y§獄I›kdb;R%• iĄEč:Źć´¨˝qŐW€sÍlšÔ/x1şŠ}ęÝŰ 8ł{c¤7eŘ´+€1Ăß;ZUy!{R1=KˇĄőźŘxŢ€Z OYUvşËĺĆ2‰Ho%Ë׊ k>gç ŞćÓ×/ë+_Fcr¤ĹÇ2—%·căÓ”aÓél‡ÜF(~Ú–SąŢ˘Ęn} lyĄ]¦Ső¨_|Qńl—‡¨˛»×b n§A%+Ől:gÎ×´8XĎ o>V-ń١˝Üi¨ćĐłÄEŘr:ÖJZ“ô¶ÖˇUŚ­lT™wo…s1<ű_n‰f7a‘ČVĄe7~Ę/üÜň©;d€żź>°hŹ Uë´kurR 'ÄhbbAĹŘ*Ű<ťŽŻ¬Lmń§c!1n °˛¨Ŕőí 6¦€¶kĹB±ÎJtl˘űeĎl|YZś[Ö}®(ÚlâÄ„NlG•OŤĆ4G¬;¶X[ý-6-~Gq7ĂěfSü˘Ăeóý »óű˛E ‡µµz¸Đ*IęP˘gŽŻÂ<-žÎŔ&¸ sÚ[uźĐřr˙„Ż?§a»y~ş™ăf˘± l6`s·µJ'7F«{÷éű®EčJrMAŃ_źÓkH5‚6B”Őë‚˙y÷Žăí[SžKpÄD Éürî˝çÜű?ç85NXţ¶ž™‡ř‚JŽ„şćŹ6Ş;vö5dă Ĺ I-ő*Tü0'x7€“ÜĘG×p7ź<[Ě/1€śÄ1đCrŇ\Âf&Eˇâôšqa‰Ň`Ćbm-=kPMxб‚žµ¦h‚+–éśUVSŘR_íÎÎBi¸«‡»'Í…»Ć^ăT¨KKÔ&Zýj‡đ5Ć–*½Ť±I)*pZ‚ítź’n«©ëŹĘňyů"M®˘pýś–©‘łE™* úb:"6š’%’÷ě14c“Q±că8„ÜÍîB%| ´Ž1aÖ‚ĺ*ăTđ" ÇGRů‚¦RůşŇrEśĘsÓĽvۦ·M@Ł_;KČĺĆĆŘ‹¦ĆŘףđŃÖ+ćÂał%BÁ­ű^ÜűÄĹmÚŰ˝rąÔ‘c7\aÄjX˘&ţćn ňf±Fó06SŢ&멉CA¨›Ń«şJĚęÝVÓďŁV\SŽáóŤG×”&nw±Nńď÷ÂčšňT[‹–ÇOnä Ű\\ń1šśh„ím MPㄆ>.ÍkÔDĐ‹;ń§Oq+Nż6s»‹č­»făč×& ­-O´íŹ!Q6ˇh“q€3ĂŤW¨„&¦dµÜZŞ€1|î-c~©7J7iM?ÁDIű-ŻĄď~`“‹ˇî÷z…ä…Wá=ř15.Á $ =íUÜ ÇĐ`&óÜ„ýÚË•if±X32úPŢĆÔíh˘PjúIĺmŃÎ =b˙lhU†NîâŢ(Ú^#µ‰\§¬ůä żđŠ˝MO-Ď€|;uÉٱcŽI}ö«Ň«†Łyăţm7ţ‹ô1Z’ÇÓ3B­¦(Źé3)GÇŽ9»tĘ:ŔQZ7`“Ô˛Ľ [{řđámXbbâşuëöMť7đ¬›r#l0ő¸ŰÚóŁ.¬8uęľ}ëÖ%ÂđÂVŔ.S.ö·ŕ1î­ę¶k°ÍÂN˝:EvčСYł]y3$Űđ6Ą”ŚŠÂÎ~ťuĺʬYłđ­N Ű,íÖĺđ·p Öř2\ľ,_ľ–® ™öÉVżţ»úŻ_r¸«ÔŹ…-**)‰BZµ.Çáî˝77zׇ•1ÚúÚË|ú˘#ΤłĺľĽľz}üĐY5HŔŞ‘f7µRl#sX¬ĺ^IÝGŚ M‘…“ĹŘŇÜ{]ýt5iŐő¶~…¦a‰Y‡>9_űňút)ĽŤ4ZÖß‚ ÔĘ]MŇn8ry˝^H<|†ĽS]Ö¨µ„Żľĺú‡¤:˘§ż@áYHgC2U`cÓq#¤mę™Ařv5 ÂŻßCÖ ?ý~oÜIRcń±ŞN1>F(Ć™Űü‡Ĺ»eĘîĚ۸Ü*°Ąl˝ËÜřÜBý*…đ-É—ËÓ¬ĄÎšyüq>€Sżµµ; ^¶ŰĄrvâÚ„ŕŢĆk”ĘoWhĺ·)/ÔĎŰ­% *öµĹO}^OłL#p#,ŞO†š¬DM[ŢŽúÂ+XÍ × ýVé›OKTÉ ·ľI{ý™ŕ^ŕÄFńGž\ľqJw¸(Ľ\}xźîçŘ{ćĐ^…ÚŰ/RŁHüͧ:µ•bĂSŕ&,O•ĚčŹZ6ó‚›BlTBxš×¨›-‡®<2ôŢ&+IKÁvÚz5ŘřEk“nĹÔôÖŇăĹ:UŠ­Ýž×A±MĘÉ…Wáx›ÔĹv÷óöÚV«BlWGx~­qŰÔ]É2ĺů:GîĂfź„zµđ˝Ťgáü[:*€QŮU [jK]¬D¦f×2“˙¤Rw«ř…ÄâA±eá ˘đö¶ŕ‹”Š^R‹_O}X˘LÍh8âTž –4Ţŕ ˛HÍxŽĆ–‚±,P…AŔµ Ě‚[3/T ęňř¦ yBxަ@4@FŁRRPi.¤CČ×2¶–JˇŽľqšÓx˝iČLQÔj6¸.%Ř8×$ĎŐß|*YZ!¶\ˇ±ůUbäąÚéä_Ś“z-ÜÍŰÂÍIé(-DŘ$4şÓ(ďł–VąH3…4Ż’‹ö˝ ŘŇľ'°•âř#ĽśT®Ň—Ż­_ż~ľĽś„Í Ýí]ůz E6…)ÂÉŕGţĘ“KÝ‘€ö»ď\Ć_ë0|áů¨űÎş^íÇV×eiŹËđű÷Ż_żľŹlš°©ÉV®„Ä­¤şaś7d˛W]R¸čđóŁ–®„ „M•ßlŮşë×ďß~¦u:24 ±ą  P‡»śµ4ľQ㪰±±ńmGôüÄž¨ąR„­ŕS_3άŚ¦q#”ÝA¸Ű÷Ťč¤™°ń±b’6†·7©@ť|\™]<'_S†,!J›¬Ü*_§.göÁ{UÓ¦­mÚôčŃĽy˙ţ“''$ÄĆŹĽ4ýüŞ’ “«¤8¤ )ĂźQRjQ¶F!ťĐ ^ŠOH¨:ąôĎéѦŤŁUÓ¦˝Şô;8łc—N…¸đ*dy‚î í´Đ 0'šuő,k·G»*Ur˘«QŚĂ]ˇI㪠Ú.ŰcUĘ—łäIÍHÎżWe*_´ëŁép4š­Ţ¤‚ŰS«†Óé¬äÂě˛ĺó–*^,K{P“·Fµ…ánRuŠvzôźŁTŢĽĺË—µŮěŃŃ.gŤw…FU«¶ĄáB [źFQ2OÇ>'ÜŻ%ů“ăÔĹş¤?µx”§5qÇÔĐxŮleËSsµR9- µ·CĄľü…my h2†śÔáNŇłUqUŞĺhŇ|ň„1uÄ \ištŇë# cŁßxüuU^·Ńpá­#ăăáiîZNW#\°â˛ůf> F gĐI@¤P€,§Ŕ—ä*Ĺ8z¬ž3Św7%Ń۸M~\ş19q+îÝKi•:+Üůćr5ÔŹF۰( ń"bčşIĐŚeˇk—Ůß„D ä¨ű%ŕiř€®T^M 2 Ó…˛)ô·ÔĺžZ|›ĽţŔŐ8ađCú\…ľfÁ°“DÍŤJČň`¦9ńŇú| Hgt’#XřĺVäp9(µ=}eóS-0q6ť2gş+~»JęîÜëőăŐŞ.˝řY”>1ُoř®ńе9„ĐÚşńĘO2-0“z#µ»°sÇíězŘîDQ••»Ő>ü!‘ĎË•Łäó˛2^<kîňXźÚIť%Um˘ď7YОŔHĎܬ<ĐIZj©N’t•Ž("RËMzéńĎb ©Ď5Wýrµ/DŤ´§´‘e”Ľô=úőĂO"ź0Áă=)~ÎYĽ'ć>á¦Řjť.0 ]µ·UěĽmY˙梥ʴű \r^‘EZŃÁĺ},±×ŹČ µöTĎ\ëÇâד ű.ŹmîĆüď»ŮŻdăGôŁtÂ/bp:xR›*¸őěç>Űšb^ĺl*Ť#݉;Ű6ŻP+ZÎp"hß‘ýtŽN¸EWr‡cĂ„ú,dúĄúT™z¬o»šF»6@uę˘ę4˘¦«{Ń>Ř"–âä,Ţîä I!*w-ݢô?Ү؂›ŹzŘd÷'P 0Kf<•.ŇyMĽÉéÉńśĂB¨÷ësôâÔŮţ oł`BŰîö>Ĺą!3ccffćń‡Îűřf ĂHűŘwśť‘úßhĹžfřÍ%Ť±±ĺŐfH¨1(v±Ŕ§Éa‘ĆiŠ:n41¸˘ŚKřű˝ÍR-wű‹Ť-kźOŢäďm~Ňf ä7ˇć˘¶xe]wË–íoź—`)ÜyŃ2Šs±±ÉNcLÍČM‘Ĺd–ÇBŢ5ěFN˙wcł`ŘŐ‰ŘçĘ–á™CŚR?{_7kąř¤~5¶ŚiQňďĆV¸]×3±Ť(ÎoRňÝ|Sďoŕ†íŤfT÷۱eLťżx`˛˘]Ď, ÄąÜBńĎ čgŃTteĄv‚ßÚ;ż—&Ă(Ž—Dµ‘0Š’˘5uÎ!ţŔ(ţh˛p™ˇljH2ŘvaR.P/¶Č‹R(±鼰‹”ÔđÂŻĽČ›~`tëu×ý Ń÷<Ďăζw•i˝ľ3żváĚ«ßóśóřľç×g±—ÔëÁ¨żĘă˙8…tPtŔŹŤąă@%Í”ű–oź+‚ß>űÜŁşÍÚ3;j•%xŘž|°é¶džýĆĎS)ťb˝÷HłÝnŔ~X}‡(Ř(‰&ę\˝–ů9ôJ§ĄŐo>#/Xڇ Ń>ňšěŠ×tč…Ť9đmÜŐ®×ĂvŁ­uµµŐn˘V,’hJ:ĐËmÚů•ЦW‚ŰÚ ĹhK„MD­APăŰÁ1ÝÍĆŮTúŤą5=]3[Śä6SnçÄFËv„ň«ăúzM[˝79ýćŃÜ÷™Ť´ Ýöpkî&žf˘¦łŮ´ÜäÔ¨|Ô!U÷ľŽ„-v‹°™¬¸,Î=jwe ¦˛¨ÎŘ4·z‘ŕ·ĐŤ×ËᣆŔfÍéť|ßÝOw’Ľ|EM•úźl™ý&ĆhźŁˇź®§ă>·Ó‰Ě°ŹŘrs±ch)ÖÝZć„¶˘í‘×\°él6¶[ę&SĹ-X|Ç»ůa$Ě“ĘtĹĆńiëí[ś« ”Éńę…—™š´šţfcr)ܶ§0ÖÖßš~l¦méćýÁ– jbÝ4˝łx$µS)Ôô÷‡iŞß‰ˇ8äzůux¬Ů‰âWlx9¦Ü60ąřâf{Y%4Ż@>Ąl°˙^SvÓrŁ@ …`8_j8ý±áµ‘ž‡ Č"@‹D •7*xMú-•%5ďľĘ5úlíqł“–Íë‡Í–c*/÷><ă>jGtz`3YM=ť“[±î~Ę XO}§«ëCw9·ťS„ÜHج¶\`ËíőoĹîb˙¨K¬?cU 5Š×Ňßě’s´O ĂáĹAGu›kôő8冫H@÷ϰĺÚl&ëµr[gßÖj¤Ą?PŮ„S-mý1Î5¶[ň)óÇqgyGźMßs›-ĐżĂf‚l=ťţĄŐHM @ŁĹx?k$CxígďŐFzĹ·É5:Dgś]\Ě˙›Ť®VOçäblަUTĹd5:Őo|ç\ÓÎ ?®Ą/ťpÔ =4e›ŻÇ—}n‹•±Ű;6ë‰*­ÜäťXÜ´´Đ›QAĐR¬F^3Îą¦ń›Ćp˛i ˝G×ăѵű«Ům&*Óü ós7‘=á4¬Ű—Ż—*«îXűن…Ä 'f§ž¸ęşz×őÍő'DÎrőŞÓŽţxh/ŘlMkůµkN˙ÂÇX„ÖŕT6ÔWČ׿ź“ćřëTKößPĄá8ůRyĐQ µuŤ>ŹNűĆ:šŹZěv»0žywŘLVRŽçVçěäŇü‹©š@WĄ‹šó´ÂË">Ůj†ôÇ©ä¦W@Éťn޲wńgt̡vĘ2ô'Řr!*k­Íى­Őąî”ie•ظä(݆¦â“O5k? Tµ %\I1˛Á:‡éfÂÍGťXÂ×±ŘŔH”dJř\ĺĚĆO/lömqő‚ł˝ i JD'‚¦Ýła8§i•#•Ď8:äµu”ďâëO¦—ďŻ v¸›É’P˛Űd4â_˛rlžžŢ·ţ‰ĄůŤ’@g˝2‚Ćń™P:MűŞ´ś´®Ô ŤÎĘŃřPěŢ®Ś…Ýf»RŢج¤FáŻkĺŐFÄú&–>ÂeÝ蚢^Př¬–)hO34(á4#Ćg‚›r\Ú5ő±JI^äBWt›ńőč“ńéĺá·ľ•™ÁÁp‡Űínn¦8…۬pšÍăńôôÜęč|0ëď›\XZś˛©–Öö.Ż×‹öŮŠZd0“Ă­e"ŕCm˙˙¶¶ Ç pĘq¨ăTł–ô\qeŻ·««˝µ%>$č­-ŹÜ÷ůVVfffĂßŔęÁĂY?pM^±9x¬ż]¦‹ŠŤ:ŠÍ"É ŃÉ­y2>Ť~ŞĄ*=§ŞNA5_‘+ –8ĐHŢV×U˘‹üÝčf<ţüůúúłh4úzőĺÓüüűŐŐŤXěĹ\$25EÚ×qs˘íú* Íŕ3ĹŚĆĎ˱ý©™ k¨É»–fëČÁrč꽄c®Pt’çĺQ>ZđŰH^á˝@{k ©…DČ^^Ą¦&ęjG`:2śgÄLŤJŤÓŽűXËě7nŰ:‘°Čq'9ÚČÉvđť_ĐÔĐP Q *ąä· ’W]-<&[´ €¬ZAݍ5l4@3ŕ tçŽcrl9ŮZyń‚4ťX°#xD    X±­:šŕp”‰XŤš2a3Ĺ,a´l ĎĚ·†äćTŃ[É­ä@Gěx‚TZBrHá»ŇŇ`0¸Čaůb€XlŘ ‘˛Ŕ…{Ě ]ŞýöÚ µďIˇ†^° ŐüüAUw—äŹt™˛™lŃKÝęeřRm'×¶śň›N¬a@ôŔÓTX…jăŽö”}7|¤e±Ő’BUÓÖË ˝ÜG~^Đ>đÁ$áă%ŕĽÎKŹ%#Kg–ĺVË”4­äÂu "ȰČ_ L¶h2NZ–&‚ĚŽÓkz>ŃSřÁ$+…‹[ÚY:´#Yť™cU‹NIÁ“:©‘ü9ńRŔ#3îźÔö¬iáŞa t&!ń‘q1˛$—¤ŕĚ¬ŚŽŮ)ľ2Jý—•ŢŃ~ŕ˘3:ŁË JóS-0C=úüâ&|-ą_üE7{–]Ö÷h9Ťív(v` ´]¦ÖN€ýGůËÁřXZZZ`ŻÔŘŤóöKčŔf{ŤŘ„´îJţĹ˙34w"vÚ“&u¨CęPYŞźŤő–ďćIEND®B`‚aio-pika-8.2.5/docs/source/_static/favicon/ms-icon-70x70.png000066400000000000000000000052371433544061600233760ustar00rootroot00000000000000‰PNG  IHDRFFFđ¶ýPLTE˙˙˙¬r+W!ýýýúúúüüü÷÷÷«q(óóóůůů‚VďďđéĚ©‚Uđńń§o*Z"ńńňîîďöööőőőëëë;'ííîââă¬s-­s+ççčććć„Y$éęęĺĺĺX:čééŕŕáÜÝÝŰŰŰ„W!ěěíŇŇŇäääŐŐÖÓÔÔĐŃŇę́ČČČëÍŞŚf7­w5¬u1ááâ×ŘŮ×××ĹŔąâČŞoF±~A`/‡^+†\(ßßßÝŢŢŮŮÚĚĚÍĆĆÇÄÂŔÂŔ˝äˮ¹­ç̬ćËŞłS®|>®y8…['€U ~T ÚÚŰŮŘŘÖÓŃĎĎĐÎÎνľżż»·ş¶±ż± Ş›Šş ¸’dź„b˛ŠZ•uMĄm(nIâăäÖÖ×××Ö×ŇĚŮŇÉÉĘÉÎËČĺ×ÇÍĘÇŇĚĹĐĘĂÂĂĂŐÉş»©’łˇ‹˘’¤w—{X®†T±„L“rI°€FŤj>‹g:‹d4®v3śg&zQ[<M3čçääâŕâŕÝŢŢÝßÝÚŰŘÖŢŮÓčŰÍËËËĐÍĘÔÎÇĘÇÄŢŃÂČĹÁÍČŔËĆŔĘĂ»ËÁ¶»¸´ćĎłÚDZɾ°˛°­čͬ¸±¨Č¶ µ¬ »®ź±§›Ä±˝­±Ą—ÚąŹµŁŹ»¤ŠĄ™Š°ś…˝śuşnµ•młŹb´Ź`ś€]•xSyQ¶†K®I©q,kGbAF.ăŢ×Ů×ŇŰÔĚŢÓĹÍÄąçҸâη···Â»˛Č»¬ŇŔŞşł©˝˛ĄŻŞ¤Íş˘·¨—Ąž•ÇŻ’­ Ň°…ą˘…¬™€©’t Śr˝jž‡j›‚c–~aŔ”^”z\™{X·ŚV }S«}ElB±{:Ž^#qKgD?)ďěéčâŰęâ×çŢÔâŰÓĺÚÍÓĐÍÜνÉÄ˝¸ş»ćÍ®ş´¬ĂµĄĂ˛ťăĂśßŔ›§ťÄ«ŤŔ§śŽ}Ť|·›y˛x¶—r¦Ťo†oĘžiŞŚf«^©‡]p?µ€>“i6Ťb.Š^) j(“a%tMsLV9j›ęV]IDATXĂÍe”Ú@Ç ´XĹ)rôÚžÔÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝĺuwŻ%4i?őu>Ü˱»ż73űźaB’˙ްD“^ů3ÇišĆ đ±BŔ®ü™‚“”–a EâaŰ1‚ÖŁ­®HS’b»­jܸO«—" Ś[ÁőZcŹÆőďaŃQ4!Á{uÝ·U̶Ăéăk9=Ž…V(vĺ´…í3gN3rM‹†”ŕ`„ž­}Ŕ¬SČ Śr±hw"Ĺ8¬Š\­”Ë•jŰü!F‚‘şUÇ2nŚcH Kä{GĄQËšşĘ‡ÇDCŇv;(!N«† Gš©ŐPBśJ%śs†ÝÔAĆ· –„› s¦RÎł,\ -‚!(Ç,ä çN]+pşŮ :Ăw§†‘ÄÄbę˝b8K›7N1´nM&9ß”§ę[ „FS;Ł,ĚĚ“Ý:`Hc9›<ĚÚ§ó?Źa"1˛=ńŔw ¤¦©2cŇŠbVFĄxíÔ ĺß§ŽjIÔMÖŮą%A @Ň\•Â1ęůĄBô¦&™Ă1ęű)đ= Ă1¶ň')Ş{Ă_ɉ5#Ěbä;ΔȠD§ ˙JMYŹŽĂĐşbQTć·ĺ÷ž„Óą48řĽAi÷xĆQŠ©ELś0Ă\WGQ)Nn˝Yş)|*”7¤w(”Ť­ŮúŇŁŰ*QLŃ=!ZšúĨ-×ÇäČ[ĆV‡ Ťył€Ă™¶ćĚѨ H’2MY cŤĘîŞŕí’5†´Oć&ŮY×Ű'ŘŔávĂ5ŇÁčԇ넩F•śYOăű€Ŕ= F78§ÁF“!w űvŚi—TLč®ú·R@˝ôMđÍOv=NY÷Ş r˘ˇ~”mËŔ‘j˘L¶Y(Ş >ßl™´hĘôbĹDŁ”sHa€7¦•›žŁd™WĘJ‘š¬Č‰ý1Á¦)ał™Xõ'•±DxĽr®ÓŃťĹÄ1”·8qłś1Ç ®u. i θÉ `¶älb†2Î]Üj7Ő…"¶Íh4uťć%’1ˇviYSrž•Ţ “46ĆWÓW¦Ŕ~Pş9¤d/ě|Â~\Ľ˘ş1z"LRlËĘ•O[Ő—yÇh蔺ENl‰Ň†kM¨ b÷˝ŽEŐ©¨šLŤĘ»đŃ—…Q!ąţ)qWÖÁPż˛PËHš,ÔäPËe¸{’˛58•ąLy¸t‚QO`C üKę\ŁZĘÄ1ň ă}-Ť6Ě#ŔŚ Ó¸Ś )Ś2Í´ )t‹G´JOęÓδ"ÄpśĚ3kZ´$i 4ZŔń)šK}şűť^{ÜNđő+‰Ő03Áa4Zşőąl¤hŚź×®kU#–ŹWH&Žáü™â‹ďµz„jDO#ś¸vuUĄJĄĘÓ8oĹź1°UlnśP­®ŰWkÁg€t„OI‘" “’Ďy“í_םˇyްWÖĎSO=ÍŁTíX•Ź)Ú©óqjj•*˙ČÚ¨™rą±o02ęW ΕĎŐ:/í"„鴬󲢥L߀Ű!řE@D±–8O Ô€ty·”?~s™\} n““ádű— †uZMYÝžbĹ<žl.“Ő®Ł¸Öű× §áĐŔčXV§Őc i\D¶Ň$FÓ¨x ĆźY!Kňě\†N4ÝIEND®B`‚aio-pika-8.2.5/docs/source/_static/icon.png000066400000000000000000000035301433544061600205430ustar00rootroot00000000000000‰PNG  IHDR@@ť·ě PLTEgD7%2" 5$ mHkG9&) ;'aAR62! 5$C,+ &jG9&cAiDG/N52! O4\<5"O6P72#   1 <(W!¬r+:'V 9&7%6$ 4# €U }SX;2! éĚ©ťh(T8uNzQsL0 |S{RqKY;ÍŻŠmHiEV9ĂŁĄ‚XQ6N3B,ÝŔťË¬‡˘UP=&yPxPwOpJkG+ Ǩ‚©a‚mS«r*>)±—y˛‘i®ŚdĄ„]Şp*Ąm)~W'™e&’a%‰[#cA[=O5K2H0ΰ‹Ĺ¦Â˘}˝žx“oBŤkB†e;~Y+¨o)ˇk(‚Y'`?F// ŕĂ Ó·•Á˘{¸™t¦‡`¨…ZŚvZťY“sL zKšvI‚`6Źf5_4‚]-zU(zS#S7Ů˝™˝Ł„«‰^ž|TŽh:Šf9†c8†`3\I1–d&uP$Ź^#J9"A/äǤܺӵвŤ˝šnł”n̢mÄ•[}iP†mN~iN¸„Ed@lW=…c9fR9|Z1UA*cI)Q6zµ`f-tRNSěş2 Ĺłç)í &ÝN.öŕ®¬Ł›j=ëăßŘÓÇŔµś”ކ`NE9ůµ0ĐIDATXĂí×ĺ[Ű@đ•mĚÝÝ}$—“¤é¨ …R¤ +2`8+0ww—˙qw•ôş’.{»gß%˝ć÷éYîˇÓţ‡KQ‘ÉxŠňëîY=ĂpVŻ]`úíë×-)áđďYÇĆĄ‹s€ýËůr{1Z#A„ O,ĎćđßPçQ;zH{%ť’ Č]ńÂ’bn"–ćÔŰĹdŽu§ @‰UHƇ!/¬]ťť\; é  ,ŇńcXµ@L«řHb&fĚ€!“°m}‘Ěć|X˘„°GĐ‚Ě*ţ# H{TĘŔ k@»Ę€“Z}X5ŇäŃ€ă*—i@ş@6c˝źlĹa€[\˝ 0"PĹ+ékH®_<›Š ü*AúÚič.ěq™>Ŕq«(†$µ `}wTŇ× »Řü9(ÓI ŇŇGLěd@lřч˘č#őR@eăđÓč| ˇ{IŽwbŘAíĽ‚„00ěŁĎ˘jŁ;©ÓĄŰŔŹ BLE†LĐťp ůD 42ŰŚ=tF{€ÔǬX’Čč_¸«5ˇ|˘źŔ–aB7źŮoÍÖŰFUB0ÔŘ?7´ęMâ[ · šK[¦ĚŽuHş[§Ě Śž‰#ůJhD5~&B¬ž<ś¸Ě*ˇý˙3}&ÔĘH{K töŞÚäŔVTĹÁódv¨Ú ĚŮjÎŹżt2ĐÝ]YŮ”úý~úţ·l္źŠćäć“$I–Y–éĹů[Î#wd!'óMHěîmJň"Ó"ł&Wů=…6IÔËóLyŔĺjoÝuůr…s,yÁęű›ëúî*55¶ň{®¦[Jť·ď¶˘śpű.5…ję#Ő±KgÂlŢC±/}ÖŠCÖňˇsneÜ}1!écgŽT—Űkę­§bΉ*÷ů¦ó.ď7›í†ť§/ú©ą®®ęš¬HŐW}CGÜ5őö*·|âFôÎŕ]›÷Ş˘TEp±oXľ2óU_KŤalNrő`ŰXĎžkkj˛+N§â=ŰVŃďfŔŐˇ—ׯô?şň, lĘ®‚iGş÷Î^´=j›h.?çv2Ó{iŕő#mŞŐGęűkn§†°2»ŠŠ])A±Ú#aEqĘrČnU¤$0n·»$ÚäR$ŦĐö39‰®} §iYĽ;°ÍŁ]Ąţ4Źk źs‡´¦Ř”Šěr SF˛±Žäǵfí/¬[ąbşá¬Řľw«ç…Ĺëç-›i4ËiżÚ8Âô7™öĺAZ[ăBQł|IEND®B`‚aio-pika-8.2.5/docs/source/_static/logo.png000066400000000000000000000233771433544061600205660ustar00rootroot00000000000000‰PNG  IHDR,,NŁ~GýPLTEJ0, - eCbA& _?1! . bAdB- + $ !Q6pJ8%I0X:\=I0W9( [<P5D-<'M3B+5#H1;'5$6$5$5%<(W!¬r+éĚ©9&‚V!4# 8%6$ 2! 7$U 3" 0 {R/ T . }S~T , yPY;jFvNtMa@oJ]>sLnIˇk)xOdB_?wOU8qKlH) * kGfDhE\=rKW:cBbAĚ­Z<¬ŠazUÚľśN4čʧąpł•qgEϰŠL2S7׹•Ó´Ťž€\u`H?*' =)ϱŚÂ¤€Q6A+R6ĺǤD-áĂ Ä©‰Şp*gDćȦ¨o)‰[#J1ŰľšÓ¶’ƨ…Ąm)†Y!F/ŕ »šquU.«r+›f&ăެŚf¤‚WŤ]#ŮĽ™ŁR†gB‹d5”b%lL%H0ßÁťż {ŚmG{[5\+źi'—d%Ƨ€•vPj;_$ĚŻŚ±ŤbŹpJšuGpZAiU;|W*qP(W%É«‡§‡`›|UwY4ťh'ŰĽ”¸šv®Źj VmTťxJr^G•o@c=}`<˝ťvŞ’u°ŹgN<&P5·ť~„^1_.aBĘ«…ž†jžzM•qF]I1dEĽ˘‚ĹŁ{Ęžh¤…^š}Z©T“sLybH‰kE´~&p…fŐpSÚ%ą$¦š‹Ö~ţl]§Ţ‚WÚ¤J"ík4)´´:Ĺ–U˘őŽJÉ Ş…hB-Ťg-:«ŰY8ą®ßEMŤ®S—ÂŇ:ś¸%…‘—[7SĘűg)Ă˙Ť@‹ÖŰ[%ŞprvfŞ“Ł›á~Dły…§Őr†Ń]žĚŞ#LżÉ©LzIü-šKJŤ®Ű€ŤŐú9“ăąĺűWvwÖÖvľËąŮ”f§ČůѢ- őełŮ·ď^O~Őy4Ž+<­GŤŮQes?%Ń„łăÉ^ě*ĺZ.N©Çĺ&4Z7ʦ‡ĺr=߸BŇş~˘ůś E+¬űI8;^ŰĹ%éU®KÓW Ľľ>$cő.ęv´đ´î^:ĆŚX «łä;În>˝ˇřęŐ€ć&ČiŚ–ĎÉţę]eŐĹ‹+$­WÎTeÄ–ČnpM©¨­Ľ ,ÚHZ®¨/Ý ZuşŮĐ\Çz<ą°Î g—VöŇÚ˘Č/@´îçň^ě«ÓĂ+lÚ:B‡uOŞVNżR»¶”˝µ4`h:ÎĎź•Ô'ď­'ëűA«ĺ*/-ňZ¦]˘łJBVXź0-˙hVzŰĐBÓj»|é,ëZDXWIó(ÎN-b8ÖT)ż˝Łu÷ËVş`đÚęŃhjmh®§ÂJT}Ńá¶a¬ú” ZŢćůÁbŔäsrőtÁBĄëć‰Ćs‰Ă´Âj",üpzŞ:»ˇŇf ç‡ÉOĘÁô޵uX×É>dK÷jo:nĂÚQę-ĘV>-Ô‚-LiÝąÁ|H„uŤ–ŢŻŐŇşˇŇ‚…Ąçä Ę¦Ś°Ąu›ęĂÉă„𸰠ËJ`MPZ¨°¶ĺŕš]Z-ŘOu¤‰2ľăÂz«´í•VĄÂ"ŔJ«aKë­ińţ~—w¬5 ¬–—)áĄć@l?Úp¬ž«ľ…Ą7 P´¦‚Ňe:H‚Őo†,­ëGŻ6a'‘÷MQ!iŇ+_ňú°L’dhá`]c7,ÂŢĄ—Z¦Á-Ţ'š$Ó4kG«Öék'ë˙ ÖŚBÓ®é_ Ž a} éĂÓ'˙!,UM„µÄa OaőţsXuŐÂâéő)D„spËzN„ŐiZ„`©°FD1 XÚ°LT’źµË5Î VN4-äiµ“ kŢŚ,Ű8ß©°žf,X¦™ŞĽmęŔŇȰ¶2˘`™®pXäžő##ňe:B†Ő%XzŹBžLĂ˝"ňdXzT`ŮÎů P5/ŽCë•UO&* ކµH†5#:<8 µŹTXď,5°Ü,ąD…5!`XZ– ëY&:C)«úqřľ",•~F –Şĺ¨°Dź°ć©¬˛™Ý ySަÂZA°x˙ă'ë:ÖkË=Y#K7v¨KË;î!÷źTX3€;VíŔ˛˝cP'­5·0¬gÔťÎ[V$`y~V!JŢń` PËľ”GKQ{E„•‡]ŮĂNÝfM{5X‡ô©ΤThYµ‹7-şń$éęˇÁĘYv hŔú—zć S+řÁx%Xťřî7™OI }{¸0b°*řđ[rnnnttn˝±~ ÝăÝś|ć(btl¤Óß…AVµË©kÓäir­ÂŰcĂňß“ŽĎy1f÷paD`9ŤŮ߇«,ϱ1˲ĆF_â]©¨1ŔkpOú4ă…}WÁ…Ĺ}ř›ł˛%aŃ©Ôë} €Ćb /śÓZ@Ö,LJ«V˙K4c˲6Á·Ą`<ň`Mëś•B•‘G]F–—¦± a=·Y™\ ž)Ç?Ö'€#Y"`­s䄥C¨˛8,Óŕ23ĐĄPîLš8LË ÁhĄđv&° k–ăĂÂěYŁv˘š¦±\ÇÁĂźţ°úÁŚ5ć…`ĽQĎzăş0B°J>DűŇ$ĎTă2ĚđlÂň]*ŤYś•ËíHą # K7ĐÇ4źmXş®ł\żÂ†ć K“§Ał#O«a+ü+‡ÚĺTzo±âÁŇ5C…źĂ‹©´,†xL–ă–ŃC“ʬ’‹đf¨ X&¸ńČľ° i^ĚB¦„‹zŔŁŚ¬ Dó`avŞž #«ÂęáŤčĎCx„ǰ^?Vý]Ëú.(.¬XŢę˝Ű'˙á…‰* đ}%XĆ6zć¸0˘°x‹‡›Ťů ź)9,¸xžf…`˝„Żn2¦űs8‘¦MĎ…„ĺřp]úâÎđx*ť*‡•taőřͤL†úŔúTĹU§F`•|¸ ëJwĹ-|RŔNdJ°fdźˇBŹ0,îĂç jM¦{•łň®4• X݀ȂĺýľN챪X8Ô,§4ćáęao lX?~°Öy€dŐŮ9F* žL¸.Ś$¬Ň¨µ…ľµőVZßý>¤I•ĂŇÖŃZÁýń˘ 4ĎÇ´ĂŇŤ7řÝłirVfv3/]k éň6đ=u?/,ş kV…+ď.&‡ő‹»;{qăţďµé·iŤQ»ő^mńFPÁw Š'ŐQańA<ŢÖQDTATQśÉ7“©“¬ł¦3ŮďŁ ¦~3ΕĚBáłG Vô/Gf;6°ěn^Čž`Á!ŁX3Nyzf°†ľVśďâ`ÁŞře)Îv‚Eła†©ÎĽbf±ĐĹ?‰,=ŕ;â~^đ‰–oE …ŮÎęË%ťę肵ÎG—ňť1qPáÚ(-š*u¶ÖŠ‹ĘŘÚ‰,8\FÎ8VÜ.Ďą"6ţ.‹=?†Jĺ*M™ZŮÂlg]°g¸Rܦ§:ĆB;‡ZëýźěćÄSG%—:˙s„ŮN XćČ˙ŰŐŃkơÖylŔŰâĘŰŤ ˇĂVGŘ®ĂÖýöČ ‹¶Â9UłZ†3ţoîść;Á¬Iăď˘ÖÄU`ŤFY¬f í02ÔZŤs0Â$»Ţ&fµ;~r÷ÁjŮv\·(N–ŰT«Zúź,]°fjťrÚťN§-®ÂON÷0<ĹŘ©ľT¬ĂíąŇg±-ŠŐH«dR,źőqòěâ锚á‚Ý:t@ieynČ kÓňôĆY‹ĘN°ťĺ%šŔŞlVPV)aĺçfĄ•ÝeefU»|ŢH )mµV&¶Âđ±V[{FŠXȢš ˙d`Ĺ]3X ­vž3RĹB–-ńÁ4Á*Ć„[-6Ś”±z—k‘Ä<•Xpa­Ę Ś!`Ak<Ôâůăą”c…·3"áU`5b  Y-pPx(vťźb¬Ŕĺé×OěŽG€áJµZÝ2żk±ËÚ¨*ÍíŁ/Ş—r,ňPď …C”‹`Ř~_c Y8Zx\Ayűhˇđ4¬7•XÁµ|/ ź w<"ÄŞi  ŮhZx@Ő(Í śdR‹…ľé}9ôü“iâaé[ŰŹ  9n[ě Hň”ß!¦‹Ź;ia1®Ď–‰Xą«ĆP±=áţ?ŁBiAK%î.Aaq.ú´N*°FĆqbŐËSľ,ˇ …%půG¨ %Xů%6=ďƨxiŃe őX(,Ëíöaĺ7t\N%––2,tY(¬פˇ «*1/tŔBa‰ůh¨ŔBnâňT,–ďÔ`!×cµ^h€őýŃŽ¸ÂR‰ő+扶Ý+Kń˙†ßě–*,ä•Hµ÷Ţ<ł¦VŮ|,T×GY”©Óçd±‹×KrXÄŞ*[:ŚłŘîÍź\ߤ¨®®ńHVNJa-Řé‘´6Ęqí*đ\Ľ‡Ý°*Áńţ>®»RVçWyHC«ľÁCÖKi˝éŁŞ9ÔŞ¦|nŇ ´śëµŚUŻé±ÜU˘°Č¨ Ö‰ !•ëŘý[?Ę׳ Ĺą¤ş÷ĽfĺąAX˝0J’]<¨đҵBa)_)ĹKI´¸\Â%Ů y<Ó°Vz<+¤Ú!Ą˛üwȰ¨ĂJi 6E—p˝–ÂBĂB¦a5=žĄ2XĎ.Ţłńľ†É¬ÔŻÁ‡«đ\«=%…µŮăą6k‹Çł$/“îD`U ö0tŘ݉ђ;ąÄă95«żÍΗÂÚ>Ńv]›o`č±oŃ:hHeŞŻVŚAXu/ĚćĽTšAaÁJ—MV`…Z6{?Uľ´V-ĹKkC]k”`…}»6;ŇĽ´đ˘®˝V«hmž6c•­ÖÂĽ\m7‹j„Ud•5Xň«S‹77מîÉÍ ®_ŐÚ2:’—Ť…V¨V`Ĺ;­D"ÖěS +‹h.M¶ďY]Ë™Ňë`€…CŔĄ RŚ ˙^Óë0ÎĺB‹W—:¬°¨@E¬čyw=°Îđń{ŕ.UXĽýŠÔ•­Z”‹yAK ¬Xű#RtÝÁq;ş4C‚E´(Ľ0:U‰u{[$ďôŔş˛MĚÓ*Ƨ °°”u«É7=°öÄ%M±RQ‚…S!1X?gĺtęô膱Ý'·Ź­\RšÖ‘([Z: +ëő,¨&[Ý\3ÍFr«;â°ĘUͰ’o°^Ý*ü×'ĆzPba¦¨ «ZŽÁş›ÔęĆá\$^Ҷřć/XEX•y±X;ZMĂJH+!ÖŮ,:ŻÖ +釛r±Yš k_F°’őđSąřěIdől‡fXčłţW§UĎĹçp"¬KÍú,RZŔ“h ż1źn˘.ţ‹†XU†5űv8’‹Ď™D٬ ±Xe`©Á?~K˛ż?÷ďßhŠ3žc™ý‡wîűŮßź··UŤŕĹc¶ŤKôpIăŤ$ZËrq98? V—N’84¶Ť•-µXĐ Ögh|±Ž—ëŘX\Ź5šÄjéD›JQ%ęDB¨¸•Âő¬`ábîśN˘ŐkFë*‘Uţ$îQM ËËŠÖłÄŐ?_Ě7ó—›F’ś+]ţł¬ĆꉬVwp|Jĉ¦Ęvń5X'b C–pmą—ßöó‡W­Č'ËIľüŽTIÔ®Ás-ęEĹhŔe{FŇ›*Ő—5×-›źOšŃŕU“»;|ߡd±[­C}ĄË¬PNHQýľ!ç‚ß>ÜS…Ő·˘i´Ř‘˝ř‘őаV[ĚJŇă#‚«­J°Jăî_Ďč‚ĺ‡i™Vną ¬1ţQ»™ˇtÁę{ëâ‚ĘŃkđˇw]°ú´Ö k‰…CY÷uÁâZöâ!cŐç…V1#XXĽˇÝ–52T¬]ţőż Ý}ϢyzX#ăĽsĎÔ ĺ\khX `E;¬¬]şÍ´¬ú°ćc–SÎäŐČLËŢ8¬Ő•Đ*sW#÷k­:–>Ö˛Zh•Á ls‹iíľš6Ö*Ľ­ «LŢÉƵГ©b5Nş}V˝9“kŮÍ^zXË*Ü*uý‹–ĎuđFJXŤ=.·Ęî炱čĚ´śf/ ,”µŞfűCÔL .{Ľtîc-ÜÝ_VŮţÄ9ĺęúňäĹjü¦înZ›Â8€řDQđčGđę¨ –i×Ín’ÍKyÔ$Ő ˇTCĽČF ‚É)Ąďí©˝‰ "ąyóęEĽú<Ďd;»ÝŤdÍn:ůź:e2›ýÍĚ’Ă3I'ĺ>1ľę_žďyp!Wď42,ţx#í|b[pĹ–AnE±¸k –ŮJ *ରRË}„š>E¤ĆĆŰE±ŞöC˘’gä¤RËŮŠb/×Zůp,ľ–TQ-+U°Îq‰jqîäż°XŮ^w0‰Â¨žVŞ`yąčŮ^ÝĘaH,Ţ´“˘’Č©!ŠtY©‚%´$×Ô+Q«śĚ‰ĹęŮAj*EŹő¨TÁňpą˝ŇÝöđŃ?±xi§7ŘD(GJRElĄ –ä’pNe:1)”Ťáé;ÓŠŰ­Ţh-•P^©¨TÁB.˙QaA“Ídbí`l×F‰ëiJŠśĘ}„76*e°.·­0Y“*CH‚É R±R)„E\ŇK‚!Y`śšPŹTŚTJa!—ô`‚ŚĐddI¨„"©x©Ă‚H/#1$“€Š[Š˘$!ÁdQjP¨$” b—˘„Çşrő^ˇPhíúŇń%»53-Ę–LSsŇŘ]ČKWFىMyCÁ>5ß…łŮv±A°;eŇőĄ6đíܬëá°îÜ_0Śq®›¦Î9cž˙;á]‡>¦AŃbP  tä&Ć…Đßž«D“ýËŔâżűýţÍĐ9»ČčC2BóĆÄččDq^tźáŔŘřŤŘëÚ’±Ó_ĂÁµă\Ćtć“fľpwO@ż‡uŤĐx.®ÖŤ›KĆbŚ„UĎifđ¤3ořڰ‹á&aóÄąń źŠy¸Ę*ci8çźíŠ1ű6X¸Lg±öšőަs]C¬źV˝jD…u°n-KXV1č6Âąga±žs€eÖźí4ÂďC…°Žż}ť{Îg3ůö÷řŁ%°2őj§™_¬Ŕ{޶–Ő(áł×ß7ěŕî3%÷÷9ÖS«äĆbo.>¬żÄšĎkQÇ˙ďőPôX<–°Ëvwł›e@’Ť‰Q X˝ŘC’CăˇÚbJŰjP m´ĄVŠVDlĹ"bAđ"zrf^¶/iLAÉ\Údg_ć}vfŢwŇž<Ţfx+>Ř“,j1ËHSo÷ <űŻÍŔź/ś6,:9X‹łéÜ@a±PJjIĂ0aIŃt]١Î}ągĎŘ;÷o_ĄĂQXś ł`ľÄŔ`‘ę‘›Ť­éé™ĺŞ(¶„bĐ[DŰóQ&XĚ7ÜXž™ŢjČŕ)÷Ý\ü“U—aé:÷Ç‹2,¬Ů°đX,  ý^…OśŮj|‰AŔ˘¸›Ë7ĘyŮřŤw6ĂE×H[°[°0ňęëyć9¶´žxˇűşˇ/pë}Ei>f7äßďyÁĘ‚‡7Ă"<3·Ŕ>Ő‘d¸ńřęŔlré%˙ČÁŔz‰›á–Ł ›)xŞ…˝nŢĎ ÜŢ×=±»ę&č‘®Žs˙מ¬ĚĽn‹‡Ę0„I‡w ·ęFřŇĐaďWŘGţsX'ú‚ú ˛…¨F{ ü ŇÖř˛~µÓsüăŘmYÚňÓÚd»˙’Ž+w*řX”K÷Éő{ .JÍĂÁm¬čDk°°Ő¬ŇŇłĹíJeűú(%Î3’?—<é,Ě3b•ľ¸^™ÝśŔßçW˘Ý':ŕ| ®_- ÂÄÔ«ŐĘěp‰rË Z‚g:K:€ĄŔÄÝ\č’jE±ćŕ÷;›_·×+µ'Ăţ)=şÁ’·FÖľ;Ů™[+ Ŕ®Btż–´4,—0n_Y÷}ÓuÝą{řjÔę:po´{s.­ĽNµh°ĄHÁ;q¸;|+­Hrý=®¬ŁZYňÝ©•d¦ÜÜ^{h‘ŇLf5=#‘*&‹©TŞčdć`¬‰¨B2ÁşNÚ:®—°ň~ĺĚHĆI:ÎÇ«´űîˇgëήîEXą2¨ŐčĐV–’‰Ă˛ĹęćîC7âŕÜ(Ë’śŽ§’Éb!•*$ť Ňz„K„K˛uò,-®&ź@¢¶€RŠşôĚ]nă t8·<Q©Ą±/–´P–Ga)ßV °ƢKŹÇ7ę¦Ň€Eáđdr-Ďř0ÄaQK.SUQ *ôěŘgn“Q»Âz ţX¤Lyéc,iő.°ČĆčěL{ČŠG•¬DÁŤ!,ł~°B!ŞŔňçW«7⺦˙ů¸j”ĂÂÜaŰ+űRkó˛Mo™Pť°05Z°Úý%Ť`aŮŠöX÷Jxë¶™ä2:Ä\pbs±ňŃu1¸ŰËOÓÎmĺ ťLĽӕ!lŢ;€ĄPšICS¶66F+«•a»ż¤P,Ą ¬ýí1ŇX ńxbUóçŁiş‘lĆÉdÝg –aVHBĎ}ßÍ:)5‘PSΰ0Î’Ű‹<Á˛u+ MÜ–ĂaÍwŔŞSďëíö$~Âb§kÖ±h°ë›§ Ş UMVź]-ç‚Ě6ŇŃhÚř.ô€µŚÖť¸ĆaˇŢޤ2ˇOAbBr‚ľ?(Ca®˝ —éťe¸ór ×+}5ř—ďqöʙٍĹŮăů°Nö1Fď`ý,ČlOMh{Vg§-ŰÖŕTVÔŕEô¬Głb:kđtµ˝ÁłBîŃŕ÷Ż ĺj4´Â44<ń#+ŠÁ)šRęÖůţׂUŔv±­*řöR/XFţ˛¬őÜŮ0,Ě‘ ;a.ac“-â­­q‡¤S—â8U|ép{ČŚdkóHk-učŽEĐÁŞť2-ôëÄń`ťíŁeŐ©¸ jçÖ°ř2ß–$zĎh~á˘t‡łĎD)ŞR©ĄIĺ@”ÂĆkD)IßjYfQzŰĎŐÂ:é@‹ţFv ŐŞ‰xĺ0¬Đ„«}ő¬3Ç˙Ç E}‡˘.K–ę„°${e Űíj^„ŤLęh·Â$:7>îŹTśq§;5#~¤ç¸ă$,K­Pź‘dŇ޲Ë`…%é“Đ'¬sÇ˙—Ł‹}Ŕ˛Ůj›ŽBwX"¤Íî­&´‰(ÚX1­4¦mbÚZmJý˙Wô KńT’“×4a7ˇ IŤ‰Z*Š—( —zĐ‚I J#!‹ô  ţŕˇ(ŠAAđ 'gćmşŃv“,űÝ^wúŢě×™·óWF¦51˘Ď٤řY¬PHŻHáńl([O_&nOIZ‰tn&H•'Iî\ń B¸!žřx V˛öë“l8VĂťĺźć |ť:ýň'řŕĹ3‹Čâ%čN“-ÝzţizţŐŹTVţ=,-Y]RČz62SOžÎżş{‹ŢűlÝŇç]˘D‹ LĘł(ů.ÄDřÜťMĚÍ?ĺ'Ţ«1tčétvč$ËÖW=&ő°©rÜĎ=[LVśěŘ÷“ŕU•Š·© \ľ'n}K˙¨RęĂňleÇG(˛'Ŕ±Šóß®(éNe˛"nÓęƆ•zČjµÔrĂÇF^ ¨(ĚRşĂůQJ4s”­#Ž„ĆŐVRqjčfŲňíktc+xţ~8ýWY9Eeĺ…Ápv˛ő˝@=‹$TüBE˛`ŽŮa"+Z­ú·Ía·Ytlµ¶×riyś˝­PKEŁă…Bˇ(É|čm&“)–PÁ Ě›CůŹ7É ‰.ů)¸Á„łH`"ŔD,,ßLĽů:÷5;©(—N§F«ôwb‡:ő} éŇjÜYC& † €¨KňCr0™żř! AHN b GR:Mš_‡”&«&Y'‡S hËŔo€ü¨\¶S~Ţdý÷jRÄăđL)‰ĘĄ%éW.Î˙B‚&z:Ż,ş´ÚwŻĘý)Aą ăEڱpQPHĎ‚ŚńŽą2ÎÁ’9 —žh“‘… ť ¸"/|ΩhťŔSH*"âl’Lça~Ę…˘·’a öXí6}^H~hŢ.ÔŔ(dádô ŕÇ„eˇ_Ŕ(Jk?Ę’°:B˘IDš#,oř·|icŻö)~Î(1€<Š” łČ•«2ľÍĄŰ É-»}µUá˝\ ĐŹĎłó—˘Qľ&Yż:śT™, a ĺňęĆÇ5N)9R•ó-(§M–·§ß®× é{¸ŢĽS¨Š…5Ţ™áP',’%azP™¬ĺQ€䫟Bš¨ň TLuаÚŃ u›VsźŽˇO_ţőPC–ÄŞĄé>:J˙pb W‡űMÎ2'Ôď-văm «1>én"WSS‹ę„úŮ:`ô-Ďăđ`ÂrĂĽĐ·Ă \ŮT'ÔmZ+V5Úšö Ć‚ć_(Č5ʰúş\ýČŐŞhXu°ŐâܲĂ`ăâÁä QaÉŢn—•¸˘ «¶,f§}ó¶ăÂrĹŕž.·ŁŮîl±ÔDZŐ`1ŰšL=˝LXŽďírŻłZm¶4ÖĹÜňë·™ď߻ᄰśŕ۰wc·Ëa5µ9míusEl­l]eé°9ŰLťŽ]ëŽî;˛uëÚ˙[Źě;şq—۵ĆÚŚT™Éë抻"ѵşÉnęl¶ö;k–ţ´K7 @D Ă"y ö®b+5¤˙ćbráŚÂiő_ Ł9ĎĎóń^%NTĽí­ţžËú;Wâpĺ]DV Dö|ÝĄ^äzŞYąŞ/SÚ ĚĽĄHĄ§š™«őrĺ ˘¨ÎWŰR™e2óëekőJÔŞ—šÍ´`бRăŚ" ůďżlì‘IEND®B`‚aio-pika-8.2.5/docs/source/_static/logo2x.png000066400000000000000000000530731433544061600210340ustar00rootroot00000000000000‰PNG  IHDRXX‰¸hîPLTEI1E.?)E.  7$ C-5$ + $,   @+:&T8=(0 ^>tN/ 6$ K2S99&jF<(W!¬r+éĚ©:':&4# 8%‚V 0 6$ 7$€U 3" . ~T 1 }S, {R2! Y;rKzQuNwOgEyPjGmIoJ_?* ) ]>a@tMqJlHbAeCdCcBiF–d%[<' \=Z<ŕ ˝¤„ĺȤ±ŹfÉŻŽćɦW:Ő·“Q5U9>)A+Şp*C-Še6ŇłŽF.µ“lI0{]8č˧´•păŢž†i›}YN4K2Üľš×ş—‡Z"S7¨o)ŢŔśÄŁ{›g&‹\#Ҷ”Ąn)׹”»ťy˘l(˝›qźi'_$Ë«…M3׺•±’ną–lŮ»—ϱŤÉ©‚Žg7ÁŁk=‚a:“a$̭ŧ‚C3«’sˇ_”uP|U%‡gAcF!¤…`˘~RŚmGe&Ç©†oP*& Ď´“ąqyU„`3nL"iI!¤Śo®Źiµ’g®‹a›uGŠg<‚Y'qKzeK¦c €ZžyL•qD|[2kL'ľ {ą›vĄ‚V‚d?‰a0¨Šeť]’j8wR$uU-„\+I7 ĹĄ|±yŔźvŞ]˛{7cO5rO#áxżťsŞŚhv`F̡ls^EwX2–n<ÖŻ€Ĺ—^WD-Ůą’ą†GYE-«…WeK)ٶ‰kOÚ…tLtRNS 8W-M%"C`­äBĐ•/[:3E+O(J>TdQ÷G^őáé~‰lÚçăąűîŢ ęuńŇÉíÁ±őóţÚśäÚű÷ř;S˘RžIDATxÚěŇA ‚@@Ń2m“ APëZ§ -¬3´hŰý/Ň0*ˇzď ˙Żŕ/¤h•ł"Z“ l÷Z–Úžu”Ý[E0?UŤJŇPEË]ůĽUăTÓP‡`K*BŤ2îĘć­ĺŞřTsíÚvú#?×Ăą˝]vM]ÇżćąrX+n5]µďž÷Óăó~‘/űćŇÓD…á…UTP«1V¤jĹKÜPҦ„„®ż™Lď4”Tj˘ĆÜ,mҰ“IJH Ć=Ŕ_ćL§ăépi§ß ĂłÖä÷É{Î|Š–ŠĹCc#ţË—,ąĚÚ’Y-ł¬ «ÎÝ˝‰§É)˛’đ] \1äją%±Z–VĎŁpö1ŕč’NÉO“x6gbÁ€÷Ţĺ Ě2˝¶Źřw|Ť&P­čÔ`ż±Ĺ™^ťőÜuµ™\ş  —ř°UÎŁr¨ĄMúŤu(Đ,đjLW€bC@~<âÓ$ží8&‡-łÎthÖ˙ĺN. äŰŔ˘řxΖwžM"µ|×Ä™^]˝®ţŕčplą\gó“`6»[uűN˙=AŰúJ¬Wíɵ2s¤-ÁĂńÉ1›ůÇ$QË7$Ň,ă{ĐäżťŁk .u =>öłSh´H{±f…üâĚjľ‹Ž&śŁCą9FČZ-<[ľÓŮň:˛¨ĄM Ţé·ľ ą‰^='ĽěPt)‹‚Цô4§Ůr]Ď&YѱÁ;­ ž[eÁŐď#Ü9*:{ns$ůářřÍ6‡g;F-"‚ÄÄ ţm¨żÁsţ4lîÂČGg†T_-Ď*??5Ťß»3ĺŹëɢˇ“Zš«ŮÚ¤J׿®˝_~óĽT*˝Y^ůřůWĄ0gź ÔnVü±nÖ•«ĽÎ,X„žţ‡;Ş[tÚÚĚ§ŞŠh(K5K.[|¨¨ÍfiĆo|Y))é…w[9‡Ůd0+Ôw!ŕĺwfÁ"ĽÁ˙Ŕr¬„Vt›ĺ ŃXü6§Č*><›M«Í× "SžŻmłáB%Ţ„OC^•exun’đĹžť]«üç]ő8ö6ŠE2‹Úl–VdY…XXOŮÔŇ˙ľxłĆoůőžÓ2„Eäüâě@«ÚÓ=µŞ™şQ =ł`¶ôˇŮ^(1űRíőż-Yˇ>ăĚâXYÍ/Â8áĘŃŮm<­Şł˝jQŚĎą®ô¸¬tĚôË4¨%…YŃ‘ľ ś–!|† _Ŕ+{väůŽÚ™ ýřލŇĘ Ą+fÖrĹ"˛^ Yľ° ™Š—űpŚđE3±gW(¬ţP»eg9ńQ0 yŐŇ*·:­tËÓ­ěßB•Ŕ¬ä± ˙UY'˙ Ëî•U ŰŞ~oZńał¨yU+)nXĘ‹ň5>¬/ĂK—W–g8A¸âěŐڞꎝĄb±HÇ,gŻ ©ŐŚâŽŮ–őRĄ…‡üwŘßďPXS„Ř+ČnQuĎn2[¤”ž“WZYqMć‹n˝,fĹo÷ ę÷űUËĘ‚Âęăza9{EvŐ^hÔšĹ`žÉ(˝gŰ()˝°’ÂÖ +ůäţż*ë¤:dWk¨˝±·ž5‹›Ő«W[3Jo,&Ą1+~ *‹ąXŢqÂX6]}_í•ę*ĺ2=gŻÖ3JŻ”4ŁOÁ¬¤0ł˘AĆ•Źîg=#<<Ź®W`V/éŃ÷ Ě*‚Y++tì,Öbż?!ü°—B+»Í* ŞóŮ,ĺbB—Rň xÖf–Đe»xßoľe1Ú…pş_ŕ{şcŻ*ű*Şë٬ű^pěŇ­ŚB‡ç9c4ńfiOnoYĚ* N÷ áŻ?PińŁfˇôÜ8ż1ŁĐ˘K^•…ťĄP'S‘ ˛´'ćw!“# 6á(· ‡W(¨ôŮ#^ÇfáÂŞL+ôYÎ곉®¬Hs˛yp€_ÎąI¸ ëĄĘ‚Yéaçß*,¨Ł:ĺ/Öä°ţFĘčČ‚+LxĂËď«L¨wżpđĺ®0a1kĚ&¶˛âÍxÉb!Vđăpx+*¦!˝w!eVYaĂ7ł˛D^Y±ĚŽ,ř÷śá.¬ÔľĘ†ęF—•…ťŻ)ŚxŇ‹«0úř>Ł—,¸Ý˝1 Â[SYń‡ťsYq"ÂđĆ;˘(˘ŕFq'"¸ráô9h´Ťi“Ž·q˘‚3ęxÝ…D „ě"Ś‚"{_Ŕ'Sk{¦˙EULý]ÚÔ÷óqÎN×L7›­zذzÂb|1ĽpîřasÉ2éť$ÖÁFRĺâ}Sď›3U›i"4zYđ–uh¤wďKáŃfR XĽŻĘăńŻę9/_ĺIŘj˝JŇWßĎî1'RHď>łűé‹I5`ń•ÇZ6Ăň…“đú˘đč[¤çsćŘĎônnď<±N%“đ‹Ąięş|aú ‘ŰÁgáů“f-䉵}ŰŽŞ^ůá$(“»3T›é’0)}!Öϵî ^żî {â,´T'á‚đřfáůćŢbů+™WŽŐCçGBf!Šĺ ë’˛é;Vť™«„¬ęĹÚ]?±ĚżkP6=Y®bÝ6W˘XŢ(徲鬇,[ő0bµ{ÂćuŃMŁXóŐ»ţD٬eNłZ{AŘĽ-˙´(Ößą§l>çNbá$l]6Óé˝ŢbÝU:W~µ,sŕžM¬DĘD±ţ±î)ť ě^nbM„λ(–P¬eĄÓź],“Ýß ťa–E±|D¬;™[D./…„ÎđĎ”ŽbÍŠőHé,ş¤wüeí;BgĹň–ď‰ŇYýK±n ťIËXľ—Jç{îP>ĚîíŽĐE±ü€b ”ÎűŇ»‹X á“D±ü€bŤ•OÓA,\ _ źFË(Vë˝ŇgöOr¸N„N7ŹçO`D~®t†Ţíb]o„ÎŁ<H=b]S:+bá{!tD±|Ož)ťŰĹZ:Ó<>›ńNĄłępoŔkĂ=ˇ“dQ,OÜČČ ~zŽ÷»XiWŘt˛u±â Ňů§Éíôٞůd WݱVňřćÝŘzJ§aí xĆJ„Îô·XŤ(– $O”ÎČše0űŤ…NÓd÷"űE±ćĹjQ6ď¬ŰW3Örž˙ WX± ‚iWŮ,9˛Ęľß67ň°Ka=Ĺ*ÖÂt¨lîY[6ŇÇÂćŐćěĹš<ńSÂŮÄę ™nĹň –0%ó±XżP,.«ËBf)xÄŞ˝X÷•ĚwkoŔóZGČŚó,Šĺ|ź’^|ŁHĄRâ϶TB'ĹR¤¬©Ŕ„çäU!¦ČJe¶1ůó«8ÚćfĹś±ĘŢ ČqLQ/!‰éG´99×o@báŤÉł'VůŇ>˛Ąş+–T ۸ëľLXË€RP#”îf@,łĆűĹ|ä™ĺqČŔšJč¦XŠ”5z–*Ľ˘Y¦­wЦ˘ŰđČá“Ýł¶8dŰšJX±Ľď1:·4 Ă4¨n˝E±(em™^lű|™°G ­Ř;:ÇU±)ëZ nI¬8šÍf3‹éOíÖ;ËŤ÷‚Ý‹E„‰Ţ-hÔ“S6TB—Ĺ*î#ĹŰľY®Xxł4ëąqë=!óŠV˘Ćű™zAPopŔˇ5ÝQV±’ZŘôĽ¸ˇĆńôý,¨ăÖ;aÚxRÖ,.bĆ‹*ˇ«b)j!WůúsšÍELOŚ[ď„ÜxŻŁIaşZC"Ö)ěz6ś'Lp[,©ţ€‡ÄňóA˝ëi±Lď[‹¬™®ěŽ3ŕÔ¦JX±˛łö=“X… ^·Ţ ÓĆ»G «°†1GŚlބΊĄ¨…;ß µ"–żHYľé®w2Ë´ńľK•Đ/ő˙…M•Đm±¨.ZY×BµXÁťqëť0kĽ§ďyˇ$ţ±ß7VUÂ*•m÷ۉUHźŚ[ď„Yă˝ó„X#ޏ´áŽ™‡ĹRÔB˙Nşô✲ëV·őŽĹ%­źU¢X8ŕĄ] Ëq±¤Z¸'ŕfĐH)Öžqëť0kĽQ†•p˙żX˙JYT g1¦7çüKVǸőN]Ĺ})W0ćš]•° b™lqxĄĚ^ĎřJnÂě*î‘úŁđ€Zmlq^,Ó-÷ňłp®ä^Óz—Żš‚0ĺ»»»ŁSK@/qZ,EĘşˇú› m—× íÖóI¬śŮ3hĚ˙Ďłc‹ßśŞ‰…·8ĽPö˝ź‰ő0 –öU3»‰•{ţ!|´ěŐÝ}±¤Zx*wˇr¨s‡ŢůWŁŞnĽ·đ6µhs k§Ác«Ć9Śą-Ą,ŰýiĆhćĹú-Ö2Ők6+öW„ŢßŘ`Ńż9KÚâŔ€g‘ĄŚwFô€XÚwĽżĎ‰EŹď–lśĂXeÄŇë\íPdiăĚ ă»Ţ “;ŢĎH,z| Źs¬«„n‹Ą¨…M8ÖéQh›™X]˝»Ţ±X¨k xúŹs¬ÚŘS±¤±ÎKřAˇĄ-ç}X?Ő3ŮlTŐĆ‘âéoË6Îa¬:bŚu¶g¦ŕĘ6őLGq”˘ ŽAÓ1ˇěáĄç0ćşXŤuN)¶Ů°‘éqŠ ŹRÔŁŐ‡ź•éśę’J%µ˛:?5±r¸°fzś˘¶ŮQŠH>S˙ůš†¶Ťs«ŠXfcťË4kĐIäŕĘđ&ŁšşŤĹŔ˝iŠŁŘ×ĺç0ćĽXŞZřSŢ­žłG§ě/žndŐ ~\µĄ«ÇľşWC,©öŕ{vw‰őkăă¤5>Jq‰EŹŢ+ß8‡1÷ĹRŤuľém÷‹!±nń ¨(VM)ÖlL¦‹fŇ'ŹJ8Îa¬’bá±Îەˌ‚×চü˝TŰIĘ"Żčá_@C*‹ž|ZÂqcK:ą:€{/ /®očĘý†ĽXéłűz7ţn Ç9ŚU@,ŐXç^¦+Wę˝kŮ[ nLňä ŢGôş$‹uŁsí¨măĆŞ"–4Öi @CŽođÜ­%•Łí±ť{t 6Ţ%Ą;0°łVJ¬l¬3€«şß`ŚšŞůĎÂg’XĎŇd‰šť;˛XµěX8ÎůĂŢťĽ> Dq˙ű„ĚŘÖ¦µiL÷jâľď(.DDQpCŠ'AO#ĆÇX¬’Â*¸oä©PŐ©%—)D6,ȢíCXG ’µ^ą'Ô –¨®CGYŹĽŁ ŻÂ×’ańY°ú°ż b•ő=Ő(úÁŞea¤H†‹ˇVI‚…ŰZŹ ±švŮ;ˇ¦°’E–uJŔšZ6xĄľOhZÂĘ”¬í4ŽJ°{e}:Š^°D‹,e)«çxŽUŢG±‚T°ŕET…`íđJżÄŇV~‘•ڞ‚UŻ`•–Ź˛ÔµÖŻ`)+e©ëd«SÁ*E« ,3Ů«OXMł‚U.X5!,'e)k/€Uͱţ[rs,€Ť˛”uŘçÖXŐ€ôÎâJÁ˛,ÇfŁ,U`ő<V{ˇn°2Űc3Xe©ËÝÁ‚i{ˇ>° `Ąź›ŤdT5/ ‹őÂň•,m`Ő¸‚ò؋]ŠŔjđcXńę˝”%K/XuAÁ:ú9P! k‚’U:YşŔb®ř‚eŻĄQ”Eš®ĂĘ•¬ÉŇwv +XćaŘľ]X„¬hCÉŠ&e“Ą¬ßCX΂•`‘ůűS»4—R–°ŔU¦™™»R°YÚŹK9”M–°jWĐO^Ąé(‹,;—,Xf•H–Ұj™Â]ul{éf˘,2˙Lş­YČ%“•aŐX0\ઞrŐoš‹r°ŮĐ5C•=>żř%“uaŐţţŰxW‡ůÇű„E¶a™…ČB.ä¨ «†ľKčęäeĘEEX¤wĐÉJRôrIŽ˘°j…#^_őW)%a‘ů§„˛L—–ް2d cXĺ\ ¨(jÂ"ÄÍÉBhŐ!S’Ą(,Á%‚qŐéRaT…EV–Uç#ť–ް¸‹„ŕŞç]5GYXd#'K|!Ä‘+KUXąË4/ţbŐ9Wí?NŰŐ…E6Űś,î2Śą`e© +_‡řÔąĚK»Ş_ŁŠÂ°Č “—Ĺ_ń“+KUX<«v:"[ŮóľçݡŚĘ°Č [łxZpö9$uą*XAL­šaápľVäŞ5®* ‹¬KÉJŃçQÁ«‡—+KUXŕŠ]¦wŐZŮ€­8mpŐ8MÇDmXdi¸‚ďî@Ń‚©0µ÷Ź´ ¶É‘Ą*¬Ľ«ö®ąs/>›×H’Ş\9VlÎФă˘8,ö»ˇÍdE—A Ş1JŔjîÜ×á×e© +tĹŔĽ›$ ŐZ€+Nę¸ďtlT‡E¶G˛€  b™°ĺGü]‘,]aŐ€«VP°bZť ` ’bŐÇ6˙Pٲ‚pWX±Ľ†®)ăAµaÁTЬV«Ă’ǧ}»‰ú°šGâ'˙:‚‹ŔĆxŚËʤ´1Yz‚FČ\5‚´ý `+•ÎČŐ©ł‰ú°H÷ “Ő_Ć ňšćż7CĺaĹăΤ`-ŰŽlur¬úöˇËË Ŕ"ËVÔdQuú1+ČŹôHUSXyW¬`ń´WúůÎ5Šf`‘Łź˝ý°‚’Ő—&k`1W¬`q´:¦âbş˘SäMs Ĺ3°ËdÁ0v +._A–ć°ř‚ĹÓ bCF_ş´@fVóHrŚ>¤ő‘±âňn±’Ą,¤`q´29t‰ČlŔ"˝–Ň‚4€—Żv_JÉšXP°PZ–Yl5#°Č:ÇIÓę«1%KwXA˝F VD˶ Z(ł‹„oßłp¬Đ’Ą#,aÁÂiŇB™Xóç9łŐŹXa%K_XĐ a†…Ň2ťQĚ-´Xf8aL`U¤dU° `ˇU¸Î,˛ßó<`…—¬Ćżî˝< ° `ˇe…oĺ̬nßVXľf{ˇÎ° `á´>Ѣ™!Xä9°*P˛*Xě—B(Xx>Ó™%Xäî\^˛*X!¬ ÖŢ·´pf ÖËc”¬p‘UÁš¤`} Ĺ3S°Č›ąĹóŁ‚Ŕš¤`힤`ͬŰ{'(YÚ·ÂpńţęÄ^% Ö˙…E®^.a¬"W –ŢĎcAÉ e™÷€Ö”‡îWowYVv/I…Ő]ľŐeŮşąKdćÖ8VsĄű¤ ΖdŤŁőťĘËpÁ˛ę‚DX+¶ş"1ţČęž®:ŐË©’Ĺ–Y#YÖhťŁňriŕfÓJ‚Ő\ęfła>‘–Ó;ǰ˛Ět#ÔüőŻlÉŠe­)-݇ËÝ|z’`Ĺ® ›’‡¤<«Ľ+(X‚’ĹËŇ’¸t_âňą Ö —Ď:ÉËwž•cĹ}°zšÁâeőmSLë'•–‡® «.I€ŐÝę Ň#Ňň-ĎĘc¬ţŕJKXĽ¬lŃr˛´ľPiYîŠŇ•kł+ĘR"-{r¬ĽtŚ]龍QÔ …˛€–ü!ÖUW•ÆŐÜęŠÍYyɱJ·AćJűŤ×ŚZN× őŤJ qĹyH‘ °»â,$Ňr3aĺCą‚6şŇ~«HNß-ąż.tĹ!˙ kŕşÓî…WbVľ¸ ®ŞÍm9Y¬BŃ ey-©ÓŃÍ®8 PXř¬AśŤDZž†¬¶+(WŕJűí¸Ť'+ˇíĐ hÉśŽnrĹYüϰ6şâ¬–9# X®|ˇ«6¸ŇVF–¸:#Yţ}:ýŠŐ›ZĹÚDäeÎŁÄ Eˇ F®t?ňdAŃÉňRyY;µ5ֺ鯱ș–çĹőJÔëŐ!MĹdyŢ5*/żŮ»»Ţ¤b0€ă_°}62Pć ˘˘â»1ę˛á 15Y˛$&z± ăŤ11ńJ˝óZ?€Ţi4FĘá9-Ę<–žöü?‚ůĄ}ěm2/oűźa•óňŠ4¸Î±kŘŐ^t=+‡°ÄíĐ“ĹƬ=k\ÍĽ´ăśc]ÎKŰGkˇ 7B©«č˝ÂQ˛¸ ľ ëY%/ëěÖ„!ë4 ˛˝lÁBWâÓsŃ «k´,¶dť€ kĺ%]Ř Ö˘tÉZ˘A¶îMXřŇç*zş—°Ä-\˛ hĹüpq=üč.¶*,X¸FoBc,áŢ›˛˛Ź Đ6+Ăó5+~vř+I­"Âb˙Ś`a#`qźŃ0X[lŹĎ®ÁŔ˘ÉÓ˘«leßNČ-XŃ+ö\óaíI' č6ůÝpß‹ĆĎńű`ś\˛4€•“Š^±—¸Ź¬ßöA\®šŔb:‹ËŐ ľÜ eEŻŘK]yÓ;ÂZ˙Q“î?|ćTyc XŘľâÁłGOWŠ‹ôDĆÁb˛˘Wě±],|MÜë8ŃyjD5ďś,§˙-°BW ÎXçŔ Ő–Áňd!-wżÇ’łBW«Fd¬ŐR–;EY-Gży—l‚ŚşňαÁ uąÄ/Yâ÷HËÉ_éHY‰ďÔ÷OŢ“`D†Ŕ:ޤ(‹ŃÚ+Đrđ«čJX¬şáËĎě+R Fd¬řq÷ą`‹ÉríR‘ŞÂ‡ęń¬ 0"C`ťĺ>óc´D[HË­ý8WĽ*d…żŐyFd¬Ü‡É¸hůmý٬ĂYĹPcŐwÁ`á’…Ďó¶–S°xWČ U1V}WŃVČuö"Ęb´ú¶R>[˙$+¬°$żůBTLU‡ş*EĂ»żř+-´…'[;”~XĽ«>*¦Š±b®J `D†Ŕ:~‘Éňh1[ËŁŐ“ĺ,ÎŐ§k ý#2Ö«k ˝Üą¬pĂÚŐżËďĹśBßÁ őnNˇ;lÍb›ˇ°řŤđ…ůOč뫬X‚ÝDę,ĎUBÖ­o`D†Ŕz¦kÇWFZ«;·ż0ýYŐn›ŰÉňąGWWVó'–“t¶ÝT‚•ęËrÖî)`ý€ŮŐŘ8|—pUë—ŹÄé캯«'Ë[˛€%Ü­ë6ĚŞÖéDÖŤĺ2ťUו`íř —`áUÜzŰX!Ł»w„ΤĎsаş˛ÜĹvÂ"¬ź0^Ż’ńµ—č ú¨KxĚ)X‰,C˛ÖöWɤŞg©ţ®ŞÁÂGu‚ĹvBËĐ· 7óDĄIŞ»/аŘϤť5ż#XżAs[+D­{ Ts7Ő`íô_+`Ĺş°LüoaăQínśjmăúô°vG° ™Ţ+D˝KTkćÔaĄÜ„•`° śŢ‘iÚOuv5‚Ą ˸?ꬵÉ4ÝŁ:{Á ÖCĐŘ#2]eޱŰ,Ő˸ł÷3dş.SmĹ?ĚE°a™7dŐÉtŐ©ľžÎÍEÇ Š¤¦ťd5Č”U©ľ~L Ë‘R¶dMë–ľ˝đ1™¶$ŐŐó9UX™ś['ďĂ+4n/Ü$ÓFµőľó/ý­pâĹ`wŕpL×:ŐÖMeXŢĄl®}6ßc™·^ ÓµJu„ßbEú)ě…oݍö tµŹL×aŞ«WWTűÓ_°Üů4YX˛¸‡ë{e}•zëU]mN·Ö´Íîń=o.v:Ö­Ô+‹Ą»á4Îţ‚-YĽ,fKäĺŮÚ]Č4U¨®żéł6…¬¸‹ţśűů—oÉb˛-´ĹűęŃş şj¬őÚqޫڀŠBSý2ÜŃý`•0XĽ,F‹Ů}y¶ŇŹAWŻŐ7ĂÚޫ±‹>VC¤sĺÚOě…ßŘ'8Zh‹óĹdťmµTeÝ(PmµŹub¬P‡Ę ›{—‚HdáŤkLďËŁ•j€¶¶kDĄšFWű˛+NTĎ”ë׉ŻqÚ2]苣µújŞĚYíEŞŻĺľ+T…˘)îć5/^d!-Äĺ_ľÖ|TŇőűŻęé8Ő×BzŕĘC… Đ^čçŢU‘˛KHŃ/ ‡ú®¬2č¬yf­ęň"ŐŮŞpń(®Q|._nKćY˛Ű¸%şđbî˝› µć©‘W»t€jm1-»*Y‚ ďăvđ:n”Ő‰ě„ĹăÂŁů"h®±qf}XŐ˛ţëf + }ňd/+áK¤•Žmţšń⥕#U_ątîŐßRšŤEa{YN?yÂËb´.ĚŹk ë(̬µVśÎ¬ut5`Ĺ{b¦X?Ň$Vóëâdµ`'…ýŞČĂĚ•đŕW‚÷=+'yuńĽÄW1Óëk E°â»W8Ł‹¦‚cZX’·{%Âü˛2]Y‡@1‹`-㛪âk˝č ›wűé^”…¸„xZl7Ś=µěUHKŢ­GRŞč{¤%$ÚBYÝ1ë.¨e ¬8v…ŞxOA» -,¤5Ň׬%PĘXËlŔ\ÉEŻ*´°&ŰBYl€O˝•lµźm„‚+ťŞB «ŰXZLVĚ“uo ˛ÖÂĽ7¸Ł«ń¬Č(Ľ°Ľ†… ˛ş|˛Ö=°8W:@aŔň’ČÂ1+“łÖQ°F¸":˛V§Ń˛b c–°¸KâŠhĘ.XZŢŹüz&e¬} nŔBWşYŮ‹˙@Ţłş›á‰Ľ°’7„KřŚ]_öÁBYÂu&d¬¶|Ŕš+ a ˛ÔĎIĂk9‹ÖŚ]ŮkXVŠÉÚ€±…VĹs•1Ŕ•Ť°Č>Ő‚q…Ö©´dŔš•+;aq˛|›abĆrXEćĘŚ‹X ‹Śłvż†Ń…ÖţLĎ•!±–DÖ|FjXeÓ\Y Ë“%ŽY{Čč5+̰ö§đ _ źˇ+ka |l k×Č9+İNfĐ•opŹ`ź8Ŕ٬D ä…Ö©=ž«LĘÁťŰaá…˛RŹ@ZhaL{® ůa'‹aŤ–•‹¬°Â:‘ĺ*‚ő?#+}$…ÖrÉHW6Ă"K”•oŔPˇ„•¬Ë׫Yo„Ŕ’ËjoXa ěď~Wł˙a'»aŤ“EZ BXĹ”±®ě†5NV¦|áu4;p•CW& XÄvXădĄ/đVŘ`-ÔýW™ćĘvXce­7ÁWČ`•weMve=¬Q˛z´rŔÂëhÚlWöĂ’ÉŠĄrýEkĺ1x… Öb ·A3]9K& ·ĂÝú…VeOÖtW.Ŕeá ŐŁu| z…Öz‰ßMtĺ,^–8hĄ‡ [X`ťÎ XáxĹÎŰ rĺ,”µ eů­ök¬Ą%Ů6hś+G` ?ôă­\± X «Y˙r•ĂmĐ4W®Ŕ’%>/]Ť‡V%–ĺ–+]9K5ĽhĄë-Ăať¬–¸ĺŠm†şrwŤ¸2Z—šĂ*Ô«ˇĺĘs5o”«żěÝM‹Ó@đ/ŻŕME¬rËlŃ<-}2tAŚĺFŻÄś–ÉA–QŽ`…cQÖžµË›‹Ó,·m˛ŮXîi®K˛\P÷*°ľ»f+ôăŹ%‚带'KÁĘÚż{_Ö`ŮŢl4Wršë "o ńŽř]Ëqq. Á˘±¨XY ÖŢLŔŞF°ş¶Óf°âÉĐÇůÖß{şÁjŘ×d••ĂšBčô¬F—M†X+ËI!t*VĂv‚bňĹŐ"XslKŰŰ’ĂšFüÉaÝšő§iÜgůĘA!"Xm«îÁbacąÂşÄă¶Řĺ.˛VÖ#S kŽÎ˙I.KÉĂĂęvźZ«ÚŚ`ť»ě ¦^Sď"+°f –‚×%a°ś¶Ö…‹ł]6ÖTB°>}úýűĎźŻ_ż6ŘęaZ*^đ">,vČtÄÝŕJ*„U€5×ČaM-ţťTÓ2»Ű`±·Ąâę1şF·L·c7XŘJčw¬ëô¬kl-ě(y–d–ţ[f۱ín×öf©®âł„J¶n™NÇ?bo%¤Ç ð®z°Ú*®ë©†•ô†K§Éu:ťŽăš­Ř§ŐüNÇźQűŹJ¶ŐvvÄN›ž6aŮË€Ą#ezaý÷;î+DÓäÉj»®Ű6[V+ˇdżÜNÇr°ř{ü´â\ #¦oBŃ3Ö…K€%Š|” ŕJ+¬ˇ÷îRčqÓTmÖ­–Ébá·ňýbźĆŠľčńď‹ďß?âZpÄ-|:!,\]úĄ+­tÂúGŢ™…¶VEaŘçDDDśpBQńUŃQ_$/>RBš&Ij&BÔ¶úr‹Ř†+© ˇ¨mµ—ěŚA[Koë;Ů: E,8<č ŕ^{ť˝ţ“ăIzҦbÍzń^۬śłö·×^gíź+ňJeŐ“k€,Ue)˛šâMMˇ¸ř…[§ÓC\,€îă˙)C™VÇ Ë#X.›•ŽČ9BWc‘ó±‹cñ©Ě-ĺŻë0± ĄÉjrŃcÁ/ÜÂ)„ÁżZH°e}ĽßÝ?Ŕ˘+áŠ=‚z ¶0Ç-zżKďvLÁBlN¬®üňĂPĎ;Ă=#oĽ·36ŐÜL1‚˘˝Ü0…(a,»_ĺöŚň:Ţ3¨śţđňÎűAöŮp€ h$ý§©Ť•Ç{Ţě™8=·K ˙Ž+6—Ş,HâŮ-ů˙ţ3cs§ďîîúńůőŻCλ<”O°8÷<˝ńüx§ŻÔ:Gw¦(BĄ*pÄzç‡7´˝§ŔBĆÂ/߯WzJťćÜhVN!“ŻújţáýĹëĘ Ůďo|°Ţ‘ąďÄÉ’ćť>HöµG°ˇßß[^4V_ĎýňŽĎaýźl„wy`;¦`QtvwFU‰=÷É.­ÎQr“&ŁĆBby˙ç^§Ăsß5…Ş•–Ăís–›çCjěšÇF|Në_ç˘ĎřÇgíŇd/`!Ó­Ţçe¤I=-P9lp.D_rhý1KEmTąˇµ÷¨ 7ĄŇdzÄ’6¸:óž`ĺ°î1Kě]'ŹŻ5`­Ä•“Ó®_1:9Č;Ë|˙ţ`±pGľ¦wÎL4ĺűů?|¬ç„ó*Ş·ă ÖĎľĘ6>Őř¸‚ĄúX!#şWďWpúĽ˘¸z5M€ŐÖöÝD÷ý'¬K–J“µ´}źvިÓ⸰aDńĘv}•­s‡|?ŐŰ1‹˘ °ĘX÷&F äؤÉöλpµ^¨čô‡G¸z˝\ë‹–¶ń˛îóź9uřÍ{{ť÷˛`1WS?ŠŰS­m ‘uĆ·ź=ßĆwy`˛ţ×`ůşżŁ „Ç)Mny¶T'NŁ±ÚąŹÓŃźÚăpęőrVx»‚űÂbČ2LBóÎ{…űő´ÂguÓ¦Úd´XQ°ĘÚĎ9•łę ,m€50±Ľ¶đůôôç ßLäíáů äČ,K'ÎŚđpünçŞóŁ/&§żš?Ů7ětZYĽ‘lŔš=)ĹĚňä©ÍÍé…műE§Ô/¸(‡+ÓRFÝXÜşúş[\‘@čY7°zßÜž=99˙ŐüäÉľ{Ů×»ÝîC’u×1KOcNôý}óĹX,˘-Ş,rjŰĆƤµCŻ[^čÇC¸Űo[•Ö–:ČČëWö˘h֞輆ŁŮ€ŐW°}%FFם¶KĹš 2wi9°9Jr¸“/Ă©$}Î Öř‡§Â±B·´0b«ö~Ňdp1ü·ÁzŕÉZŔ*ĽüY&™N0X˘­Ë>•ś°^6`)ťřł˘×>GŮí™l6KX±Íç1›O1®f3VB€ŐË$L«kŤ Z3ďâ›äŔŐXZM] ,.Ď6P).§c‰Ô?ŔYű>śJǡ›ŔÇä*ę ,˙Ź{ßµ¶„“©t¦!‹.HV_ÓR^ ŹNVL)M w\¸ctOćrą¬ÂJŹ>9-âyüY“6Ş+¤Ŕ‚ ;´ëůŹž´Í¶2î6°,@® VCĂ\§Ě‚“ękÄ#—† ĎXC$uTsŇş%IÍ…ź¬.†Ç¬±`ĽťÂŁĐJĄ’Éd,IśĹ"“2€ÎŐÎV„tâ뙩~WâJŤI:ĄŚ˛bq9ĹŽëţ¦łN Xůb ËΓĘřšgĺ§ż†\ë„ůÉ+±ËůÜÁ˘O)vü+hçÍSbLëEß´¦ĆX‘jŹśţK:™ůČ|ôCsuëő,mJ]I–ˇż¦±oLx¦%)°B–M'NÖ°#ăŃ ®ôTgŁŮ˝)iൌÇsc›ě`}ŐE`±÷L&Ăţe/|š±Î’•€ˇ$Ë•T9°ü oŕSQâ7©–ic±n©9+t|±%3»ŢLňUÔX:ĘH[ÉĆńJKxŢŐkÚ; 6°:"‹pPňÜŚKqĄ^ů&Ó rĘçŔŐCĐéjKŔę{éĄ@q•á+–†5UĚË® Îâ,Ť1çeĄ){»Ç݆ĚqjXouôg1`÷;y€ŕKđ V'¶9ߊ(UF4–®¤l·—X;ř@Y;ÉÉľ®ŔŇ\í![U+ę,?öă>ŇÍKç^ o3Ć7Dädµ*é±nČçn|~ĚVźµ˘vAK˘QűĆ˝ßX°_Őö9íC2WRĆ•†n3˛XŮH˝EÁyúź{Mĺ¤XĘ Mů Eý„xrJůL~KĎfů˛`ńůýd°~UţMkÍ^řh° ¨ éwŞëťhW—µm`ăĘ:¨+EPßVO`©°ţ“«Ţ|ţ¶ÁÁÁĽ¬g÷kL~‹ [¬vŃÖŮŔšµJ,$HkEZ@î5XĘB^ŔB…ŐE=-ę3Đ—»qőő€Ďi…üBí)Oë,ŠÎ^É<Ý^ţm+BĆŠĚŽÜVGĚXÍv°ŤA¤”Ŕ2­¨`°p€Á–)áŕK­€e˛&1ÚKçą<Ž˝ÍpŽEŮř¤ún»®ü×ĎO}źFä˘Ů\ˇ^Á˘čśxă7»%­/•ý¦¬öJ` ,ŰRŤ9)Ka–;ú´#R,ÝťTÜ–€µśsÖîXjă˛Z•/buY˘tÉş¬Ó¶ô6źŕČŮCWÇ`©ţ,SîĂťx`¬ŚČV…çÝ*ÁBńpÉ(F_ŚâÝůP%°2ĽX V_í!'X!I;†[Ż`˝ú×0:,[1éą&¬ŻeJvżĄČ±üZäÉŃ`ĄuÚ¬°ttzM]5ß‘ŐÚôVłŇ7ĄĐÚ®¬]™đ¬vw°ÖĄÝ`kĺŤ/ÉŢrZźŮoTfk7Đcj°¦«ëŐżŢÁ9¢t!c٧äi[fŁÓ"¤‰WˇHzÉVřĐ`=xŚŔz¦aE6ń‰+ Ž’ŹłĐÖR'OT VČ Ú_ ¬i X¬LP0Â9ʦA‡ŇÜoĎXU ŇęÁzu=ŇáďYÚMłfŠŮ¬%ëW‘F:ő˝˝xŻ+°ÔŚ›Ő&B‡i+ÚŠ:yÄöTč¬&yTZްŠś|`Y’’­Ëa*.ץ0_a)\•ůR–‡Îű‹Ęf>¶íŇZ”…{Zň›l.G\V9 Ý©:‹˘3Ő)KŽŰĚ$óŐ2âĆF-$ny®Z°° ÍÍI×â˝G~Iž MĘjQy2-Ş(fŚ´`‘m!#:ÁÂ&´ĺ>XXŻż>3‚únçK¶ţ… ]ÔŢ·˛Dą]«“ŽX‡Ü„Ft$ŇÓÔf¦í ző«}7}ŢÁ⨯ěNŔ B6#`Y*@s`(I¦ţKăĹ;'XÓ®{»á u´Ľőúë/)łíDçéXm…7W ç¬ó"-Zw‡°ČÖXţUѢQtx[,N/RńZ•`›ćdłŤ›“n Ň1I˛QM‘ŐŘFk0ڍĽ:¨¬Y}2ś–ˇ_ÔîŢ XŠŞ®@ ‡cľÂgv˛ä;&L©gtýDż]Đ’ßÖ¬áß„~čÉCŔZő,E'©»´1ťďP5`hŞŘËs,U§ľ@6ó,sb¨•Mż’] Ţ,°ĐŁĘ¸n鼏–vŚĽ©+z:NM”!‹o˛łmž’´Z‹›î#ľč«-XŹX-XŹţ”Ĺŕ"Ůčcy ĘňZňŹ˙!aÉ&ŰD kúAéçöĘtýŚ–XxŚĆ8'“=iť÷U€ĄÁý¨dxdM‚, ¬Qs÷ú;Čß9pHk Öý¬+XçX—׬ŇׯüĘkFŁD‡O˛RôŞ«QŞÖž´ţ <ŰĽrJě‰ÜÉíßxŔ;ű۲ˇRÜ{g$W|KŰČÚ˝g°(˙¨†¬2…V B™˝W´n?d°ă×DtÖ¬«/Ľřš#ëü .żçé'ckJÚä ÎX –6&`uy‹4+sňˇ/ÔGL>˛Ć]IÄ â3kj [ýâfŽS:xĂ–ó0ĄŮź%ŁÍä #¬ć\a†_擯lkDVěĐžÜ#Ŕ’Čů§()¬čáÁ ^K`Ýp”`]uůÍm5«©GĘ\}´Ü„Žë„ő^/`9Ź…âŹB!đyÉń/>µ˝¸É€0©ąRWĺĺ\!¬żhގ.ü"P¸÷|ü+×aµ¤ô1ůđŻř®ŮC–>d6&Ąý–yţ@ä¦(Ő¬@ ŔJÝ{ëÍÖĺÖ9GÖ%Š)ÔXxt霎€~°ŮWŢÁŇk᤭ěMfLŰś˝îb.Źdi%t9~îDŠ `Áş‹8°ĘO•srŐ3Qş€UéÝ múĺôîŠYŇ/“•—C˛ŕľ0ŁŁń6qfČWs°nżóÖ›oąţÁşé‰'mܱ>—CËÚÎüh-7žÁb$i-lť°ŐAźę3yć üƲW¨ 9Đ{X˙47ß,÷~uQȇ9äď`%Ô@çsňá5Ű­`ËR=üâ›†ŠĄçü7†şšu÷u·ŢqËő7^yůUçXç]zŮ!ű Ř n´!đÖ&Ź˝†`w%o \Ŕ*«nŔKAř@jűÔÜ/ü¤šŇÜÎűÁű0ăŚr°—‚ôYŘŢTWÍîëďŔýÔąLÉąĆgŞxŤůSĆb‹“pů®aŔZDBĎŻ©ŮC÷¨"‡{\.Ô¬‡®»ŤŔş‰Á:«ö`ťM`Őč±ĐŻÂÓéŤ/o|¶¸řŮúĘmú`E’iýĚî¶đÁŢęŘŘĆÎ{4ěřś*$ŁTÖ· ˛¤®¬žX\›{{ŔžĘ–¬|hyK†šťńL‚ >IëźµżlżĹ/×ĆN,ž›űDt‚= X‡ú]}Ým´UxÓĄGYw>úäˇÍ::şâ«dkQX•¶t¸PâúYýŮĎA–»} dĺ\UÖ‹oUp_řŤ:âe_ĽĆаx’Ä9-Y­™°í^¶Ű8§7«}' ąŮŔ÷viňaÁją[…GVMŞwN.mŘş€ˇQÔńŤë)ť ŻŠdŻ-“wu/6EQüÍç©‘|†$ß/˘ ‘H÷í>x™„ăÖq É×›)’ń8Q’)š¸QľRJC>áQyđXkŻuÎďśuďąuζ‹™ß Ůîş{˙Î﬽öľ{íő¦y˛âťŞŰâ†Ă* ¬S'˛uő™'BÜćl…UÉş*ňîH$Ő!ĘŞAYUY¬ąçMŞş{ßu8%,Ď«×ć·Ę6VŤwldµüµ «»çÂ,ó÷+]”L„UČÇ\n‹Ë­ŁŰ:ÝmöB˙:BÇtřÁ‡•KXgş˛ş~íëŞĘÝŇËmÍ­É,” H‹˝ś|(ľľ–”5poÄ‹žnŽŔÜmLÇ2ł0WɇM\´Ń˘p- ÇYbYčdá×’@'¶Í‚‹ŽĂc%ŇO éë¸Y É{ŻIŻű˝čË,+÷ů¨čŠŹgßE…uu{ă®?ţŔÇaĺ§aé0îy7×qëX ,}I˘.=Ő:çÔ˙“N[hh˙ţt†?~AĚÁc™ßL  L!VŔE!–…ł·üeEńСł ľzÉIL±$Ţm$¬Ôڦzĺśűh¤ĄËÄŠWMäV• ë*ťqů|˝ţ…xŔîü®PĘ. `…µ/5}BYw ‡ßܢŃýűţ/ ¦Ă5É˙ŠšľVĽŻô«¬v±űĚYᄥAÖ_™ UtĚĽÖź–Ŕí·/5?®Ö't/ywbby÷ôéÓ „ÁŢÜ‚’'ü8DY˝ßšńf­"ąůňP0Ę/,Ö-,ký'S=P%űŞ+Sňä÷ńý`Z>;_kË«ŞqsPÖ…OąÝ ˝ńđÉC™ň±>¨*uŻ”ş•±‡°ÚZĆîřşecÉ›(«ú˛öĺćăł—Żś˝wsđ͇tŠ„D[ŁůÎĺÔđ©t„JP–fÜT>Ô?Ý»~ĺÚŤŰý}—ÎU(y—ńŰ*3ą„ĺtʼnE/k7oźľ|ëlß’ŠEŽÇčJÁ®¸ źŁG]9Kgr,©—d—SµVĄ]†Ş¬Ţ}żź]¸sďôĺ·÷]ş[µÔÉXýŠSl]Î!–ýĄ0H5a}É(LÁ´őrΕSbn¤ŽV?éeFQ0‹ʵC ®ÁĚîŤRnşR˘4‰K=Ë…„ußéŠlqć…Öh°ýô%1pĎÜ'‚–Xl8Í÷ÂK'y0‡DXşĎĺ˛ŠŞ NŇ} udÔłPÓć%Q5"ĚL Ë{.„˛”6d]©Ş´Š¤É)ˇĘNä˛ř„D Ěş)„–)ŽĺlË“­»|1ź°äLD#׺OeBW(YYÚcĽ$MĆ’P–ˆ#>¤ĆŞîsI"a=uR8F¨ó(Z™ĐeR ±dµ,ÚUň*Sěpüh0ńO’_¨E<ťťZ s„¦>pvJúB=릏íF ]jšoň—îµÂŇ3yüřä;ül_žľ)Ý+Cĺ.CŐMÇeq3óŔŐäŮ8ůGH_.Ă“ń1s Ž;ăă°vL¤™Pv±…X [¦űţ­´©sŃ7R ˘b–==.]•ß‘thŮÄăŘ#†aZDÚUHXîćl)Ň“ú ±oe …hŹqŇ8{, Č5+Úu>Ç JÝc©Ű›¤Ž•‘ŔŇąn&ÔŁ A…Esáâö’¬zxE˘bBP…r„N}řˇ^#QpŇaě’eŃ«ćÝ]ĺVttĘ}CÜu‘­q‡č!{,h©«Ţ‘ŕa[”5!ŠÓâű“Ô±Q ¶v®Ô™pVŔ™sá$_—eiëć ‡…ńĄřL¸2ŽĎąĆťhYŘeĂÎ.,r©°°x3s?ç׺¬ý kߎ”şD iŕëÍXŚ µ™Ú‰B4\†“Vz„ĘśR§ĚAWE°bîdY†ś ±.śą”=€Ň®(L·ýEtˇ„€Ů„"şM‹Q¬<.µË PË•ˇ:ʰ$;¦‡#şűY=jĎ=–rÜŽKś%ęWĹÉAIÉYu>şjß:Š’ éČLĐ™멾.ËŢ//Cß\âC Y i)Ż ÓběÂ0,»Ç.L{«Ň+G§ŕVłíŁGnĎ3Í Š‰@öFz„q@ľ­ŠĂjMaééĚEž‡g¬ˇĘGeű"Ł…ŃĐ®6–aĚ Ź°pÂßűŤ:D°=n>´C{ř?f„¦őÔÂŢU­Ł4t;"|í»—ex…s–m Ă. Ă2xö–śÖ‘U€@˙¬P|,ęź@Jă6a® ÚÄa…ÜÄ‚°ÔeMöß~·Ľ #“4ĺ° ĂČ!,DH˘©}ź±Í™ć bý"uXş\ÖrÄďţ([°ëa8[X¤«ö˙ćíë"‡Ĺ†î8ůÎ.kd[’J!`÷’¨Őrnó˙>uKç.5fÚ”ŕËş¬QëKĂFX]©PCkµNž'K°ËFY“&n( Ô kwt%ČPŐŐŽĺ4Ҧ;źkď°ŕ˛ĆµLźşv¨’ÚHX|}i\™˘'»Uam]FáŚiąÓ’0ĽĂ‚Ëš5vôś¶ŇpAąśÖ>­«ß>dgÂĄKh"ÔČ=ĽĂ‚ˢ_ i2ś˝©4LŕŽäAXU-1dC¬?ěÝANŰ@ŕM[“@ 5ÔŰcă„”€ MBÓ¨ŠP#yÇ UHI›Eşi%v¬“ëŢ3ôĺ]sţ7c2ÂY'vĆţŹźŢ˙f2Qöę«8RNs°‚ýeh4ý„„˙ęÄß˙˙îďűÁ—%¤]±šeZ°¨énźNV¸ 7ëI9˛íť?"ľľ~Y^ZXÍ}Z°<[Ḛ́EŇÉP©'dfŤźÓ›Äŕ­´¬‡Âć:\ą,V„ÓßÜĂe¸L˛*É'ŕ^ ‹–U+}óÓĚezŤŞCă*o©cW/#X°&ee6ŰĚ×J=?ÍÜĺŞŃްq•5±^e¶Wrä*‚y–…;xZ´64ĎýPęúić*ĆcUČb\ŃzĹîE#t%dáÓť>´T+[~ż›6âÜäňëYÍa¬ňŤ+ťÖöĄ¨] Y¨Ă`h™VÖX;ŰMů9ČŻ»Ź8 VWTKořýU„® ‹nJ©ˇĹiąFő¤1ęĘu—*çaç„©*¬lŰŐâĘňëWqp%dńˇĐň˛naÁiµŹ;·†Ł›ţďnšXdĐż ďn;ÇíŁ:P­Pĺf9+ťmW¬cŕJÔ!†ëC˘e«žE¶Śz­uô©Ý~›&i·ß´Şe2U*¦ĘS©u´ŕŤ«Č׫,>´Đ‡4µ°Ćkd+ďH—a‘Ő41H1©‚›gŞl°Ę`Z¬âPBVÖ–NcKC'Z¤ Ľ…4qHqa ¨L®JĎl+jÁŘÔ`xh1Z9˘•-ŕÂä2‰€Ą‰E,Ä3aŠP‘Ş­ç¬bRB– …]‹ĆĂEşŔËÖXÔ4‘FcÁżă±ÝzIm˘(·?`lËńPÄ„d˙›L©UH x”Q ÎŮB_^×ÇÔÔs?{U·a8eVĄćęUZS[WÔ}QĆ3ŚăąGUMcő(›Őď´âÖęmÝ{\—ŻČ+ś©!Ţ"žä’QEUÓÉžY•ě*ĘĘ´˛­9®Űwä.”đ¦¦zT±UŹCő¬ţ¦mÍq]‡p7*¸‡ˇ7•Q÷ĺłZ?Äh+ăęuuW*8…ŢTFUU˝­^ĎVĆ59PÍq‰j cµjk\Ý‘Bö]FµˇŞf-ăJ{ŠŘ-ÚöŞJ-í¨ĄµíFµhTôđO?eÄD?m•IEND®B`‚aio-pika-8.2.5/docs/source/_static/tutorial/000077500000000000000000000000001433544061600207475ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/_static/tutorial/bindings.svg000066400000000000000000000140071433544061600232670ustar00rootroot00000000000000
P
P
X
X
binding
binding
binding
binding
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/consumer.svg000066400000000000000000000030501433544061600233210ustar00rootroot00000000000000
C
C
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/direct-exchange-multiple.svg000066400000000000000000000257341433544061600263660ustar00rootroot00000000000000
P
P
X
X
black
black
type=direct
type=direct
C1
C1
C2
C2
Q1
Q1
Q2
Q2
black
black
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/direct-exchange.svg000066400000000000000000000301111433544061600245160ustar00rootroot00000000000000
P
P
X
X
orange
orange
type=direct
type=direct
C1
C1
C2
C2
Q1
Q1
Q2
Q2
black
black
green
green
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/exchanges.svg000066400000000000000000000135711433544061600234440ustar00rootroot00000000000000
P
P
X
X
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/prefetch-count.svg000066400000000000000000000166211433544061600244240ustar00rootroot00000000000000
P
P
C1
C1
C2
C2
queue_name=hello
queue_name=hello
prefetch=1
prefetch=1
prefetch=1
prefetch=1
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/producer.svg000066400000000000000000000030461433544061600233160ustar00rootroot00000000000000
P
P
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-five.svg000066400000000000000000000302031433544061600237360ustar00rootroot00000000000000
P
P
X
X
*.orange.*
*.orange.*
lazy.#
lazy.#
type=topic
type=topic
C1
C1
C2
C2
Q1
Q1
Q2
Q2
*.*.rabbit
*.*.rabbit
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-four.svg000066400000000000000000000323671433544061600237750ustar00rootroot00000000000000
P
P
X
X
error
error
type=direct
type=direct
C1
C1
C2
C2
amqp.gen-S9b...
amqp.gen-S9b...
amqp.gen-Ag1...
amqp.gen-Ag1...
info
info
error
error
warning
warning
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-one-overall.svg000066400000000000000000000106501433544061600252340ustar00rootroot00000000000000
P
P
C
C
hello
hello
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-six.svg000066400000000000000000000256301433544061600236200ustar00rootroot00000000000000
Request
reply_to=amqp.gen-Xa2...
correlation_id=abc
Request...
Reply
correlation_id=abc
Reply...
C
C
S
S
Server
Server
Client
Client
rpc_queue
rpc_queue
amqp.gen-Xa2...
amqp.gen-Xa2...
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-three-overall.svg000066400000000000000000000210161433544061600255600ustar00rootroot00000000000000
P
P
amq.gen-Rq6...
amq.gen-Rq6...
amq.gen-As8...
amq.gen-As8...
X
X
C1
C1
C2
C2
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/python-two.svg000066400000000000000000000115101433544061600236160ustar00rootroot00000000000000
P
P
C1
C1
C2
C2
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/queue.svg000066400000000000000000000040731433544061600226200ustar00rootroot00000000000000
queue_name
queue_name
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/receiving.svg000066400000000000000000000063651433544061600234550ustar00rootroot00000000000000
C
C
hello
hello
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_static/tutorial/sending.svg000066400000000000000000000063601433544061600231240ustar00rootroot00000000000000
P
P
hello
hello
Text is not SVG - cannot display
aio-pika-8.2.5/docs/source/_templates/000077500000000000000000000000001433544061600176135ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/_templates/layout.html000066400000000000000000000030701433544061600220160ustar00rootroot00000000000000{% extends "!layout.html" %} {% block extrahead %} {{ super() }} {% endblock %} aio-pika-8.2.5/docs/source/apidoc.rst000066400000000000000000000004331433544061600174470ustar00rootroot00000000000000API Reference ============= .. automodule:: aio_pika :members: .. autoclass:: aio_pika.patterns.base :members: .. autoclass:: aio_pika.patterns.Master :members: .. autoclass:: aio_pika.patterns.Worker :members: .. autoclass:: aio_pika.patterns.RPC :members: aio-pika-8.2.5/docs/source/conf.py000066400000000000000000000163521433544061600167640ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # aio-pika documentation build configuration file, created by # sphinx-quickstart on Fri Mar 31 17:03:20 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import datetime import os import sys from importlib.machinery import SourceFileLoader sys.path.insert(0, os.path.abspath(os.path.dirname("__file__"))) module = SourceFileLoader( "version", os.path.join( os.path.dirname(os.path.abspath(__file__)), "..", "..", "aio_pika", "version.py", ), ).load_module() autoclass_content = "both" # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.coverage", "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "aio-pika" copyright = "{}, Dmitry Orlov".format(datetime.datetime.now().year) author = "Dmitry Orlov" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = ".".join(map(str, module.version_info[:-1])) # The full version, including alpha/beta/rc tags. release = ".".join(map(str, module.version_info)) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} html_theme_options = { "sidebar_hide_name": True, "source_repository": "https://github.com/mosquito/aio-pika/", "source_branch": "master", "source_directory": "docs/source", "footer_icons": [ { "name": "GitHub", "url": "https://github.com/mosquito/aio-pika", "html": ( """""" """""" ), "class": "", }, ], } html_title = "Wrapper for the aiormq for asyncio and humans" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = "aio-pikadoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'a4paper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '12pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "aio-pika.tex", "aio-pika Documentation", "Dmitry Orlov", "manual", ), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "aio-pika", "aio-pika Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "aio-pika", "aio-pika Documentation", author, "aio-pika", "One line description of project.", "Miscellaneous", ), ] # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] html_logo = "_static/logo2x.png" html_favicon = "_static/icon.png" # html_sidebars = {"**": ["about.html", "navigation.html", "searchbox.html"]} aio-pika-8.2.5/docs/source/examples/000077500000000000000000000000001433544061600172745ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/examples/extend-patterns.py000066400000000000000000000010701433544061600227710ustar00rootroot00000000000000from typing import Any import msgpack # type: ignore from aio_pika.patterns import RPC, Master class MsgpackRPC(RPC): CONTENT_TYPE = "application/msgpack" def serialize(self, data: Any) -> bytes: return msgpack.dumps(data) def deserialize(self, data: bytes) -> bytes: return msgpack.loads(data) class MsgpackMaster(Master): CONTENT_TYPE = "application/msgpack" def serialize(self, data: Any) -> bytes: return msgpack.dumps(data) def deserialize(self, data: bytes) -> bytes: return msgpack.loads(data) aio-pika-8.2.5/docs/source/examples/external-credentials.py000066400000000000000000000014661433544061600237720ustar00rootroot00000000000000import asyncio import ssl import aio_pika from aio_pika.abc import SSLOptions async def main() -> None: connection = await aio_pika.connect_robust( host="127.0.0.1", login="", ssl=True, ssl_options=SSLOptions( cafile="cacert.pem", certfile="cert.pem", keyfile="key.pem", no_verify_ssl=ssl.CERT_REQUIRED, ), client_properties={"connection_name": "aio-pika external credentials"}, ) async with connection: routing_key = "test_queue" channel = await connection.channel() await channel.default_exchange.publish( aio_pika.Message(body="Hello {}".format(routing_key).encode()), routing_key=routing_key, ) if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/log-level-set.py000066400000000000000000000001151433544061600223220ustar00rootroot00000000000000import logging from aio_pika import logger logger.setLevel(logging.ERROR) aio-pika-8.2.5/docs/source/examples/main.py000066400000000000000000000022311433544061600205700ustar00rootroot00000000000000import asyncio from aio_pika import Message, connect_robust from aio_pika.abc import AbstractIncomingMessage async def main() -> None: connection = await connect_robust( "amqp://guest:guest@127.0.0.1/?name=aio-pika%20example", ) queue_name = "test_queue" routing_key = "test_queue" # Creating channel channel = await connection.channel() # Declaring exchange exchange = await channel.declare_exchange("direct", auto_delete=True) # Declaring queue queue = await channel.declare_queue(queue_name, auto_delete=True) # Binding queue await queue.bind(exchange, routing_key) await exchange.publish( Message( bytes("Hello", "utf-8"), content_type="text/plain", headers={"foo": "bar"}, ), routing_key, ) # Receiving message if incoming_message := await queue.get(timeout=5, fail=False): # Confirm message await incoming_message.ack() else: print("Queue empty") await queue.unbind(exchange, routing_key) await queue.delete() await connection.close() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/master.py000066400000000000000000000014241433544061600211420ustar00rootroot00000000000000import asyncio from aio_pika import connect_robust from aio_pika.patterns import Master async def main() -> None: connection = await connect_robust( "amqp://guest:guest@127.0.0.1/?name=aio-pika%20master", ) async with connection: # Creating channel channel = await connection.channel() master = Master(channel) # Creates tasks by proxy object for task_id in range(1000): await master.proxy.my_task_name(task_id=task_id) # Or using create_task method for task_id in range(1000): await master.create_task( "my_task_name", kwargs=dict(task_id=task_id), ) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) aio-pika-8.2.5/docs/source/examples/pooling.py000066400000000000000000000031001433544061600213070ustar00rootroot00000000000000import asyncio import aio_pika from aio_pika.abc import AbstractRobustConnection from aio_pika.pool import Pool async def main() -> None: loop = asyncio.get_event_loop() async def get_connection() -> AbstractRobustConnection: return await aio_pika.connect_robust("amqp://guest:guest@localhost/") connection_pool: Pool = Pool(get_connection, max_size=2, loop=loop) async def get_channel() -> aio_pika.Channel: async with connection_pool.acquire() as connection: return await connection.channel() channel_pool: Pool = Pool(get_channel, max_size=10, loop=loop) queue_name = "pool_queue" async def consume() -> None: async with channel_pool.acquire() as channel: # type: aio_pika.Channel await channel.set_qos(10) queue = await channel.declare_queue( queue_name, durable=False, auto_delete=False, ) async with queue.iterator() as queue_iter: async for message in queue_iter: print(message) await message.ack() async def publish() -> None: async with channel_pool.acquire() as channel: # type: aio_pika.Channel await channel.default_exchange.publish( aio_pika.Message(("Channel: %r" % channel).encode()), queue_name, ) async with connection_pool, channel_pool: task = loop.create_task(consume()) await asyncio.wait([publish() for _ in range(50)]) await task if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/rpc-callee.py000066400000000000000000000011641433544061600216570ustar00rootroot00000000000000import asyncio from aio_pika import connect_robust from aio_pika.patterns import RPC async def multiply(*, x: int, y: int) -> int: return x * y async def main() -> None: connection = await connect_robust( "amqp://guest:guest@127.0.0.1/", client_properties={"connection_name": "callee"}, ) # Creating channel channel = await connection.channel() rpc = await RPC.create(channel) await rpc.register("multiply", multiply, auto_delete=True) try: await asyncio.Future() finally: await connection.close() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/rpc-caller.py000066400000000000000000000013161433544061600216730ustar00rootroot00000000000000import asyncio from aio_pika import connect_robust from aio_pika.patterns import RPC async def main() -> None: connection = await connect_robust( "amqp://guest:guest@127.0.0.1/", client_properties={"connection_name": "caller"}, ) async with connection: # Creating channel channel = await connection.channel() rpc = await RPC.create(channel) # Creates tasks by proxy object for i in range(1000): print(await rpc.proxy.multiply(x=100, y=i)) # Or using create_task method for i in range(1000): print(await rpc.call("multiply", kwargs=dict(x=100, y=i))) if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/simple_async_consumer.py000066400000000000000000000015331433544061600242510ustar00rootroot00000000000000import asyncio import aio_pika async def process_message( message: aio_pika.abc.AbstractIncomingMessage, ) -> None: async with message.process(): print(message.body) await asyncio.sleep(1) async def main() -> None: connection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", ) queue_name = "test_queue" # Creating channel channel = await connection.channel() # Maximum message count which will be processing at the same time. await channel.set_qos(prefetch_count=100) # Declaring queue queue = await channel.declare_queue(queue_name, auto_delete=True) await queue.consume(process_message) try: # Wait until terminate await asyncio.Future() finally: await connection.close() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/simple_consumer.py000066400000000000000000000015601433544061600230540ustar00rootroot00000000000000import asyncio import logging import aio_pika async def main() -> None: logging.basicConfig(level=logging.DEBUG) connection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", ) queue_name = "test_queue" async with connection: # Creating channel channel = await connection.channel() # Will take no more than 10 messages in advance await channel.set_qos(prefetch_count=10) # Declaring queue queue = await channel.declare_queue(queue_name, auto_delete=True) async with queue.iterator() as queue_iter: async for message in queue_iter: async with message.process(): print(message.body) if queue.name in message.body.decode(): break if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/simple_publisher.py000066400000000000000000000007411433544061600232160ustar00rootroot00000000000000import asyncio import aio_pika async def main() -> None: connection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", ) async with connection: routing_key = "test_queue" channel = await connection.channel() await channel.default_exchange.publish( aio_pika.Message(body=f"Hello {routing_key}".encode()), routing_key=routing_key, ) if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/simple_publisher_transactions.py000066400000000000000000000027551433544061600260150ustar00rootroot00000000000000import asyncio import aio_pika async def main() -> None: connection = await aio_pika.connect_robust( "amqp://guest:guest@127.0.0.1/", ) async with connection: routing_key = "test_queue" # Transactions conflicts with `publisher_confirms` channel = await connection.channel(publisher_confirms=False) # Use transactions with async context manager async with channel.transaction(): # Publishing messages but delivery will not be done # before committing this transaction for i in range(10): message = aio_pika.Message(body="Hello #{}".format(i).encode()) await channel.default_exchange.publish( message, routing_key=routing_key, ) # Using transactions manually tx = channel.transaction() # start transaction manually await tx.select() await channel.default_exchange.publish( aio_pika.Message(body="Hello {}".format(routing_key).encode()), routing_key=routing_key, ) await tx.commit() # Using transactions manually tx = channel.transaction() # start transaction manually await tx.select() await channel.default_exchange.publish( aio_pika.Message(body="Should be rejected".encode()), routing_key=routing_key, ) await tx.rollback() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/tornado-pubsub.py000066400000000000000000000025251433544061600226160ustar00rootroot00000000000000import asyncio import tornado.ioloop import tornado.web from aio_pika import Message, connect_robust class Base: QUEUE: asyncio.Queue class SubscriberHandler(tornado.web.RequestHandler, Base): async def get(self) -> None: message = await self.QUEUE.get() await self.finish(message.body) class PublisherHandler(tornado.web.RequestHandler): async def post(self) -> None: connection = self.application.settings["amqp_connection"] channel = await connection.channel() try: await channel.default_exchange.publish( Message(body=self.request.body), routing_key="test", ) finally: await channel.close() await self.finish("OK") async def make_app() -> tornado.web.Application: amqp_connection = await connect_robust() channel = await amqp_connection.channel() queue = await channel.declare_queue("test", auto_delete=True) Base.QUEUE = asyncio.Queue() await queue.consume(Base.QUEUE.put, no_ack=True) return tornado.web.Application( [(r"/publish", PublisherHandler), (r"/subscribe", SubscriberHandler)], amqp_connection=amqp_connection, ) async def main() -> None: app = await make_app() app.listen(8888) await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/examples/worker.py000066400000000000000000000016471433544061600211670ustar00rootroot00000000000000import asyncio from aio_pika import connect_robust from aio_pika.abc import AbstractConnection from aio_pika.patterns import Master, NackMessage, RejectMessage async def worker(*, task_id: int) -> None: # If you want to reject message or send # nack you might raise special exception if task_id % 2 == 0: raise RejectMessage(requeue=False) if task_id % 2 == 1: raise NackMessage(requeue=False) print(task_id) async def main() -> None: connection = await connect_robust( "amqp://guest:guest@127.0.0.1/?name=aio-pika%20worker", ) # Creating channel channel = await connection.channel() # Initializing Master with channel master = Master(channel) await master.create_worker("my_task_name", worker, auto_delete=True) try: await asyncio.Future() finally: await connection.close() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/index.rst000066400000000000000000000126061433544061600173240ustar00rootroot00000000000000.. aio-pika documentation master file, created by sphinx-quickstart on Fri Mar 31 17:03:20 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. _aio-pika: https://github.com/mosquito/aio-pika .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _aiormq: http://github.com/mosquito/aiormq/ Welcome to aio-pika's documentation! ==================================== .. image:: https://coveralls.io/repos/github/mosquito/aio-pika/badge.svg?branch=master :target: https://coveralls.io/github/mosquito/aio-pika :alt: Coveralls .. image:: https://travis-ci.org/mosquito/aio-pika.svg :target: https://travis-ci.org/mosquito/aio-pika :alt: Travis CI .. image:: https://img.shields.io/pypi/v/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ :alt: Latest Version .. image:: https://img.shields.io/pypi/wheel/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ .. image:: https://img.shields.io/pypi/pyversions/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ .. image:: https://img.shields.io/pypi/l/aio-pika.svg :target: https://pypi.python.org/pypi/aio-pika/ `aio-pika`_ is a wrapper for the `aiormq`_ for `asyncio`_ and humans. Features ++++++++ * Completely asynchronous API. * Object oriented API. * Transparent auto-reconnects with complete state recovery with `connect_robust` (e.g. declared queues or exchanges, consuming state and bindings). * Python 3.6+ compatible. * For python 3.5 users available `aio-pika<7` * Transparent `publisher confirms`_ support * `Transactions`_ support * Completely type-hints coverage. .. _publisher confirms: https://www.rabbitmq.com/confirms.html .. _Transactions: https://www.rabbitmq.com/semantics.html#tx Installation ++++++++++++ Installation with pip: .. code-block:: shell pip install aio-pika Installation from git: .. code-block:: shell # via pip pip install https://github.com/mosquito/aio-pika/archive/master.zip # manually git clone https://github.com/mosquito/aio-pika.git cd aio-pika python setup.py install Development +++++++++++ Clone the project: .. code-block:: shell git clone https://github.com/mosquito/aio-pika.git cd aio-pika Create a new virtualenv for `aio-pika`_: .. code-block:: shell virtualenv -p python3.5 env Install all requirements for `aio-pika`_: .. code-block:: shell env/bin/pip install -e '.[develop]' Table Of Contents +++++++++++++++++ .. toctree:: :glob: :maxdepth: 3 quick-start patterns rabbitmq-tutorial/index apidoc Thanks for contributing +++++++++++++++++++++++ * `@mosquito`_ (author) * `@decaz`_ (steel persuasiveness while code review) * `@heckad`_ (bug fixes) * `@smagafurov`_ (bug fixes) * `@hellysmile`_ (bug fixes and ideas) * `@altvod`_ (bug fixes) * `@alternativehood`_ (bugfixes) * `@cprieto`_ (bug fixes) * `@akhoronko`_ (bug fixes) * `@iselind`_ (bug fixes) * `@DXist`_ (bug fixes) * `@blazewicz`_ (bug fixes) * `@chibby0ne`_ (bug fixes) * `@jmccarrell`_ (bug fixes) * `@taybin`_ (bug fixes) * `@ollamh`_ (bug fixes) * `@DriverX`_ (bug fixes) * `@brianmedigate`_ (bug fixes) * `@dan-stone`_ (bug fixes) * `@Kludex`_ (bug fixes) * `@bmario`_ (bug fixes) * `@tzoiker`_ (bug fixes) * `@Pehat`_ (bug fixes) * `@WindowGenerator`_ (bug fixes) * `@dhontecillas`_ (bug fixes) * `@tilsche`_ (bug fixes) * `@leenr`_ (bug fixes) * `@la0rg`_ (bug fixes) * `@SolovyovAlexander`_ (bug fixes) * `@kremius`_ (bug fixes) * `@zyp`_ (bug fixes) * `@kajetanj`_ (bug fixes) * `@Alviner`_ (moral support, debug sessions and good mood) * `@Pavkazzz`_ (composure, and patience while debug sessions) * `@bbrodriges`_ (supplying grammar while writing documentation) * `@dizballanze`_ (review, grammar) .. _@mosquito: https://github.com/mosquito .. _@decaz: https://github.com/decaz .. _@heckad: https://github.com/heckad .. _@smagafurov: https://github.com/smagafurov .. _@hellysmile: https://github.com/hellysmile .. _@altvod: https://github.com/altvod .. _@alternativehood: https://github.com/alternativehood .. _@cprieto: https://github.com/cprieto .. _@akhoronko: https://github.com/akhoronko .. _@iselind: https://github.com/iselind .. _@DXist: https://github.com/DXist .. _@blazewicz: https://github.com/blazewicz .. _@chibby0ne: https://github.com/chibby0ne .. _@jmccarrell: https://github.com/jmccarrell .. _@taybin: https://github.com/taybin .. _@ollamh: https://github.com/ollamh .. _@DriverX: https://github.com/DriverX .. _@brianmedigate: https://github.com/brianmedigate .. _@dan-stone: https://github.com/dan-stone .. _@Kludex: https://github.com/Kludex .. _@bmario: https://github.com/bmario .. _@tzoiker: https://github.com/tzoiker .. _@Pehat: https://github.com/Pehat .. _@WindowGenerator: https://github.com/WindowGenerator .. _@dhontecillas: https://github.com/dhontecillas .. _@tilsche: https://github.com/tilsche .. _@leenr: https://github.com/leenr .. _@la0rg: https://github.com/la0rg .. _@SolovyovAlexander: https://github.com/SolovyovAlexander .. _@kremius: https://github.com/kremius .. _@zyp: https://github.com/zyp .. _@kajetanj: https://github.com/kajetanj .. _@Alviner: https://github.com/Alviner .. _@Pavkazzz: https://github.com/Pavkazzz .. _@bbrodriges: https://github.com/bbrodriges .. _@dizballanze: https://github.com/dizballanze Versioning ========== This software follows `Semantic Versioning`_ .. _Semantic Versioning: http://semver.org/ aio-pika-8.2.5/docs/source/patterns.rst000066400000000000000000000023711433544061600200530ustar00rootroot00000000000000.. _aio-pika: https://github.com/mosquito/aio-pika Patterns and helpers ++++++++++++++++++++ .. note:: Available since `aio-pika>=1.7.0` `aio-pika`_ includes some useful patterns for creating distributed systems. .. _patterns-worker: Master/Worker ~~~~~~~~~~~~~ Helper which implements Master/Worker pattern. This applicable for balancing tasks between multiple workers. The master creates tasks: .. literalinclude:: examples/master.py :language: python Worker code: .. literalinclude:: examples/worker.py :language: python The one or multiple workers executes tasks. .. _patterns-rpc: RPC ~~~ Helper which implements Remote Procedure Call pattern. This applicable for balancing tasks between multiple workers. The caller creates tasks and awaiting results: .. literalinclude:: examples/rpc-caller.py :language: python One or multiple callees executing tasks: .. literalinclude:: examples/rpc-callee.py :language: python Extending ~~~~~~~~~ Both patterns serialization behaviour might be changed by inheritance and redefinition of methods :func:`aio_pika.patterns.base.serialize` and :func:`aio_pika.patterns.base.deserialize`. Following examples demonstrates it: .. literalinclude:: examples/extend-patterns.py :language: python aio-pika-8.2.5/docs/source/quick-start.rst000066400000000000000000000025261433544061600204640ustar00rootroot00000000000000Quick start +++++++++++ Some useful examples. Simple consumer ~~~~~~~~~~~~~~~ .. literalinclude:: examples/simple_consumer.py :language: python Simple publisher ~~~~~~~~~~~~~~~~ .. literalinclude:: examples/simple_publisher.py :language: python Asynchronous message processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/simple_async_consumer.py :language: python Working with RabbitMQ transactions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/simple_publisher_transactions.py :language: python Get single message example ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/main.py :language: python Set logging level ~~~~~~~~~~~~~~~~~ Sometimes you want to see only your debug logs, but when you just call `logging.basicConfig(logging.DEBUG)` you set the debug log level for all loggers, includes all aio_pika's modules. If you want to set logging level independently see following example: .. literalinclude:: examples/log-level-set.py :language: python Tornado example ~~~~~~~~~~~~~~~ .. literalinclude:: examples/tornado-pubsub.py :language: python External credentials example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/external-credentials.py :language: python Connection pooling ~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/pooling.py :language: python aio-pika-8.2.5/docs/source/rabbitmq-tutorial/000077500000000000000000000000001433544061600211205ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/1-introduction.rst000066400000000000000000000241661433544061600245420ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-one-python.html .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _asyncio tutorial: https://pymotw.com/3/asyncio/coroutines.html .. _introduction: Introduction ============ .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. It is expected that you are familiar with the basics of `asyncio`_. Anyway following examples work as written. You feel free to download them and test it as is without any changes (in case your RabbitMQ installation allows access for user "guest"). Otherwise we recommend to read `asyncio tutorial`_. .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users RabbitMQ is a message broker. The principal idea is pretty simple: it accepts and forwards messages. You can think about it as a post office: when you send mail to the post box you're pretty sure that Mr. Postman will eventually deliver the mail to your recipient. Using this metaphor RabbitMQ is a post box, a post office and a postman. The major difference between RabbitMQ and the post office is the fact that it doesn't deal with paper, instead it accepts, stores and forwards binary blobs of data ‒ messages. RabbitMQ, and messaging in general, uses some jargon. * Producing means nothing more than sending. A program that sends messages is a producer. We'll draw it like that, with "P": .. image:: /_static/tutorial/producer.svg :align: center * A queue is the name for a mailbox. It lives inside RabbitMQ. Although messages flow through RabbitMQ and your applications, they can be stored only inside a queue. A queue is not bound by any limits, it can store as many messages as you like ‒ it's essentially an infinite buffer. Many producers can send messages that go to one queue, many consumers can try to receive data from one queue. A queue will be drawn as like that, with its name above it: .. image:: /_static/tutorial/queue.svg :align: center * Consuming has a similar meaning to receiving. A consumer is a program that mostly waits to receive messages. On our drawings it's shown with "C": .. image:: /_static/tutorial/consumer.svg :align: center .. note:: Note that the producer, consumer, and broker do not have to reside on the same machine; indeed in most applications they don't. Hello World! ++++++++++++ .. note:: Using the `aio-pika`_ async Python client Our "Hello world" won't be too complex ‒ let's send a message, receive it and print it on the screen. To do so we need two programs: one that sends a message and one that receives and prints it. Our overall design will look like: .. image:: /_static/tutorial/python-one-overall.svg :align: center Producer sends messages to the "hello" queue. The consumer receives messages from that queue. .. note:: **RabbitMQ libraries** RabbitMQ speaks AMQP 0.9.1, which is an open, general-purpose protocol for messaging. There are a number of clients for RabbitMQ in `many different languages`_. In this tutorial series we're going to use `aio-pika`_, which is the Python client recommended by the RabbitMQ team. To install it you can use the `pip`_ package management tool. .. _many different languages: https://www.rabbitmq.com/devtools.html .. _pip: https://pip.pypa.io/en/stable/quickstart/ Sending +++++++ .. image:: /_static/tutorial/sending.svg :align: center Our first program *send.py* will send a single message to the queue. The first thing we need to do is to establish a connection with RabbitMQ server. .. literalinclude:: examples/1-introduction/send.py :language: python :lines: 5-8 We're connected now, to a broker on the local machine - hence the localhost. If we wanted to connect to a broker on a different machine we'd simply specify its name or IP address here. Next, before sending we need to make sure the recipient queue exists. If we send a message to non-existing location, RabbitMQ will just trash the message. Let's create a queue to which the message will be delivered, let's name it *hello*: .. literalinclude:: examples/1-introduction/send.py :language: python :lines: 14-15 At that point we're ready to send a message. Our first message will just contain a string Hello World! and we want to send it to our hello queue. In RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange. But let's not get dragged down by the details ‒ you can read more about exchanges in the :ref:`third part of this tutorial `. All we need to know now is how to use a default exchange identified by an empty string. This exchange is special ‒ it allows us to specify exactly to which queue the message should go. The queue name needs to be specified in the *routing_key* parameter: .. literalinclude:: examples/1-introduction/send.py :language: python :lines: 17-21 Before exiting the program we need to make sure the network buffers were flushed and our message was actually delivered to RabbitMQ. We can do it by gently closing the connection. In this example async context manager has been used. .. literalinclude:: examples/1-introduction/send.py :language: python :lines: 10-12 .. note:: *Sending doesn't work!* If this is your first time using RabbitMQ and you don't see the "Sent" message then you may be left scratching your head wondering what could be wrong. Maybe the broker was started without enough free disk space (by default it needs at least 1Gb free) and is therefore refusing to accept messages. Check the broker logfile to confirm and reduce the limit if necessary. The `configuration file documentation`_ will show you how to set *disk_free_limit*. .. _configuration file documentation: http://www.rabbitmq.com/configure.html#config-items Receiving +++++++++ .. image:: /_static/tutorial/receiving.svg :align: center Our second program *receive.py* will receive messages from the queue and print them on the screen. Again, first we need to connect to RabbitMQ server. The code responsible for connecting to Rabbit is the same as previously. The next step, just like before, is to make sure that the queue exists. Creating a queue using *queue_declare* is idempotent ‒ we can run the command as many times as we like, and only one will be created. .. literalinclude:: examples/1-introduction/receive.py :language: python :lines: 22-28 You may ask why we declare the queue again ‒ we have already declared it in our previous code. We could avoid that if we were sure that the queue already exists. For example if *send.py* program was run before. But we're not yet sure which program to run first. In such cases it's a good practice to repeat declaring the queue in both programs. .. note:: **Listing queues** You may wish to see what queues RabbitMQ has and how many messages are in them. You can do it (as a privileged user) using the rabbitmqctl tool: :: $ sudo rabbitmqctl list_queues Listing queues ... hello 0 ...done. (omit sudo on Windows) Receiving messages from the queue is simple. It works by subscribing a `callback function` to a queue or using `simple get`. Whenever we receive a message, this callback function is called by the `aio-pika`_ library. In our case this function will print on the screen the contents of the message. .. literalinclude:: examples/1-introduction/receive.py :language: python :pyobject: on_message Next, we need to tell RabbitMQ that this particular callback function should receive messages from our hello queue: .. literalinclude:: examples/1-introduction/receive.py :language: python :pyobject: main The *no_ack* parameter will be described :ref:`later on `. Putting it all together +++++++++++++++++++++++ Full code for :download:`send.py `: .. literalinclude:: examples/1-introduction/send.py :language: python Full :download:`receive.py ` code: .. literalinclude:: examples/1-introduction/receive.py :language: python Now we can try out our programs in a terminal. First, let's send a message using our send.py program:: $ python send.py [x] Sent 'Hello World!' The producer program send.py will stop after every run. Let's receive it:: $ python receive.py [x] Received message IncomingMessage:{ "app_id": null, "body_size": 12, "cluster_id": null, "consumer_tag": "ctag1.11fa33f5f4fa41f6a6488648181656e0", "content_encoding": null, "content_type": null, "correlation_id": "b'None'", "delivery_mode": 1, "delivery_tag": 1, "exchange": "", "expiration": null, "headers": null, "message_id": null, "priority": null, "redelivered": false, "reply_to": null, "routing_key": "hello", "synchronous": false, "timestamp": null, "type": "None", "user_id": null } Message body is: b'Hello World!' Hurray! We were able to send our first message through RabbitMQ. As you might have noticed, the *receive.py* program doesn't exit. It will stay ready to receive further messages, and may be interrupted with **Ctrl-C**. Try to run *send.py* again in a new terminal. We've learned how to send and receive a message from a named queue. It's time to move on to :ref:`part 2 ` and build a simple work queue. .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/2-work-queues.rst000066400000000000000000000276011433544061600243060ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-two-python.html .. _work-queues: Work Queues =========== .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. This implementation is a part of official tutorial. Since version 1.7.0 `aio-pika`_ has :ref:`patterns submodule `. You might use :class:`aio_pika.patterns.Master` for real projects. .. note:: Using the `aio-pika`_ async Python client .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users .. image:: /_static/tutorial/python-two.svg :align: center In the :ref:`first tutorial ` we wrote programs to send and receive messages from a named queue. In this one we'll create a Work Queue that will be used to distribute time-consuming tasks among multiple workers. The main idea behind Work Queues (aka: *Task Queues*) is to avoid doing a resource-intensive task immediately and having to wait for it to complete. Instead we schedule the task to be done later. We encapsulate a task as a message and send it to the queue. A worker process running in the background will pop the tasks and eventually execute the job. When you run many workers the tasks will be shared between them. This concept is especially useful in web applications where it's impossible to handle a complex task during a short HTTP request window. Preparation +++++++++++ In the previous part of this tutorial we sent a message containing `"Hello World!"`. Now we'll be sending strings that stand for complex tasks. We don't have a real-world task, like images to be resized or pdf files to be rendered, so let's fake it by just pretending we're busy - by using the time.sleep() function. We'll take the number of dots in the string as its complexity; every dot will account for one second of "work". For example, a fake task described by Hello... will take three seconds. We will slightly modify the send.py code from our previous example, to allow arbitrary messages to be sent from the command line. This program will schedule tasks to our work queue, so let's name it *new_task.py*: .. literalinclude:: examples/2-work-queues/new_task.py :language: python :pyobject: main Our old receive.py script also requires some changes: it needs to fake a second of work for every dot in the message body. It will pop messages from the queue and perform the task, so let's call it *worker.py*: .. literalinclude:: examples/2-work-queues/worker.py :language: python :pyobject: on_message Round-robin dispatching +++++++++++++++++++++++ One of the advantages of using a Task Queue is the ability to easily parallelise work. If we are building up a backlog of work, we can just add more workers and that way, scale easily. First, let's try to run two *worker.py* scripts at the same time. They will both get messages from the queue, but how exactly? Let's see. You need three consoles open. Two will run the worker.py script. These consoles will be our two consumers - C1 and C2. :: shell1$ python worker.py [*] Waiting for messages. To exit press CTRL+C :: shell2$ python worker.py [*] Waiting for messages. To exit press CTRL+C In the third one we'll publish new tasks. Once you've started the consumers you can publish a few messages:: shell3$ python new_task.py First message. shell3$ python new_task.py Second message.. shell3$ python new_task.py Third message... shell3$ python new_task.py Fourth message.... shell3$ python new_task.py Fifth message..... Let's see what is delivered to our workers:: shell1$ python worker.py [*] Waiting for messages. To exit press CTRL+C [x] Received 'First message.' [x] Received 'Third message...' [x] Received 'Fifth message.....' :: shell2$ python worker.py [*] Waiting for messages. To exit press CTRL+C [x] Received 'Second message..' [x] Received 'Fourth message....' By default, RabbitMQ will send each message to the next consumer, in sequence. On average every consumer will get the same number of messages. This way of distributing messages is called round-robin. Try this out with three or more workers. Message acknowledgment ++++++++++++++++++++++ Doing a task can take a few seconds. You may wonder what happens if one of the consumers starts a long task and dies with it only partly done. With our current code once RabbitMQ delivers message to the customer it immediately removes it from memory. In this case, if you kill a worker we will lose the message it was just processing. We'll also lose all the messages that were dispatched to this particular worker but were not yet handled. But we don't want to lose any tasks. If a worker dies, we'd like the task to be delivered to another worker. In order to make sure a message is never lost, RabbitMQ supports message acknowledgments. An ack(nowledgement) is sent back from the consumer to tell RabbitMQ that a particular message had been received, processed and that RabbitMQ is free to delete it. If a consumer dies (its channel is closed, connection is closed, or TCP connection is lost) without sending an ack, RabbitMQ will understand that a message wasn't processed fully and will re-queue it. If there are other consumers online at the same time, it will then quickly redeliver it to another consumer. That way you can be sure that no message is lost, even if the workers occasionally die. There aren't any message timeouts; RabbitMQ will redeliver the message when the consumer dies. It's fine even if processing a message takes a very, very long time. Message acknowledgments are turned on by default. In previous examples we explicitly turned them off via the `no_ack=True` flag. It's time to remove this flag and send a proper acknowledgment from the worker, once we're done with a task. .. code-block:: python async def on_message(message: IncomingMessage): print(" [x] Received %r" % message.body) await asyncio.sleep(message.body.count(b'.'), loop=loop) print(" [x] Done") await message.ack() or using special context processor: .. literalinclude:: examples/2-work-queues/worker.py :language: python :lines: 8-10 If context processor will catch an exception, the message will be returned to the queue. Using this code we can be sure that even if you kill a worker using CTRL+C while it was processing a message, nothing will be lost. Soon after the worker dies all unacknowledged messages will be redelivered. .. note:: **Forgotten acknowledgment** It's a common mistake to miss the ack. It's an easy error, but the consequences are serious. Messages will be redelivered when your client quits (which may look like random redelivery), but RabbitMQ will eat more and more memory as it won't be able to release any unacked messages. In order to debug this kind of mistake you can use rabbitmqctl to print the messages_unacknowledged field:: $ sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged Listing queues ... hello 0 0 ...done. Message durability ++++++++++++++++++ We have learned how to make sure that even if the consumer dies, the task isn't lost. But our tasks will still be lost if RabbitMQ server stops. When RabbitMQ quits or crashes it will forget the queues and messages unless you tell it not to. Two things are required to make sure that messages aren't lost: we need to mark both the queue and messages as durable. First, we need to make sure that RabbitMQ will never lose our queue. In order to do so, we need to declare it as *durable*: .. literalinclude:: examples/2-work-queues/worker.py :language: python :lines: 23-26 Although this command is correct by itself, it won't work in our setup. That's because we've already defined a queue called hello which is not durable. RabbitMQ doesn't allow you to redefine an existing queue with different parameters and will return an error to any program that tries to do that. But there is a quick workaround - let's declare a queue with different name, for example task_queue: .. literalinclude:: examples/2-work-queues/worker.py :language: python :lines: 22-26 This queue_declare change needs to be applied to both the producer and consumer code. At that point we're sure that the task_queue queue won't be lost even if RabbitMQ restarts. Now we need to mark our messages as persistent - by supplying a delivery_mode property with a value `PERSISTENT` (see enum :class:`aio_pika.DeliveryMode`). .. literalinclude:: examples/2-work-queues/new_task.py :language: python :pyobject: main .. note:: **Note on message persistence** Marking messages as persistent doesn't fully guarantee that a message won't be lost. Although it tells RabbitMQ to save the message to disk, there is still a short time window when RabbitMQ has accepted a message and hasn't saved it yet. Also, RabbitMQ doesn't do fsync(2) for every message -- it may be just saved to cache and not really written to the disk. The persistence guarantees aren't strong, but it's more than enough for our simple task queue. If you need a stronger guarantee then you can use `publisher confirms`_. `aio-pika`_ supports `publisher confirms`_ out of the box. .. _publisher confirms: https://www.rabbitmq.com/confirms.html Fair dispatch +++++++++++++ You might have noticed that the dispatching still doesn't work exactly as we want. For example in a situation with two workers, when all odd messages are heavy and even messages are light, one worker will be constantly busy and the other one will do hardly any work. Well, RabbitMQ doesn't know anything about that and will still dispatch messages evenly. This happens because RabbitMQ just dispatches a message when the message enters the queue. It doesn't look at the number of unacknowledged messages for a consumer. It just blindly dispatches every n-th message to the n-th consumer. .. image:: /_static/tutorial/prefetch-count.svg :align: center In order to defeat that we can use the basic.qos method with the `prefetch_count=1` setting. This tells RabbitMQ not to give more than one message to a worker at a time. Or, in other words, don't dispatch a new message to a worker until it has processed and acknowledged the previous one. Instead, it will dispatch it to the next worker that is not still busy. .. literalinclude:: examples/2-work-queues/worker.py :language: python :lines: 17-20 .. note:: **Note about queue size** If all the workers are busy, your queue can fill up. You will want to keep an eye on that, and maybe add more workers, or have some other strategy. Putting it all together +++++++++++++++++++++++ Final code of our :download:`new_task.py ` script: .. literalinclude:: examples/2-work-queues/new_task.py :language: python And our :download:`worker.py `: .. literalinclude:: examples/2-work-queues/worker.py :language: python Using message acknowledgments and prefetch_count you can set up a work queue. The durability options let the tasks survive even if RabbitMQ is restarted. Now we can move on to :ref:`tutorial 3 ` and learn how to deliver the same message to many consumers. .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/3-publish-subscribe.rst000066400000000000000000000210431433544061600254370ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-three-python.html .. _publish-subscribe: Publish/Subscribe ================= .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. .. note:: Using the `aio-pika`_ async Python client .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users In the :ref:`previous tutorial ` we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we'll do something completely different — we'll deliver a message to multiple consumers. This pattern is known as "publish/subscribe". To illustrate the pattern, we're going to build a simple logging system. It will consist of two programs — the first will emit log messages and the second will receive and print them. In our logging system every running copy of the receiver program will get the messages. That way we'll be able to run one receiver and direct the logs to disk; and at the same time we'll be able to run another receiver and see the logs on the screen. Essentially, published log messages are going to be broadcast to all the receivers. Exchanges +++++++++ In previous parts of the tutorial we sent and received messages to and from a queue. Now it's time to introduce the full messaging model in Rabbit. Let's quickly go over what we covered in the previous tutorials: * A producer is a user application that sends messages. * A queue is a buffer that stores messages. * A consumer is a user application that receives messages. The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue. Actually, quite often the producer doesn't even know if a message will be delivered to any queue at all. Instead, the producer can only send messages to an exchange. An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type. .. image:: /_static/tutorial/exchanges.svg :align: center There are a few exchange types available: `DIRECT`, `TOPIC`, `HEADERS` and `FANOUT` (see :class:`aio_pika.ExchangeType`). We'll focus on the last one — the fanout. Let's create an exchange of that type, and call it `logs`: .. literalinclude:: examples/3-publish-subscribe/emit_log.py :language: python :lines: 15-17 The fanout exchange is very simple. As you can probably guess from the name, it just broadcasts all the messages it receives to all the queues it knows. And that's exactly what we need for our logger. .. note:: **Listing exchanges** To list the exchanges on the server you can run the ever useful rabbitmqctl:: $ sudo rabbitmqctl list_exchanges Listing exchanges ... logs fanout amq.direct direct amq.topic topic amq.fanout fanout amq.headers headers ...done. In this list there are some `amq.*` exchanges and the default (unnamed) exchange. These are created by default, but it is unlikely you'll need to use them at the moment. **Nameless exchange** In previous parts of the tutorial we knew nothing about exchanges, but still were able to send messages to queues. That was possible because we were using a default exchange, which we identify by the empty string (""). Recall how we published a message before: .. code-block:: python await channel.default_exchange.publish( Message(message_body), routing_key='hello', ) The exchange parameter is the name of the exchange. The empty string denotes the default or nameless exchange: messages are routed to the queue with the name specified by routing_key, if it exists. Now, we can publish to our named exchange instead: .. literalinclude:: examples/3-publish-subscribe/emit_log.py :language: python :lines: 19-29 Temporary queues ++++++++++++++++ As you may remember previously we were using queues which had a specified name (remember `hello` and `task_queue`?). Being able to name a queue was crucial for us — we needed to point the workers to the same queue. Giving a queue a name is important when you want to share the queue between producers and consumers. But that's not the case for our logger. We want to hear about all log messages, not just a subset of them. We're also interested only in currently flowing messages not in the old ones. To solve that we need two things. Firstly, whenever we connect to Rabbit we need a fresh, empty queue. To do it we could create a queue with a random name, or, even better - let the server choose a random queue name for us. We can do this by not supplying the queue parameter to `declare_queue`: .. code-block:: python queue = await channel.declare_queue() Secondly, once we disconnect the consumer the queue should be deleted. There's an exclusive flag for that: .. literalinclude:: examples/3-publish-subscribe/receive_logs.py :language: python :lines: 26 Bindings ++++++++ .. image:: /_static/tutorial/bindings.svg :align: center We've already created a fanout exchange and a queue. Now we need to tell the exchange to send messages to our queue. That relationship between exchange and a queue is called a binding. .. literalinclude:: examples/3-publish-subscribe/receive_logs.py :language: python :lines: 21-29 From now on the logs exchange will append messages to our queue. .. note:: **Listing bindings** You can list existing bindings using, you guessed it, `rabbitmqctl list_bindings`. Putting it all together +++++++++++++++++++++++ .. image:: /_static/tutorial/python-three-overall.svg :align: center The producer program, which emits log messages, doesn't look much different from the previous tutorial. The most important change is that we now want to publish messages to our logs exchange instead of the nameless one. We need to supply a routing_key when sending, but its value is ignored for fanout exchanges. Here goes the code for :download:`emit_log.py ` script: .. literalinclude:: examples/3-publish-subscribe/emit_log.py :language: python As you see, after establishing the connection we declared the exchange. This step is necessary as publishing to a non-existing exchange is forbidden. The messages will be lost if no queue is bound to the exchange yet, but that's okay for us; if no consumer is listening yet we can safely discard the message. The code for :download:`receive_logs.py ` script: .. literalinclude:: examples/3-publish-subscribe/receive_logs.py :language: python We're done. If you want to save logs to a file, just open a console and type:: $ python receive_logs.py > logs_from_rabbit.log If you wish to see the logs on your screen, spawn a new terminal and run:: $ python receive_logs.py And of course, to emit logs type:: $ python emit_log.py Using *rabbitmqctl list_bindings* you can verify that the code actually creates bindings and queues as we want. With two *receive_logs.py* programs running you should see something like:: $ sudo rabbitmqctl list_bindings Listing bindings ... logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue [] logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue [] ...done. The interpretation of the result is straightforward: data from exchange logs goes to two queues with server-assigned names. And that's exactly what we intended. To find out how to listen for a subset of messages, let's move on to :ref:`tutorial 4 ` .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/4-routing.rst000066400000000000000000000155001433544061600235030ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-four-python.html .. _routing: Routing ======= .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. .. note:: Using the `aio-pika`_ async Python client .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users In the :ref:`previous tutorial ` we built a simple logging system. We were able to broadcast log messages to many receivers. In this tutorial we're going to add a feature to it — we're going to make it possible to subscribe only to a subset of the messages. For example, we will be able to direct only critical error messages to the log file (to save disk space), while still being able to print all of the log messages on the console. Bindings ++++++++ In previous examples we were already creating bindings. You may recall code like: .. code-block:: python async def main(): ... # Binding the queue to the exchange await queue.bind(logs_exchange) ... A binding is a relationship between an exchange and a queue. This can be simply read as: the queue is interested in messages from this exchange. Bindings can take an extra *routing_key* parameter. To avoid the confusion with a *basic_publish* parameter we're going to call it a *binding key*. This is how we could create a binding with a key: .. code-block:: python async def main(): ... # Binding the queue to the exchange await queue.bind(logs_exchange, routing_key="black") ... The meaning of a binding key depends on the exchange type. The *fanout* exchanges, which we used previously, simply ignored its value. Direct exchange +++++++++++++++ Our logging system from the previous tutorial broadcasts all messages to all consumers. We want to extend that to allow filtering messages based on their severity. For example we may want the script which is writing log messages to the disk to only receive critical errors, and not waste disk space on warning or info log messages. We were using a fanout exchange, which doesn't give us too much flexibility — it's only capable of mindless broadcasting. We will use a direct exchange instead. The routing algorithm behind a direct exchange is simple — a message goes to the queues whose binding key exactly matches the routing key of the message. To illustrate that, consider the following setup: .. image:: /_static/tutorial/direct-exchange.svg :align: center In this setup, we can see the *direct* exchange X with two queues bound to it. The first queue is bound with binding key *orange*, and the second has two bindings, one with binding key *black* and the other one with *green*. In such a setup a message published to the exchange with a routing key *orange* will be routed to queue *Q1*. Messages with a routing key of *black* or *green* will go to *Q2*. All other messages will be discarded. Multiple bindings +++++++++++++++++ .. image:: /_static/tutorial/direct-exchange-multiple.svg :align: center It is perfectly legal to bind multiple queues with the same binding key. In our example we could add a binding between *X* and *Q1* with binding key *black*. In that case, the *direct* exchange will behave like fanout and will broadcast the message to all the matching queues. A message with routing key black will be delivered to both *Q1* and *Q2*. Emitting logs +++++++++++++ We'll use this model for our logging system. Instead of *fanout* we'll send messages to a *direct* exchange. We will supply the log severity as a *routing key*. That way the receiving script will be able to select the severity it wants to receive. Let's focus on emitting logs first. Like always we need to create an exchange first: .. code-block:: python from aio_pika import ExchangeType async def main(): ... direct_logs_exchange = await channel.declare_exchange( 'logs', ExchangeType.DIRECT ) And we're ready to send a message: .. code-block:: python async def main(): ... await direct_logs_exchange.publish( Message(message_body), routing_key=severity, ) To simplify things we will assume that `'severity'` can be one of `'info'`, `'warning'`, `'error'`. Subscribing +++++++++++ Receiving messages will work just like in the previous tutorial, with one exception - we're going to create a new binding for each severity we're interested in. .. code-block:: python async def main(): ... # Declaring queue queue = await channel.declare_queue(exclusive=True) # Binding the queue to the exchange await queue.bind(direct_logs_exchange, routing_key=severity) ... Putting it all together +++++++++++++++++++++++ .. image:: /_static/tutorial/python-four.svg :align: center The simplified code for :download:`receive_logs_direct_somple.py `: .. literalinclude:: examples/4-routing/receive_logs_direct_simple.py :language: python The code for :download:`emit_log_direct.py `: .. literalinclude:: examples/4-routing/emit_log_direct.py :language: python .. note:: The callback-based code for :download:`receive_logs_direct.py `: .. literalinclude:: examples/4-routing/receive_logs_direct.py :language: python If you want to save only *'warning'* and *'error'* (and not *'info'*) log messages to a file, just open a console and type:: $ python receive_logs_direct_simple.py warning error > logs_from_rabbit.log If you'd like to see all the log messages on your screen, open a new terminal and do:: $ python receive_logs_direct.py info warning error [*] Waiting for logs. To exit press CTRL+C And, for example, to emit an error log message just type:: $ python emit_log_direct.py error "Run. Run. Or it will explode." [x] Sent 'error':'Run. Run. Or it will explode.' Move on to :ref:`tutorial 5 ` to find out how to listen for messages based on a pattern. .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/5-topics.rst000066400000000000000000000137651433544061600233310ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _syslog: http://en.wikipedia.org/wiki/Syslog .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-five-python.html .. _topics: Topics ====== .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. .. note:: Using the `aio-pika`_ async Python client .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users In the :ref:`previous tutorial ` we improved our logging system. Instead of using a fanout exchange only capable of dummy broadcasting, we used a direct one, and gained a possibility of selectively receiving the logs. Although using the direct exchange improved our system, it still has limitations — it can't do routing based on multiple criteria. In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog_ unix tool, which routes logs based on both severity (`info`/`warn`/`crit`...) and facility (`auth`/`cron`/`kern`...). That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'. To implement that in our logging system we need to learn about a more complex topic exchange. Topic exchange ++++++++++++++ Messages sent to a topic exchange can't have an arbitrary *routing_key* - it must be a list of words, delimited by dots. The words can be anything, but usually they specify some features connected to the message. A few valid routing key examples: `"stock.usd.nyse"`, `"nyse.vmw"`, `"quick.orange.rabbit"`. There can be as many words in the routing key as you like, up to the limit of 255 bytes. The binding key must also be in the same form. The logic behind the topic exchange is similar to a direct one - a message sent with a particular routing key will be delivered to all the queues that are bound with a matching binding key. However there are two important special cases for binding keys: * `*` (star) can substitute for exactly one word. * `#` (hash) can substitute for zero or more words. It's easiest to explain this in an example: .. image:: /_static/tutorial/python-five.svg :align: center In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe a celerity, second a colour and third a species: `".."`. We created three bindings: *Q1* is bound with binding key `"*.orange.*"` and Q2 with `"*.*.rabbit"` and `"lazy.#"`. These bindings can be summarised as: * Q1 is interested in all the orange animals. * Q2 wants to hear everything about rabbits, and everything about lazy animals. * A message with a routing key set to `"quick.orange.rabbit"` will be delivered to both queues. Message `"lazy.orange.elephant"` also will go to both of them. On the other hand `"quick.orange.fox"` will only go to the first queue, and `"lazy.brown.fox"` only to the second. `"lazy.pink.rabbit"` will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded. What happens if we break our contract and send a message with one or four words, like `"orange"` or `"quick.orange.male.rabbit"`? Well, these messages won't match any bindings and will be lost. On the other hand `"lazy.orange.male.rabbit"`, even though it has four words, will match the last binding and will be delivered to the second queue. .. note:: **Topic exchange** Topic exchange is powerful and can behave like other exchanges. When a queue is bound with `"#"` (hash) binding key - it will receive all the messages, regardless of the routing key - like in fanout exchange. When special characters `"*"` (star) and `"#"` (hash) aren't used in bindings, the topic exchange will behave just like a direct one. Putting it all together +++++++++++++++++++++++ We're going to use a topic exchange in our logging system. We'll start off with a working assumption that the routing keys of logs will have two words: `"."`. The code is almost the same as in the :ref:`previous tutorial `. The code for :download:`emit_log_topic.py `: .. literalinclude:: examples/5-topics/emit_log_topic.py :language: python The code for :download:`receive_logs_topic.py `: .. literalinclude:: examples/5-topics/receive_logs_topic.py :language: python To receive all the logs run:: python receive_logs_topic.py "#" To receive all logs from the facility `"kern"`:: python receive_logs_topic.py "kern.*" Or if you want to hear only about `"critical"` logs:: python receive_logs_topic.py "*.critical" You can create multiple bindings:: python receive_logs_topic.py "kern.*" "*.critical" And to emit a log with a routing key `"kern.critical"` type:: python emit_log_topic.py "kern.critical" "A critical kernel error" Have fun playing with these programs. Note that the code doesn't make any assumption about the routing or binding keys, you may want to play with more than two routing key parameters. Move on to :ref:`tutorial 6 ` to learn about RPC. .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/6-rpc.rst000066400000000000000000000230761433544061600226110ustar00rootroot00000000000000.. _issue: https://github.com/mosquito/aio-pika/issues .. _pull request: https://github.com/mosquito/aio-pika/compare .. _aio-pika: https://github.com/mosquito/aio-pika .. _official tutorial: https://www.rabbitmq.com/tutorials/tutorial-six-python.html .. _rpc: Remote procedure call (RPC) =========================== .. warning:: This is a beta version of the port from `official tutorial`_. Please when you found an error create `issue`_ or `pull request`_ for me. This implementation is a part of official tutorial. Since version 1.7.0 `aio-pika`_ has :ref:`patterns submodule `. You might use :class:`aio_pika.patterns.RPC` for real projects. .. note:: Using the `aio-pika`_ async Python client .. note:: **Prerequisites** This tutorial assumes RabbitMQ is installed_ and running on localhost on standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. .. _installed: https://www.rabbitmq.com/download.html **Where to get help** If you're having trouble going through this tutorial you can `contact us`_ through the mailing list. .. _contact us: https://groups.google.com/forum/#!forum/rabbitmq-users In the :ref:`second tutorial ` we learned how to use *Work Queues* to distribute time-consuming tasks among multiple workers. But what if we need to run a function on a remote computer and wait for the result? Well, that's a different story. This pattern is commonly known as *Remote Procedure Call or RPC*. In this tutorial we're going to use RabbitMQ to build an RPC system: a client and a scalable RPC server. As we don't have any time-consuming tasks that are worth distributing, we're going to create a dummy RPC service that returns Fibonacci numbers. Client interface ++++++++++++++++ To illustrate how an RPC service could be used we're going to create a simple client class. It's going to expose a method named call which sends an RPC request and blocks until the answer is received: .. code-block:: python async def main(): fibonacci_rpc = FibonacciRpcClient() result = await fibonacci_rpc.call(4) print("fib(4) is %r" % result) .. note:: **A note on RPC** Although RPC is a pretty common pattern in computing, it's often criticised. The problems arise when a programmer is not aware whether a function call is local or if it's a slow RPC. Confusions like that result in an unpredictable system and adds unnecessary complexity to debugging. Instead of simplifying software, misused RPC can result in unmaintainable spaghetti code. Bearing that in mind, consider the following advice: * Make sure it's obvious which function call is local and which is remote. * Document your system. Make the dependencies between components clear. * Handle error cases. How should the client react when the RPC server is down for a long time? When in doubt avoid RPC. If you can, you should use an asynchronous pipeline - instead of RPC-like blocking, results are asynchronously pushed to a next computation stage. Callback queue ++++++++++++++ In general doing RPC over RabbitMQ is easy. A client sends a request message and a server replies with a response message. In order to receive a response the client needs to send a 'callback' queue address with the request. Let's try it: .. code-block:: python async def main(): ... # Queue for results callback_queue = await channel.declare_queue(exclusive=True) await channel.default_exchange.publish( Message( request, reply_to=callback_queue.name ), routing_key='rpc_queue' ) # ... and some code to read a response message from the callback_queue ... ... .. note:: **Message properties** The AMQP protocol predefines a set of 14 properties that go with a message. Most of the properties are rarely used, with the exception of the following: * `delivery_mode`: Marks a message as persistent (with a value of 2) or transient (any other value). You may remember this property from the :ref:`second tutorial `. * `content_type`: Used to describe the mime-type of the encoding. For example for the often used JSON encoding it is a good practice to set this property to: application/json. * `reply_to`: Commonly used to name a callback queue. * `correlation_id`: Useful to correlate RPC responses with requests. See additional info in :class:`aio_pika.Message` Correlation id ++++++++++++++ In the method presented above we suggest creating a callback queue for every RPC request. That's pretty inefficient, but fortunately there is a better way - let's create a single callback queue per client. That raises a new issue, having received a response in that queue it's not clear to which request the response belongs. That's when the `correlation_id` property is used. We're going to set it to a unique value for every request. Later, when we receive a message in the callback queue we'll look at this property, and based on that we'll be able to match a response with a request. If we see an unknown `correlation_id` value, we may safely discard the message - it doesn't belong to our requests. You may ask, why should we ignore unknown messages in the callback queue, rather than failing with an error? It's due to a possibility of a race condition on the server side. Although unlikely, it is possible that the RPC server will die just after sending us the answer, but before sending an acknowledgment message for the request. If that happens, the restarted RPC server will process the request again. That's why on the client we must handle the duplicate responses gracefully, and the RPC should ideally be idempotent. Summary +++++++ .. image:: /_static/tutorial/python-six.svg :align: center Our RPC will work like this: * When the Client starts up, it creates an anonymous exclusive callback queue. * For an RPC request, the Client sends a message with two properties: `reply_to`, which is set to the callback queue and `correlation_id`, which is set to a unique value for every request. * The request is sent to an rpc_queue queue. * The RPC worker (aka: server) is waiting for requests on that queue. When a request appears, it does the job and sends a message with the result back to the Client, using the queue from the reply_to field. * The client waits for data on the callback queue. When a message appears, it checks the `correlation_id` property. If it matches the value from the request it returns the response to the application. Putting it all together +++++++++++++++++++++++ The code for :download:`rpc_server.py `: .. literalinclude:: examples/6-rpc/rpc_server.py :language: python :linenos: The server code is rather straightforward: * (34) As usual we start by establishing the connection and declaring the queue. * (6) We declare our fibonacci function. It assumes only valid positive integer input. (Don't expect this one to work for big numbers, it's probably the slowest recursive implementation possible). * (15) We declare a callback for basic_consume, the core of the RPC server. It's executed when the request is received. It does the work and sends the response back. The code for :download:`rpc_client.py `: .. literalinclude:: examples/6-rpc/rpc_client.py :language: python :linenos: The client code is slightly more involved: * (15) We establish a connection, channel and declare an exclusive 'callback' queue for replies. * (22) We subscribe to the 'callback' queue, so that we can receive RPC responses. * (26) The 'on_response' callback executed on every response is doing a very simple job, for every response message it checks if the correlation_id is the one we're looking for. If so, it saves the response in self.response and breaks the consuming loop. * (30) Next, we define our main call method - it does the actual RPC request. * (31) In this method, first we generate a unique correlation_id number and save it - the 'on_response' callback function will use this value to catch the appropriate response. * (36) Next, we publish the request message, with two properties: reply_to and correlation_id. And finally we return the response back to the user. Our RPC service is now ready. We can start the server:: $ python rpc_server.py [x] Awaiting RPC requests To request a fibonacci number run the client:: $ python rpc_client.py [x] Requesting fib(30) The presented design is not the only possible implementation of a RPC service, but it has some important advantages: If the RPC server is too slow, you can scale up by just running another one. Try running a second rpc_server.py in a new console. On the client side, the RPC requires sending and receiving only one message. No synchronous calls like queue_declare are required. As a result the RPC client needs only one network round trip for a single RPC request. Our code is still pretty simplistic and doesn't try to solve more complex (but important) problems, like: * How should the client react if there are no servers running? * Should a client have some kind of timeout for the RPC? * If the server malfunctions and raises an exception, should it be forwarded to the client? * Protecting against invalid incoming messages (eg checking bounds) before processing. .. note:: If you want to experiment, you may find the rabbitmq-management plugin useful for viewing the queues. .. note:: This material was adopted from `official tutorial`_ on **rabbitmq.org**. aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/000077500000000000000000000000001433544061600227365ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/1-introduction/000077500000000000000000000000001433544061600256155ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/1-introduction/receive.py000066400000000000000000000020331433544061600276070ustar00rootroot00000000000000import asyncio from aio_pika import connect from aio_pika.abc import AbstractIncomingMessage async def on_message(message: AbstractIncomingMessage) -> None: """ 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() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() # Declaring queue queue = await channel.declare_queue("hello") # Start listening the queue with name 'hello' await queue.consume(on_message, no_ack=True) print(" [*] Waiting for messages. To exit press CTRL+C") await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/1-introduction/send.py000066400000000000000000000011441433544061600271200ustar00rootroot00000000000000import asyncio from aio_pika import Message, connect async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() # Declaring queue queue = await channel.declare_queue("hello") # Sending the message await channel.default_exchange.publish( Message(b"Hello World!"), routing_key=queue.name, ) print(" [x] Sent 'Hello World!'") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/2-work-queues/000077500000000000000000000000001433544061600253645ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/2-work-queues/new_task.py000066400000000000000000000013501433544061600275500ustar00rootroot00000000000000import asyncio import sys from aio_pika import DeliveryMode, Message, connect async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() message_body = b" ".join( arg.encode() for arg in sys.argv[1:] ) or b"Hello World!" message = Message( message_body, delivery_mode=DeliveryMode.PERSISTENT, ) # Sending the message await channel.default_exchange.publish( message, routing_key="task_queue", ) print(f" [x] Sent {message!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/2-work-queues/worker.py000066400000000000000000000016721433544061600272550ustar00rootroot00000000000000import asyncio from aio_pika import connect from aio_pika.abc import AbstractIncomingMessage async def on_message(message: AbstractIncomingMessage) -> None: async with message.process(): print(f" [x] Received message {message!r}") print(f" Message body is: {message.body!r}") async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() await channel.set_qos(prefetch_count=1) # Declaring queue queue = await channel.declare_queue( "task_queue", durable=True, ) # Start listening the queue with name 'task_queue' await queue.consume(on_message) print(" [*] Waiting for messages. To exit press CTRL+C") await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/3-publish-subscribe/000077500000000000000000000000001433544061600265235ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/3-publish-subscribe/emit_log.py000066400000000000000000000015061433544061600306760ustar00rootroot00000000000000import asyncio import sys from aio_pika import DeliveryMode, ExchangeType, Message, connect async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() logs_exchange = await channel.declare_exchange( "logs", ExchangeType.FANOUT, ) message_body = b" ".join( arg.encode() for arg in sys.argv[1:] ) or b"Hello World!" message = Message( message_body, delivery_mode=DeliveryMode.PERSISTENT, ) # Sending the message await logs_exchange.publish(message, routing_key="info") print(f" [x] Sent {message!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/3-publish-subscribe/receive_logs.py000066400000000000000000000017711433544061600315510ustar00rootroot00000000000000import asyncio from aio_pika import ExchangeType, connect from aio_pika.abc import AbstractIncomingMessage async def on_message(message: AbstractIncomingMessage) -> None: async with message.process(): print(f"[x] {message.body!r}") async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() await channel.set_qos(prefetch_count=1) logs_exchange = await channel.declare_exchange( "logs", ExchangeType.FANOUT, ) # Declaring queue queue = await channel.declare_queue(exclusive=True) # Binding the queue to the exchange await queue.bind(logs_exchange) # Start listening the queue await queue.consume(on_message) print(" [*] Waiting for logs. To exit press CTRL+C") await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/4-routing/000077500000000000000000000000001433544061600245665ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/4-routing/emit_log_direct.py000066400000000000000000000016231433544061600302730ustar00rootroot00000000000000import asyncio import sys from aio_pika import DeliveryMode, ExchangeType, Message, connect async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() logs_exchange = await channel.declare_exchange( "logs", ExchangeType.DIRECT, ) message_body = b" ".join( arg.encode() for arg in sys.argv[2:] ) or b"Hello World!" message = Message( message_body, delivery_mode=DeliveryMode.PERSISTENT, ) # Sending the message routing_key = sys.argv[1] if len(sys.argv) > 2 else "info" await logs_exchange.publish(message, routing_key=routing_key) print(f" [x] Sent {message.body!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/4-routing/receive_logs_direct.py000066400000000000000000000024661433544061600311500ustar00rootroot00000000000000import asyncio import sys from aio_pika import ExchangeType, connect from aio_pika.abc import AbstractIncomingMessage async def on_message(message: AbstractIncomingMessage) -> None: async with message.process(): print(" [x] %r:%r" % (message.routing_key, message.body)) async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() await channel.set_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 direct_logs_exchange = await channel.declare_exchange( "logs", ExchangeType.DIRECT, ) # Declaring random queue queue = await channel.declare_queue(durable=True) for severity in severities: await queue.bind(direct_logs_exchange, routing_key=severity) # Start listening the random queue await queue.consume(on_message) print(" [*] Waiting for messages. To exit press CTRL+C") await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/4-routing/receive_logs_direct_simple.py000066400000000000000000000024761433544061600325220ustar00rootroot00000000000000import asyncio import sys from aio_pika import ExchangeType, connect from aio_pika.abc import AbstractIncomingMessage async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") async with connection: # Creating a channel channel = await connection.channel() await channel.set_qos(prefetch_count=1) severities = sys.argv[1:] if not severities: sys.stderr.write( f"Usage: {sys.argv[0]} [info] [warning] [error]\n", ) sys.exit(1) # Declare an exchange direct_logs_exchange = await channel.declare_exchange( "logs", ExchangeType.DIRECT, ) # Declaring random queue queue = await channel.declare_queue(durable=True) for severity in severities: await queue.bind(direct_logs_exchange, routing_key=severity) async with queue.iterator() as iterator: message: AbstractIncomingMessage async for message in iterator: async with message.process(): print(f" [x] {message.routing_key!r}:{message.body!r}") print(" [*] Waiting for messages. To exit press CTRL+C") await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/5-topics/000077500000000000000000000000001433544061600244015ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/5-topics/emit_log_topic.py000066400000000000000000000016711433544061600277550ustar00rootroot00000000000000import asyncio import sys from aio_pika import DeliveryMode, ExchangeType, Message, connect async def main() -> None: # Perform connection connection = await connect( "amqp://guest:guest@localhost/", ) async with connection: # Creating a channel channel = await connection.channel() topic_logs_exchange = await channel.declare_exchange( "topic_logs", ExchangeType.TOPIC, ) routing_key = sys.argv[1] if len(sys.argv) > 2 else "anonymous.info" message_body = b" ".join( arg.encode() for arg in sys.argv[2:] ) or b"Hello World!" message = Message( message_body, delivery_mode=DeliveryMode.PERSISTENT, ) # Sending the message await topic_logs_exchange.publish(message, routing_key=routing_key) print(f" [x] Sent {message!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/5-topics/receive_logs_topic.py000066400000000000000000000023471433544061600306250ustar00rootroot00000000000000import asyncio import sys from aio_pika import ExchangeType, connect from aio_pika.abc import AbstractIncomingMessage async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") # Creating a channel channel = await connection.channel() await channel.set_qos(prefetch_count=1) # Declare an exchange topic_logs_exchange = await channel.declare_exchange( "topic_logs", ExchangeType.TOPIC, ) # Declaring queue queue = await channel.declare_queue( "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 queue.bind(topic_logs_exchange, routing_key=binding_key) print(" [*] Waiting for messages. To exit press CTRL+C") # Start listening the queue with name 'task_queue' async with queue.iterator() as iterator: message: AbstractIncomingMessage async for message in iterator: async with message.process(): print(f" [x] {message.routing_key!r}:{message.body!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/6-rpc/000077500000000000000000000000001433544061600236655ustar00rootroot00000000000000aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/6-rpc/rpc_client.py000066400000000000000000000036541433544061600263710ustar00rootroot00000000000000import asyncio import uuid from typing import MutableMapping from aio_pika import Message, connect from aio_pika.abc import ( AbstractChannel, AbstractConnection, AbstractIncomingMessage, AbstractQueue, ) class FibonacciRpcClient: connection: AbstractConnection channel: AbstractChannel callback_queue: AbstractQueue loop: asyncio.AbstractEventLoop def __init__(self) -> None: self.futures: MutableMapping[str, asyncio.Future] = {} self.loop = asyncio.get_running_loop() async def connect(self) -> "FibonacciRpcClient": self.connection = await connect( "amqp://guest:guest@localhost/", loop=self.loop, ) self.channel = await self.connection.channel() self.callback_queue = await self.channel.declare_queue(exclusive=True) await self.callback_queue.consume(self.on_response) return self def on_response(self, message: AbstractIncomingMessage) -> None: if message.correlation_id is None: print(f"Bad message {message!r}") return future: asyncio.Future = self.futures.pop(message.correlation_id) future.set_result(message.body) async def call(self, n: int) -> int: correlation_id = str(uuid.uuid4()) future = self.loop.create_future() self.futures[correlation_id] = future await self.channel.default_exchange.publish( Message( str(n).encode(), content_type="text/plain", correlation_id=correlation_id, reply_to=self.callback_queue.name, ), routing_key="rpc_queue", ) return int(await future) async def main() -> None: fibonacci_rpc = await FibonacciRpcClient().connect() print(" [x] Requesting fib(30)") response = await fibonacci_rpc.call(30) print(f" [.] Got {response!r}") if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/examples/6-rpc/rpc_server.py000066400000000000000000000030241433544061600264100ustar00rootroot00000000000000import asyncio import logging from aio_pika import Message, connect from aio_pika.abc import AbstractIncomingMessage def fib(n: int) -> int: if n == 0: return 0 elif n == 1: return 1 else: return fib(n - 1) + fib(n - 2) async def main() -> None: # Perform connection connection = await connect("amqp://guest:guest@localhost/") # Creating a channel channel = await connection.channel() exchange = channel.default_exchange # Declaring queue queue = await channel.declare_queue("rpc_queue") print(" [x] Awaiting RPC requests") # Start listening the queue with name 'hello' async with queue.iterator() as qiterator: message: AbstractIncomingMessage async for message in qiterator: try: async with message.process(requeue=False): assert message.reply_to is not None n = int(message.body.decode()) print(f" [.] fib({n})") response = str(fib(n)).encode() await exchange.publish( Message( body=response, correlation_id=message.correlation_id, ), routing_key=message.reply_to, ) print("Request complete") except Exception: logging.exception("Processing error for message %r", message) if __name__ == "__main__": asyncio.run(main()) aio-pika-8.2.5/docs/source/rabbitmq-tutorial/index.rst000066400000000000000000000002101433544061600227520ustar00rootroot00000000000000RabbitMQ tutorial ================= .. toctree:: :glob: :maxdepth: 3 :caption: RabbitMQ tutorial adopted for aio-pika *-* aio-pika-8.2.5/logo.svg000066400000000000000000000240131433544061600147070ustar00rootroot00000000000000 image/svg+xmlaio-pika aio-pika-8.2.5/noxfile.py000066400000000000000000000005761433544061600152540ustar00rootroot00000000000000import nox @nox.session def docs(session): session.install(".") session.install("sphinx", "sphinx-autobuild") session.run("rm", "-rf", "build/html", external=True) sphinx_args = ["-W", "docs/source", "build/html"] if "serve" in session.posargs: session.run("sphinx-autobuild", *sphinx_args) else: session.run("sphinx-build", *sphinx_args) aio-pika-8.2.5/pylava.ini000066400000000000000000000001621433544061600152220ustar00rootroot00000000000000[pylava] ignore=C901,E252 skip = env*,.tox*,*build*,.nox*,.venv*,venv* [pylava:pycodestyle] max_line_length = 80 aio-pika-8.2.5/pytest.ini000066400000000000000000000001171433544061600152560ustar00rootroot00000000000000[pytest] log_cli = true addopts = -p no:asyncio markers = asyncio: asyncio aio-pika-8.2.5/setup.cfg000066400000000000000000000006371433544061600150550ustar00rootroot00000000000000[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 aio-pika-8.2.5/setup.py000066400000000000000000000040621433544061600147420ustar00rootroot00000000000000import os from setuptools import setup, find_packages from importlib.machinery import SourceFileLoader module = SourceFileLoader( "version", os.path.join("aio_pika", "version.py") ).load_module() setup( name="aio-pika", version=module.__version__, author=module.__author__, author_email=module.team_email, license=module.package_license, description=module.package_info, long_description=open("README.rst").read(), platforms="all", classifiers=[ "License :: OSI Approved :: Apache Software License", "Topic :: Internet", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "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.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", ], packages=find_packages(exclude=["tests"]), package_data={"aio_pika": ["py.typed"]}, install_requires=[ "aiormq~=6.4.0", "yarl", 'typing_extensions; python_version < "3.8.0"' ], python_requires=">3.6, <4", extras_require={ "develop": [ "aiomisc~=16.0", "coverage!=4.3", "coveralls", "pylava", "pyflakes<2.5", "pytest", "pytest-cov", "shortuuid", "nox", "sphinx", "sphinx-autobuild", "timeout-decorator", "tox>=2.4", ], }, project_urls={ "Documentation": "https://aio-pika.readthedocs.org/", "Source": "https://github.com/mosquito/aio-pika", }, ) aio-pika-8.2.5/tests/000077500000000000000000000000001433544061600143705ustar00rootroot00000000000000aio-pika-8.2.5/tests/__init__.py000066400000000000000000000002701433544061600165000ustar00rootroot00000000000000import shortuuid def get_random_name(*args): prefix = ["test"] for item in args: prefix.append(item) prefix.append(shortuuid.uuid()) return ".".join(prefix) aio-pika-8.2.5/tests/conftest.py000066400000000000000000000106461433544061600165760ustar00rootroot00000000000000import asyncio import gc import os import tracemalloc from contextlib import suppress from functools import partial import aiormq import pamqp import pytest from aiomisc import awaitable from aiormq.connection import DEFAULT_PORTS from yarl import URL import aio_pika @pytest.fixture async def add_cleanup(loop): entities = [] def payload(func, *args, **kwargs): nonlocal entities func = partial(awaitable(func), *args, **kwargs) entities.append(func) try: yield payload finally: for func in entities[::-1]: await func() entities.clear() @pytest.fixture async def create_task(loop): tasks = [] def payload(coroutine): nonlocal tasks task = loop.create_task(coroutine) tasks.append(task) return task try: yield payload finally: cancelled = [] for task in tasks: if task.done(): continue task.cancel() cancelled.append(task) results = await asyncio.gather(*cancelled, return_exceptions=True) for result in results: if not isinstance(result, asyncio.CancelledError): raise result @pytest.fixture def amqp_direct_url(request) -> URL: url = URL( os.getenv("AMQP_URL", "amqp://guest:guest@localhost"), ).update_query(name=request.node.nodeid) default_port = DEFAULT_PORTS[url.scheme] if not url.port: url = url.with_port(default_port) return url @pytest.fixture def amqp_url(request, amqp_direct_url) -> URL: query = dict(amqp_direct_url.query) query["name"] = request.node.nodeid return amqp_direct_url.with_query(**query) @pytest.fixture( scope="module", params=[aio_pika.connect, aio_pika.connect_robust], ids=["connect", "connect_robust"], ) def connection_fabric(request): return request.param @pytest.fixture def create_connection(connection_fabric, loop, amqp_url): return partial(connection_fabric, amqp_url, loop=loop) @pytest.fixture def create_channel(connection: aio_pika.Connection, add_cleanup): conn = connection async def fabric(cleanup=True, connection=None, *args, **kwargs): nonlocal add_cleanup, conn if connection is None: connection = conn channel = await connection.channel(*args, **kwargs) if cleanup: add_cleanup(channel.close) return channel return fabric # noinspection PyTypeChecker @pytest.fixture async def connection(create_connection) -> aio_pika.Connection: async with await create_connection() as conn: yield conn # noinspection PyTypeChecker @pytest.fixture async def channel(connection: aio_pika.Connection) -> aio_pika.Channel: async with connection.channel() as ch: yield ch @pytest.fixture def declare_queue(connection, channel, add_cleanup): ch = channel async def fabric( *args, cleanup=True, channel=None, **kwargs ) -> aio_pika.Queue: nonlocal ch, add_cleanup if channel is None: channel = ch queue = await channel.declare_queue(*args, **kwargs) if cleanup and not kwargs.get("auto_delete"): add_cleanup(queue.delete) return queue return fabric @pytest.fixture def declare_exchange(connection, channel, add_cleanup): ch = channel async def fabric( *args, channel=None, cleanup=True, **kwargs ) -> aio_pika.Exchange: nonlocal ch, add_cleanup if channel is None: channel = ch exchange = await channel.declare_exchange(*args, **kwargs) if cleanup and not kwargs.get("auto_delete"): add_cleanup(exchange.delete) return exchange return fabric @pytest.fixture(autouse=True) def memory_tracer(): tracemalloc.start() tracemalloc.clear_traces() filters = ( tracemalloc.Filter(True, aiormq.__file__), tracemalloc.Filter(True, pamqp.__file__), tracemalloc.Filter(True, aio_pika.__file__), ) snapshot_before = tracemalloc.take_snapshot().filter_traces(filters) try: yield with suppress(Exception): gc.collect() snapshot_after = tracemalloc.take_snapshot().filter_traces(filters) top_stats = snapshot_after.compare_to( snapshot_before, "lineno", cumulative=True, ) assert not top_stats finally: tracemalloc.stop() aio-pika-8.2.5/tests/test_amqp.py000066400000000000000000001446601433544061600167520ustar00rootroot00000000000000import asyncio import logging import os import time import uuid from datetime import datetime from typing import Awaitable, Callable, Optional from unittest import mock import aiormq.exceptions import pytest import shortuuid from yarl import URL import aio_pika import aio_pika.exceptions from aio_pika import Channel, DeliveryMode, Message from aio_pika.abc import AbstractConnection, AbstractIncomingMessage from aio_pika.exceptions import ( DeliveryError, MessageProcessError, ProbableAuthenticationError, ) from aio_pika.exchange import ExchangeType from aio_pika.message import ReturnedMessage from tests import get_random_name log = logging.getLogger(__name__) class TestCaseAmqpBase: @staticmethod def create_channel( connection: aio_pika.Connection, ) -> Awaitable[aio_pika.Channel]: return connection.channel() @staticmethod @pytest.fixture(name="declare_queue") def declare_queue_(declare_queue): return declare_queue @staticmethod @pytest.fixture(name="declare_exchange") def declare_exchange_(declare_exchange): return declare_exchange class TestCaseAmqp(TestCaseAmqpBase): async def test_properties(self, loop, connection: aio_pika.Connection): assert not connection.is_closed async def test_channel_close(self, connection: aio_pika.Connection): event = asyncio.Event() closed = False def on_close( ch: aio_pika.abc.AbstractChannel, exc: Optional[Exception] = None, ): nonlocal event, closed log.info("Close called") closed = True assert ch.is_closed event.set() channel = await self.create_channel(connection) channel.close_callbacks.add(on_close) await channel.close() await event.wait() assert closed with pytest.raises(RuntimeError): await channel.initialize() async with self.create_channel(connection) as ch: assert not ch.is_closed async def test_channel_reopen(self, connection): channel = await self.create_channel(connection) await channel.close() assert channel.is_closed await channel.reopen() assert not channel.is_closed async def test_delete_queue_and_exchange( self, connection, declare_exchange, declare_queue, ): queue_name = get_random_name("test_connection") exchange = get_random_name() channel = await self.create_channel(connection) await declare_exchange(exchange, auto_delete=True) await declare_queue(queue_name, auto_delete=True) await channel.queue_delete(queue_name) await channel.exchange_delete(exchange) async def test_temporary_queue(self, connection, declare_queue): channel = await self.create_channel(connection) queue = await declare_queue(auto_delete=True) assert queue.name != "" body = os.urandom(32) await channel.default_exchange.publish( Message(body=body), routing_key=queue.name, ) await asyncio.sleep(1) message = await queue.get() assert message.body == body await channel.queue_delete(queue.name) @pytest.mark.skip(reason="This was deprecated in AMQP 0-9-1") async def test_internal_exchange( self, channel: aio_pika.Channel, declare_exchange, declare_queue, ): routing_key = get_random_name() exchange_name = get_random_name("internal", "exchange") exchange = await declare_exchange( exchange_name, auto_delete=True, internal=True, ) queue = await declare_queue(auto_delete=True) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") with pytest.raises(ValueError): f = exchange.publish( Message( body, content_type="text/plain", headers={"foo": "bar"}, ), routing_key, ) await f await queue.unbind(exchange, routing_key) async def test_declare_exchange_with_passive_flag( self, connection, declare_exchange: Callable, ): exchange_name = get_random_name() channel = await self.create_channel(connection) with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await declare_exchange( exchange_name, auto_delete=True, passive=True, channel=channel, ) channel1 = await self.create_channel(connection) channel2 = await self.create_channel(connection) await declare_exchange( exchange_name, auto_delete=True, passive=False, channel=channel1, ) # Check ignoring different exchange options await declare_exchange( exchange_name, auto_delete=False, passive=True, channel=channel2, ) async def test_declare_queue_with_passive_flag( self, connection, declare_exchange: Callable, declare_queue: Callable, ): queue_name = get_random_name() ch1 = await self.create_channel(connection) ch2 = await self.create_channel(connection) ch3 = await self.create_channel(connection) with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await declare_queue( queue_name, auto_delete=True, passive=True, channel=ch1, ) await declare_queue( queue_name, auto_delete=True, passive=False, channel=ch2, ) # Check ignoring different queue options await declare_queue( queue_name, auto_delete=False, passive=True, channel=ch3, ) async def test_simple_publish_and_receive( self, channel: aio_pika.Channel, declare_queue: Callable, declare_exchange: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=True, channel=channel, ) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") result = await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) assert result incoming_message = await queue.get(timeout=5) await incoming_message.ack() assert incoming_message.body == body await queue.unbind(exchange, routing_key) async def test_simple_publish_without_confirm( self, connection: aio_pika.Connection, declare_exchange: Callable, declare_queue: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() channel = await connection.channel(publisher_confirms=False) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=True, channel=channel, ) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") result = await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) assert result is None incoming_message = await queue.get(timeout=5) await incoming_message.ack() assert incoming_message.body == body await queue.unbind(exchange, routing_key) async def test_simple_publish_and_receive_delivery_mode_explicitly( self, channel: aio_pika.Channel, declare_queue: Callable, declare_exchange: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=True, channel=channel, ) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message( body, content_type="text/plain", headers={"foo": "bar"}, delivery_mode=None, ), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.ack() assert incoming_message.body == body await queue.unbind(exchange, routing_key) async def test_simple_publish_and_receive_to_bound_exchange( self, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): routing_key = get_random_name() src_name = get_random_name("source", "exchange") dest_name = get_random_name("destination", "exchange") src_exchange = await declare_exchange(src_name, auto_delete=True) dest_exchange = await declare_exchange(dest_name, auto_delete=True) queue = await declare_queue(auto_delete=True) await queue.bind(dest_exchange, routing_key) await dest_exchange.bind(src_exchange, routing_key) add_cleanup(dest_exchange.unbind, src_exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await src_exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.ack() assert incoming_message.body == body await queue.unbind(dest_exchange, routing_key) async def test_incoming_message_info( self, channel: aio_pika.Channel, declare_queue: Callable, declare_exchange: Callable, add_cleanup: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") info = { "headers": {"foo": "bar"}, "content_type": "application/json", "content_encoding": "text", "delivery_mode": DeliveryMode.PERSISTENT.value, "priority": 0, "correlation_id": "1", "reply_to": "test", "expiration": 1.5, "message_id": shortuuid.uuid(), "timestamp": datetime.utcfromtimestamp(int(time.time())), "type": "0", "user_id": "guest", "app_id": "test", "body_size": len(body), } msg = Message( body=body, headers={"foo": "bar"}, content_type="application/json", content_encoding="text", delivery_mode=DeliveryMode.PERSISTENT, priority=0, correlation_id=1, reply_to="test", expiration=1.5, message_id=info["message_id"], timestamp=info["timestamp"], type="0", user_id="guest", app_id="test", ) await exchange.publish(msg, routing_key) incoming_message = await queue.get(timeout=5) await incoming_message.ack() info["routing_key"] = incoming_message.routing_key info["redelivered"] = incoming_message.redelivered info["exchange"] = incoming_message.exchange info["delivery_tag"] = incoming_message.delivery_tag info["consumer_tag"] = incoming_message.consumer_tag info["cluster_id"] = incoming_message.cluster_id assert incoming_message.body == body assert incoming_message.info() == info async def test_context_process( self, channel: aio_pika.Channel, declare_queue: Callable, declare_exchange: Callable, add_cleanup: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) if not channel.publisher_confirms: await asyncio.sleep(1) incoming_message: AbstractIncomingMessage = await queue.get(timeout=5) with pytest.raises(AssertionError): async with incoming_message.process(requeue=True): raise AssertionError assert incoming_message.locked incoming_message = await queue.get(timeout=5) async with incoming_message.process(): pass assert incoming_message.body == body await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) with pytest.raises(MessageProcessError): async with incoming_message.process(): await incoming_message.reject(requeue=True) assert incoming_message.locked incoming_message = await queue.get(timeout=5) async with incoming_message.process(ignore_processed=True): await incoming_message.reject(requeue=False) assert incoming_message.body == body await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) with pytest.raises(AssertionError): async with incoming_message.process( requeue=True, reject_on_redelivered=True, ): raise AssertionError incoming_message = await queue.get(timeout=5) with pytest.raises(AssertionError): async with incoming_message.process( requeue=True, reject_on_redelivered=True, ): raise AssertionError assert incoming_message.locked async def test_context_process_redelivery( self, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) if not channel.publisher_confirms: await asyncio.sleep(1) incoming_message = await queue.get(timeout=5) with pytest.raises(AssertionError): async with incoming_message.process( requeue=True, reject_on_redelivered=True, ): raise AssertionError incoming_message = await queue.get(timeout=5) with mock.patch("aio_pika.message.log") as message_logger: with pytest.raises(Exception): async with incoming_message.process( requeue=True, reject_on_redelivered=True, ): raise Exception assert message_logger.info.called assert ( message_logger.info.mock_calls[0][1][1].body == incoming_message.body ) assert incoming_message.body == body async def test_no_ack_redelivery( self, connection, add_cleanup: Callable, declare_queue, declare_exchange, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=False, channel=channel, cleanup=False, ) await queue.bind(exchange, routing_key) # publish 2 messages for _ in range(2): body = bytes(shortuuid.uuid(), "utf-8") msg = Message(body) await exchange.publish(msg, routing_key) if not channel.publisher_confirms: await asyncio.sleep(1) # ack 1 message out of 2 first_message = await queue.get(timeout=5) last_message = await queue.get(timeout=5) await last_message.ack() # close channel, not acked message should be redelivered await channel.close() channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=False, channel=channel, ) # receive not acked message message = await queue.get(timeout=5) assert message.body == first_message.body await message.ack() await queue.unbind(exchange, routing_key) async def test_ack_multiple( self, connection, declare_exchange, declare_queue, add_cleanup: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=False, cleanup=False, channel=channel, ) await queue.bind(exchange, routing_key) # publish 2 messages for _ in range(2): body = bytes(shortuuid.uuid(), "utf-8") msg = Message(body) await exchange.publish(msg, routing_key) if not channel.publisher_confirms: await asyncio.sleep(1) # ack only last mesage with multiple flag, first # message should be acked too await queue.get(timeout=5) last_message = await queue.get(timeout=5) await last_message.ack(multiple=True) # close channel, no messages should be redelivered await channel.close() channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue( queue_name, auto_delete=False, cleanup=False, channel=channel, ) with pytest.raises(aio_pika.exceptions.QueueEmpty): await queue.get() await queue.unbind(exchange, routing_key) await queue.delete() async def test_ack_twice( self, channel: aio_pika.Connection, declare_queue, declare_exchange, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.ack() with pytest.raises(MessageProcessError): await incoming_message.ack() assert incoming_message.body == body await queue.unbind(exchange, routing_key) await queue.delete() async def test_reject_twice( self, channel: aio_pika.Channel, add_cleanup: Callable, declare_queue: Callable, declare_exchange: Callable, ): queue_name = get_random_name("test_connection") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.reject(requeue=False) with pytest.raises(MessageProcessError): await incoming_message.reject(requeue=False) assert incoming_message.body == body async def test_consuming( self, loop, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): queue_name = get_random_name("tc2") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") f = loop.create_future() async def handle(message): await message.ack() assert message.body == body assert message.routing_key == routing_key f.set_result(True) await queue.consume(handle) await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) if not f.done(): await f async def test_consuming_not_coroutine( self, loop, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): queue_name = get_random_name("tc2") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) add_cleanup(queue.unbind, exchange, routing_key) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") f = loop.create_future() async def handle(message): await message.ack() assert message.body == body assert message.routing_key == routing_key f.set_result(True) await queue.consume(handle) await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) if not f.done(): await f async def test_ack_reject( self, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): queue_name = get_random_name("test_connection3") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5, no_ack=True) with pytest.raises(TypeError): await incoming_message.ack() await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.reject() await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5, no_ack=True) with pytest.raises(TypeError): await incoming_message.reject() assert incoming_message.body == body async def test_purge_queue( self, declare_queue: Callable, declare_exchange: Callable, channel: aio_pika.Channel, ): queue_name = get_random_name("test_connection4") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) try: body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message( body, content_type="text/plain", headers={"foo": "bar"}, ), routing_key, ) await queue.purge() with pytest.raises(asyncio.TimeoutError): await queue.get(timeout=1) except aio_pika.exceptions.QueueEmpty: await queue.unbind(exchange, routing_key) await queue.delete() async def test_connection_refused(self, connection_fabric: Callable): with pytest.raises(ConnectionError): await connection_fabric("amqp://guest:guest@localhost:9999") async def test_wrong_credentials( self, connection_fabric: Callable, amqp_url, ): amqp_url = amqp_url.with_user(uuid.uuid4().hex).with_password( uuid.uuid4().hex, ) with pytest.raises(ProbableAuthenticationError): await connection_fabric(amqp_url) async def test_set_qos(self, channel: aio_pika.Channel): await channel.set_qos(prefetch_count=1, global_=True) async def test_exchange_delete( self, channel: aio_pika.Channel, declare_exchange, ): exchange = await declare_exchange("test", auto_delete=True) await exchange.delete() async def test_dlx( self, loop, channel: aio_pika.Channel, declare_exchange: Callable, declare_queue: Callable, add_cleanup: Callable, ): suffix = get_random_name() routing_key = "%s_routing_key" % suffix dlx_routing_key = "%s_dlx_routing_key" % suffix f = loop.create_future() async def dlx_handle(message): await message.ack() assert message.body == body assert message.routing_key == dlx_routing_key f.set_result(True) direct_exchange = await declare_exchange( "direct", channel=channel, auto_delete=True, ) # type: aio_pika.Exchange dlx_exchange = await declare_exchange( "dlx", ExchangeType.DIRECT, auto_delete=True, ) direct_queue = await declare_queue( "%s_direct_queue" % suffix, auto_delete=True, arguments={ "x-message-ttl": 300, "x-dead-letter-exchange": "dlx", "x-dead-letter-routing-key": dlx_routing_key, }, ) dlx_queue = await declare_queue( "%s_dlx_queue" % suffix, auto_delete=True, ) await dlx_queue.consume(dlx_handle) await dlx_queue.bind(dlx_exchange, dlx_routing_key) await direct_queue.bind(direct_exchange, routing_key) add_cleanup(dlx_queue.unbind, dlx_exchange, routing_key) add_cleanup(direct_queue.unbind, direct_exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await direct_exchange.publish( Message( body, content_type="text/plain", headers={ "x-message-ttl": 100, "x-dead-letter-exchange": "dlx", }, ), routing_key, ) if not f.done(): await f async def test_expiration( self, channel: aio_pika.Channel, loop, declare_exchange, declare_queue, ): dlx_queue = await declare_queue( get_random_name("test_dlx"), cleanup=False, ) # type: aio_pika.Queue dlx_exchange = await declare_exchange( get_random_name("dlx"), cleanup=False, ) # type: aio_pika.Exchange await dlx_queue.bind(dlx_exchange, routing_key=dlx_queue.name) queue = await declare_queue( get_random_name("test_expiration"), arguments={ "x-message-ttl": 10000, "x-dead-letter-exchange": dlx_exchange.name, "x-dead-letter-routing-key": dlx_queue.name, }, ) # type: aio_pika.Queue body = bytes(shortuuid.uuid(), "utf-8") await channel.default_exchange.publish( Message( body, content_type="text/plain", headers={"foo": "bar"}, expiration=0.5, ), queue.name, ) f = loop.create_future() await dlx_queue.consume(f.set_result, no_ack=True) message = await f assert message.body == body assert message.headers["x-death"][0]["original-expiration"] == "500" async def test_add_close_callback(self, create_connection: Callable): connection = await create_connection() shared_list = [] def share(*a, **kw): shared_list.append((a, kw)) connection.close_callbacks.add(share) del share assert len(connection.close_callbacks) == 1 await connection.close() assert len(shared_list) == 1 async def test_big_message( self, channel: aio_pika.Channel, add_cleanup: Callable, declare_queue, declare_exchange, ): queue_name = get_random_name("test_big") routing_key = get_random_name() exchange = await declare_exchange("direct", auto_delete=True) queue = await declare_queue(queue_name, auto_delete=True) await queue.bind(exchange, routing_key) add_cleanup(queue.unbind, exchange, routing_key) add_cleanup(queue.delete) body = bytes(shortuuid.uuid(), "utf-8") * 1000000 await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) await incoming_message.ack() assert incoming_message.body == body async def test_unexpected_channel_close( self, channel: aio_pika.Channel, declare_queue, ): with pytest.raises(aio_pika.exceptions.ChannelClosed): await declare_queue("amq.restricted_queue_name", auto_delete=True) with pytest.raises(aiormq.exceptions.ChannelInvalidStateError): await channel.set_qos(100) async def test_declaration_result( self, channel: aio_pika.Channel, declare_queue, ): queue = await declare_queue(auto_delete=True) assert queue.declaration_result.message_count == 0 assert queue.declaration_result.consumer_count == 0 async def test_declaration_result_with_consumers( self, connection, declare_queue, ): channel1 = await self.create_channel(connection) channel2 = await self.create_channel(connection) queue_name = get_random_name("queue", "declaration-result") queue1 = await declare_queue( queue_name, auto_delete=True, channel=channel1, ) await queue1.consume(print) queue2 = await declare_queue( queue_name, passive=True, channel=channel2, cleanup=False, ) assert queue2.declaration_result.consumer_count == 1 async def test_declaration_result_with_messages( self, connection, declare_queue, declare_exchange, ): channel1 = await self.create_channel(connection) channel2 = await self.create_channel(connection) queue_name = get_random_name("queue", "declaration-result") queue1 = await declare_queue( queue_name, auto_delete=True, channel=channel1, ) await channel1.default_exchange.publish( Message(body=b"test"), routing_key=queue1.name, ) await asyncio.sleep(1) queue2 = await declare_queue( queue_name, passive=True, channel=channel2, ) await queue2.get() await queue2.delete() assert queue2.declaration_result.consumer_count == 0 assert queue2.declaration_result.message_count == 1 async def test_queue_empty_exception( self, channel: aio_pika.Channel, add_cleanup: Callable, declare_queue, ): queue_name = get_random_name("test_get_on_empty_queue") queue = await declare_queue(queue_name, auto_delete=True) add_cleanup(queue.delete) with pytest.raises(aio_pika.exceptions.QueueEmpty): await queue.get(timeout=5) await channel.default_exchange.publish( Message(b"test"), queue_name, ) message = await queue.get(timeout=5) assert message.body == b"test" # test again for #110 with pytest.raises(aio_pika.exceptions.QueueEmpty): await queue.get(timeout=5) async def test_queue_empty_fail_false( self, channel: aio_pika.Channel, declare_queue, ): queue_name = get_random_name("test_get_on_empty_queue") queue = await declare_queue(queue_name, auto_delete=True) result = await queue.get(fail=False) assert result is None async def test_message_nack( self, channel: aio_pika.Channel, declare_queue, ): queue_name = get_random_name("test_nack_queue") body = uuid.uuid4().bytes queue = await declare_queue(queue_name, auto_delete=True) await channel.default_exchange.publish( Message(body=body), routing_key=queue_name, ) message = await queue.get() # type: aio_pika.IncomingMessage assert message.body == body await message.nack(requeue=True) message = await queue.get() assert message.redelivered assert message.body == body await message.ack() async def test_on_return_raises(self, connection: aio_pika.Connection): queue_name = get_random_name("test_on_return_raises") body = uuid.uuid4().bytes with pytest.raises(RuntimeError): await connection.channel( publisher_confirms=False, on_return_raises=True, ) channel = await connection.channel( publisher_confirms=True, on_return_raises=True, ) for _ in range(100): with pytest.raises(aio_pika.exceptions.DeliveryError): await channel.default_exchange.publish( Message(body=body), routing_key=queue_name, ) async def test_transaction_when_publisher_confirms_error( self, connection: aio_pika.Connection, ): async with connection.channel(publisher_confirms=True) as channel: with pytest.raises(RuntimeError): channel.transaction() async def test_transaction_simple_commit( self, connection: aio_pika.Connection, ): async with connection.channel(publisher_confirms=False) as channel: tx = channel.transaction() await tx.select() await tx.commit() async def test_transaction_simple_rollback( self, connection: aio_pika.Connection, ): async with connection.channel(publisher_confirms=False) as channel: tx = channel.transaction() await tx.select() await tx.rollback() async def test_transaction_simple_async_commit( self, connection: aio_pika.Connection, ): async with connection.channel(publisher_confirms=False) as channel: async with channel.transaction(): pass async def test_transaction_simple_async_rollback( self, connection: aio_pika.Connection, ): async with connection.channel(publisher_confirms=False) as channel: with pytest.raises(ValueError): async with channel.transaction(): raise ValueError async def test_async_for_queue(self, loop, connection, declare_queue): channel2 = await self.create_channel(connection) queue = await declare_queue( get_random_name("queue", "is_async", "for"), auto_delete=True, channel=channel2, ) messages = 100 async def publisher(): channel1 = await self.create_channel(connection) for i in range(messages): await channel1.default_exchange.publish( Message(body=str(i).encode()), routing_key=queue.name, ) loop.create_task(publisher()) count = 0 data = list() async for message in queue: async with message.process(): count += 1 data.append(message.body) if count >= messages: break assert data == list(map(lambda x: str(x).encode(), range(messages))) async def test_async_for_queue_context( self, loop, connection, declare_queue, ): channel2 = await self.create_channel(connection) queue = await declare_queue( get_random_name("queue", "is_async", "for"), auto_delete=True, channel=channel2, ) messages = 100 async def publisher(): channel1 = await self.create_channel(connection) for i in range(messages): await channel1.default_exchange.publish( Message(body=str(i).encode()), routing_key=queue.name, ) loop.create_task(publisher()) count = 0 data = list() async with queue.iterator() as queue_iterator: async for message in queue_iterator: async with message.process(): count += 1 data.append(message.body) if count >= messages: break assert data == list(map(lambda x: str(x).encode(), range(messages))) async def test_async_with_connection( self, create_connection: Callable, connection, loop, declare_queue, ): async with await create_connection() as connection: channel = await self.create_channel(connection) queue = await declare_queue( get_random_name("queue", "is_async", "for"), auto_delete=True, channel=channel, ) messages = 100 async def publisher(): channel1 = await self.create_channel(connection) for i in range(messages): await channel1.default_exchange.publish( Message(body=str(i).encode()), routing_key=queue.name, ) loop.create_task(publisher()) count = 0 data = list() async with queue.iterator() as queue_iterator: async for message in queue_iterator: async with message.process(): count += 1 data.append(message.body) if count >= messages: break assert data == list( map(lambda x: str(x).encode(), range(messages)), ) assert channel.is_closed async def test_async_with_channel(self, connection: aio_pika.Connection): async with self.create_channel(connection) as channel: assert isinstance(channel, Channel) assert channel.is_closed async def test_delivery_fail( self, channel: aio_pika.Channel, declare_queue, ): queue = await declare_queue( exclusive=True, arguments={"x-max-length": 1, "x-overflow": "reject-publish"}, auto_delete=True, ) await channel.default_exchange.publish( aio_pika.Message(body=b"queue me"), routing_key=queue.name, ) with pytest.raises(DeliveryError): for _ in range(10): await channel.default_exchange.publish( aio_pika.Message(body=b"reject me"), routing_key=queue.name, ) async def test_channel_locked_resource( self, connection, declare_queue, add_cleanup: Callable, ): ch1 = await self.create_channel(connection) ch2 = await self.create_channel(connection) qname = get_random_name("channel", "locked", "resource") q1 = await declare_queue( qname, exclusive=True, channel=ch1, cleanup=False, ) add_cleanup(q1.delete) tag = await q1.consume(print, exclusive=True) add_cleanup(q1.cancel, tag) with pytest.raises(aiormq.exceptions.ChannelAccessRefused): q2 = await declare_queue( qname, exclusive=True, channel=ch2, cleanup=False, ) await q2.consume(print, exclusive=True) async def test_queue_iterator_close_was_called_twice( self, create_connection: Callable, loop, declare_queue, ): future = loop.create_future() event = asyncio.Event() queue_name = get_random_name() async def task_inner(): nonlocal future nonlocal event nonlocal create_connection try: connection = await create_connection() async with connection: channel = await self.create_channel(connection) queue = await declare_queue( queue_name, channel=channel, cleanup=False, ) async with queue.iterator() as q: event.set() async for message in q: with message.process(): break except asyncio.CancelledError as e: future.set_exception(e) raise task = loop.create_task(task_inner()) await event.wait() loop.call_soon(task.cancel) with pytest.raises(asyncio.CancelledError): await task with pytest.raises(asyncio.CancelledError): await future async def test_queue_iterator_close_with_noack( self, create_connection: Callable, loop, add_cleanup: Callable, declare_queue, ): messages = [] queue_name = get_random_name("test_queue") body = get_random_name("test_body").encode() async def task_inner(): nonlocal messages nonlocal create_connection nonlocal add_cleanup connection = await create_connection() add_cleanup(connection.close) async with connection: channel = await self.create_channel(connection) queue = await declare_queue( queue_name, channel=channel, cleanup=False, passive=True, ) async with queue.iterator(no_ack=True) as q: async for message in q: messages.append(message) return async with await create_connection() as connection: channel = await self.create_channel(connection) queue = await declare_queue( queue_name, channel=channel, cleanup=False, ) try: await channel.default_exchange.publish( Message(body), routing_key=queue_name, ) task = loop.create_task(task_inner()) await task assert messages assert messages[0].body == body finally: await queue.delete() async def test_passive_for_exchange( self, declare_exchange: Callable, connection, add_cleanup: Callable, ): name = get_random_name("passive", "exchange") ch1 = await self.create_channel(connection) ch2 = await self.create_channel(connection) ch3 = await self.create_channel(connection) with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await declare_exchange(name, passive=True, channel=ch1) exchange = await declare_exchange(name, auto_delete=True, channel=ch2) exchange_passive = await declare_exchange( name, passive=True, channel=ch3, ) assert exchange.name == exchange_passive.name async def test_passive_queue( self, declare_queue: Callable, connection: aio_pika.Connection, ): name = get_random_name("passive", "queue") ch1 = await self.create_channel(connection) ch2 = await self.create_channel(connection) ch3 = await self.create_channel(connection) with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await declare_queue(name, passive=True, channel=ch1) queue = await declare_queue(name, auto_delete=True, channel=ch2) queue_passive = await declare_queue(name, passive=True, channel=ch3) assert queue.name == queue_passive.name async def test_get_exchange(self, connection, declare_exchange): channel = await self.create_channel(connection) name = get_random_name("passive", "exchange") with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await channel.get_exchange(name) channel = await self.create_channel(connection) exchange = await declare_exchange( name, auto_delete=True, channel=channel, ) exchange_passive = await channel.get_exchange(name) assert exchange.name == exchange_passive.name async def test_get_queue(self, connection, declare_queue): channel = await self.create_channel(connection) name = get_random_name("passive", "queue") with pytest.raises(aio_pika.exceptions.ChannelNotFoundEntity): await channel.get_queue(name) channel = await self.create_channel(connection) queue = await declare_queue(name, auto_delete=True, channel=channel) queue_passive = await channel.get_queue(name) assert queue.name, queue_passive.name @pytest.mark.skip(reason="temporary skip") async def test_channel_blocking_timeout(self, connection): channel = await connection.channel() close_reasons = [] close_event = asyncio.Event() def on_done(*args): close_reasons.append(args) close_event.set() return channel.close_callbacks.add(on_done) async def run(): await channel.set_qos(1) time.sleep(1) await channel.set_qos(0) with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(run(), timeout=0.2) await close_event.wait() assert channel.is_closed # Ensure close callback has been called assert close_reasons with pytest.raises(RuntimeError): await channel.set_qos(10) async def test_heartbeat_disabling( self, loop, amqp_url: URL, connection_fabric, ): url = amqp_url.update_query(heartbeat=0) connection: AbstractConnection = await connection_fabric(url) async with connection: assert ( connection.transport.connection.connection_tune.heartbeat == 0 ) class TestCaseAmqpNoConfirms(TestCaseAmqp): @staticmethod def create_channel(connection: aio_pika.Connection): return connection.channel(publisher_confirms=False) class TestCaseAmqpWithConfirms(TestCaseAmqpBase): @staticmethod def create_channel(connection: aio_pika.Connection): return connection.channel(publisher_confirms=True) @pytest.mark.skip(reason="Have to find another way to close connection") async def test_connection_close( self, connection: aio_pika.Connection, declare_exchange: Callable, ): routing_key = get_random_name() channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) try: with pytest.raises(aio_pika.exceptions.ChannelPreconditionFailed): msg = Message(bytes(shortuuid.uuid(), "utf-8")) msg.delivery_mode = 8 await exchange.publish(msg, routing_key) channel = await self.create_channel(connection) exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) finally: await exchange.delete() async def test_basic_return(self, connection: aio_pika.connection, loop): channel = await self.create_channel( connection, ) # type: aio_pika.Channel f = loop.create_future() def handler(channel, message: ReturnedMessage): f.set_result(message) channel.return_callbacks.add(handler) body = bytes(shortuuid.uuid(), "utf-8") await channel.default_exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), get_random_name("test_basic_return"), ) returned = await f assert returned.body == body # handler with exception f = loop.create_future() await channel.close() channel = await self.create_channel( connection, ) # type: aio_pika.Channel def bad_handler( channel: aio_pika.abc.AbstractChannel, message: aio_pika.message.IncomingMessage, ): try: raise ValueError finally: f.set_result(message) channel.return_callbacks.add(bad_handler) body = bytes(shortuuid.uuid(), "utf-8") await channel.default_exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), get_random_name("test_basic_return"), ) returned = await f assert returned.body == body aio-pika-8.2.5/tests/test_amqp_robust.py000066400000000000000000000051301433544061600203340ustar00rootroot00000000000000import asyncio from functools import partial import pytest from aiormq import ChannelNotFoundEntity import aio_pika from aio_pika import RobustChannel from tests import get_random_name from tests.test_amqp import ( TestCaseAmqp, TestCaseAmqpNoConfirms, TestCaseAmqpWithConfirms, ) @pytest.fixture def connection_fabric(): return aio_pika.connect_robust @pytest.fixture def create_connection(connection_fabric, loop, amqp_url): return partial(connection_fabric, amqp_url, loop=loop) class TestCaseNoRobust(TestCaseAmqp): PARAMS = [{"robust": True}, {"robust": False}] IDS = ["robust=1", "robust=0"] @staticmethod @pytest.fixture(name="declare_queue", params=PARAMS, ids=IDS) def declare_queue_(request, declare_queue): async def fabric(*args, **kwargs) -> aio_pika.Queue: kwargs.update(request.param) return await declare_queue(*args, **kwargs) return fabric @staticmethod @pytest.fixture(name="declare_exchange", params=PARAMS, ids=IDS) def declare_exchange_(request, declare_exchange): async def fabric(*args, **kwargs) -> aio_pika.Queue: kwargs.update(request.param) return await declare_exchange(*args, **kwargs) return fabric async def test_add_reconnect_callback(self, create_connection): connection = await create_connection() def cb(*a, **kw): pass connection.reconnect_callbacks.add(cb) del cb assert len(connection.reconnect_callbacks) == 1 async def test_channel_blocking_timeout_reopen(self, connection): channel: RobustChannel = await connection.channel() # type: ignore close_reasons = [] close_event = asyncio.Event() reopen_event = asyncio.Event() channel.reopen_callbacks.add(lambda _: reopen_event.set()) queue_name = get_random_name("test_channel_blocking_timeout_reopen") def on_done(*args): close_reasons.append(args) close_event.set() return channel.close_callbacks.add(on_done) with pytest.raises(ChannelNotFoundEntity): await channel.declare_queue(queue_name, passive=True) await close_event.wait() assert channel.is_closed # Ensure close callback has been called assert close_reasons await asyncio.wait_for(reopen_event.wait(), timeout=60) await channel.declare_queue(queue_name, auto_delete=True) class TestCaseAmqpNoConfirmsRobust(TestCaseAmqpNoConfirms): pass class TestCaseAmqpWithConfirmsRobust(TestCaseAmqpWithConfirms): pass aio-pika-8.2.5/tests/test_amqp_robust_proxy.py000066400000000000000000000423201433544061600215770ustar00rootroot00000000000000import asyncio import itertools import logging from contextlib import suppress from functools import partial from typing import Callable, Type import aiomisc import aiormq.exceptions import pytest import shortuuid from aiomisc_pytest.pytest_plugin import TCPProxy from pamqp.exceptions import AMQPFrameError from yarl import URL import aio_pika from aio_pika.exceptions import QueueEmpty from aio_pika.message import IncomingMessage, Message from aio_pika.robust_channel import RobustChannel from aio_pika.robust_connection import RobustConnection from aio_pika.robust_queue import RobustQueue from tests import get_random_name @pytest.fixture async def proxy(tcp_proxy: Type[TCPProxy], amqp_direct_url: URL): p = tcp_proxy(amqp_direct_url.host, amqp_direct_url.port) await p.start() try: yield p finally: await p.close() @pytest.fixture def amqp_url(amqp_direct_url, proxy: TCPProxy): return amqp_direct_url.with_host( proxy.proxy_host, ).with_port( proxy.proxy_port, ).update_query( reconnect_interval=1, heartbeat=1, ) @pytest.fixture def proxy_port(aiomisc_unused_port_factory) -> int: return aiomisc_unused_port_factory() @pytest.fixture(scope="module") def connection_fabric(): return aio_pika.connect_robust @pytest.fixture def create_direct_connection(loop, amqp_direct_url): return partial( aio_pika.connect, amqp_direct_url.update_query( name=amqp_direct_url.query["name"] + "::direct", heartbeat=30, ), loop=loop, ) @pytest.fixture def create_connection(connection_fabric, loop, amqp_url): return partial(connection_fabric, amqp_url, loop=loop) @pytest.fixture async def direct_connection(create_direct_connection) -> aio_pika.Connection: async with await create_direct_connection() as conn: yield conn async def test_channel_fixture(channel: aio_pika.RobustChannel): assert isinstance(channel, aio_pika.RobustChannel) async def test_connection_fixture(connection: aio_pika.RobustConnection): assert isinstance(connection, aio_pika.RobustConnection) def test_amqp_url_is_not_direct(amqp_url, amqp_direct_url): assert amqp_url != amqp_direct_url async def test_set_qos(channel: aio_pika.Channel): await channel.set_qos(prefetch_count=1) async def test_revive_passive_queue_on_reconnect( create_connection, direct_connection, proxy: TCPProxy, ): client = await create_connection() assert isinstance(client, RobustConnection) reconnect_event = asyncio.Event() reconnect_count = 0 def reconnect_callback(conn): nonlocal reconnect_count reconnect_count += 1 reconnect_event.set() reconnect_event.clear() client.reconnect_callbacks.add(reconnect_callback) queue_name = get_random_name() channel = await client.channel() assert isinstance(channel, RobustChannel) direct_channel = await direct_connection.channel() direct_queue = await direct_channel.declare_queue( queue_name, auto_delete=True, passive=False, ) queue2 = await channel.declare_queue( direct_queue.name, passive=True, auto_delete=False, ) assert isinstance(queue2, RobustQueue) await proxy.disconnect_all() await reconnect_event.wait() assert reconnect_count == 1 with suppress(asyncio.TimeoutError): await asyncio.wait_for( reconnect_event.wait(), client.reconnect_interval * 2, ) assert reconnect_count == 1 @aiomisc.timeout(30) async def test_robust_reconnect( create_connection, direct_connection, proxy: TCPProxy, loop, add_cleanup: Callable, ): read_conn = await create_connection() # type: aio_pika.RobustConnection reconnect_event = asyncio.Event() read_conn.reconnect_callbacks.add( lambda *_: reconnect_event.set(), ) assert isinstance(read_conn, aio_pika.RobustConnection) write_channel = await direct_connection.channel() async with read_conn: read_channel = await read_conn.channel() assert isinstance(read_channel, aio_pika.RobustChannel) qname = get_random_name("robust", "proxy", "shared") async with read_channel: shared = [] # Declaring temporary queue queue = await write_channel.declare_queue( qname, auto_delete=False, durable=True, ) consumer_event = asyncio.Event() async def reader(queue_name): nonlocal shared try: queue = await read_channel.declare_queue( name=queue_name, passive=True, ) async with queue.iterator() as q: loop.call_soon(consumer_event.set) async for message in q: shared.append(message) await message.ack() finally: logging.info("Exit reader task") try: reader_task = loop.create_task(reader(queue.name)) await consumer_event.wait() logging.info("Disconnect all clients") with proxy.slowdown(1, 1): for i in range(5): await write_channel.default_exchange.publish( Message(str(i).encode()), queue.name, ) await proxy.disconnect_all() # noinspection PyTypeChecker with pytest.raises(AMQPFrameError): await read_conn.channel() logging.info("Waiting reconnect") await reconnect_event.wait() logging.info("Waiting connections") await asyncio.wait_for(read_conn.ready(), timeout=20) for i in range(5, 10): await write_channel.default_exchange.publish( Message(str(i).encode()), queue.name, ) while len(shared) < 10: await asyncio.sleep(0.1) assert len(shared) == 10 reader_task.cancel() await asyncio.gather(reader_task, return_exceptions=True) with pytest.raises(QueueEmpty): await queue.get(timeout=0.5) finally: await queue.purge() # Waiting for rabbitmq queue not in use await asyncio.sleep(1) await queue.delete() async def test_channel_locked_resource2(connection: aio_pika.RobustConnection): ch1 = await connection.channel() ch2 = await connection.channel() qname = get_random_name("channel", "locked", "resource") q1 = await ch1.declare_queue(qname, exclusive=True, robust=False) await q1.consume(print, exclusive=True) with pytest.raises(aiormq.exceptions.ChannelAccessRefused): q2 = await ch2.declare_queue(qname, exclusive=True, robust=False) await q2.consume(print, exclusive=True) async def test_channel_close_when_exclusive_queue( create_connection, create_direct_connection, proxy: TCPProxy, loop, ): logging.info("Creating connections") direct_conn, proxy_conn = await asyncio.gather( create_direct_connection(), create_connection(), ) logging.info("Creating channels") direct_channel, proxy_channel = await asyncio.gather( direct_conn.channel(), proxy_conn.channel(), ) reconnect_event = asyncio.Event() proxy_conn.reconnect_callbacks.add( lambda *_: reconnect_event.set(), weak=False, ) qname = get_random_name("robust", "exclusive", "queue") logging.info("Declaring exclusing queue: %s", qname) proxy_queue = await proxy_channel.declare_queue( qname, exclusive=True, durable=True, ) logging.info("Disconnecting all proxy connections") await proxy.disconnect_all() await asyncio.sleep(0.5) logging.info("Declaring exclusive queue through direct channel") await direct_channel.declare_queue( qname, exclusive=True, durable=True, ) async def close_after(delay, closer): await asyncio.sleep(delay) logging.info("Disconnecting direct connection") await closer() logging.info("Closed") await loop.create_task(close_after(5, direct_conn.close)) # reconnect fired await reconnect_event.wait() # Wait method ready await proxy_conn.connected.wait() await proxy_queue.delete() async def test_context_process_abrupt_channel_close( connection: aio_pika.RobustConnection, declare_exchange: Callable, declare_queue: Callable, ): # https://github.com/mosquito/aio-pika/issues/302 queue_name = get_random_name("test_connection") routing_key = get_random_name("rounting_key") channel = await connection.channel() exchange = await declare_exchange( "direct", auto_delete=True, channel=channel, ) queue = await declare_queue(queue_name, auto_delete=True, channel=channel) await queue.bind(exchange, routing_key) body = bytes(shortuuid.uuid(), "utf-8") await exchange.publish( Message(body, content_type="text/plain", headers={"foo": "bar"}), routing_key, ) incoming_message = await queue.get(timeout=5) # close aiormq channel to emulate abrupt connection/channel close await channel.channel.close() with pytest.raises(aiormq.exceptions.ChannelInvalidStateError): async with incoming_message.process(): # emulate some activity on closed channel await channel.channel.basic_publish( "dummy", exchange="", routing_key="non_existent", ) # emulate connection/channel restoration of connect_robust await channel.reopen() # cleanup queue incoming_message = await queue.get(timeout=5) async with incoming_message.process(): pass await queue.unbind(exchange, routing_key) @aiomisc.timeout(10) async def test_robust_duplicate_queue( connection: aio_pika.RobustConnection, direct_connection: aio_pika.Connection, declare_exchange: Callable, declare_queue: Callable, proxy: TCPProxy, create_task: Callable, ): queue_name = get_random_name("test") channel = await connection.channel() direct_channel = await direct_connection.channel() reconnect_event = asyncio.Event() shared_condition = asyncio.Condition() connection.reconnect_callbacks.add( lambda *_: reconnect_event.set(), ) shared = {} # noinspection PyShadowingNames async def reader(queue: aio_pika.Queue): nonlocal shared async with queue.iterator() as q: async for message in q: message: IncomingMessage # https://www.rabbitmq.com/confirms.html#automatic-requeueing async with shared_condition: shared[message.message_id] = message shared_condition.notify_all() await message.ack() queue = await declare_queue( queue_name, channel=channel, cleanup=False, ) create_task(reader(queue)) for x in range(5): await direct_channel.default_exchange.publish( aio_pika.Message(b"1234567890", message_id=f"0-{x}"), queue_name, ) async with shared_condition: await asyncio.wait_for( shared_condition.wait_for(lambda: len(shared) == 5), timeout=5, ) logging.info("Disconnect all clients") await proxy.disconnect_all() assert len(shared) == 5, shared for x in range(5): await direct_channel.default_exchange.publish( Message(b"1234567890", message_id=f"1-{x}"), queue_name, ) await asyncio.wait_for(reconnect_event.wait(), timeout=5) logging.info("Waiting connections") async with shared_condition: await asyncio.wait_for( shared_condition.wait_for(lambda: len(shared) == 10), timeout=5, ) assert len(shared) == 10 @aiomisc.timeout(10) async def test_channel_restore( connection_fabric, loop, amqp_url, proxy: TCPProxy, add_cleanup: Callable, ): heartbeat = 10 amqp_url = amqp_url.update_query(heartbeat=heartbeat) on_reopen = asyncio.Event() conn = await connection_fabric(amqp_url, loop=loop) assert isinstance(conn, aio_pika.RobustConnection) async with conn: channel = await conn.channel() channel.reopen_callbacks.add(lambda *_: on_reopen.set(), weak=False) assert isinstance(channel, aio_pika.RobustChannel) async with channel: await channel.set_qos(0) await channel.set_qos(1) with pytest.raises(asyncio.TimeoutError): with proxy.slowdown(1, 1): await channel.set_qos(0, timeout=0.5) await on_reopen.wait() await channel.set_qos(0) await channel.set_qos(1) @aiomisc.timeout(20) async def test_channel_reconnect( connection_fabric, loop, amqp_url, proxy: TCPProxy, add_cleanup: Callable, ): on_reconnect = asyncio.Event() conn = await connection_fabric(amqp_url, loop=loop) assert isinstance(conn, aio_pika.RobustConnection) conn.reconnect_callbacks.add(lambda *_: on_reconnect.set(), weak=False) async with conn: channel = await conn.channel() assert isinstance(channel, aio_pika.RobustChannel) async with channel: await channel.set_qos(0) await channel.set_qos(1) await proxy.disconnect_all() await on_reconnect.wait() await channel.set_qos(0) await channel.set_qos(1) @aiomisc.timeout(15) @pytest.mark.parametrize( "reconnect_timeout", [ "0", "1", "0.5", "0.1", "0.05", "0.025", ], ) async def test_channel_reconnect_after_5kb( reconnect_timeout, amqp_url, amqp_direct_url, connection_fabric, loop: asyncio.AbstractEventLoop, proxy: TCPProxy, add_cleanup: Callable, ): connection = await aio_pika.connect_robust( amqp_url.update_query(reconnect_interval=reconnect_timeout), loop=loop, ) direct_connection = await aio_pika.connect(amqp_direct_url, loop=loop) on_reconnect = asyncio.Event() connection.reconnect_callbacks.add( lambda *_: on_reconnect.set(), weak=False, ) num_bytes = 0 def server_to_client(chunk: bytes) -> bytes: nonlocal num_bytes if num_bytes >= 0: num_bytes += len(chunk) if num_bytes < 5000: return chunk num_bytes = 0 loop.call_soon(proxy.disconnect_all) return chunk proxy.set_content_processors( lambda chunk: chunk, server_to_client, ) MESSAGES_TO_EXCHANGE = 50 async with connection.channel() as channel: queue = await channel.declare_queue(auto_delete=False) async with direct_connection.channel() as publish_channel: for _ in range(MESSAGES_TO_EXCHANGE): await publish_channel.default_exchange.publish( aio_pika.Message(body=b"Hello world" * 100), routing_key=queue.name, ) messages = [] async for message in queue.iterator(): messages.append(message) if len(messages) == MESSAGES_TO_EXCHANGE: break assert messages await connection.close() await direct_connection.close() @aiomisc.timeout(30) @pytest.mark.parametrize( "reconnect_timeout,stair", list(itertools.product(["0.1", "0"], [48, 64, 128, 256, 512])), ) async def test_channel_reconnect_stairway( reconnect_timeout, stair, amqp_url, amqp_direct_url, connection_fabric, loop: asyncio.AbstractEventLoop, proxy: TCPProxy, add_cleanup: Callable, ): connection = await aio_pika.connect_robust( amqp_url.update_query(reconnect_interval=reconnect_timeout), loop=loop, ) direct_connection = await aio_pika.connect(amqp_direct_url, loop=loop) on_reconnect = asyncio.Event() connection.reconnect_callbacks.add( lambda *_: on_reconnect.set(), weak=False, ) num_bytes = 0 def server_to_client(chunk: bytes) -> bytes: nonlocal num_bytes, stair if num_bytes >= 0: num_bytes += len(chunk) if num_bytes < stair: return chunk num_bytes = 0 loop.call_soon(proxy.disconnect_all) stair += stair return chunk proxy.set_content_processors( lambda chunk: chunk, server_to_client, ) MESSAGES_TO_EXCHANGE = 50 async with connection.channel() as channel: queue = await channel.declare_queue(auto_delete=False) async with direct_connection.channel() as publish_channel: for _ in range(MESSAGES_TO_EXCHANGE): await publish_channel.default_exchange.publish( aio_pika.Message(body=b"Hello world" * 100), routing_key=queue.name, ) messages = [] async for message in queue.iterator(): messages.append(message) if len(messages) == MESSAGES_TO_EXCHANGE: break assert messages await connection.close() await direct_connection.close() aio-pika-8.2.5/tests/test_amqps.py000066400000000000000000000021651433544061600171260ustar00rootroot00000000000000import ssl from functools import partial import pytest import aio_pika import tests @pytest.fixture( scope="module", params=[aio_pika.connect, aio_pika.connect_robust], ) def connection_fabric(request): return request.param @pytest.fixture def create_connection(connection_fabric, loop, amqp_url): ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.VerifyMode.CERT_NONE return partial( connection_fabric, amqp_url.with_scheme("amqps").with_port(5671), loop=loop, ssl_context=ssl_context, ) async def test_default_context(connection_fabric, amqp_url): with pytest.raises(ConnectionError): await connection_fabric( amqp_url.with_scheme("amqps").with_port(5671), ssl_context=None, ) ssl_context = ssl.create_default_context() with pytest.raises(ConnectionError): await connection_fabric( amqp_url.with_scheme("amqps").with_port(5671), ssl_context=ssl_context, ) class TestCaseAMQPS(tests.test_amqp.TestCaseAmqp): pass aio-pika-8.2.5/tests/test_connect.py000066400000000000000000000023251433544061600174340ustar00rootroot00000000000000import asyncio import pytest from yarl import URL from aio_pika import connect VARIANTS = ( (dict(url="amqp://localhost/"), "amqp://localhost/"), (dict(url="amqp://localhost"), "amqp://localhost/"), (dict(url="amqp://localhost:5674"), "amqp://localhost:5674/"), (dict(url="amqp://localhost:5674//"), "amqp://localhost:5674//"), (dict(url="amqp://localhost:5674/"), "amqp://localhost:5674/"), (dict(host="localhost", port=8888), "amqp://guest:guest@localhost:8888//"), ( dict(host="localhost", port=8888, virtualhost="foo"), "amqp://guest:guest@localhost:8888/foo", ), ( dict(host="localhost", port=8888, virtualhost="/foo"), "amqp://guest:guest@localhost:8888//foo", ), ) class FakeConnection: def __init__(self, url, **kwargs): self.url = URL(url) self.kwargs = kwargs async def connect(self, timeout=None, **kwargs): return @pytest.mark.parametrize("kwargs,expected", VARIANTS) def test_simple(kwargs, expected): loop = asyncio.get_event_loop() # noinspection PyTypeChecker conn = loop.run_until_complete( connect(connection_class=FakeConnection, **kwargs), ) assert conn.url == URL(expected) aio-pika-8.2.5/tests/test_kwargs.py000066400000000000000000000037331433544061600173050ustar00rootroot00000000000000from aiormq.connection import parse_bool, parse_int, parse_timeout from aio_pika import connect from aio_pika.connection import Connection from aio_pika.robust_connection import RobustConnection class MockConnection(Connection): async def connect(self, timeout=None, **kwargs): return self class MockConnectionRobust(RobustConnection): async def connect(self, timeout=None, **kwargs): self.kwargs["reconnect_interval"] = self.reconnect_interval self.kwargs["fail_fast"] = self.fail_fast return self VALUE_GENERATORS = { parse_int: { "-1": -1, "0": 0, "43": 43, "9999999999999999": 9999999999999999, "hello": 0, }, parse_bool: { "disabled": False, "enable": True, "yes": True, "no": False, "": False, None: False, }, parse_timeout: { "0": 0, "Vasyan": 0, "0.1": 0.1, "0.54": 0.54, "1": 1, "100": 100, "1000:": 0, }, } class TestCase: CONNECTION_CLASS = MockConnection async def get_instance(self, url): return await connect(url, connection_class=self.CONNECTION_CLASS) async def test_kwargs(self): instance = await self.get_instance("amqp://localhost/") for key, parser, default in self.CONNECTION_CLASS.KWARGS_TYPES: assert key in instance.kwargs assert instance.kwargs[key] is parser(default) async def test_kwargs_values(self): for key, parser, default in self.CONNECTION_CLASS.KWARGS_TYPES: positives = VALUE_GENERATORS[parser] for example, expected in positives.items(): instance = await self.get_instance( "amqp://localhost/?{}={}".format(key, example), ) assert key in instance.kwargs assert instance.kwargs[key] == expected class TestCaseRobust(TestCase): CONNECTION_CLASS = MockConnectionRobust aio-pika-8.2.5/tests/test_master.py000066400000000000000000000054461433544061600173050ustar00rootroot00000000000000import asyncio import aio_pika from aio_pika.patterns.master import ( CompressedJsonMaster, JsonMaster, Master, NackMessage, RejectMessage, ) class TestMaster: MASTER_CLASS = Master async def test_simple(self, channel: aio_pika.Channel): master = self.MASTER_CLASS(channel) event = asyncio.Event() self.state = [] def worker_func(*, foo, bar): nonlocal event self.state.append((foo, bar)) event.set() worker = await master.create_worker( "worker.foo", worker_func, auto_delete=True, ) await master.proxy.worker.foo(foo=1, bar=2) await event.wait() assert self.state == [(1, 2)] await worker.close() async def test_simple_coro(self, channel: aio_pika.Channel): master = self.MASTER_CLASS(channel) event = asyncio.Event() self.state = [] async def worker_func(*, foo, bar): nonlocal event self.state.append((foo, bar)) event.set() worker = await master.create_worker( "worker.foo", worker_func, auto_delete=True, ) await master.proxy.worker.foo(foo=1, bar=2) await event.wait() assert self.state == [(1, 2)] await worker.close() async def test_simple_many(self, channel: aio_pika.Channel): master = self.MASTER_CLASS(channel) tasks = 100 state = [] def worker_func(*, foo): nonlocal tasks, state state.append(foo) tasks -= 1 worker = await master.create_worker( "worker.foo", worker_func, auto_delete=True, ) for item in range(100): await master.proxy.worker.foo(foo=item) while tasks > 0: await asyncio.sleep(0) assert state == list(range(100)) await worker.close() async def test_exception_classes(self, channel: aio_pika.Channel): master = self.MASTER_CLASS(channel) counter = 200 self.state = [] def worker_func(*, foo): nonlocal counter counter -= 1 if foo < 50: raise RejectMessage(requeue=False) if foo > 100: raise NackMessage(requeue=False) self.state.append(foo) worker = await master.create_worker( "worker.foo", worker_func, auto_delete=True, ) for item in range(200): await master.proxy.worker.foo(foo=item) while counter > 0: await asyncio.sleep(0) assert self.state == list(range(50, 101)) await worker.close() class TestJsonMaster(TestMaster): MASTER_CLASS = JsonMaster class TestCompressedJsonMaster(TestMaster): MASTER_CLASS = CompressedJsonMaster aio-pika-8.2.5/tests/test_memory_leak.py000066400000000000000000000016731433544061600203140ustar00rootroot00000000000000import gc import weakref import aio_pika async def test_leak_unclosed_channel(create_connection): rabbitmq_connection = await create_connection() weakset = weakref.WeakSet() async def f(rabbitmq_connection: aio_pika.Connection, weakset): weakset.add(await rabbitmq_connection.channel()) async with rabbitmq_connection: for i in range(5): await f(rabbitmq_connection, weakset) gc.collect() assert len(tuple(weakset)) == 0 async def test_leak_closed_channel(create_connection): rabbitmq_connection = await create_connection() weakset = weakref.WeakSet() async def f(rabbitmq_connection: aio_pika.Connection, weakset): async with rabbitmq_connection.channel() as channel: weakset.add(channel) async with rabbitmq_connection: for i in range(5): await f(rabbitmq_connection, weakset) gc.collect() assert len(tuple(weakset)) == 0 aio-pika-8.2.5/tests/test_message.py000066400000000000000000000044671433544061600174400ustar00rootroot00000000000000import time from copy import copy from datetime import datetime import shortuuid from aio_pika import DeliveryMode, Message def test_message_copy(): msg1 = Message( bytes(shortuuid.uuid(), "utf-8"), content_type="application/json", content_encoding="text", timestamp=datetime(2000, 1, 1), headers={"h1": "v1", "h2": "v2"}, ) msg2 = copy(msg1) msg1.lock() assert not msg2.locked def test_message_info(): body = bytes(shortuuid.uuid(), "utf-8") info = { "headers": {"foo": "bar"}, "content_type": "application/json", "content_encoding": "text", "delivery_mode": DeliveryMode.PERSISTENT.value, "priority": 0, "correlation_id": "1", "reply_to": "test", "expiration": 1.5, "message_id": shortuuid.uuid(), "timestamp": datetime.utcfromtimestamp(int(time.time())), "type": "0", "user_id": "guest", "app_id": "test", "body_size": len(body), } msg = Message( body=body, headers={"foo": "bar"}, content_type="application/json", content_encoding="text", delivery_mode=DeliveryMode.PERSISTENT, priority=0, correlation_id=1, reply_to="test", expiration=1.5, message_id=info["message_id"], timestamp=info["timestamp"], type="0", user_id="guest", app_id="test", ) assert info == msg.info() def test_headers_setter(): data = {"foo": "bar"} data_expected = {"foo": "bar"} msg = Message(b"", headers={"bar": "baz"}) msg.headers = data assert msg.headers_raw == data_expected def test_headers_content(): data = ( [42, 42], [b"foo", b"foo"], [b"\00", b"\00"], ) for src, value in data: msg = Message(b"", headers={"value": src}) assert msg.headers["value"] == value def test_headers_set(): msg = Message(b"", headers={"header": "value"}) data = ( ["header-1", 42, 42], ["header-2", b"foo", b"foo"], ["header-3", b"\00", b"\00"], ["header-4", {"foo": "bar"}, {"foo": "bar"}], ) for name, src, value in data: msg.headers[name] = value assert msg.headers[name] == value assert msg.headers["header"] == "value" aio-pika-8.2.5/tests/test_pool.py000066400000000000000000000076371433544061600167670ustar00rootroot00000000000000import asyncio from collections import Counter import pytest from aio_pika.pool import Pool, PoolInstance @pytest.mark.parametrize("max_size", [50, 10, 5, 1]) async def test_simple(max_size, loop): counter = 0 async def create_instance(): nonlocal counter await asyncio.sleep(0) counter += 1 return counter pool = Pool(create_instance, max_size=max_size, loop=loop) async def getter(): nonlocal counter, pool async with pool.acquire() as instance: assert instance > 0 await asyncio.sleep(1 if counter < max_size else 0) return instance, counter results = await asyncio.gather(*[getter() for _ in range(200)]) for instance, total in results: assert instance > -1 assert total > -1 assert counter == max_size class TestInstanceBase: class Instance(PoolInstance): def __init__(self): self.closed = False async def close(self): if self.closed: raise RuntimeError self.closed = True @pytest.fixture def instances(self): return set() @pytest.fixture(params=[50, 40, 30, 20, 10]) def max_size(self, request): return request.param @pytest.fixture def pool(self, max_size, instances, loop): async def create_instance(): nonlocal instances obj = TestInstanceBase.Instance() instances.add(obj) return obj return Pool(create_instance, max_size=max_size, loop=loop) class TestInstance(TestInstanceBase): async def test_close(self, pool, instances, loop, max_size): async def getter(): async with pool.acquire(): await asyncio.sleep(0.05) assert not pool.is_closed assert len(instances) == 0 await asyncio.gather(*[getter() for _ in range(200)]) assert len(instances) == max_size for instance in instances: assert not instance.closed await pool.close() for instance in instances: assert instance.closed assert pool.is_closed async def test_close_context_manager(self, pool, instances): async def getter(): async with pool.acquire(): await asyncio.sleep(0.05) async with pool: assert not pool.is_closed assert len(instances) == 0 await asyncio.gather(*[getter() for _ in range(200)]) assert len(instances) > 1 for instance in instances: assert not instance.closed assert not pool.is_closed assert pool.is_closed for instance in instances: assert instance.closed class TestCaseNoMaxSize(TestInstance): async def test_simple(self, pool, loop): call_count = 200 counter = 0 async def getter(): nonlocal counter async with pool.acquire() as instance: await asyncio.sleep(1) assert isinstance(instance, TestInstanceBase.Instance) counter += 1 return counter results = await asyncio.gather(*[getter() for _ in range(call_count)]) for result in results: assert result > -1 assert counter == call_count class TestCaseItemReuse(TestInstanceBase): @pytest.fixture def call_count(self, max_size): return max_size * 5 async def test_simple(self, pool, call_count, instances): counter = Counter() async def getter(): nonlocal counter async with pool.acquire() as instance: await asyncio.sleep(0.05) counter[instance] += 1 await asyncio.gather(*[getter() for _ in range(call_count)]) assert sum(counter.values()) == call_count assert set(counter) == set(instances) assert len(set(counter.values())) == 1 aio-pika-8.2.5/tests/test_rpc.py000066400000000000000000000123701433544061600165700ustar00rootroot00000000000000import asyncio import logging import pytest import aio_pika from aio_pika import Message from aio_pika.exceptions import MessageProcessError from aio_pika.message import IncomingMessage from aio_pika.patterns.rpc import RPC from aio_pika.patterns.rpc import log as rpc_logger from tests import get_random_name def rpc_func(*, foo, bar): assert not foo assert not bar return {"foo": "bar"} class TestCase: async def test_simple(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.register("test.rpc", rpc_func, auto_delete=True) result = await rpc.proxy.test.rpc(foo=None, bar=None) assert result == {"foo": "bar"} await rpc.unregister(rpc_func) await rpc.close() # Close already closed await rpc.close() async def test_error(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.register("test.rpc", rpc_func, auto_delete=True) with pytest.raises(AssertionError): await rpc.proxy.test.rpc(foo=True, bar=None) await rpc.unregister(rpc_func) await rpc.close() async def test_unroutable(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.register("test.rpc", rpc_func, auto_delete=True) with pytest.raises(MessageProcessError): await rpc.proxy.unroutable() await rpc.unregister(rpc_func) await rpc.close() async def test_timed_out(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.register("test.rpc", rpc_func, auto_delete=True) await channel.declare_queue( "test.timed_out", auto_delete=True, arguments={"x-dead-letter-exchange": RPC.DLX_NAME}, ) with pytest.raises(asyncio.TimeoutError): await rpc.call("test.timed_out", expiration=1) await rpc.unregister(rpc_func) await rpc.close() async def test_close_twice(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.close() await rpc.close() async def test_init_twice(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.initialize() await rpc.close() async def test_send_unknown_message( self, channel: aio_pika.Channel, caplog, ): rpc = await RPC.create(channel, auto_delete=True) body = b"test body" with caplog.at_level(logging.WARNING, logger=rpc_logger.name): await channel.default_exchange.publish( Message(body), routing_key=rpc.result_queue.name, ) await asyncio.sleep(0.5) for log_record in caplog.records: if log_record.levelno == logging.WARNING: break else: raise pytest.fail("Expected log message") incoming = log_record.args[0] assert isinstance(incoming, IncomingMessage) assert incoming.body == body assert ( "Message without correlation_id was received:" in log_record.message ) with caplog.at_level(logging.WARNING, logger=rpc_logger.name): await channel.default_exchange.publish( Message(body), routing_key="should-returned", ) await asyncio.sleep(0.5) for log_record in caplog.records: if log_record.levelno == logging.WARNING: break else: raise pytest.fail("Expected log message") incoming = log_record.args[0] assert isinstance(incoming, IncomingMessage) assert incoming.body == body assert ( "Message without correlation_id was received:" in log_record.message ) await rpc.close() async def test_close_cancelling(self, channel: aio_pika.Channel, loop): rpc = await RPC.create(channel, auto_delete=True) async def sleeper(): await asyncio.sleep(60) method_name = get_random_name("test", "sleeper") await rpc.register(method_name, sleeper, auto_delete=True) tasks = set() for _ in range(10): tasks.add(loop.create_task(rpc.call(method_name))) await rpc.close() logging.info("Waiting for results") for task in tasks: with pytest.raises(asyncio.CancelledError): await task async def test_register_twice(self, channel: aio_pika.Channel): rpc = await RPC.create(channel, auto_delete=True) await rpc.register("test.sleeper", lambda x: None, auto_delete=True) with pytest.raises(RuntimeError): await rpc.register( "test.sleeper", lambda x: None, auto_delete=True, ) await rpc.register("test.one", rpc_func, auto_delete=True) with pytest.raises(RuntimeError): await rpc.register("test.two", rpc_func, auto_delete=True) await rpc.unregister(rpc_func) await rpc.unregister(rpc_func) await rpc.close() aio-pika-8.2.5/tests/test_tools.py000066400000000000000000000064521433544061600171500ustar00rootroot00000000000000import asyncio import logging from copy import copy from unittest import mock import pytest from aio_pika.tools import CallbackCollection log = logging.getLogger(__name__) # noinspection PyTypeChecker class TestCase: @pytest.fixture def instance(self): return mock.MagicMock() @pytest.fixture def collection(self, instance): return CallbackCollection(instance) def test_basic(self, collection): def func(sender, *args, **kwargs): pass collection.add(func) assert func in collection with pytest.raises(ValueError): collection.add(None) collection.remove(func) with pytest.raises(LookupError): collection.remove(func) for _ in range(10): collection.add(func) assert len(collection) == 1 collection.freeze() with pytest.raises(RuntimeError): collection.freeze() assert len(collection) == 1 with pytest.raises(RuntimeError): collection.add(func) with pytest.raises(RuntimeError): collection.remove(func) with pytest.raises(RuntimeError): collection.clear() collection2 = copy(collection) collection.unfreeze() assert not copy(collection).is_frozen assert collection.is_frozen != collection2.is_frozen with pytest.raises(RuntimeError): collection.unfreeze() collection.clear() assert collection2 assert not collection def test_callback_call(self, collection): l1 = list() l2 = list() assert l1 == l2 collection.add(lambda sender, x: l1.append(x)) collection.add(lambda sender, x: l2.append(x)) collection(1) collection(2) assert l1 == l2 assert l1 == [1, 2] async def test_blank_awaitable_callback(self, collection): await collection() async def test_awaitable_callback(self, loop, collection, instance): future = loop.create_future() shared = [] async def coro(arg): nonlocal shared shared.append(arg) def task_maker(arg): return loop.create_task(coro(arg)) collection.add(future.set_result) collection.add(coro) collection.add(task_maker) await collection() assert shared == [instance, instance] assert await future == instance async def test_collection_create_tasks(self, loop, collection, instance): future = loop.create_future() async def coro(arg): await asyncio.sleep(0.5) future.set_result(arg) collection.add(coro) # noinspection PyAsyncCall collection() assert await future == instance async def test_collection_run_tasks_parallel(self, collection): class Callable: def __init__(self): self.counter = 0 async def __call__(self, *args, **kwargs): await asyncio.sleep(1) self.counter += 1 callables = [Callable() for _ in range(100)] for callable in callables: collection.add(callable) await asyncio.wait_for(collection(), timeout=2) assert [c.counter for c in callables] == [1] * 100 aio-pika-8.2.5/tests/test_types.py000066400000000000000000000017221433544061600171470ustar00rootroot00000000000000import aio_pika import aio_pika.abc async def test_connect_robust(amqp_url) -> None: async with await aio_pika.connect_robust(amqp_url) as connection: assert isinstance(connection, aio_pika.abc.AbstractRobustConnection) assert isinstance(connection, aio_pika.abc.AbstractConnection) channel = await connection.channel() assert isinstance(channel, aio_pika.abc.AbstractRobustChannel) assert isinstance(channel, aio_pika.abc.AbstractChannel) async def test_connect(amqp_url) -> None: async with await aio_pika.connect(amqp_url) as connection: assert isinstance(connection, aio_pika.abc.AbstractConnection) assert not isinstance( connection, aio_pika.abc.AbstractRobustConnection, ) channel = await connection.channel() assert isinstance(channel, aio_pika.abc.AbstractChannel) assert not isinstance( channel, aio_pika.abc.AbstractRobustChannel, ) aio-pika-8.2.5/tox.ini000066400000000000000000000021641433544061600145440ustar00rootroot00000000000000[tox] envlist = lint,mypy,py3{7,8,9,10}{,-uvloop} [testenv] passenv = COVERALLS_* AMQP_* FORCE_COLOR deps = py37-uvloop: uvloop~=0.16.0 py38-uvloop: uvloop~=0.16.0 py39-uvloop: uvloop~=0.16.0 py310-uvloop: uvloop~=0.16.0 extras = develop commands= pytest -vv --cov=aio_pika --cov-report=term-missing --doctest-modules --aiomisc-test-timeout=30 tests - coveralls [testenv:lint] deps = pyflakes<2.5 pylava commands= pylava -o pylava.ini aio_pika tests [testenv:checkdoc] deps = collective.checkdocs pygments commands = python setup.py checkdocs [testenv:mypy] basepython = python3.10 usedevelop = true deps = mypy==0.971 commands = mypy --install-types --non-interactive \ aio_pika \ tests \ docs/source/examples mypy docs/source/rabbitmq-tutorial/examples/1-introduction mypy docs/source/rabbitmq-tutorial/examples/2-work-queues mypy docs/source/rabbitmq-tutorial/examples/3-publish-subscribe mypy docs/source/rabbitmq-tutorial/examples/4-routing mypy docs/source/rabbitmq-tutorial/examples/5-topics mypy docs/source/rabbitmq-tutorial/examples/6-rpc