From 1f079eb29f16ce38ed25248c4907901b4dd26394 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Mon, 1 Jul 2024 10:44:43 +0200 Subject: [PATCH 1/2] update Cerbo release document and scripts --- .../Cerbo_Release/Cerbo Rollout Guidance.pdf | Bin 354347 -> 352462 bytes .../__pycache__/config.cpython-38.pyc | Bin .../__pycache__/convert.cpython-38.pyc | Bin .../__pycache__/data.cpython-38.pyc | Bin .../dbus-fzsonick-48tl.cpython-38.pyc | Bin .../dbus-fzsonick-48tl/config.py | 0 .../dbus-fzsonick-48tl/convert.py | 0 .../dbus-fzsonick-48tl/data.py | 0 .../dbus-fzsonick-48tl/dbus-fzsonick-48tl.py | 0 .../__pycache__/ve_utils.cpython-38.pyc | Bin .../__pycache__/vedbus.cpython-38.pyc | Bin .../ext/velib_python/ve_utils.py | 0 .../ext/velib_python/vedbus.py | 0 .../dbus-fzsonick-48tl/start.sh | 0 .../flows.json | 0 .../openvpn | Bin .../pika-0.13.1/.checkignore | 0 .../pika-0.13.1/.codeclimate.yml | 0 .../pika-0.13.1/.coveragerc | 0 .../pika-0.13.1/.github/ISSUE_TEMPLATE.md | 0 .../.github/PULL_REQUEST_TEMPLATE.md | 0 .../pika-0.13.1/.gitignore | 0 .../pika-0.13.1/.travis.yml | 0 .../pika-0.13.1/CHANGELOG.rst | 0 .../pika-0.13.1/CONTRIBUTING.md | 0 .../pika-0.13.1/LICENSE | 0 .../pika-0.13.1/MANIFEST.in | 0 .../pika-0.13.1/README.rst | 0 .../pika-0.13.1/appveyor.yml | 0 .../pika-0.13.1/docs/Makefile | 0 .../pika-0.13.1/docs/conf.py | 0 .../pika-0.13.1/docs/contributors.rst | 0 .../pika-0.13.1/docs/examples.rst | 0 .../asynchronous_consumer_example.rst | 0 .../asynchronous_publisher_example.rst | 0 .../docs/examples/asyncio_consumer.rst | 0 .../docs/examples/blocking_basic_get.rst | 0 .../docs/examples/blocking_consume.rst | 0 .../examples/blocking_consumer_generator.rst | 0 .../blocking_delivery_confirmations.rst | 0 .../examples/blocking_publish_mandatory.rst | 0 .../comparing_publishing_sync_async.rst | 0 .../docs/examples/connecting_async.rst | 0 .../docs/examples/direct_reply_to.rst | 0 .../heartbeat_and_blocked_timeouts.rst | 0 .../examples/tls_mutual_authentication.rst | 0 .../examples/tls_server_uathentication.rst | 0 .../docs/examples/tornado_consumer.rst | 0 .../docs/examples/twisted_example.rst | 0 .../docs/examples/using_urlparameters.rst | 0 .../pika-0.13.1/docs/faq.rst | 0 .../pika-0.13.1/docs/index.rst | 0 .../pika-0.13.1/docs/intro.rst | 0 .../docs/modules/adapters/asyncio.rst | 0 .../docs/modules/adapters/blocking.rst | 0 .../docs/modules/adapters/index.rst | 0 .../docs/modules/adapters/select.rst | 0 .../docs/modules/adapters/tornado.rst | 0 .../docs/modules/adapters/twisted.rst | 0 .../pika-0.13.1/docs/modules/channel.rst | 0 .../pika-0.13.1/docs/modules/connection.rst | 0 .../pika-0.13.1/docs/modules/credentials.rst | 0 .../pika-0.13.1/docs/modules/exceptions.rst | 0 .../pika-0.13.1/docs/modules/index.rst | 0 .../pika-0.13.1/docs/modules/parameters.rst | 0 .../pika-0.13.1/docs/modules/spec.rst | 0 .../pika-0.13.1/docs/version_history.rst | 0 .../examples/asynchronous_consumer_example.py | 0 .../asynchronous_publisher_example.py | 0 .../examples/basic_consumer_threaded.py | 0 .../pika-0.13.1/examples/confirmation.py | 0 .../pika-0.13.1/examples/consume.py | 0 .../pika-0.13.1/examples/consumer_queued.py | 0 .../pika-0.13.1/examples/consumer_simple.py | 0 .../pika-0.13.1/examples/direct_reply_to.py | 0 .../heartbeat_and_blocked_timeouts.py | 0 .../pika-0.13.1/examples/producer.py | 0 .../pika-0.13.1/examples/publish.py | 0 .../pika-0.13.1/examples/send.py | 0 .../pika-0.13.1/examples/twisted_service.py | 0 .../pika-0.13.1/pika/__init__.py | 0 .../pika-0.13.1/pika/adapters/__init__.py | 0 .../pika/adapters/asyncio_connection.py | 0 .../pika/adapters/base_connection.py | 0 .../pika/adapters/blocking_connection.py | 0 .../pika/adapters/select_connection.py | 0 .../pika/adapters/tornado_connection.py | 0 .../pika/adapters/twisted_connection.py | 0 .../pika-0.13.1/pika/amqp_object.py | 0 .../pika-0.13.1/pika/callback.py | 0 .../pika-0.13.1/pika/channel.py | 0 .../pika-0.13.1/pika/compat.py | 0 .../pika-0.13.1/pika/connection.py | 0 .../pika-0.13.1/pika/credentials.py | 0 .../pika-0.13.1/pika/data.py | 0 .../pika-0.13.1/pika/exceptions.py | 0 .../pika-0.13.1/pika/frame.py | 0 .../pika-0.13.1/pika/heartbeat.py | 0 .../pika-0.13.1/pika/spec.py | 0 .../pika-0.13.1/pika/tcp_socket_opts.py | 0 .../pika-0.13.1/pika/utils.py | 0 .../pika-0.13.1/pylintrc | 0 .../pika-0.13.1/setup.cfg | 0 .../pika-0.13.1/setup.py | 0 .../pika-0.13.1/test-requirements.txt | 0 .../testdata/certs/ca_certificate.pem | 0 .../pika-0.13.1/testdata/certs/ca_key.pem | 0 .../testdata/certs/client_certificate.pem | 0 .../pika-0.13.1/testdata/certs/client_key.pem | 0 .../testdata/certs/server_certificate.pem | 0 .../pika-0.13.1/testdata/certs/server_key.pem | 0 .../pika-0.13.1/testdata/rabbitmq.conf.in | 0 .../pika-0.13.1/testdata/wait-epmd.ps1 | 0 .../pika-0.13.1/testdata/wait-rabbitmq.ps1 | 0 .../tests/acceptance/async_adapter_tests.py | 0 .../tests/acceptance/async_test_base.py | 0 .../tests/acceptance/blocking_adapter_test.py | 0 .../acceptance/enforce_one_basicget_test.py | 0 .../tests/acceptance/forward_server.py | 0 .../tests/acceptance/test_utils.py | 0 .../tests/unit/amqp_object_tests.py | 0 .../tests/unit/base_connection_tests.py | 0 .../tests/unit/blocking_channel_tests.py | 0 .../tests/unit/blocking_connection_tests.py | 0 .../pika-0.13.1/tests/unit/callback_tests.py | 0 .../pika-0.13.1/tests/unit/channel_tests.py | 0 .../pika-0.13.1/tests/unit/compat_tests.py | 0 .../tests/unit/connection_parameters_tests.py | 0 .../tests/unit/connection_tests.py | 0 .../tests/unit/connection_timeout_tests.py | 0 .../unit/content_frame_assembler_tests.py | 0 .../tests/unit/credentials_tests.py | 0 .../pika-0.13.1/tests/unit/data_tests.py | 0 .../pika-0.13.1/tests/unit/exceptions_test.py | 0 .../pika-0.13.1/tests/unit/frame_tests.py | 0 .../pika-0.13.1/tests/unit/heartbeat_tests.py | 0 .../unit/select_connection_ioloop_tests.py | 0 .../unit/select_connection_timer_tests.py | 0 .../pika-0.13.1/tests/unit/tornado_tests.py | 0 .../pika-0.13.1/tests/unit/twisted_tests.py | 0 .../pika-0.13.1/tests/unit/utils_tests.py | 0 .../pika-0.13.1/utils/codegen.py | 0 .../rc.local | 0 .../service/log/run | 0 .../service/log/supervise/lock | 0 .../service/log/supervise/status | Bin .../service/run | 0 .../service/supervise/lock | 0 .../service/supervise/status | Bin .../settings-user.js | 0 firmware/Cerbo_Release/update_Cerbo.py | 21 ++++++------------ 151 files changed, 7 insertions(+), 14 deletions(-) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/__pycache__/config.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/__pycache__/convert.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/__pycache__/data.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/__pycache__/dbus-fzsonick-48tl.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/config.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/convert.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/data.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/__pycache__/ve_utils.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/__pycache__/vedbus.cpython-38.pyc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/vedbus.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/dbus-fzsonick-48tl/start.sh (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/flows.json (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/openvpn (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.checkignore (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.codeclimate.yml (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.coveragerc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.github/ISSUE_TEMPLATE.md (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.github/PULL_REQUEST_TEMPLATE.md (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.gitignore (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/.travis.yml (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/CHANGELOG.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/CONTRIBUTING.md (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/LICENSE (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/MANIFEST.in (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/README.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/appveyor.yml (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/Makefile (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/conf.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/contributors.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/asynchronous_consumer_example.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/asynchronous_publisher_example.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/asyncio_consumer.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/blocking_basic_get.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/blocking_consume.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/blocking_consumer_generator.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/blocking_delivery_confirmations.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/blocking_publish_mandatory.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/comparing_publishing_sync_async.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/connecting_async.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/direct_reply_to.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/heartbeat_and_blocked_timeouts.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/tls_mutual_authentication.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/tls_server_uathentication.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/tornado_consumer.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/twisted_example.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/examples/using_urlparameters.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/faq.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/index.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/intro.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/asyncio.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/blocking.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/index.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/select.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/tornado.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/adapters/twisted.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/channel.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/connection.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/credentials.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/exceptions.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/index.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/parameters.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/modules/spec.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/docs/version_history.rst (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/asynchronous_consumer_example.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/asynchronous_publisher_example.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/basic_consumer_threaded.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/confirmation.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/consume.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/consumer_queued.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/consumer_simple.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/direct_reply_to.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/heartbeat_and_blocked_timeouts.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/producer.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/publish.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/send.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/examples/twisted_service.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/__init__.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/__init__.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/asyncio_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/base_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/blocking_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/select_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/tornado_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/adapters/twisted_connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/amqp_object.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/callback.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/channel.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/compat.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/connection.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/credentials.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/data.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/exceptions.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/frame.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/heartbeat.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/spec.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/tcp_socket_opts.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pika/utils.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/pylintrc (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/setup.cfg (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/setup.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/test-requirements.txt (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/ca_certificate.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/ca_key.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/client_certificate.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/client_key.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/server_certificate.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/certs/server_key.pem (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/rabbitmq.conf.in (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/wait-epmd.ps1 (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/testdata/wait-rabbitmq.ps1 (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/async_adapter_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/async_test_base.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/blocking_adapter_test.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/enforce_one_basicget_test.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/forward_server.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/acceptance/test_utils.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/amqp_object_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/base_connection_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/blocking_channel_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/blocking_connection_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/callback_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/channel_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/compat_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/connection_parameters_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/connection_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/connection_timeout_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/content_frame_assembler_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/credentials_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/data_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/exceptions_test.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/frame_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/heartbeat_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/select_connection_ioloop_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/select_connection_timer_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/tornado_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/twisted_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/tests/unit/utils_tests.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/pika-0.13.1/utils/codegen.py (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/rc.local (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/log/run (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/log/supervise/lock (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/log/supervise/status (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/run (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/supervise/lock (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/service/supervise/status (100%) rename firmware/Cerbo_Release/{NodeRedFiles => CerboReleaseFiles}/settings-user.js (100%) diff --git a/firmware/Cerbo_Release/Cerbo Rollout Guidance.pdf b/firmware/Cerbo_Release/Cerbo Rollout Guidance.pdf index 4b2883c472226292627274841a4ebb8ec6acd88b..d3b99b8f08ad842baec61116491fc9306c2d2efb 100644 GIT binary patch delta 131662 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

Z>wQs0fnu?DXa7}rv->nJQ8*ldv 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%0DhzW2aDTEizx2UBF>wzj*N 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

j_L@wS1Z)@d4qc9mMS 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 Hl0^Pr=O~_< delta 133587 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 z !-{`_6P3JiE$q=$HdCMFX4HH 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
=0itv#5JvB}j$mXklu^}am>S(%&h2tEo%X2Wd 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 t Date: Mon, 1 Jul 2024 11:49:57 +0200 Subject: [PATCH 2/2] add Venus Release document and scripts --- .../dbus-fzsonick-48tl/__init__.py | 0 .../dbus-fzsonick-48tl/config.py | 0 .../dbus-fzsonick-48tl/config.pyc | Bin .../dbus-fzsonick-48tl/convert.py | 0 .../dbus-fzsonick-48tl/convert.pyc | Bin .../dbus-fzsonick-48tl/data.py | 0 .../dbus-fzsonick-48tl/data.pyc | Bin .../dbus-fzsonick-48tl/dbus-fzsonick-48tl.py | 0 .../ext/velib_python/ve_utils.py | 0 .../ext/velib_python/ve_utils.pyc | Bin .../ext/velib_python/vedbus.py | 0 .../ext/velib_python/vedbus.pyc | Bin .../python_libs/__init__.py | 0 .../python_libs/__init__.pyc | Bin .../python_libs/ie_dbus/__init__.py | 0 .../python_libs/ie_dbus/__init__.pyc | Bin .../python_libs/ie_dbus/dbus_service.py | 0 .../python_libs/ie_dbus/dbus_service.pyc | Bin .../python_libs/ie_dbus/private/__init__.py | 0 .../python_libs/ie_dbus/private/__init__.pyc | Bin .../python_libs/ie_dbus/private/datatypes.py | 0 .../python_libs/ie_dbus/private/datatypes.pyc | Bin .../ie_dbus/private/dbus_connection.py | 0 .../ie_dbus/private/dbus_connection.pyc | Bin .../ie_dbus/private/dbus_daemon.py | 0 .../ie_dbus/private/dbus_daemon.pyc | Bin .../python_libs/ie_dbus/private/dbus_types.py | 0 .../ie_dbus/private/dbus_types.pyc | Bin .../ie_dbus/private/message_types.py | 0 .../ie_dbus/private/message_types.pyc | Bin .../ie_dbus/private/own_properties.py | 0 .../ie_dbus/private/own_properties.pyc | Bin .../ie_dbus/private/remote_properties.py | 0 .../ie_dbus/private/remote_properties.pyc | Bin .../python_libs/ie_dbus/private/settings.py | 0 .../python_libs/ie_dbus/private/settings.pyc | Bin .../ie_dbus/private/ve_constants.py | 0 .../ie_dbus/private/ve_constants.pyc | Bin .../python_libs/ie_utils/__init__.py | 0 .../python_libs/ie_utils/__init__.pyc | Bin .../python_libs/ie_utils/filters.py | 0 .../python_libs/ie_utils/filters.pyc | Bin .../python_libs/ie_utils/main_loop.py | 0 .../python_libs/ie_utils/main_loop.pyc | Bin .../python_libs/ie_utils/mixins.py | 0 .../python_libs/ie_utils/mixins.pyc | Bin .../python_libs/ie_utils/utils.py | 0 .../python_libs/ie_utils/utils.pyc | Bin .../python_libs/pysnooper/__init__.py | 0 .../python_libs/pysnooper/pycompat.py | 0 .../python_libs/pysnooper/tracer.py | 0 .../python_libs/pysnooper/utils.py | 0 .../python_libs/pysnooper/variables.py | 0 .../dbus-fzsonick-48tl/service/down | 0 .../dbus-fzsonick-48tl/service/log/down | 0 .../dbus-fzsonick-48tl/service/log/run | 0 .../dbus-fzsonick-48tl/service/run | 0 .../dbus-fzsonick-48tl/signals.py | 0 .../dbus-fzsonick-48tl/signals.pyc | Bin .../dbus-fzsonick-48tl/start.sh | 0 .../pika-0.13.1/.checkignore | 5 + .../pika-0.13.1/.codeclimate.yml | 8 + .../VenusReleaseFiles/pika-0.13.1/.coveragerc | 2 + .../pika-0.13.1/.github/ISSUE_TEMPLATE.md | 15 + .../.github/PULL_REQUEST_TEMPLATE.md | 43 + .../VenusReleaseFiles/pika-0.13.1/.gitignore | 20 + .../VenusReleaseFiles/pika-0.13.1/.travis.yml | 103 + .../pika-0.13.1/CHANGELOG.rst | 760 +++++ .../pika-0.13.1/CONTRIBUTING.md | 68 + .../VenusReleaseFiles/pika-0.13.1/LICENSE | 25 + .../VenusReleaseFiles/pika-0.13.1/MANIFEST.in | 2 + .../VenusReleaseFiles/pika-0.13.1/README.rst | 157 + .../pika-0.13.1/appveyor.yml | 107 + .../pika-0.13.1/docs/Makefile | 153 + .../pika-0.13.1/docs/conf.py | 34 + .../pika-0.13.1/docs/contributors.rst | 104 + .../pika-0.13.1/docs/examples.rst | 23 + .../asynchronous_consumer_example.rst | 357 +++ .../asynchronous_publisher_example.rst | 359 +++ .../docs/examples/asyncio_consumer.rst | 355 +++ .../docs/examples/blocking_basic_get.rst | 23 + .../docs/examples/blocking_consume.rst | 29 + .../examples/blocking_consumer_generator.rst | 73 + .../blocking_delivery_confirmations.rst | 28 + .../examples/blocking_publish_mandatory.rst | 29 + .../comparing_publishing_sync_async.rst | 64 + .../docs/examples/connecting_async.rst | 49 + .../docs/examples/direct_reply_to.rst | 81 + .../heartbeat_and_blocked_timeouts.rst | 37 + .../examples/tls_mutual_authentication.rst | 61 + .../examples/tls_server_uathentication.rst | 60 + .../docs/examples/tornado_consumer.rst | 349 +++ .../docs/examples/twisted_example.rst | 49 + .../docs/examples/using_urlparameters.rst | 68 + .../pika-0.13.1/docs/faq.rst | 18 + .../pika-0.13.1/docs/index.rst | 37 + .../pika-0.13.1/docs/intro.rst | 125 + .../docs/modules/adapters/asyncio.rst | 9 + .../docs/modules/adapters/blocking.rst | 13 + .../docs/modules/adapters/index.rst | 15 + .../docs/modules/adapters/select.rst | 7 + .../docs/modules/adapters/tornado.rst | 9 + .../docs/modules/adapters/twisted.rst | 15 + .../pika-0.13.1/docs/modules/channel.rst | 10 + .../pika-0.13.1/docs/modules/connection.rst | 7 + .../pika-0.13.1/docs/modules/credentials.rst | 18 + .../pika-0.13.1/docs/modules/exceptions.rst | 5 + .../pika-0.13.1/docs/modules/index.rst | 21 + .../pika-0.13.1/docs/modules/parameters.rst | 42 + .../pika-0.13.1/docs/modules/spec.rst | 8 + .../pika-0.13.1/docs/version_history.rst | 760 +++++ .../examples/asynchronous_consumer_example.py | 350 +++ .../asynchronous_publisher_example.py | 353 +++ .../examples/basic_consumer_threaded.py | 68 + .../pika-0.13.1/examples/confirmation.py | 47 + .../pika-0.13.1/examples/consume.py | 44 + .../pika-0.13.1/examples/consumer_queued.py | 66 + .../pika-0.13.1/examples/consumer_simple.py | 57 + .../pika-0.13.1/examples/direct_reply_to.py | 80 + .../heartbeat_and_blocked_timeouts.py | 48 + .../pika-0.13.1/examples/producer.py | 42 + .../pika-0.13.1/examples/publish.py | 32 + .../pika-0.13.1/examples/send.py | 41 + .../pika-0.13.1/examples/twisted_service.py | 209 ++ .../pika-0.13.1/pika/__init__.py | 17 + .../pika-0.13.1/pika/adapters/__init__.py | 21 + .../pika/adapters/asyncio_connection.py | 254 ++ .../pika/adapters/base_connection.py | 582 ++++ .../pika/adapters/blocking_connection.py | 2616 +++++++++++++++ .../pika/adapters/select_connection.py | 1178 +++++++ .../pika/adapters/tornado_connection.py | 122 + .../pika/adapters/twisted_connection.py | 474 +++ .../pika-0.13.1/pika/amqp_object.py | 66 + .../pika-0.13.1/pika/callback.py | 410 +++ .../pika-0.13.1/pika/channel.py | 1436 +++++++++ .../pika-0.13.1/pika/compat.py | 210 ++ .../pika-0.13.1/pika/connection.py | 2356 ++++++++++++++ .../pika-0.13.1/pika/credentials.py | 120 + .../pika-0.13.1/pika/data.py | 322 ++ .../pika-0.13.1/pika/exceptions.py | 257 ++ .../pika-0.13.1/pika/frame.py | 265 ++ .../pika-0.13.1/pika/heartbeat.py | 214 ++ .../pika-0.13.1/pika/spec.py | 2319 ++++++++++++++ .../pika-0.13.1/pika/tcp_socket_opts.py | 43 + .../pika-0.13.1/pika/utils.py | 16 + .../VenusReleaseFiles/pika-0.13.1/pylintrc | 391 +++ .../VenusReleaseFiles/pika-0.13.1/setup.cfg | 16 + .../VenusReleaseFiles/pika-0.13.1/setup.py | 51 + .../pika-0.13.1/test-requirements.txt | 6 + .../testdata/certs/ca_certificate.pem | 19 + .../pika-0.13.1/testdata/certs/ca_key.pem | 28 + .../testdata/certs/client_certificate.pem | 19 + .../pika-0.13.1/testdata/certs/client_key.pem | 27 + .../testdata/certs/server_certificate.pem | 19 + .../pika-0.13.1/testdata/certs/server_key.pem | 27 + .../pika-0.13.1/testdata/rabbitmq.conf.in | 13 + .../pika-0.13.1/testdata/wait-epmd.ps1 | 21 + .../pika-0.13.1/testdata/wait-rabbitmq.ps1 | 21 + .../tests/acceptance/async_adapter_tests.py | 587 ++++ .../tests/acceptance/async_test_base.py | 247 ++ .../tests/acceptance/blocking_adapter_test.py | 2791 +++++++++++++++++ .../acceptance/enforce_one_basicget_test.py | 29 + .../tests/acceptance/forward_server.py | 526 ++++ .../tests/acceptance/test_utils.py | 73 + .../tests/unit/amqp_object_tests.py | 62 + .../tests/unit/base_connection_tests.py | 85 + .../tests/unit/blocking_channel_tests.py | 82 + .../tests/unit/blocking_connection_tests.py | 328 ++ .../pika-0.13.1/tests/unit/callback_tests.py | 413 +++ .../pika-0.13.1/tests/unit/channel_tests.py | 1437 +++++++++ .../pika-0.13.1/tests/unit/compat_tests.py | 24 + .../tests/unit/connection_parameters_tests.py | 743 +++++ .../tests/unit/connection_tests.py | 907 ++++++ .../tests/unit/connection_timeout_tests.py | 143 + .../unit/content_frame_assembler_tests.py | 151 + .../tests/unit/credentials_tests.py | 159 + .../pika-0.13.1/tests/unit/data_tests.py | 85 + .../pika-0.13.1/tests/unit/exceptions_test.py | 40 + .../pika-0.13.1/tests/unit/frame_tests.py | 110 + .../pika-0.13.1/tests/unit/heartbeat_tests.py | 189 ++ .../unit/select_connection_ioloop_tests.py | 634 ++++ .../unit/select_connection_timer_tests.py | 353 +++ .../pika-0.13.1/tests/unit/tornado_tests.py | 18 + .../pika-0.13.1/tests/unit/twisted_tests.py | 21 + .../pika-0.13.1/tests/unit/utils_tests.py | 11 + .../pika-0.13.1/utils/codegen.py | 404 +++ .../{ => VenusReleaseFiles}/rc.local | 0 .../Venus_Release/Vernus Rollout Guidance.pdf | Bin 0 -> 194481 bytes firmware/Venus_Release/update_Venus.py | 103 + 189 files changed, 30967 insertions(+) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/config.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/config.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/convert.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/convert.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/data.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/data.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/ve_utils.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/vedbus.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/ext/velib_python/vedbus.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/__init__.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/filters.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/filters.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/utils.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/ie_utils/utils.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/pysnooper/__init__.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/pysnooper/pycompat.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/pysnooper/tracer.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/pysnooper/utils.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/python_libs/pysnooper/variables.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/service/down (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/service/log/down (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/service/log/run (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/service/run (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/signals.py (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/signals.pyc (100%) rename firmware/Venus_Release/{ => VenusReleaseFiles}/dbus-fzsonick-48tl/start.sh (100%) create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.checkignore create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.codeclimate.yml create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.coveragerc create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/ISSUE_TEMPLATE.md create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.gitignore create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.travis.yml create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CHANGELOG.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CONTRIBUTING.md create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/LICENSE create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/MANIFEST.in create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/README.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/appveyor.yml create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/Makefile create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/conf.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/contributors.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_consumer_example.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_publisher_example.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asyncio_consumer.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_basic_get.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consume.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consumer_generator.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_delivery_confirmations.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_publish_mandatory.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/comparing_publishing_sync_async.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/connecting_async.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/direct_reply_to.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/heartbeat_and_blocked_timeouts.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_mutual_authentication.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_server_uathentication.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tornado_consumer.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/twisted_example.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/using_urlparameters.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/faq.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/index.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/intro.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/asyncio.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/blocking.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/index.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/select.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/tornado.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/twisted.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/channel.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/connection.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/credentials.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/exceptions.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/index.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/parameters.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/spec.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/version_history.rst create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_consumer_example.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_publisher_example.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/basic_consumer_threaded.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/confirmation.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consume.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_queued.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_simple.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/direct_reply_to.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/heartbeat_and_blocked_timeouts.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/producer.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/publish.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/send.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/twisted_service.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/__init__.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/__init__.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/asyncio_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/base_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/blocking_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/select_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/tornado_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/twisted_connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/amqp_object.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/callback.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/channel.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/compat.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/connection.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/credentials.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/data.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/exceptions.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/frame.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/heartbeat.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/spec.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/tcp_socket_opts.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/utils.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pylintrc create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.cfg create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/test-requirements.txt create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_certificate.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_key.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_certificate.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_key.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_certificate.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_key.pem create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/rabbitmq.conf.in create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-epmd.ps1 create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-rabbitmq.ps1 create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_adapter_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_test_base.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/blocking_adapter_test.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/enforce_one_basicget_test.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/forward_server.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/test_utils.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/amqp_object_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/base_connection_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/blocking_channel_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/blocking_connection_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/callback_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/channel_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/compat_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/connection_parameters_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/connection_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/connection_timeout_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/content_frame_assembler_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/credentials_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/data_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/exceptions_test.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/frame_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/heartbeat_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/select_connection_ioloop_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/select_connection_timer_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/tornado_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/twisted_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/utils_tests.py create mode 100644 firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/utils/codegen.py rename firmware/Venus_Release/{ => VenusReleaseFiles}/rc.local (100%) create mode 100644 firmware/Venus_Release/Vernus Rollout Guidance.pdf create mode 100644 firmware/Venus_Release/update_Venus.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/config.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/config.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/config.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/config.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/convert.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/convert.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/convert.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/convert.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/convert.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/convert.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/convert.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/convert.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/data.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/data.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/data.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/data.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/data.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/data.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/data.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/data.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/ve_utils.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/ve_utils.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/ve_utils.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/ve_utils.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/ve_utils.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/vedbus.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/vedbus.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/vedbus.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/vedbus.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/vedbus.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/vedbus.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/ext/velib_python/vedbus.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/ext/velib_python/vedbus.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/__init__.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/__init__.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/__init__.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/__init__.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/__init__.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/dbus_service.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/__init__.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/datatypes.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_connection.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_daemon.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/dbus_types.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/message_types.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/own_properties.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/remote_properties.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/settings.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_dbus/private/ve_constants.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/__init__.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/filters.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/filters.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/filters.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/filters.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/filters.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/filters.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/filters.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/filters.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/main_loop.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/mixins.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/utils.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/utils.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/utils.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/utils.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/utils.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/utils.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/ie_utils/utils.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/ie_utils/utils.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/__init__.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/__init__.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/__init__.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/pycompat.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/pycompat.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/pycompat.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/pycompat.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/tracer.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/tracer.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/tracer.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/tracer.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/utils.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/utils.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/utils.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/utils.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/variables.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/variables.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/python_libs/pysnooper/variables.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/python_libs/pysnooper/variables.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/service/down b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/down similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/service/down rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/down diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/service/log/down b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/log/down similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/service/log/down rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/log/down diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/service/log/run b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/log/run similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/service/log/run rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/log/run diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/service/run b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/run similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/service/run rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/service/run diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/signals.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/signals.py similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/signals.py rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/signals.py diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/signals.pyc b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/signals.pyc similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/signals.pyc rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/signals.pyc diff --git a/firmware/Venus_Release/dbus-fzsonick-48tl/start.sh b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/start.sh similarity index 100% rename from firmware/Venus_Release/dbus-fzsonick-48tl/start.sh rename to firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/start.sh diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.checkignore b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.checkignore new file mode 100644 index 000000000..e5a2365ca --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.checkignore @@ -0,0 +1,5 @@ +**/docs +**/examples +**/test +**/utils +setup.py diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.codeclimate.yml b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.codeclimate.yml new file mode 100644 index 000000000..ba9e7cc78 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.codeclimate.yml @@ -0,0 +1,8 @@ +languages: + - python +exclude_paths: + - docs/* + - tests/* + - utils/* + - pika/examples/* + - pika/spec.py diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.coveragerc b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.coveragerc new file mode 100644 index 000000000..16b0aa24c --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = pika/spec.py diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/ISSUE_TEMPLATE.md b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..97428b0c5 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +Thank you for using Pika. + +GitHub issues are **strictly** used for actionable work and pull +requests. + +Pika's maintainers do NOT use GitHub issues for questions, root cause +analysis, conversations, code reviews, etc. + +Please direct all non-work issues to either the `pika-python` or +`rabbitmq-users` mailing list: + +* https://groups.google.com/forum/#!forum/pika-python +* https://groups.google.com/forum/#!forum/rabbitmq-users + +Thank you diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/PULL_REQUEST_TEMPLATE.md b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..13131ca02 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +## Proposed Changes + +Please describe the big picture of your changes here to communicate to +the Pika team why we should accept this pull request. If it fixes a bug +or resolves a feature request, be sure to link to that issue. + +A pull request that doesn't explain **why** the change was made has a +much lower chance of being accepted. + +If English isn't your first language, don't worry about it and try to +communicate the problem you are trying to solve to the best of your +abilities. As long as we can understand the intent, it's all good. + +## Types of Changes + +What types of changes does your code introduce to this project? +_Put an `x` in the boxes that apply_ + +- [ ] Bugfix (non-breaking change which fixes issue #NNNN) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation (correction or otherwise) +- [ ] Cosmetics (whitespace, appearance) + +## Checklist + +_Put an `x` in the boxes that apply. You can also fill these out after +creating the PR. If you're unsure about any of them, don't hesitate to +ask on the +[`pika-python`](https://groups.google.com/forum/#!forum/pika-python) +mailing list. We're here to help! This is simply a reminder of what we +are going to look for before merging your code._ + +- [ ] I have read the `CONTRIBUTING.md` document +- [ ] All tests pass locally with my changes +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have added necessary documentation (if appropriate) + +## Further Comments + +If this is a relatively large or complex change, kick off the discussion +by explaining why you chose the solution you did and what alternatives +you considered, etc. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.gitignore b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.gitignore new file mode 100644 index 000000000..8d1682e26 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.gitignore @@ -0,0 +1,20 @@ +*.pyc +*~ +.idea +.coverage +.tox +.DS_Store +.python-version +pika.iml +codegen +pika.egg-info +debug/ +examples/pika +examples/blocking/pika +atlassian*xml +build +dist +docs/_build +venv*/ +env/ +testdata/*.conf diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.travis.yml b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.travis.yml new file mode 100644 index 000000000..a359dff13 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/.travis.yml @@ -0,0 +1,103 @@ +language: python + +sudo: false + +addons: + apt: + sources: + - sourceline: deb https://packages.erlang-solutions.com/ubuntu trusty contrib + key_url: https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc + packages: + # apt-cache show erlang-nox=1:20.3-1 | grep Depends | tr ' ' '\n' | grep erlang | grep -v erlang-base-hipe | tr -d ',' | sed 's/$/=1:20.3-1/' + - erlang-nox + +env: + global: + - RABBITMQ_VERSION=3.7.8 + - RABBITMQ_DOWNLOAD_URL="https://github.com/rabbitmq/rabbitmq-server/releases/download/v$RABBITMQ_VERSION/rabbitmq-server-generic-unix-$RABBITMQ_VERSION.tar.xz" + - RABBITMQ_TAR="rabbitmq-$RABBITMQ_VERSION.tar.xz" + - PATH=$HOME/.local/bin:$PATH + - AWS_DEFAULT_REGION=us-east-1 + - secure: "Eghft2UgJmWuCgnqz6O+KV5F9AERzUbKIeXkcw7vsFAVdkB9z01XgqVLhQ6N+n6i8mkiRDkc0Jes6htVtO4Hi6lTTFeDhu661YCXXTFdRdsx+D9v5bgw8Q2bP41xFy0iao7otYqkzFKIo32Q2cUYzMUqXlS661Yai5DXldr3mjM=" + - secure: "LjieH/Yh0ng5gwT6+Pl3rL7RMxxb/wOlogoLG7cS99XKdX6N4WRVFvWbHWwCxoVr0be2AcyQynu4VOn+0jC8iGfQjkJZ7UrJjZCDGWbNjAWrNcY0F9VdretFDy8Vn2sHfBXq8fINqszJkgTnmbQk8dZWUtj0m/RNVnOBeBcsIOU=" + +stages: +- test +- name: coverage + if: repo = pika/pika +- name: deploy + if: tag IS present + +cache: + apt: true + directories: + - $HOME/.cache + +install: + - pip install -r test-requirements.txt + - pip install awscli==1.11.18 + - if [ ! -d "$HOME/.cache" ]; then mkdir "$HOME/.cache"; fi + - if [ -s "$HOME/.cache/$RABBITMQ_TAR" ]; then echo "[INFO] found cached $RABBITMQ_TAR file"; else wget -O "$HOME/.cache/$RABBITMQ_TAR" "$RABBITMQ_DOWNLOAD_URL"; fi + - tar -C "$TRAVIS_BUILD_DIR" -xvf "$HOME/.cache/$RABBITMQ_TAR" + - sed -e "s#PIKA_DIR#$TRAVIS_BUILD_DIR#g" "$TRAVIS_BUILD_DIR/testdata/rabbitmq.conf.in" > "$TRAVIS_BUILD_DIR/testdata/rabbitmq.conf" + +before_script: + - pip freeze + - /bin/sh -c "RABBITMQ_PID_FILE=$TRAVIS_BUILD_DIR/rabbitmq.pid RABBITMQ_CONFIG_FILE=$TRAVIS_BUILD_DIR/testdata/rabbitmq $TRAVIS_BUILD_DIR/rabbitmq_server-$RABBITMQ_VERSION/sbin/rabbitmq-server &" + - /bin/sh "$TRAVIS_BUILD_DIR/rabbitmq_server-$RABBITMQ_VERSION/sbin/rabbitmqctl" wait "$TRAVIS_BUILD_DIR/rabbitmq.pid" + - /bin/sh "$TRAVIS_BUILD_DIR/rabbitmq_server-$RABBITMQ_VERSION/sbin/rabbitmqctl" status + +script: + # See https://github.com/travis-ci/travis-ci/issues/1066 and https://github.com/pika/pika/pull/984#issuecomment-370565220 + # as to why 'set -e' and 'set +e' are added here + - set -e + - nosetests + - PIKA_TEST_TLS=true nosetests + - set +e + +after_success: + - aws s3 cp .coverage "s3://com-gavinroy-travis/pika/$TRAVIS_BUILD_NUMBER/.coverage.${TRAVIS_PYTHON_VERSION}" + +jobs: + include: + - python: pypy3 + - python: pypy + - python: 2.7 + - python: 3.4 + - python: 3.5 + - python: 3.6 + - python: 3.7 + dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) + - stage: coverage + if: fork = false OR type != pull_request + python: 3.6 + services: [] + install: + - pip install awscli coverage codecov + before_script: [] + script: + - mkdir coverage + - aws s3 cp --recursive s3://com-gavinroy-travis/pika/$TRAVIS_BUILD_NUMBER/ coverage + - cd coverage + - coverage combine + - cd .. + - mv coverage/.coverage . + - coverage report + after_success: codecov + - stage: deploy + if: repo = pika/pika + python: 3.6 + services: [] + install: true + before_script: [] + script: true + after_success: [] + deploy: + distributions: sdist bdist_wheel + provider: pypi + user: crad + on: + tags: true + all_branches: true + password: + secure: "V/JTU/X9C6uUUVGEAWmWWbmKW7NzVVlC/JWYpo05Ha9c0YV0vX4jOfov2EUAphM0WwkD/MRhz4dq3kCU5+cjHxR3aTSb+sbiElsCpaciaPkyrns+0wT5MCMO29Lpnq2qBLc1ePR1ey5aTWC/VibgFJOL7H/3wyvukL6ZaCnktYk=" diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CHANGELOG.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CHANGELOG.rst new file mode 100644 index 000000000..8a7578d77 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CHANGELOG.rst @@ -0,0 +1,760 @@ +Version History +=============== + +0.13.1 2019-03-07 +----------------- + +`GitHub milestone `_ + +0.13.0 2019-01-17 +----------------- + +`GitHub milestone `_ + +- `AsyncioConnection`, `TornadoConnection` and `TwistedProtocolConnection` are no longer auto-imported (`PR `_) +- Python `3.7` support (`Issue `_) + +0.12.0 2018-06-19 +----------------- + +`GitHub milestone `_ + +This is an interim release prior to version `1.0.0`. It includes the following backported pull requests and commits from the `master` branch: + +- `PR #908 `_ +- `PR #910 `_ +- `PR #918 `_ +- `PR #920 `_ +- `PR #924 `_ +- `PR #937 `_ +- `PR #938 `_ +- `PR #933 `_ +- `PR #940 `_ +- `PR #932 `_ +- `PR #928 `_ +- `PR #934 `_ +- `PR #915 `_ +- `PR #946 `_ +- `PR #947 `_ +- `PR #952 `_ +- `PR #956 `_ +- `PR #966 `_ +- `PR #975 `_ +- `PR #978 `_ +- `PR #981 `_ +- `PR #994 `_ +- `PR #1007 `_ +- `PR #1045 `_ (manually backported) +- `PR #1011 `_ + +Commits: + +Travis CI fail fast - 3f0e739 + +New features: + +`BlockingConnection` now supports the `add_callback_threadsafe` method which allows a function to be executed correctly on the IO loop thread. The main use-case for this is as follows: + +- Application sets up a thread for `BlockingConnection` and calls `basic_consume` on it +- When a message is received, work is done on another thread +- When the work is done, the worker uses `connection.add_callback_threadsafe` to call the `basic_ack` method on the channel instance. + +Please see `examples/basic_consumer_threaded.py` for an example. As always, `SelectConnection` and a fully async consumer/publisher is the preferred method of using Pika. + +Heartbeats are now sent at an interval equal to 1/2 of the negotiated idle connection timeout. RabbitMQ's default timeout value is 60 seconds, so heartbeats will be sent at a 30 second interval. In addition, Pika's check for an idle connection will be done at an interval equal to the timeout value plus 5 seconds to allow for delays. This results in an interval of 65 seconds by default. + +0.11.2 2017-11-30 +----------------- + +`GitHub milestone `_ + +`0.11.2 `_ + +- Remove `+` character from platform releases string (`PR `_) + +0.11.1 2017-11-27 +----------------- + +`GitHub milestone `_ + +`0.11.1 `_ + +- Fix `BlockingConnection` to ensure event loop exits (`PR `_) +- Heartbeat timeouts will use the client value if specified (`PR `_) +- Allow setting some common TCP options (`PR `_) +- Errors when decoding Unicode are ignored (`PR `_) +- Fix large number encoding (`PR `_) + +0.11.0 2017-07-29 +----------------- + +`GitHub milestone `_ + +`0.11.0 `_ + + - Simplify Travis CI configuration for OS X. + - Add `asyncio` connection adapter for Python 3.4 and newer. + - Connection failures that occur after the socket is opened and before the + AMQP connection is ready to go are now reported by calling the connection + error callback. Previously these were not consistently reported. + - In BaseConnection.close, call _handle_ioloop_stop only if the connection is + already closed to allow the asynchronous close operation to complete + gracefully. + - Pass error information from failed socket connection to user callbacks + on_open_error_callback and on_close_callback with result_code=-1. + - ValueError is raised when a completion callback is passed to an asynchronous + (nowait) Channel operation. It's an application error to pass a non-None + completion callback with an asynchronous request, because this callback can + never be serviced in the asynchronous scenario. + - `Channel.basic_reject` fixed to allow `delivery_tag` to be of type `long` + as well as `int`. (by quantum5) + - Implemented support for blocked connection timeouts in + `pika.connection.Connection`. This feature is available to all pika adapters. + See `pika.connection.ConnectionParameters` docstring to learn more about + `blocked_connection_timeout` configuration. + - Deprecated the `heartbeat_interval` arg in `pika.ConnectionParameters` in + favor of the `heartbeat` arg for consistency with the other connection + parameters classes `pika.connection.Parameters` and `pika.URLParameters`. + - When the `port` arg is not set explicitly in `ConnectionParameters` + constructor, but the `ssl` arg is set explicitly, then set the port value to + to the default AMQP SSL port if SSL is enabled, otherwise to the default + AMQP plaintext port. + - `URLParameters` will raise ValueError if a non-empty URL scheme other than + {amqp | amqps | http | https} is specified. + - `InvalidMinimumFrameSize` and `InvalidMaximumFrameSize` exceptions are + deprecated. pika.connection.Parameters.frame_max property setter now raises + the standard `ValueError` exception when the value is out of bounds. + - Removed deprecated parameter `type` in `Channel.exchange_declare` and + `BlockingChannel.exchange_declare` in favor of the `exchange_type` arg that + doesn't overshadow the builtin `type` keyword. + - Channel.close() on OPENING channel transitions it to CLOSING instead of + raising ChannelClosed. + - Channel.close() on CLOSING channel raises `ChannelAlreadyClosing`; used to + raise `ChannelClosed`. + - Connection.channel() raises `ConnectionClosed` if connection is not in OPEN + state. + - When performing graceful close on a channel and `Channel.Close` from broker + arrives while waiting for CloseOk, don't release the channel number until + CloseOk arrives to avoid race condition that may lead to a new channel + receiving the CloseOk that was destined for the closing channel. + - The `backpressure_detection` option of `ConnectionParameters` and + `URLParameters` property is DEPRECATED in favor of `Connection.Blocked` and + `Connection.Unblocked`. See `Connection.add_on_connection_blocked_callback`. + +0.10.0 2015-09-02 +----------------- + +`0.10.0 `_ + + - a9bf96d - LibevConnection: Fixed dict chgd size during iteration (Michael Laing) + - 388c55d - SelectConnection: Fixed KeyError exceptions in IOLoop timeout executions (Shinji Suzuki) + - 4780de3 - BlockingConnection: Add support to make BlockingConnection a Context Manager (@reddec) + +0.10.0b2 2015-07-15 +------------------- + + - f72b58f - Fixed failure to purge _ConsumerCancellationEvt from BlockingChannel._pending_events during basic_cancel. (Vitaly Kruglikov) + +0.10.0b1 2015-07-10 +------------------- + +High-level summary of notable changes: + +- Change to 3-Clause BSD License +- Python 3.x support +- Over 150 commits from 19 contributors +- Refactoring of SelectConnection ioloop +- This major release contains certain non-backward-compatible API changes as + well as significant performance improvements in the `BlockingConnection` + adapter. +- Non-backward-compatible changes in `Channel.add_on_return_callback` callback's + signature. +- The `AsyncoreConnection` adapter was retired + +**Details** + +Python 3.x: this release introduces python 3.x support. Tested on Python 3.3 +and 3.4. + +`AsyncoreConnection`: Retired this legacy adapter to reduce maintenance burden; +the recommended replacement is the `SelectConnection` adapter. + +`SelectConnection`: ioloop was refactored for compatibility with other ioloops. + +`Channel.add_on_return_callback`: The callback is now passed the individual +parameters channel, method, properties, and body instead of a tuple of those +values for congruence with other similar callbacks. + +`BlockingConnection`: This adapter underwent a makeover under the hood and +gained significant performance improvements as well as enhanced timer +resolution. It is now implemented as a client of the `SelectConnection` adapter. + +Below is an overview of the `BlockingConnection` and `BlockingChannel` API +changes: + + - Recursion: the new implementation eliminates callback recursion that + sometimes blew out the stack in the legacy implementation (e.g., + publish -> consumer_callback -> publish -> consumer_callback, etc.). While + `BlockingConnection.process_data_events` and `BlockingConnection.sleep` may + still be called from the scope of the blocking adapter's callbacks in order + to process pending I/O, additional callbacks will be suppressed whenever + `BlockingConnection.process_data_events` and `BlockingConnection.sleep` are + nested in any combination; in that case, the callback information will be + bufferred and dispatched once nesting unwinds and control returns to the + level-zero dispatcher. + - `BlockingConnection.connect`: this method was removed in favor of the + constructor as the only way to establish connections; this reduces + maintenance burden, while improving reliability of the adapter. + - `BlockingConnection.process_data_events`: added the optional parameter + `time_limit`. + - `BlockingConnection.add_on_close_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_on_open_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_on_open_error_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_backpressure_callback`: not supported + - `BlockingConnection.set_backpressure_multiplier`: not supported + - `BlockingChannel.add_on_flow_callback`: not supported; per docstring in + channel.py: "Note that newer versions of RabbitMQ will not issue this but + instead use TCP backpressure". + - `BlockingChannel.flow`: not supported + - `BlockingChannel.force_data_events`: removed as it is no longer necessary + following redesign of the adapter. + - Removed the `nowait` parameter from `BlockingChannel` methods, forcing + `nowait=False` (former API default) in the implementation; this is more + suitable for the blocking nature of the adapter and its error-reporting + strategy; this concerns the following methods: `basic_cancel`, + `confirm_delivery`, `exchange_bind`, `exchange_declare`, `exchange_delete`, + `exchange_unbind`, `queue_bind`, `queue_declare`, `queue_delete`, and + `queue_purge`. + - `BlockingChannel.basic_cancel`: returns a sequence instead of None; for a + `no_ack=True` consumer, `basic_cancel` returns a sequence of pending + messages that arrived before broker confirmed the cancellation. + - `BlockingChannel.consume`: added new optional kwargs `arguments` and + `inactivity_timeout`. Also, raises ValueError if the consumer creation + parameters don't match those used to create the existing queue consumer + generator, if any; this happens when you break out of the consume loop, then + call `BlockingChannel.consume` again with different consumer-creation args + without first cancelling the previous queue consumer generator via + `BlockingChannel.cancel`. The legacy implementation would silently resume + consuming from the existing queue consumer generator even if the subsequent + `BlockingChannel.consume` was invoked with a different queue name, etc. + - `BlockingChannel.cancel`: returns 0; the legacy implementation tried to + return the number of requeued messages, but this number was not accurate + as it didn't include the messages returned by the Channel class; this count + is not generally useful, so returning 0 is a reasonable replacement. + - `BlockingChannel.open`: removed in favor of having a single mechanism for + creating a channel (`BlockingConnection.channel`); this reduces maintenance + burden, while improving reliability of the adapter. + - `BlockingChannel.confirm_delivery`: raises UnroutableError when unroutable + messages that were sent prior to this call are returned before we receive + Confirm.Select-ok. + - `BlockingChannel.basic_publish: always returns True when delivery + confirmation is not enabled (publisher-acks = off); the legacy implementation + returned a bool in this case if `mandatory=True` to indicate whether the + message was delivered; however, this was non-deterministic, because + Basic.Return is asynchronous and there is no way to know how long to wait + for it or its absence. The legacy implementation returned None when + publishing with publisher-acks = off and `mandatory=False`. The new + implementation always returns True when publishing while + publisher-acks = off. + - `BlockingChannel.publish`: a new alternate method (vs. `basic_publish`) for + publishing a message with more detailed error reporting via UnroutableError + and NackError exceptions. + - `BlockingChannel.start_consuming`: raises pika.exceptions.RecursionError if + called from the scope of a `BlockingConnection` or `BlockingChannel` + callback. + - `BlockingChannel.get_waiting_message_count`: new method; returns the number + of messages that may be retrieved from the current queue consumer generator + via `BasicChannel.consume` without blocking. + +**Commits** + + - 5aaa753 - Fixed SSL import and removed no_ack=True in favor of explicit AMQP message handling based on deferreds (skftn) + - 7f222c2 - Add checkignore for codeclimate (Gavin M. Roy) + - 4dec370 - Implemented BlockingChannel.flow; Implemented BlockingConnection.add_on_connection_blocked_callback; Implemented BlockingConnection.add_on_connection_unblocked_callback. (Vitaly Kruglikov) + - 4804200 - Implemented blocking adapter acceptance test for exchange-to-exchange binding. Added rudimentary validation of BasicProperties passthru in blocking adapter publish tests. Updated CHANGELOG. (Vitaly Kruglikov) + - 4ec07fd - Fixed sending of data in TwistedProtocolConnection (Vitaly Kruglikov) + - a747fb3 - Remove my copyright from forward_server.py test utility. (Vitaly Kruglikov) + - 94246d2 - Return True from basic_publish when pubacks is off. Implemented more blocking adapter accceptance tests. (Vitaly Kruglikov) + - 3ce013d - PIKA-609 Wait for broker to dispatch all messages to client before cancelling consumer in TestBasicCancelWithNonAckableConsumer and TestBasicCancelWithAckableConsumer (Vitaly Kruglikov) + - 293f778 - Created CHANGELOG entry for release 0.10.0. Fixed up callback documentation for basic_get, basic_consume, and add_on_return_callback. (Vitaly Kruglikov) + - 16d360a - Removed the legacy AsyncoreConnection adapter in favor of the recommended SelectConnection adapter. (Vitaly Kruglikov) + - 240a82c - Defer creation of poller's event loop interrupt socket pair until start is called, because some SelectConnection users (e.g., BlockingConnection adapter) don't use the event loop, and these sockets would just get reported as resource leaks. (Vitaly Kruglikov) + - aed5cae - Added EINTR loops in select_connection pollers. Addressed some pylint findings, including an error or two. Wrap socket.send and socket.recv calls in EINTR loops Use the correct exception for socket.error and select.error and get errno depending on python version. (Vitaly Kruglikov) + - 498f1be - Allow passing exchange, queue and routing_key as text, handle short strings as text in python3 (saarni) + - 9f7f243 - Restored basic_consume, basic_cancel, and add_on_cancel_callback (Vitaly Kruglikov) + - 18c9909 - Reintroduced BlockingConnection.process_data_events. (Vitaly Kruglikov) + - 4b25cb6 - Fixed BlockingConnection/BlockingChannel acceptance and unit tests (Vitaly Kruglikov) + - bfa932f - Facilitate proper connection state after BasicConnection._adapter_disconnect (Vitaly Kruglikov) + - 9a09268 - Fixed BlockingConnection test that was failing with ConnectionClosed error. (Vitaly Kruglikov) + - 5a36934 - Copied synchronous_connection.py from pika-synchronous branch Fixed pylint findings Integrated SynchronousConnection with the new ioloop in SelectConnection Defined dedicated message classes PolledMessage and ConsumerMessage and moved from BlockingChannel to module-global scope. Got rid of nowait args from BlockingChannel public API methods Signal unroutable messages via UnroutableError exception. Signal Nack'ed messages via NackError exception. These expose more information about the failure than legacy basic_publich API. Removed set_timeout and backpressure callback methods Restored legacy `is_open`, etc. property names (Vitaly Kruglikov) + - 6226dc0 - Remove deprecated --use-mirrors (Gavin M. Roy) + - 1a7112f - Raise ConnectionClosed when sending a frame with no connection (#439) (Gavin M. Roy) + - 9040a14 - Make delivery_tag non-optional (#498) (Gavin M. Roy) + - 86aabc2 - Bump version (Gavin M. Roy) + - 562075a - Update a few testing things (Gavin M. Roy) + - 4954d38 - use unicode_type in blocking_connection.py (Antti Haapala) + - 133d6bc - Let Travis install ordereddict for Python 2.6, and ttest 3.3, 3.4 too. (Antti Haapala) + - 0d2287d - Pika Python 3 support (Antti Haapala) + - 3125c79 - SSLWantRead is not supported before python 2.7.9 and 3.3 (Will) + - 9a9c46c - Fixed TestDisconnectDuringConnectionStart: it turns out that depending on callback order, it might get either ProbableAuthenticationError or ProbableAccessDeniedError. (Vitaly Kruglikov) + - cd8c9b0 - A fix the write starvation problem that we see with tornado and pika (Will) + - 8654fbc - SelectConnection - make interrupt socketpair non-blocking (Will) + - 4f3666d - Added copyright in forward_server.py and fixed NameError bug (Vitaly Kruglikov) + - f8ebbbc - ignore docs (Gavin M. Roy) + - a344f78 - Updated codeclimate config (Gavin M. Roy) + - 373c970 - Try and fix pathing issues in codeclimate (Gavin M. Roy) + - 228340d - Ignore codegen (Gavin M. Roy) + - 4db0740 - Add a codeclimate config (Gavin M. Roy) + - 7e989f9 - Slight code re-org, usage comment and better naming of test file. (Will) + - 287be36 - Set up _kqueue member of KQueuePoller before calling super constructor to avoid exception due to missing _kqueue member. Call `self._map_event(event)` instead of `self._map_event(event.filter)`, because `KQueuePoller._map_event()` assumes it's getting an event, not an event filter. (Vitaly Kruglikov) + - 62810fb - Fix issue #412: reset BlockingConnection._read_poller in BlockingConnection._adapter_disconnect() to guard against accidental access to old file descriptor. (Vitaly Kruglikov) + - 03400ce - Rationalise adapter acceptance tests (Will) + - 9414153 - Fix bug selecting non epoll poller (Will) + - 4f063df - Use user heartbeat setting if server proposes none (Pau Gargallo) + - 9d04d6e - Deactivate heartbeats when heartbeat_interval is 0 (Pau Gargallo) + - a52a608 - Bug fix and review comments. (Will) + - e3ebb6f - Fix incorrect x-expires argument in acceptance tests (Will) + - 294904e - Get BlockingConnection into consistent state upon loss of TCP/IP connection with broker and implement acceptance tests for those cases. (Vitaly Kruglikov) + - 7f91a68 - Make SelectConnection behave like an ioloop (Will) + - dc9db2b - Perhaps 5 seconds is too agressive for travis (Gavin M. Roy) + - c23e532 - Lower the stuck test timeout (Gavin M. Roy) + - 1053ebc - Late night bug (Gavin M. Roy) + - cd6c1bf - More BaseConnection._handle_error cleanup (Gavin M. Roy) + - a0ff21c - Fix the test to work with Python 2.6 (Gavin M. Roy) + - 748e8aa - Remove pypy for now (Gavin M. Roy) + - 1c921c1 - Socket close/shutdown cleanup (Gavin M. Roy) + - 5289125 - Formatting update from PR (Gavin M. Roy) + - d235989 - Be more specific when calling getaddrinfo (Gavin M. Roy) + - b5d1b31 - Reflect the method name change in pika.callback (Gavin M. Roy) + - df7d3b7 - Cleanup BlockingConnection in a few places (Gavin M. Roy) + - cd99e1c - Rename method due to use in BlockingConnection (Gavin M. Roy) + - 7e0d1b3 - Use google style with yapf instead of pep8 (Gavin M. Roy) + - 7dc9bab - Refactor socket writing to not use sendall #481 (Gavin M. Roy) + - 4838789 - Dont log the fd #521 (Gavin M. Roy) + - 765107d - Add Connection.Blocked callback registration methods #476 (Gavin M. Roy) + - c15b5c1 - Fix _blocking typo pointed out in #513 (Gavin M. Roy) + - 759ac2c - yapf of codegen (Gavin M. Roy) + - 9dadd77 - yapf cleanup of codegen and spec (Gavin M. Roy) + - ddba7ce - Do not reject consumers with no_ack=True #486 #530 (Gavin M. Roy) + - 4528a1a - yapf reformatting of tests (Gavin M. Roy) + - e7b6d73 - Remove catching AttributError (#531) (Gavin M. Roy) + - 41ea5ea - Update README badges [skip ci] (Gavin M. Roy) + - 6af987b - Add note on contributing (Gavin M. Roy) + - 161fc0d - yapf formatting cleanup (Gavin M. Roy) + - edcb619 - Add PYPY to travis testing (Gavin M. Roy) + - 2225771 - Change the coverage badge (Gavin M. Roy) + - 8f7d451 - Move to codecov from coveralls (Gavin M. Roy) + - b80407e - Add confirm_delivery to example (Andrew Smith) + - 6637212 - Update base_connection.py (bstemshorn) + - 1583537 - #544 get_waiting_message_count() (markcf) + - 0c9be99 - Fix #535: pass expected reply_code and reply_text from method frame to Connection._on_disconnect from Connection._on_connection_closed (Vitaly Kruglikov) + - d11e73f - Propagate ConnectionClosed exception out of BlockingChannel._send_method() and log ConnectionClosed in BlockingConnection._on_connection_closed() (Vitaly Kruglikov) + - 63d2951 - Fix #541 - make sure connection state is properly reset when BlockingConnection._check_state_on_disconnect raises ConnectionClosed. This supplements the previously-merged PR #450 by getting the connection into consistent state. (Vitaly Kruglikov) + - 71bc0eb - Remove unused self.fd attribute from BaseConnection (Vitaly Kruglikov) + - 8c08f93 - PIKA-532 Removed unnecessary params (Vitaly Kruglikov) + - 6052ecf - PIKA-532 Fix bug in BlockingConnection._handle_timeout that was preventing _on_connection_closed from being called when not closing. (Vitaly Kruglikov) + - 562aa15 - pika: callback: Display exception message when callback fails. (Stuart Longland) + - 452995c - Typo fix in connection.py (Andrew) + - 361c0ad - Added some missing yields (Robert Weidlich) + - 0ab5a60 - Added complete example for python twisted service (Robert Weidlich) + - 4429110 - Add deployment and webhooks (Gavin M. Roy) + - 7e50302 - Fix has_content style in codegen (Andrew Grigorev) + - 28c2214 - Fix the trove categorization (Gavin M. Roy) + - de8b545 - Ensure frames can not be interspersed on send (Gavin M. Roy) + - 8fe6bdd - Fix heartbeat behaviour after connection failure. (Kyösti Herrala) + - c123472 - Updating BlockingChannel.basic_get doc (it does not receive a callback like the rest of the adapters) (Roberto Decurnex) + - b5f52fb - Fix number of arguments passed to _on_return callback (Axel Eirola) + - 765139e - Lower default TIMEOUT to 0.01 (bra-fsn) + - 6cc22a5 - Fix confirmation on reconnects (bra-fsn) + - f4faf0a - asynchronous publisher and subscriber examples refactored to follow the StepDown rule (Riccardo Cirimelli) + +0.9.14 - 2014-07-11 +------------------- + +`0.9.14 `_ + + - 57fe43e - fix test to generate a correct range of random ints (ml) + - 0d68dee - fix async watcher for libev_connection (ml) + - 01710ad - Use default username and password if not specified in URLParameters (Sean Dwyer) + - fae328e - documentation typo (Jeff Fein-Worton) + - afbc9e0 - libev_connection: reset_io_watcher (ml) + - 24332a2 - Fix the manifest (Gavin M. Roy) + - acdfdef - Remove useless test (Gavin M. Roy) + - 7918e1a - Skip libev tests if pyev is not installed or if they are being run in pypy (Gavin M. Roy) + - bb583bf - Remove the deprecated test (Gavin M. Roy) + - aecf3f2 - Don't reject a message if the channel is not open (Gavin M. Roy) + - e37f336 - Remove UTF-8 decoding in spec (Gavin M. Roy) + - ddc35a9 - Update the unittest to reflect removal of force binary (Gavin M. Roy) + - fea2476 - PEP8 cleanup (Gavin M. Roy) + - 9b97956 - Remove force_binary (Gavin M. Roy) + - a42dd90 - Whitespace required (Gavin M. Roy) + - 85867ea - Update the content_frame_dispatcher tests to reflect removal of auto-cast utf-8 (Gavin M. Roy) + - 5a4bd5d - Remove unicode casting (Gavin M. Roy) + - efea53d - Remove force binary and unicode casting (Gavin M. Roy) + - e918d15 - Add methods to remove deprecation warnings from asyncore (Gavin M. Roy) + - 117f62d - Add a coveragerc to ignore the auto generated pika.spec (Gavin M. Roy) + - 52f4485 - Remove pypy tests from travis for now (Gavin M. Roy) + - c3aa958 - Update README.rst (Gavin M. Roy) + - 3e2319f - Delete README.md (Gavin M. Roy) + - c12b0f1 - Move to RST (Gavin M. Roy) + - 704f5be - Badging updates (Gavin M. Roy) + - 7ae33ca - Update for coverage info (Gavin M. Roy) + - ae7ca86 - add libev_adapter_tests.py; modify .travis.yml to install libev and pyev (ml) + - f86aba5 - libev_connection: add **kwargs to _handle_event; suppress default_ioloop reuse warning (ml) + - 603f1cf - async_test_base: add necessary args to _on_cconn_closed (ml) + - 3422007 - add libev_adapter_tests.py (ml) + - 6cbab0c - removed relative imports and importing urlparse from urllib.parse for py3+ (a-tal) + - f808464 - libev_connection: add async watcher; add optional parameters to add_timeout (ml) + - c041c80 - Remove ev all together for now (Gavin M. Roy) + - 9408388 - Update the test descriptions and timeout (Gavin M. Roy) + - 1b552e0 - Increase timeout (Gavin M. Roy) + - 69a1f46 - Remove the pyev requirement for 2.6 testing (Gavin M. Roy) + - fe062d2 - Update package name (Gavin M. Roy) + - 611ad0e - Distribute the LICENSE and README.md (#350) (Gavin M. Roy) + - df5e1d8 - Ensure that the entire frame is written using socket.sendall (#349) (Gavin M. Roy) + - 69ec8cf - Move the libev install to before_install (Gavin M. Roy) + - a75f693 - Update test structure (Gavin M. Roy) + - 636b424 - Update things to ignore (Gavin M. Roy) + - b538c68 - Add tox, nose.cfg, update testing config (Gavin M. Roy) + - a0e7063 - add some tests to increase coverage of pika.connection (Charles Law) + - c76d9eb - Address issue #459 (Gavin M. Roy) + - 86ad2db - Raise exception if positional arg for parameters isn't an instance of Parameters (Gavin M. Roy) + - 14d08e1 - Fix for python 2.6 (Gavin M. Roy) + - bd388a3 - Use the first unused channel number addressing #404, #460 (Gavin M. Roy) + - e7676e6 - removing a debug that was left in last commit (James Mutton) + - 6c93b38 - Fixing connection-closed behavior to detect on attempt to publish (James Mutton) + - c3f0356 - Initialize bytes_written in _handle_write() (Jonathan Kirsch) + - 4510e95 - Fix _handle_write() may not send full frame (Jonathan Kirsch) + - 12b793f - fixed Tornado Consumer example to successfully reconnect (Yang Yang) + - f074444 - remove forgotten import of ordereddict (Pedro Abranches) + - 1ba0aea - fix last merge (Pedro Abranches) + - 10490a6 - change timeouts structure to list to maintain scheduling order (Pedro Abranches) + - 7958394 - save timeouts in ordered dict instead of dict (Pedro Abranches) + - d2746bf - URLParameters and ConnectionParameters accept unicode strings (Allard Hoeve) + - 596d145 - previous fix for AttributeError made parent and child class methods identical, remove duplication (James Mutton) + - 42940dd - UrlParameters Docs: fixed amqps scheme examples (Riccardo Cirimelli) + - 43904ff - Dont test this in PyPy due to sort order issue (Gavin M. Roy) + - d7d293e - Don't leave __repr__ sorting up to chance (Gavin M. Roy) + - 848c594 - Add integration test to travis and fix invocation (Gavin M. Roy) + - 2678275 - Add pypy to travis tests (Gavin M. Roy) + - 1877f3d - Also addresses issue #419 (Gavin M. Roy) + - 470c245 - Address issue #419 (Gavin M. Roy) + - ca3cb59 - Address issue #432 (Gavin M. Roy) + - a3ff6f2 - Default frame max should be AMQP FRAME_MAX (Gavin M. Roy) + - ff3d5cb - Remove max consumer tag test due to change in code. (Gavin M. Roy) + - 6045dda - Catch KeyError (#437) to ensure that an exception is not raised in a race condition (Gavin M. Roy) + - 0b4d53a - Address issue #441 (Gavin M. Roy) + - 180e7c4 - Update license and related files (Gavin M. Roy) + - 256ed3d - Added Jython support. (Erik Olof Gunnar Andersson) + - f73c141 - experimental work around for recursion issue. (Erik Olof Gunnar Andersson) + - a623f69 - Prevent #436 by iterating the keys and not the dict (Gavin M. Roy) + - 755fcae - Add support for authentication_failure_close, connection.blocked (Gavin M. Roy) + - c121243 - merge upstream master (Michael Laing) + - a08dc0d - add arg to channel.basic_consume (Pedro Abranches) + - 10b136d - Documentation fix (Anton Ryzhov) + - 9313307 - Fixed minor markup errors. (Jorge Puente Sarrín) + - fb3e3cf - Fix the spelling of UnsupportedAMQPFieldException (Garrett Cooper) + - 03d5da3 - connection.py: Propagate the force_channel keyword parameter to methods involved in channel creation (Michael Laing) + - 7bbcff5 - Documentation fix for basic_publish (JuhaS) + - 01dcea7 - Expose no_ack and exclusive to BlockingChannel.consume (Jeff Tang) + - d39b6aa - Fix BlockingChannel.basic_consume does not block on non-empty queues (Juhyeong Park) + - 6e1d295 - fix for issue 391 and issue 307 (Qi Fan) + - d9ffce9 - Update parameters.rst (cacovsky) + - 6afa41e - Add additional badges (Gavin M. Roy) + - a255925 - Fix return value on dns resolution issue (Laurent Eschenauer) + - 3f7466c - libev_connection: tweak docs (Michael Laing) + - 0aaed93 - libev_connection: Fix varable naming (Michael Laing) + - 0562d08 - libev_connection: Fix globals warning (Michael Laing) + - 22ada59 - libev_connection: use globals to track sigint and sigterm watchers as they are created globally within libev (Michael Laing) + - 2649b31 - Move badge [skip ci] (Gavin M. Roy) + - f70eea1 - Remove pypy and installation attempt of pyev (Gavin M. Roy) + - f32e522 - Conditionally skip external connection adapters if lib is not installed (Gavin M. Roy) + - cce97c5 - Only install pyev on python 2.7 (Gavin M. Roy) + - ff84462 - Add travis ci support (Gavin M. Roy) + - cf971da - lib_evconnection: improve signal handling; add callback (Michael Laing) + - 9adb269 - bugfix in returning a list in Py3k (Alex Chandel) + - c41d5b9 - update exception syntax for Py3k (Alex Chandel) + - c8506f1 - fix _adapter_connect (Michael Laing) + - 67cb660 - Add LibevConnection to README (Michael Laing) + - 1f9e72b - Propagate low-level connection errors to the AMQPConnectionError. (Bjorn Sandberg) + - e1da447 - Avoid race condition in _on_getok on successive basic_get() when clearing out callbacks (Jeff) + - 7a09979 - Add support for upcoming Connection.Blocked/Unblocked (Gavin M. Roy) + - 53cce88 - TwistedChannel correctly handles multi-argument deferreds. (eivanov) + - 66f8ace - Use uuid when creating unique consumer tag (Perttu Ranta-aho) + - 4ee2738 - Limit the growth of Channel._cancelled, use deque instead of list. (Perttu Ranta-aho) + - 0369aed - fix adapter references and tweak docs (Michael Laing) + - 1738c23 - retry select.select() on EINTR (Cenk Alti) + - 1e55357 - libev_connection: reset internal state on reconnect (Michael Laing) + - 708559e - libev adapter (Michael Laing) + - a6b7c8b - Prioritize EPollPoller and KQueuePoller over PollPoller and SelectPoller (Anton Ryzhov) + - 53400d3 - Handle socket errors in PollPoller and EPollPoller Correctly check 'select.poll' availability (Anton Ryzhov) + - a6dc969 - Use dict.keys & items instead of iterkeys & iteritems (Alex Chandel) + - 5c1b0d0 - Use print function syntax, in examples (Alex Chandel) + - ac9f87a - Fixed a typo in the name of the Asyncore Connection adapter (Guruprasad) + - dfbba50 - Fixed bug mentioned in Issue #357 (Erik Andersson) + - c906a2d - Drop additional flags when getting info for the hostnames, log errors (#352) (Gavin M. Roy) + - baf23dd - retry poll() on EINTR (Cenk Alti) + - 7cd8762 - Address ticket #352 catching an error when socket.getprotobyname fails (Gavin M. Roy) + - 6c3ec75 - Prep for 0.9.14 (Gavin M. Roy) + - dae7a99 - Bump to 0.9.14p0 (Gavin M. Roy) + - 620edc7 - Use default port and virtual host if omitted in URLParameters (Issue #342) (Gavin M. Roy) + - 42a8787 - Move the exception handling inside the while loop (Gavin M. Roy) + - 10e0264 - Fix connection back pressure detection issue #347 (Gavin M. Roy) + - 0bfd670 - Fixed mistake in commit 3a19d65. (Erik Andersson) + - da04bc0 - Fixed Unknown state on disconnect error message generated when closing connections. (Erik Andersson) + - 3a19d65 - Alternative solution to fix #345. (Erik Andersson) + - abf9fa8 - switch to sendall to send entire frame (Dustin Koupal) + - 9ce8ce4 - Fixed the async publisher example to work with reconnections (Raphaël De Giusti) + - 511028a - Fix typo in TwistedChannel docstring (cacovsky) + - 8b69e5a - calls self._adapter_disconnect() instead of self.disconnect() which doesn't actually exist #294 (Mark Unsworth) + - 06a5cf8 - add NullHandler to prevent logging warnings (Cenk Alti) + - f404a9a - Fix #337 cannot start ioloop after stop (Ralf Nyren) + +0.9.13 - 2013-05-15 +------------------- + +`0.9.13 `_ + +**Major Changes** + +- IPv6 Support with thanks to Alessandro Tagliapietra for initial prototype +- Officially remove support for <= Python 2.5 even though it was broken already +- Drop pika.simplebuffer.SimpleBuffer in favor of the Python stdlib collections.deque object +- New default object for receiving content is a "bytes" object which is a str wrapper in Python 2, but paves way for Python 3 support +- New "Raw" mode for frame decoding content frames (#334) addresses issues #331, #229 added by Garth Williamson +- Connection and Disconnection logic refactored, allowing for cleaner separation of protocol logic and socket handling logic as well as connection state management +- New "on_open_error_callback" argument in creating connection objects and new Connection.add_on_open_error_callback method +- New Connection.connect method to cleanly allow for reconnection code +- Support for all AMQP field types, using protocol specified signed/unsigned unpacking + +**Backwards Incompatible Changes** + +- Method signature for creating connection objects has new argument "on_open_error_callback" which is positionally before "on_close_callback" +- Internal callback variable names in connection.Connection have been renamed and constants used. If you relied on any of these callbacks outside of their internal use, make sure to check out the new constants. +- Connection._connect method, which was an internal only method is now deprecated and will raise a DeprecationWarning. If you relied on this method, your code needs to change. +- pika.simplebuffer has been removed + +**Bugfixes** + +- BlockingConnection consumer generator does not free buffer when exited (#328) +- Unicode body payloads in the blocking adapter raises exception (#333) +- Support "b" short-short-int AMQP data type (#318) +- Docstring type fix in adapters/select_connection (#316) fix by Rikard Hultén +- IPv6 not supported (#309) +- Stop the HeartbeatChecker when connection is closed (#307) +- Unittest fix for SelectConnection (#336) fix by Erik Andersson +- Handle condition where no connection or socket exists but SelectConnection needs a timeout for retrying a connection (#322) +- TwistedAdapter lagging behind BaseConnection changes (#321) fix by Jan Urbański + +**Other** + +- Refactored documentation +- Added Twisted Adapter example (#314) by nolinksoft + +0.9.12 - 2013-03-18 +------------------- + +`0.9.12 `_ + +**Bugfixes** + +- New timeout id hashing was not unique + +0.9.11 - 2013-03-17 +------------------- + +`0.9.11 `_ + +**Bugfixes** + +- Address inconsistent channel close callback documentation and add the signature + change to the TwistedChannel class (#305) +- Address a missed timeout related internal data structure name change + introduced in the SelectConnection 0.9.10 release. Update all connection + adapters to use same signature and docstring (#306). + +0.9.10 - 2013-03-16 +------------------- + +`0.9.10 `_ + +**Bugfixes** + +- Fix timeout in twisted adapter (Submitted by cellscape) +- Fix blocking_connection poll timer resolution to milliseconds (Submitted by cellscape) +- Fix channel._on_close() without a method frame (Submitted by Richard Boulton) +- Addressed exception on close (Issue #279 - fix by patcpsc) +- 'messages' not initialized in BlockingConnection.cancel() (Issue #289 - fix by Mik Kocikowski) +- Make queue_unbind behave like queue_bind (Issue #277) +- Address closing behavioral issues for connections and channels (Issue #275) +- Pass a Method frame to Channel._on_close in Connection._on_disconnect (Submitted by Jan Urbański) +- Fix channel closed callback signature in the Twisted adapter (Submitted by Jan Urbański) +- Don't stop the IOLoop on connection close for in the Twisted adapter (Submitted by Jan Urbański) +- Update the asynchronous examples to fix reconnecting and have it work +- Warn if the socket was closed such as if RabbitMQ dies without a Close frame +- Fix URLParameters ssl_options (Issue #296) +- Add state to BlockingConnection addressing (Issue #301) +- Encode unicode body content prior to publishing (Issue #282) +- Fix an issue with unicode keys in BasicProperties headers key (Issue #280) +- Change how timeout ids are generated (Issue #254) +- Address post close state issues in Channel (Issue #302) + +** Behavior changes ** + +- Change core connection communication behavior to prefer outbound writes over reads, addressing a recursion issue +- Update connection on close callbacks, changing callback method signature +- Update channel on close callbacks, changing callback method signature +- Give more info in the ChannelClosed exception +- Change the constructor signature for BlockingConnection, block open/close callbacks +- Disable the use of add_on_open_callback/add_on_close_callback methods in BlockingConnection + + +0.9.9 - 2013-01-29 +------------------ + +`0.9.9 `_ + +**Bugfixes** + +- Only remove the tornado_connection.TornadoConnection file descriptor from the IOLoop if it's still open (Issue #221) +- Allow messages with no body (Issue #227) +- Allow for empty routing keys (Issue #224) +- Don't raise an exception when trying to send a frame to a closed connection (Issue #229) +- Only send a Connection.CloseOk if the connection is still open. (Issue #236 - Fix by noleaf) +- Fix timeout threshold in blocking connection - (Issue #232 - Fix by Adam Flynn) +- Fix closing connection while a channel is still open (Issue #230 - Fix by Adam Flynn) +- Fixed misleading warning and exception messages in BaseConnection (Issue #237 - Fix by Tristan Penman) +- Pluralised and altered the wording of the AMQPConnectionError exception (Issue #237 - Fix by Tristan Penman) +- Fixed _adapter_disconnect in TornadoConnection class (Issue #237 - Fix by Tristan Penman) +- Fixing hang when closing connection without any channel in BlockingConnection (Issue #244 - Fix by Ales Teska) +- Remove the process_timeouts() call in SelectConnection (Issue #239) +- Change the string validation to basestring for host connection parameters (Issue #231) +- Add a poller to the BlockingConnection to address latency issues introduced in Pika 0.9.8 (Issue #242) +- reply_code and reply_text is not set in ChannelException (Issue #250) +- Add the missing constraint parameter for Channel._on_return callback processing (Issue #257 - Fix by patcpsc) +- Channel callbacks not being removed from callback manager when channel is closed or deleted (Issue #261) + +0.9.8 - 2012-11-18 +------------------ + +`0.9.8 `_ + +**Bugfixes** + +- Channel.queue_declare/BlockingChannel.queue_declare not setting up callbacks property for empty queue name (Issue #218) +- Channel.queue_bind/BlockingChannel.queue_bind not allowing empty routing key +- Connection._on_connection_closed calling wrong method in Channel (Issue #219) +- Fix tx_commit and tx_rollback bugs in BlockingChannel (Issue #217) + +0.9.7 - 2012-11-11 +------------------ + +`0.9.7 `_ + +**New features** + +- generator based consumer in BlockingChannel (See :doc:`examples/blocking_consumer_generator` for example) + +**Changes** + +- BlockingChannel._send_method will only wait if explicitly told to + +**Bugfixes** + +- Added the exchange "type" parameter back but issue a DeprecationWarning +- Dont require a queue name in Channel.queue_declare() +- Fixed KeyError when processing timeouts (Issue # 215 - Fix by Raphael De Giusti) +- Don't try and close channels when the connection is closed (Issue #216 - Fix by Charles Law) +- Dont raise UnexpectedFrame exceptions, log them instead +- Handle multiple synchronous RPC calls made without waiting for the call result (Issues #192, #204, #211) +- Typo in docs (Issue #207 Fix by Luca Wehrstedt) +- Only sleep on connection failure when retry attempts are > 0 (Issue #200) +- Bypass _rpc method and just send frames for Basic.Ack, Basic.Nack, Basic.Reject (Issue #205) + +0.9.6 - 2012-10-29 +------------------ + +`0.9.6 `_ + +**New features** + +- URLParameters +- BlockingChannel.start_consuming() and BlockingChannel.stop_consuming() +- Delivery Confirmations +- Improved unittests + +**Major bugfix areas** + +- Connection handling +- Blocking functionality in the BlockingConnection +- SSL +- UTF-8 Handling + +**Removals** + +- pika.reconnection_strategies +- pika.channel.ChannelTransport +- pika.log +- pika.template +- examples directory + +0.9.5 - 2011-03-29 +------------------ + +`0.9.5 `_ + +**Changelog** + +- Scope changes with adapter IOLoops and CallbackManager allowing for cleaner, multi-threaded operation +- Add support for Confirm.Select with channel.Channel.confirm_delivery() +- Add examples of delivery confirmation to examples (demo_send_confirmed.py) +- Update uses of log.warn with warning.warn for TCP Back-pressure alerting +- License boilerplate updated to simplify license text in source files +- Increment the timeout in select_connection.SelectPoller reducing CPU utilization +- Bug fix in Heartbeat frame delivery addressing issue #35 +- Remove abuse of pika.log.method_call through a majority of the code +- Rename of key modules: table to data, frames to frame +- Cleanup of frame module and related classes +- Restructure of tests and test runner +- Update functional tests to respect RABBITMQ_HOST, RABBITMQ_PORT environment variables +- Bug fixes to reconnection_strategies module +- Fix the scale of timeout for PollPoller to be specified in milliseconds +- Remove mutable default arguments in RPC calls +- Add data type validation to RPC calls +- Move optional credentials erasing out of connection.Connection into credentials module +- Add support to allow for additional external credential types +- Add a NullHandler to prevent the 'No handlers could be found for logger "pika"' error message when not using pika.log in a client app at all. +- Clean up all examples to make them easier to read and use +- Move documentation into its own repository https://github.com/pika/documentation + +- channel.py + + - Move channel.MAX_CHANNELS constant from connection.CHANNEL_MAX + - Add default value of None to ChannelTransport.rpc + - Validate callback and acceptable replies parameters in ChannelTransport.RPC + - Remove unused connection attribute from Channel + +- connection.py + + - Remove unused import of struct + - Remove direct import of pika.credentials.PlainCredentials + - Change to import pika.credentials + - Move CHANNEL_MAX to channel.MAX_CHANNELS + - Change ConnectionParameters initialization parameter heartbeat to boolean + - Validate all inbound parameter types in ConnectionParameters + - Remove the Connection._erase_credentials stub method in favor of letting the Credentials object deal with that itself. + - Warn if the credentials object intends on erasing the credentials and a reconnection strategy other than NullReconnectionStrategy is specified. + - Change the default types for callback and acceptable_replies in Connection._rpc + - Validate the callback and acceptable_replies data types in Connection._rpc + +- adapters.blocking_connection.BlockingConnection + + - Addition of _adapter_disconnect to blocking_connection.BlockingConnection + - Add timeout methods to BlockingConnection addressing issue #41 + - BlockingConnection didn't allow you register more than one consumer callback because basic_consume was overridden to block immediately. New behavior allows you to do so. + - Removed overriding of base basic_consume and basic_cancel methods. Now uses underlying Channel versions of those methods. + - Added start_consuming() method to BlockingChannel to start the consumption loop. + - Updated stop_consuming() to iterate through all the registered consumers in self._consumers and issue a basic_cancel. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CONTRIBUTING.md b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CONTRIBUTING.md new file mode 100644 index 000000000..d856697ca --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing + +## Test Coverage + +To contribute to Pika, please make sure that any new features or changes +to existing functionality **include test coverage**. + +*Pull requests that add or change code without coverage have a much lower chance +of being accepted.* + + +## Prerequisites + +Pika test suite has a couple of requirements: + + * Dependencies from `test-dependencies.txt` are installed + * A RabbitMQ node with all defaults is running on `localhost:5672` + + +## Installing Dependencies + +To install the dependencies needed to run Pika tests, use + + pip install -r test-requirements.txt + +which on Python 3 might look like this + + pip3 install -r test-requirements.txt + + +## Running Tests + +To run all test suites, use + + nosetests + +Note that some tests are OS-specific (e.g. epoll on Linux +or kqueue on MacOS and BSD). Those will be skipped +automatically. + +If you would like to run TLS/SSL tests, use the following procedure: + +* Create a `rabbitmq.conf` file: + + ``` + sed -e "s#PIKA_DIR#$PWD#g" ./testdata/rabbitmq.conf.in > ./testdata/rabbitmq.conf + ``` + +* Start RabbitMQ and use the configuration file you just created. An example command + that works with the `generic-unix` package is as follows: + + ``` + $ RABBITMQ_CONFIG_FILE=/path/to/pika/testdata/rabbitmq.conf ./sbin/rabbitmq-server + ``` + +* Run the tests indicating that TLS/SSL connections should be used: + + ``` + PIKA_TEST_TLS=true nosetests + ``` + + +## Code Formatting + +Please format your code using [yapf](http://pypi.python.org/pypi/yapf) +with ``google`` style prior to issuing your pull request. *Note: only format those +lines that you have changed in your pull request. If you format an entire file and +change code outside of the scope of your PR, it will likely be rejected.* diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/LICENSE b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/LICENSE new file mode 100644 index 000000000..0aed110fe --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2009-2017, Tony Garnock-Jones, Gavin M. Roy, Pivotal and others. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Pika project nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/MANIFEST.in b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/MANIFEST.in new file mode 100644 index 000000000..9c8317c45 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.rst \ No newline at end of file diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/README.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/README.rst new file mode 100644 index 000000000..ac39d70f2 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/README.rst @@ -0,0 +1,157 @@ +Pika +==== +Pika is a RabbitMQ (AMQP-0-9-1) client library for Python. + +|Version| |Python versions| |Status| |Coverage| |License| |Docs| + +Introduction +------------- +Pika is a pure-Python implementation of the AMQP 0-9-1 protocol including RabbitMQ's +extensions. + +- Python 2.7 and 3.4+ are supported. + +- Since threads aren't appropriate to every situation, it doesn't + require threads. It takes care not to forbid them, either. The same + goes for greenlets, callbacks, continuations and generators. It is + not necessarily thread-safe however, and your mileage will vary. + +- People may be using direct sockets, plain old `select()`, + or any of the wide variety of ways of getting network events to and from a + python application. Pika tries to stay compatible with all of these, and to + make adapting it to a new environment as simple as possible. + +Documentation +------------- +Pika's documentation can be found at `https://pika.readthedocs.io `_ + +Example +------- +Here is the most simple example of use, sending a message with the BlockingConnection adapter: + +.. code :: python + + import pika + connection = pika.BlockingConnection() + channel = connection.channel() + channel.basic_publish(exchange='example', + routing_key='test', + body='Test Message') + connection.close() + +And an example of writing a blocking consumer: + +.. code :: python + + import pika + connection = pika.BlockingConnection() + channel = connection.channel() + + for method_frame, properties, body in channel.consume('test'): + + # Display the message parts and ack the message + print(method_frame, properties, body) + channel.basic_ack(method_frame.delivery_tag) + + # Escape out of the loop after 10 messages + if method_frame.delivery_tag == 10: + break + + # Cancel the consumer and return any pending messages + requeued_messages = channel.cancel() + print('Requeued %i messages' % requeued_messages) + connection.close() + +Pika provides the following adapters +------------------------------------ + +- AsyncioConnection - adapter for the Python3 AsyncIO event loop +- BlockingConnection - enables blocking, synchronous operation on top of library for simple uses +- SelectConnection - fast asynchronous adapter +- TornadoConnection - adapter for use with the Tornado IO Loop http://tornadoweb.org +- TwistedConnection - adapter for use with the Twisted asynchronous package http://twistedmatrix.com/ + +Requesting message ACKs from another thread +------------------------------------------- +The single-threaded usage constraint of an individual Pika connection adapter +instance may result in a dropped AMQP/stream connection due to AMQP heartbeat +timeout in consumers that take a long time to process an incoming message. A +common solution is to delegate processing of the incoming messages to another +thread, while the connection adapter's thread continues to service its ioloop's +message pump, permitting AMQP heartbeats and other I/O to be serviced in a +timely fashion. + +Messages processed in another thread may not be ACK'ed directly from that thread, +since all accesses to the connection adapter instance must be from a single +thread - the thread that is running the adapter's ioloop. However, this may be +accomplished by requesting a callback to be executed in the adapter's ioloop +thread. For example, the callback function's implementation might look like this: + +.. code :: python + + def ack_message(channel, delivery_tag): + """Note that `channel` must be the same pika channel instance via which + the message being ACKed was retrieved (AMQP protocol constraint). + """ + if channel.is_open: + channel.basic_ack(delivery_tag) + else: + # Channel is already closed, so we can't ACK this message; + # log and/or do something that makes sense for your app in this case. + pass + +The code running in the other thread may request the `ack_message()` function +to be executed in the connection adapter's ioloop thread using an +adapter-specific mechanism: + +- :py:class:`pika.BlockingConnection` abstracts its ioloop from the application + and thus exposes :py:meth:`pika.BlockingConnection.add_callback_threadsafe()`. + Refer to this method's docstring for additional information. For example: + + .. code :: python + + connection.add_callback_threadsafe(functools.partial(ack_message, channel, delivery_tag)) + +- When using a non-blocking connection adapter, such as +:py:class:`pika.adapters.asyncio_connection.AsyncioConnection` or +:py:class:`pika.SelectConnection`, you use the underlying asynchronous +framework's native API for requesting an ioloop-bound callback from +another thread. For example, `SelectConnection`'s `IOLoop` provides +`add_callback_threadsafe()`, `Tornado`'s `IOLoop` has +`add_callback()`, while `asyncio`'s event loop exposes +`call_soon_threadsafe()`. + +This threadsafe callback request mechanism may also be used to delegate +publishing of messages, etc., from a background thread to the connection adapter's +thread. + +Contributing +------------ +To contribute to pika, please make sure that any new features or changes +to existing functionality **include test coverage**. + +*Pull requests that add or change code without coverage will most likely be rejected.* + +Additionally, please format your code using `yapf `_ +with ``google`` style prior to issuing your pull request. *Note: only format those +lines that you have changed in your pull request. If you format an entire file and +change code outside of the scope of your PR, it will likely be rejected.* + +.. |Version| image:: https://img.shields.io/pypi/v/pika.svg? + :target: http://badge.fury.io/py/pika + +.. |Python versions| image:: https://img.shields.io/pypi/pyversions/pika.svg + :target: https://pypi.python.org/pypi/pika + +.. |Status| image:: https://img.shields.io/travis/pika/pika.svg? + :target: https://travis-ci.org/pika/pika + +.. |Coverage| image:: https://img.shields.io/codecov/c/github/pika/pika.svg? + :target: https://codecov.io/github/pika/pika?branch=master + +.. |License| image:: https://img.shields.io/pypi/l/pika.svg? + :target: https://pika.readthedocs.io + +.. |Docs| image:: https://readthedocs.org/projects/pika/badge/?version=stable + :target: https://pika.readthedocs.io + :alt: Documentation Status diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/appveyor.yml b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/appveyor.yml new file mode 100644 index 000000000..13259f809 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/appveyor.yml @@ -0,0 +1,107 @@ +# Windows build and test of Pika + +environment: + erlang_download_url: "http://erlang.org/download/otp_win64_19.3.exe" + erlang_exe_path: "C:\\Users\\appveyor\\erlang_19.3.exe" + erlang_home_dir: "C:\\Users\\appveyor\\erlang" + erlang_erts_version: "erts-8.3" + + rabbitmq_version: 3.7.4 + rabbitmq_installer_download_url: "https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.4/rabbitmq-server-3.7.4.exe" + rabbitmq_installer_path: "C:\\Users\\appveyor\\rabbitmq-server-3.7.4.exe" + + matrix: + - PYTHON_ARCH: "32" + PYTHONHOME: "C:\\Python27" + PIKA_TEST_TLS: false + - PYTHON_ARCH: "32" + PYTHONHOME: "C:\\Python27" + PIKA_TEST_TLS: true + + +cache: + # RabbitMQ is a pretty big package, so caching it in hopes of expediting the + # runtime + - "%erlang_exe_path%" + - "%rabbitmq_installer_path%" + + +install: + - SET PYTHONPATH=%PYTHONHOME% + - SET PATH=%PYTHONHOME%\Scripts;%PYTHONHOME%;%PATH% + + # For diagnostics + - ECHO %PYTHONPATH% + - ECHO %PATH% + - python --version + + - ECHO Upgrading pip... + - python -m pip install --upgrade pip setuptools + - pip --version + + - ECHO Installing wheel... + - pip install wheel + + +build_script: + - ECHO Building distributions... + - python setup.py sdist bdist bdist_wheel + - DIR /s *.whl + + +artifacts: + - path: 'dist\*.whl' + name: pika wheel + + +before_test: + # Install test requirements + - ECHO Installing pika... + - python setup.py install + + - ECHO Installing pika test requirements... + - pip install -r test-requirements.txt + + # List conents of C:\ to help debug caching of rabbitmq artifacts + # - DIR C:\ + + - ps: $webclient=New-Object System.Net.WebClient + + - ECHO Downloading Erlang... + - ps: if (-Not (Test-Path "$env:erlang_exe_path")) { $webclient.DownloadFile("$env:erlang_download_url", "$env:erlang_exe_path") } else { Write-Host "Found" $env:erlang_exe_path "in cache." } + + - ECHO Installing Erlang... + - start /B /WAIT %erlang_exe_path% /S /D=%erlang_home_dir% + - set ERLANG_HOME=%erlang_home_dir% + + - ECHO Downloading RabbitMQ... + - ps: if (-Not (Test-Path "$env:rabbitmq_installer_path")) { $webclient.DownloadFile("$env:rabbitmq_installer_download_url", "$env:rabbitmq_installer_path") } else { Write-Host "Found" $env:rabbitmq_installer_path "in cache." } + + - ECHO Creating directory %AppData%\RabbitMQ... + - ps: New-Item -ItemType Directory -ErrorAction Continue -Path "$env:AppData/RabbitMQ" + + - ECHO Creating RabbitMQ configuration file in %AppData%\RabbitMQ... + - ps: Get-Content C:/Projects/pika/testdata/rabbitmq.conf.in | %{ $_ -replace 'PIKA_DIR', 'C:/projects/pika' } | Set-Content -Path "$env:AppData/RabbitMQ/rabbitmq.conf" + - ps: Get-Content "$env:AppData/RabbitMQ/rabbitmq.conf" + + - ECHO Creating Erlang cookie files... + - ps: '[System.IO.File]::WriteAllText("C:\Users\appveyor\.erlang.cookie", "PIKAISTHEBEST", [System.Text.Encoding]::ASCII)' + - ps: '[System.IO.File]::WriteAllText("C:\Windows\System32\config\systemprofile\.erlang.cookie", "PIKAISTHEBEST", [System.Text.Encoding]::ASCII)' + + - ECHO Installing and starting RabbitMQ with default config... + - start /B /WAIT %rabbitmq_installer_path% /S + - ps: (Get-Service -Name RabbitMQ).Status + + - ECHO Waiting for epmd to report that RabbitMQ has started... + - ps: 'C:\projects\pika\testdata\wait-epmd.ps1' + - ps: 'C:\projects\pika\testdata\wait-rabbitmq.ps1' + + - ECHO Getting RabbitMQ status... + - cmd /c "C:\Program Files\RabbitMQ Server\rabbitmq_server-%rabbitmq_version%\sbin\rabbitmqctl.bat" status + + +test_script: + - nosetests + +# Since Pika is source-only there's no need to deploy from Windows +deploy: false diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/Makefile b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/Makefile new file mode 100644 index 000000000..f7b78b37a --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pika.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pika.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pika" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pika" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/conf.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/conf.py new file mode 100644 index 000000000..bb1aa36dc --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/conf.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +import sys +sys.path.insert(0, '../') +#needs_sphinx = '1.0' + +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx'] + +intersphinx_mapping = {'python': ('https://docs.python.org/3/', + 'https://docs.python.org/3/objects.inv'), + 'tornado': ('http://www.tornadoweb.org/en/stable/', + 'http://www.tornadoweb.org/en/stable/objects.inv')} + +templates_path = ['_templates'] + +source_suffix = '.rst' +master_doc = 'index' + +project = 'pika' +copyright = '2009-2017, Tony Garnock-Jones, Gavin M. Roy, Pivotal Software, Inc and contributors.' + +import pika +release = pika.__version__ +version = '.'.join(release.split('.')[0:1]) + +exclude_patterns = ['_build'] +add_function_parentheses = True +add_module_names = True +show_authors = True +pygments_style = 'sphinx' +modindex_common_prefix = ['pika'] +html_theme = 'default' +html_static_path = ['_static'] +htmlhelp_basename = 'pikadoc' diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/contributors.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/contributors.rst new file mode 100644 index 000000000..9cd50ef20 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/contributors.rst @@ -0,0 +1,104 @@ +Contributors +============ +The following people have directly contributes code by way of new features and/or bug fixes to Pika: + + - Gavin M. Roy + - Tony Garnock-Jones + - Vitaly Kruglikov + - Michael Laing + - Marek Majkowski + - Jan Urbański + - Brian K. Jones + - Ask Solem + - ml + - Will + - atatsu + - Fredrik Svensson + - Pedro Abranches + - Kyösti Herrala + - Erik Andersson + - Charles Law + - Alex Chandel + - Tristan Penman + - Raphaël De Giusti + - Jozef Van Eenbergen + - Josh Braegger + - Jason J. W. Williams + - James Mutton + - Cenk Alti + - Asko Soukka + - Antti Haapala + - Anton Ryzhov + - cellscape + - cacovsky + - bra-fsn + - ateska + - Roey Berman + - Robert Weidlich + - Riccardo Cirimelli + - Perttu Ranta-aho + - Pau Gargallo + - Kane + - Kamil Kisiel + - Jonty Wareing + - Jonathan Kirsch + - Jacek 'Forger' Całusiński + - Garth Williamson + - Erik Olof Gunnar Andersson + - David Strauss + - Anton V. Yanchenko + - Alexey Myasnikov + - Alessandro Tagliapietra + - Adam Flynn + - skftn + - saarni + - pavlobaron + - nonleaf + - markcf + - george y + - eivanov + - bstemshorn + - a-tal + - Yang Yang + - Stuart Longland + - Sigurd Høgsbro + - Sean Dwyer + - Samuel Stauffer + - Roberto Decurnex + - Rikard Hultén + - Richard Boulton + - Ralf Nyren + - Qi Fan + - Peter Magnusson + - Pankrat + - Olivier Le Thanh Duong + - Njal Karevoll + - Milan Skuhra + - Mik Kocikowski + - Michael Kenney + - Mark Unsworth + - Luca Wehrstedt + - Laurent Eschenauer + - Lars van de Kerkhof + - Kyösti Herrala + - Juhyeong Park + - JuhaS + - Josh Hansen + - Jorge Puente Sarrín + - Jeff Tang + - Jeff Fein-Worton + - Jeff + - Hunter Morris + - Guruprasad + - Garrett Cooper + - Frank Slaughter + - Dustin Koupal + - Bjorn Sandberg + - Axel Eirola + - Andrew Smith + - Andrew Grigorev + - Andrew + - Allard Hoeve + - A.Shaposhnikov + +*Contributors listed by commit count.* diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples.rst new file mode 100644 index 000000000..5df6740c8 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples.rst @@ -0,0 +1,23 @@ +Usage Examples +============== + +Pika has various methods of use, between the synchronous BlockingConnection adapter and the various asynchronous connection adapter. The following examples illustrate the various ways that you can use Pika in your projects. + +.. toctree:: + :glob: + :maxdepth: 1 + + examples/using_urlparameters + examples/connecting_async + examples/blocking_basic_get + examples/blocking_consume + examples/blocking_consumer_generator + examples/comparing_publishing_sync_async + examples/blocking_delivery_confirmations + examples/blocking_publish_mandatory + examples/asynchronous_consumer_example + examples/asynchronous_publisher_example + examples/twisted_example + examples/tornado_consumer + examples/tls_mutual_authentication + examples/tls_server_authentication diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_consumer_example.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_consumer_example.rst new file mode 100644 index 000000000..8ec3f25f7 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_consumer_example.rst @@ -0,0 +1,357 @@ +Asynchronous consumer example +============================= +The following example implements a consumer that will respond to RPC commands sent from RabbitMQ. For example, it will reconnect if RabbitMQ closes the connection and will shutdown if RabbitMQ cancels the consumer or closes the channel. While it may look intimidating, each method is very short and represents a individual actions that a consumer can do. + +consumer.py:: + + # -*- coding: utf-8 -*- + + import logging + import pika + + LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') + LOGGER = logging.getLogger(__name__) + + + class ExampleConsumer(object): + """This is an example consumer that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + If the channel is closed, it will indicate a problem with one of the + commands that were issued and that should surface in the output as well. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Create a new instance of the consumer class, passing in the AMQP + URL used to connect to RabbitMQ. + + :param str amqp_url: The AMQP url to connect with + + """ + self._connection = None + self._channel = None + self._closing = False + self._consumer_tag = None + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return pika.SelectConnection(pika.URLParameters(self._url), + self.on_connection_open, + stop_ioloop_on_close=False) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.add_on_connection_close_callback() + self.open_channel() + + def add_on_connection_close_callback(self): + """This method adds an on close callback that will be invoked by pika + when RabbitMQ closes the connection to the publisher unexpectedly. + + """ + LOGGER.info('Adding connection close callback') + self._connection.add_on_close_callback(self.on_connection_closed) + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._closing: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self.reconnect) + + def reconnect(self): + """Will be invoked by the IOLoop timer if the connection is + closed. See the on_connection_closed method. + + """ + # This is the old connection IOLoop instance, stop its ioloop + self._connection.ioloop.stop() + + if not self._closing: + + # Create a new connection + self._connection = self.connect() + + # There is now a new connection, needs a new ioloop to run + self._connection.ioloop.start() + + def open_channel(self): + """Open a new channel with RabbitMQ by issuing the Channel.Open RPC + command. When RabbitMQ responds that the channel is open, the + on_channel_open callback will be invoked by pika. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel %i was closed: (%s) %s', + channel, reply_code, reply_text) + self._connection.close() + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def on_bindok(self, unused_frame): + """Invoked by pika when the Queue.Bind method has completed. At this + point we will start consuming messages by calling start_consuming + which will invoke the needed RPC commands to start the process. + + :param pika.frame.Method unused_frame: The Queue.BindOk response frame + + """ + LOGGER.info('Queue bound') + self.start_consuming() + + def start_consuming(self): + """This method sets up the consumer by first calling + add_on_cancel_callback so that the object is notified if RabbitMQ + cancels the consumer. It then issues the Basic.Consume RPC command + which returns the consumer tag that is used to uniquely identify the + consumer with RabbitMQ. We keep the value to use it when we want to + cancel consuming. The on_message method is passed in as a callback pika + will invoke when a message is fully received. + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.add_on_cancel_callback() + self._consumer_tag = self._channel.basic_consume(self.on_message, + self.QUEUE) + + def add_on_cancel_callback(self): + """Add a callback that will be invoked if RabbitMQ cancels the consumer + for some reason. If RabbitMQ does cancel the consumer, + on_consumer_cancelled will be invoked by pika. + + """ + LOGGER.info('Adding consumer cancellation callback') + self._channel.add_on_cancel_callback(self.on_consumer_cancelled) + + def on_consumer_cancelled(self, method_frame): + """Invoked by pika when RabbitMQ sends a Basic.Cancel for a consumer + receiving messages. + + :param pika.frame.Method method_frame: The Basic.Cancel frame + + """ + LOGGER.info('Consumer was cancelled remotely, shutting down: %r', + method_frame) + if self._channel: + self._channel.close() + + def on_message(self, unused_channel, basic_deliver, properties, body): + """Invoked by pika when a message is delivered from RabbitMQ. The + channel is passed for your convenience. The basic_deliver object that + is passed in carries the exchange, routing key, delivery tag and + a redelivered flag for the message. The properties passed in is an + instance of BasicProperties with the message properties and the body + is the message that was sent. + + :param pika.channel.Channel unused_channel: The channel object + :param pika.Spec.Basic.Deliver: basic_deliver method + :param pika.Spec.BasicProperties: properties + :param str|unicode body: The message body + + """ + LOGGER.info('Received message # %s from %s: %s', + basic_deliver.delivery_tag, properties.app_id, body) + self.acknowledge_message(basic_deliver.delivery_tag) + + def acknowledge_message(self, delivery_tag): + """Acknowledge the message delivery from RabbitMQ by sending a + Basic.Ack RPC method for the delivery tag. + + :param int delivery_tag: The delivery tag from the Basic.Deliver frame + + """ + LOGGER.info('Acknowledging message %s', delivery_tag) + self._channel.basic_ack(delivery_tag) + + def stop_consuming(self): + """Tell RabbitMQ that you would like to stop consuming by sending the + Basic.Cancel RPC command. + + """ + if self._channel: + LOGGER.info('Sending a Basic.Cancel RPC command to RabbitMQ') + self._channel.basic_cancel(self.on_cancelok, self._consumer_tag) + + def on_cancelok(self, unused_frame): + """This method is invoked by pika when RabbitMQ acknowledges the + cancellation of a consumer. At this point we will close the channel. + This will invoke the on_channel_closed method once the channel has been + closed, which will in-turn close the connection. + + :param pika.frame.Method unused_frame: The Basic.CancelOk frame + + """ + LOGGER.info('RabbitMQ acknowledged the cancellation of the consumer') + self.close_channel() + + def close_channel(self): + """Call to close the channel with RabbitMQ cleanly by issuing the + Channel.Close RPC command. + + """ + LOGGER.info('Closing the channel') + self._channel.close() + + def run(self): + """Run the example consumer by connecting to RabbitMQ and then + starting the IOLoop to block and allow the SelectConnection to operate. + + """ + self._connection = self.connect() + self._connection.ioloop.start() + + def stop(self): + """Cleanly shutdown the connection to RabbitMQ by stopping the consumer + with RabbitMQ. When RabbitMQ confirms the cancellation, on_cancelok + will be invoked by pika, which will then closing the channel and + connection. The IOLoop is started again because this method is invoked + when CTRL-C is pressed raising a KeyboardInterrupt exception. This + exception stops the IOLoop which needs to be running for pika to + communicate with RabbitMQ. All of the commands issued prior to starting + the IOLoop will be buffered but not processed. + + """ + LOGGER.info('Stopping') + self._closing = True + self.stop_consuming() + self._connection.ioloop.start() + LOGGER.info('Stopped') + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + LOGGER.info('Closing connection') + self._connection.close() + + + def main(): + logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) + example = ExampleConsumer('amqp://guest:guest@localhost:5672/%2F') + try: + example.run() + except KeyboardInterrupt: + example.stop() + + + if __name__ == '__main__': + main() + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_publisher_example.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_publisher_example.rst new file mode 100644 index 000000000..887148db0 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asynchronous_publisher_example.rst @@ -0,0 +1,359 @@ +Asynchronous publisher example +============================== +The following example implements a publisher that will respond to RPC commands sent from RabbitMQ and uses delivery confirmations. It will reconnect if RabbitMQ closes the connection and will shutdown if RabbitMQ closes the channel. While it may look intimidating, each method is very short and represents a individual actions that a publisher can do. + +publisher.py:: + + # -*- coding: utf-8 -*- + + import logging + import pika + import json + + LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') + LOGGER = logging.getLogger(__name__) + + + class ExamplePublisher(object): + """This is an example publisher that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + It uses delivery confirmations and illustrates one way to keep track of + messages that have been sent and if they've been confirmed by RabbitMQ. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + PUBLISH_INTERVAL = 1 + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Setup the example publisher object, passing in the URL we will use + to connect to RabbitMQ. + + :param str amqp_url: The URL for connecting to RabbitMQ + + """ + self._connection = None + self._channel = None + + self._deliveries = None + self._acked = None + self._nacked = None + self._message_number = None + + self._stopping = False + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. If you want the reconnection to work, make + sure you set stop_ioloop_on_close to False, which is not the default + behavior of this adapter. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return pika.SelectConnection(pika.URLParameters(self._url), + on_open_callback=self.on_connection_open, + on_close_callback=self.on_connection_closed, + stop_ioloop_on_close=False) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.open_channel() + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._stopping: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self._connection.ioloop.stop) + + def open_channel(self): + """This method will open a new channel with RabbitMQ by issuing the + Channel.Open RPC command. When RabbitMQ confirms the channel is open + by sending the Channel.OpenOK RPC reply, the on_channel_open method + will be invoked. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel was closed: (%s) %s', reply_code, reply_text) + self._channel = None + if not self._stopping: + self._connection.close() + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def on_bindok(self, unused_frame): + """This method is invoked by pika when it receives the Queue.BindOk + response from RabbitMQ. Since we know we're now setup and bound, it's + time to start publishing.""" + LOGGER.info('Queue bound') + self.start_publishing() + + def start_publishing(self): + """This method will enable delivery confirmations and schedule the + first message to be sent to RabbitMQ + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.enable_delivery_confirmations() + self.schedule_next_message() + + def enable_delivery_confirmations(self): + """Send the Confirm.Select RPC method to RabbitMQ to enable delivery + confirmations on the channel. The only way to turn this off is to close + the channel and create a new one. + + When the message is confirmed from RabbitMQ, the + on_delivery_confirmation method will be invoked passing in a Basic.Ack + or Basic.Nack method from RabbitMQ that will indicate which messages it + is confirming or rejecting. + + """ + LOGGER.info('Issuing Confirm.Select RPC command') + self._channel.confirm_delivery(self.on_delivery_confirmation) + + def on_delivery_confirmation(self, method_frame): + """Invoked by pika when RabbitMQ responds to a Basic.Publish RPC + command, passing in either a Basic.Ack or Basic.Nack frame with + the delivery tag of the message that was published. The delivery tag + is an integer counter indicating the message number that was sent + on the channel via Basic.Publish. Here we're just doing house keeping + to keep track of stats and remove message numbers that we expect + a delivery confirmation of from the list used to keep track of messages + that are pending confirmation. + + :param pika.frame.Method method_frame: Basic.Ack or Basic.Nack frame + + """ + confirmation_type = method_frame.method.NAME.split('.')[1].lower() + LOGGER.info('Received %s for delivery tag: %i', + confirmation_type, + method_frame.method.delivery_tag) + if confirmation_type == 'ack': + self._acked += 1 + elif confirmation_type == 'nack': + self._nacked += 1 + self._deliveries.remove(method_frame.method.delivery_tag) + LOGGER.info('Published %i messages, %i have yet to be confirmed, ' + '%i were acked and %i were nacked', + self._message_number, len(self._deliveries), + self._acked, self._nacked) + + def schedule_next_message(self): + """If we are not closing our connection to RabbitMQ, schedule another + message to be delivered in PUBLISH_INTERVAL seconds. + + """ + LOGGER.info('Scheduling next message for %0.1f seconds', + self.PUBLISH_INTERVAL) + self._connection.add_timeout(self.PUBLISH_INTERVAL, + self.publish_message) + + def publish_message(self): + """If the class is not stopping, publish a message to RabbitMQ, + appending a list of deliveries with the message number that was sent. + This list will be used to check for delivery confirmations in the + on_delivery_confirmations method. + + Once the message has been sent, schedule another message to be sent. + The main reason I put scheduling in was just so you can get a good idea + of how the process is flowing by slowing down and speeding up the + delivery intervals by changing the PUBLISH_INTERVAL constant in the + class. + + """ + if self._channel is None or not self._channel.is_open: + return + + hdrs = {u'مفتاح': u' قيمة', + u'键': u'值', + u'キー': u'値'} + properties = pika.BasicProperties(app_id='example-publisher', + content_type='application/json', + headers=hdrs) + + message = u'مفتاح قيمة 键 值 キー 値' + self._channel.basic_publish(self.EXCHANGE, self.ROUTING_KEY, + json.dumps(message, ensure_ascii=False), + properties) + self._message_number += 1 + self._deliveries.append(self._message_number) + LOGGER.info('Published message # %i', self._message_number) + self.schedule_next_message() + + def run(self): + """Run the example code by connecting and then starting the IOLoop. + + """ + while not self._stopping: + self._connection = None + self._deliveries = [] + self._acked = 0 + self._nacked = 0 + self._message_number = 0 + + try: + self._connection = self.connect() + self._connection.ioloop.start() + except KeyboardInterrupt: + self.stop() + if (self._connection is not None and + not self._connection.is_closed): + # Finish closing + self._connection.ioloop.start() + + LOGGER.info('Stopped') + + def stop(self): + """Stop the example by closing the channel and connection. We + set a flag here so that we stop scheduling new messages to be + published. The IOLoop is started because this method is + invoked by the Try/Catch below when KeyboardInterrupt is caught. + Starting the IOLoop again will allow the publisher to cleanly + disconnect from RabbitMQ. + + """ + LOGGER.info('Stopping') + self._stopping = True + self.close_channel() + self.close_connection() + + def close_channel(self): + """Invoke this command to close the channel with RabbitMQ by sending + the Channel.Close RPC command. + + """ + if self._channel is not None: + LOGGER.info('Closing the channel') + self._channel.close() + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + if self._connection is not None: + LOGGER.info('Closing connection') + self._connection.close() + + + def main(): + logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + + # Connect to localhost:5672 as guest with the password guest and virtual host "/" (%2F) + example = ExamplePublisher('amqp://guest:guest@localhost:5672/%2F?connection_attempts=3&heartbeat_interval=3600') + example.run() + + + if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asyncio_consumer.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asyncio_consumer.rst new file mode 100644 index 000000000..1ea654ae7 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/asyncio_consumer.rst @@ -0,0 +1,355 @@ +Asyncio Consumer +================ +The following example implements a consumer using the +:class:`Asyncio adapter ` for the +`Asyncio library `_ that will respond to RPC commands sent + from RabbitMQ. For example, it will reconnect if RabbitMQ closes the connection and will shutdown if + RabbitMQ cancels the consumer or closes the channel. While it may look intimidating, each method is + very short and represents a individual actions that a consumer can do. + +consumer.py:: + + from pika import adapters + import pika + import logging + + LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') + + LOGGER = logging.getLogger(__name__) + + + class ExampleConsumer(object): + """This is an example consumer that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + If the channel is closed, it will indicate a problem with one of the + commands that were issued and that should surface in the output as well. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Create a new instance of the consumer class, passing in the AMQP + URL used to connect to RabbitMQ. + + :param str amqp_url: The AMQP url to connect with + + """ + self._connection = None + self._channel = None + self._closing = False + self._consumer_tag = None + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return adapters.asyncio_connection.AsyncioConnection(pika.URLParameters(self._url), + self.on_connection_open) + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + LOGGER.info('Closing connection') + self._connection.close() + + def add_on_connection_close_callback(self): + """This method adds an on close callback that will be invoked by pika + when RabbitMQ closes the connection to the publisher unexpectedly. + + """ + LOGGER.info('Adding connection close callback') + self._connection.add_on_close_callback(self.on_connection_closed) + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._closing: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self.reconnect) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.add_on_connection_close_callback() + self.open_channel() + + def reconnect(self): + """Will be invoked by the IOLoop timer if the connection is + closed. See the on_connection_closed method. + + """ + if not self._closing: + + # Create a new connection + self._connection = self.connect() + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel %i was closed: (%s) %s', + channel, reply_code, reply_text) + self._connection.close() + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def add_on_cancel_callback(self): + """Add a callback that will be invoked if RabbitMQ cancels the consumer + for some reason. If RabbitMQ does cancel the consumer, + on_consumer_cancelled will be invoked by pika. + + """ + LOGGER.info('Adding consumer cancellation callback') + self._channel.add_on_cancel_callback(self.on_consumer_cancelled) + + def on_consumer_cancelled(self, method_frame): + """Invoked by pika when RabbitMQ sends a Basic.Cancel for a consumer + receiving messages. + + :param pika.frame.Method method_frame: The Basic.Cancel frame + + """ + LOGGER.info('Consumer was cancelled remotely, shutting down: %r', + method_frame) + if self._channel: + self._channel.close() + + def acknowledge_message(self, delivery_tag): + """Acknowledge the message delivery from RabbitMQ by sending a + Basic.Ack RPC method for the delivery tag. + + :param int delivery_tag: The delivery tag from the Basic.Deliver frame + + """ + LOGGER.info('Acknowledging message %s', delivery_tag) + self._channel.basic_ack(delivery_tag) + + def on_message(self, unused_channel, basic_deliver, properties, body): + """Invoked by pika when a message is delivered from RabbitMQ. The + channel is passed for your convenience. The basic_deliver object that + is passed in carries the exchange, routing key, delivery tag and + a redelivered flag for the message. The properties passed in is an + instance of BasicProperties with the message properties and the body + is the message that was sent. + + :param pika.channel.Channel unused_channel: The channel object + :param pika.Spec.Basic.Deliver: basic_deliver method + :param pika.Spec.BasicProperties: properties + :param str|unicode body: The message body + + """ + LOGGER.info('Received message # %s from %s: %s', + basic_deliver.delivery_tag, properties.app_id, body) + self.acknowledge_message(basic_deliver.delivery_tag) + + def on_cancelok(self, unused_frame): + """This method is invoked by pika when RabbitMQ acknowledges the + cancellation of a consumer. At this point we will close the channel. + This will invoke the on_channel_closed method once the channel has been + closed, which will in-turn close the connection. + + :param pika.frame.Method unused_frame: The Basic.CancelOk frame + + """ + LOGGER.info('RabbitMQ acknowledged the cancellation of the consumer') + self.close_channel() + + def stop_consuming(self): + """Tell RabbitMQ that you would like to stop consuming by sending the + Basic.Cancel RPC command. + + """ + if self._channel: + LOGGER.info('Sending a Basic.Cancel RPC command to RabbitMQ') + self._channel.basic_cancel(self.on_cancelok, self._consumer_tag) + + def start_consuming(self): + """This method sets up the consumer by first calling + add_on_cancel_callback so that the object is notified if RabbitMQ + cancels the consumer. It then issues the Basic.Consume RPC command + which returns the consumer tag that is used to uniquely identify the + consumer with RabbitMQ. We keep the value to use it when we want to + cancel consuming. The on_message method is passed in as a callback pika + will invoke when a message is fully received. + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.add_on_cancel_callback() + self._consumer_tag = self._channel.basic_consume(self.on_message, + self.QUEUE) + + def on_bindok(self, unused_frame): + """Invoked by pika when the Queue.Bind method has completed. At this + point we will start consuming messages by calling start_consuming + which will invoke the needed RPC commands to start the process. + + :param pika.frame.Method unused_frame: The Queue.BindOk response frame + + """ + LOGGER.info('Queue bound') + self.start_consuming() + + def close_channel(self): + """Call to close the channel with RabbitMQ cleanly by issuing the + Channel.Close RPC command. + + """ + LOGGER.info('Closing the channel') + self._channel.close() + + def open_channel(self): + """Open a new channel with RabbitMQ by issuing the Channel.Open RPC + command. When RabbitMQ responds that the channel is open, the + on_channel_open callback will be invoked by pika. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def run(self): + """Run the example consumer by connecting to RabbitMQ and then + starting the IOLoop to block and allow the SelectConnection to operate. + + """ + self._connection = self.connect() + self._connection.ioloop.start() + + def stop(self): + """Cleanly shutdown the connection to RabbitMQ by stopping the consumer + with RabbitMQ. When RabbitMQ confirms the cancellation, on_cancelok + will be invoked by pika, which will then closing the channel and + connection. The IOLoop is started again because this method is invoked + when CTRL-C is pressed raising a KeyboardInterrupt exception. This + exception stops the IOLoop which needs to be running for pika to + communicate with RabbitMQ. All of the commands issued prior to starting + the IOLoop will be buffered but not processed. + + """ + LOGGER.info('Stopping') + self._closing = True + self.stop_consuming() + self._connection.ioloop.start() + LOGGER.info('Stopped') + + + def main(): + logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) + example = ExampleConsumer('amqp://guest:guest@localhost:5672/%2F') + try: + example.run() + except KeyboardInterrupt: + example.stop() + + + if __name__ == '__main__': + main() + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_basic_get.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_basic_get.rst new file mode 100644 index 000000000..d679ea825 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_basic_get.rst @@ -0,0 +1,23 @@ +Using the Blocking Connection to get a message from RabbitMQ +============================================================ + +.. _example_blocking_basic_get: + +The :py:meth:`BlockingChannel.basic_get ` method will return a tuple with the members. + +If the server returns a message, the first item in the tuple will be a :class:`pika.spec.Basic.GetOk` object with the current message count, the redelivered flag, the routing key that was used to put the message in the queue, and the exchange the message was published to. The second item will be a :py:class:`~pika.spec.BasicProperties` object and the third will be the message body. + +If the server did not return a message a tuple of None, None, None will be returned. + +Example of getting a message and acknowledging it:: + + import pika + + connection = pika.BlockingConnection() + channel = connection.channel() + method_frame, header_frame, body = channel.basic_get('test') + if method_frame: + print(method_frame, header_frame, body) + channel.basic_ack(method_frame.delivery_tag) + else: + print('No message returned') diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consume.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consume.rst new file mode 100644 index 000000000..85852e460 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consume.rst @@ -0,0 +1,29 @@ +Using the Blocking Connection to consume messages from RabbitMQ +=============================================================== + +.. _example_blocking_basic_consume: + +The :py:meth:`BlockingChannel.basic_consume ` method assign a callback method to be called every time that RabbitMQ delivers messages to your consuming application. + +When pika calls your method, it will pass in the channel, a :py:class:`pika.spec.Basic.Deliver` object with the delivery tag, the redelivered flag, the routing key that was used to put the message in the queue, and the exchange the message was published to. The third argument will be a :py:class:`pika.spec.BasicProperties` object and the last will be the message body. + +Example of consuming messages and acknowledging them:: + + import pika + + + def on_message(channel, method_frame, header_frame, body): + print(method_frame.delivery_tag) + print(body) + print() + channel.basic_ack(delivery_tag=method_frame.delivery_tag) + + + connection = pika.BlockingConnection() + channel = connection.channel() + channel.basic_consume(on_message, 'test') + try: + channel.start_consuming() + except KeyboardInterrupt: + channel.stop_consuming() + connection.close() \ No newline at end of file diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consumer_generator.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consumer_generator.rst new file mode 100644 index 000000000..f875a1095 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_consumer_generator.rst @@ -0,0 +1,73 @@ +Using the BlockingChannel.consume generator to consume messages +=============================================================== + +.. _example_blocking_basic_get: + +The :py:meth:`BlockingChannel.consume ` method is a generator that will return a tuple of method, properties and body. + +When you escape out of the loop, be sure to call consumer.cancel() to return any unprocessed messages. + +Example of consuming messages and acknowledging them:: + + import pika + + connection = pika.BlockingConnection() + channel = connection.channel() + + # Get ten messages and break out + for method_frame, properties, body in channel.consume('test'): + + # Display the message parts + print(method_frame) + print(properties) + print(body) + + # Acknowledge the message + channel.basic_ack(method_frame.delivery_tag) + + # Escape out of the loop after 10 messages + if method_frame.delivery_tag == 10: + break + + # Cancel the consumer and return any pending messages + requeued_messages = channel.cancel() + print('Requeued %i messages' % requeued_messages) + + # Close the channel and the connection + channel.close() + connection.close() + +If you have pending messages in the test queue, your output should look something like:: + + (pika)gmr-0x02:pika gmr$ python blocking_nack.py + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + Requeued 1894 messages diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_delivery_confirmations.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_delivery_confirmations.rst new file mode 100644 index 000000000..ade888f58 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_delivery_confirmations.rst @@ -0,0 +1,28 @@ +Using Delivery Confirmations with the BlockingConnection +======================================================== + +The following code demonstrates how to turn on delivery confirmations with the BlockingConnection and how to check for confirmation from RabbitMQ:: + + import pika + + # Open a connection to RabbitMQ on localhost using all default parameters + connection = pika.BlockingConnection() + + # Open the channel + channel = connection.channel() + + # Declare the queue + channel.queue_declare(queue="test", durable=True, exclusive=False, auto_delete=False) + + # Turn on delivery confirmations + channel.confirm_delivery() + + # Send a message + if channel.basic_publish(exchange='test', + routing_key='test', + body='Hello World!', + properties=pika.BasicProperties(content_type='text/plain', + delivery_mode=1)): + print('Message publish was confirmed') + else: + print('Message could not be confirmed') diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_publish_mandatory.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_publish_mandatory.rst new file mode 100644 index 000000000..800cf667d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/blocking_publish_mandatory.rst @@ -0,0 +1,29 @@ +Ensuring message delivery with the mandatory flag +================================================= + +The following example demonstrates how to check if a message is delivered by setting the mandatory flag and checking the return result when using the BlockingConnection:: + + import pika + + # Open a connection to RabbitMQ on localhost using all default parameters + connection = pika.BlockingConnection() + + # Open the channel + channel = connection.channel() + + # Declare the queue + channel.queue_declare(queue="test", durable=True, exclusive=False, auto_delete=False) + + # Enabled delivery confirmations + channel.confirm_delivery() + + # Send a message + if channel.basic_publish(exchange='test', + routing_key='test', + body='Hello World!', + properties=pika.BasicProperties(content_type='text/plain', + delivery_mode=1), + mandatory=True): + print('Message was published') + else: + print('Message was returned') diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/comparing_publishing_sync_async.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/comparing_publishing_sync_async.rst new file mode 100644 index 000000000..89c48faa7 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/comparing_publishing_sync_async.rst @@ -0,0 +1,64 @@ +Comparing Message Publishing with BlockingConnection and SelectConnection +========================================================================= + +For those doing simple, non-asynchronous programming, :py:meth:`pika.adapters.blocking_connection.BlockingConnection` proves to be the easiest way to get up and running with Pika to publish messages. + +In the following example, a connection is made to RabbitMQ listening to port *5672* on *localhost* using the username *guest* and password *guest* and virtual host */*. Once connected, a channel is opened and a message is published to the *test_exchange* exchange using the *test_routing_key* routing key. The BasicProperties value passed in sets the message to delivery mode *1* (non-persisted) with a content-type of *text/plain*. Once the message is published, the connection is closed:: + + import pika + + parameters = pika.URLParameters('amqp://guest:guest@localhost:5672/%2F') + + connection = pika.BlockingConnection(parameters) + + channel = connection.channel() + + channel.basic_publish('test_exchange', + 'test_routing_key', + 'message body value', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + + connection.close() + + +In contrast, using :py:meth:`pika.adapters.select_connection.SelectConnection` and the other asynchronous adapters is more complicated and less pythonic, but when used with other asynchronous services can have tremendous performance improvements. In the following code example, all of the same parameters and values are used as were used in the previous example:: + + import pika + + # Step #3 + def on_open(connection): + + connection.channel(on_channel_open) + + # Step #4 + def on_channel_open(channel): + + channel.basic_publish('test_exchange', + 'test_routing_key', + 'message body value', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + + connection.close() + + # Step #1: Connect to RabbitMQ + parameters = pika.URLParameters('amqp://guest:guest@localhost:5672/%2F') + + connection = pika.SelectConnection(parameters=parameters, + on_open_callback=on_open) + + try: + + # Step #2 - Block on the IOLoop + connection.ioloop.start() + + # Catch a Keyboard Interrupt to make sure that the connection is closed cleanly + except KeyboardInterrupt: + + # Gracefully close the connection + connection.close() + + # Start the IOLoop again so Pika can communicate, it will stop on its own when the connection is closed + connection.ioloop.start() + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/connecting_async.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/connecting_async.rst new file mode 100644 index 000000000..125de3c96 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/connecting_async.rst @@ -0,0 +1,49 @@ +Connecting to RabbitMQ with Callback-Passing Style +================================================== + +When you connect to RabbitMQ with an asynchronous adapter, you are writing event +oriented code. The connection adapter will block on the IOLoop that is watching +to see when pika should read data from and write data to RabbitMQ. Because you're +now blocking on the IOLoop, you will receive callback notifications when specific +events happen. + +Example Code +------------ +In the example, there are three steps that take place: + +1. Setup the connection to RabbitMQ +2. Start the IOLoop +3. Once connected, the on_open method will be called by Pika with a handle to + the connection. In this method, a new channel will be opened on the connection. +4. Once the channel is opened, you can do your other actions, whether they be + publishing messages, consuming messages or other RabbitMQ related activities.:: + + import pika + + # Step #3 + def on_open(connection): + connection.channel(on_channel_open) + + # Step #4 + def on_channel_open(channel): + channel.basic_publish('exchange_name', + 'routing_key', + 'Test Message', + pika.BasicProperties(content_type='text/plain', + type='example')) + + # Step #1: Connect to RabbitMQ + connection = pika.SelectConnection(on_open_callback=on_open) + + try: + # Step #2 - Block on the IOLoop + connection.ioloop.start() + + # Catch a Keyboard Interrupt to make sure that the connection is closed cleanly + except KeyboardInterrupt: + + # Gracefully close the connection + connection.close() + + # Start the IOLoop again so Pika can communicate, it will stop on its own when the connection is closed + connection.ioloop.start() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/direct_reply_to.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/direct_reply_to.rst new file mode 100644 index 000000000..3d8f6d8ec --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/direct_reply_to.rst @@ -0,0 +1,81 @@ +Direct reply-to example +============================== +The following example demonstrates the use of the RabbitMQ "Direct reply-to" feature via `pika.BlockingConnection`. See https://www.rabbitmq.com/direct-reply-to.html for more info about this feature. + +direct_reply_to.py:: + + # -*- coding: utf-8 -*- + + """ + This example demonstrates the RabbitMQ "Direct reply-to" usage via + `pika.BlockingConnection`. See https://www.rabbitmq.com/direct-reply-to.html + for more info about this feature. + """ + import pika + + + SERVER_QUEUE = 'rpc.server.queue' + + + def main(): + """ Here, Client sends "Marco" to RPC Server, and RPC Server replies with + "Polo". + + NOTE Normally, the server would be running separately from the client, but + in this very simple example both are running in the same thread and sharing + connection and channel. + + """ + with pika.BlockingConnection() as conn: + channel = conn.channel() + + # Set up server + + channel.queue_declare(queue=SERVER_QUEUE, + exclusive=True, + auto_delete=True) + channel.basic_consume(on_server_rx_rpc_request, queue=SERVER_QUEUE) + + + # Set up client + + # NOTE Client must create its consumer and publish RPC requests on the + # same channel to enable the RabbitMQ broker to make the necessary + # associations. + # + # Also, client must create the consumer *before* starting to publish the + # RPC requests. + # + # Client must create its consumer with no_ack=True, because the reply-to + # queue isn't real. + + channel.basic_consume(on_client_rx_reply_from_server, + queue='amq.rabbitmq.reply-to', + no_ack=True) + channel.basic_publish( + exchange='', + routing_key=SERVER_QUEUE, + body='Marco', + properties=pika.BasicProperties(reply_to='amq.rabbitmq.reply-to')) + + channel.start_consuming() + + + def on_server_rx_rpc_request(ch, method_frame, properties, body): + print 'RPC Server got request:', body + + ch.basic_publish('', routing_key=properties.reply_to, body='Polo') + + ch.basic_ack(delivery_tag=method_frame.delivery_tag) + + print 'RPC Server says good bye' + + + def on_client_rx_reply_from_server(ch, method_frame, properties, body): + print 'RPC Client got reply:', body + + # NOTE A real client might want to make additional RPC requests, but in this + # simple example we're closing the channel after getting our first reply + # to force control to return from channel.start_consuming() + print 'RPC Client says bye' + ch.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/heartbeat_and_blocked_timeouts.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/heartbeat_and_blocked_timeouts.rst new file mode 100644 index 000000000..d7469a100 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/heartbeat_and_blocked_timeouts.rst @@ -0,0 +1,37 @@ +Ensuring well-behaved connection with heartbeat and blocked-connection timeouts +=============================================================================== + + +This example demonstrates explicit setting of heartbeat and blocked connection timeouts. + +Starting with RabbitMQ 3.5.5, the broker's default heartbeat timeout decreased from 580 seconds to 60 seconds. As a result, applications that perform lengthy processing in the same thread that also runs their Pika connection may experience unexpected dropped connections due to heartbeat timeout. Here, we specify an explicit lower bound for heartbeat timeout. + +When RabbitMQ broker is running out of certain resources, such as memory and disk space, it may block connections that are performing resource-consuming operations, such as publishing messages. Once a connection is blocked, RabbitMQ stops reading from that connection's socket, so no commands from the client will get through to the broker on that connection until the broker unblocks it. A blocked connection may last for an indefinite period of time, stalling the connection and possibly resulting in a hang (e.g., in BlockingConnection) until the connection is unblocked. Blocked Connection Timeout is intended to interrupt (i.e., drop) a connection that has been blocked longer than the given timeout value. + +Example of configuring hertbeat and blocked-connection timeouts:: + + import pika + + + def main(): + + # NOTE: These parameters work with all Pika connection types + params = pika.ConnectionParameters(heartbeat_interval=600, + blocked_connection_timeout=300) + + conn = pika.BlockingConnection(params) + + chan = conn.channel() + + chan.basic_publish('', 'my-alphabet-queue', "abc") + + # If publish causes the connection to become blocked, then this conn.close() + # would hang until the connection is unblocked, if ever. However, the + # blocked_connection_timeout connection parameter would interrupt the wait, + # resulting in ConnectionClosed exception from BlockingConnection (or the + # on_connection_closed callback call in an asynchronous adapter) + conn.close() + + + if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_mutual_authentication.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_mutual_authentication.rst new file mode 100644 index 000000000..9cd8decd1 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_mutual_authentication.rst @@ -0,0 +1,61 @@ +TLS parameters example +============================= +This examples demonstrates a TLS session with RabbitMQ using mutual authentication. + +It was tested against RabbitMQ 3.6.10, using Python 3.6.1 and pre-release Pika `0.11.0` + +Note the use of `ssl_version=ssl.PROTOCOL_TLSv1`. The recent verions of RabbitMQ disable older versions of +SSL due to security vulnerabilities. + +See https://www.rabbitmq.com/ssl.html for certificate creation and rabbitmq SSL configuration instructions. + + +tls_example.py:: + + import ssl + import pika + import logging + + logging.basicConfig(level=logging.INFO) + + cp = pika.ConnectionParameters( + ssl=True, + ssl_options=dict( + ssl_version=ssl.PROTOCOL_TLSv1, + ca_certs="/Users/me/tls-gen/basic/testca/cacert.pem", + keyfile="/Users/me/tls-gen/basic/client/key.pem", + certfile="/Users/me/tls-gen/basic/client/cert.pem", + cert_reqs=ssl.CERT_REQUIRED)) + + conn = pika.BlockingConnection(cp) + ch = conn.channel() + print(ch.queue_declare("sslq")) + ch.publish("", "sslq", "abc") + print(ch.basic_get("sslq")) + + +rabbitmq.config:: + + %% Both the client and rabbitmq server were running on the same machine, a MacBookPro laptop. + %% + %% rabbitmq.config was created in its default location for OS X: /usr/local/etc/rabbitmq/rabbitmq.config. + %% + %% The contents of the example rabbitmq.config are for demonstration purposes only. See https://www.rabbitmq.com/ssl.html for instructions about creating the test certificates and the contents of rabbitmq.config. + + + [ + {rabbit, + [ + {ssl_listeners, [{"127.0.0.1", 5671}]}, + + %% Configuring SSL. + %% See http://www.rabbitmq.com/ssl.html for full documentation. + %% + {ssl_options, [{cacertfile, "/Users/me/tls-gen/basic/testca/cacert.pem"}, + {certfile, "/Users/me/tls-gen/basic/server/cert.pem"}, + {keyfile, "/Users/me/tls-gen/basic/server/key.pem"}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}]} + ] + } + ]. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_server_uathentication.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_server_uathentication.rst new file mode 100644 index 000000000..2cdb4ecd7 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tls_server_uathentication.rst @@ -0,0 +1,60 @@ +TLS parameters example +============================= +This examples demonstrates a TLS session with RabbitMQ using server authentication. + +It was tested against RabbitMQ 3.6.10, using Python 3.6.1 and pre-release Pika `0.11.0` + +Note the use of `ssl_version=ssl.PROTOCOL_TLSv1`. The recent versions of RabbitMQ disable older versions of +SSL due to security vulnerabilities. + +See https://www.rabbitmq.com/ssl.html for certificate creation and rabbitmq SSL configuration instructions. + + +tls_example.py:: + + import ssl + import pika + import logging + + logging.basicConfig(level=logging.INFO) + + cp = pika.ConnectionParameters( + ssl=True, + ssl_options=dict( + ssl_version=ssl.PROTOCOL_TLSv1, + ca_certs="/Users/me/tls-gen/basic/testca/cacert.pem", + cert_reqs=ssl.CERT_REQUIRED)) + + conn = pika.BlockingConnection(cp) + ch = conn.channel() + print(ch.queue_declare("sslq")) + ch.publish("", "sslq", "abc") + print(ch.basic_get("sslq")) + + +rabbitmq.config:: + + %% Both the client and rabbitmq server were running on the same machine, a MacBookPro laptop. + %% + %% rabbitmq.config was created in its default location for OS X: /usr/local/etc/rabbitmq/rabbitmq.config. + %% + %% The contents of the example rabbitmq.config are for demonstration purposes only. See https://www.rabbitmq.com/ssl.html for instructions about creating the test certificates and the contents of rabbitmq.config. + %% + %% Note that the {fail_if_no_peer_cert,false} option, states that RabbitMQ should accept clients that don't have a certificate to send to the broker, but through the {verify,verify_peer} option, we state that if the client does send a certificate to the broker, the broker must be able to establish a chain of trust to it. + + [ + {rabbit, + [ + {ssl_listeners, [{"127.0.0.1", 5671}]}, + + %% Configuring SSL. + %% See http://www.rabbitmq.com/ssl.html for full documentation. + %% + {ssl_options, [{cacertfile, "/Users/me/tls-gen/basic/testca/cacert.pem"}, + {certfile, "/Users/me/tls-gen/basic/server/cert.pem"}, + {keyfile, "/Users/me/tls-gen/basic/server/key.pem"}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}]} + ] + } + ]. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tornado_consumer.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tornado_consumer.rst new file mode 100644 index 000000000..0dae22181 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/tornado_consumer.rst @@ -0,0 +1,349 @@ +Tornado Consumer +================ +The following example implements a consumer using the :class:`Tornado adapter ` for the `Tornado framework `_ that will respond to RPC commands sent from RabbitMQ. For example, it will reconnect if RabbitMQ closes the connection and will shutdown if RabbitMQ cancels the consumer or closes the channel. While it may look intimidating, each method is very short and represents a individual actions that a consumer can do. + +consumer.py:: + + from pika import adapters + import pika + import logging + + LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') + LOGGER = logging.getLogger(__name__) + + + class ExampleConsumer(object): + """This is an example consumer that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + If the channel is closed, it will indicate a problem with one of the + commands that were issued and that should surface in the output as well. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Create a new instance of the consumer class, passing in the AMQP + URL used to connect to RabbitMQ. + + :param str amqp_url: The AMQP url to connect with + + """ + self._connection = None + self._channel = None + self._closing = False + self._consumer_tag = None + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return adapters.tornado_connection.TornadoConnection(pika.URLParameters(self._url), + self.on_connection_open) + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + LOGGER.info('Closing connection') + self._connection.close() + + def add_on_connection_close_callback(self): + """This method adds an on close callback that will be invoked by pika + when RabbitMQ closes the connection to the publisher unexpectedly. + + """ + LOGGER.info('Adding connection close callback') + self._connection.add_on_close_callback(self.on_connection_closed) + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._closing: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self.reconnect) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.add_on_connection_close_callback() + self.open_channel() + + def reconnect(self): + """Will be invoked by the IOLoop timer if the connection is + closed. See the on_connection_closed method. + + """ + if not self._closing: + + # Create a new connection + self._connection = self.connect() + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel %i was closed: (%s) %s', + channel, reply_code, reply_text) + self._connection.close() + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def add_on_cancel_callback(self): + """Add a callback that will be invoked if RabbitMQ cancels the consumer + for some reason. If RabbitMQ does cancel the consumer, + on_consumer_cancelled will be invoked by pika. + + """ + LOGGER.info('Adding consumer cancellation callback') + self._channel.add_on_cancel_callback(self.on_consumer_cancelled) + + def on_consumer_cancelled(self, method_frame): + """Invoked by pika when RabbitMQ sends a Basic.Cancel for a consumer + receiving messages. + + :param pika.frame.Method method_frame: The Basic.Cancel frame + + """ + LOGGER.info('Consumer was cancelled remotely, shutting down: %r', + method_frame) + if self._channel: + self._channel.close() + + def acknowledge_message(self, delivery_tag): + """Acknowledge the message delivery from RabbitMQ by sending a + Basic.Ack RPC method for the delivery tag. + + :param int delivery_tag: The delivery tag from the Basic.Deliver frame + + """ + LOGGER.info('Acknowledging message %s', delivery_tag) + self._channel.basic_ack(delivery_tag) + + def on_message(self, unused_channel, basic_deliver, properties, body): + """Invoked by pika when a message is delivered from RabbitMQ. The + channel is passed for your convenience. The basic_deliver object that + is passed in carries the exchange, routing key, delivery tag and + a redelivered flag for the message. The properties passed in is an + instance of BasicProperties with the message properties and the body + is the message that was sent. + + :param pika.channel.Channel unused_channel: The channel object + :param pika.Spec.Basic.Deliver: basic_deliver method + :param pika.Spec.BasicProperties: properties + :param str|unicode body: The message body + + """ + LOGGER.info('Received message # %s from %s: %s', + basic_deliver.delivery_tag, properties.app_id, body) + self.acknowledge_message(basic_deliver.delivery_tag) + + def on_cancelok(self, unused_frame): + """This method is invoked by pika when RabbitMQ acknowledges the + cancellation of a consumer. At this point we will close the channel. + This will invoke the on_channel_closed method once the channel has been + closed, which will in-turn close the connection. + + :param pika.frame.Method unused_frame: The Basic.CancelOk frame + + """ + LOGGER.info('RabbitMQ acknowledged the cancellation of the consumer') + self.close_channel() + + def stop_consuming(self): + """Tell RabbitMQ that you would like to stop consuming by sending the + Basic.Cancel RPC command. + + """ + if self._channel: + LOGGER.info('Sending a Basic.Cancel RPC command to RabbitMQ') + self._channel.basic_cancel(self.on_cancelok, self._consumer_tag) + + def start_consuming(self): + """This method sets up the consumer by first calling + add_on_cancel_callback so that the object is notified if RabbitMQ + cancels the consumer. It then issues the Basic.Consume RPC command + which returns the consumer tag that is used to uniquely identify the + consumer with RabbitMQ. We keep the value to use it when we want to + cancel consuming. The on_message method is passed in as a callback pika + will invoke when a message is fully received. + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.add_on_cancel_callback() + self._consumer_tag = self._channel.basic_consume(self.on_message, + self.QUEUE) + + def on_bindok(self, unused_frame): + """Invoked by pika when the Queue.Bind method has completed. At this + point we will start consuming messages by calling start_consuming + which will invoke the needed RPC commands to start the process. + + :param pika.frame.Method unused_frame: The Queue.BindOk response frame + + """ + LOGGER.info('Queue bound') + self.start_consuming() + + def close_channel(self): + """Call to close the channel with RabbitMQ cleanly by issuing the + Channel.Close RPC command. + + """ + LOGGER.info('Closing the channel') + self._channel.close() + + def open_channel(self): + """Open a new channel with RabbitMQ by issuing the Channel.Open RPC + command. When RabbitMQ responds that the channel is open, the + on_channel_open callback will be invoked by pika. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def run(self): + """Run the example consumer by connecting to RabbitMQ and then + starting the IOLoop to block and allow the SelectConnection to operate. + + """ + self._connection = self.connect() + self._connection.ioloop.start() + + def stop(self): + """Cleanly shutdown the connection to RabbitMQ by stopping the consumer + with RabbitMQ. When RabbitMQ confirms the cancellation, on_cancelok + will be invoked by pika, which will then closing the channel and + connection. The IOLoop is started again because this method is invoked + when CTRL-C is pressed raising a KeyboardInterrupt exception. This + exception stops the IOLoop which needs to be running for pika to + communicate with RabbitMQ. All of the commands issued prior to starting + the IOLoop will be buffered but not processed. + + """ + LOGGER.info('Stopping') + self._closing = True + self.stop_consuming() + self._connection.ioloop.start() + LOGGER.info('Stopped') + + + def main(): + logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) + example = ExampleConsumer('amqp://guest:guest@localhost:5672/%2F') + try: + example.run() + except KeyboardInterrupt: + example.stop() + + + if __name__ == '__main__': + main() + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/twisted_example.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/twisted_example.rst new file mode 100644 index 000000000..e4a36f8a1 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/twisted_example.rst @@ -0,0 +1,49 @@ +Twisted Consumer Example +======================== +Example of writing a consumer using the :py:class:`Twisted connection adapter `:: + + # -*- coding:utf-8 -*- + + import pika + from pika import exceptions + from pika.adapters import twisted_connection + from twisted.internet import defer, reactor, protocol,task + + + @defer.inlineCallbacks + def run(connection): + + channel = yield connection.channel() + + exchange = yield channel.exchange_declare(exchange='topic_link', exchange_type='topic') + + queue = yield channel.queue_declare(queue='hello', auto_delete=False, exclusive=False) + + yield channel.queue_bind(exchange='topic_link',queue='hello',routing_key='hello.world') + + yield channel.basic_qos(prefetch_count=1) + + queue_object, consumer_tag = yield channel.basic_consume(queue='hello',no_ack=False) + + l = task.LoopingCall(read, queue_object) + + l.start(0.01) + + + @defer.inlineCallbacks + def read(queue_object): + + ch,method,properties,body = yield queue_object.get() + + if body: + print(body) + + yield ch.basic_ack(delivery_tag=method.delivery_tag) + + + parameters = pika.ConnectionParameters() + cc = protocol.ClientCreator(reactor, twisted_connection.TwistedProtocolConnection, parameters) + d = cc.connectTCP('hostname', 5672) + d.addCallback(lambda protocol: protocol.ready) + d.addCallback(run) + reactor.run() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/using_urlparameters.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/using_urlparameters.rst new file mode 100644 index 000000000..b9f73cd8d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/examples/using_urlparameters.rst @@ -0,0 +1,68 @@ +Using URLParameters +=================== +Pika has two methods of encapsulating the data that lets it know how to connect +to RabbitMQ, :py:class:`pika.connection.ConnectionParameters` and :py:class:`pika.connection.URLParameters`. + +.. note:: + If you're connecting to RabbitMQ on localhost on port 5672, with the default virtual host of */* and the default username and password of *guest* and *guest*, you do not need to specify connection parameters when connecting. + +Using :py:class:`pika.connection.URLParameters` is an easy way to minimize the +variables required to connect to RabbitMQ and supports all of the directives +that :py:class:`pika.connection.ConnectionParameters` supports. + +The following is the format for the URLParameters connection value:: + + scheme://username:password@host:port/virtual_host?key=value&key=value + +As you can see, by default, the scheme (amqp, amqps), username, password, host, port and virtual host make up the core of the URL and any other parameter is passed in as query string values. + +Example Connection URLS +----------------------- + +The default connection URL connects to the / virtual host as guest using the guest password on localhost port 5672. Note the forwardslash in the URL is encoded to %2F:: + + amqp://guest:guest@localhost:5672/%2F + +Connect to a host *rabbit1* as the user *www-data* using the password *rabbit_pwd* on the virtual host *web_messages*:: + + amqp://www-data:rabbit_pwd@rabbit1/web_messages + +Connecting via SSL is pretty easy too. To connect via SSL for the previous example, simply change the scheme to *amqps*. If you do not specify a port, Pika will use the default SSL port of 5671:: + + amqps://www-data:rabbit_pwd@rabbit1/web_messages + +If you're looking to tweak other parameters, such as enabling heartbeats, simply add the key/value pair as a query string value. The following builds upon the SSL connection, enabling heartbeats every 30 seconds:: + + amqps://www-data:rabbit_pwd@rabbit1/web_messages?heartbeat=30 + + +Options that are available as query string values: + +- backpressure_detection: Pass in a value of *t* to enable backpressure detection, it is disabled by default. +- channel_max: Alter the default channel maximum by passing in a 32-bit integer value here. +- connection_attempts: Alter the default of 1 connection attempt by passing in an integer value here. +- frame_max: Alter the default frame maximum size value by passing in a long integer value [#f1]_. +- heartbeat: Pass a value greater than zero to enable heartbeats between the server and your application. The integer value you pass here will be the number of seconds between heartbeats. +- locale: Set the locale of the client using underscore delimited posix Locale code in ll_CC format (en_US, pt_BR, de_DE). +- retry_delay: The number of seconds to wait before attempting to reconnect on a failed connection, if connection_attempts is > 0. +- socket_timeout: Change the default socket timeout duration from 0.25 seconds to another integer or float value. Adjust with caution. +- ssl_options: A url encoded dict of values for the SSL connection. The available keys are: + - ca_certs + - cert_reqs + - certfile + - keyfile + - ssl_version + +For an information on what the ssl_options can be set to reference the `official Python documentation `_. Here is an example of setting the client certificate and key:: + + amqp://www-data:rabbit_pwd@rabbit1/web_messages?heartbeat=30&ssl_options=%7B%27keyfile%27%3A+%27%2Fetc%2Fssl%2Fmykey.pem%27%2C+%27certfile%27%3A+%27%2Fetc%2Fssl%2Fmycert.pem%27%7D + +The following example demonstrates how to generate the ssl_options string with `Python's urllib `_:: + + import urllib + urllib.urlencode({'ssl_options': {'certfile': '/etc/ssl/mycert.pem', 'keyfile': '/etc/ssl/mykey.pem'}}) + + +.. rubric:: Footnotes + +.. [#f1] The AMQP specification states that a server can reject a request for a frame size larger than the value it passes during content negotiation. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/faq.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/faq.rst new file mode 100644 index 000000000..f70ef5528 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/faq.rst @@ -0,0 +1,18 @@ +Frequently Asked Questions +-------------------------- + +- Is Pika thread safe? + + Pika does not have any notion of threading in the code. If you want to use Pika with threading, make sure you have a Pika connection per thread, created in that thread. It is not safe to share one Pika connection across threads, with one exception: you may call the connection method `add_callback_threadsafe` from another thread to schedule a callback within an active pika connection. + +- How do I report a bug with Pika? + + The `main Pika repository `_ is hosted on `Github `_ and we use the Issue tracker at `https://github.com/pika/pika/issues `_. + +- Is there a mailing list for Pika? + + Yes, Pika's mailing list is available `on Google Groups `_ and the email address is pika-python@googlegroups.com, though traditionally questions about Pika have been asked on the `RabbitMQ-Discuss mailing list `_. + +- How can I contribute to Pika? + + You can `fork the project on Github `_ and issue `Pull Requests `_ when you believe you have something solid to be added to the main repository. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/index.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/index.rst new file mode 100644 index 000000000..7f7677864 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/index.rst @@ -0,0 +1,37 @@ +Introduction to Pika +==================== +Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library. + +If you have not developed with Pika or RabbitMQ before, the :doc:`intro` documentation is a good place to get started. + +Installing Pika +--------------- +Pika is available for download via PyPI and may be installed using easy_install or pip:: + + pip install pika + +or:: + + easy_install pika + +To install from source, run "python setup.py install" in the root source directory. + +Using Pika +---------- +.. toctree:: + :glob: + :maxdepth: 1 + + intro + modules/index + examples + faq + contributors + version_history + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/intro.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/intro.rst new file mode 100644 index 000000000..ab701879d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/intro.rst @@ -0,0 +1,125 @@ +Introduction to Pika +==================== + +IO and Event Looping +-------------------- +As AMQP is a two-way RPC protocol where the client can send requests to the server and the server can send requests to a client, Pika implements or extends IO loops in each of its asynchronous connection adapters. These IO loops are blocking methods which loop and listen for events. Each asynchronous adapter follows the same standard for invoking the IO loop. The IO loop is created when the connection adapter is created. To start an IO loop for any given adapter, call the ``connection.ioloop.start()`` method. + +If you are using an external IO loop such as Tornado's :class:`~tornado.ioloop.IOLoop` you invoke it normally and then add the Pika Tornado adapter to it. + +Example:: + + import pika + + def on_open(connection): + # Invoked when the connection is open + pass + + # Create our connection object, passing in the on_open method + connection = pika.SelectConnection(on_open_callback=on_open) + + try: + # Loop so we can communicate with RabbitMQ + connection.ioloop.start() + except KeyboardInterrupt: + # Gracefully close the connection + connection.close() + # Loop until we're fully closed, will stop on its own + connection.ioloop.start() + +.. _intro_to_cps: + +Continuation-Passing Style +-------------------------- + +Interfacing with Pika asynchronously is done by passing in callback methods you would like to have invoked when a certain event completes. For example, if you are going to declare a queue, you pass in a method that will be called when the RabbitMQ server returns a `Queue.DeclareOk `_ response. + +In our example below we use the following five easy steps: + +#. We start by creating our connection object, then starting our event loop. +#. When we are connected, the *on_connected* method is called. In that method we create a channel. +#. When the channel is created, the *on_channel_open* method is called. In that method we declare a queue. +#. When the queue is declared successfully, *on_queue_declared* is called. In that method we call :py:meth:`channel.basic_consume ` telling it to call the handle_delivery for each message RabbitMQ delivers to us. +#. When RabbitMQ has a message to send us, it calls the handle_delivery method passing the AMQP Method frame, Header frame, and Body. + +.. NOTE:: + Step #1 is on line #28 and Step #2 is on line #6. This is so that Python knows about the functions we'll call in Steps #2 through #5. + +.. _cps_example: + +Example:: + + import pika + + # Create a global channel variable to hold our channel object in + channel = None + + # Step #2 + def on_connected(connection): + """Called when we are fully connected to RabbitMQ""" + # Open a channel + connection.channel(on_channel_open) + + # Step #3 + def on_channel_open(new_channel): + """Called when our channel has opened""" + global channel + channel = new_channel + channel.queue_declare(queue="test", durable=True, exclusive=False, auto_delete=False, callback=on_queue_declared) + + # Step #4 + def on_queue_declared(frame): + """Called when RabbitMQ has told us our Queue has been declared, frame is the response from RabbitMQ""" + channel.basic_consume(handle_delivery, queue='test') + + # Step #5 + def handle_delivery(channel, method, header, body): + """Called when we receive a message from RabbitMQ""" + print(body) + + # Step #1: Connect to RabbitMQ using the default parameters + parameters = pika.ConnectionParameters() + connection = pika.SelectConnection(parameters, on_connected) + + try: + # Loop so we can communicate with RabbitMQ + connection.ioloop.start() + except KeyboardInterrupt: + # Gracefully close the connection + connection.close() + # Loop until we're fully closed, will stop on its own + connection.ioloop.start() + +Credentials +----------- +The :mod:`pika.credentials` module provides the mechanism by which you pass the username and password to the :py:class:`ConnectionParameters ` class when it is created. + +Example:: + + import pika + credentials = pika.PlainCredentials('username', 'password') + parameters = pika.ConnectionParameters(credentials=credentials) + +.. _connection_parameters: + +Connection Parameters +--------------------- +There are two types of connection parameter classes in Pika to allow you to pass the connection information into a connection adapter, :class:`ConnectionParameters ` and :class:`URLParameters `. Both classes share the same default connection values. + + +.. _intro_to_backpressure: + +TCP Backpressure +---------------- + +As of RabbitMQ 2.0, client side `Channel.Flow `_ has been removed [#f1]_. Instead, the RabbitMQ broker uses TCP Backpressure to slow your client if it is delivering messages too fast. If you pass in backpressure_detection into your connection parameters, Pika attempts to help you handle this situation by providing a mechanism by which you may be notified if Pika has noticed too many frames have yet to be delivered. By registering a callback function with the :py:meth:`add_backpressure_callback ` method of any connection adapter, your function will be called when Pika sees that a backlog of 10 times the average frame size you have been sending has been exceeded. You may tweak the notification multiplier value by calling the :py:meth:`set_backpressure_multiplier ` method passing any integer value. + +Example:: + + import pika + + parameters = pika.URLParameters('amqp://guest:guest@rabbit-server1:5672/%2F?backpressure_detection=t') + +.. rubric:: Footnotes + +.. [#f1] "more effective flow control mechanism that does not require cooperation from clients and reacts quickly to prevent the broker from exhausting memory - see http://www.rabbitmq.com/extensions.html#memsup" from http://lists.rabbitmq.com/pipermail/rabbitmq-announce/attachments/20100825/2c672695/attachment.txt diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/asyncio.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/asyncio.rst new file mode 100644 index 000000000..2e58db450 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/asyncio.rst @@ -0,0 +1,9 @@ +asyncio Connection Adapter +========================== +.. automodule:: pika.adapters.asyncio_connection + +Be sure to check out the :doc:`asynchronous examples ` including the asyncio specific :doc:`consumer ` example. + +.. autoclass:: pika.adapters.asyncio_connection.AsyncioConnection + :members: + :inherited-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/blocking.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/blocking.rst new file mode 100644 index 000000000..81b2eb504 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/blocking.rst @@ -0,0 +1,13 @@ +BlockingConnection +------------------ +.. automodule:: pika.adapters.blocking_connection + +Be sure to check out examples in :doc:`/examples`. + +.. autoclass:: pika.adapters.blocking_connection.BlockingConnection + :members: + :inherited-members: + +.. autoclass:: pika.adapters.blocking_connection.BlockingChannel + :members: + :inherited-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/index.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/index.rst new file mode 100644 index 000000000..7bc694da4 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/index.rst @@ -0,0 +1,15 @@ +Connection Adapters +=================== +Pika uses connection adapters to provide a flexible method for adapting pika's +core communication to different IOLoop implementations. In addition to asynchronous adapters, there is the :class:`BlockingConnection ` adapter that provides a more idiomatic procedural approach to using Pika. + +Adapters +-------- +.. toctree:: + :glob: + :maxdepth: 1 + + blocking + select + tornado + twisted diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/select.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/select.rst new file mode 100644 index 000000000..e02b57135 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/select.rst @@ -0,0 +1,7 @@ +Select Connection Adapter +========================== +.. automodule:: pika.adapters.select_connection + +.. autoclass:: pika.adapters.select_connection.SelectConnection + :members: + :inherited-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/tornado.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/tornado.rst new file mode 100644 index 000000000..97784a5c5 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/tornado.rst @@ -0,0 +1,9 @@ +Tornado Connection Adapter +========================== +.. automodule:: pika.adapters.tornado_connection + +Be sure to check out the :doc:`asynchronous examples ` including the Tornado specific :doc:`consumer ` example. + +.. autoclass:: pika.adapters.tornado_connection.TornadoConnection + :members: + :inherited-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/twisted.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/twisted.rst new file mode 100644 index 000000000..434201ced --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/adapters/twisted.rst @@ -0,0 +1,15 @@ +Twisted Connection Adapter +========================== +.. automodule:: pika.adapters.twisted_connection + +.. autoclass:: pika.adapters.twisted_connection.TwistedConnection + :members: + :inherited-members: + +.. autoclass:: pika.adapters.twisted_connection.TwistedProtocolConnection + :members: + :inherited-members: + +.. autoclass:: pika.adapters.twisted_connection.TwistedChannel + :members: + :inherited-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/channel.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/channel.rst new file mode 100644 index 000000000..eb729c564 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/channel.rst @@ -0,0 +1,10 @@ +Channel +======= +.. automodule:: pika.channel + +Channel +------- +.. autoclass:: Channel + :members: + :inherited-members: + :member-order: bysource diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/connection.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/connection.rst new file mode 100644 index 000000000..de42f5c72 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/connection.rst @@ -0,0 +1,7 @@ +Connection +---------- +The :class:`~pika.connection.Connection` class implements the base behavior +that all connection adapters extend. + +.. autoclass:: pika.connection.Connection + :members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/credentials.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/credentials.rst new file mode 100644 index 000000000..94a2de54e --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/credentials.rst @@ -0,0 +1,18 @@ +Authentication Credentials +========================== +.. automodule:: pika.credentials + +PlainCredentials +---------------- +.. autoclass:: PlainCredentials + :members: + :inherited-members: + :noindex: + +ExternalCredentials +------------------- +.. autoclass:: ExternalCredentials + :members: + :inherited-members: + :noindex: + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/exceptions.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/exceptions.rst new file mode 100644 index 000000000..3bb3afdac --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/exceptions.rst @@ -0,0 +1,5 @@ +Exceptions +========== +.. automodule:: pika.exceptions + :members: + :undoc-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/index.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/index.rst new file mode 100644 index 000000000..33f5b0c72 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/index.rst @@ -0,0 +1,21 @@ +Core Class and Module Documentation +=================================== +For the end user, Pika is organized into a small set of objects for all communication with RabbitMQ. + +- A :doc:`connection adapter ` is used to connect to RabbitMQ and manages the connection. +- :doc:`Connection parameters ` are used to instruct the :class:`~pika.connection.Connection` object how to connect to RabbitMQ. +- :doc:`credentials` are used to encapsulate all authentication information for the :class:`~pika.connection.ConnectionParameters` class. +- A :class:`~pika.channel.Channel` object is used to communicate with RabbitMQ via the AMQP RPC methods. +- :doc:`exceptions` are raised at various points when using Pika when something goes wrong. + +.. toctree:: + :hidden: + :maxdepth: 1 + + adapters/index + channel + connection + credentials + exceptions + parameters + spec diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/parameters.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/parameters.rst new file mode 100644 index 000000000..fa05ed094 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/parameters.rst @@ -0,0 +1,42 @@ +Connection Parameters +===================== +To maintain flexibility in how you specify the connection information required for your applications to properly connect to RabbitMQ, pika implements two classes for encapsulating the information, :class:`~pika.connection.ConnectionParameters` and :class:`~pika.connection.URLParameters`. + +ConnectionParameters +-------------------- +The classic object for specifying all of the connection parameters required to connect to RabbitMQ, :class:`~pika.connection.ConnectionParameters` provides attributes for tweaking every possible connection option. + +Example:: + + import pika + + # Set the connection parameters to connect to rabbit-server1 on port 5672 + # on the / virtual host using the username "guest" and password "guest" + credentials = pika.PlainCredentials('guest', 'guest') + parameters = pika.ConnectionParameters('rabbit-server1', + 5672, + '/', + credentials) + +.. autoclass:: pika.connection.ConnectionParameters + :members: + :inherited-members: + :member-order: bysource + +URLParameters +------------- +The :class:`~pika.connection.URLParameters` class allows you to pass in an AMQP URL when creating the object and supports the host, port, virtual host, ssl, username and password in the base URL and other options are passed in via query parameters. + +Example:: + + import pika + + # Set the connection parameters to connect to rabbit-server1 on port 5672 + # on the / virtual host using the username "guest" and password "guest" + parameters = pika.URLParameters('amqp://guest:guest@rabbit-server1:5672/%2F') + +.. autoclass:: pika.connection.URLParameters + :members: + :inherited-members: + :member-order: bysource + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/spec.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/spec.rst new file mode 100644 index 000000000..d494300a8 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/modules/spec.rst @@ -0,0 +1,8 @@ +pika.spec +========= + +.. automodule:: pika.spec + :members: + :inherited-members: + :member-order: bysource + :undoc-members: diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/version_history.rst b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/version_history.rst new file mode 100644 index 000000000..8a7578d77 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/docs/version_history.rst @@ -0,0 +1,760 @@ +Version History +=============== + +0.13.1 2019-03-07 +----------------- + +`GitHub milestone `_ + +0.13.0 2019-01-17 +----------------- + +`GitHub milestone `_ + +- `AsyncioConnection`, `TornadoConnection` and `TwistedProtocolConnection` are no longer auto-imported (`PR `_) +- Python `3.7` support (`Issue `_) + +0.12.0 2018-06-19 +----------------- + +`GitHub milestone `_ + +This is an interim release prior to version `1.0.0`. It includes the following backported pull requests and commits from the `master` branch: + +- `PR #908 `_ +- `PR #910 `_ +- `PR #918 `_ +- `PR #920 `_ +- `PR #924 `_ +- `PR #937 `_ +- `PR #938 `_ +- `PR #933 `_ +- `PR #940 `_ +- `PR #932 `_ +- `PR #928 `_ +- `PR #934 `_ +- `PR #915 `_ +- `PR #946 `_ +- `PR #947 `_ +- `PR #952 `_ +- `PR #956 `_ +- `PR #966 `_ +- `PR #975 `_ +- `PR #978 `_ +- `PR #981 `_ +- `PR #994 `_ +- `PR #1007 `_ +- `PR #1045 `_ (manually backported) +- `PR #1011 `_ + +Commits: + +Travis CI fail fast - 3f0e739 + +New features: + +`BlockingConnection` now supports the `add_callback_threadsafe` method which allows a function to be executed correctly on the IO loop thread. The main use-case for this is as follows: + +- Application sets up a thread for `BlockingConnection` and calls `basic_consume` on it +- When a message is received, work is done on another thread +- When the work is done, the worker uses `connection.add_callback_threadsafe` to call the `basic_ack` method on the channel instance. + +Please see `examples/basic_consumer_threaded.py` for an example. As always, `SelectConnection` and a fully async consumer/publisher is the preferred method of using Pika. + +Heartbeats are now sent at an interval equal to 1/2 of the negotiated idle connection timeout. RabbitMQ's default timeout value is 60 seconds, so heartbeats will be sent at a 30 second interval. In addition, Pika's check for an idle connection will be done at an interval equal to the timeout value plus 5 seconds to allow for delays. This results in an interval of 65 seconds by default. + +0.11.2 2017-11-30 +----------------- + +`GitHub milestone `_ + +`0.11.2 `_ + +- Remove `+` character from platform releases string (`PR `_) + +0.11.1 2017-11-27 +----------------- + +`GitHub milestone `_ + +`0.11.1 `_ + +- Fix `BlockingConnection` to ensure event loop exits (`PR `_) +- Heartbeat timeouts will use the client value if specified (`PR `_) +- Allow setting some common TCP options (`PR `_) +- Errors when decoding Unicode are ignored (`PR `_) +- Fix large number encoding (`PR `_) + +0.11.0 2017-07-29 +----------------- + +`GitHub milestone `_ + +`0.11.0 `_ + + - Simplify Travis CI configuration for OS X. + - Add `asyncio` connection adapter for Python 3.4 and newer. + - Connection failures that occur after the socket is opened and before the + AMQP connection is ready to go are now reported by calling the connection + error callback. Previously these were not consistently reported. + - In BaseConnection.close, call _handle_ioloop_stop only if the connection is + already closed to allow the asynchronous close operation to complete + gracefully. + - Pass error information from failed socket connection to user callbacks + on_open_error_callback and on_close_callback with result_code=-1. + - ValueError is raised when a completion callback is passed to an asynchronous + (nowait) Channel operation. It's an application error to pass a non-None + completion callback with an asynchronous request, because this callback can + never be serviced in the asynchronous scenario. + - `Channel.basic_reject` fixed to allow `delivery_tag` to be of type `long` + as well as `int`. (by quantum5) + - Implemented support for blocked connection timeouts in + `pika.connection.Connection`. This feature is available to all pika adapters. + See `pika.connection.ConnectionParameters` docstring to learn more about + `blocked_connection_timeout` configuration. + - Deprecated the `heartbeat_interval` arg in `pika.ConnectionParameters` in + favor of the `heartbeat` arg for consistency with the other connection + parameters classes `pika.connection.Parameters` and `pika.URLParameters`. + - When the `port` arg is not set explicitly in `ConnectionParameters` + constructor, but the `ssl` arg is set explicitly, then set the port value to + to the default AMQP SSL port if SSL is enabled, otherwise to the default + AMQP plaintext port. + - `URLParameters` will raise ValueError if a non-empty URL scheme other than + {amqp | amqps | http | https} is specified. + - `InvalidMinimumFrameSize` and `InvalidMaximumFrameSize` exceptions are + deprecated. pika.connection.Parameters.frame_max property setter now raises + the standard `ValueError` exception when the value is out of bounds. + - Removed deprecated parameter `type` in `Channel.exchange_declare` and + `BlockingChannel.exchange_declare` in favor of the `exchange_type` arg that + doesn't overshadow the builtin `type` keyword. + - Channel.close() on OPENING channel transitions it to CLOSING instead of + raising ChannelClosed. + - Channel.close() on CLOSING channel raises `ChannelAlreadyClosing`; used to + raise `ChannelClosed`. + - Connection.channel() raises `ConnectionClosed` if connection is not in OPEN + state. + - When performing graceful close on a channel and `Channel.Close` from broker + arrives while waiting for CloseOk, don't release the channel number until + CloseOk arrives to avoid race condition that may lead to a new channel + receiving the CloseOk that was destined for the closing channel. + - The `backpressure_detection` option of `ConnectionParameters` and + `URLParameters` property is DEPRECATED in favor of `Connection.Blocked` and + `Connection.Unblocked`. See `Connection.add_on_connection_blocked_callback`. + +0.10.0 2015-09-02 +----------------- + +`0.10.0 `_ + + - a9bf96d - LibevConnection: Fixed dict chgd size during iteration (Michael Laing) + - 388c55d - SelectConnection: Fixed KeyError exceptions in IOLoop timeout executions (Shinji Suzuki) + - 4780de3 - BlockingConnection: Add support to make BlockingConnection a Context Manager (@reddec) + +0.10.0b2 2015-07-15 +------------------- + + - f72b58f - Fixed failure to purge _ConsumerCancellationEvt from BlockingChannel._pending_events during basic_cancel. (Vitaly Kruglikov) + +0.10.0b1 2015-07-10 +------------------- + +High-level summary of notable changes: + +- Change to 3-Clause BSD License +- Python 3.x support +- Over 150 commits from 19 contributors +- Refactoring of SelectConnection ioloop +- This major release contains certain non-backward-compatible API changes as + well as significant performance improvements in the `BlockingConnection` + adapter. +- Non-backward-compatible changes in `Channel.add_on_return_callback` callback's + signature. +- The `AsyncoreConnection` adapter was retired + +**Details** + +Python 3.x: this release introduces python 3.x support. Tested on Python 3.3 +and 3.4. + +`AsyncoreConnection`: Retired this legacy adapter to reduce maintenance burden; +the recommended replacement is the `SelectConnection` adapter. + +`SelectConnection`: ioloop was refactored for compatibility with other ioloops. + +`Channel.add_on_return_callback`: The callback is now passed the individual +parameters channel, method, properties, and body instead of a tuple of those +values for congruence with other similar callbacks. + +`BlockingConnection`: This adapter underwent a makeover under the hood and +gained significant performance improvements as well as enhanced timer +resolution. It is now implemented as a client of the `SelectConnection` adapter. + +Below is an overview of the `BlockingConnection` and `BlockingChannel` API +changes: + + - Recursion: the new implementation eliminates callback recursion that + sometimes blew out the stack in the legacy implementation (e.g., + publish -> consumer_callback -> publish -> consumer_callback, etc.). While + `BlockingConnection.process_data_events` and `BlockingConnection.sleep` may + still be called from the scope of the blocking adapter's callbacks in order + to process pending I/O, additional callbacks will be suppressed whenever + `BlockingConnection.process_data_events` and `BlockingConnection.sleep` are + nested in any combination; in that case, the callback information will be + bufferred and dispatched once nesting unwinds and control returns to the + level-zero dispatcher. + - `BlockingConnection.connect`: this method was removed in favor of the + constructor as the only way to establish connections; this reduces + maintenance burden, while improving reliability of the adapter. + - `BlockingConnection.process_data_events`: added the optional parameter + `time_limit`. + - `BlockingConnection.add_on_close_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_on_open_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_on_open_error_callback`: removed; legacy raised + `NotImplementedError`. + - `BlockingConnection.add_backpressure_callback`: not supported + - `BlockingConnection.set_backpressure_multiplier`: not supported + - `BlockingChannel.add_on_flow_callback`: not supported; per docstring in + channel.py: "Note that newer versions of RabbitMQ will not issue this but + instead use TCP backpressure". + - `BlockingChannel.flow`: not supported + - `BlockingChannel.force_data_events`: removed as it is no longer necessary + following redesign of the adapter. + - Removed the `nowait` parameter from `BlockingChannel` methods, forcing + `nowait=False` (former API default) in the implementation; this is more + suitable for the blocking nature of the adapter and its error-reporting + strategy; this concerns the following methods: `basic_cancel`, + `confirm_delivery`, `exchange_bind`, `exchange_declare`, `exchange_delete`, + `exchange_unbind`, `queue_bind`, `queue_declare`, `queue_delete`, and + `queue_purge`. + - `BlockingChannel.basic_cancel`: returns a sequence instead of None; for a + `no_ack=True` consumer, `basic_cancel` returns a sequence of pending + messages that arrived before broker confirmed the cancellation. + - `BlockingChannel.consume`: added new optional kwargs `arguments` and + `inactivity_timeout`. Also, raises ValueError if the consumer creation + parameters don't match those used to create the existing queue consumer + generator, if any; this happens when you break out of the consume loop, then + call `BlockingChannel.consume` again with different consumer-creation args + without first cancelling the previous queue consumer generator via + `BlockingChannel.cancel`. The legacy implementation would silently resume + consuming from the existing queue consumer generator even if the subsequent + `BlockingChannel.consume` was invoked with a different queue name, etc. + - `BlockingChannel.cancel`: returns 0; the legacy implementation tried to + return the number of requeued messages, but this number was not accurate + as it didn't include the messages returned by the Channel class; this count + is not generally useful, so returning 0 is a reasonable replacement. + - `BlockingChannel.open`: removed in favor of having a single mechanism for + creating a channel (`BlockingConnection.channel`); this reduces maintenance + burden, while improving reliability of the adapter. + - `BlockingChannel.confirm_delivery`: raises UnroutableError when unroutable + messages that were sent prior to this call are returned before we receive + Confirm.Select-ok. + - `BlockingChannel.basic_publish: always returns True when delivery + confirmation is not enabled (publisher-acks = off); the legacy implementation + returned a bool in this case if `mandatory=True` to indicate whether the + message was delivered; however, this was non-deterministic, because + Basic.Return is asynchronous and there is no way to know how long to wait + for it or its absence. The legacy implementation returned None when + publishing with publisher-acks = off and `mandatory=False`. The new + implementation always returns True when publishing while + publisher-acks = off. + - `BlockingChannel.publish`: a new alternate method (vs. `basic_publish`) for + publishing a message with more detailed error reporting via UnroutableError + and NackError exceptions. + - `BlockingChannel.start_consuming`: raises pika.exceptions.RecursionError if + called from the scope of a `BlockingConnection` or `BlockingChannel` + callback. + - `BlockingChannel.get_waiting_message_count`: new method; returns the number + of messages that may be retrieved from the current queue consumer generator + via `BasicChannel.consume` without blocking. + +**Commits** + + - 5aaa753 - Fixed SSL import and removed no_ack=True in favor of explicit AMQP message handling based on deferreds (skftn) + - 7f222c2 - Add checkignore for codeclimate (Gavin M. Roy) + - 4dec370 - Implemented BlockingChannel.flow; Implemented BlockingConnection.add_on_connection_blocked_callback; Implemented BlockingConnection.add_on_connection_unblocked_callback. (Vitaly Kruglikov) + - 4804200 - Implemented blocking adapter acceptance test for exchange-to-exchange binding. Added rudimentary validation of BasicProperties passthru in blocking adapter publish tests. Updated CHANGELOG. (Vitaly Kruglikov) + - 4ec07fd - Fixed sending of data in TwistedProtocolConnection (Vitaly Kruglikov) + - a747fb3 - Remove my copyright from forward_server.py test utility. (Vitaly Kruglikov) + - 94246d2 - Return True from basic_publish when pubacks is off. Implemented more blocking adapter accceptance tests. (Vitaly Kruglikov) + - 3ce013d - PIKA-609 Wait for broker to dispatch all messages to client before cancelling consumer in TestBasicCancelWithNonAckableConsumer and TestBasicCancelWithAckableConsumer (Vitaly Kruglikov) + - 293f778 - Created CHANGELOG entry for release 0.10.0. Fixed up callback documentation for basic_get, basic_consume, and add_on_return_callback. (Vitaly Kruglikov) + - 16d360a - Removed the legacy AsyncoreConnection adapter in favor of the recommended SelectConnection adapter. (Vitaly Kruglikov) + - 240a82c - Defer creation of poller's event loop interrupt socket pair until start is called, because some SelectConnection users (e.g., BlockingConnection adapter) don't use the event loop, and these sockets would just get reported as resource leaks. (Vitaly Kruglikov) + - aed5cae - Added EINTR loops in select_connection pollers. Addressed some pylint findings, including an error or two. Wrap socket.send and socket.recv calls in EINTR loops Use the correct exception for socket.error and select.error and get errno depending on python version. (Vitaly Kruglikov) + - 498f1be - Allow passing exchange, queue and routing_key as text, handle short strings as text in python3 (saarni) + - 9f7f243 - Restored basic_consume, basic_cancel, and add_on_cancel_callback (Vitaly Kruglikov) + - 18c9909 - Reintroduced BlockingConnection.process_data_events. (Vitaly Kruglikov) + - 4b25cb6 - Fixed BlockingConnection/BlockingChannel acceptance and unit tests (Vitaly Kruglikov) + - bfa932f - Facilitate proper connection state after BasicConnection._adapter_disconnect (Vitaly Kruglikov) + - 9a09268 - Fixed BlockingConnection test that was failing with ConnectionClosed error. (Vitaly Kruglikov) + - 5a36934 - Copied synchronous_connection.py from pika-synchronous branch Fixed pylint findings Integrated SynchronousConnection with the new ioloop in SelectConnection Defined dedicated message classes PolledMessage and ConsumerMessage and moved from BlockingChannel to module-global scope. Got rid of nowait args from BlockingChannel public API methods Signal unroutable messages via UnroutableError exception. Signal Nack'ed messages via NackError exception. These expose more information about the failure than legacy basic_publich API. Removed set_timeout and backpressure callback methods Restored legacy `is_open`, etc. property names (Vitaly Kruglikov) + - 6226dc0 - Remove deprecated --use-mirrors (Gavin M. Roy) + - 1a7112f - Raise ConnectionClosed when sending a frame with no connection (#439) (Gavin M. Roy) + - 9040a14 - Make delivery_tag non-optional (#498) (Gavin M. Roy) + - 86aabc2 - Bump version (Gavin M. Roy) + - 562075a - Update a few testing things (Gavin M. Roy) + - 4954d38 - use unicode_type in blocking_connection.py (Antti Haapala) + - 133d6bc - Let Travis install ordereddict for Python 2.6, and ttest 3.3, 3.4 too. (Antti Haapala) + - 0d2287d - Pika Python 3 support (Antti Haapala) + - 3125c79 - SSLWantRead is not supported before python 2.7.9 and 3.3 (Will) + - 9a9c46c - Fixed TestDisconnectDuringConnectionStart: it turns out that depending on callback order, it might get either ProbableAuthenticationError or ProbableAccessDeniedError. (Vitaly Kruglikov) + - cd8c9b0 - A fix the write starvation problem that we see with tornado and pika (Will) + - 8654fbc - SelectConnection - make interrupt socketpair non-blocking (Will) + - 4f3666d - Added copyright in forward_server.py and fixed NameError bug (Vitaly Kruglikov) + - f8ebbbc - ignore docs (Gavin M. Roy) + - a344f78 - Updated codeclimate config (Gavin M. Roy) + - 373c970 - Try and fix pathing issues in codeclimate (Gavin M. Roy) + - 228340d - Ignore codegen (Gavin M. Roy) + - 4db0740 - Add a codeclimate config (Gavin M. Roy) + - 7e989f9 - Slight code re-org, usage comment and better naming of test file. (Will) + - 287be36 - Set up _kqueue member of KQueuePoller before calling super constructor to avoid exception due to missing _kqueue member. Call `self._map_event(event)` instead of `self._map_event(event.filter)`, because `KQueuePoller._map_event()` assumes it's getting an event, not an event filter. (Vitaly Kruglikov) + - 62810fb - Fix issue #412: reset BlockingConnection._read_poller in BlockingConnection._adapter_disconnect() to guard against accidental access to old file descriptor. (Vitaly Kruglikov) + - 03400ce - Rationalise adapter acceptance tests (Will) + - 9414153 - Fix bug selecting non epoll poller (Will) + - 4f063df - Use user heartbeat setting if server proposes none (Pau Gargallo) + - 9d04d6e - Deactivate heartbeats when heartbeat_interval is 0 (Pau Gargallo) + - a52a608 - Bug fix and review comments. (Will) + - e3ebb6f - Fix incorrect x-expires argument in acceptance tests (Will) + - 294904e - Get BlockingConnection into consistent state upon loss of TCP/IP connection with broker and implement acceptance tests for those cases. (Vitaly Kruglikov) + - 7f91a68 - Make SelectConnection behave like an ioloop (Will) + - dc9db2b - Perhaps 5 seconds is too agressive for travis (Gavin M. Roy) + - c23e532 - Lower the stuck test timeout (Gavin M. Roy) + - 1053ebc - Late night bug (Gavin M. Roy) + - cd6c1bf - More BaseConnection._handle_error cleanup (Gavin M. Roy) + - a0ff21c - Fix the test to work with Python 2.6 (Gavin M. Roy) + - 748e8aa - Remove pypy for now (Gavin M. Roy) + - 1c921c1 - Socket close/shutdown cleanup (Gavin M. Roy) + - 5289125 - Formatting update from PR (Gavin M. Roy) + - d235989 - Be more specific when calling getaddrinfo (Gavin M. Roy) + - b5d1b31 - Reflect the method name change in pika.callback (Gavin M. Roy) + - df7d3b7 - Cleanup BlockingConnection in a few places (Gavin M. Roy) + - cd99e1c - Rename method due to use in BlockingConnection (Gavin M. Roy) + - 7e0d1b3 - Use google style with yapf instead of pep8 (Gavin M. Roy) + - 7dc9bab - Refactor socket writing to not use sendall #481 (Gavin M. Roy) + - 4838789 - Dont log the fd #521 (Gavin M. Roy) + - 765107d - Add Connection.Blocked callback registration methods #476 (Gavin M. Roy) + - c15b5c1 - Fix _blocking typo pointed out in #513 (Gavin M. Roy) + - 759ac2c - yapf of codegen (Gavin M. Roy) + - 9dadd77 - yapf cleanup of codegen and spec (Gavin M. Roy) + - ddba7ce - Do not reject consumers with no_ack=True #486 #530 (Gavin M. Roy) + - 4528a1a - yapf reformatting of tests (Gavin M. Roy) + - e7b6d73 - Remove catching AttributError (#531) (Gavin M. Roy) + - 41ea5ea - Update README badges [skip ci] (Gavin M. Roy) + - 6af987b - Add note on contributing (Gavin M. Roy) + - 161fc0d - yapf formatting cleanup (Gavin M. Roy) + - edcb619 - Add PYPY to travis testing (Gavin M. Roy) + - 2225771 - Change the coverage badge (Gavin M. Roy) + - 8f7d451 - Move to codecov from coveralls (Gavin M. Roy) + - b80407e - Add confirm_delivery to example (Andrew Smith) + - 6637212 - Update base_connection.py (bstemshorn) + - 1583537 - #544 get_waiting_message_count() (markcf) + - 0c9be99 - Fix #535: pass expected reply_code and reply_text from method frame to Connection._on_disconnect from Connection._on_connection_closed (Vitaly Kruglikov) + - d11e73f - Propagate ConnectionClosed exception out of BlockingChannel._send_method() and log ConnectionClosed in BlockingConnection._on_connection_closed() (Vitaly Kruglikov) + - 63d2951 - Fix #541 - make sure connection state is properly reset when BlockingConnection._check_state_on_disconnect raises ConnectionClosed. This supplements the previously-merged PR #450 by getting the connection into consistent state. (Vitaly Kruglikov) + - 71bc0eb - Remove unused self.fd attribute from BaseConnection (Vitaly Kruglikov) + - 8c08f93 - PIKA-532 Removed unnecessary params (Vitaly Kruglikov) + - 6052ecf - PIKA-532 Fix bug in BlockingConnection._handle_timeout that was preventing _on_connection_closed from being called when not closing. (Vitaly Kruglikov) + - 562aa15 - pika: callback: Display exception message when callback fails. (Stuart Longland) + - 452995c - Typo fix in connection.py (Andrew) + - 361c0ad - Added some missing yields (Robert Weidlich) + - 0ab5a60 - Added complete example for python twisted service (Robert Weidlich) + - 4429110 - Add deployment and webhooks (Gavin M. Roy) + - 7e50302 - Fix has_content style in codegen (Andrew Grigorev) + - 28c2214 - Fix the trove categorization (Gavin M. Roy) + - de8b545 - Ensure frames can not be interspersed on send (Gavin M. Roy) + - 8fe6bdd - Fix heartbeat behaviour after connection failure. (Kyösti Herrala) + - c123472 - Updating BlockingChannel.basic_get doc (it does not receive a callback like the rest of the adapters) (Roberto Decurnex) + - b5f52fb - Fix number of arguments passed to _on_return callback (Axel Eirola) + - 765139e - Lower default TIMEOUT to 0.01 (bra-fsn) + - 6cc22a5 - Fix confirmation on reconnects (bra-fsn) + - f4faf0a - asynchronous publisher and subscriber examples refactored to follow the StepDown rule (Riccardo Cirimelli) + +0.9.14 - 2014-07-11 +------------------- + +`0.9.14 `_ + + - 57fe43e - fix test to generate a correct range of random ints (ml) + - 0d68dee - fix async watcher for libev_connection (ml) + - 01710ad - Use default username and password if not specified in URLParameters (Sean Dwyer) + - fae328e - documentation typo (Jeff Fein-Worton) + - afbc9e0 - libev_connection: reset_io_watcher (ml) + - 24332a2 - Fix the manifest (Gavin M. Roy) + - acdfdef - Remove useless test (Gavin M. Roy) + - 7918e1a - Skip libev tests if pyev is not installed or if they are being run in pypy (Gavin M. Roy) + - bb583bf - Remove the deprecated test (Gavin M. Roy) + - aecf3f2 - Don't reject a message if the channel is not open (Gavin M. Roy) + - e37f336 - Remove UTF-8 decoding in spec (Gavin M. Roy) + - ddc35a9 - Update the unittest to reflect removal of force binary (Gavin M. Roy) + - fea2476 - PEP8 cleanup (Gavin M. Roy) + - 9b97956 - Remove force_binary (Gavin M. Roy) + - a42dd90 - Whitespace required (Gavin M. Roy) + - 85867ea - Update the content_frame_dispatcher tests to reflect removal of auto-cast utf-8 (Gavin M. Roy) + - 5a4bd5d - Remove unicode casting (Gavin M. Roy) + - efea53d - Remove force binary and unicode casting (Gavin M. Roy) + - e918d15 - Add methods to remove deprecation warnings from asyncore (Gavin M. Roy) + - 117f62d - Add a coveragerc to ignore the auto generated pika.spec (Gavin M. Roy) + - 52f4485 - Remove pypy tests from travis for now (Gavin M. Roy) + - c3aa958 - Update README.rst (Gavin M. Roy) + - 3e2319f - Delete README.md (Gavin M. Roy) + - c12b0f1 - Move to RST (Gavin M. Roy) + - 704f5be - Badging updates (Gavin M. Roy) + - 7ae33ca - Update for coverage info (Gavin M. Roy) + - ae7ca86 - add libev_adapter_tests.py; modify .travis.yml to install libev and pyev (ml) + - f86aba5 - libev_connection: add **kwargs to _handle_event; suppress default_ioloop reuse warning (ml) + - 603f1cf - async_test_base: add necessary args to _on_cconn_closed (ml) + - 3422007 - add libev_adapter_tests.py (ml) + - 6cbab0c - removed relative imports and importing urlparse from urllib.parse for py3+ (a-tal) + - f808464 - libev_connection: add async watcher; add optional parameters to add_timeout (ml) + - c041c80 - Remove ev all together for now (Gavin M. Roy) + - 9408388 - Update the test descriptions and timeout (Gavin M. Roy) + - 1b552e0 - Increase timeout (Gavin M. Roy) + - 69a1f46 - Remove the pyev requirement for 2.6 testing (Gavin M. Roy) + - fe062d2 - Update package name (Gavin M. Roy) + - 611ad0e - Distribute the LICENSE and README.md (#350) (Gavin M. Roy) + - df5e1d8 - Ensure that the entire frame is written using socket.sendall (#349) (Gavin M. Roy) + - 69ec8cf - Move the libev install to before_install (Gavin M. Roy) + - a75f693 - Update test structure (Gavin M. Roy) + - 636b424 - Update things to ignore (Gavin M. Roy) + - b538c68 - Add tox, nose.cfg, update testing config (Gavin M. Roy) + - a0e7063 - add some tests to increase coverage of pika.connection (Charles Law) + - c76d9eb - Address issue #459 (Gavin M. Roy) + - 86ad2db - Raise exception if positional arg for parameters isn't an instance of Parameters (Gavin M. Roy) + - 14d08e1 - Fix for python 2.6 (Gavin M. Roy) + - bd388a3 - Use the first unused channel number addressing #404, #460 (Gavin M. Roy) + - e7676e6 - removing a debug that was left in last commit (James Mutton) + - 6c93b38 - Fixing connection-closed behavior to detect on attempt to publish (James Mutton) + - c3f0356 - Initialize bytes_written in _handle_write() (Jonathan Kirsch) + - 4510e95 - Fix _handle_write() may not send full frame (Jonathan Kirsch) + - 12b793f - fixed Tornado Consumer example to successfully reconnect (Yang Yang) + - f074444 - remove forgotten import of ordereddict (Pedro Abranches) + - 1ba0aea - fix last merge (Pedro Abranches) + - 10490a6 - change timeouts structure to list to maintain scheduling order (Pedro Abranches) + - 7958394 - save timeouts in ordered dict instead of dict (Pedro Abranches) + - d2746bf - URLParameters and ConnectionParameters accept unicode strings (Allard Hoeve) + - 596d145 - previous fix for AttributeError made parent and child class methods identical, remove duplication (James Mutton) + - 42940dd - UrlParameters Docs: fixed amqps scheme examples (Riccardo Cirimelli) + - 43904ff - Dont test this in PyPy due to sort order issue (Gavin M. Roy) + - d7d293e - Don't leave __repr__ sorting up to chance (Gavin M. Roy) + - 848c594 - Add integration test to travis and fix invocation (Gavin M. Roy) + - 2678275 - Add pypy to travis tests (Gavin M. Roy) + - 1877f3d - Also addresses issue #419 (Gavin M. Roy) + - 470c245 - Address issue #419 (Gavin M. Roy) + - ca3cb59 - Address issue #432 (Gavin M. Roy) + - a3ff6f2 - Default frame max should be AMQP FRAME_MAX (Gavin M. Roy) + - ff3d5cb - Remove max consumer tag test due to change in code. (Gavin M. Roy) + - 6045dda - Catch KeyError (#437) to ensure that an exception is not raised in a race condition (Gavin M. Roy) + - 0b4d53a - Address issue #441 (Gavin M. Roy) + - 180e7c4 - Update license and related files (Gavin M. Roy) + - 256ed3d - Added Jython support. (Erik Olof Gunnar Andersson) + - f73c141 - experimental work around for recursion issue. (Erik Olof Gunnar Andersson) + - a623f69 - Prevent #436 by iterating the keys and not the dict (Gavin M. Roy) + - 755fcae - Add support for authentication_failure_close, connection.blocked (Gavin M. Roy) + - c121243 - merge upstream master (Michael Laing) + - a08dc0d - add arg to channel.basic_consume (Pedro Abranches) + - 10b136d - Documentation fix (Anton Ryzhov) + - 9313307 - Fixed minor markup errors. (Jorge Puente Sarrín) + - fb3e3cf - Fix the spelling of UnsupportedAMQPFieldException (Garrett Cooper) + - 03d5da3 - connection.py: Propagate the force_channel keyword parameter to methods involved in channel creation (Michael Laing) + - 7bbcff5 - Documentation fix for basic_publish (JuhaS) + - 01dcea7 - Expose no_ack and exclusive to BlockingChannel.consume (Jeff Tang) + - d39b6aa - Fix BlockingChannel.basic_consume does not block on non-empty queues (Juhyeong Park) + - 6e1d295 - fix for issue 391 and issue 307 (Qi Fan) + - d9ffce9 - Update parameters.rst (cacovsky) + - 6afa41e - Add additional badges (Gavin M. Roy) + - a255925 - Fix return value on dns resolution issue (Laurent Eschenauer) + - 3f7466c - libev_connection: tweak docs (Michael Laing) + - 0aaed93 - libev_connection: Fix varable naming (Michael Laing) + - 0562d08 - libev_connection: Fix globals warning (Michael Laing) + - 22ada59 - libev_connection: use globals to track sigint and sigterm watchers as they are created globally within libev (Michael Laing) + - 2649b31 - Move badge [skip ci] (Gavin M. Roy) + - f70eea1 - Remove pypy and installation attempt of pyev (Gavin M. Roy) + - f32e522 - Conditionally skip external connection adapters if lib is not installed (Gavin M. Roy) + - cce97c5 - Only install pyev on python 2.7 (Gavin M. Roy) + - ff84462 - Add travis ci support (Gavin M. Roy) + - cf971da - lib_evconnection: improve signal handling; add callback (Michael Laing) + - 9adb269 - bugfix in returning a list in Py3k (Alex Chandel) + - c41d5b9 - update exception syntax for Py3k (Alex Chandel) + - c8506f1 - fix _adapter_connect (Michael Laing) + - 67cb660 - Add LibevConnection to README (Michael Laing) + - 1f9e72b - Propagate low-level connection errors to the AMQPConnectionError. (Bjorn Sandberg) + - e1da447 - Avoid race condition in _on_getok on successive basic_get() when clearing out callbacks (Jeff) + - 7a09979 - Add support for upcoming Connection.Blocked/Unblocked (Gavin M. Roy) + - 53cce88 - TwistedChannel correctly handles multi-argument deferreds. (eivanov) + - 66f8ace - Use uuid when creating unique consumer tag (Perttu Ranta-aho) + - 4ee2738 - Limit the growth of Channel._cancelled, use deque instead of list. (Perttu Ranta-aho) + - 0369aed - fix adapter references and tweak docs (Michael Laing) + - 1738c23 - retry select.select() on EINTR (Cenk Alti) + - 1e55357 - libev_connection: reset internal state on reconnect (Michael Laing) + - 708559e - libev adapter (Michael Laing) + - a6b7c8b - Prioritize EPollPoller and KQueuePoller over PollPoller and SelectPoller (Anton Ryzhov) + - 53400d3 - Handle socket errors in PollPoller and EPollPoller Correctly check 'select.poll' availability (Anton Ryzhov) + - a6dc969 - Use dict.keys & items instead of iterkeys & iteritems (Alex Chandel) + - 5c1b0d0 - Use print function syntax, in examples (Alex Chandel) + - ac9f87a - Fixed a typo in the name of the Asyncore Connection adapter (Guruprasad) + - dfbba50 - Fixed bug mentioned in Issue #357 (Erik Andersson) + - c906a2d - Drop additional flags when getting info for the hostnames, log errors (#352) (Gavin M. Roy) + - baf23dd - retry poll() on EINTR (Cenk Alti) + - 7cd8762 - Address ticket #352 catching an error when socket.getprotobyname fails (Gavin M. Roy) + - 6c3ec75 - Prep for 0.9.14 (Gavin M. Roy) + - dae7a99 - Bump to 0.9.14p0 (Gavin M. Roy) + - 620edc7 - Use default port and virtual host if omitted in URLParameters (Issue #342) (Gavin M. Roy) + - 42a8787 - Move the exception handling inside the while loop (Gavin M. Roy) + - 10e0264 - Fix connection back pressure detection issue #347 (Gavin M. Roy) + - 0bfd670 - Fixed mistake in commit 3a19d65. (Erik Andersson) + - da04bc0 - Fixed Unknown state on disconnect error message generated when closing connections. (Erik Andersson) + - 3a19d65 - Alternative solution to fix #345. (Erik Andersson) + - abf9fa8 - switch to sendall to send entire frame (Dustin Koupal) + - 9ce8ce4 - Fixed the async publisher example to work with reconnections (Raphaël De Giusti) + - 511028a - Fix typo in TwistedChannel docstring (cacovsky) + - 8b69e5a - calls self._adapter_disconnect() instead of self.disconnect() which doesn't actually exist #294 (Mark Unsworth) + - 06a5cf8 - add NullHandler to prevent logging warnings (Cenk Alti) + - f404a9a - Fix #337 cannot start ioloop after stop (Ralf Nyren) + +0.9.13 - 2013-05-15 +------------------- + +`0.9.13 `_ + +**Major Changes** + +- IPv6 Support with thanks to Alessandro Tagliapietra for initial prototype +- Officially remove support for <= Python 2.5 even though it was broken already +- Drop pika.simplebuffer.SimpleBuffer in favor of the Python stdlib collections.deque object +- New default object for receiving content is a "bytes" object which is a str wrapper in Python 2, but paves way for Python 3 support +- New "Raw" mode for frame decoding content frames (#334) addresses issues #331, #229 added by Garth Williamson +- Connection and Disconnection logic refactored, allowing for cleaner separation of protocol logic and socket handling logic as well as connection state management +- New "on_open_error_callback" argument in creating connection objects and new Connection.add_on_open_error_callback method +- New Connection.connect method to cleanly allow for reconnection code +- Support for all AMQP field types, using protocol specified signed/unsigned unpacking + +**Backwards Incompatible Changes** + +- Method signature for creating connection objects has new argument "on_open_error_callback" which is positionally before "on_close_callback" +- Internal callback variable names in connection.Connection have been renamed and constants used. If you relied on any of these callbacks outside of their internal use, make sure to check out the new constants. +- Connection._connect method, which was an internal only method is now deprecated and will raise a DeprecationWarning. If you relied on this method, your code needs to change. +- pika.simplebuffer has been removed + +**Bugfixes** + +- BlockingConnection consumer generator does not free buffer when exited (#328) +- Unicode body payloads in the blocking adapter raises exception (#333) +- Support "b" short-short-int AMQP data type (#318) +- Docstring type fix in adapters/select_connection (#316) fix by Rikard Hultén +- IPv6 not supported (#309) +- Stop the HeartbeatChecker when connection is closed (#307) +- Unittest fix for SelectConnection (#336) fix by Erik Andersson +- Handle condition where no connection or socket exists but SelectConnection needs a timeout for retrying a connection (#322) +- TwistedAdapter lagging behind BaseConnection changes (#321) fix by Jan Urbański + +**Other** + +- Refactored documentation +- Added Twisted Adapter example (#314) by nolinksoft + +0.9.12 - 2013-03-18 +------------------- + +`0.9.12 `_ + +**Bugfixes** + +- New timeout id hashing was not unique + +0.9.11 - 2013-03-17 +------------------- + +`0.9.11 `_ + +**Bugfixes** + +- Address inconsistent channel close callback documentation and add the signature + change to the TwistedChannel class (#305) +- Address a missed timeout related internal data structure name change + introduced in the SelectConnection 0.9.10 release. Update all connection + adapters to use same signature and docstring (#306). + +0.9.10 - 2013-03-16 +------------------- + +`0.9.10 `_ + +**Bugfixes** + +- Fix timeout in twisted adapter (Submitted by cellscape) +- Fix blocking_connection poll timer resolution to milliseconds (Submitted by cellscape) +- Fix channel._on_close() without a method frame (Submitted by Richard Boulton) +- Addressed exception on close (Issue #279 - fix by patcpsc) +- 'messages' not initialized in BlockingConnection.cancel() (Issue #289 - fix by Mik Kocikowski) +- Make queue_unbind behave like queue_bind (Issue #277) +- Address closing behavioral issues for connections and channels (Issue #275) +- Pass a Method frame to Channel._on_close in Connection._on_disconnect (Submitted by Jan Urbański) +- Fix channel closed callback signature in the Twisted adapter (Submitted by Jan Urbański) +- Don't stop the IOLoop on connection close for in the Twisted adapter (Submitted by Jan Urbański) +- Update the asynchronous examples to fix reconnecting and have it work +- Warn if the socket was closed such as if RabbitMQ dies without a Close frame +- Fix URLParameters ssl_options (Issue #296) +- Add state to BlockingConnection addressing (Issue #301) +- Encode unicode body content prior to publishing (Issue #282) +- Fix an issue with unicode keys in BasicProperties headers key (Issue #280) +- Change how timeout ids are generated (Issue #254) +- Address post close state issues in Channel (Issue #302) + +** Behavior changes ** + +- Change core connection communication behavior to prefer outbound writes over reads, addressing a recursion issue +- Update connection on close callbacks, changing callback method signature +- Update channel on close callbacks, changing callback method signature +- Give more info in the ChannelClosed exception +- Change the constructor signature for BlockingConnection, block open/close callbacks +- Disable the use of add_on_open_callback/add_on_close_callback methods in BlockingConnection + + +0.9.9 - 2013-01-29 +------------------ + +`0.9.9 `_ + +**Bugfixes** + +- Only remove the tornado_connection.TornadoConnection file descriptor from the IOLoop if it's still open (Issue #221) +- Allow messages with no body (Issue #227) +- Allow for empty routing keys (Issue #224) +- Don't raise an exception when trying to send a frame to a closed connection (Issue #229) +- Only send a Connection.CloseOk if the connection is still open. (Issue #236 - Fix by noleaf) +- Fix timeout threshold in blocking connection - (Issue #232 - Fix by Adam Flynn) +- Fix closing connection while a channel is still open (Issue #230 - Fix by Adam Flynn) +- Fixed misleading warning and exception messages in BaseConnection (Issue #237 - Fix by Tristan Penman) +- Pluralised and altered the wording of the AMQPConnectionError exception (Issue #237 - Fix by Tristan Penman) +- Fixed _adapter_disconnect in TornadoConnection class (Issue #237 - Fix by Tristan Penman) +- Fixing hang when closing connection without any channel in BlockingConnection (Issue #244 - Fix by Ales Teska) +- Remove the process_timeouts() call in SelectConnection (Issue #239) +- Change the string validation to basestring for host connection parameters (Issue #231) +- Add a poller to the BlockingConnection to address latency issues introduced in Pika 0.9.8 (Issue #242) +- reply_code and reply_text is not set in ChannelException (Issue #250) +- Add the missing constraint parameter for Channel._on_return callback processing (Issue #257 - Fix by patcpsc) +- Channel callbacks not being removed from callback manager when channel is closed or deleted (Issue #261) + +0.9.8 - 2012-11-18 +------------------ + +`0.9.8 `_ + +**Bugfixes** + +- Channel.queue_declare/BlockingChannel.queue_declare not setting up callbacks property for empty queue name (Issue #218) +- Channel.queue_bind/BlockingChannel.queue_bind not allowing empty routing key +- Connection._on_connection_closed calling wrong method in Channel (Issue #219) +- Fix tx_commit and tx_rollback bugs in BlockingChannel (Issue #217) + +0.9.7 - 2012-11-11 +------------------ + +`0.9.7 `_ + +**New features** + +- generator based consumer in BlockingChannel (See :doc:`examples/blocking_consumer_generator` for example) + +**Changes** + +- BlockingChannel._send_method will only wait if explicitly told to + +**Bugfixes** + +- Added the exchange "type" parameter back but issue a DeprecationWarning +- Dont require a queue name in Channel.queue_declare() +- Fixed KeyError when processing timeouts (Issue # 215 - Fix by Raphael De Giusti) +- Don't try and close channels when the connection is closed (Issue #216 - Fix by Charles Law) +- Dont raise UnexpectedFrame exceptions, log them instead +- Handle multiple synchronous RPC calls made without waiting for the call result (Issues #192, #204, #211) +- Typo in docs (Issue #207 Fix by Luca Wehrstedt) +- Only sleep on connection failure when retry attempts are > 0 (Issue #200) +- Bypass _rpc method and just send frames for Basic.Ack, Basic.Nack, Basic.Reject (Issue #205) + +0.9.6 - 2012-10-29 +------------------ + +`0.9.6 `_ + +**New features** + +- URLParameters +- BlockingChannel.start_consuming() and BlockingChannel.stop_consuming() +- Delivery Confirmations +- Improved unittests + +**Major bugfix areas** + +- Connection handling +- Blocking functionality in the BlockingConnection +- SSL +- UTF-8 Handling + +**Removals** + +- pika.reconnection_strategies +- pika.channel.ChannelTransport +- pika.log +- pika.template +- examples directory + +0.9.5 - 2011-03-29 +------------------ + +`0.9.5 `_ + +**Changelog** + +- Scope changes with adapter IOLoops and CallbackManager allowing for cleaner, multi-threaded operation +- Add support for Confirm.Select with channel.Channel.confirm_delivery() +- Add examples of delivery confirmation to examples (demo_send_confirmed.py) +- Update uses of log.warn with warning.warn for TCP Back-pressure alerting +- License boilerplate updated to simplify license text in source files +- Increment the timeout in select_connection.SelectPoller reducing CPU utilization +- Bug fix in Heartbeat frame delivery addressing issue #35 +- Remove abuse of pika.log.method_call through a majority of the code +- Rename of key modules: table to data, frames to frame +- Cleanup of frame module and related classes +- Restructure of tests and test runner +- Update functional tests to respect RABBITMQ_HOST, RABBITMQ_PORT environment variables +- Bug fixes to reconnection_strategies module +- Fix the scale of timeout for PollPoller to be specified in milliseconds +- Remove mutable default arguments in RPC calls +- Add data type validation to RPC calls +- Move optional credentials erasing out of connection.Connection into credentials module +- Add support to allow for additional external credential types +- Add a NullHandler to prevent the 'No handlers could be found for logger "pika"' error message when not using pika.log in a client app at all. +- Clean up all examples to make them easier to read and use +- Move documentation into its own repository https://github.com/pika/documentation + +- channel.py + + - Move channel.MAX_CHANNELS constant from connection.CHANNEL_MAX + - Add default value of None to ChannelTransport.rpc + - Validate callback and acceptable replies parameters in ChannelTransport.RPC + - Remove unused connection attribute from Channel + +- connection.py + + - Remove unused import of struct + - Remove direct import of pika.credentials.PlainCredentials + - Change to import pika.credentials + - Move CHANNEL_MAX to channel.MAX_CHANNELS + - Change ConnectionParameters initialization parameter heartbeat to boolean + - Validate all inbound parameter types in ConnectionParameters + - Remove the Connection._erase_credentials stub method in favor of letting the Credentials object deal with that itself. + - Warn if the credentials object intends on erasing the credentials and a reconnection strategy other than NullReconnectionStrategy is specified. + - Change the default types for callback and acceptable_replies in Connection._rpc + - Validate the callback and acceptable_replies data types in Connection._rpc + +- adapters.blocking_connection.BlockingConnection + + - Addition of _adapter_disconnect to blocking_connection.BlockingConnection + - Add timeout methods to BlockingConnection addressing issue #41 + - BlockingConnection didn't allow you register more than one consumer callback because basic_consume was overridden to block immediately. New behavior allows you to do so. + - Removed overriding of base basic_consume and basic_cancel methods. Now uses underlying Channel versions of those methods. + - Added start_consuming() method to BlockingChannel to start the consumption loop. + - Updated stop_consuming() to iterate through all the registered consumers in self._consumers and issue a basic_cancel. diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_consumer_example.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_consumer_example.py new file mode 100644 index 000000000..58662e32e --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_consumer_example.py @@ -0,0 +1,350 @@ +# -*- coding: utf-8 -*- + +import logging +import pika + +LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') +LOGGER = logging.getLogger(__name__) + + +class ExampleConsumer(object): + """This is an example consumer that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + If the channel is closed, it will indicate a problem with one of the + commands that were issued and that should surface in the output as well. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Create a new instance of the consumer class, passing in the AMQP + URL used to connect to RabbitMQ. + + :param str amqp_url: The AMQP url to connect with + + """ + self._connection = None + self._channel = None + self._closing = False + self._consumer_tag = None + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return pika.SelectConnection(pika.URLParameters(self._url), + self.on_connection_open, + stop_ioloop_on_close=False) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.add_on_connection_close_callback() + self.open_channel() + + def add_on_connection_close_callback(self): + """This method adds an on close callback that will be invoked by pika + when RabbitMQ closes the connection to the publisher unexpectedly. + + """ + LOGGER.info('Adding connection close callback') + self._connection.add_on_close_callback(self.on_connection_closed) + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._closing: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self.reconnect) + + def reconnect(self): + """Will be invoked by the IOLoop timer if the connection is + closed. See the on_connection_closed method. + + """ + # This is the old connection IOLoop instance, stop its ioloop + self._connection.ioloop.stop() + + if not self._closing: + + # Create a new connection + self._connection = self.connect() + + # There is now a new connection, needs a new ioloop to run + self._connection.ioloop.start() + + def open_channel(self): + """Open a new channel with RabbitMQ by issuing the Channel.Open RPC + command. When RabbitMQ responds that the channel is open, the + on_channel_open callback will be invoked by pika. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel %i was closed: (%s) %s', + channel, reply_code, reply_text) + self._connection.close() + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def on_bindok(self, unused_frame): + """Invoked by pika when the Queue.Bind method has completed. At this + point we will start consuming messages by calling start_consuming + which will invoke the needed RPC commands to start the process. + + :param pika.frame.Method unused_frame: The Queue.BindOk response frame + + """ + LOGGER.info('Queue bound') + self.start_consuming() + + def start_consuming(self): + """This method sets up the consumer by first calling + add_on_cancel_callback so that the object is notified if RabbitMQ + cancels the consumer. It then issues the Basic.Consume RPC command + which returns the consumer tag that is used to uniquely identify the + consumer with RabbitMQ. We keep the value to use it when we want to + cancel consuming. The on_message method is passed in as a callback pika + will invoke when a message is fully received. + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.add_on_cancel_callback() + self._consumer_tag = self._channel.basic_consume(self.on_message, + self.QUEUE) + + def add_on_cancel_callback(self): + """Add a callback that will be invoked if RabbitMQ cancels the consumer + for some reason. If RabbitMQ does cancel the consumer, + on_consumer_cancelled will be invoked by pika. + + """ + LOGGER.info('Adding consumer cancellation callback') + self._channel.add_on_cancel_callback(self.on_consumer_cancelled) + + def on_consumer_cancelled(self, method_frame): + """Invoked by pika when RabbitMQ sends a Basic.Cancel for a consumer + receiving messages. + + :param pika.frame.Method method_frame: The Basic.Cancel frame + + """ + LOGGER.info('Consumer was cancelled remotely, shutting down: %r', + method_frame) + if self._channel: + self._channel.close() + + def on_message(self, unused_channel, basic_deliver, properties, body): + """Invoked by pika when a message is delivered from RabbitMQ. The + channel is passed for your convenience. The basic_deliver object that + is passed in carries the exchange, routing key, delivery tag and + a redelivered flag for the message. The properties passed in is an + instance of BasicProperties with the message properties and the body + is the message that was sent. + + :param pika.channel.Channel unused_channel: The channel object + :param pika.Spec.Basic.Deliver: basic_deliver method + :param pika.Spec.BasicProperties: properties + :param str|unicode body: The message body + + """ + LOGGER.info('Received message # %s from %s: %s', + basic_deliver.delivery_tag, properties.app_id, body) + self.acknowledge_message(basic_deliver.delivery_tag) + + def acknowledge_message(self, delivery_tag): + """Acknowledge the message delivery from RabbitMQ by sending a + Basic.Ack RPC method for the delivery tag. + + :param int delivery_tag: The delivery tag from the Basic.Deliver frame + + """ + LOGGER.info('Acknowledging message %s', delivery_tag) + self._channel.basic_ack(delivery_tag) + + def stop_consuming(self): + """Tell RabbitMQ that you would like to stop consuming by sending the + Basic.Cancel RPC command. + + """ + if self._channel: + LOGGER.info('Sending a Basic.Cancel RPC command to RabbitMQ') + self._channel.basic_cancel(self.on_cancelok, self._consumer_tag) + + def on_cancelok(self, unused_frame): + """This method is invoked by pika when RabbitMQ acknowledges the + cancellation of a consumer. At this point we will close the channel. + This will invoke the on_channel_closed method once the channel has been + closed, which will in-turn close the connection. + + :param pika.frame.Method unused_frame: The Basic.CancelOk frame + + """ + LOGGER.info('RabbitMQ acknowledged the cancellation of the consumer') + self.close_channel() + + def close_channel(self): + """Call to close the channel with RabbitMQ cleanly by issuing the + Channel.Close RPC command. + + """ + LOGGER.info('Closing the channel') + self._channel.close() + + def run(self): + """Run the example consumer by connecting to RabbitMQ and then + starting the IOLoop to block and allow the SelectConnection to operate. + + """ + self._connection = self.connect() + self._connection.ioloop.start() + + def stop(self): + """Cleanly shutdown the connection to RabbitMQ by stopping the consumer + with RabbitMQ. When RabbitMQ confirms the cancellation, on_cancelok + will be invoked by pika, which will then closing the channel and + connection. The IOLoop is started again because this method is invoked + when CTRL-C is pressed raising a KeyboardInterrupt exception. This + exception stops the IOLoop which needs to be running for pika to + communicate with RabbitMQ. All of the commands issued prior to starting + the IOLoop will be buffered but not processed. + + """ + LOGGER.info('Stopping') + self._closing = True + self.stop_consuming() + self._connection.ioloop.start() + LOGGER.info('Stopped') + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + LOGGER.info('Closing connection') + self._connection.close() + + +def main(): + logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) + example = ExampleConsumer('amqp://guest:guest@localhost:5672/%2F') + try: + example.run() + except KeyboardInterrupt: + example.stop() + + +if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_publisher_example.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_publisher_example.py new file mode 100644 index 000000000..5ffbc89c3 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/asynchronous_publisher_example.py @@ -0,0 +1,353 @@ +# -*- coding: utf-8 -*- + +import logging +import pika +import json + +LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') +LOGGER = logging.getLogger(__name__) + + +class ExamplePublisher(object): + """This is an example publisher that will handle unexpected interactions + with RabbitMQ such as channel and connection closures. + + If RabbitMQ closes the connection, it will reopen it. You should + look at the output, as there are limited reasons why the connection may + be closed, which usually are tied to permission related issues or + socket timeouts. + + It uses delivery confirmations and illustrates one way to keep track of + messages that have been sent and if they've been confirmed by RabbitMQ. + + """ + EXCHANGE = 'message' + EXCHANGE_TYPE = 'topic' + PUBLISH_INTERVAL = 1 + QUEUE = 'text' + ROUTING_KEY = 'example.text' + + def __init__(self, amqp_url): + """Setup the example publisher object, passing in the URL we will use + to connect to RabbitMQ. + + :param str amqp_url: The URL for connecting to RabbitMQ + + """ + self._connection = None + self._channel = None + + self._deliveries = None + self._acked = None + self._nacked = None + self._message_number = None + + self._stopping = False + self._url = amqp_url + + def connect(self): + """This method connects to RabbitMQ, returning the connection handle. + When the connection is established, the on_connection_open method + will be invoked by pika. If you want the reconnection to work, make + sure you set stop_ioloop_on_close to False, which is not the default + behavior of this adapter. + + :rtype: pika.SelectConnection + + """ + LOGGER.info('Connecting to %s', self._url) + return pika.SelectConnection(pika.URLParameters(self._url), + on_open_callback=self.on_connection_open, + on_close_callback=self.on_connection_closed, + stop_ioloop_on_close=False) + + def on_connection_open(self, unused_connection): + """This method is called by pika once the connection to RabbitMQ has + been established. It passes the handle to the connection object in + case we need it, but in this case, we'll just mark it unused. + + :type unused_connection: pika.SelectConnection + + """ + LOGGER.info('Connection opened') + self.open_channel() + + def on_connection_closed(self, connection, reply_code, reply_text): + """This method is invoked by pika when the connection to RabbitMQ is + closed unexpectedly. Since it is unexpected, we will reconnect to + RabbitMQ if it disconnects. + + :param pika.connection.Connection connection: The closed connection obj + :param int reply_code: The server provided reply_code if given + :param str reply_text: The server provided reply_text if given + + """ + self._channel = None + if self._stopping: + self._connection.ioloop.stop() + else: + LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', + reply_code, reply_text) + self._connection.add_timeout(5, self._connection.ioloop.stop) + + def open_channel(self): + """This method will open a new channel with RabbitMQ by issuing the + Channel.Open RPC command. When RabbitMQ confirms the channel is open + by sending the Channel.OpenOK RPC reply, the on_channel_open method + will be invoked. + + """ + LOGGER.info('Creating a new channel') + self._connection.channel(on_open_callback=self.on_channel_open) + + def on_channel_open(self, channel): + """This method is invoked by pika when the channel has been opened. + The channel object is passed in so we can make use of it. + + Since the channel is now open, we'll declare the exchange to use. + + :param pika.channel.Channel channel: The channel object + + """ + LOGGER.info('Channel opened') + self._channel = channel + self.add_on_channel_close_callback() + self.setup_exchange(self.EXCHANGE) + + def add_on_channel_close_callback(self): + """This method tells pika to call the on_channel_closed method if + RabbitMQ unexpectedly closes the channel. + + """ + LOGGER.info('Adding channel close callback') + self._channel.add_on_close_callback(self.on_channel_closed) + + def on_channel_closed(self, channel, reply_code, reply_text): + """Invoked by pika when RabbitMQ unexpectedly closes the channel. + Channels are usually closed if you attempt to do something that + violates the protocol, such as re-declare an exchange or queue with + different parameters. In this case, we'll close the connection + to shutdown the object. + + :param pika.channel.Channel channel: The closed channel + :param int reply_code: The numeric reason the channel was closed + :param str reply_text: The text reason the channel was closed + + """ + LOGGER.warning('Channel was closed: (%s) %s', reply_code, reply_text) + self._channel = None + if not self._stopping: + self._connection.close() + + def setup_exchange(self, exchange_name): + """Setup the exchange on RabbitMQ by invoking the Exchange.Declare RPC + command. When it is complete, the on_exchange_declareok method will + be invoked by pika. + + :param str|unicode exchange_name: The name of the exchange to declare + + """ + LOGGER.info('Declaring exchange %s', exchange_name) + self._channel.exchange_declare(self.on_exchange_declareok, + exchange_name, + self.EXCHANGE_TYPE) + + def on_exchange_declareok(self, unused_frame): + """Invoked by pika when RabbitMQ has finished the Exchange.Declare RPC + command. + + :param pika.Frame.Method unused_frame: Exchange.DeclareOk response frame + + """ + LOGGER.info('Exchange declared') + self.setup_queue(self.QUEUE) + + def setup_queue(self, queue_name): + """Setup the queue on RabbitMQ by invoking the Queue.Declare RPC + command. When it is complete, the on_queue_declareok method will + be invoked by pika. + + :param str|unicode queue_name: The name of the queue to declare. + + """ + LOGGER.info('Declaring queue %s', queue_name) + self._channel.queue_declare(self.on_queue_declareok, queue_name) + + def on_queue_declareok(self, method_frame): + """Method invoked by pika when the Queue.Declare RPC call made in + setup_queue has completed. In this method we will bind the queue + and exchange together with the routing key by issuing the Queue.Bind + RPC command. When this command is complete, the on_bindok method will + be invoked by pika. + + :param pika.frame.Method method_frame: The Queue.DeclareOk frame + + """ + LOGGER.info('Binding %s to %s with %s', + self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) + self._channel.queue_bind(self.on_bindok, self.QUEUE, + self.EXCHANGE, self.ROUTING_KEY) + + def on_bindok(self, unused_frame): + """This method is invoked by pika when it receives the Queue.BindOk + response from RabbitMQ. Since we know we're now setup and bound, it's + time to start publishing.""" + LOGGER.info('Queue bound') + self.start_publishing() + + def start_publishing(self): + """This method will enable delivery confirmations and schedule the + first message to be sent to RabbitMQ + + """ + LOGGER.info('Issuing consumer related RPC commands') + self.enable_delivery_confirmations() + self.schedule_next_message() + + def enable_delivery_confirmations(self): + """Send the Confirm.Select RPC method to RabbitMQ to enable delivery + confirmations on the channel. The only way to turn this off is to close + the channel and create a new one. + + When the message is confirmed from RabbitMQ, the + on_delivery_confirmation method will be invoked passing in a Basic.Ack + or Basic.Nack method from RabbitMQ that will indicate which messages it + is confirming or rejecting. + + """ + LOGGER.info('Issuing Confirm.Select RPC command') + self._channel.confirm_delivery(self.on_delivery_confirmation) + + def on_delivery_confirmation(self, method_frame): + """Invoked by pika when RabbitMQ responds to a Basic.Publish RPC + command, passing in either a Basic.Ack or Basic.Nack frame with + the delivery tag of the message that was published. The delivery tag + is an integer counter indicating the message number that was sent + on the channel via Basic.Publish. Here we're just doing house keeping + to keep track of stats and remove message numbers that we expect + a delivery confirmation of from the list used to keep track of messages + that are pending confirmation. + + :param pika.frame.Method method_frame: Basic.Ack or Basic.Nack frame + + """ + confirmation_type = method_frame.method.NAME.split('.')[1].lower() + LOGGER.info('Received %s for delivery tag: %i', + confirmation_type, + method_frame.method.delivery_tag) + if confirmation_type == 'ack': + self._acked += 1 + elif confirmation_type == 'nack': + self._nacked += 1 + self._deliveries.remove(method_frame.method.delivery_tag) + LOGGER.info('Published %i messages, %i have yet to be confirmed, ' + '%i were acked and %i were nacked', + self._message_number, len(self._deliveries), + self._acked, self._nacked) + + def schedule_next_message(self): + """If we are not closing our connection to RabbitMQ, schedule another + message to be delivered in PUBLISH_INTERVAL seconds. + + """ + LOGGER.info('Scheduling next message for %0.1f seconds', + self.PUBLISH_INTERVAL) + self._connection.add_timeout(self.PUBLISH_INTERVAL, + self.publish_message) + + def publish_message(self): + """If the class is not stopping, publish a message to RabbitMQ, + appending a list of deliveries with the message number that was sent. + This list will be used to check for delivery confirmations in the + on_delivery_confirmations method. + + Once the message has been sent, schedule another message to be sent. + The main reason I put scheduling in was just so you can get a good idea + of how the process is flowing by slowing down and speeding up the + delivery intervals by changing the PUBLISH_INTERVAL constant in the + class. + + """ + if self._channel is None or not self._channel.is_open: + return + + hdrs = {u'مفتاح': u' قيمة', + u'键': u'值', + u'キー': u'値'} + properties = pika.BasicProperties(app_id='example-publisher', + content_type='application/json', + headers=hdrs) + + message = u'مفتاح قيمة 键 值 キー 値' + self._channel.basic_publish(self.EXCHANGE, self.ROUTING_KEY, + json.dumps(message, ensure_ascii=False), + properties) + self._message_number += 1 + self._deliveries.append(self._message_number) + LOGGER.info('Published message # %i', self._message_number) + self.schedule_next_message() + + def run(self): + """Run the example code by connecting and then starting the IOLoop. + + """ + while not self._stopping: + self._connection = None + self._deliveries = [] + self._acked = 0 + self._nacked = 0 + self._message_number = 0 + + try: + self._connection = self.connect() + self._connection.ioloop.start() + except KeyboardInterrupt: + self.stop() + if (self._connection is not None and + not self._connection.is_closed): + # Finish closing + self._connection.ioloop.start() + + LOGGER.info('Stopped') + + def stop(self): + """Stop the example by closing the channel and connection. We + set a flag here so that we stop scheduling new messages to be + published. The IOLoop is started because this method is + invoked by the Try/Catch below when KeyboardInterrupt is caught. + Starting the IOLoop again will allow the publisher to cleanly + disconnect from RabbitMQ. + + """ + LOGGER.info('Stopping') + self._stopping = True + self.close_channel() + self.close_connection() + + def close_channel(self): + """Invoke this command to close the channel with RabbitMQ by sending + the Channel.Close RPC command. + + """ + if self._channel is not None: + LOGGER.info('Closing the channel') + self._channel.close() + + def close_connection(self): + """This method closes the connection to RabbitMQ.""" + if self._connection is not None: + LOGGER.info('Closing connection') + self._connection.close() + + +def main(): + logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + + # Connect to localhost:5672 as guest with the password guest and virtual host "/" (%2F) + example = ExamplePublisher('amqp://guest:guest@localhost:5672/%2F?connection_attempts=3&heartbeat_interval=3600') + example.run() + + +if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/basic_consumer_threaded.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/basic_consumer_threaded.py new file mode 100644 index 000000000..ba521c171 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/basic_consumer_threaded.py @@ -0,0 +1,68 @@ +import functools +import logging +import pika +import threading +import time + +LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') +LOGGER = logging.getLogger(__name__) + +logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + +def ack_message(channel, delivery_tag): + """Note that `channel` must be the same pika channel instance via which + the message being ACKed was retrieved (AMQP protocol constraint). + """ + if channel.is_open: + channel.basic_ack(delivery_tag) + else: + # Channel is already closed, so we can't ACK this message; + # log and/or do something that makes sense for your app in this case. + pass + +def do_work(connection, channel, delivery_tag, body): + thread_id = threading.get_ident() + fmt1 = 'Thread id: {} Delivery tag: {} Message body: {}' + LOGGER.info(fmt1.format(thread_id, delivery_tag, body)) + # Sleeping to simulate 10 seconds of work + time.sleep(10) + cb = functools.partial(ack_message, channel, delivery_tag) + connection.add_callback_threadsafe(cb) + +def on_message(channel, method_frame, header_frame, body, args): + (connection, threads) = args + delivery_tag = method_frame.delivery_tag + t = threading.Thread(target=do_work, args=(connection, channel, delivery_tag, body)) + t.start() + threads.append(t) + +credentials = pika.PlainCredentials('guest', 'guest') +# Note: sending a short heartbeat to prove that heartbeats are still +# sent even though the worker simulates long-running work +parameters = pika.ConnectionParameters('localhost', credentials=credentials, heartbeat=5) +connection = pika.BlockingConnection(parameters) + +channel = connection.channel() +channel.exchange_declare(exchange="test_exchange", exchange_type="direct", passive=False, durable=True, auto_delete=False) +channel.queue_declare(queue="standard", auto_delete=True) +channel.queue_bind(queue="standard", exchange="test_exchange", routing_key="standard_key") +# Note: prefetch is set to 1 here as an example only and to keep the number of threads created +# to a reasonable amount. In production you will want to test with different prefetch values +# to find which one provides the best performance and usability for your solution +channel.basic_qos(prefetch_count=1) + +threads = [] +on_message_callback = functools.partial(on_message, args=(connection, threads)) +channel.basic_consume(on_message_callback, 'standard') + +try: + channel.start_consuming() +except KeyboardInterrupt: + channel.stop_consuming() + +# Wait for all to complete +for thread in threads: + thread.join() + +connection.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/confirmation.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/confirmation.py new file mode 100644 index 000000000..231147044 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/confirmation.py @@ -0,0 +1,47 @@ +import pika +from pika import spec +import logging + +ITERATIONS = 100 + +logging.basicConfig(level=logging.INFO) + +confirmed = 0 +errors = 0 +published = 0 + +def on_open(connection): + connection.channel(on_channel_open) + + +def on_channel_open(channel): + global published + channel.confirm_delivery(on_delivery_confirmation) + for iteration in xrange(0, ITERATIONS): + channel.basic_publish('test', 'test.confirm', + 'message body value', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + published += 1 + +def on_delivery_confirmation(frame): + global confirmed, errors + if isinstance(frame.method, spec.Basic.Ack): + confirmed += 1 + logging.info('Received confirmation: %r', frame.method) + else: + logging.error('Received negative confirmation: %r', frame.method) + errors += 1 + if (confirmed + errors) == ITERATIONS: + logging.info('All confirmations received, published %i, confirmed %i with %i errors', published, confirmed, errors) + connection.close() + +parameters = pika.URLParameters('amqp://guest:guest@localhost:5672/%2F?connection_attempts=50') +connection = pika.SelectConnection(parameters=parameters, + on_open_callback=on_open) + +try: + connection.ioloop.start() +except KeyboardInterrupt: + connection.close() + connection.ioloop.start() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consume.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consume.py new file mode 100644 index 000000000..26e4620f6 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consume.py @@ -0,0 +1,44 @@ +"""Basic message consumer example""" +import functools +import logging +import pika + +LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' + '-35s %(lineno) -5d: %(message)s') +LOGGER = logging.getLogger(__name__) + +logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + +def on_message(chan, method_frame, _header_frame, body, userdata=None): + """Called when a message is received. Log message and ack it.""" + LOGGER.info('Userdata: %s Message body: %s', userdata, body) + chan.basic_ack(delivery_tag=method_frame.delivery_tag) + +def main(): + """Main method.""" + credentials = pika.PlainCredentials('guest', 'guest') + parameters = pika.ConnectionParameters('localhost', credentials=credentials) + connection = pika.BlockingConnection(parameters) + + channel = connection.channel() + channel.exchange_declare(exchange="test_exchange", + exchange_type="direct", + passive=False, + durable=True, + auto_delete=False) + channel.queue_declare(queue="standard", auto_delete=True) + channel.queue_bind(queue="standard", exchange="test_exchange", routing_key="standard_key") + channel.basic_qos(prefetch_count=1) + + on_message_callback = functools.partial(on_message, userdata='on_message_userdata') + channel.basic_consume(on_message_callback, 'standard') + + try: + channel.start_consuming() + except KeyboardInterrupt: + channel.stop_consuming() + + connection.close() + +if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_queued.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_queued.py new file mode 100644 index 000000000..f0d527f8a --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_queued.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pika +import json +import threading + + +buffer = [] +lock = threading.Lock() + +print('pika version: %s' % pika.__version__) + + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) + +main_channel = connection.channel() +consumer_channel = connection.channel() +bind_channel = connection.channel() + +if pika.__version__=='0.9.5': + main_channel.exchange_declare(exchange='com.micex.sten', type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', type='direct') +else: + main_channel.exchange_declare(exchange='com.micex.sten', exchange_type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', exchange_type='direct') + +queue = main_channel.queue_declare(exclusive=True).method.queue +queue_tickers = main_channel.queue_declare(exclusive=True).method.queue + +main_channel.queue_bind(exchange='com.micex.sten', queue=queue, routing_key='order.stop.create') + + + +def process_buffer(): + if not lock.acquire(False): + print('locked!') + return + try: + while len(buffer): + body = buffer.pop(0) + + ticker = None + if 'ticker' in body['data']['params']['condition']: ticker = body['data']['params']['condition']['ticker'] + if not ticker: continue + + print('got ticker %s, gonna bind it...' % ticker) + bind_channel.queue_bind(exchange='com.micex.lasttrades', queue=queue_tickers, routing_key=str(ticker)) + print('ticker %s binded ok' % ticker) + finally: + lock.release() + + +def callback(ch, method, properties, body): + body = json.loads(body)['order.stop.create'] + buffer.append(body) + process_buffer() + + +consumer_channel.basic_consume(callback, + queue=queue, no_ack=True) + +try: + consumer_channel.start_consuming() +finally: + connection.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_simple.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_simple.py new file mode 100644 index 000000000..6866f7675 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/consumer_simple.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pika +import json + + +print('pika version: %s' % pika.__version__) + + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) + +main_channel = connection.channel() +consumer_channel = connection.channel() +bind_channel = connection.channel() + +if pika.__version__=='0.9.5': + main_channel.exchange_declare(exchange='com.micex.sten', type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', type='direct') +else: + main_channel.exchange_declare(exchange='com.micex.sten', exchange_type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', exchange_type='direct') + +queue = main_channel.queue_declare(exclusive=True).method.queue +queue_tickers = main_channel.queue_declare(exclusive=True).method.queue + +main_channel.queue_bind(exchange='com.micex.sten', queue=queue, routing_key='order.stop.create') + + +def hello(): + print('Hello world') + +connection.add_timeout(5, hello) + + +def callback(ch, method, properties, body): + body = json.loads(body)['order.stop.create'] + + ticker = None + if 'ticker' in body['data']['params']['condition']: ticker = body['data']['params']['condition']['ticker'] + if not ticker: return + + print('got ticker %s, gonna bind it...' % ticker) + bind_channel.queue_bind(exchange='com.micex.lasttrades', queue=queue_tickers, routing_key=str(ticker)) + print('ticker %s binded ok' % ticker) + + +import logging +logging.basicConfig(level=logging.INFO) + +consumer_channel.basic_consume(callback, + queue=queue, no_ack=True) + +try: + consumer_channel.start_consuming() +finally: + connection.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/direct_reply_to.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/direct_reply_to.py new file mode 100644 index 000000000..43173dc1d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/direct_reply_to.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +""" +This example demonstrates RabbitMQ's "Direct reply-to" usage via +`pika.BlockingConnection`. See https://www.rabbitmq.com/direct-reply-to.html +for more info about this feature. +""" + +import pika + + +SERVER_QUEUE = 'rpc.server.queue' + + +def main(): + """ Here, Client sends "Marco" to RPC Server, and RPC Server replies with + "Polo". + + NOTE Normally, the server would be running separately from the client, but + in this very simple example both are running in the same thread and sharing + connection and channel. + + """ + with pika.BlockingConnection() as conn: + channel = conn.channel() + + # Set up server + + channel.queue_declare(queue=SERVER_QUEUE, + exclusive=True, + auto_delete=True) + channel.basic_consume(on_server_rx_rpc_request, queue=SERVER_QUEUE) + + + # Set up client + + # NOTE Client must create its consumer and publish RPC requests on the + # same channel to enable the RabbitMQ broker to make the necessary + # associations. + # + # Also, client must create the consumer *before* starting to publish the + # RPC requests. + # + # Client must create its consumer with no_ack=True, because the reply-to + # queue isn't real. + + channel.basic_consume(on_client_rx_reply_from_server, + queue='amq.rabbitmq.reply-to', + no_ack=True) + channel.basic_publish( + exchange='', + routing_key=SERVER_QUEUE, + body='Marco', + properties=pika.BasicProperties(reply_to='amq.rabbitmq.reply-to')) + + channel.start_consuming() + + +def on_server_rx_rpc_request(ch, method_frame, properties, body): + print 'RPC Server got request:', body + + ch.basic_publish('', routing_key=properties.reply_to, body='Polo') + + ch.basic_ack(delivery_tag=method_frame.delivery_tag) + + print 'RPC Server says good bye' + + +def on_client_rx_reply_from_server(ch, method_frame, properties, body): + print 'RPC Client got reply:', body + + # NOTE A real client might want to make additional RPC requests, but in this + # simple example we're closing the channel after getting our first reply + # to force control to return from channel.start_consuming() + print 'RPC Client says bye' + ch.close() + + +if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/heartbeat_and_blocked_timeouts.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/heartbeat_and_blocked_timeouts.py new file mode 100644 index 000000000..3fe9a9956 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/heartbeat_and_blocked_timeouts.py @@ -0,0 +1,48 @@ +""" +This example demonstrates explicit setting of heartbeat and blocked connection +timeouts. + +Starting with RabbitMQ 3.5.5, the broker's default hearbeat timeout decreased +from 580 seconds to 60 seconds. As a result, applications that perform lengthy +processing in the same thread that also runs their Pika connection may +experience unexpected dropped connections due to heartbeat timeout. Here, we +specify an explicit lower bound for heartbeat timeout. + +When RabbitMQ broker is running out of certain resources, such as memory and +disk space, it may block connections that are performing resource-consuming +operations, such as publishing messages. Once a connection is blocked, RabbiMQ +stops reading from that connection's socket, so no commands from the client will +get through to te broker on that connection until the broker unblocks it. A +blocked connection may last for an indefinite period of time, stalling the +connection and possibly resulting in a hang (e.g., in BlockingConnection) until +the connection is unblocked. Blocked Connectin Timeout is intended to interrupt +(i.e., drop) a connection that has been blocked longer than the given timeout +value. +""" + + +import pika + + +def main(): + + # NOTE: These paramerers work with all Pika connection types + params = pika.ConnectionParameters(heartbeat_interval=600, + blocked_connection_timeout=300) + + conn = pika.BlockingConnection(params) + + chan = conn.channel() + + chan.basic_publish('', 'my-alphabet-queue', "abc") + + # If publish causes the connection to become blocked, then this conn.close() + # would hang until the connection is unblocked, if ever. However, the + # blocked_connection_timeout connection parameter would interrupt the wait, + # resulting in ConnectionClosed exception from BlockingConnection (or the + # on_connection_closed callback call in an asynchronous adapter) + conn.close() + + +if __name__ == '__main__': + main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/producer.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/producer.py new file mode 100644 index 000000000..11bc7e87b --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/producer.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pika +import json +import random + +print('pika version: %s' % pika.__version__) + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) +main_channel = connection.channel() + +if pika.__version__=='0.9.5': + main_channel.exchange_declare(exchange='com.micex.sten', type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', type='direct') +else: + main_channel.exchange_declare(exchange='com.micex.sten', exchange_type='direct') + main_channel.exchange_declare(exchange='com.micex.lasttrades', exchange_type='direct') + +tickers = {'MXSE.EQBR.LKOH': (1933, 1940), + 'MXSE.EQBR.MSNG': (1.35, 1.45), + 'MXSE.EQBR.SBER': (90, 92), + 'MXSE.EQNE.GAZP': (156, 162), + 'MXSE.EQNE.PLZL': (1025, 1040), + 'MXSE.EQNL.VTBR': (0.05, 0.06)} + + +def getticker(): return list(tickers.keys())[random.randrange(0,len(tickers)-1)] + +_COUNT_ = 10 + +for i in range(0,_COUNT_): + ticker = getticker() + msg = {'order.stop.create':{'data':{'params':{'condition':{'ticker':ticker}}}}} + main_channel.basic_publish(exchange='com.micex.sten', + routing_key='order.stop.create', + body=json.dumps(msg), + properties=pika.BasicProperties(content_type='application/json') + ) + print('send ticker %s' % ticker) + +connection.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/publish.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/publish.py new file mode 100644 index 000000000..d31ad9068 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/publish.py @@ -0,0 +1,32 @@ +import pika +import logging + +logging.basicConfig(level=logging.DEBUG) + +credentials = pika.PlainCredentials('guest', 'guest') +parameters = pika.ConnectionParameters('localhost', credentials=credentials) +connection = pika.BlockingConnection(parameters) +channel = connection.channel() +channel.exchange_declare(exchange="test_exchange", exchange_type="direct", + passive=False, durable=True, auto_delete=False) + +print("Sending message to create a queue") +channel.basic_publish('test_exchange', 'standard_key', 'queue:group', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + +connection.sleep(5) + +print("Sending text message to group") +channel.basic_publish('test_exchange', 'group_key', 'Message to group_key', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + +connection.sleep(5) + +print("Sending text message") +channel.basic_publish('test_exchange', 'standard_key', 'Message to standard_key', + pika.BasicProperties(content_type='text/plain', + delivery_mode=1)) + +connection.close() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/send.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/send.py new file mode 100644 index 000000000..57098f8f9 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/send.py @@ -0,0 +1,41 @@ +import pika +import time +import logging + +logging.basicConfig(level=logging.DEBUG) + +ITERATIONS = 100 + +connection = pika.BlockingConnection(pika.URLParameters('amqp://guest:guest@localhost:5672/%2F?heartbeat_interval=1')) +channel = connection.channel() + +def closeit(): + print('Close it') + connection.close() + +connection.add_timeout(5, closeit) + +connection.sleep(100) + +""" +channel.confirm_delivery() +start_time = time.time() + +for x in range(0, ITERATIONS): + if not channel.basic_publish(exchange='test', + routing_key='', + body='Test 123', + properties=pika.BasicProperties(content_type='text/plain', + app_id='test', + delivery_mode=1)): + print('Delivery not confirmed') + else: + print('Confirmed delivery') + +channel.close() +connection.close() + +duration = time.time() - start_time +print("Published %i messages in %.4f seconds (%.2f messages per second)" % (ITERATIONS, duration, (ITERATIONS/duration))) + +""" diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/twisted_service.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/twisted_service.py new file mode 100644 index 000000000..235f1e897 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/examples/twisted_service.py @@ -0,0 +1,209 @@ +""" +# -*- coding:utf-8 -*- +# based on: +# - txamqp-helpers by Dan Siemon (March 2010) +# http://git.coverfire.com/?p=txamqp-twistd.git;a=tree +# - Post by Brian Chandler +# https://groups.google.com/forum/#!topic/pika-python/o_deVmGondk +# - Pika Documentation +# https://pika.readthedocs.io/en/latest/examples/twisted_example.html + + +Fire up this test application via `twistd -ny twisted_service.py` + +The application will answer to requests to exchange "foobar" and any of the +routing_key values: "request1", "request2", or "request3" +with messages to the same exchange, but with routing_key "response" + +When a routing_key of "task" is used on the exchange "foobar", +the application can asynchronously run a maximum of 2 tasks at once +as defined by PREFETCH_COUNT +""" + +import pika +from pika import spec +from pika import exceptions +from pika.adapters import twisted_connection + +from twisted.internet import protocol +from twisted.application import internet +from twisted.application import service +from twisted.internet.defer import inlineCallbacks +from twisted.internet import ssl, defer, task +from twisted.python import log +from twisted.internet import reactor + +PREFETCH_COUNT = 2 + +class PikaService(service.MultiService): + name = 'amqp' + + def __init__(self, parameter): + service.MultiService.__init__(self) + self.parameters = parameter + + def startService(self): + self.connect() + service.MultiService.startService(self) + + def getFactory(self): + if len(self.services) > 0: + return self.services[0].factory + + def connect(self): + f = PikaFactory(self.parameters) + if self.parameters.ssl: + s = ssl.ClientContextFactory() + serv = internet.SSLClient(host=self.parameters.host, port=self.parameters.port, factory=f, contextFactory=s) + else: + serv = internet.TCPClient(host=self.parameters.host, port=self.parameters.port, factory=f) + serv.factory = f + f.service = serv + name = '%s%s:%d' % ('ssl:' if self.parameters.ssl else '', self.parameters.host, self.parameters.port) + serv.__repr__ = lambda : '' % name + serv.setName(name) + serv.parent = self + self.addService(serv) + + +class PikaProtocol(twisted_connection.TwistedProtocolConnection): + connected = False + name = 'AMQP:Protocol' + + @inlineCallbacks + def connected(self, connection): + self.channel = yield connection.channel() + yield self.channel.basic_qos(prefetch_count=PREFETCH_COUNT) + self.connected = True + for (exchange, routing_key, callback,) in self.factory.read_list: + yield self.setup_read(exchange, routing_key, callback) + + self.send() + + @inlineCallbacks + def read(self, exchange, routing_key, callback): + """Add an exchange to the list of exchanges to read from.""" + if self.connected: + yield self.setup_read(exchange, routing_key, callback) + + @inlineCallbacks + def setup_read(self, exchange, routing_key, callback): + """This function does the work to read from an exchange.""" + if not exchange == '': + yield self.channel.exchange_declare(exchange=exchange, exchange_type='topic', durable=True, auto_delete=False) + + yield self.channel.queue_declare(queue=routing_key, durable=True) + yield self.channel.queue_bind(queue=routing_key, exchange=exchange, routing_key=routing_key) + + (queue, consumer_tag,) = yield self.channel.basic_consume(queue=routing_key, no_ack=False) + d = queue.get() + d.addCallback(self._read_item, queue, callback) + d.addErrback(self._read_item_err) + + def _read_item(self, item, queue, callback): + """Callback function which is called when an item is read.""" + d = queue.get() + d.addCallback(self._read_item, queue, callback) + d.addErrback(self._read_item_err) + (channel, deliver, props, msg,) = item + + log.msg('%s (%s): %s' % (deliver.exchange, deliver.routing_key, repr(msg)), system='Pika:<=') + d = defer.maybeDeferred(callback, item) + d.addCallbacks( + lambda _: channel.basic_ack(deliver.delivery_tag), + lambda _: channel.basic_nack(deliver.delivery_tag) + ) + + def _read_item_err(self, error): + print(error) + + def send(self): + """If connected, send all waiting messages.""" + if self.connected: + while len(self.factory.queued_messages) > 0: + (exchange, r_key, message,) = self.factory.queued_messages.pop(0) + self.send_message(exchange, r_key, message) + + @inlineCallbacks + def send_message(self, exchange, routing_key, msg): + """Send a single message.""" + log.msg('%s (%s): %s' % (exchange, routing_key, repr(msg)), system='Pika:=>') + yield self.channel.exchange_declare(exchange=exchange, exchange_type='topic', durable=True, auto_delete=False) + prop = spec.BasicProperties(delivery_mode=2) + try: + yield self.channel.basic_publish(exchange=exchange, routing_key=routing_key, body=msg, properties=prop) + except Exception as error: + log.msg('Error while sending message: %s' % error, system=self.name) + + +class PikaFactory(protocol.ReconnectingClientFactory): + name = 'AMQP:Factory' + + def __init__(self, parameters): + self.parameters = parameters + self.client = None + self.queued_messages = [] + self.read_list = [] + + def startedConnecting(self, connector): + log.msg('Started to connect.', system=self.name) + + def buildProtocol(self, addr): + self.resetDelay() + log.msg('Connected', system=self.name) + self.client = PikaProtocol(self.parameters) + self.client.factory = self + self.client.ready.addCallback(self.client.connected) + return self.client + + def clientConnectionLost(self, connector, reason): + log.msg('Lost connection. Reason: %s' % reason, system=self.name) + protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) + + def clientConnectionFailed(self, connector, reason): + log.msg('Connection failed. Reason: %s' % reason, system=self.name) + protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) + + def send_message(self, exchange = None, routing_key = None, message = None): + self.queued_messages.append((exchange, routing_key, message)) + if self.client is not None: + self.client.send() + + def read_messages(self, exchange, routing_key, callback): + """Configure an exchange to be read from.""" + self.read_list.append((exchange, routing_key, callback)) + if self.client is not None: + self.client.read(exchange, routing_key, callback) + + +application = service.Application("pikaapplication") + +ps = PikaService(pika.ConnectionParameters(host="localhost", virtual_host="/", credentials=pika.PlainCredentials("guest", "guest"))) +ps.setServiceParent(application) + + +class TestService(service.Service): + + def task(self, msg): + """ + Method for a time consuming task. + + This function must return a deferred. If it is successfull, + a `basic.ack` will be sent to AMQP. If the task was not completed a + `basic.nack` will be sent. In this example it will always return + successfully after a 2 second pause. + """ + return task.deferLater(reactor, 2, lambda: log.msg("task completed")) + + def respond(self, msg): + self.amqp.send_message('foobar', 'response', msg[3]) + + def startService(self): + self.amqp = self.parent.getServiceNamed("amqp").getFactory() + self.amqp.read_messages("foobar", "request1", self.respond) + self.amqp.read_messages("foobar", "request2", self.respond) + self.amqp.read_messages("foobar", "request3", self.respond) + self.amqp.read_messages("foobar", "task", self.task) + +ts = TestService() +ts.setServiceParent(application) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/__init__.py new file mode 100644 index 000000000..f21dc8b67 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/__init__.py @@ -0,0 +1,17 @@ +__version__ = '0.13.1' + +import logging +from logging import NullHandler + +# Add NullHandler to prevent logging warnings +logging.getLogger(__name__).addHandler(NullHandler()) + +from pika.connection import ConnectionParameters +from pika.connection import URLParameters +from pika.connection import SSLOptions +from pika.credentials import PlainCredentials +from pika.spec import BasicProperties + +from pika.adapters import BaseConnection +from pika.adapters import BlockingConnection +from pika.adapters import SelectConnection diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/__init__.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/__init__.py new file mode 100644 index 000000000..236a98246 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/__init__.py @@ -0,0 +1,21 @@ +""" +Connection Adapters +=================== + +Pika provides multiple adapters to connect to RabbitMQ: + +- adapters.asyncio_connection.AsyncioConnection: Native Python3 AsyncIO use +- adapters.blocking_connection.BlockingConnection: Enables blocking, + synchronous operation on top of library for simple uses. +- adapters.select_connection.SelectConnection: A native event based connection + adapter that implements select, kqueue, poll and epoll. +- adapters.tornado_connection.TornadoConnection: Connection adapter for use + with the Tornado web framework. +- adapters.twisted_connection.TwistedConnection: Connection adapter for use + with the Twisted framework + +""" +from pika.adapters.base_connection import BaseConnection +from pika.adapters.blocking_connection import BlockingConnection +from pika.adapters.select_connection import SelectConnection +from pika.adapters.select_connection import IOLoop diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/asyncio_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/asyncio_connection.py new file mode 100644 index 000000000..57cb2d443 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/asyncio_connection.py @@ -0,0 +1,254 @@ +"""Use pika with the Asyncio EventLoop""" +import asyncio +from functools import partial + +from pika.adapters import base_connection + + +class IOLoopAdapter: + def __init__(self, loop): + """ + Basic adapter for asyncio event loop + + :type loop: asyncio.AbstractEventLoop + :param loop: Asyncio Loop + + """ + self.loop = loop + + self.handlers = {} + self.readers = set() + self.writers = set() + + def close(self): + """Release ioloop's resources. + + This method is intended to be called by the application or test code + only after the ioloop's outermost `start()` call returns. After calling + `close()`, no other interaction with the closed instance of ioloop + should be performed. + + """ + self.loop.close() + + def add_timeout(self, deadline, callback_method): + """Add the callback_method to the EventLoop timer to fire after deadline + seconds. Returns a Handle to the timeout. + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + :rtype: asyncio.Handle + + """ + return self.loop.call_later(deadline, callback_method) + + @staticmethod + def remove_timeout(handle): + """ + Cancel asyncio.Handle + + :type handle: asyncio.Handle + :rtype: bool + """ + return handle.cancel() + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this IOLoop's thread. + + NOTE: This is the only thread-safe method offered by the IOLoop adapter. + All other manipulations of the IOLoop adapter and its parent connection + must be performed from the connection's thread. + + For example, a thread may request a call to the + `channel.basic_ack` method of a connection that is running in a + different thread via + + ``` + connection.add_callback_threadsafe( + functools.partial(channel.basic_ack, delivery_tag=...)) + ``` + + :param method callback: The callback method; must be callable. + + """ + self.loop.call_soon_threadsafe(callback) + + def add_handler(self, fd, cb, event_state): + """ Registers the given handler to receive the given events for ``fd``. + + The ``fd`` argument is an integer file descriptor. + + The ``event_state`` argument is a bitwise or of the constants + ``base_connection.BaseConnection.READ``, ``base_connection.BaseConnection.WRITE``, + and ``base_connection.BaseConnection.ERROR``. + + """ + + if fd in self.handlers: + raise ValueError("fd {} added twice".format(fd)) + self.handlers[fd] = cb + + if event_state & base_connection.BaseConnection.READ: + self.loop.add_reader( + fd, + partial( + cb, + fd=fd, + events=base_connection.BaseConnection.READ + ) + ) + self.readers.add(fd) + + if event_state & base_connection.BaseConnection.WRITE: + self.loop.add_writer( + fd, + partial( + cb, + fd=fd, + events=base_connection.BaseConnection.WRITE + ) + ) + self.writers.add(fd) + + def remove_handler(self, fd): + """ Stop listening for events on ``fd``. """ + + if fd not in self.handlers: + return + + if fd in self.readers: + self.loop.remove_reader(fd) + self.readers.remove(fd) + + if fd in self.writers: + self.loop.remove_writer(fd) + self.writers.remove(fd) + + del self.handlers[fd] + + def update_handler(self, fd, event_state): + if event_state & base_connection.BaseConnection.READ: + if fd not in self.readers: + self.loop.add_reader( + fd, + partial( + self.handlers[fd], + fd=fd, + events=base_connection.BaseConnection.READ + ) + ) + self.readers.add(fd) + else: + if fd in self.readers: + self.loop.remove_reader(fd) + self.readers.remove(fd) + + if event_state & base_connection.BaseConnection.WRITE: + if fd not in self.writers: + self.loop.add_writer( + fd, + partial( + self.handlers[fd], + fd=fd, + events=base_connection.BaseConnection.WRITE + ) + ) + self.writers.add(fd) + else: + if fd in self.writers: + self.loop.remove_writer(fd) + self.writers.remove(fd) + + + def start(self): + """ Start Event Loop """ + if self.loop.is_running(): + return + + self.loop.run_forever() + + def stop(self): + """ Stop Event Loop """ + if self.loop.is_closed(): + return + + self.loop.stop() + + +class AsyncioConnection(base_connection.BaseConnection): + """ The AsyncioConnection runs on the Asyncio EventLoop. + + :param pika.connection.Parameters parameters: Connection parameters + :param on_open_callback: The method to call when the connection is open + :type on_open_callback: method + :param on_open_error_callback: Method to call if the connection cant be opened + :type on_open_error_callback: method + :param asyncio.AbstractEventLoop loop: By default asyncio.get_event_loop() + + """ + def __init__(self, + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None, + stop_ioloop_on_close=False, + custom_ioloop=None): + """ Create a new instance of the AsyncioConnection class, connecting + to RabbitMQ automatically + + :param pika.connection.Parameters parameters: Connection parameters + :param on_open_callback: The method to call when the connection is open + :type on_open_callback: method + :param on_open_error_callback: Method to call if the connection cant be opened + :type on_open_error_callback: method + :param asyncio.AbstractEventLoop loop: By default asyncio.get_event_loop() + + """ + self.sleep_counter = 0 + self.loop = custom_ioloop or asyncio.get_event_loop() + self.ioloop = IOLoopAdapter(self.loop) + + super().__init__( + parameters, on_open_callback, + on_open_error_callback, + on_close_callback, self.ioloop, + stop_ioloop_on_close=stop_ioloop_on_close, + ) + + def _adapter_connect(self): + """Connect to the remote socket, adding the socket to the EventLoop if + connected. + + :rtype: bool + + """ + error = super()._adapter_connect() + + if not error: + self.ioloop.add_handler( + self.socket.fileno(), + self._handle_events, + self.event_state, + ) + + return error + + def _adapter_disconnect(self): + """Disconnect from the RabbitMQ broker""" + + if self.socket: + self.ioloop.remove_handler( + self.socket.fileno() + ) + + super()._adapter_disconnect() + + def _handle_disconnect(self): + # No other way to handle exceptions.ProbableAuthenticationError + try: + super()._handle_disconnect() + super()._handle_write() + except Exception as e: + # FIXME: Pass None or other constant instead "-1" + self._on_disconnect(-1, e) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/base_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/base_connection.py new file mode 100644 index 000000000..60078e5e3 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/base_connection.py @@ -0,0 +1,582 @@ +"""Base class extended by connection adapters. This extends the +connection.Connection class to encapsulate connection behavior but still +isolate socket and low level communication. + +""" +import errno +import logging +import socket +import ssl + +import pika.compat +import pika.tcp_socket_opts + +from pika import __version__ +from pika import connection +from pika.compat import SOCKET_ERROR +from pika.compat import SOL_TCP + +LOGGER = logging.getLogger(__name__) + + +class BaseConnection(connection.Connection): + """BaseConnection class that should be extended by connection adapters""" + + # Use epoll's constants to keep life easy + READ = 0x0001 + WRITE = 0x0004 + ERROR = 0x0008 + + ERRORS_TO_ABORT = [ + errno.EBADF, errno.ECONNABORTED, errno.EPIPE, errno.ETIMEDOUT + ] + ERRORS_TO_IGNORE = [errno.EWOULDBLOCK, errno.EAGAIN, errno.EINTR] + DO_HANDSHAKE = True + WARN_ABOUT_IOLOOP = False + + def __init__(self, + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None, + ioloop=None, + stop_ioloop_on_close=True): + """Create a new instance of the Connection object. + + :param pika.connection.Parameters parameters: Connection parameters + :param method on_open_callback: Method to call on connection open + :param method on_open_error_callback: Called if the connection can't + be established: on_open_error_callback(connection, str|exception) + :param method on_close_callback: Called when the connection is closed: + on_close_callback(connection, reason_code, reason_text) + :param object ioloop: IOLoop object to use + :param bool stop_ioloop_on_close: Call ioloop.stop() if disconnected + :raises: RuntimeError + :raises: ValueError + + """ + if parameters and not isinstance(parameters, connection.Parameters): + raise ValueError( + 'Expected instance of Parameters, not %r' % (parameters,)) + + # Let the developer know we could not import SSL + if parameters and parameters.ssl and not ssl: + raise RuntimeError("SSL specified but it is not available") + self.base_events = self.READ | self.ERROR + self.event_state = self.base_events + self.ioloop = ioloop + self.socket = None + self.stop_ioloop_on_close = stop_ioloop_on_close + self.write_buffer = None + super(BaseConnection, + self).__init__(parameters, on_open_callback, + on_open_error_callback, on_close_callback) + + def __repr__(self): + + def get_socket_repr(sock): + """Return socket info suitable for use in repr""" + if sock is None: + return None + + sockname = None + peername = None + try: + sockname = sock.getsockname() + except SOCKET_ERROR: + # closed? + pass + else: + try: + peername = sock.getpeername() + except SOCKET_ERROR: + # not connected? + pass + + return '%s->%s' % (sockname, peername) + + return ('<%s %s socket=%s params=%s>' % + (self.__class__.__name__, + self._STATE_NAMES[self.connection_state], + get_socket_repr(self.socket), self.params)) + + def add_timeout(self, deadline, callback_method): + """Add the callback_method to the IOLoop timer to fire after deadline + seconds. Returns a handle to the timeout + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + :rtype: str + + """ + return self.ioloop.add_timeout(deadline, callback_method) + + def close(self, reply_code=200, reply_text='Normal shutdown'): + """Disconnect from RabbitMQ. If there are any open channels, it will + attempt to close them prior to fully disconnecting. Channels which + have active consumers will attempt to send a Basic.Cancel to RabbitMQ + to cleanly stop the delivery of messages prior to closing the channel. + + :param int reply_code: The code number for the close + :param str reply_text: The text reason for the close + + """ + try: + super(BaseConnection, self).close(reply_code, reply_text) + finally: + if self.is_closed: + self._handle_ioloop_stop() + + def remove_timeout(self, timeout_id): + """Remove the timeout from the IOLoop by the ID returned from + add_timeout. + + :rtype: str + + """ + self.ioloop.remove_timeout(timeout_id) + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this connection's IOLoop thread. + + NOTE: This is the only thread-safe method offered by the connection. All + other manipulations of the connection must be performed from the + connection's thread. + + For example, a thread may request a call to the + `channel.basic_ack` method of a connection that is running in a + different thread via + + ``` + connection.add_callback_threadsafe( + functools.partial(channel.basic_ack, delivery_tag=...)) + ``` + + :param method callback: The callback method; must be callable. + + """ + if not callable(callback): + raise TypeError( + 'callback must be a callable, but got %r' % (callback,)) + + self.ioloop.add_callback_threadsafe(callback) + + def _adapter_connect(self): + """Connect to the RabbitMQ broker, returning True if connected. + + :returns: error string or exception instance on error; None on success + + """ + # Get the addresses for the socket, supporting IPv4 & IPv6 + while True: + try: + addresses = self._getaddrinfo( + self.params.host, self.params.port, 0, socket.SOCK_STREAM, + socket.IPPROTO_TCP) + break + except SOCKET_ERROR as error: + if error.errno == errno.EINTR: + continue + + LOGGER.critical('Could not get addresses to use: %s (%s)', + error, self.params.host) + return error + + # If the socket is created and connected, continue on + error = "No socket addresses available" + for sock_addr in addresses: + error = self._create_and_connect_to_socket(sock_addr) + if not error: + # Make the socket non-blocking after the connect + self.socket.setblocking(0) + return None + self._cleanup_socket() + + # Failed to connect + return error + + def _adapter_disconnect(self): + """Invoked if the connection is being told to disconnect""" + try: + self._cleanup_socket() + finally: + self._handle_ioloop_stop() + + def _cleanup_socket(self): + """Close the socket cleanly""" + if self.socket: + try: + self.socket.shutdown(socket.SHUT_RDWR) + except SOCKET_ERROR: + pass + self.socket.close() + self.socket = None + + def _create_and_connect_to_socket(self, sock_addr_tuple): + """Create socket and connect to it, using SSL if enabled. + + :returns: error string on failure; None on success + """ + self.socket = self._create_tcp_connection_socket( + sock_addr_tuple[0], sock_addr_tuple[1], sock_addr_tuple[2]) + self.socket.setsockopt(SOL_TCP, socket.TCP_NODELAY, 1) + self.socket.settimeout(self.params.socket_timeout) + pika.tcp_socket_opts.set_sock_opts(self.params.tcp_options, self.socket) + + # Wrap socket if using SSL + if self.params.ssl: + self.socket = self._wrap_socket(self.socket) + ssl_text = " with SSL" + else: + ssl_text = "" + + LOGGER.info('Pika version %s connecting to %s:%s%s', + __version__, + sock_addr_tuple[4][0], + sock_addr_tuple[4][1], ssl_text) + + # Connect to the socket + try: + self.socket.connect(sock_addr_tuple[4]) + except socket.timeout: + error = 'Connection to %s:%s failed: timeout' % ( + sock_addr_tuple[4][0], sock_addr_tuple[4][1]) + LOGGER.error(error) + return error + except SOCKET_ERROR as error: + error = 'Connection to %s:%s failed: %s' % (sock_addr_tuple[4][0], + sock_addr_tuple[4][1], + error) + LOGGER.error(error) + return error + + # Handle SSL Connection Negotiation + if self.params.ssl and self.DO_HANDSHAKE: + try: + self._do_ssl_handshake() + except ssl.SSLError as error: + error = 'SSL connection to %s:%s failed: %s' % ( + sock_addr_tuple[4][0], sock_addr_tuple[4][1], error) + LOGGER.error(error) + return error + # Made it this far + return None + + @staticmethod + def _create_tcp_connection_socket(sock_family, sock_type, sock_proto): + """ Create TCP/IP stream socket for AMQP connection + + :param int sock_family: socket family + :param int sock_type: socket type + :param int sock_proto: socket protocol number + + NOTE We break this out to make it easier to patch in mock tests + """ + return socket.socket(sock_family, sock_type, sock_proto) + + def _do_ssl_handshake(self): + """Perform SSL handshaking, copied from python stdlib test_ssl.py. + + """ + if not self.DO_HANDSHAKE: + return + while True: + try: + self.socket.do_handshake() + break + # TODO should be using SSLWantReadError, etc. directly + except ssl.SSLError as err: + # TODO these exc are for non-blocking sockets, but ours isn't + # at this stage, so it's not clear why we have this. + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + self.event_state = self.READ + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + self.event_state = self.WRITE + else: + raise + self._manage_event_state() + + @staticmethod + def _getaddrinfo(host, port, family, socktype, proto): + """Wrap `socket.getaddrinfo` to make it easier to patch for unit tests + """ + return socket.getaddrinfo(host, port, family, socktype, proto) + + @staticmethod + def _get_error_code(error_value): + """Get the error code from the error_value accounting for Python + version differences. + + :rtype: int + + """ + if not error_value: + return None + + return error_value.errno + + def _flush_outbound(self): + """Have the state manager schedule the necessary I/O. + """ + # NOTE: We don't call _handle_write() from this context, because pika + # code was not designed to be writing to (or reading from) the socket + # from any methods, except from ioloop handler callbacks. Many methods + # in pika core and adapters do not deal gracefully with connection + # errors occurring in their context; e.g., Connection.channel (pika + # issue #659), Connection._on_connection_tune (if connection loss is + # detected in _send_connection_tune_ok, before _send_connection_open is + # called), etc., etc., etc. + self._manage_event_state() + + def _handle_ioloop_stop(self): + """Invoked when the connection is closed to determine if the IOLoop + should be stopped or not. + + """ + if self.stop_ioloop_on_close and self.ioloop: + self.ioloop.stop() + elif self.WARN_ABOUT_IOLOOP: + LOGGER.warning('Connection is closed but not stopping IOLoop') + + def _handle_error(self, error_value): + """Internal error handling method. Here we expect a socket error + coming in and will handle different socket errors differently. + + :param int|object error_value: The inbound error + + """ + # TODO doesn't seem right: docstring defines error_value as int|object, + # but _get_error_code expects a falsie or an exception-like object + error_code = self._get_error_code(error_value) + + if not error_code: + LOGGER.critical("Tried to handle an error where no error existed") + return + + # Ok errors, just continue what we were doing before + if error_code in self.ERRORS_TO_IGNORE: + LOGGER.debug("Ignoring %s", error_code) + return + + # Socket is no longer connected, abort + elif error_code in self.ERRORS_TO_ABORT: + LOGGER.error("Fatal Socket Error: %r", error_value) + + elif self.params.ssl and isinstance(error_value, ssl.SSLError): + + if error_value.args[0] == ssl.SSL_ERROR_WANT_READ: + # TODO doesn't seem right: this logic updates event state, but + # the logic at the bottom unconditionaly disconnects anyway. + self.event_state = self.READ + elif error_value.args[0] == ssl.SSL_ERROR_WANT_WRITE: + self.event_state = self.WRITE + else: + LOGGER.error("SSL Socket error: %r", error_value) + + else: + # Haven't run into this one yet, log it. + LOGGER.error("Socket Error: %s", error_code) + + # Disconnect from our IOLoop and let Connection know what's up + self._on_terminate(connection.InternalCloseReasons.SOCKET_ERROR, + repr(error_value)) + + def _handle_timeout(self): + """Handle a socket timeout in read or write. + We don't do anything in the non-blocking handlers because we + only have the socket in a blocking state during connect.""" + LOGGER.warning("Unexpected socket timeout") + + def _handle_events(self, fd, events, error=None, write_only=False): + """Handle IO/Event loop events, processing them. + + :param int fd: The file descriptor for the events + :param int events: Events from the IO/Event loop + :param int error: Was an error specified; TODO none of the current + adapters appear to be able to pass the `error` arg - is it needed? + :param bool write_only: Only handle write events + + """ + if not self.socket: + LOGGER.error('Received events on closed socket: %r', fd) + return + + if self.socket and (events & self.WRITE): + self._handle_write() + self._manage_event_state() + + if self.socket and not write_only and (events & self.READ): + self._handle_read() + + if (self.socket and write_only and (events & self.READ) and + (events & self.ERROR)): + error_msg = ('BAD libc: Write-Only but Read+Error. ' + 'Assume socket disconnected.') + LOGGER.error(error_msg) + self._on_terminate(connection.InternalCloseReasons.SOCKET_ERROR, + error_msg) + + if self.socket and (events & self.ERROR): + LOGGER.error('Error event %r, %r', events, error) + self._handle_error(error) + + def _handle_read(self): + """Read from the socket and call our on_data_available with the data.""" + try: + while True: + try: + if self.params.ssl: + data = self.socket.read(self._buffer_size) + else: + data = self.socket.recv(self._buffer_size) + + break + except SOCKET_ERROR as error: + if error.errno == errno.EINTR: + continue + else: + raise + + except socket.timeout: + self._handle_timeout() + return 0 + + except ssl.SSLError as error: + if error.args[0] == ssl.SSL_ERROR_WANT_READ: + # ssl wants more data but there is nothing currently + # available in the socket, wait for it to become readable. + return 0 + return self._handle_error(error) + + except SOCKET_ERROR as error: + if error.errno in (errno.EAGAIN, errno.EWOULDBLOCK): + return 0 + return self._handle_error(error) + + # Empty data, should disconnect + if not data or data == 0: + LOGGER.error('Read empty data, calling disconnect') + return self._on_terminate( + connection.InternalCloseReasons.SOCKET_ERROR, "EOF") + + # Pass the data into our top level frame dispatching method + self._on_data_available(data) + return len(data) + + def _handle_write(self): + """Try and write as much as we can, if we get blocked requeue + what's left""" + total_bytes_sent = 0 + try: + while self.outbound_buffer: + frame = self.outbound_buffer.popleft() + while True: + try: + num_bytes_sent = self.socket.send(frame) + break + except SOCKET_ERROR as error: + if error.errno == errno.EINTR: + continue + else: + raise + + total_bytes_sent += num_bytes_sent + if num_bytes_sent < len(frame): + LOGGER.debug("Partial write, requeing remaining data") + self.outbound_buffer.appendleft(frame[num_bytes_sent:]) + break + + except socket.timeout: + # Will only come here if the socket is blocking + LOGGER.debug("socket timeout, requeuing frame") + self.outbound_buffer.appendleft(frame) + self._handle_timeout() + + except ssl.SSLError as error: + if error.args[0] == ssl.SSL_ERROR_WANT_WRITE: + # In Python 3.5+, SSLSocket.send raises this if the socket is + # not currently able to write. Handle this just like an + # EWOULDBLOCK socket error. + LOGGER.debug("Would block, requeuing frame") + self.outbound_buffer.appendleft(frame) + else: + return self._handle_error(error) + + except SOCKET_ERROR as error: + if error.errno in (errno.EAGAIN, errno.EWOULDBLOCK): + LOGGER.debug("Would block, requeuing frame") + self.outbound_buffer.appendleft(frame) + else: + return self._handle_error(error) + + return total_bytes_sent + + def _init_connection_state(self): + """Initialize or reset all of our internal state variables for a given + connection. If we disconnect and reconnect, all of our state needs to + be wiped. + + """ + super(BaseConnection, self)._init_connection_state() + self.base_events = self.READ | self.ERROR + self.event_state = self.base_events + self.socket = None + + def _manage_event_state(self): + """Manage the bitmask for reading/writing/error which is used by the + io/event handler to specify when there is an event such as a read or + write. + + """ + if self.outbound_buffer: + if not self.event_state & self.WRITE: + self.event_state |= self.WRITE + self.ioloop.update_handler(self.socket.fileno(), + self.event_state) + elif self.event_state & self.WRITE: + self.event_state = self.base_events + self.ioloop.update_handler(self.socket.fileno(), self.event_state) + + def _wrap_socket(self, sock): + """Wrap the socket for connecting over SSL. This allows the user to use + a dict for the usual SSL options or an SSLOptions object for more + advanced control. + + :rtype: ssl.SSLSocket + + """ + ssl_options = self.params.ssl_options or {} + # our wrapped return sock + ssl_sock = None + + if isinstance(ssl_options, connection.SSLOptions): + context = ssl.SSLContext(ssl_options.ssl_version) + context.verify_mode = ssl_options.verify_mode + if ssl_options.certfile is not None: + context.load_cert_chain( + certfile=ssl_options.certfile, + keyfile=ssl_options.keyfile, + password=ssl_options.key_password) + + # only one of either cafile or capath have to defined + if ssl_options.cafile is not None or ssl_options.capath is not None: + context.load_verify_locations( + cafile=ssl_options.cafile, + capath=ssl_options.capath, + cadata=ssl_options.cadata) + + if ssl_options.ciphers is not None: + context.set_ciphers(ssl_options.ciphers) + + ssl_sock = context.wrap_socket( + sock, + server_side=ssl_options.server_side, + do_handshake_on_connect=ssl_options.do_handshake_on_connect, + suppress_ragged_eofs=ssl_options.suppress_ragged_eofs, + server_hostname=ssl_options.server_hostname) + else: + ssl_sock = ssl.wrap_socket( + sock, do_handshake_on_connect=self.DO_HANDSHAKE, **ssl_options) + + return ssl_sock diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/blocking_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/blocking_connection.py new file mode 100644 index 000000000..9acf9c927 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/blocking_connection.py @@ -0,0 +1,2616 @@ +"""The blocking connection adapter module implements blocking semantics on top +of Pika's core AMQP driver. While most of the asynchronous expectations are +removed when using the blocking connection adapter, it attempts to remain true +to the asynchronous RPC nature of the AMQP protocol, supporting server sent +RPC commands. + +The user facing classes in the module consist of the +:py:class:`~pika.adapters.blocking_connection.BlockingConnection` +and the :class:`~pika.adapters.blocking_connection.BlockingChannel` +classes. + +""" +# Suppress too-many-lines +# pylint: disable=C0302 + +# Disable "access to protected member warnings: this wrapper implementation is +# a friend of those instances +# pylint: disable=W0212 + +from collections import namedtuple, deque +import contextlib +import functools +import logging +import time + +import pika.channel +from pika import compat +from pika import exceptions +import pika.spec +# NOTE: import SelectConnection after others to avoid circular depenency +from pika.adapters.select_connection import SelectConnection + + +LOGGER = logging.getLogger(__name__) + + +class _CallbackResult(object): + """ CallbackResult is a non-thread-safe implementation for receiving + callback results; INTERNAL USE ONLY! + """ + __slots__ = ('_value_class', '_ready', '_values') + def __init__(self, value_class=None): + """ + :param callable value_class: only needed if the CallbackResult + instance will be used with + `set_value_once` and `append_element`. + *args and **kwargs of the value setter + methods will be passed to this class. + + """ + self._value_class = value_class + self._ready = None + self._values = None + self.reset() + + def reset(self): + """Reset value, but not _value_class""" + self._ready = False + self._values = None + + def __bool__(self): + """ Called by python runtime to implement truth value testing and the + built-in operation bool(); NOTE: python 3.x + """ + return self.is_ready() + + # python 2.x version of __bool__ + __nonzero__ = __bool__ + + def __enter__(self): + """ Entry into context manager that automatically resets the object + on exit; this usage pattern helps garbage-collection by eliminating + potential circular references. + """ + return self + + def __exit__(self, *args, **kwargs): + """Reset value""" + self.reset() + + def is_ready(self): + """ + :returns: True if the object is in a signaled state + """ + return self._ready + + @property + def ready(self): + """True if the object is in a signaled state""" + return self._ready + + def signal_once(self, *_args, **_kwargs): + """ Set as ready + + :raises AssertionError: if result was already signalled + """ + assert not self._ready, '_CallbackResult was already set' + self._ready = True + + def set_value_once(self, *args, **kwargs): + """ Set as ready with value; the value may be retrieved via the `value` + property getter + + :raises AssertionError: if result was already set + """ + self.signal_once() + try: + self._values = (self._value_class(*args, **kwargs),) + except Exception: + LOGGER.error( + "set_value_once failed: value_class=%r; args=%r; kwargs=%r", + self._value_class, args, kwargs) + raise + + def append_element(self, *args, **kwargs): + """Append an element to values""" + assert not self._ready or isinstance(self._values, list), ( + '_CallbackResult state is incompatible with append_element: ' + 'ready=%r; values=%r' % (self._ready, self._values)) + + try: + value = self._value_class(*args, **kwargs) + except Exception: + LOGGER.error( + "append_element failed: value_class=%r; args=%r; kwargs=%r", + self._value_class, args, kwargs) + raise + + if self._values is None: + self._values = [value] + else: + self._values.append(value) + + self._ready = True + + + @property + def value(self): + """ + :returns: a reference to the value that was set via `set_value_once` + :raises AssertionError: if result was not set or value is incompatible + with `set_value_once` + """ + assert self._ready, '_CallbackResult was not set' + assert isinstance(self._values, tuple) and len(self._values) == 1, ( + '_CallbackResult value is incompatible with set_value_once: %r' + % (self._values,)) + + return self._values[0] + + + @property + def elements(self): + """ + :returns: a reference to the list containing one or more elements that + were added via `append_element` + :raises AssertionError: if result was not set or value is incompatible + with `append_element` + """ + assert self._ready, '_CallbackResult was not set' + assert isinstance(self._values, list) and self._values, ( + '_CallbackResult value is incompatible with append_element: %r' + % (self._values,)) + + return self._values + + +class _IoloopTimerContext(object): + """Context manager for registering and safely unregistering a + SelectConnection ioloop-based timer + """ + + def __init__(self, duration, connection): + """ + :param float duration: non-negative timer duration in seconds + :param SelectConnection connection: + """ + assert hasattr(connection, 'add_timeout'), connection + self._duration = duration + self._connection = connection + self._callback_result = _CallbackResult() + self._timer_id = None + + def __enter__(self): + """Register a timer""" + self._timer_id = self._connection.add_timeout( + self._duration, + self._callback_result.signal_once) + return self + + def __exit__(self, *_args, **_kwargs): + """Unregister timer if it hasn't fired yet""" + if not self._callback_result: + self._connection.remove_timeout(self._timer_id) + + def is_ready(self): + """ + :returns: True if timer has fired, False otherwise + """ + return self._callback_result.is_ready() + + +class _TimerEvt(object): + """Represents a timer created via `BlockingConnection.add_timeout`""" + __slots__ = ('timer_id', '_callback') + + def __init__(self, callback): + """ + :param callback: see callback_method in `BlockingConnection.add_timeout` + """ + self._callback = callback + + # Will be set to timer id returned from the underlying implementation's + # `add_timeout` method + self.timer_id = None + + def __repr__(self): + return '<%s timer_id=%s callback=%s>' % (self.__class__.__name__, + self.timer_id, self._callback) + + def dispatch(self): + """Dispatch the user's callback method""" + self._callback() + + +class _ConnectionBlockedUnblockedEvtBase(object): + """Base class for `_ConnectionBlockedEvt` and `_ConnectionUnblockedEvt`""" + __slots__ = ('_callback', '_method_frame') + + def __init__(self, callback, method_frame): + """ + :param callback: see callback_method parameter in + `BlockingConnection.add_on_connection_blocked_callback` and + `BlockingConnection.add_on_connection_unblocked_callback` + :param pika.frame.Method method_frame: with method_frame.method of type + `pika.spec.Connection.Blocked` or `pika.spec.Connection.Unblocked` + """ + self._callback = callback + self._method_frame = method_frame + + def __repr__(self): + return '<%s callback=%s, frame=%s>' % (self.__class__.__name__, + self._callback, + self._method_frame) + + def dispatch(self): + """Dispatch the user's callback method""" + self._callback(self._method_frame) + + +class _ConnectionBlockedEvt(_ConnectionBlockedUnblockedEvtBase): + """Represents a Connection.Blocked notification from RabbitMQ broker`""" + pass + + +class _ConnectionUnblockedEvt(_ConnectionBlockedUnblockedEvtBase): + """Represents a Connection.Unblocked notification from RabbitMQ broker`""" + pass + + +class BlockingConnection(object): + """The BlockingConnection creates a layer on top of Pika's asynchronous core + providing methods that will block until their expected response has + returned. Due to the asynchronous nature of the `Basic.Deliver` and + `Basic.Return` calls from RabbitMQ to your application, you can still + implement continuation-passing style asynchronous methods if you'd like to + receive messages from RabbitMQ using + :meth:`basic_consume ` or if you want to be + notified of a delivery failure when using + :meth:`basic_publish `. + + For more information about communicating with the blocking_connection + adapter, be sure to check out the + :class:`BlockingChannel ` class which implements the + :class:`Channel ` based communication for the + blocking_connection adapter. + + To prevent recursion/reentrancy, the blocking connection and channel + implementations queue asynchronously-delivered events received + in nested context (e.g., while waiting for `BlockingConnection.channel` or + `BlockingChannel.queue_declare` to complete), dispatching them synchronously + once nesting returns to the desired context. This concerns all callbacks, + such as those registered via `BlockingConnection.add_timeout`, + `BlockingConnection.add_on_connection_blocked_callback`, + `BlockingConnection.add_on_connection_unblocked_callback`, + `BlockingChannel.basic_consume`, etc. + + Blocked Connection deadlock avoidance: when RabbitMQ becomes low on + resources, it emits Connection.Blocked (AMQP extension) to the client + connection when client makes a resource-consuming request on that connection + or its channel (e.g., `Basic.Publish`); subsequently, RabbitMQ suspsends + processing requests from that connection until the affected resources are + restored. See http://www.rabbitmq.com/connection-blocked.html. This + may impact `BlockingConnection` and `BlockingChannel` operations in a + way that users might not be expecting. For example, if the user dispatches + `BlockingChannel.basic_publish` in non-publisher-confirmation mode while + RabbitMQ is in this low-resource state followed by a synchronous request + (e.g., `BlockingConnection.channel`, `BlockingChannel.consume`, + `BlockingChannel.basic_consume`, etc.), the synchronous request will block + indefinitely (until Connection.Unblocked) waiting for RabbitMQ to reply. If + the blocked state persists for a long time, the blocking operation will + appear to hang. In this state, `BlockingConnection` instance and its + channels will not dispatch user callbacks. SOLUTION: To break this potential + deadlock, applications may configure the `blocked_connection_timeout` + connection parameter when instantiating `BlockingConnection`. Upon blocked + connection timeout, this adapter will raise ConnectionClosed exception with + first exception arg of + `pika.connection.InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT`. See + `pika.connection.ConnectionParameters` documentation to learn more about + `blocked_connection_timeout` configuration. + + """ + # Connection-opened callback args + _OnOpenedArgs = namedtuple('BlockingConnection__OnOpenedArgs', + 'connection') + + # Connection-establishment error callback args + _OnOpenErrorArgs = namedtuple('BlockingConnection__OnOpenErrorArgs', + 'connection error') + + # Connection-closing callback args + _OnClosedArgs = namedtuple('BlockingConnection__OnClosedArgs', + 'connection reason_code reason_text') + + # Channel-opened callback args + _OnChannelOpenedArgs = namedtuple( + 'BlockingConnection__OnChannelOpenedArgs', + 'channel') + + def __init__(self, parameters=None, _impl_class=None): + """Create a new instance of the Connection object. + + :param pika.connection.Parameters parameters: Connection parameters + :param _impl_class: for tests/debugging only; implementation class; + None=default + + :raises RuntimeError: + + """ + # Used by the _acquire_event_dispatch decorator; when already greater + # than 0, event dispatch is already acquired higher up the call stack + self._event_dispatch_suspend_depth = 0 + + # Connection-specific events that are ready for dispatch: _TimerEvt, + # _ConnectionBlockedEvt, _ConnectionUnblockedEvt + self._ready_events = deque() + + # Channel numbers of channels that are requesting a call to their + # BlockingChannel._dispatch_events method; See + # `_request_channel_dispatch` + self._channels_pending_dispatch = set() + + # Receives on_open_callback args from Connection + self._opened_result = _CallbackResult(self._OnOpenedArgs) + + # Receives on_open_error_callback args from Connection + self._open_error_result = _CallbackResult(self._OnOpenErrorArgs) + + # Receives on_close_callback args from Connection + self._closed_result = _CallbackResult(self._OnClosedArgs) + + # Set to True when when user calls close() on the connection + # NOTE: this is a workaround to detect socket error because + # on_close_callback passes reason_code=0 when called due to socket error + self._user_initiated_close = False + + impl_class = _impl_class or SelectConnection + self._impl = impl_class( + parameters=parameters, + on_open_callback=self._opened_result.set_value_once, + on_open_error_callback=self._open_error_result.set_value_once, + on_close_callback=self._closed_result.set_value_once, + stop_ioloop_on_close=False) + + self._impl.ioloop.activate_poller() + + self._process_io_for_connection_setup() + + def __repr__(self): + return '<%s impl=%r>' % (self.__class__.__name__, self._impl) + + def _cleanup(self): + """Clean up members that might inhibit garbage collection""" + self._impl.ioloop.close() + self._ready_events.clear() + self._opened_result.reset() + self._open_error_result.reset() + self._closed_result.reset() + + @contextlib.contextmanager + def _acquire_event_dispatch(self): + """ Context manager that controls access to event dispatcher for + preventing reentrancy. + + The "as" value is True if the managed code block owns the event + dispatcher and False if caller higher up in the call stack already owns + it. Only managed code that gets ownership (got True) is permitted to + dispatch + """ + try: + # __enter__ part + self._event_dispatch_suspend_depth += 1 + yield self._event_dispatch_suspend_depth == 1 + finally: + # __exit__ part + self._event_dispatch_suspend_depth -= 1 + + def _process_io_for_connection_setup(self): + """ Perform follow-up processing for connection setup request: flush + connection output and process input while waiting for connection-open + or connection-error. + + :raises AMQPConnectionError: on connection open error + """ + if not self._open_error_result.ready: + self._flush_output(self._opened_result.is_ready, + self._open_error_result.is_ready) + + if self._open_error_result.ready: + try: + exception_or_message = self._open_error_result.value.error + if isinstance(exception_or_message, Exception): + raise exception_or_message + raise exceptions.AMQPConnectionError(exception_or_message) + finally: + self._cleanup() + + assert self._opened_result.ready + assert self._opened_result.value.connection is self._impl + + def _flush_output(self, *waiters): + """ Flush output and process input while waiting for any of the given + callbacks to return true. The wait is aborted upon connection-close. + Otherwise, processing continues until the output is flushed AND at least + one of the callbacks returns true. If there are no callbacks, then + processing ends when all output is flushed. + + :param waiters: sequence of zero or more callables taking no args and + returning true when it's time to stop processing. + Their results are OR'ed together. + """ + if self.is_closed: + raise exceptions.ConnectionClosed() + + # Conditions for terminating the processing loop: + # connection closed + # OR + # empty outbound buffer and no waiters + # OR + # empty outbound buffer and any waiter is ready + is_done = (lambda: + self._closed_result.ready or + (not self._impl.outbound_buffer and + (not waiters or any(ready() for ready in waiters)))) + + # Process I/O until our completion condition is satisified + while not is_done(): + self._impl.ioloop.poll() + self._impl.ioloop.process_timeouts() + + if self._open_error_result.ready or self._closed_result.ready: + try: + if not self._user_initiated_close: + if self._open_error_result.ready: + maybe_exception = self._open_error_result.value.error + LOGGER.error('Connection open failed - %r', + maybe_exception) + if isinstance(maybe_exception, Exception): + raise maybe_exception + else: + raise exceptions.ConnectionClosed(maybe_exception) + else: + result = self._closed_result.value + LOGGER.error('Connection close detected; result=%r', + result) + raise exceptions.ConnectionClosed(result.reason_code, + result.reason_text) + else: + LOGGER.info('Connection closed; result=%r', + self._closed_result.value) + finally: + self._cleanup() + + def _request_channel_dispatch(self, channel_number): + """Called by BlockingChannel instances to request a call to their + _dispatch_events method or to terminate `process_data_events`; + BlockingConnection will honor these requests from a safe context. + + :param int channel_number: positive channel number to request a call + to the channel's `_dispatch_events`; a negative channel number to + request termination of `process_data_events` + """ + self._channels_pending_dispatch.add(channel_number) + + def _dispatch_channel_events(self): + """Invoke the `_dispatch_events` method on open channels that requested + it + """ + if not self._channels_pending_dispatch: + return + + with self._acquire_event_dispatch() as dispatch_acquired: + if not dispatch_acquired: + # Nested dispatch or dispatch blocked higher in call stack + return + + candidates = list(self._channels_pending_dispatch) + self._channels_pending_dispatch.clear() + + for channel_number in candidates: + if channel_number < 0: + # This was meant to terminate process_data_events + continue + + try: + impl_channel = self._impl._channels[channel_number] + except KeyError: + continue + + if impl_channel.is_open: + impl_channel._get_cookie()._dispatch_events() + + def _on_timer_ready(self, evt): + """Handle expiry of a timer that was registered via `add_timeout` + + :param _TimerEvt evt: + + """ + self._ready_events.append(evt) + + def _on_threadsafe_callback(self, user_callback): + """Handle callback that was registered via `add_callback_threadsafe`. + + :param user_callback: callback passed to `add_callback_threadsafe` by + the application. + + """ + # Turn it into a 0-delay timeout to take advantage of our existing logic + # that deals with reentrancy + self.add_timeout(0, user_callback) + + + def _on_connection_blocked(self, user_callback, method_frame): + """Handle Connection.Blocked notification from RabbitMQ broker + + :param callable user_callback: callback_method passed to + `add_on_connection_blocked_callback` + :param pika.frame.Method method_frame: method frame having `method` + member of type `pika.spec.Connection.Blocked` + """ + self._ready_events.append( + _ConnectionBlockedEvt(user_callback, method_frame)) + + def _on_connection_unblocked(self, user_callback, method_frame): + """Handle Connection.Unblocked notification from RabbitMQ broker + + :param callable user_callback: callback_method passed to + `add_on_connection_unblocked_callback` + :param pika.frame.Method method_frame: method frame having `method` + member of type `pika.spec.Connection.Blocked` + """ + self._ready_events.append( + _ConnectionUnblockedEvt(user_callback, method_frame)) + + def _dispatch_connection_events(self): + """Dispatch ready connection events""" + if not self._ready_events: + return + + with self._acquire_event_dispatch() as dispatch_acquired: + if not dispatch_acquired: + # Nested dispatch or dispatch blocked higher in call stack + return + + # Limit dispatch to the number of currently ready events to avoid + # getting stuck in this loop + for _ in compat.xrange(len(self._ready_events)): + try: + evt = self._ready_events.popleft() + except IndexError: + # Some events (e.g., timers) must have been cancelled + break + + evt.dispatch() + + def add_on_connection_blocked_callback(self, callback_method): + """Add a callback to be notified when RabbitMQ has sent a + `Connection.Blocked` frame indicating that RabbitMQ is low on + resources. Publishers can use this to voluntarily suspend publishing, + instead of relying on back pressure throttling. The callback + will be passed the `Connection.Blocked` method frame. + + See also `ConnectionParameters.blocked_connection_timeout`. + + :param method callback_method: Callback to call on `Connection.Blocked`, + having the signature `callback_method(pika.frame.Method)`, where the + method frame's `method` member is of type + `pika.spec.Connection.Blocked` + + """ + self._impl.add_on_connection_blocked_callback( + functools.partial(self._on_connection_blocked, callback_method)) + + def add_on_connection_unblocked_callback(self, callback_method): + """Add a callback to be notified when RabbitMQ has sent a + `Connection.Unblocked` frame letting publishers know it's ok + to start publishing again. The callback will be passed the + `Connection.Unblocked` method frame. + + :param method callback_method: Callback to call on + `Connection.Unblocked`, having the signature + `callback_method(pika.frame.Method)`, where the method frame's + `method` member is of type `pika.spec.Connection.Unblocked` + + """ + self._impl.add_on_connection_unblocked_callback( + functools.partial(self._on_connection_unblocked, callback_method)) + + def add_timeout(self, deadline, callback_method): + """Create a single-shot timer to fire after deadline seconds. Do not + confuse with Tornado's timeout where you pass in the time you want to + have your callback called. Only pass in the seconds until it's to be + called. + + NOTE: the timer callbacks are dispatched only in the scope of + specially-designated methods: see + `BlockingConnection.process_data_events` and + `BlockingChannel.start_consuming`. + + :param float deadline: The number of seconds to wait to call callback + :param callable callback_method: The callback method with the signature + callback_method() + + :returns: opaque timer id + + """ + if not callable(callback_method): + raise ValueError( + 'callback_method parameter must be callable, but got %r' + % (callback_method,)) + + evt = _TimerEvt(callback=callback_method) + timer_id = self._impl.add_timeout( + deadline, + functools.partial(self._on_timer_ready, evt)) + evt.timer_id = timer_id + + return timer_id + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this connection's thread. + + NOTE: This is the only thread-safe method in `BlockingConnection`. All + other manipulations of `BlockingConnection` must be performed from the + connection's thread. + + For example, a thread may request a call to the + `BlockingChannel.basic_ack` method of a `BlockingConnection` that is + running in a different thread via + + ``` + connection.add_callback_threadsafe( + functools.partial(channel.basic_ack, delivery_tag=...)) + ``` + + NOTE: if you know that the requester is running on the same thread as + the connection it is more efficient to use the + `BlockingConnection.add_timeout()` method with a deadline of 0. + + :param method callback: The callback method; must be callable + + """ + self._impl.add_callback_threadsafe( + functools.partial(self._on_threadsafe_callback, callback)) + + def remove_timeout(self, timeout_id): + """Remove a timer if it's still in the timeout stack + + :param timeout_id: The opaque timer id to remove + + """ + # Remove from the impl's timeout stack + self._impl.remove_timeout(timeout_id) + + # Remove from ready events, if the timer fired already + for i, evt in enumerate(self._ready_events): + if isinstance(evt, _TimerEvt) and evt.timer_id == timeout_id: + index_to_remove = i + break + else: + # Not found + return + + del self._ready_events[index_to_remove] + + def close(self, reply_code=200, reply_text='Normal shutdown'): + """Disconnect from RabbitMQ. If there are any open channels, it will + attempt to close them prior to fully disconnecting. Channels which + have active consumers will attempt to send a Basic.Cancel to RabbitMQ + to cleanly stop the delivery of messages prior to closing the channel. + + :param int reply_code: The code number for the close + :param str reply_text: The text reason for the close + + """ + if self.is_closed: + LOGGER.debug('Close called on closed connection (%s): %s', + reply_code, reply_text) + return + + LOGGER.info('Closing connection (%s): %s', reply_code, reply_text) + + self._user_initiated_close = True + + # Close channels that remain opened + for impl_channel in pika.compat.dictvalues(self._impl._channels): + channel = impl_channel._get_cookie() + if channel.is_open: + try: + channel.close(reply_code, reply_text) + except exceptions.ChannelClosed as exc: + # Log and suppress broker-closed channel + LOGGER.warning('Got ChannelClosed while closing channel ' + 'from connection.close: %r', exc) + + # Close the connection + self._impl.close(reply_code, reply_text) + + self._flush_output(self._closed_result.is_ready) + + def process_data_events(self, time_limit=0): + """Will make sure that data events are processed. Dispatches timer and + channel callbacks if not called from the scope of BlockingConnection or + BlockingChannel callback. Your app can block on this method. + + :param float time_limit: suggested upper bound on processing time in + seconds. The actual blocking time depends on the granularity of the + underlying ioloop. Zero means return as soon as possible. None means + there is no limit on processing time and the function will block + until I/O produces actionable events. Defaults to 0 for backward + compatibility. This parameter is NEW in pika 0.10.0. + """ + with self._acquire_event_dispatch() as dispatch_acquired: + # Check if we can actually process pending events + common_terminator = lambda: bool(dispatch_acquired and + (self._channels_pending_dispatch or self._ready_events)) + if time_limit is None: + self._flush_output(common_terminator) + else: + with _IoloopTimerContext(time_limit, self._impl) as timer: + self._flush_output(timer.is_ready, common_terminator) + + if self._ready_events: + self._dispatch_connection_events() + + if self._channels_pending_dispatch: + self._dispatch_channel_events() + + def sleep(self, duration): + """A safer way to sleep than calling time.sleep() directly that would + keep the adapter from ignoring frames sent from the broker. The + connection will "sleep" or block the number of seconds specified in + duration in small intervals. + + :param float duration: The time to sleep in seconds + + """ + assert duration >= 0, duration + + deadline = time.time() + duration + time_limit = duration + # Process events at least once + while True: + self.process_data_events(time_limit) + time_limit = deadline - time.time() + if time_limit <= 0: + break + + def channel(self, channel_number=None): + """Create a new channel with the next available channel number or pass + in a channel number to use. Must be non-zero if you would like to + specify but it is recommended that you let Pika manage the channel + numbers. + + :rtype: pika.adapters.blocking_connection.BlockingChannel + """ + with _CallbackResult(self._OnChannelOpenedArgs) as opened_args: + impl_channel = self._impl.channel( + on_open_callback=opened_args.set_value_once, + channel_number=channel_number) + + # Create our proxy channel + channel = BlockingChannel(impl_channel, self) + + # Link implementation channel with our proxy channel + impl_channel._set_cookie(channel) + + # Drive I/O until Channel.Open-ok + channel._flush_output(opened_args.is_ready) + + return channel + + def __enter__(self): + # Prepare `with` context + return self + + def __exit__(self, exc_type, value, traceback): + # Close connection after `with` context + self.close() + + # + # Connections state properties + # + + @property + def is_closed(self): + """ + Returns a boolean reporting the current connection state. + """ + return self._impl.is_closed + + @property + def is_closing(self): + """ + Returns True if connection is in the process of closing due to + client-initiated `close` request, but closing is not yet complete. + """ + return self._impl.is_closing + + @property + def is_open(self): + """ + Returns a boolean reporting the current connection state. + """ + return self._impl.is_open + + # + # Properties that reflect server capabilities for the current connection + # + + @property + def basic_nack_supported(self): + """Specifies if the server supports basic.nack on the active connection. + + :rtype: bool + + """ + return self._impl.basic_nack + + @property + def consumer_cancel_notify_supported(self): + """Specifies if the server supports consumer cancel notification on the + active connection. + + :rtype: bool + + """ + return self._impl.consumer_cancel_notify + + @property + def exchange_exchange_bindings_supported(self): + """Specifies if the active connection supports exchange to exchange + bindings. + + :rtype: bool + + """ + return self._impl.exchange_exchange_bindings + + @property + def publisher_confirms_supported(self): + """Specifies if the active connection can use publisher confirmations. + + :rtype: bool + + """ + return self._impl.publisher_confirms + + # Legacy property names for backward compatibility + basic_nack = basic_nack_supported + consumer_cancel_notify = consumer_cancel_notify_supported + exchange_exchange_bindings = exchange_exchange_bindings_supported + publisher_confirms = publisher_confirms_supported + + +class _ChannelPendingEvt(object): + """Base class for BlockingChannel pending events""" + pass + + +class _ConsumerDeliveryEvt(_ChannelPendingEvt): + """This event represents consumer message delivery `Basic.Deliver`; it + contains method, properties, and body of the delivered message. + """ + + __slots__ = ('method', 'properties', 'body') + + def __init__(self, method, properties, body): + """ + :param spec.Basic.Deliver method: NOTE: consumer_tag and delivery_tag + are valid only within source channel + :param spec.BasicProperties properties: message properties + :param body: message body; empty string if no body + :type body: str or unicode + """ + self.method = method + self.properties = properties + self.body = body + + +class _ConsumerCancellationEvt(_ChannelPendingEvt): + """This event represents server-initiated consumer cancellation delivered to + client via Basic.Cancel. After receiving Basic.Cancel, there will be no + further deliveries for the consumer identified by `consumer_tag` in + `Basic.Cancel` + """ + + __slots__ = ('method_frame',) + + def __init__(self, method_frame): + """ + :param pika.frame.Method method_frame: method frame with method of type + `spec.Basic.Cancel` + """ + self.method_frame = method_frame + + def __repr__(self): + return '<%s method_frame=%r>' % (self.__class__.__name__, + self.method_frame) + + @property + def method(self): + """method of type spec.Basic.Cancel""" + return self.method_frame.method + + +class _ReturnedMessageEvt(_ChannelPendingEvt): + """This event represents a message returned by broker via `Basic.Return`""" + + __slots__ = ('callback', 'channel', 'method', 'properties', 'body') + + def __init__(self, callback, channel, method, properties, body): + """ + :param callable callback: user's callback, having the signature + callback(channel, method, properties, body), where + channel: pika.Channel + method: pika.spec.Basic.Return + properties: pika.spec.BasicProperties + body: str, unicode, or bytes (python 3.x) + + :param pika.Channel channel: + :param pika.spec.Basic.Return method: + :param pika.spec.BasicProperties properties: + :param body: str, unicode, or bytes (python 3.x) + """ + self.callback = callback + self.channel = channel + self.method = method + self.properties = properties + self.body = body + + def __repr__(self): + return ('<%s callback=%r channel=%r method=%r properties=%r ' + 'body=%.300r>') % (self.__class__.__name__, self.callback, + self.channel, self.method, self.properties, + self.body) + + def dispatch(self): + """Dispatch user's callback""" + self.callback(self.channel, self.method, self.properties, self.body) + + +class ReturnedMessage(object): + """Represents a message returned via Basic.Return in publish-acknowledgments + mode + """ + + __slots__ = ('method', 'properties', 'body') + + def __init__(self, method, properties, body): + """ + :param spec.Basic.Return method: + :param spec.BasicProperties properties: message properties + :param body: message body; empty string if no body + :type body: str or unicode + """ + self.method = method + self.properties = properties + self.body = body + + +class _ConsumerInfo(object): + """Information about an active consumer""" + + __slots__ = ('consumer_tag', 'no_ack', 'consumer_cb', + 'alternate_event_sink', 'state') + + # Consumer states + SETTING_UP = 1 + ACTIVE = 2 + TEARING_DOWN = 3 + CANCELLED_BY_BROKER = 4 + + def __init__(self, consumer_tag, no_ack, consumer_cb=None, + alternate_event_sink=None): + """ + NOTE: exactly one of consumer_cb/alternate_event_sink musts be non-None. + + :param str consumer_tag: + :param bool no_ack: the no-ack value for the consumer + :param callable consumer_cb: The function for dispatching messages to + user, having the signature: + consumer_callback(channel, method, properties, body) + channel: BlockingChannel + method: spec.Basic.Deliver + properties: spec.BasicProperties + body: str or unicode + :param callable alternate_event_sink: if specified, _ConsumerDeliveryEvt + and _ConsumerCancellationEvt objects will be diverted to this + callback instead of being deposited in the channel's + `_pending_events` container. Signature: + alternate_event_sink(evt) + """ + assert (consumer_cb is None) != (alternate_event_sink is None), ( + 'exactly one of consumer_cb/alternate_event_sink must be non-None', + consumer_cb, alternate_event_sink) + self.consumer_tag = consumer_tag + self.no_ack = no_ack + self.consumer_cb = consumer_cb + self.alternate_event_sink = alternate_event_sink + self.state = self.SETTING_UP + + @property + def setting_up(self): + """True if in SETTING_UP state""" + return self.state == self.SETTING_UP + + @property + def active(self): + """True if in ACTIVE state""" + return self.state == self.ACTIVE + + @property + def tearing_down(self): + """True if in TEARING_DOWN state""" + return self.state == self.TEARING_DOWN + + @property + def cancelled_by_broker(self): + """True if in CANCELLED_BY_BROKER state""" + return self.state == self.CANCELLED_BY_BROKER + + +class _QueueConsumerGeneratorInfo(object): + """Container for information about the active queue consumer generator """ + __slots__ = ('params', 'consumer_tag', 'pending_events') + + def __init__(self, params, consumer_tag): + """ + :params tuple params: a three-tuple (queue, no_ack, exclusive) that were + used to create the queue consumer + :param str consumer_tag: consumer tag + """ + self.params = params + self.consumer_tag = consumer_tag + #self.messages = deque() + + # Holds pending events of types _ConsumerDeliveryEvt and + # _ConsumerCancellationEvt + self.pending_events = deque() + + def __repr__(self): + return '<%s params=%r consumer_tag=%r>' % ( + self.__class__.__name__, self.params, self.consumer_tag) + + +class BlockingChannel(object): + """The BlockingChannel implements blocking semantics for most things that + one would use callback-passing-style for with the + :py:class:`~pika.channel.Channel` class. In addition, + the `BlockingChannel` class implements a :term:`generator` that allows + you to :doc:`consume messages ` + without using callbacks. + + Example of creating a BlockingChannel:: + + import pika + + # Create our connection object + connection = pika.BlockingConnection() + + # The returned object will be a synchronous channel + channel = connection.channel() + + """ + + # Used as value_class with _CallbackResult for receiving Basic.GetOk args + _RxMessageArgs = namedtuple( + 'BlockingChannel__RxMessageArgs', + [ + 'channel', # implementation pika.Channel instance + 'method', # Basic.GetOk + 'properties', # pika.spec.BasicProperties + 'body' # str, unicode, or bytes (python 3.x) + ]) + + + # For use as value_class with any _CallbackResult that expects method_frame + # as the only arg + _MethodFrameCallbackResultArgs = namedtuple( + 'BlockingChannel__MethodFrameCallbackResultArgs', + 'method_frame') + + # Broker's basic-ack/basic-nack args when delivery confirmation is enabled; + # may concern a single or multiple messages + _OnMessageConfirmationReportArgs = namedtuple( + 'BlockingChannel__OnMessageConfirmationReportArgs', + 'method_frame') + + # Parameters for broker-initiated Channel.Close request: reply_code + # holds the broker's non-zero error code and reply_text holds the + # corresponding error message text. + _OnChannelClosedByBrokerArgs = namedtuple( + 'BlockingChannel__OnChannelClosedByBrokerArgs', + 'method_frame') + + # For use as value_class with _CallbackResult expecting Channel.Flow + # confirmation. + _FlowOkCallbackResultArgs = namedtuple( + 'BlockingChannel__FlowOkCallbackResultArgs', + 'active' # True if broker will start or continue sending; False if not + ) + + _CONSUMER_CANCELLED_CB_KEY = 'blocking_channel_consumer_cancelled' + + def __init__(self, channel_impl, connection): + """Create a new instance of the Channel + + :param channel_impl: Channel implementation object as returned from + SelectConnection.channel() + :param BlockingConnection connection: The connection object + + """ + self._impl = channel_impl + self._connection = connection + + # A mapping of consumer tags to _ConsumerInfo for active consumers + self._consumer_infos = dict() + + # Queue consumer generator generator info of type + # _QueueConsumerGeneratorInfo created by BlockingChannel.consume + self._queue_consumer_generator = None + + # Whether RabbitMQ delivery confirmation has been enabled + self._delivery_confirmation = False + + # Receives message delivery confirmation report (Basic.ack or + # Basic.nack) from broker when delivery confirmations are enabled + self._message_confirmation_result = _CallbackResult( + self._OnMessageConfirmationReportArgs) + + # deque of pending events: _ConsumerDeliveryEvt and + # _ConsumerCancellationEvt objects that will be returned by + # `BlockingChannel.get_event()` + self._pending_events = deque() + + # Holds a ReturnedMessage object representing a message received via + # Basic.Return in publisher-acknowledgments mode. + self._puback_return = None + + # Receives Basic.ConsumeOk reply from server + self._basic_consume_ok_result = _CallbackResult() + + # Receives the broker-inititated Channel.Close parameters + self._channel_closed_by_broker_result = _CallbackResult( + self._OnChannelClosedByBrokerArgs) + + # Receives args from Basic.GetEmpty response + # http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.get + self._basic_getempty_result = _CallbackResult( + self._MethodFrameCallbackResultArgs) + + self._impl.add_on_cancel_callback(self._on_consumer_cancelled_by_broker) + + self._impl.add_callback( + self._basic_consume_ok_result.signal_once, + replies=[pika.spec.Basic.ConsumeOk], + one_shot=False) + + self._impl.add_callback( + self._on_channel_closed, + replies=[pika.spec.Channel.Close], + one_shot=True) + + self._impl.add_callback( + self._basic_getempty_result.set_value_once, + replies=[pika.spec.Basic.GetEmpty], + one_shot=False) + + LOGGER.info("Created channel=%s", self.channel_number) + + def __int__(self): + """Return the channel object as its channel number + + NOTE: inherited from legacy BlockingConnection; might be error-prone; + use `channel_number` property instead. + + :rtype: int + + """ + return self.channel_number + + def __repr__(self): + return '<%s impl=%r>' % (self.__class__.__name__, self._impl) + + def __enter__(self): + return self + + def __exit__(self, exc_type, value, traceback): + try: + self.close() + except exceptions.ChannelClosed: + pass + + def _cleanup(self): + """Clean up members that might inhibit garbage collection""" + self._message_confirmation_result.reset() + self._pending_events = deque() + self._consumer_infos = dict() + + @property + def channel_number(self): + """Channel number""" + return self._impl.channel_number + + @property + def connection(self): + """The channel's BlockingConnection instance""" + return self._connection + + @property + def is_closed(self): + """Returns True if the channel is closed. + + :rtype: bool + + """ + return self._impl.is_closed + + @property + def is_closing(self): + """Returns True if client-initiated closing of the channel is in + progress. + + :rtype: bool + + """ + return self._impl.is_closing + + @property + def is_open(self): + """Returns True if the channel is open. + + :rtype: bool + + """ + return self._impl.is_open + + _ALWAYS_READY_WAITERS = ((lambda: True), ) + + def _flush_output(self, *waiters): + """ Flush output and process input while waiting for any of the given + callbacks to return true. The wait is aborted upon channel-close or + connection-close. + Otherwise, processing continues until the output is flushed AND at least + one of the callbacks returns true. If there are no callbacks, then + processing ends when all output is flushed. + + :param waiters: sequence of zero or more callables taking no args and + returning true when it's time to stop processing. + Their results are OR'ed together. + """ + if self.is_closed: + raise exceptions.ChannelClosed() + + if not waiters: + waiters = self._ALWAYS_READY_WAITERS + + self._connection._flush_output( + self._channel_closed_by_broker_result.is_ready, + *waiters) + + if self._channel_closed_by_broker_result: + # Channel was force-closed by broker + self._cleanup() + method = ( + self._channel_closed_by_broker_result.value.method_frame.method) + raise exceptions.ChannelClosed(method.reply_code, method.reply_text) + + def _on_puback_message_returned(self, channel, method, properties, body): + """Called as the result of Basic.Return from broker in + publisher-acknowledgements mode. Saves the info as a ReturnedMessage + instance in self._puback_return. + + :param pika.Channel channel: our self._impl channel + :param pika.spec.Basic.Return method: + :param pika.spec.BasicProperties properties: message properties + :param body: returned message body; empty string if no body + :type body: str, unicode + + """ + assert channel is self._impl, ( + channel.channel_number, self.channel_number) + + assert isinstance(method, pika.spec.Basic.Return), method + assert isinstance(properties, pika.spec.BasicProperties), ( + properties) + + LOGGER.warning( + "Published message was returned: _delivery_confirmation=%s; " + "channel=%s; method=%r; properties=%r; body_size=%d; " + "body_prefix=%.255r", self._delivery_confirmation, + channel.channel_number, method, properties, + len(body) if body is not None else None, body) + + self._puback_return = ReturnedMessage(method, properties, body) + + + def _add_pending_event(self, evt): + """Append an event to the channel's list of events that are ready for + dispatch to user and signal our connection that this channel is ready + for event dispatch + + :param _ChannelPendingEvt evt: an event derived from _ChannelPendingEvt + """ + self._pending_events.append(evt) + self.connection._request_channel_dispatch(self.channel_number) + + + def _on_channel_closed(self, method_frame): + """Called by impl when a channel is closed by the broker + via Channel.Close + + :param pika.Channel channel: channel closed by the + `spec.Channel.Close` method + :param int reply_code: The reply code sent via Channel.Close + :param str reply_text: The reply text sent via Channel.Close + + """ + LOGGER.debug('_on_channel_closed_by_broker %s', method_frame) + self._channel_closed_by_broker_result.set_value_once(method_frame) + channel_number = method_frame.channel_number + self.connection._request_channel_dispatch(-channel_number) + self._cleanup() + method = method_frame.method + raise exceptions.ChannelClosed(method.reply_code, + method.reply_text) + + def _on_consumer_cancelled_by_broker(self, method_frame): + """Called by impl when broker cancels consumer via Basic.Cancel. + + This is a RabbitMQ-specific feature. The circumstances include deletion + of queue being consumed as well as failure of a HA node responsible for + the queue being consumed. + + :param pika.frame.Method method_frame: method frame with the + `spec.Basic.Cancel` method + + """ + evt = _ConsumerCancellationEvt(method_frame) + + consumer = self._consumer_infos[method_frame.method.consumer_tag] + + # Don't interfere with client-initiated cancellation flow + if not consumer.tearing_down: + consumer.state = _ConsumerInfo.CANCELLED_BY_BROKER + + if consumer.alternate_event_sink is not None: + consumer.alternate_event_sink(evt) + else: + self._add_pending_event(evt) + + def _on_consumer_message_delivery(self, _channel, method, properties, body): + """Called by impl when a message is delivered for a consumer + + :param Channel channel: The implementation channel object + :param spec.Basic.Deliver method: + :param pika.spec.BasicProperties properties: message properties + :param body: delivered message body; empty string if no body + :type body: str, unicode, or bytes (python 3.x) + + """ + evt = _ConsumerDeliveryEvt(method, properties, body) + + consumer = self._consumer_infos[method.consumer_tag] + + if consumer.alternate_event_sink is not None: + consumer.alternate_event_sink(evt) + else: + self._add_pending_event(evt) + + def _on_consumer_generator_event(self, evt): + """Sink for the queue consumer generator's consumer events; append the + event to queue consumer generator's pending events buffer. + + :param evt: an object of type _ConsumerDeliveryEvt or + _ConsumerCancellationEvt + """ + self._queue_consumer_generator.pending_events.append(evt) + # Schedule termination of connection.process_data_events using a + # negative channel number + self.connection._request_channel_dispatch(-self.channel_number) + + def _cancel_all_consumers(self): + """Cancel all consumers. + + NOTE: pending non-ackable messages will be lost; pending ackable + messages will be rejected. + + """ + if self._consumer_infos: + LOGGER.debug('Cancelling %i consumers', len(self._consumer_infos)) + + if self._queue_consumer_generator is not None: + # Cancel queue consumer generator + self.cancel() + + # Cancel consumers created via basic_consume + for consumer_tag in pika.compat.dictkeys(self._consumer_infos): + self.basic_cancel(consumer_tag) + + def _dispatch_events(self): + """Called by BlockingConnection to dispatch pending events. + + `BlockingChannel` schedules this callback via + `BlockingConnection._request_channel_dispatch` + """ + while self._pending_events: + evt = self._pending_events.popleft() + + if type(evt) is _ConsumerDeliveryEvt: + consumer_info = self._consumer_infos[evt.method.consumer_tag] + consumer_info.consumer_cb(self, evt.method, evt.properties, + evt.body) + + elif type(evt) is _ConsumerCancellationEvt: + del self._consumer_infos[evt.method_frame.method.consumer_tag] + + self._impl.callbacks.process(self.channel_number, + self._CONSUMER_CANCELLED_CB_KEY, + self, + evt.method_frame) + else: + evt.dispatch() + + + def close(self, reply_code=0, reply_text="Normal shutdown"): + """Will invoke a clean shutdown of the channel with the AMQP Broker. + + :param int reply_code: The reply code to close the channel with + :param str reply_text: The reply text to close the channel with + + """ + LOGGER.debug('Channel.close(%s, %s)', reply_code, reply_text) + + # Cancel remaining consumers + self._cancel_all_consumers() + + # Close the channel + try: + with _CallbackResult() as close_ok_result: + self._impl.add_callback(callback=close_ok_result.signal_once, + replies=[pika.spec.Channel.CloseOk], + one_shot=True) + + self._impl.close(reply_code=reply_code, reply_text=reply_text) + self._flush_output(close_ok_result.is_ready) + finally: + self._cleanup() + + def flow(self, active): + """Turn Channel flow control off and on. + + NOTE: RabbitMQ doesn't support active=False; per + https://www.rabbitmq.com/specification.html: "active=false is not + supported by the server. Limiting prefetch with basic.qos provides much + better control" + + For more information, please reference: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#channel.flow + + :param bool active: Turn flow on (True) or off (False) + + :returns: True if broker will start or continue sending; False if not + :rtype: bool + + """ + with _CallbackResult(self._FlowOkCallbackResultArgs) as flow_ok_result: + self._impl.flow(callback=flow_ok_result.set_value_once, + active=active) + self._flush_output(flow_ok_result.is_ready) + return flow_ok_result.value.active + + def add_on_cancel_callback(self, callback): + """Pass a callback function that will be called when Basic.Cancel + is sent by the broker. The callback function should receive a method + frame parameter. + + :param callable callback: a callable for handling broker's Basic.Cancel + notification with the call signature: callback(method_frame) + where method_frame is of type `pika.frame.Method` with method of + type `spec.Basic.Cancel` + + """ + self._impl.callbacks.add(self.channel_number, + self._CONSUMER_CANCELLED_CB_KEY, + callback, + one_shot=False) + + def add_on_return_callback(self, callback): + """Pass a callback function that will be called when a published + message is rejected and returned by the server via `Basic.Return`. + + :param callable callback: The method to call on callback with the + signature callback(channel, method, properties, body), where + channel: pika.Channel + method: pika.spec.Basic.Return + properties: pika.spec.BasicProperties + body: str, unicode, or bytes (python 3.x) + + """ + self._impl.add_on_return_callback( + lambda _channel, method, properties, body: ( + self._add_pending_event( + _ReturnedMessageEvt( + callback, self, method, properties, body)))) + + def basic_consume(self, + consumer_callback, + queue, + no_ack=False, + exclusive=False, + consumer_tag=None, + arguments=None): + """Sends the AMQP command Basic.Consume to the broker and binds messages + for the consumer_tag to the consumer callback. If you do not pass in + a consumer_tag, one will be automatically generated for you. Returns + the consumer tag. + + NOTE: the consumer callbacks are dispatched only in the scope of + specially-designated methods: see + `BlockingConnection.process_data_events` and + `BlockingChannel.start_consuming`. + + For more information about Basic.Consume, see: + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.consume + + :param callable consumer_callback: The function for dispatching messages + to user, having the signature: + consumer_callback(channel, method, properties, body) + channel: BlockingChannel + method: spec.Basic.Deliver + properties: spec.BasicProperties + body: str or unicode + :param queue: The queue to consume from + :type queue: str or unicode + :param bool no_ack: Tell the broker to not expect a response (i.e., + no ack/nack) + :param bool exclusive: Don't allow other consumers on the queue + :param consumer_tag: You may specify your own consumer tag; if left + empty, a consumer tag will be generated automatically + :type consumer_tag: str or unicode + :param dict arguments: Custom key/value pair arguments for the consumer + :returns: consumer tag + :rtype: str + + :raises pika.exceptions.DuplicateConsumerTag: if consumer with given + consumer_tag is already present. + + """ + if not callable(consumer_callback): + raise ValueError('consumer callback must be callable; got %r' + % consumer_callback) + + return self._basic_consume_impl( + queue=queue, + no_ack=no_ack, + exclusive=exclusive, + consumer_tag=consumer_tag, + arguments=arguments, + consumer_callback=consumer_callback) + + def _basic_consume_impl(self, + queue, + no_ack, + exclusive, + consumer_tag, + arguments=None, + consumer_callback=None, + alternate_event_sink=None): + """The low-level implementation used by `basic_consume` and `consume`. + See `basic_consume` docstring for more info. + + NOTE: exactly one of consumer_callback/alternate_event_sink musts be + non-None. + + This method has one additional parameter alternate_event_sink over the + args described in `basic_consume`. + + :param callable alternate_event_sink: if specified, _ConsumerDeliveryEvt + and _ConsumerCancellationEvt objects will be diverted to this + callback instead of being deposited in the channel's + `_pending_events` container. Signature: + alternate_event_sink(evt) + + :raises pika.exceptions.DuplicateConsumerTag: if consumer with given + consumer_tag is already present. + + """ + if (consumer_callback is None) == (alternate_event_sink is None): + raise ValueError( + ('exactly one of consumer_callback/alternate_event_sink must ' + 'be non-None', consumer_callback, alternate_event_sink)) + + if not consumer_tag: + # Need a consumer tag to register consumer info before sending + # request to broker, because I/O might dispatch incoming messages + # immediately following Basic.Consume-ok before _flush_output + # returns + consumer_tag = self._impl._generate_consumer_tag() + + if consumer_tag in self._consumer_infos: + raise exceptions.DuplicateConsumerTag(consumer_tag) + + # Create new consumer + self._consumer_infos[consumer_tag] = _ConsumerInfo( + consumer_tag, + no_ack=no_ack, + consumer_cb=consumer_callback, + alternate_event_sink=alternate_event_sink) + + try: + with self._basic_consume_ok_result as ok_result: + tag = self._impl.basic_consume( + consumer_callback=self._on_consumer_message_delivery, + queue=queue, + no_ack=no_ack, + exclusive=exclusive, + consumer_tag=consumer_tag, + arguments=arguments) + + assert tag == consumer_tag, (tag, consumer_tag) + + self._flush_output(ok_result.is_ready) + except Exception: + # If channel was closed, self._consumer_infos will be empty + if consumer_tag in self._consumer_infos: + del self._consumer_infos[consumer_tag] + # Schedule termination of connection.process_data_events using a + # negative channel number + self.connection._request_channel_dispatch(-self.channel_number) + raise + + # NOTE: Consumer could get cancelled by broker immediately after opening + # (e.g., queue getting deleted externally) + if self._consumer_infos[consumer_tag].setting_up: + self._consumer_infos[consumer_tag].state = _ConsumerInfo.ACTIVE + + return consumer_tag + + def basic_cancel(self, consumer_tag): + """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. + + NOTE: When cancelling a no_ack=False consumer, this implementation + automatically Nacks and suppresses any incoming messages that have not + yet been dispatched to the consumer's callback. However, when cancelling + a no_ack=True consumer, this method will return any pending messages + that arrived before broker confirmed the cancellation. + + :param str consumer_tag: Identifier for the consumer; the result of + passing a consumer_tag that was created on another channel is + undefined (bad things will happen) + + :returns: (NEW IN pika 0.10.0) empty sequence for a no_ack=False + consumer; for a no_ack=True consumer, returns a (possibly empty) + sequence of pending messages that arrived before broker confirmed + the cancellation (this is done instead of via consumer's callback in + order to prevent reentrancy/recursion. Each message is four-tuple: + (channel, method, properties, body) + channel: BlockingChannel + method: spec.Basic.Deliver + properties: spec.BasicProperties + body: str or unicode + """ + try: + consumer_info = self._consumer_infos[consumer_tag] + except KeyError: + LOGGER.warning( + "User is attempting to cancel an unknown consumer=%s; " + "already cancelled by user or broker?", consumer_tag) + return [] + + try: + # Assertion failure here is most likely due to reentrance + assert consumer_info.active or consumer_info.cancelled_by_broker, ( + consumer_info.state) + + # Assertion failure here signals disconnect between consumer state + # in BlockingChannel and Channel + assert (consumer_info.cancelled_by_broker or + consumer_tag in self._impl._consumers), consumer_tag + + no_ack = consumer_info.no_ack + + consumer_info.state = _ConsumerInfo.TEARING_DOWN + + with _CallbackResult() as cancel_ok_result: + # Nack pending messages for no_ack=False consumer + if not no_ack: + pending_messages = self._remove_pending_deliveries( + consumer_tag) + if pending_messages: + # NOTE: we use impl's basic_reject to avoid the + # possibility of redelivery before basic_cancel takes + # control of nacking. + # NOTE: we can't use basic_nack with the multiple option + # to avoid nacking messages already held by our client. + for message in pending_messages: + self._impl.basic_reject(message.method.delivery_tag, + requeue=True) + + # Cancel the consumer; impl takes care of rejecting any + # additional deliveries that arrive for a no_ack=False + # consumer + self._impl.basic_cancel( + callback=cancel_ok_result.signal_once, + consumer_tag=consumer_tag, + nowait=False) + + # Flush output and wait for Basic.Cancel-ok or + # broker-initiated Basic.Cancel + self._flush_output( + cancel_ok_result.is_ready, + lambda: consumer_tag not in self._impl._consumers) + + if no_ack: + # Return pending messages for no_ack=True consumer + return [ + (evt.method, evt.properties, evt.body) + for evt in self._remove_pending_deliveries(consumer_tag)] + else: + # impl takes care of rejecting any incoming deliveries during + # cancellation + messages = self._remove_pending_deliveries(consumer_tag) + assert not messages, messages + + return [] + finally: + # NOTE: The entry could be purged if channel or connection closes + if consumer_tag in self._consumer_infos: + del self._consumer_infos[consumer_tag] + # Schedule termination of connection.process_data_events using a + # negative channel number + self.connection._request_channel_dispatch(-self.channel_number) + + def _remove_pending_deliveries(self, consumer_tag): + """Extract _ConsumerDeliveryEvt objects destined for the given consumer + from pending events, discarding the _ConsumerCancellationEvt, if any + + :param str consumer_tag: + + :returns: a (possibly empty) sequence of _ConsumerDeliveryEvt destined + for the given consumer tag + """ + remaining_events = deque() + unprocessed_messages = [] + while self._pending_events: + evt = self._pending_events.popleft() + if type(evt) is _ConsumerDeliveryEvt: + if evt.method.consumer_tag == consumer_tag: + unprocessed_messages.append(evt) + continue + if type(evt) is _ConsumerCancellationEvt: + if evt.method_frame.method.consumer_tag == consumer_tag: + # A broker-initiated Basic.Cancel must have arrived + # before our cancel request completed + continue + + remaining_events.append(evt) + + self._pending_events = remaining_events + + return unprocessed_messages + + def start_consuming(self): + """Processes I/O events and dispatches timers and `basic_consume` + callbacks until all consumers are cancelled. + + NOTE: this blocking function may not be called from the scope of a + pika callback, because dispatching `basic_consume` callbacks from this + context would constitute recursion. + + :raises pika.exceptions.RecursionError: if called from the scope of a + `BlockingConnection` or `BlockingChannel` callback + + """ + # Check if called from the scope of an event dispatch callback + with self.connection._acquire_event_dispatch() as dispatch_allowed: + if not dispatch_allowed: + raise exceptions.RecursionError( + 'start_consuming may not be called from the scope of ' + 'another BlockingConnection or BlockingChannel callback') + + # Process events as long as consumers exist on this channel + while self._consumer_infos: + self.connection.process_data_events(time_limit=None) + + def stop_consuming(self, consumer_tag=None): + """ Cancels all consumers, signalling the `start_consuming` loop to + exit. + + NOTE: pending non-ackable messages will be lost; pending ackable + messages will be rejected. + + """ + if consumer_tag: + self.basic_cancel(consumer_tag) + else: + self._cancel_all_consumers() + + def consume(self, queue, no_ack=False, + exclusive=False, arguments=None, + inactivity_timeout=None): + """Blocking consumption of a queue instead of via a callback. This + method is a generator that yields each message as a tuple of method, + properties, and body. The active generator iterator terminates when the + consumer is cancelled by client via `BlockingChannel.cancel()` or by + broker. + + Example: + + for method, properties, body in channel.consume('queue'): + print body + channel.basic_ack(method.delivery_tag) + + You should call `BlockingChannel.cancel()` when you escape out of the + generator loop. + + If you don't cancel this consumer, then next call on the same channel + to `consume()` with the exact same (queue, no_ack, exclusive) parameters + will resume the existing consumer generator; however, calling with + different parameters will result in an exception. + + :param queue: The queue name to consume + :type queue: str or unicode + :param bool no_ack: Tell the broker to not expect a ack/nack response + :param bool exclusive: Don't allow other consumers on the queue + :param dict arguments: Custom key/value pair arguments for the consumer + :param float inactivity_timeout: if a number is given (in + seconds), will cause the method to yield (None, None, None) after the + given period of inactivity; this permits for pseudo-regular maintenance + activities to be carried out by the user while waiting for messages + to arrive. If None is given (default), then the method blocks until + the next event arrives. NOTE that timing granularity is limited by + the timer resolution of the underlying implementation. + NEW in pika 0.10.0. + + :yields: tuple(spec.Basic.Deliver, spec.BasicProperties, str or unicode) + + :raises ValueError: if consumer-creation parameters don't match those + of the existing queue consumer generator, if any. + NEW in pika 0.10.0 + """ + params = (queue, no_ack, exclusive) + + if self._queue_consumer_generator is not None: + if params != self._queue_consumer_generator.params: + raise ValueError( + 'Consume with different params not allowed on existing ' + 'queue consumer generator; previous params: %r; ' + 'new params: %r' + % (self._queue_consumer_generator.params, + (queue, no_ack, exclusive))) + else: + LOGGER.debug('Creating new queue consumer generator; params: %r', + params) + # Need a consumer tag to register consumer info before sending + # request to broker, because I/O might pick up incoming messages + # in addition to Basic.Consume-ok + consumer_tag = self._impl._generate_consumer_tag() + + self._queue_consumer_generator = _QueueConsumerGeneratorInfo( + params, + consumer_tag) + + try: + self._basic_consume_impl( + queue=queue, + no_ack=no_ack, + exclusive=exclusive, + consumer_tag=consumer_tag, + arguments=arguments, + alternate_event_sink=self._on_consumer_generator_event) + except Exception: + self._queue_consumer_generator = None + raise + + LOGGER.info('Created new queue consumer generator %r', + self._queue_consumer_generator) + + while self._queue_consumer_generator is not None: + if self._queue_consumer_generator.pending_events: + evt = self._queue_consumer_generator.pending_events.popleft() + if type(evt) is _ConsumerCancellationEvt: + # Consumer was cancelled by broker + self._queue_consumer_generator = None + break + else: + yield (evt.method, evt.properties, evt.body) + continue + + # Wait for a message to arrive + if inactivity_timeout is None: + self.connection.process_data_events(time_limit=None) + continue + + # Wait with inactivity timeout + wait_start_time = time.time() + wait_deadline = wait_start_time + inactivity_timeout + delta = inactivity_timeout + + while (self._queue_consumer_generator is not None and + not self._queue_consumer_generator.pending_events): + self.connection.process_data_events(time_limit=delta) + + if not self._queue_consumer_generator: + # Consumer was cancelled by client + break + + if self._queue_consumer_generator.pending_events: + # Got message(s) + break + + delta = wait_deadline - time.time() + if delta <= 0.0: + # Signal inactivity timeout + yield (None, None, None) + break + + def get_waiting_message_count(self): + """Returns the number of messages that may be retrieved from the current + queue consumer generator via `BlockingChannel.consume` without blocking. + NEW in pika 0.10.0 + + :rtype: int + """ + if self._queue_consumer_generator is not None: + pending_events = self._queue_consumer_generator.pending_events + count = len(pending_events) + if count and type(pending_events[-1]) is _ConsumerCancellationEvt: + count -= 1 + else: + count = 0 + + return count + + def cancel(self): + """Cancel the queue consumer created by `BlockingChannel.consume`, + rejecting all pending ackable messages. + + NOTE: If you're looking to cancel a consumer issued with + BlockingChannel.basic_consume then you should call + BlockingChannel.basic_cancel. + + :return int: The number of messages requeued by Basic.Nack. + NEW in 0.10.0: returns 0 + + """ + if self._queue_consumer_generator is None: + LOGGER.warning('cancel: queue consumer generator is inactive ' + '(already cancelled by client or broker?)') + return 0 + + try: + _, no_ack, _ = self._queue_consumer_generator.params + if not no_ack: + # Reject messages held by queue consumer generator; NOTE: we + # can't use basic_nack with the multiple option to avoid nacking + # messages already held by our client. + pending_events = self._queue_consumer_generator.pending_events + for _ in compat.xrange(self.get_waiting_message_count()): + evt = pending_events.popleft() + self._impl.basic_reject(evt.method.delivery_tag, + requeue=True) + + self.basic_cancel(self._queue_consumer_generator.consumer_tag) + finally: + self._queue_consumer_generator = None + + # Return 0 for compatibility with legacy implementation; the number of + # nacked messages is not meaningful since only messages consumed with + # no_ack=False may be nacked, and those arriving after calling + # basic_cancel will be rejected automatically by impl channel, so we'll + # never know how many of those were nacked. + return 0 + + def basic_ack(self, delivery_tag=0, multiple=False): + """Acknowledge one or more messages. When sent by the client, this + method acknowledges one or more messages delivered via the Deliver or + Get-Ok methods. When sent by server, this method acknowledges one or + more messages published with the Publish method on a channel in + confirm mode. The acknowledgement can be for a single message or a + set of messages up to and including a specific message. + + :param int delivery-tag: The server-assigned delivery tag + :param bool multiple: If set to True, the 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 delivery tag refers to a single + message. If the multiple field is 1, and the + delivery tag is zero, this indicates + acknowledgement of all outstanding messages. + """ + self._impl.basic_ack(delivery_tag=delivery_tag, multiple=multiple) + self._flush_output() + + def basic_nack(self, delivery_tag=None, multiple=False, requeue=True): + """This method allows a client to reject one or more incoming messages. + It can be used to interrupt and cancel large incoming messages, or + return untreatable messages to their original queue. + + :param int delivery-tag: The server-assigned delivery tag + :param bool multiple: If set to True, the 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 delivery tag refers to a single + message. If the multiple field is 1, and the + delivery tag is zero, this indicates + acknowledgement of all outstanding messages. + :param bool requeue: If requeue is true, the server will attempt to + requeue the message. If requeue is false or the + requeue attempt fails the messages are discarded or + dead-lettered. + + """ + self._impl.basic_nack(delivery_tag=delivery_tag, multiple=multiple, + requeue=requeue) + self._flush_output() + + def basic_get(self, queue=None, no_ack=False): + """Get a single message from the AMQP broker. Returns a sequence with + the method frame, message properties, and body. + + :param queue: Name of queue to get a message from + :type queue: str or unicode + :param bool no_ack: Tell the broker to not expect a reply + :returns: a three-tuple; (None, None, None) if the queue was empty; + otherwise (method, properties, body); NOTE: body may be None + :rtype: (None, None, None)|(spec.Basic.GetOk, + spec.BasicProperties, + str or unicode or None) + """ + assert not self._basic_getempty_result + # NOTE: nested with for python 2.6 compatibility + with _CallbackResult(self._RxMessageArgs) as get_ok_result: + with self._basic_getempty_result: + self._impl.basic_get(callback=get_ok_result.set_value_once, + queue=queue, + no_ack=no_ack) + self._flush_output(get_ok_result.is_ready, + self._basic_getempty_result.is_ready) + if get_ok_result: + evt = get_ok_result.value + return evt.method, evt.properties, evt.body + else: + assert self._basic_getempty_result, ( + "wait completed without GetOk and GetEmpty") + return None, None, None + + def basic_publish(self, exchange, routing_key, body, + properties=None, mandatory=False, immediate=False): + """Publish to the channel with the given exchange, routing key and body. + Returns a boolean value indicating the success of the operation. + + This is the legacy BlockingChannel method for publishing. See also + `BlockingChannel.publish` that provides more information about failures. + + For more information on basic_publish and what the parameters do, see: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.publish + + NOTE: mandatory and immediate may be enabled even without delivery + confirmation, but in the absence of delivery confirmation the + synchronous implementation has no way to know how long to wait for + the Basic.Return or lack thereof. + + :param exchange: The exchange to publish to + :type exchange: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param body: The message body; empty string if no body + :type body: str or unicode + :param pika.spec.BasicProperties properties: message properties + :param bool mandatory: The mandatory flag + :param bool immediate: The immediate flag + + :returns: True if delivery confirmation is not enabled (NEW in pika + 0.10.0); otherwise returns False if the message could not be + delivered (Basic.nack and/or Basic.Return) and True if the message + was delivered (Basic.ack and no Basic.Return) + """ + try: + self.publish(exchange, routing_key, body, properties, + mandatory, immediate) + except (exceptions.NackError, exceptions.UnroutableError): + return False + else: + return True + + def publish(self, exchange, routing_key, body, + properties=None, mandatory=False, immediate=False): + """Publish to the channel with the given exchange, routing key, and + body. Unlike the legacy `BlockingChannel.basic_publish`, this method + provides more information about failures via exceptions. + + For more information on basic_publish and what the parameters do, see: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.publish + + NOTE: mandatory and immediate may be enabled even without delivery + confirmation, but in the absence of delivery confirmation the + synchronous implementation has no way to know how long to wait for + the Basic.Return. + + :param exchange: The exchange to publish to + :type exchange: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param body: The message body; empty string if no body + :type body: str or unicode + :param pika.spec.BasicProperties properties: message properties + :param bool mandatory: The mandatory flag + :param bool immediate: The immediate flag + + :raises UnroutableError: raised when a message published in + publisher-acknowledgments mode (see + `BlockingChannel.confirm_delivery`) is returned via `Basic.Return` + followed by `Basic.Ack`. + :raises NackError: raised when a message published in + publisher-acknowledgements mode is Nack'ed by the broker. See + `BlockingChannel.confirm_delivery`. + + """ + if self._delivery_confirmation: + # In publisher-acknowledgments mode + with self._message_confirmation_result: + self._impl.basic_publish(exchange=exchange, + routing_key=routing_key, + body=body, + properties=properties, + mandatory=mandatory, + immediate=immediate) + + self._flush_output(self._message_confirmation_result.is_ready) + conf_method = (self._message_confirmation_result.value + .method_frame + .method) + + if isinstance(conf_method, pika.spec.Basic.Nack): + # Broker was unable to process the message due to internal + # error + LOGGER.warning( + "Message was Nack'ed by broker: nack=%r; channel=%s; " + "exchange=%s; routing_key=%s; mandatory=%r; " + "immediate=%r", conf_method, self.channel_number, + exchange, routing_key, mandatory, immediate) + if self._puback_return is not None: + returned_messages = [self._puback_return] + self._puback_return = None + else: + returned_messages = [] + raise exceptions.NackError(returned_messages) + + else: + assert isinstance(conf_method, pika.spec.Basic.Ack), ( + conf_method) + + if self._puback_return is not None: + # Unroutable message was returned + messages = [self._puback_return] + self._puback_return = None + raise exceptions.UnroutableError(messages) + else: + # In non-publisher-acknowledgments mode + self._impl.basic_publish(exchange=exchange, + routing_key=routing_key, + body=body, + properties=properties, + mandatory=mandatory, + immediate=immediate) + self._flush_output() + + def basic_qos(self, prefetch_size=0, prefetch_count=0, all_channels=False): + """Specify quality of service. This method requests a specific quality + of service. The QoS can be specified for the current channel or for all + channels on the connection. The client can request that messages be sent + in advance so that when the client finishes processing a message, the + following message is already held locally, rather than needing to be + sent down the channel. Prefetching gives a performance improvement. + + :param int prefetch_size: This field specifies the prefetch window + size. The server will send a message in + advance if it is equal to or smaller in size + than the available prefetch size (and also + falls into other prefetch limits). May be set + to zero, meaning "no specific limit", + although other prefetch limits may still + apply. The prefetch-size is ignored if the + no-ack option is set in the consumer. + :param int prefetch_count: Specifies a prefetch window in terms of whole + messages. This field may be used in + combination with the prefetch-size field; a + message will only be sent in advance if both + prefetch windows (and those at the channel + and connection level) allow it. The + prefetch-count is ignored if the no-ack + option is set in the consumer. + :param bool all_channels: Should the QoS apply to all channels + + """ + with _CallbackResult() as qos_ok_result: + self._impl.basic_qos(callback=qos_ok_result.signal_once, + prefetch_size=prefetch_size, + prefetch_count=prefetch_count, + all_channels=all_channels) + self._flush_output(qos_ok_result.is_ready) + + def basic_recover(self, requeue=False): + """This method asks the server to redeliver all unacknowledged messages + on a specified channel. Zero or more messages may be redelivered. This + method replaces the asynchronous Recover. + + :param bool requeue: If False, the message will be redelivered to the + original recipient. If True, the server will + attempt to requeue the message, potentially then + delivering it to an alternative subscriber. + + """ + with _CallbackResult() as recover_ok_result: + self._impl.basic_recover(callback=recover_ok_result.signal_once, + requeue=requeue) + self._flush_output(recover_ok_result.is_ready) + + def basic_reject(self, delivery_tag=None, requeue=True): + """Reject an incoming message. This method allows a client to reject a + message. It can be used to interrupt and cancel large incoming messages, + or return untreatable messages to their original queue. + + :param int delivery-tag: The server-assigned delivery tag + :param bool requeue: If requeue is true, the server will attempt to + requeue the message. If requeue is false or the + requeue attempt fails the messages are discarded or + dead-lettered. + + """ + self._impl.basic_reject(delivery_tag=delivery_tag, requeue=requeue) + self._flush_output() + + def confirm_delivery(self): + """Turn on RabbitMQ-proprietary Confirm mode in the channel. + + For more information see: + http://www.rabbitmq.com/extensions.html#confirms + """ + if self._delivery_confirmation: + LOGGER.error('confirm_delivery: confirmation was already enabled ' + 'on channel=%s', self.channel_number) + return + + with _CallbackResult() as select_ok_result: + self._impl.add_callback(callback=select_ok_result.signal_once, + replies=[pika.spec.Confirm.SelectOk], + one_shot=True) + + self._impl.confirm_delivery( + callback=self._message_confirmation_result.set_value_once, + nowait=False) + + self._flush_output(select_ok_result.is_ready) + + self._delivery_confirmation = True + + # Unroutable messages returned after this point will be in the context + # of publisher acknowledgments + self._impl.add_on_return_callback(self._on_puback_message_returned) + + def exchange_declare(self, exchange=None, + exchange_type='direct', passive=False, durable=False, + auto_delete=False, internal=False, + arguments=None): + """This method creates an exchange if it does not already exist, and if + the exchange exists, verifies that it is of the correct and expected + class. + + If passive set, the server will reply with Declare-Ok if the exchange + already exists with the same name, and raise an error if not and if the + exchange does not already exist, the server MUST raise a channel + exception with reply code 404 (not found). + + :param exchange: The exchange name consists of a non-empty sequence of + these characters: letters, digits, hyphen, underscore, + period, or colon. + :type exchange: str or unicode + :param str exchange_type: The exchange type to use + :param bool passive: Perform a declare or just check to see if it exists + :param bool durable: Survive a reboot of RabbitMQ + :param bool auto_delete: Remove when no more queues are bound to it + :param bool internal: Can only be published to by other exchanges + :param dict arguments: Custom key/value pair arguments for the exchange + + :returns: Method frame from the Exchange.Declare-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Exchange.DeclareOk` + + """ + with _CallbackResult( + self._MethodFrameCallbackResultArgs) as declare_ok_result: + self._impl.exchange_declare( + callback=declare_ok_result.set_value_once, + exchange=exchange, + exchange_type=exchange_type, + passive=passive, + durable=durable, + auto_delete=auto_delete, + internal=internal, + nowait=False, + arguments=arguments) + + self._flush_output(declare_ok_result.is_ready) + return declare_ok_result.value.method_frame + + def exchange_delete(self, exchange=None, if_unused=False): + """Delete the exchange. + + :param exchange: The exchange name + :type exchange: str or unicode + :param bool if_unused: only delete if the exchange is unused + + :returns: Method frame from the Exchange.Delete-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Exchange.DeleteOk` + + """ + with _CallbackResult( + self._MethodFrameCallbackResultArgs) as delete_ok_result: + self._impl.exchange_delete( + callback=delete_ok_result.set_value_once, + exchange=exchange, + if_unused=if_unused, + nowait=False) + + self._flush_output(delete_ok_result.is_ready) + return delete_ok_result.value.method_frame + + def exchange_bind(self, destination=None, source=None, routing_key='', + arguments=None): + """Bind an exchange to another exchange. + + :param destination: The destination exchange to bind + :type destination: str or unicode + :param source: The source exchange to bind to + :type source: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param dict arguments: Custom key/value pair arguments for the binding + + :returns: Method frame from the Exchange.Bind-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Exchange.BindOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + bind_ok_result: + self._impl.exchange_bind( + callback=bind_ok_result.set_value_once, + destination=destination, + source=source, + routing_key=routing_key, + nowait=False, + arguments=arguments) + + self._flush_output(bind_ok_result.is_ready) + return bind_ok_result.value.method_frame + + def exchange_unbind(self, destination=None, source=None, routing_key='', + arguments=None): + """Unbind an exchange from another exchange. + + :param destination: The destination exchange to unbind + :type destination: str or unicode + :param source: The source exchange to unbind from + :type source: str or unicode + :param routing_key: The routing key to unbind + :type routing_key: str or unicode + :param dict arguments: Custom key/value pair arguments for the binding + + :returns: Method frame from the Exchange.Unbind-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Exchange.UnbindOk` + + """ + with _CallbackResult( + self._MethodFrameCallbackResultArgs) as unbind_ok_result: + self._impl.exchange_unbind( + callback=unbind_ok_result.set_value_once, + destination=destination, + source=source, + routing_key=routing_key, + nowait=False, + arguments=arguments) + + self._flush_output(unbind_ok_result.is_ready) + return unbind_ok_result.value.method_frame + + def queue_declare(self, queue='', passive=False, durable=False, + exclusive=False, auto_delete=False, + arguments=None): + """Declare queue, create if needed. This method creates or checks a + queue. When creating a new queue the client can specify various + properties that control the durability of the queue and its contents, + and the level of sharing for the queue. + + Leave the queue name empty for a auto-named queue in RabbitMQ + + :param queue: The queue name + :type queue: str or unicode; if empty string, the broker will create a + unique queue name; + :param bool passive: Only check to see if the queue exists and raise + `ChannelClosed` if it doesn't; + :param bool durable: Survive reboots of the broker + :param bool exclusive: Only allow access by the current connection + :param bool auto_delete: Delete after consumer cancels or disconnects + :param dict arguments: Custom key/value arguments for the queue + + :returns: Method frame from the Queue.Declare-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Queue.DeclareOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + declare_ok_result: + self._impl.queue_declare( + callback=declare_ok_result.set_value_once, + queue=queue, + passive=passive, + durable=durable, + exclusive=exclusive, + auto_delete=auto_delete, + nowait=False, + arguments=arguments) + + self._flush_output(declare_ok_result.is_ready) + return declare_ok_result.value.method_frame + + def queue_delete(self, queue='', if_unused=False, if_empty=False): + """Delete a queue from the broker. + + :param queue: The queue to delete + :type queue: str or unicode + :param bool if_unused: only delete if it's unused + :param bool if_empty: only delete if the queue is empty + + :returns: Method frame from the Queue.Delete-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Queue.DeleteOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + delete_ok_result: + self._impl.queue_delete(callback=delete_ok_result.set_value_once, + queue=queue, + if_unused=if_unused, + if_empty=if_empty, + nowait=False) + + self._flush_output(delete_ok_result.is_ready) + return delete_ok_result.value.method_frame + + def queue_purge(self, queue=''): + """Purge all of the messages from the specified queue + + :param queue: The queue to purge + :type queue: str or unicode + + :returns: Method frame from the Queue.Purge-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Queue.PurgeOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + purge_ok_result: + self._impl.queue_purge(callback=purge_ok_result.set_value_once, + queue=queue, + nowait=False) + + self._flush_output(purge_ok_result.is_ready) + return purge_ok_result.value.method_frame + + def queue_bind(self, queue, exchange, routing_key=None, + arguments=None): + """Bind the queue to the specified exchange + + :param queue: The queue to bind to the exchange + :type queue: str or unicode + :param exchange: The source exchange to bind to + :type exchange: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param dict arguments: Custom key/value pair arguments for the binding + + :returns: Method frame from the Queue.Bind-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Queue.BindOk` + + """ + with _CallbackResult( + self._MethodFrameCallbackResultArgs) as bind_ok_result: + self._impl.queue_bind(callback=bind_ok_result.set_value_once, + queue=queue, + exchange=exchange, + routing_key=routing_key, + nowait=False, + arguments=arguments) + + self._flush_output(bind_ok_result.is_ready) + return bind_ok_result.value.method_frame + + def queue_unbind(self, queue='', exchange=None, routing_key=None, + arguments=None): + """Unbind a queue from an exchange. + + :param queue: The queue to unbind from the exchange + :type queue: str or unicode + :param exchange: The source exchange to bind from + :type exchange: str or unicode + :param routing_key: The routing key to unbind + :type routing_key: str or unicode + :param dict arguments: Custom key/value pair arguments for the binding + + :returns: Method frame from the Queue.Unbind-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Queue.UnbindOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + unbind_ok_result: + self._impl.queue_unbind(callback=unbind_ok_result.set_value_once, + queue=queue, + exchange=exchange, + routing_key=routing_key, + arguments=arguments) + self._flush_output(unbind_ok_result.is_ready) + return unbind_ok_result.value.method_frame + + def tx_select(self): + """Select standard transaction mode. This method sets the channel to use + standard transactions. The client must use this method at least once on + a channel before using the Commit or Rollback methods. + + :returns: Method frame from the Tx.Select-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Tx.SelectOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + select_ok_result: + self._impl.tx_select(select_ok_result.set_value_once) + + self._flush_output(select_ok_result.is_ready) + return select_ok_result.value.method_frame + + def tx_commit(self): + """Commit a transaction. + + :returns: Method frame from the Tx.Commit-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Tx.CommitOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + commit_ok_result: + self._impl.tx_commit(commit_ok_result.set_value_once) + + self._flush_output(commit_ok_result.is_ready) + return commit_ok_result.value.method_frame + + def tx_rollback(self): + """Rollback a transaction. + + :returns: Method frame from the Tx.Commit-ok response + :rtype: `pika.frame.Method` having `method` attribute of type + `spec.Tx.CommitOk` + + """ + with _CallbackResult(self._MethodFrameCallbackResultArgs) as \ + rollback_ok_result: + self._impl.tx_rollback(rollback_ok_result.set_value_once) + + self._flush_output(rollback_ok_result.is_ready) + return rollback_ok_result.value.method_frame diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/select_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/select_connection.py new file mode 100644 index 000000000..29d856989 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/select_connection.py @@ -0,0 +1,1178 @@ +"""A connection adapter that tries to use the best polling method for the +platform pika is running on. + +""" +import abc +import collections +import errno +import functools +import heapq +import logging +import select +import time +import threading + +import pika.compat + +from pika.adapters.base_connection import BaseConnection + +LOGGER = logging.getLogger(__name__) + +# One of select, epoll, kqueue or poll +SELECT_TYPE = None + +# Use epoll's constants to keep life easy +READ = 0x0001 +WRITE = 0x0004 +ERROR = 0x0008 + +# Reason for this unconventional dict initialization is the fact that on some +# platforms select.error is an aliases for OSError. We don't want the lambda +# for select.error to win over one for OSError. +_SELECT_ERROR_CHECKERS = {} +if pika.compat.PY3: + #InterruptedError is undefined in PY2 + #pylint: disable=E0602 + _SELECT_ERROR_CHECKERS[InterruptedError] = lambda e: True +_SELECT_ERROR_CHECKERS[select.error] = lambda e: e.args[0] == errno.EINTR +_SELECT_ERROR_CHECKERS[IOError] = lambda e: e.errno == errno.EINTR +_SELECT_ERROR_CHECKERS[OSError] = lambda e: e.errno == errno.EINTR + +# We can reduce the number of elements in the list by looking at super-sub +# class relationship because only the most generic ones needs to be caught. +# For now the optimization is left out. +# Following is better but still incomplete. +#_SELECT_ERRORS = tuple(filter(lambda e: not isinstance(e, OSError), +# _SELECT_ERROR_CHECKERS.keys()) +# + [OSError]) +_SELECT_ERRORS = tuple(_SELECT_ERROR_CHECKERS.keys()) + + +def _is_resumable(exc): + ''' Check if caught exception represents EINTR error. + :param exc: exception; must be one of classes in _SELECT_ERRORS ''' + checker = _SELECT_ERROR_CHECKERS.get(exc.__class__, None) + if checker is not None: + return checker(exc) + else: + return False + + +class SelectConnection(BaseConnection): + """An asynchronous connection adapter that attempts to use the fastest + event loop adapter for the given platform. + + """ + + def __init__( + self, # pylint: disable=R0913 + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None, + stop_ioloop_on_close=True, + custom_ioloop=None): + """Create a new instance of the Connection object. + + :param pika.connection.Parameters parameters: Connection parameters + :param method on_open_callback: Method to call on connection open + :param method on_open_error_callback: Called if the connection can't + be established: on_open_error_callback(connection, str|exception) + :param method on_close_callback: Called when the connection is closed: + on_close_callback(connection, reason_code, reason_text) + :param bool stop_ioloop_on_close: Call ioloop.stop() if disconnected + :param custom_ioloop: Override using the global IOLoop in Tornado + :raises: RuntimeError + + """ + ioloop = custom_ioloop or IOLoop() + super(SelectConnection, self).__init__( + parameters, on_open_callback, on_open_error_callback, + on_close_callback, ioloop, stop_ioloop_on_close) + + def _adapter_connect(self): + """Connect to the RabbitMQ broker, returning True on success, False + on failure. + + :rtype: bool + + """ + error = super(SelectConnection, self)._adapter_connect() + if not error: + self.ioloop.add_handler(self.socket.fileno(), self._handle_events, + self.event_state) + return error + + def _adapter_disconnect(self): + """Disconnect from the RabbitMQ broker""" + if self.socket: + self.ioloop.remove_handler(self.socket.fileno()) + super(SelectConnection, self)._adapter_disconnect() + + +@functools.total_ordering +class _Timeout(object): + """Represents a timeout""" + + __slots__ = ('deadline', 'callback',) + + def __init__(self, deadline, callback): + """ + :param float deadline: timer expiration as non-negative epoch number + :param callable callback: callback to call when timeout expires + :raises ValueError, TypeError: + """ + + if deadline < 0: + raise ValueError( + 'deadline must be non-negative epoch number, but got %r' % + (deadline,)) + + if not callable(callback): + raise TypeError( + 'callback must be a callable, but got %r' % (callback,)) + + self.deadline = deadline + self.callback = callback + + def __eq__(self, other): + """NOTE: not supporting sort stability""" + return self.deadline == other.deadline + + def __lt__(self, other): + """NOTE: not supporting sort stability""" + return self.deadline < other.deadline + + def __le__(self, other): + """NOTE: not supporting sort stability""" + return self.deadline <= other.deadline + + +class _Timer(object): + """Manage timeouts for use in ioloop""" + + # Cancellation count threshold for triggering garbage collection of + # cancelled timers + _GC_CANCELLATION_THRESHOLD = 1024 + + def __init__(self): + self._timeout_heap = [] + + # Number of canceled timeouts on heap; for scheduling garbage + # collection of canceled timeouts + self._num_cancellations = 0 + + def close(self): + """Release resources. Don't use the `_Timer` instance after closing + it + """ + # Eliminate potential reference cycles to aid garbage-collection + if self._timeout_heap is not None: + for timeout in self._timeout_heap: + timeout.callback = None + self._timeout_heap = None + + def call_later(self, delay, callback): + """Schedule a one-shot timeout given delay seconds. + + NOTE: you may cancel the timer before dispatch of the callback. Timer + Manager cancels the timer upon dispatch of the callback. + + :param float delay: Non-negative number of seconds from now until + expiration + :param method callback: The callback method, having the signature + `callback()` + + :rtype: _Timeout + :raises ValueError, TypeError + + """ + if delay < 0: + raise ValueError( + 'call_later: delay must be non-negative, but got %r' + % (delay,)) + + now = time.time() + + timeout = _Timeout(now + delay, callback) + + heapq.heappush(self._timeout_heap, timeout) + + LOGGER.debug('call_later: added timeout %r with deadline=%r and ' + 'callback=%r; now=%s; delay=%s', timeout, timeout.deadline, + timeout.callback, now, delay) + + return timeout + + def remove_timeout(self, timeout): + """Cancel the timeout + + :param _Timeout timeout: The timer to cancel + + """ + # NOTE removing from the heap is difficult, so we just deactivate the + # timeout and garbage-collect it at a later time; see discussion + # in http://docs.python.org/library/heapq.html + if timeout.callback is None: + LOGGER.warning( + 'remove_timeout: timeout was already removed or called %r', + timeout) + else: + LOGGER.debug('remove_timeout: removing timeout %r with deadline=%r ' + 'and callback=%r', timeout, timeout.deadline, + timeout.callback) + timeout.callback = None + self._num_cancellations += 1 + + def get_remaining_interval(self): + """Get the interval to the next timeout expiration + + :returns: non-negative number of seconds until next timer expiration; + None if there are no timers + :rtype: float + + """ + if self._timeout_heap: + interval = max(0, self._timeout_heap[0].deadline - time.time()) + else: + interval = None + + return interval + + def process_timeouts(self): + """Process pending timeouts, invoking callbacks for those whose time has + come + + """ + if self._timeout_heap: + now = time.time() + + # Remove ready timeouts from the heap now to prevent IO starvation + # from timeouts added during callback processing + ready_timeouts = [] + + while self._timeout_heap and self._timeout_heap[0].deadline <= now: + timeout = heapq.heappop(self._timeout_heap) + if timeout.callback is not None: + ready_timeouts.append(timeout) + else: + self._num_cancellations -= 1 + + # Invoke ready timeout callbacks + for timeout in ready_timeouts: + if timeout.callback is None: + # Must have been canceled from a prior callback + self._num_cancellations -= 1 + continue + + timeout.callback() + timeout.callback = None + + # Garbage-collect canceled timeouts if they exceed threshold + if (self._num_cancellations >= self._GC_CANCELLATION_THRESHOLD and + self._num_cancellations > (len(self._timeout_heap) >> 1)): + self._num_cancellations = 0 + self._timeout_heap = [t for t in self._timeout_heap + if t.callback is not None] + heapq.heapify(self._timeout_heap) + + +class IOLoop(object): + """Singleton wrapper that decides which type of poller to use, creates an + instance of it in start_poller and keeps the invoking application in a + blocking state by calling the pollers start method. Poller should keep + looping until IOLoop.instance().stop() is called or there is a socket + error. + + Passes through all operations to the loaded poller object. + + """ + + def __init__(self): + self._timer = _Timer() + + # Callbacks requested via `add_callback` + self._callbacks = collections.deque() + + self._poller = self._get_poller(self._get_remaining_interval, + self.process_timeouts) + + def close(self): + """Release IOLoop's resources. + + `IOLoop.close` is intended to be called by the application or test code + only after `IOLoop.start()` returns. After calling `close()`, no other + interaction with the closed instance of `IOLoop` should be performed. + + """ + if self._callbacks is not None: + self._poller.close() + self._timer.close() + self._callbacks = None + + @staticmethod + def _get_poller(get_wait_seconds, process_timeouts): + """Determine the best poller to use for this environment and instantiate + it. + + :param get_wait_seconds: Function for getting the maximum number of + seconds to wait for IO for use by the poller + :param process_timeouts: Function for processing timeouts for use by the + poller + + :returns: the instantiated poller instance supporting `_PollerBase` API + """ + + poller = None + + kwargs = dict(get_wait_seconds=get_wait_seconds, + process_timeouts=process_timeouts) + + if hasattr(select, 'epoll'): + if not SELECT_TYPE or SELECT_TYPE == 'epoll': + LOGGER.debug('Using EPollPoller') + poller = EPollPoller(**kwargs) + + if not poller and hasattr(select, 'kqueue'): + if not SELECT_TYPE or SELECT_TYPE == 'kqueue': + LOGGER.debug('Using KQueuePoller') + poller = KQueuePoller(**kwargs) + + if (not poller and hasattr(select, 'poll') and + hasattr(select.poll(), 'modify')): # pylint: disable=E1101 + if not SELECT_TYPE or SELECT_TYPE == 'poll': + LOGGER.debug('Using PollPoller') + poller = PollPoller(**kwargs) + + if not poller: + LOGGER.debug('Using SelectPoller') + poller = SelectPoller(**kwargs) + + return poller + + def add_timeout(self, deadline, callback_method): + """[API] Add the callback_method to the IOLoop timer to fire after + deadline seconds. Returns a handle to the timeout. Do not confuse with + Tornado's timeout where you pass in the time you want to have your + callback called. Only pass in the seconds until it's to be called. + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + :rtype: str + + """ + return self._timer.call_later(deadline, callback_method) + + def remove_timeout(self, timeout_id): + """[API] Remove a timeout + + :param str timeout_id: The timeout id to remove + + """ + self._timer.remove_timeout(timeout_id) + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this IOLoop's thread. + + NOTE: This is the only thread-safe method in IOLoop. All other + manipulations of IOLoop must be performed from the IOLoop's thread. + + For example, a thread may request a call to the `stop` method of an + ioloop that is running in a different thread via + `ioloop.add_callback_threadsafe(ioloop.stop)` + + NOTE: if you know that the requester is running on the same thread as + the connection it is more efficient to use the + `call_later()` method with a delay of 0. + + :param method callback: The callback method + + """ + if not callable(callback): + raise TypeError( + 'callback must be a callable, but got %r' % (callback,)) + + # NOTE: `deque.append` is atomic + self._callbacks.append(callback) + + # Wake up the IOLoop which may be running in another thread + self._poller.wake_threadsafe() + + LOGGER.debug('add_callback_threadsafe: added callback=%r', callback) + + def process_timeouts(self): + """[Extension] Process pending callbacks and timeouts, invoking those + whose time has come. Internal use only. + + """ + # Avoid I/O starvation by postponing new callbacks to the next iteration + for _ in pika.compat.xrange(len(self._callbacks)): + self._callbacks.popleft()() + + self._timer.process_timeouts() + + def _get_remaining_interval(self): + """Get the remaining interval to the next callback or timeout + expiration. + + :returns: non-negative number of seconds until next callback or timer + expiration; None if there are no callbacks and timers + :rtype: float + + """ + if self._callbacks: + return 0 + + return self._timer.get_remaining_interval() + + def add_handler(self, fileno, handler, events): + """[API] Add a new fileno to the set to be monitored + + :param int fileno: The file descriptor + :param method handler: What is called when an event happens + :param int events: The event mask using READ, WRITE, ERROR + + """ + self._poller.add_handler(fileno, handler, events) + + def update_handler(self, fileno, events): + """[API] Set the events to the current events + + :param int fileno: The file descriptor + :param int events: The event mask using READ, WRITE, ERROR + + """ + self._poller.update_handler(fileno, events) + + def remove_handler(self, fileno): + """[API] Remove a file descriptor from the set + + :param int fileno: The file descriptor + + """ + self._poller.remove_handler(fileno) + + def start(self): + """[API] Start the main poller loop. It will loop until requested to + exit. See `IOLoop.stop`. + + """ + self._poller.start() + + def stop(self): + """[API] Request exit from the ioloop. The loop is NOT guaranteed to + stop before this method returns. + + To invoke `stop()` safely from a thread other than this IOLoop's thread, + call it via `add_callback_threadsafe`; e.g., + + `ioloop.add_callback_threadsafe(ioloop.stop)` + + """ + self._poller.stop() + + def activate_poller(self): + """[Extension] Activate the poller + + """ + self._thread_id = threading.current_thread().ident + self._poller.activate_poller() + + def deactivate_poller(self): + """[Extension] Deactivate the poller + + """ + self._poller.deactivate_poller() + + def poll(self): + """[Extension] Wait for events of interest on registered file + descriptors until an event of interest occurs or next timer deadline or + `_PollerBase._MAX_POLL_TIMEOUT`, whichever is sooner, and dispatch the + corresponding event handlers. + + """ + self._poller.poll() + + +_AbstractBase = abc.ABCMeta('_AbstractBase', (object,), {}) + + +class _PollerBase(_AbstractBase): # pylint: disable=R0902 + """Base class for select-based IOLoop implementations""" + + # Drop out of the poll loop every _MAX_POLL_TIMEOUT secs as a worst case; + # this is only a backstop value; we will run timeouts when they are + # scheduled. + _MAX_POLL_TIMEOUT = 5 + + # if the poller uses MS override with 1000 + POLL_TIMEOUT_MULT = 1 + + def __init__(self, get_wait_seconds, process_timeouts): + """ + :param get_wait_seconds: Function for getting the maximum number of + seconds to wait for IO for use by the poller + :param process_timeouts: Function for processing timeouts for use by the + poller + + """ + self._get_wait_seconds = get_wait_seconds + self._process_timeouts = process_timeouts + + # We guard access to the waking file descriptors to avoid races from + # closing them while another thread is calling our `wake()` method. + self._waking_mutex = threading.Lock() + + # fd-to-handler function mappings + self._fd_handlers = dict() + + # event-to-fdset mappings + self._fd_events = {READ: set(), WRITE: set(), ERROR: set()} + + self._processing_fd_event_map = {} + + # Reentrancy tracker of the `start` method + self._start_nesting_levels = 0 + + self._stopping = False + + # Create ioloop-interrupt socket pair and register read handler. + self._r_interrupt, self._w_interrupt = self._get_interrupt_pair() + self.add_handler(self._r_interrupt.fileno(), self._read_interrupt, READ) + + def close(self): + """Release poller's resources. + + `close()` is intended to be called after the poller's `start()` method + returns. After calling `close()`, no other interaction with the closed + poller instance should be performed. + + """ + # Unregister and close ioloop-interrupt socket pair; mutual exclusion is + # necessary to avoid race condition with `wake_threadsafe` executing in + # another thread's context + assert self._start_nesting_levels == 0, \ + 'Cannot call close() before start() unwinds.' + + with self._waking_mutex: + if self._w_interrupt is not None: + self.remove_handler(self._r_interrupt.fileno()) # pylint: disable=E1101 + self._r_interrupt.close() + self._r_interrupt = None + self._w_interrupt.close() + self._w_interrupt = None + + self.deactivate_poller() + + self._fd_handlers = None + self._fd_events = None + self._processing_fd_event_map = None + + def wake_threadsafe(self): + """Wake up the poller as soon as possible. As the name indicates, this + method is thread-safe. + + """ + with self._waking_mutex: + if self._w_interrupt is None: + return + + try: + # Send byte to interrupt the poll loop, use send() instead of + # os.write for Windows compatibility + self._w_interrupt.send(b'X') + except pika.compat.SOCKET_ERROR as err: + if err.errno != errno.EWOULDBLOCK: + raise + except Exception as err: + # There's nothing sensible to do here, we'll exit the interrupt + # loop after POLL_TIMEOUT secs in worst case anyway. + LOGGER.warning("Failed to send interrupt to poller: %s", err) + raise + + + def _get_max_wait(self): + """Get the interval to the next timeout event, or a default interval + + :returns: maximum number of self.POLL_TIMEOUT_MULT-scaled time units + to wait for IO events + + """ + delay = self._get_wait_seconds() + if delay is None: + delay = self._MAX_POLL_TIMEOUT + else: + delay = min(delay, self._MAX_POLL_TIMEOUT) + + return delay * self.POLL_TIMEOUT_MULT + + def add_handler(self, fileno, handler, events): + """Add a new fileno to the set to be monitored + + :param int fileno: The file descriptor + :param method handler: What is called when an event happens + :param int events: The event mask using READ, WRITE, ERROR + + """ + self._fd_handlers[fileno] = handler + self._set_handler_events(fileno, events) + + # Inform the derived class + self._register_fd(fileno, events) + + def update_handler(self, fileno, events): + """Set the events to the current events + + :param int fileno: The file descriptor + :param int events: The event mask using READ, WRITE, ERROR + + """ + # Record the change + events_cleared, events_set = self._set_handler_events(fileno, events) + + # Inform the derived class + self._modify_fd_events( + fileno, + events=events, + events_to_clear=events_cleared, + events_to_set=events_set) + + def remove_handler(self, fileno): + """Remove a file descriptor from the set + + :param int fileno: The file descriptor + + """ + try: + del self._processing_fd_event_map[fileno] + except KeyError: + pass + + events_cleared, _ = self._set_handler_events(fileno, 0) + del self._fd_handlers[fileno] + + # Inform the derived class + self._unregister_fd(fileno, events_to_clear=events_cleared) + + def _set_handler_events(self, fileno, events): + """Set the handler's events to the given events; internal to + `_PollerBase`. + + :param int fileno: The file descriptor + :param int events: The event mask (READ, WRITE, ERROR) + + :returns: a 2-tuple (events_cleared, events_set) + """ + events_cleared = 0 + events_set = 0 + + for evt in (READ, WRITE, ERROR): + if events & evt: + if fileno not in self._fd_events[evt]: + self._fd_events[evt].add(fileno) + events_set |= evt + else: + if fileno in self._fd_events[evt]: + self._fd_events[evt].discard(fileno) + events_cleared |= evt + + return events_cleared, events_set + + def activate_poller(self): + """Activate the poller + + """ + # Activate the underlying poller and register current events + self._init_poller() + fd_to_events = collections.defaultdict(int) + for event, file_descriptors in self._fd_events.items(): + for fileno in file_descriptors: + fd_to_events[fileno] |= event + + for fileno, events in fd_to_events.items(): + self._register_fd(fileno, events) + + def deactivate_poller(self): + """Deactivate the poller + + """ + self._uninit_poller() + + def start(self): + """Start the main poller loop. It will loop until requested to exit + + """ + self._start_nesting_levels += 1 + + if self._start_nesting_levels == 1: + LOGGER.debug('Entering IOLoop') + + # Activate the underlying poller and register current events + self.activate_poller() + + else: + LOGGER.debug('Reentering IOLoop at nesting level=%s', + self._start_nesting_levels) + + try: + # Run event loop + while not self._stopping: + self.poll() + self._process_timeouts() + + finally: + self._start_nesting_levels -= 1 + + if self._start_nesting_levels == 0: + try: + LOGGER.debug('Deactivating poller') + + # Deactivate the underlying poller + self.deactivate_poller() + finally: + self._stopping = False + else: + LOGGER.debug('Leaving IOLoop with %s nesting levels remaining', + self._start_nesting_levels) + + def stop(self): + """Request exit from the ioloop. The loop is NOT guaranteed to stop + before this method returns. + + """ + LOGGER.debug('Stopping IOLoop') + self._stopping = True + + self.wake_threadsafe() + + @abc.abstractmethod + def poll(self): + """Wait for events on interested filedescriptors. + """ + raise NotImplementedError + + @abc.abstractmethod + def _init_poller(self): + """Notify the implementation to allocate the poller resource""" + raise NotImplementedError + + @abc.abstractmethod + def _uninit_poller(self): + """Notify the implementation to release the poller resource""" + raise NotImplementedError + + @abc.abstractmethod + def _register_fd(self, fileno, events): + """The base class invokes this method to notify the implementation to + register the file descriptor with the polling object. The request must + be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: The event mask (READ, WRITE, ERROR) + """ + raise NotImplementedError + + @abc.abstractmethod + def _modify_fd_events(self, fileno, events, events_to_clear, events_to_set): + """The base class invoikes this method to notify the implementation to + modify an already registered file descriptor. The request must be + ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: absolute events (READ, WRITE, ERROR) + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + :param int events_to_set: The events to set (READ, WRITE, ERROR) + """ + raise NotImplementedError + + @abc.abstractmethod + def _unregister_fd(self, fileno, events_to_clear): + """The base class invokes this method to notify the implementation to + unregister the file descriptor being tracked by the polling object. The + request must be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + """ + raise NotImplementedError + + def _dispatch_fd_events(self, fd_event_map): + """ Helper to dispatch callbacks for file descriptors that received + events. + + Before doing so we re-calculate the event mask based on what is + currently set in case it has been changed under our feet by a + previous callback. We also take a store a refernce to the + fd_event_map so that we can detect removal of an + fileno during processing of another callback and not generate + spurious callbacks on it. + + :param dict fd_event_map: Map of fds to events received on them. + """ + # Reset the prior map; if the call is nested, this will suppress the + # remaining dispatch in the earlier call. + self._processing_fd_event_map.clear() + + self._processing_fd_event_map = fd_event_map + + for fileno in pika.compat.dictkeys(fd_event_map): + if fileno not in fd_event_map: + # the fileno has been removed from the map under our feet. + continue + + events = fd_event_map[fileno] + for evt in [READ, WRITE, ERROR]: + if fileno not in self._fd_events[evt]: + events &= ~evt + + if events: + handler = self._fd_handlers[fileno] + handler(fileno, events) + + @staticmethod + def _get_interrupt_pair(): + """ Use a socketpair to be able to interrupt the ioloop if called + from another thread. Socketpair() is not supported on some OS (Win) + so use a pair of simple TCP sockets instead. The sockets will be + closed and garbage collected by python when the ioloop itself is. + """ + return pika.compat._nonblocking_socketpair() # pylint: disable=W0212 + + def _read_interrupt(self, interrupt_fd, events): # pylint: disable=W0613 + """ Read the interrupt byte(s). We ignore the event mask as we can ony + get here if there's data to be read on our fd. + + :param int interrupt_fd: The file descriptor to read from + :param int events: (unused) The events generated for this fd + """ + try: + # NOTE Use recv instead of os.read for windows compatibility + self._r_interrupt.recv(512) # pylint: disable=E1101 + except pika.compat.SOCKET_ERROR as err: + if err.errno != errno.EAGAIN: + raise + + +class SelectPoller(_PollerBase): + """Default behavior is to use Select since it's the widest supported and has + all of the methods we need for child classes as well. One should only need + to override the update_handler and start methods for additional types. + + """ + # if the poller uses MS specify 1000 + POLL_TIMEOUT_MULT = 1 + + def poll(self): + """Wait for events of interest on registered file descriptors until an + event of interest occurs or next timer deadline or _MAX_POLL_TIMEOUT, + whichever is sooner, and dispatch the corresponding event handlers. + + """ + while True: + try: + if (self._fd_events[READ] or self._fd_events[WRITE] or + self._fd_events[ERROR]): + read, write, error = select.select( + self._fd_events[READ], self._fd_events[WRITE], + self._fd_events[ERROR], self._get_max_wait()) + else: + # NOTE When called without any FDs, select fails on + # Windows with error 10022, 'An invalid argument was + # supplied'. + time.sleep(self._get_max_wait()) + read, write, error = [], [], [] + break + except _SELECT_ERRORS as error: + if _is_resumable(error): + continue + else: + raise + + # Build an event bit mask for each fileno we've received an event for + + fd_event_map = collections.defaultdict(int) + for fd_set, evt in zip((read, write, error), (READ, WRITE, ERROR)): + for fileno in fd_set: + fd_event_map[fileno] |= evt + + self._dispatch_fd_events(fd_event_map) + + def _init_poller(self): + """Notify the implementation to allocate the poller resource""" + # It's a no op in SelectPoller + pass + + def _uninit_poller(self): + """Notify the implementation to release the poller resource""" + # It's a no op in SelectPoller + pass + + def _register_fd(self, fileno, events): + """The base class invokes this method to notify the implementation to + register the file descriptor with the polling object. The request must + be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: The event mask using READ, WRITE, ERROR + """ + # It's a no op in SelectPoller + pass + + def _modify_fd_events(self, fileno, events, events_to_clear, events_to_set): + """The base class invoikes this method to notify the implementation to + modify an already registered file descriptor. The request must be + ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: absolute events (READ, WRITE, ERROR) + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + :param int events_to_set: The events to set (READ, WRITE, ERROR) + """ + # It's a no op in SelectPoller + pass + + def _unregister_fd(self, fileno, events_to_clear): + """The base class invokes this method to notify the implementation to + unregister the file descriptor being tracked by the polling object. The + request must be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + """ + # It's a no op in SelectPoller + pass + + +class KQueuePoller(_PollerBase): + """KQueuePoller works on BSD based systems and is faster than select""" + + def __init__(self, get_wait_seconds, process_timeouts): + """Create an instance of the KQueuePoller + """ + self._kqueue = None + super(KQueuePoller, self).__init__(get_wait_seconds, process_timeouts) + + @staticmethod + def _map_event(kevent): + """return the event type associated with a kevent object + + :param kevent kevent: a kevent object as returned by kqueue.control() + + """ + if kevent.filter == select.KQ_FILTER_READ: + return READ + elif kevent.filter == select.KQ_FILTER_WRITE: + return WRITE + elif kevent.flags & select.KQ_EV_ERROR: + return ERROR + + # Should never happen + return None + + def poll(self): + """Wait for events of interest on registered file descriptors until an + event of interest occurs or next timer deadline or _MAX_POLL_TIMEOUT, + whichever is sooner, and dispatch the corresponding event handlers. + + """ + while True: + try: + kevents = self._kqueue.control(None, 1000, + self._get_max_wait()) + break + except _SELECT_ERRORS as error: + if _is_resumable(error): + continue + else: + raise + + fd_event_map = collections.defaultdict(int) + for event in kevents: + fd_event_map[event.ident] |= self._map_event(event) + + self._dispatch_fd_events(fd_event_map) + + def _init_poller(self): + """Notify the implementation to allocate the poller resource""" + assert self._kqueue is None + + self._kqueue = select.kqueue() + + def _uninit_poller(self): + """Notify the implementation to release the poller resource""" + if self._kqueue is not None: + self._kqueue.close() + self._kqueue = None + + def _register_fd(self, fileno, events): + """The base class invokes this method to notify the implementation to + register the file descriptor with the polling object. The request must + be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: The event mask using READ, WRITE, ERROR + """ + self._modify_fd_events( + fileno, events=events, events_to_clear=0, events_to_set=events) + + def _modify_fd_events(self, fileno, events, events_to_clear, events_to_set): + """The base class invoikes this method to notify the implementation to + modify an already registered file descriptor. The request must be + ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: absolute events (READ, WRITE, ERROR) + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + :param int events_to_set: The events to set (READ, WRITE, ERROR) + """ + if self._kqueue is None: + return + + kevents = list() + + if events_to_clear & READ: + kevents.append( + select.kevent( + fileno, + filter=select.KQ_FILTER_READ, + flags=select.KQ_EV_DELETE)) + if events_to_set & READ: + kevents.append( + select.kevent( + fileno, + filter=select.KQ_FILTER_READ, + flags=select.KQ_EV_ADD)) + if events_to_clear & WRITE: + kevents.append( + select.kevent( + fileno, + filter=select.KQ_FILTER_WRITE, + flags=select.KQ_EV_DELETE)) + if events_to_set & WRITE: + kevents.append( + select.kevent( + fileno, + filter=select.KQ_FILTER_WRITE, + flags=select.KQ_EV_ADD)) + + self._kqueue.control(kevents, 0) + + def _unregister_fd(self, fileno, events_to_clear): + """The base class invokes this method to notify the implementation to + unregister the file descriptor being tracked by the polling object. The + request must be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + """ + self._modify_fd_events( + fileno, events=0, events_to_clear=events_to_clear, events_to_set=0) + + +class PollPoller(_PollerBase): + """Poll works on Linux and can have better performance than EPoll in + certain scenarios. Both are faster than select. + + """ + POLL_TIMEOUT_MULT = 1000 + + def __init__(self, get_wait_seconds, process_timeouts): + """Create an instance of the KQueuePoller + + """ + self._poll = None + super(PollPoller, self).__init__(get_wait_seconds, process_timeouts) + + @staticmethod + def _create_poller(): + """ + :rtype: `select.poll` + """ + return select.poll() # pylint: disable=E1101 + + def poll(self): + """Wait for events of interest on registered file descriptors until an + event of interest occurs or next timer deadline or _MAX_POLL_TIMEOUT, + whichever is sooner, and dispatch the corresponding event handlers. + + """ + while True: + try: + events = self._poll.poll(self._get_max_wait()) + break + except _SELECT_ERRORS as error: + if _is_resumable(error): + continue + else: + raise + + fd_event_map = collections.defaultdict(int) + for fileno, event in events: + fd_event_map[fileno] |= event + + self._dispatch_fd_events(fd_event_map) + + def _init_poller(self): + """Notify the implementation to allocate the poller resource""" + assert self._poll is None + + self._poll = self._create_poller() + + def _uninit_poller(self): + """Notify the implementation to release the poller resource""" + if self._poll is not None: + if hasattr(self._poll, "close"): + self._poll.close() + + self._poll = None + + def _register_fd(self, fileno, events): + """The base class invokes this method to notify the implementation to + register the file descriptor with the polling object. The request must + be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: The event mask using READ, WRITE, ERROR + """ + if self._poll is not None: + self._poll.register(fileno, events) + + def _modify_fd_events(self, fileno, events, events_to_clear, events_to_set): + """The base class invoikes this method to notify the implementation to + modify an already registered file descriptor. The request must be + ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events: absolute events (READ, WRITE, ERROR) + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + :param int events_to_set: The events to set (READ, WRITE, ERROR) + """ + if self._poll is not None: + self._poll.modify(fileno, events) + + def _unregister_fd(self, fileno, events_to_clear): + """The base class invokes this method to notify the implementation to + unregister the file descriptor being tracked by the polling object. The + request must be ignored if the poller is not activated. + + :param int fileno: The file descriptor + :param int events_to_clear: The events to clear (READ, WRITE, ERROR) + """ + if self._poll is not None: + self._poll.unregister(fileno) + + +class EPollPoller(PollPoller): + """EPoll works on Linux and can have better performance than Poll in + certain scenarios. Both are faster than select. + + """ + POLL_TIMEOUT_MULT = 1 + + @staticmethod + def _create_poller(): + """ + :rtype: `select.poll` + """ + return select.epoll() # pylint: disable=E1101 diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/tornado_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/tornado_connection.py new file mode 100644 index 000000000..db34dfd99 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/tornado_connection.py @@ -0,0 +1,122 @@ +"""Use pika with the Tornado IOLoop""" +from tornado import ioloop +import logging +import time + +from pika.adapters import base_connection + +LOGGER = logging.getLogger(__name__) + + +class TornadoConnection(base_connection.BaseConnection): + """The TornadoConnection runs on the Tornado IOLoop. If you're running the + connection in a web app, make sure you set stop_ioloop_on_close to False, + which is the default behavior for this adapter, otherwise the web app + will stop taking requests. + + :param pika.connection.Parameters parameters: Connection parameters + :param on_open_callback: The method to call when the connection is open + :type on_open_callback: method + :param on_open_error_callback: Method to call if the connection cant + be opened + :type on_open_error_callback: method + :param bool stop_ioloop_on_close: Call ioloop.stop() if disconnected + :param custom_ioloop: Override using the global IOLoop in Tornado + + """ + WARN_ABOUT_IOLOOP = True + + def __init__(self, + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None, + stop_ioloop_on_close=False, + custom_ioloop=None): + """Create a new instance of the TornadoConnection class, connecting + to RabbitMQ automatically + + :param pika.connection.Parameters parameters: Connection parameters + :param on_open_callback: The method to call when the connection is open + :type on_open_callback: method + :param method on_open_error_callback: Called if the connection can't + be established: on_open_error_callback(connection, str|exception) + :param method on_close_callback: Called when the connection is closed: + on_close_callback(connection, reason_code, reason_text) + :param bool stop_ioloop_on_close: Call ioloop.stop() if disconnected + :param custom_ioloop: Override using the global IOLoop in Tornado + + """ + self.sleep_counter = 0 + self.ioloop = custom_ioloop or ioloop.IOLoop.instance() + super(TornadoConnection, self).__init__(parameters, on_open_callback, + on_open_error_callback, + on_close_callback, self.ioloop, + stop_ioloop_on_close) + + def _adapter_connect(self): + """Connect to the remote socket, adding the socket to the IOLoop if + connected. + + :rtype: bool + + """ + error = super(TornadoConnection, self)._adapter_connect() + if not error: + self.ioloop.add_handler(self.socket.fileno(), self._handle_events, + self.event_state) + return error + + def _adapter_disconnect(self): + """Disconnect from the RabbitMQ broker""" + if self.socket: + self.ioloop.remove_handler(self.socket.fileno()) + super(TornadoConnection, self)._adapter_disconnect() + + def add_timeout(self, deadline, callback_method): + """Add the callback_method to the IOLoop timer to fire after deadline + seconds. Returns a handle to the timeout. Do not confuse with + Tornado's timeout where you pass in the time you want to have your + callback called. Only pass in the seconds until it's to be called. + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + :rtype: str + + """ + return self.ioloop.add_timeout(time.time() + deadline, callback_method) + + def remove_timeout(self, timeout_id): + """Remove the timeout from the IOLoop by the ID returned from + add_timeout. + + :rtype: str + + """ + return self.ioloop.remove_timeout(timeout_id) + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this connection's IOLoop thread. + + NOTE: This is the only thread-safe method offered by the connection. All + other manipulations of the connection must be performed from the + connection's thread. + + For example, a thread may request a call to the + `channel.basic_ack` method of a connection that is running in a + different thread via + + ``` + connection.add_callback_threadsafe( + functools.partial(channel.basic_ack, delivery_tag=...)) + ``` + + :param method callback: The callback method; must be callable. + + """ + if not callable(callback): + raise TypeError( + 'callback must be a callable, but got %r' % (callback,)) + + self.ioloop.add_callback(callback) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/twisted_connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/twisted_connection.py new file mode 100644 index 000000000..1dac51f44 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/adapters/twisted_connection.py @@ -0,0 +1,474 @@ +"""Using Pika with a Twisted reactor. + +Supports two methods of establishing the connection, using TwistedConnection +or TwistedProtocolConnection. For details about each method, see the docstrings +of the corresponding classes. + +The interfaces in this module are Deferred-based when possible. This means that +the connection.channel() method and most of the channel methods return +Deferreds instead of taking a callback argument and that basic_consume() +returns a Twisted DeferredQueue where messages from the server will be +stored. Refer to the docstrings for TwistedConnection.channel() and the +TwistedChannel class for details. + +""" +import functools +from twisted.internet import defer, error, reactor +from twisted.python import log + +from pika import connection +from pika import exceptions +from pika.adapters import base_connection + + +class ClosableDeferredQueue(defer.DeferredQueue): + """ + Like the normal Twisted DeferredQueue, but after close() is called with an + Exception instance all pending Deferreds are errbacked and further attempts + to call get() or put() return a Failure wrapping that exception. + """ + + def __init__(self, size=None, backlog=None): + self.closed = None + super(ClosableDeferredQueue, self).__init__(size, backlog) + + def put(self, obj): + if self.closed: + return defer.fail(self.closed) + return defer.DeferredQueue.put(self, obj) + + def get(self): + if self.closed: + return defer.fail(self.closed) + return defer.DeferredQueue.get(self) + + def close(self, reason): + self.closed = reason + while self.waiting: + self.waiting.pop().errback(reason) + self.pending = [] + + +class TwistedChannel(object): + """A wrapper wround Pika's Channel. + + Channel methods that normally take a callback argument are wrapped to + return a Deferred that fires with whatever would be passed to the callback. + If the channel gets closed, all pending Deferreds are errbacked with a + ChannelClosed exception. The returned Deferreds fire with whatever + arguments the callback to the original method would receive. + + The basic_consume method is wrapped in a special way, see its docstring for + details. + """ + + WRAPPED_METHODS = ('exchange_declare', 'exchange_delete', 'queue_declare', + 'queue_bind', 'queue_purge', 'queue_unbind', 'basic_qos', + 'basic_get', 'basic_recover', 'tx_select', 'tx_commit', + 'tx_rollback', 'flow', 'basic_cancel') + + def __init__(self, channel): + self.__channel = channel + self.__closed = None + self.__calls = set() + self.__consumers = {} + + channel.add_on_close_callback(self.channel_closed) + + def channel_closed(self, channel, reply_code, reply_text): + # enter the closed state + self.__closed = exceptions.ChannelClosed(reply_code, reply_text) + # errback all pending calls + for d in self.__calls: + d.errback(self.__closed) + # close all open queues + for consumers in self.__consumers.values(): + for c in consumers: + c.close(self.__closed) + # release references to stored objects + self.__calls = set() + self.__consumers = {} + + def basic_consume(self, *args, **kwargs): + """Consume from a server queue. Returns a Deferred that fires with a + tuple: (queue_object, consumer_tag). The queue object is an instance of + ClosableDeferredQueue, where data received from the queue will be + stored. Clients should use its get() method to fetch individual + message. + """ + if self.__closed: + return defer.fail(self.__closed) + + queue = ClosableDeferredQueue() + queue_name = kwargs['queue'] + kwargs['consumer_callback'] = lambda *args: queue.put(args) + self.__consumers.setdefault(queue_name, set()).add(queue) + + try: + consumer_tag = self.__channel.basic_consume(*args, **kwargs) + # TODO this except without types would suppress system-exiting + # exceptions, such as SystemExit and KeyboardInterrupt. It should be at + # least `except Exception` and preferably more specific. + except: + return defer.fail() + + return defer.succeed((queue, consumer_tag)) + + def queue_delete(self, *args, **kwargs): + """Wraps the method the same way all the others are wrapped, but removes + the reference to the queue object after it gets deleted on the server. + + """ + wrapped = self.__wrap_channel_method('queue_delete') + queue_name = kwargs['queue'] + + d = wrapped(*args, **kwargs) + return d.addCallback(self.__clear_consumer, queue_name) + + def basic_publish(self, *args, **kwargs): + """Make sure the channel is not closed and then publish. Return a + Deferred that fires with the result of the channel's basic_publish. + + """ + if self.__closed: + return defer.fail(self.__closed) + return defer.succeed(self.__channel.basic_publish(*args, **kwargs)) + + def __wrap_channel_method(self, name): + """Wrap Pika's Channel method to make it return a Deferred that fires + when the method completes and errbacks if the channel gets closed. If + the original method's callback would receive more than one argument, the + Deferred fires with a tuple of argument values. + + """ + method = getattr(self.__channel, name) + + @functools.wraps(method) + def wrapped(*args, **kwargs): + if self.__closed: + return defer.fail(self.__closed) + + d = defer.Deferred() + self.__calls.add(d) + d.addCallback(self.__clear_call, d) + + def single_argument(*args): + """ + Make sure that the deferred is called with a single argument. + In case the original callback fires with more than one, convert + to a tuple. + """ + if len(args) > 1: + d.callback(tuple(args)) + else: + d.callback(*args) + + kwargs['callback'] = single_argument + + try: + method(*args, **kwargs) + # TODO this except without types would suppress system-exiting + # exceptions, such as SystemExit and KeyboardInterrupt. It should be + # at least `except Exception` and preferably more specific. + except: + return defer.fail() + return d + + return wrapped + + def __clear_consumer(self, ret, queue_name): + self.__consumers.pop(queue_name, None) + return ret + + def __clear_call(self, ret, d): + self.__calls.discard(d) + return ret + + def __getattr__(self, name): + # Wrap methods defined in WRAPPED_METHODS, forward the rest of accesses + # to the channel. + if name in self.WRAPPED_METHODS: + return self.__wrap_channel_method(name) + return getattr(self.__channel, name) + + +class IOLoopReactorAdapter(object): + """An adapter providing Pika's IOLoop interface using a Twisted reactor. + + Accepts a TwistedConnection object and a Twisted reactor object. + + """ + + def __init__(self, connection, reactor): + self.connection = connection + self.reactor = reactor + self.started = False + + def add_timeout(self, deadline, callback_method): + """Add the callback_method to the IOLoop timer to fire after deadline + seconds. Returns a handle to the timeout. Do not confuse with + Tornado's timeout where you pass in the time you want to have your + callback called. Only pass in the seconds until it's to be called. + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + :rtype: twisted.internet.interfaces.IDelayedCall + + """ + return self.reactor.callLater(deadline, callback_method) + + def remove_timeout(self, call): + """Remove a call + + :param twisted.internet.interfaces.IDelayedCall call: The call to cancel + + """ + call.cancel() + + def add_callback_threadsafe(self, callback): + """Requests a call to the given function as soon as possible in the + context of this IOLoop's thread. + + NOTE: This is the only thread-safe method offered by the IOLoop adapter. + All other manipulations of the IOLoop adapter and its parent connection + must be performed from the connection's thread. + + For example, a thread may request a call to the + `channel.basic_ack` method of a connection that is running in a + different thread via + + ``` + connection.add_callback_threadsafe( + functools.partial(channel.basic_ack, delivery_tag=...)) + ``` + + :param method callback: The callback method; must be callable. + + """ + self.reactor.callFromThread(callback) + + def stop(self): + # Guard against stopping the reactor multiple times + if not self.started: + return + self.started = False + self.reactor.stop() + + def start(self): + # Guard against starting the reactor multiple times + if self.started: + return + self.started = True + self.reactor.run() + + def remove_handler(self, _): + # The fileno is irrelevant, as it's the connection's job to provide it + # to the reactor when asked to do so. Removing the handler from the + # ioloop is removing it from the reactor in Twisted's parlance. + self.reactor.removeReader(self.connection) + self.reactor.removeWriter(self.connection) + + def update_handler(self, _, event_state): + # Same as in remove_handler, the fileno is irrelevant. First remove the + # connection entirely from the reactor, then add it back depending on + # the event state. + self.reactor.removeReader(self.connection) + self.reactor.removeWriter(self.connection) + + if event_state & self.connection.READ: + self.reactor.addReader(self.connection) + + if event_state & self.connection.WRITE: + self.reactor.addWriter(self.connection) + + +class TwistedConnection(base_connection.BaseConnection): + """A standard Pika connection adapter. You instantiate the class passing the + connection parameters and the connected callback and when it gets called + you can start using it. + + The problem is that connection establishing is done using the blocking + socket module. For instance, if the host you are connecting to is behind a + misconfigured firewall that just drops packets, the whole process will + freeze until the connection timeout passes. To work around that problem, + use TwistedProtocolConnection, but read its docstring first. + + Objects of this class get put in the Twisted reactor which will notify them + when the socket connection becomes readable or writable, so apart from + implementing the BaseConnection interface, they also provide Twisted's + IReadWriteDescriptor interface. + + """ + + def __init__(self, + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None, + stop_ioloop_on_close=False): + super(TwistedConnection, self).__init__( + parameters=parameters, + on_open_callback=on_open_callback, + on_open_error_callback=on_open_error_callback, + on_close_callback=on_close_callback, + ioloop=IOLoopReactorAdapter(self, reactor), + stop_ioloop_on_close=stop_ioloop_on_close) + + def _adapter_connect(self): + """Connect to the RabbitMQ broker""" + # Connect (blockignly!) to the server + error = super(TwistedConnection, self)._adapter_connect() + if not error: + # Set the I/O events we're waiting for (see IOLoopReactorAdapter + # docstrings for why it's OK to pass None as the file descriptor) + self.ioloop.update_handler(None, self.event_state) + return error + + def _adapter_disconnect(self): + """Called when the adapter should disconnect""" + self.ioloop.remove_handler(None) + self._cleanup_socket() + + def _on_connected(self): + """Call superclass and then update the event state to flush the outgoing + frame out. Commit 50d842526d9f12d32ad9f3c4910ef60b8c301f59 removed a + self._flush_outbound call that was in _send_frame which previously + made this step unnecessary. + + """ + super(TwistedConnection, self)._on_connected() + self._manage_event_state() + + def channel(self, channel_number=None): + """Return a Deferred that fires with an instance of a wrapper around the + Pika Channel class. + + """ + d = defer.Deferred() + base_connection.BaseConnection.channel(self, d.callback, channel_number) + return d.addCallback(TwistedChannel) + + # IReadWriteDescriptor methods + + def fileno(self): + return self.socket.fileno() + + def logPrefix(self): + return "twisted-pika" + + def connectionLost(self, reason): + # If the connection was not closed cleanly, log the error + if not reason.check(error.ConnectionDone): + log.err(reason) + + self._on_terminate(connection.InternalCloseReasons.SOCKET_ERROR, + str(reason)) + + def doRead(self): + self._handle_read() + + def doWrite(self): + self._handle_write() + self._manage_event_state() + + +class TwistedProtocolConnection(base_connection.BaseConnection): + """A hybrid between a Pika Connection and a Twisted Protocol. Allows using + Twisted's non-blocking connectTCP/connectSSL methods for connecting to the + server. + + It has one caveat: TwistedProtocolConnection objects have a ready + instance variable that's a Deferred which fires when the connection is + ready to be used (the initial AMQP handshaking has been done). You *have* + to wait for this Deferred to fire before requesting a channel. + + Since it's Twisted handling connection establishing it does not accept + connect callbacks, you have to implement that within Twisted. Also remember + that the host, port and ssl values of the connection parameters are ignored + because, yet again, it's Twisted who manages the connection. + + """ + + def __init__(self, parameters=None, on_close_callback=None): + self.ready = defer.Deferred() + super(TwistedProtocolConnection, self).__init__( + parameters=parameters, + on_open_callback=self.connectionReady, + on_open_error_callback=self.connectionFailed, + on_close_callback=on_close_callback, + ioloop=IOLoopReactorAdapter(self, reactor), + stop_ioloop_on_close=False) + + def connect(self): + # The connection is open asynchronously by Twisted, so skip the whole + # connect() part, except for setting the connection state + self._set_connection_state(self.CONNECTION_INIT) + + def _adapter_connect(self): + # Should never be called, as we override connect() and leave the + # building of a TCP connection to Twisted, but implement anyway to keep + # the interface + return False + + def _adapter_disconnect(self): + # Disconnect from the server + self.transport.loseConnection() + + def _flush_outbound(self): + """Override BaseConnection._flush_outbound to send all bufferred data + the Twisted way, by writing to the transport. No need for buffering, + Twisted handles that for us. + """ + while self.outbound_buffer: + self.transport.write(self.outbound_buffer.popleft()) + + def channel(self, channel_number=None): + """Create a new channel with the next available channel number or pass + in a channel number to use. Must be non-zero if you would like to + specify but it is recommended that you let Pika manage the channel + numbers. + + Return a Deferred that fires with an instance of a wrapper around the + Pika Channel class. + + :param int channel_number: The channel number to use, defaults to the + next available. + + """ + d = defer.Deferred() + base_connection.BaseConnection.channel(self, d.callback, channel_number) + return d.addCallback(TwistedChannel) + + # IProtocol methods + + def dataReceived(self, data): + # Pass the bytes to Pika for parsing + self._on_data_available(data) + + def connectionLost(self, reason): + # Let the caller know there's been an error + d, self.ready = self.ready, None + if d: + d.errback(reason) + + def makeConnection(self, transport): + self.transport = transport + self.connectionMade() + + def connectionMade(self): + # Tell everyone we're connected + self._on_connected() + + # Our own methods + + def connectionReady(self, res): + d, self.ready = self.ready, None + if d: + d.callback(res) + + def connectionFailed(self, connection_unused, error_message=None): + d, self.ready = self.ready, None + if d: + attempts = self.params.connection_attempts + exc = exceptions.AMQPConnectionError(attempts) + d.errback(exc) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/amqp_object.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/amqp_object.py new file mode 100644 index 000000000..576a2c412 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/amqp_object.py @@ -0,0 +1,66 @@ +"""Base classes that are extended by low level AMQP frames and higher level +AMQP classes and methods. + +""" + + +class AMQPObject(object): + """Base object that is extended by AMQP low level frames and AMQP classes + and methods. + + """ + NAME = 'AMQPObject' + INDEX = None + + def __repr__(self): + items = list() + for key, value in self.__dict__.items(): + if getattr(self.__class__, key, None) != value: + items.append('%s=%s' % (key, value)) + if not items: + return "<%s>" % self.NAME + return "<%s(%s)>" % (self.NAME, sorted(items)) + + +class Class(AMQPObject): + """Is extended by AMQP classes""" + NAME = 'Unextended Class' + + +class Method(AMQPObject): + """Is extended by AMQP methods""" + NAME = 'Unextended Method' + synchronous = False + + def _set_content(self, properties, body): + """If the method is a content frame, set the properties and body to + be carried as attributes of the class. + + :param pika.frame.Properties properties: AMQP Basic Properties + :param body: The message body + :type body: str or unicode + + """ + self._properties = properties + self._body = body + + def get_properties(self): + """Return the properties if they are set. + + :rtype: pika.frame.Properties + + """ + return self._properties + + def get_body(self): + """Return the message body if it is set. + + :rtype: str|unicode + + """ + return self._body + + +class Properties(AMQPObject): + """Class to encompass message properties (AMQP Basic.Properties)""" + NAME = 'Unextended Properties' diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/callback.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/callback.py new file mode 100644 index 000000000..6ac58bd95 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/callback.py @@ -0,0 +1,410 @@ +"""Callback management class, common area for keeping track of all callbacks in +the Pika stack. + +""" +import functools +import logging + +from pika import frame +from pika import amqp_object +from pika.compat import xrange, canonical_str + +LOGGER = logging.getLogger(__name__) + + +def name_or_value(value): + """Will take Frame objects, classes, etc and attempt to return a valid + string identifier for them. + + :param value: The value to sanitize + :type value: pika.amqp_object.AMQPObject|pika.frame.Frame|int|unicode|str + :rtype: str + + """ + # Is it subclass of AMQPObject + try: + if issubclass(value, amqp_object.AMQPObject): + return value.NAME + except TypeError: + pass + + # Is it a Pika frame object? + if isinstance(value, frame.Method): + return value.method.NAME + + # Is it a Pika frame object (go after Method since Method extends this) + if isinstance(value, amqp_object.AMQPObject): + return value.NAME + + # Cast the value to a str (python 2 and python 3); encoding as UTF-8 on Python 2 + return canonical_str(value) + + +def sanitize_prefix(function): + """Automatically call name_or_value on the prefix passed in.""" + + @functools.wraps(function) + def wrapper(*args, **kwargs): + args = list(args) + offset = 1 + if 'prefix' in kwargs: + kwargs['prefix'] = name_or_value(kwargs['prefix']) + elif len(args) - 1 >= offset: + args[offset] = name_or_value(args[offset]) + offset += 1 + if 'key' in kwargs: + kwargs['key'] = name_or_value(kwargs['key']) + elif len(args) - 1 >= offset: + args[offset] = name_or_value(args[offset]) + + return function(*tuple(args), **kwargs) + + return wrapper + + +def check_for_prefix_and_key(function): + """Automatically return false if the key or prefix is not in the callbacks + for the instance. + + """ + + @functools.wraps(function) + def wrapper(*args, **kwargs): + offset = 1 + # Sanitize the prefix + if 'prefix' in kwargs: + prefix = name_or_value(kwargs['prefix']) + else: + prefix = name_or_value(args[offset]) + offset += 1 + + # Make sure to sanitize the key as well + if 'key' in kwargs: + key = name_or_value(kwargs['key']) + else: + key = name_or_value(args[offset]) + + # Make sure prefix and key are in the stack + if prefix not in args[0]._stack or key not in args[0]._stack[prefix]: + return False + + # Execute the method + return function(*args, **kwargs) + + return wrapper + + +class CallbackManager(object): + """CallbackManager is a global callback system designed to be a single place + where Pika can manage callbacks and process them. It should be referenced + by the CallbackManager.instance() method instead of constructing new + instances of it. + + """ + CALLS = 'calls' + ARGUMENTS = 'arguments' + DUPLICATE_WARNING = 'Duplicate callback found for "%s:%s"' + CALLBACK = 'callback' + ONE_SHOT = 'one_shot' + ONLY_CALLER = 'only' + + def __init__(self): + """Create an instance of the CallbackManager""" + self._stack = dict() + + @sanitize_prefix + def add(self, prefix, key, callback, + one_shot=True, + only_caller=None, + arguments=None): + """Add a callback to the stack for the specified key. If the call is + specified as one_shot, it will be removed after being fired + + The prefix is usually the channel number but the class is generic + and prefix and key may be any value. If you pass in only_caller + CallbackManager will restrict processing of the callback to only + the calling function/object that you specify. + + :param prefix: Categorize the callback + :type prefix: str or int + :param key: The key for the callback + :type key: object or str or dict + :param method callback: The callback to call + :param bool one_shot: Remove this callback after it is called + :param object only_caller: Only allow one_caller value to call the + event that fires the callback. + :param dict arguments: Arguments to validate when processing + :rtype: tuple(prefix, key) + + """ + # Prep the stack + if prefix not in self._stack: + self._stack[prefix] = dict() + + if key not in self._stack[prefix]: + self._stack[prefix][key] = list() + + # Check for a duplicate + for callback_dict in self._stack[prefix][key]: + if (callback_dict[self.CALLBACK] == callback and + callback_dict[self.ARGUMENTS] == arguments and + callback_dict[self.ONLY_CALLER] == only_caller): + if callback_dict[self.ONE_SHOT] is True: + callback_dict[self.CALLS] += 1 + LOGGER.debug('Incremented callback reference counter: %r', + callback_dict) + else: + LOGGER.warning(self.DUPLICATE_WARNING, prefix, key) + return prefix, key + + # Create the callback dictionary + callback_dict = self._callback_dict(callback, one_shot, only_caller, + arguments) + self._stack[prefix][key].append(callback_dict) + LOGGER.debug('Added: %r', callback_dict) + return prefix, key + + def clear(self): + """Clear all the callbacks if there are any defined.""" + self._stack = dict() + LOGGER.debug('Callbacks cleared') + + @sanitize_prefix + def cleanup(self, prefix): + """Remove all callbacks from the stack by a prefix. Returns True + if keys were there to be removed + + :param str or int prefix: The prefix for keeping track of callbacks with + :rtype: bool + + """ + LOGGER.debug('Clearing out %r from the stack', prefix) + if prefix not in self._stack or not self._stack[prefix]: + return False + del self._stack[prefix] + return True + + @sanitize_prefix + def pending(self, prefix, key): + """Return count of callbacks for a given prefix or key or None + + :param prefix: Categorize the callback + :type prefix: str or int + :param key: The key for the callback + :type key: object or str or dict + :rtype: None or int + + """ + if not prefix in self._stack or not key in self._stack[prefix]: + return None + return len(self._stack[prefix][key]) + + @sanitize_prefix + @check_for_prefix_and_key + def process(self, prefix, key, caller, *args, **keywords): + """Run through and process all the callbacks for the specified keys. + Caller should be specified at all times so that callbacks which + require a specific function to call CallbackManager.process will + not be processed. + + :param prefix: Categorize the callback + :type prefix: str or int + :param key: The key for the callback + :type key: object or str or dict + :param object caller: Who is firing the event + :param list args: Any optional arguments + :param dict keywords: Optional keyword arguments + :rtype: bool + + """ + LOGGER.debug('Processing %s:%s', prefix, key) + if prefix not in self._stack or key not in self._stack[prefix]: + return False + + callbacks = list() + # Check each callback, append it to the list if it should be called + for callback_dict in list(self._stack[prefix][key]): + if self._should_process_callback(callback_dict, caller, list(args)): + callbacks.append(callback_dict[self.CALLBACK]) + if callback_dict[self.ONE_SHOT]: + self._use_one_shot_callback(prefix, key, callback_dict) + + # Call each callback + for callback in callbacks: + LOGGER.debug('Calling %s for "%s:%s"', callback, prefix, key) + try: + callback(*args, **keywords) + except: + LOGGER.exception('Calling %s for "%s:%s" failed', callback, + prefix, key) + raise + return True + + @sanitize_prefix + @check_for_prefix_and_key + def remove(self, prefix, key, callback_value=None, arguments=None): + """Remove a callback from the stack by prefix, key and optionally + the callback itself. If you only pass in prefix and key, all + callbacks for that prefix and key will be removed. + + :param str or int prefix: The prefix for keeping track of callbacks with + :param str key: The callback key + :param method callback_value: The method defined to call on callback + :param dict arguments: Optional arguments to check + :rtype: bool + + """ + if callback_value: + offsets_to_remove = list() + for offset in xrange(len(self._stack[prefix][key]), 0, -1): + callback_dict = self._stack[prefix][key][offset - 1] + + if (callback_dict[self.CALLBACK] == callback_value and + self._arguments_match(callback_dict, [arguments])): + offsets_to_remove.append(offset - 1) + + for offset in offsets_to_remove: + try: + LOGGER.debug('Removing callback #%i: %r', offset, + self._stack[prefix][key][offset]) + del self._stack[prefix][key][offset] + except KeyError: + pass + + self._cleanup_callback_dict(prefix, key) + return True + + @sanitize_prefix + @check_for_prefix_and_key + def remove_all(self, prefix, key): + """Remove all callbacks for the specified prefix and key. + + :param str prefix: The prefix for keeping track of callbacks with + :param str key: The callback key + + """ + del self._stack[prefix][key] + self._cleanup_callback_dict(prefix, key) + + def _arguments_match(self, callback_dict, args): + """Validate if the arguments passed in match the expected arguments in + the callback_dict. We expect this to be a frame passed in to *args for + process or passed in as a list from remove. + + :param dict callback_dict: The callback dictionary to evaluate against + :param list args: The arguments passed in as a list + + """ + if callback_dict[self.ARGUMENTS] is None: + return True + if not args: + return False + if isinstance(args[0], dict): + return self._dict_arguments_match(args[0], + callback_dict[self.ARGUMENTS]) + return self._obj_arguments_match(args[0].method + if hasattr(args[0], 'method') else + args[0], callback_dict[self.ARGUMENTS]) + + def _callback_dict(self, callback, one_shot, only_caller, arguments): + """Return the callback dictionary. + + :param method callback: The callback to call + :param bool one_shot: Remove this callback after it is called + :param object only_caller: Only allow one_caller value to call the + event that fires the callback. + :rtype: dict + + """ + value = { + self.CALLBACK: callback, + self.ONE_SHOT: one_shot, + self.ONLY_CALLER: only_caller, + self.ARGUMENTS: arguments + } + if one_shot: + value[self.CALLS] = 1 + return value + + def _cleanup_callback_dict(self, prefix, key=None): + """Remove empty dict nodes in the callback stack. + + :param str or int prefix: The prefix for keeping track of callbacks with + :param str key: The callback key + + """ + if key and key in self._stack[prefix] and not self._stack[prefix][key]: + del self._stack[prefix][key] + if prefix in self._stack and not self._stack[prefix]: + del self._stack[prefix] + + @staticmethod + def _dict_arguments_match(value, expectation): + """Checks an dict to see if it has attributes that meet the expectation. + + :param dict value: The dict to evaluate + :param dict expectation: The values to check against + :rtype: bool + + """ + LOGGER.debug('Comparing %r to %r', value, expectation) + for key in expectation: + if value.get(key) != expectation[key]: + LOGGER.debug('Values in dict do not match for %s', key) + return False + return True + + @staticmethod + def _obj_arguments_match(value, expectation): + """Checks an object to see if it has attributes that meet the + expectation. + + :param object value: The object to evaluate + :param dict expectation: The values to check against + :rtype: bool + + """ + for key in expectation: + if not hasattr(value, key): + LOGGER.debug('%r does not have required attribute: %s', + type(value), key) + return False + if getattr(value, key) != expectation[key]: + LOGGER.debug('Values in %s do not match for %s', type(value), + key) + return False + return True + + def _should_process_callback(self, callback_dict, caller, args): + """Returns True if the callback should be processed. + + :param dict callback_dict: The callback configuration + :param object caller: Who is firing the event + :param list args: Any optional arguments + :rtype: bool + + """ + if not self._arguments_match(callback_dict, args): + LOGGER.debug('Arguments do not match for %r, %r', callback_dict, + args) + return False + return (callback_dict[self.ONLY_CALLER] is None or + (callback_dict[self.ONLY_CALLER] and + callback_dict[self.ONLY_CALLER] == caller)) + + def _use_one_shot_callback(self, prefix, key, callback_dict): + """Process the one-shot callback, decrementing the use counter and + removing it from the stack if it's now been fully used. + + :param str or int prefix: The prefix for keeping track of callbacks with + :param str key: The callback key + :param dict callback_dict: The callback dict to process + + """ + LOGGER.debug('Processing use of oneshot callback') + callback_dict[self.CALLS] -= 1 + LOGGER.debug('%i registered uses left', callback_dict[self.CALLS]) + + if callback_dict[self.CALLS] <= 0: + self.remove(prefix, key, callback_dict[self.CALLBACK], + callback_dict[self.ARGUMENTS]) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/channel.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/channel.py new file mode 100644 index 000000000..745e47bdd --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/channel.py @@ -0,0 +1,1436 @@ +"""The Channel class provides a wrapper for interacting with RabbitMQ +implementing the methods and behaviors for an AMQP Channel. + +""" + +import collections +import logging +import uuid + +import pika.frame as frame +import pika.exceptions as exceptions +import pika.spec as spec +from pika.utils import is_callable +from pika.compat import unicode_type, dictkeys, is_integer + + +LOGGER = logging.getLogger(__name__) + +MAX_CHANNELS = 65535 # per AMQP 0.9.1 spec. + + +class Channel(object): + """A Channel is the primary communication method for interacting with + RabbitMQ. It is recommended that you do not directly invoke + the creation of a channel object in your application code but rather + construct the a channel by calling the active connection's channel() + method. + + """ + + # Disable pyling messages concerning "method could be a function" + # pylint: disable=R0201 + + CLOSED = 0 + OPENING = 1 + OPEN = 2 + CLOSING = 3 # client-initiated close in progress + + _STATE_NAMES = { + CLOSED: 'CLOSED', + OPENING: 'OPENING', + OPEN: 'OPEN', + CLOSING: 'CLOSING' + } + + _ON_CHANNEL_CLEANUP_CB_KEY = '_on_channel_cleanup' + + def __init__(self, connection, channel_number, on_open_callback): + """Create a new instance of the Channel + + :param pika.connection.Connection connection: The connection + :param int channel_number: The channel number for this instance + :param callable on_open_callback: The callback to call on channel open + + """ + if not isinstance(channel_number, int): + raise exceptions.InvalidChannelNumber + self.channel_number = channel_number + self.callbacks = connection.callbacks + self.connection = connection + + # Initially, flow is assumed to be active + self.flow_active = True + + self._content_assembler = ContentFrameAssembler() + + self._blocked = collections.deque(list()) + self._blocking = None + self._has_on_flow_callback = False + self._cancelled = set() + self._consumers = dict() + self._consumers_with_noack = set() + self._on_flowok_callback = None + self._on_getok_callback = None + self._on_openok_callback = on_open_callback + self._state = self.CLOSED + + # We save the closing reason code and text to be passed to + # on-channel-close callback at closing of the channel. Channel.close + # stores the given reply_code/reply_text if the channel was in OPEN or + # OPENING states. An incoming Channel.Close AMQP method from broker will + # override this value. And a sudden loss of connection has the highest + # prececence to override it. + self._closing_code_and_text = (0, '') + + # opaque cookie value set by wrapper layer (e.g., BlockingConnection) + # via _set_cookie + self._cookie = None + + def __int__(self): + """Return the channel object as its channel number + + :rtype: int + + """ + return self.channel_number + + def __repr__(self): + return '<%s number=%s %s conn=%r>' % (self.__class__.__name__, + self.channel_number, + self._STATE_NAMES[self._state], + self.connection) + + def add_callback(self, callback, replies, one_shot=True): + """Pass in a callback handler and a list replies from the + RabbitMQ broker which you'd like the callback notified of. Callbacks + should allow for the frame parameter to be passed in. + + :param callable callback: The callback to call + :param list replies: The replies to get a callback for + :param bool one_shot: Only handle the first type callback + + """ + for reply in replies: + self.callbacks.add(self.channel_number, reply, callback, one_shot) + + def add_on_cancel_callback(self, callback): + """Pass a callback function that will be called when the basic_cancel + is sent by the server. The callback function should receive a frame + parameter. + + :param callable callback: The callback to call on Basic.Cancel from + broker + + """ + self.callbacks.add(self.channel_number, spec.Basic.Cancel, callback, + False) + + def add_on_close_callback(self, callback): + """Pass a callback function that will be called when the channel is + closed. The callback function will receive the channel, the + reply_code (int) and the reply_text (string) describing why the channel was + closed. + + If the channel is closed by broker via Channel.Close, the callback will + receive the reply_code/reply_text provided by the broker. + + If channel closing is initiated by user (either directly of indirectly + by closing a connection containing the channel) and closing + concludes gracefully without Channel.Close from the broker and without + loss of connection, the callback will receive 0 as reply_code and empty + string as reply_text. + + If channel was closed due to loss of connection, the callback will + receive reply_code and reply_text representing the loss of connection. + + :param callable callback: The callback, having the signature: + callback(Channel, int reply_code, str reply_text) + + """ + self.callbacks.add(self.channel_number, '_on_channel_close', callback, + False, self) + + def add_on_flow_callback(self, callback): + """Pass a callback function that will be called when Channel.Flow is + called by the remote server. Note that newer versions of RabbitMQ + will not issue this but instead use TCP backpressure + + :param callable callback: The callback function + + """ + self._has_on_flow_callback = True + self.callbacks.add(self.channel_number, spec.Channel.Flow, callback, + False) + + def add_on_return_callback(self, callback): + """Pass a callback function that will be called when basic_publish as + sent a message that has been rejected and returned by the server. + + :param callable callback: The function to call, having the signature + callback(channel, method, properties, body) + where + channel: pika.Channel + method: pika.spec.Basic.Return + properties: pika.spec.BasicProperties + body: str, unicode, or bytes (python 3.x) + + """ + self.callbacks.add(self.channel_number, '_on_return', callback, False) + + def basic_ack(self, delivery_tag=0, multiple=False): + """Acknowledge one or more messages. When sent by the client, this + method acknowledges one or more messages delivered via the Deliver or + Get-Ok methods. When sent by server, this method acknowledges one or + more messages published with the Publish method on a channel in + confirm mode. The acknowledgement can be for a single message or a + set of messages up to and including a specific message. + + :param integer delivery_tag: int/long The server-assigned delivery tag + :param bool multiple: If set to True, the 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 delivery tag refers to a single + message. If the multiple field is 1, and the + delivery tag is zero, this indicates + acknowledgement of all outstanding messages. + """ + if not self.is_open: + raise exceptions.ChannelClosed() + return self._send_method(spec.Basic.Ack(delivery_tag, multiple)) + + def basic_cancel(self, callback=None, consumer_tag='', nowait=False): + """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 callable callback: Callback to call for a Basic.CancelOk + response; MUST be None when nowait=True. MUST be callable when + nowait=False. + :param str consumer_tag: Identifier for the consumer + :param bool nowait: Do not expect a Basic.CancelOk response + + :raises ValueError: + + """ + self._validate_channel_and_callback(callback) + + if nowait: + if callback is not None: + raise ValueError( + 'Completion callback must be None when nowait=True') + else: + if callback is None: + raise ValueError( + 'Must have completion callback with nowait=False') + + if consumer_tag in self._cancelled: + # We check for cancelled first, because basic_cancel removes + # consumers closed with nowait from self._consumers + LOGGER.warning('basic_cancel - consumer is already cancelling: %s', + consumer_tag) + return + + if consumer_tag not in self._consumers: + # Could be cancelled by user or broker earlier + LOGGER.warning('basic_cancel - consumer not found: %s', + consumer_tag) + return + + LOGGER.debug('Cancelling consumer: %s (nowait=%s)', + consumer_tag, nowait) + + if nowait: + # This is our last opportunity while the channel is open to remove + # this consumer callback and help gc; unfortunately, this consumer's + # self._cancelled and self._consumers_with_noack (if any) entries + # will persist until the channel is closed. + del self._consumers[consumer_tag] + + if callback is not None: + if nowait: + raise ValueError('Cannot pass a callback if nowait is True') + self.callbacks.add(self.channel_number, spec.Basic.CancelOk, + callback) + + self._cancelled.add(consumer_tag) + + self._rpc(spec.Basic.Cancel(consumer_tag=consumer_tag, nowait=nowait), + self._on_cancelok if not nowait else None, + [(spec.Basic.CancelOk, {'consumer_tag': consumer_tag})] if + nowait is False else []) + + def basic_consume(self, consumer_callback, + queue='', + no_ack=False, + exclusive=False, + consumer_tag=None, + arguments=None): + """Sends the AMQP 0-9-1 command Basic.Consume to the broker and binds messages + for the consumer_tag to the consumer callback. If you do not pass in + a consumer_tag, one will be automatically generated for you. Returns + the consumer tag. + + For more information on basic_consume, see: + Tutorial 2 at http://www.rabbitmq.com/getstarted.html + http://www.rabbitmq.com/confirms.html + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.consume + + + :param callable consumer_callback: The function to call when consuming + with the signature consumer_callback(channel, method, properties, + body), where + channel: pika.Channel + method: pika.spec.Basic.Deliver + properties: pika.spec.BasicProperties + body: str, unicode, or bytes (python 3.x) + + :param queue: The queue to consume from + :type queue: str or unicode + :param bool no_ack: if set to True, automatic acknowledgement mode will be used + (see http://www.rabbitmq.com/confirms.html) + :param bool exclusive: Don't allow other consumers on the queue + :param consumer_tag: Specify your own consumer tag + :type consumer_tag: str or unicode + :param dict arguments: Custom key/value pair arguments for the consumer + :rtype: str + + """ + self._validate_channel_and_callback(consumer_callback) + + # If a consumer tag was not passed, create one + if not consumer_tag: + consumer_tag = self._generate_consumer_tag() + + if consumer_tag in self._consumers or consumer_tag in self._cancelled: + raise exceptions.DuplicateConsumerTag(consumer_tag) + + if no_ack: + self._consumers_with_noack.add(consumer_tag) + + self._consumers[consumer_tag] = consumer_callback + self._rpc(spec.Basic.Consume(queue=queue, + consumer_tag=consumer_tag, + no_ack=no_ack, + exclusive=exclusive, + arguments=arguments or dict()), + self._on_eventok, [(spec.Basic.ConsumeOk, + {'consumer_tag': consumer_tag})]) + + return consumer_tag + + def _generate_consumer_tag(self): + """Generate a consumer tag + + NOTE: this protected method may be called by derived classes + + :returns: consumer tag + :rtype: str + """ + return 'ctag%i.%s' % (self.channel_number, + uuid.uuid4().hex) + + def basic_get(self, callback=None, queue='', no_ack=False): + """Get a single message from the AMQP broker. If you want to + be notified of Basic.GetEmpty, use the Channel.add_callback method + adding your Basic.GetEmpty callback which should expect only one + parameter, frame. Due to implementation details, this cannot be called + a second time until the callback is executed. For more information on + basic_get and its parameters, see: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.get + + :param callable callback: The callback to call with a message that has + the signature callback(channel, method, properties, body), where: + channel: pika.Channel + method: pika.spec.Basic.GetOk + properties: pika.spec.BasicProperties + body: str, unicode, or bytes (python 3.x) + :param queue: The queue to get a message from + :type queue: str or unicode + :param bool no_ack: Tell the broker to not expect a reply + + """ + self._validate_channel_and_callback(callback) + # TODO Is basic_get meaningful when callback is None? + if self._on_getok_callback is not None: + raise exceptions.DuplicateGetOkCallback() + self._on_getok_callback = callback + # TODO Strangely, not using _rpc for the synchronous Basic.Get. Would + # need to extend _rpc to handle Basic.GetOk method, header, and body + # frames (or similar) + self._send_method(spec.Basic.Get(queue=queue, no_ack=no_ack)) + + def basic_nack(self, delivery_tag=None, multiple=False, requeue=True): + """This method allows a client to reject one or more incoming messages. + It can be used to interrupt and cancel large incoming messages, or + return untreatable messages to their original queue. + + :param integer delivery-tag: int/long The server-assigned delivery tag + :param bool multiple: If set to True, the 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 delivery tag refers to a single + message. If the multiple field is 1, and the + delivery tag is zero, this indicates + acknowledgement of all outstanding messages. + :param bool requeue: If requeue is true, the server will attempt to + requeue the message. If requeue is false or the + requeue attempt fails the messages are discarded or + dead-lettered. + + """ + if not self.is_open: + raise exceptions.ChannelClosed() + return self._send_method(spec.Basic.Nack(delivery_tag, multiple, + requeue)) + + def basic_publish(self, exchange, routing_key, body, + properties=None, + mandatory=False, + immediate=False): + """Publish to the channel with the given exchange, routing key and body. + For more information on basic_publish and what the parameters do, see: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.publish + + :param exchange: The exchange to publish to + :type exchange: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param body: The message body + :type body: str or unicode + :param pika.spec.BasicProperties properties: Basic.properties + :param bool mandatory: The mandatory flag + :param bool immediate: The immediate flag + + """ + if not self.is_open: + raise exceptions.ChannelClosed() + if immediate: + LOGGER.warning('The immediate flag is deprecated in RabbitMQ') + if isinstance(body, unicode_type): + body = body.encode('utf-8') + properties = properties or spec.BasicProperties() + self._send_method(spec.Basic.Publish(exchange=exchange, + routing_key=routing_key, + mandatory=mandatory, + immediate=immediate), + (properties, body)) + + def basic_qos(self, + callback=None, + prefetch_size=0, + prefetch_count=0, + all_channels=False): + """Specify quality of service. This method requests a specific quality + of service. The QoS can be specified for the current channel or for all + channels on the connection. The client can request that messages be sent + in advance so that when the client finishes processing a message, the + following message is already held locally, rather than needing to be + sent down the channel. Prefetching gives a performance improvement. + + :param callable callback: The callback to call for Basic.QosOk response + :param int prefetch_size: This field specifies the prefetch window + size. The server will send a message in + advance if it is equal to or smaller in size + than the available prefetch size (and also + falls into other prefetch limits). May be set + to zero, meaning "no specific limit", + although other prefetch limits may still + apply. The prefetch-size is ignored if the + no-ack option is set. + :param int prefetch_count: Specifies a prefetch window in terms of whole + messages. This field may be used in + combination with the prefetch-size field; a + message will only be sent in advance if both + prefetch windows (and those at the channel + and connection level) allow it. The + prefetch-count is ignored if the no-ack + option is set. + :param bool all_channels: Should the QoS apply to all channels + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Basic.Qos(prefetch_size, prefetch_count, + all_channels), + callback, [spec.Basic.QosOk]) + + def basic_reject(self, delivery_tag, requeue=True): + """Reject an incoming message. This method allows a client to reject a + message. It can be used to interrupt and cancel large incoming messages, + or return untreatable messages to their original queue. + + :param integer delivery-tag: int/long The server-assigned delivery tag + :param bool requeue: If requeue is true, the server will attempt to + requeue the message. If requeue is false or the + requeue attempt fails the messages are discarded or + dead-lettered. + :raises: TypeError + + """ + if not self.is_open: + raise exceptions.ChannelClosed() + if not is_integer(delivery_tag): + raise TypeError('delivery_tag must be an integer') + return self._send_method(spec.Basic.Reject(delivery_tag, requeue)) + + def basic_recover(self, callback=None, requeue=False): + """This method asks the server to redeliver all unacknowledged messages + on a specified channel. Zero or more messages may be redelivered. This + method replaces the asynchronous Recover. + + :param callable callback: Callback to call when receiving + Basic.RecoverOk + :param bool requeue: If False, the message will be redelivered to the + original recipient. If True, the server will + attempt to requeue the message, potentially then + delivering it to an alternative subscriber. + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Basic.Recover(requeue), callback, + [spec.Basic.RecoverOk]) + + def close(self, reply_code=0, reply_text="Normal shutdown"): + """Invoke a graceful shutdown of the channel with the AMQP Broker. + + If channel is OPENING, transition to CLOSING and suppress the incoming + Channel.OpenOk, if any. + + :param int reply_code: The reason code to send to broker + :param str reply_text: The reason text to send to broker + + :raises ChannelClosed: if channel is already closed + :raises ChannelAlreadyClosing: if channel is already closing + """ + if self.is_closed: + # Whoever is calling `close` might expect the on-channel-close-cb + # to be called, which won't happen when it's already closed + raise exceptions.ChannelClosed('Already closed: %s' % self) + + if self.is_closing: + # Whoever is calling `close` might expect their reply_code and + # reply_text to be sent to broker, which won't happen if we're + # already closing. + raise exceptions.ChannelAlreadyClosing('Already closing: %s' % self) + + # If channel is OPENING, we will transition it to CLOSING state, + # causing the _on_openok method to suppress the OPEN state transition + # and the on-channel-open-callback + + LOGGER.info('Closing channel (%s): %r on %s', + reply_code, reply_text, self) + + for consumer_tag in dictkeys(self._consumers): + if consumer_tag not in self._cancelled: + self.basic_cancel(consumer_tag=consumer_tag, nowait=True) + + # Change state after cancelling consumers to avoid ChannelClosed + # exception from basic_cancel + self._set_state(self.CLOSING) + + self._rpc(spec.Channel.Close(reply_code, reply_text, 0, 0), + self._on_closeok, [spec.Channel.CloseOk]) + + def confirm_delivery(self, callback=None, nowait=False): + """Turn on Confirm mode in the channel. Pass in a callback to be + notified by the Broker when a message has been confirmed as received or + rejected (Basic.Ack, Basic.Nack) from the broker to the publisher. + + For more information see: + http://www.rabbitmq.com/extensions.html#confirms + + :param callable callback: The callback for delivery confirmations that + has the following signature: callback(pika.frame.Method), where + method_frame contains either method `spec.Basic.Ack` or + `spec.Basic.Nack`. + :param bool nowait: Do not send a reply frame (Confirm.SelectOk) + + """ + self._validate_channel_and_callback(callback) + + # TODO confirm_deliver should require a callback; it's meaningless + # without a user callback to receieve Basic.Ack/Basic.Nack notifications + + if not (self.connection.publisher_confirms and + self.connection.basic_nack): + raise exceptions.MethodNotImplemented('Not Supported on Server') + + # Add the ack and nack callbacks + if callback is not None: + self.callbacks.add(self.channel_number, spec.Basic.Ack, callback, + False) + self.callbacks.add(self.channel_number, spec.Basic.Nack, callback, + False) + + # Send the RPC command + self._rpc(spec.Confirm.Select(nowait), + self._on_selectok if not nowait else None, + [spec.Confirm.SelectOk] if nowait is False else []) + + @property + def consumer_tags(self): + """Property method that returns a list of currently active consumers + + :rtype: list + + """ + return dictkeys(self._consumers) + + def exchange_bind(self, + callback=None, + destination=None, + source=None, + routing_key='', + nowait=False, + arguments=None): + """Bind an exchange to another exchange. + + :param callable callback: The callback to call on Exchange.BindOk; MUST + be None when nowait=True + :param destination: The destination exchange to bind + :type destination: str or unicode + :param source: The source exchange to bind to + :type source: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param bool nowait: Do not wait for an Exchange.BindOk + :param dict arguments: Custom key/value pair arguments for the binding + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Exchange.Bind(0, destination, source, routing_key, + nowait, arguments or dict()), + callback, [spec.Exchange.BindOk] if nowait is False + else []) + + def exchange_declare(self, + callback=None, + exchange=None, + exchange_type='direct', + passive=False, + durable=False, + auto_delete=False, + internal=False, + nowait=False, + arguments=None): + """This method creates an exchange if it does not already exist, and if + the exchange exists, verifies that it is of the correct and expected + class. + + If passive set, the server will reply with Declare-Ok if the exchange + already exists with the same name, and raise an error if not and if the + exchange does not already exist, the server MUST raise a channel + exception with reply code 404 (not found). + + :param callable callback: Call this method on Exchange.DeclareOk; MUST + be None when nowait=True + :param exchange: The exchange name consists of a non-empty + :type exchange: str or unicode + sequence of these characters: letters, + digits, hyphen, underscore, period, or + colon. + :param str exchange_type: The exchange type to use + :param bool passive: Perform a declare or just check to see if it exists + :param bool durable: Survive a reboot of RabbitMQ + :param bool auto_delete: Remove when no more queues are bound to it + :param bool internal: Can only be published to by other exchanges + :param bool nowait: Do not expect an Exchange.DeclareOk response + :param dict arguments: Custom key/value pair arguments for the exchange + + """ + self._validate_channel_and_callback(callback) + + return self._rpc(spec.Exchange.Declare(0, exchange, exchange_type, + passive, durable, auto_delete, + internal, nowait, + arguments or dict()), + callback, + [spec.Exchange.DeclareOk] if nowait is False else []) + + def exchange_delete(self, + callback=None, + exchange=None, + if_unused=False, + nowait=False): + """Delete the exchange. + + :param callable callback: The function to call on Exchange.DeleteOk; + MUST be None when nowait=True. + :param exchange: The exchange name + :type exchange: str or unicode + :param bool if_unused: only delete if the exchange is unused + :param bool nowait: Do not wait for an Exchange.DeleteOk + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Exchange.Delete(0, exchange, if_unused, nowait), + callback, [spec.Exchange.DeleteOk] if nowait is False + else []) + + def exchange_unbind(self, + callback=None, + destination=None, + source=None, + routing_key='', + nowait=False, + arguments=None): + """Unbind an exchange from another exchange. + + :param callable callback: The callback to call on Exchange.UnbindOk; + MUST be None when nowait=True. + :param destination: The destination exchange to unbind + :type destination: str or unicode + :param source: The source exchange to unbind from + :type source: str or unicode + :param routing_key: The routing key to unbind + :type routing_key: str or unicode + :param bool nowait: Do not wait for an Exchange.UnbindOk + :param dict arguments: Custom key/value pair arguments for the binding + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Exchange.Unbind(0, destination, source, + routing_key, nowait, arguments), + callback, + [spec.Exchange.UnbindOk] if nowait is False else []) + + def flow(self, callback, active): + """Turn Channel flow control off and on. Pass a callback to be notified + of the response from the server. active is a bool. Callback should + expect a bool in response indicating channel flow state. For more + information, please reference: + + http://www.rabbitmq.com/amqp-0-9-1-reference.html#channel.flow + + :param callable callback: The callback to call upon completion + :param bool active: Turn flow on or off + + """ + self._validate_channel_and_callback(callback) + self._on_flowok_callback = callback + self._rpc(spec.Channel.Flow(active), self._on_flowok, + [spec.Channel.FlowOk]) + + @property + def is_closed(self): + """Returns True if the channel is closed. + + :rtype: bool + + """ + return self._state == self.CLOSED + + @property + def is_closing(self): + """Returns True if client-initiated closing of the channel is in + progress. + + :rtype: bool + + """ + return self._state == self.CLOSING + + @property + def is_open(self): + """Returns True if the channel is open. + + :rtype: bool + + """ + return self._state == self.OPEN + + def open(self): + """Open the channel""" + self._set_state(self.OPENING) + self._add_callbacks() + self._rpc(spec.Channel.Open(), self._on_openok, [spec.Channel.OpenOk]) + + def queue_bind(self, callback, queue, exchange, + routing_key=None, + nowait=False, + arguments=None): + """Bind the queue to the specified exchange + + :param callable callback: The callback to call on Queue.BindOk; + MUST be None when nowait=True. + :param queue: The queue to bind to the exchange + :type queue: str or unicode + :param exchange: The source exchange to bind to + :type exchange: str or unicode + :param routing_key: The routing key to bind on + :type routing_key: str or unicode + :param bool nowait: Do not wait for a Queue.BindOk + :param dict arguments: Custom key/value pair arguments for the binding + + """ + self._validate_channel_and_callback(callback) + replies = [spec.Queue.BindOk] if nowait is False else [] + if routing_key is None: + routing_key = queue + return self._rpc(spec.Queue.Bind(0, queue, exchange, routing_key, + nowait, arguments or dict()), + callback, replies) + + def queue_declare(self, callback, + queue='', + passive=False, + durable=False, + exclusive=False, + auto_delete=False, + nowait=False, + arguments=None): + """Declare queue, create if needed. This method creates or checks a + queue. When creating a new queue the client can specify various + properties that control the durability of the queue and its contents, + and the level of sharing for the queue. + + Leave the queue name empty for a auto-named queue in RabbitMQ + + :param callable callback: callback(pika.frame.Method) for method + Queue.DeclareOk; MUST be None when nowait=True. + :param queue: The queue name + :type queue: str or unicode + :param bool passive: Only check to see if the queue exists + :param bool durable: Survive reboots of the broker + :param bool exclusive: Only allow access by the current connection + :param bool auto_delete: Delete after consumer cancels or disconnects + :param bool nowait: Do not wait for a Queue.DeclareOk + :param dict arguments: Custom key/value arguments for the queue + + """ + if queue: + condition = (spec.Queue.DeclareOk, + {'queue': queue}) + else: + condition = spec.Queue.DeclareOk # pylint: disable=R0204 + replies = [condition] if nowait is False else [] + self._validate_channel_and_callback(callback) + return self._rpc(spec.Queue.Declare(0, queue, passive, durable, + exclusive, auto_delete, nowait, + arguments or dict()), + callback, replies) + + def queue_delete(self, + callback=None, + queue='', + if_unused=False, + if_empty=False, + nowait=False): + """Delete a queue from the broker. + + :param callable callback: The callback to call on Queue.DeleteOk; + MUST be None when nowait=True. + :param queue: The queue to delete + :type queue: str or unicode + :param bool if_unused: only delete if it's unused + :param bool if_empty: only delete if the queue is empty + :param bool nowait: Do not wait for a Queue.DeleteOk + + """ + replies = [spec.Queue.DeleteOk] if nowait is False else [] + self._validate_channel_and_callback(callback) + return self._rpc(spec.Queue.Delete(0, queue, if_unused, if_empty, + nowait), + callback, replies) + + def queue_purge(self, callback=None, queue='', nowait=False): + """Purge all of the messages from the specified queue + + :param callable callback: The callback to call on Queue.PurgeOk; + MUST be None when nowait=True. + :param queue: The queue to purge + :type queue: str or unicode + :param bool nowait: Do not expect a Queue.PurgeOk response + + """ + replies = [spec.Queue.PurgeOk] if nowait is False else [] + self._validate_channel_and_callback(callback) + return self._rpc(spec.Queue.Purge(0, queue, nowait), callback, replies) + + def queue_unbind(self, + callback=None, + queue='', + exchange=None, + routing_key=None, + arguments=None): + """Unbind a queue from an exchange. + + :param callable callback: The callback to call on Queue.UnbindOk + :param queue: The queue to unbind from the exchange + :type queue: str or unicode + :param exchange: The source exchange to bind from + :type exchange: str or unicode + :param routing_key: The routing key to unbind + :type routing_key: str or unicode + :param dict arguments: Custom key/value pair arguments for the binding + + """ + self._validate_channel_and_callback(callback) + if routing_key is None: + routing_key = queue + return self._rpc(spec.Queue.Unbind(0, queue, exchange, routing_key, + arguments or dict()), + callback, [spec.Queue.UnbindOk]) + + def tx_commit(self, callback=None): + """Commit a transaction + + :param callable callback: The callback for delivery confirmations + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Tx.Commit(), callback, [spec.Tx.CommitOk]) + + def tx_rollback(self, callback=None): + """Rollback a transaction. + + :param callable callback: The callback for delivery confirmations + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Tx.Rollback(), callback, [spec.Tx.RollbackOk]) + + def tx_select(self, callback=None): + """Select standard transaction mode. This method sets the channel to use + standard transactions. The client must use this method at least once on + a channel before using the Commit or Rollback methods. + + :param callable callback: The callback for delivery confirmations + + """ + self._validate_channel_and_callback(callback) + return self._rpc(spec.Tx.Select(), callback, [spec.Tx.SelectOk]) + + # Internal methods + + def _add_callbacks(self): + """Callbacks that add the required behavior for a channel when + connecting and connected to a server. + + """ + # Add a callback for Basic.GetEmpty + self.callbacks.add(self.channel_number, spec.Basic.GetEmpty, + self._on_getempty, False) + + # Add a callback for Basic.Cancel + self.callbacks.add(self.channel_number, spec.Basic.Cancel, + self._on_cancel, False) + + # Deprecated in newer versions of RabbitMQ but still register for it + self.callbacks.add(self.channel_number, spec.Channel.Flow, + self._on_flow, False) + + # Add a callback for when the server closes our channel + self.callbacks.add(self.channel_number, spec.Channel.Close, + self._on_close, True) + + def _add_on_cleanup_callback(self, callback): + """For internal use only (e.g., Connection needs to remove closed + channels from its channel container). Pass a callback function that will + be called when the channel is being cleaned up after all channel-close + callbacks callbacks. + + :param callable callback: The callback to call, having the + signature: callback(channel) + + """ + self.callbacks.add(self.channel_number, self._ON_CHANNEL_CLEANUP_CB_KEY, + callback, one_shot=True, only_caller=self) + + def _cleanup(self): + """Remove all consumers and any callbacks for the channel.""" + self.callbacks.process(self.channel_number, + self._ON_CHANNEL_CLEANUP_CB_KEY, self, + self) + self._consumers = dict() + self.callbacks.cleanup(str(self.channel_number)) + self._cookie = None + + def _cleanup_consumer_ref(self, consumer_tag): + """Remove any references to the consumer tag in internal structures + for consumer state. + + :param str consumer_tag: The consumer tag to cleanup + + """ + self._consumers_with_noack.discard(consumer_tag) + self._consumers.pop(consumer_tag, None) + self._cancelled.discard(consumer_tag) + + def _get_cookie(self): + """Used by the wrapper implementation (e.g., `BlockingChannel`) to + retrieve the cookie that it set via `_set_cookie` + + :returns: opaque cookie value that was set via `_set_cookie` + """ + return self._cookie + + def _handle_content_frame(self, frame_value): + """This is invoked by the connection when frames that are not registered + with the CallbackManager have been found. This should only be the case + when the frames are related to content delivery. + + The _content_assembler will be invoked which will return the fully + formed message in three parts when all of the body frames have been + received. + + :param pika.amqp_object.Frame frame_value: The frame to deliver + + """ + try: + response = self._content_assembler.process(frame_value) + except exceptions.UnexpectedFrameError: + self._on_unexpected_frame(frame_value) + return + + if response: + if isinstance(response[0].method, spec.Basic.Deliver): + self._on_deliver(*response) + elif isinstance(response[0].method, spec.Basic.GetOk): + self._on_getok(*response) + elif isinstance(response[0].method, spec.Basic.Return): + self._on_return(*response) + + def _on_cancel(self, method_frame): + """When the broker cancels a consumer, delete it from our internal + dictionary. + + :param pika.frame.Method method_frame: The method frame received + + """ + if method_frame.method.consumer_tag in self._cancelled: + # User-initiated cancel is waiting for Cancel-ok + return + + self._cleanup_consumer_ref(method_frame.method.consumer_tag) + + def _on_cancelok(self, method_frame): + """Called in response to a frame from the Broker when the + client sends Basic.Cancel + + :param pika.frame.Method method_frame: The method frame received + + """ + self._cleanup_consumer_ref(method_frame.method.consumer_tag) + + def _on_close(self, method_frame): + """Handle the case where our channel has been closed for us + + :param pika.frame.Method method_frame: Method frame with Channel.Close + method + + """ + LOGGER.warning('Received remote Channel.Close (%s): %r on %s', + method_frame.method.reply_code, + method_frame.method.reply_text, + self) + + # AMQP 0.9.1 requires CloseOk response to Channel.Close; Note, we should + # not be called when connection is closed + self._send_method(spec.Channel.CloseOk()) + + if self.is_closing: + # Since we already sent Channel.Close, we need to wait for CloseOk + # before cleaning up to avoid a race condition whereby our channel + # number might get reused before our CloseOk arrives + + # Save the details to provide to user callback when CloseOk arrives + self._closing_code_and_text = (method_frame.method.reply_code, + method_frame.method.reply_text) + else: + self._set_state(self.CLOSED) + try: + self.callbacks.process(self.channel_number, '_on_channel_close', + self, self, + method_frame.method.reply_code, + method_frame.method.reply_text) + finally: + self._cleanup() + + def _on_close_meta(self, reply_code, reply_text): + """Handle meta-close request from Connection's cleanup logic after + sudden connection loss. We use this opportunity to transition to + CLOSED state, clean up the channel, and dispatch the on-channel-closed + callbacks. + + :param int reply_code: The reply code to pass to on-close callback + :param str reply_text: The reply text to pass to on-close callback + + """ + LOGGER.debug('Handling meta-close on %s', self) + + if not self.is_closed: + self._closing_code_and_text = reply_code, reply_text + + self._set_state(self.CLOSED) + + try: + self.callbacks.process(self.channel_number, '_on_channel_close', + self, self, + reply_code, + reply_text) + finally: + self._cleanup() + + def _on_closeok(self, method_frame): + """Invoked when RabbitMQ replies to a Channel.Close method + + :param pika.frame.Method method_frame: Method frame with Channel.CloseOk + method + + """ + LOGGER.info('Received %s on %s', method_frame.method, self) + + self._set_state(self.CLOSED) + + try: + self.callbacks.process(self.channel_number, '_on_channel_close', + self, self, + self._closing_code_and_text[0], + self._closing_code_and_text[1]) + finally: + self._cleanup() + + def _on_deliver(self, method_frame, header_frame, body): + """Cope with reentrancy. If a particular consumer is still active when + another delivery appears for it, queue the deliveries up until it + finally exits. + + :param pika.frame.Method method_frame: The method frame received + :param pika.frame.Header header_frame: The header frame received + :param body: The body received + :type body: str or unicode + + """ + consumer_tag = method_frame.method.consumer_tag + + if consumer_tag in self._cancelled: + if self.is_open and consumer_tag not in self._consumers_with_noack: + self.basic_reject(method_frame.method.delivery_tag) + return + + if consumer_tag not in self._consumers: + LOGGER.error('Unexpected delivery: %r', method_frame) + return + + self._consumers[consumer_tag](self, method_frame.method, + header_frame.properties, body) + + def _on_eventok(self, method_frame): + """Generic events that returned ok that may have internal callbacks. + We keep a list of what we've yet to implement so that we don't silently + drain events that we don't support. + + :param pika.frame.Method method_frame: The method frame received + + """ + LOGGER.debug('Discarding frame %r', method_frame) + + def _on_flow(self, _method_frame_unused): + """Called if the server sends a Channel.Flow frame. + + :param pika.frame.Method method_frame_unused: The Channel.Flow frame + + """ + if self._has_on_flow_callback is False: + LOGGER.warning('Channel.Flow received from server') + + def _on_flowok(self, method_frame): + """Called in response to us asking the server to toggle on Channel.Flow + + :param pika.frame.Method method_frame: The method frame received + + """ + self.flow_active = method_frame.method.active + if self._on_flowok_callback: + self._on_flowok_callback(method_frame.method.active) + self._on_flowok_callback = None + else: + LOGGER.warning('Channel.FlowOk received with no active callbacks') + + def _on_getempty(self, method_frame): + """When we receive an empty reply do nothing but log it + + :param pika.frame.Method method_frame: The method frame received + + """ + LOGGER.debug('Received Basic.GetEmpty: %r', method_frame) + if self._on_getok_callback is not None: + self._on_getok_callback = None + + def _on_getok(self, method_frame, header_frame, body): + """Called in reply to a Basic.Get when there is a message. + + :param pika.frame.Method method_frame: The method frame received + :param pika.frame.Header header_frame: The header frame received + :param body: The body received + :type body: str or unicode + + """ + if self._on_getok_callback is not None: + callback = self._on_getok_callback + self._on_getok_callback = None + callback(self, method_frame.method, header_frame.properties, body) + else: + LOGGER.error('Basic.GetOk received with no active callback') + + def _on_openok(self, method_frame): + """Called by our callback handler when we receive a Channel.OpenOk and + subsequently calls our _on_openok_callback which was passed into the + Channel constructor. The reason we do this is because we want to make + sure that the on_open_callback parameter passed into the Channel + constructor is not the first callback we make. + + Suppress the state transition and callback if channel is already in + CLOSING state. + + :param pika.frame.Method method_frame: Channel.OpenOk frame + + """ + # Suppress OpenOk if the user or Connection.Close started closing it + # before open completed. + if self.is_closing: + LOGGER.debug('Suppressing while in closing state: %s', method_frame) + else: + self._set_state(self.OPEN) + if self._on_openok_callback is not None: + self._on_openok_callback(self) + + def _on_return(self, method_frame, header_frame, body): + """Called if the server sends a Basic.Return frame. + + :param pika.frame.Method method_frame: The Basic.Return frame + :param pika.frame.Header header_frame: The content header frame + :param body: The message body + :type body: str or unicode + + """ + if not self.callbacks.process(self.channel_number, '_on_return', self, + self, + method_frame.method, + header_frame.properties, + body): + LOGGER.warning('Basic.Return received from server (%r, %r)', + method_frame.method, header_frame.properties) + + def _on_selectok(self, method_frame): + """Called when the broker sends a Confirm.SelectOk frame + + :param pika.frame.Method method_frame: The method frame received + + """ + LOGGER.debug("Confirm.SelectOk Received: %r", method_frame) + + def _on_synchronous_complete(self, _method_frame_unused): + """This is called when a synchronous command is completed. It will undo + the blocking state and send all the frames that stacked up while we + were in the blocking state. + + :param pika.frame.Method method_frame_unused: The method frame received + + """ + LOGGER.debug('%i blocked frames', len(self._blocked)) + self._blocking = None + while self._blocked and self._blocking is None: + self._rpc(*self._blocked.popleft()) + + def _rpc(self, method, callback=None, acceptable_replies=None): + """Make a syncronous channel RPC call for a synchronous method frame. If + the channel is already in the blocking state, then enqueue the request, + but don't send it at this time; it will be eventually sent by + `_on_synchronous_complete` after the prior blocking request receives a + resposne. If the channel is not in the blocking state and + `acceptable_replies` is not empty, transition the channel to the + blocking state and register for `_on_synchronous_complete` before + sending the request. + + NOTE: A callback must be accompanied by non-empty acceptable_replies. + + :param pika.amqp_object.Method method: The AMQP method to invoke + :param callable callback: The callback for the RPC response + :param acceptable_replies: A (possibly empty) sequence of + replies this RPC call expects or None + :type acceptable_replies: list or None + + """ + assert method.synchronous, ( + 'Only synchronous-capable methods may be used with _rpc: %r' + % (method,)) + + # Validate we got None or a list of acceptable_replies + if not isinstance(acceptable_replies, (type(None), list)): + raise TypeError('acceptable_replies should be list or None') + + if callback is not None: + # Validate the callback is callable + if not is_callable(callback): + raise TypeError( + 'callback should be None or a callable') + + # Make sure that callback is accompanied by acceptable replies + if not acceptable_replies: + raise ValueError( + 'Unexpected callback for asynchronous (nowait) operation.') + + # Make sure the channel is not closed yet + if self.is_closed: + raise exceptions.ChannelClosed + + # If the channel is blocking, add subsequent commands to our stack + if self._blocking: + LOGGER.debug('Already in blocking state, so enqueueing method %s; ' + 'acceptable_replies=%r', + method, acceptable_replies) + return self._blocked.append([method, callback, acceptable_replies]) + + # If acceptable replies are set, add callbacks + if acceptable_replies: + # Block until a response frame is received for synchronous frames + self._blocking = method.NAME + LOGGER.debug( + 'Entering blocking state on frame %s; acceptable_replies=%r', + method, acceptable_replies) + + for reply in acceptable_replies: + if isinstance(reply, tuple): + reply, arguments = reply + else: + arguments = None + LOGGER.debug('Adding on_synchronous_complete callback') + self.callbacks.add(self.channel_number, reply, + self._on_synchronous_complete, + arguments=arguments) + if callback is not None: + LOGGER.debug('Adding passed-in callback') + self.callbacks.add(self.channel_number, reply, callback, + arguments=arguments) + + self._send_method(method) + + def _send_method(self, method, content=None): + """Shortcut wrapper to send a method through our connection, passing in + the channel number + + :param pika.amqp_object.Method method: The method to send + :param tuple content: If set, is a content frame, is tuple of + properties and body. + + """ + # pylint: disable=W0212 + self.connection._send_method(self.channel_number, method, content) + + def _set_cookie(self, cookie): + """Used by wrapper layer (e.g., `BlockingConnection`) to link the + channel implementation back to the proxy. See `_get_cookie`. + + :param cookie: an opaque value; typically a proxy channel implementation + instance (e.g., `BlockingChannel` instance) + """ + self._cookie = cookie + + def _set_state(self, connection_state): + """Set the channel connection state to the specified state value. + + :param int connection_state: The connection_state value + + """ + self._state = connection_state + + def _on_unexpected_frame(self, frame_value): + """Invoked when a frame is received that is not setup to be processed. + + :param pika.frame.Frame frame_value: The frame received + + """ + LOGGER.error('Unexpected frame: %r', frame_value) + + def _validate_channel_and_callback(self, callback): + """Verify that channel is open and callback is callable if not None + + :raises ChannelClosed: if channel is closed + :raises ValueError: if callback is not None and is not callable + """ + if not self.is_open: + raise exceptions.ChannelClosed() + if callback is not None and not is_callable(callback): + raise ValueError('callback must be a function or method') + + +class ContentFrameAssembler(object): + """Handle content related frames, building a message and return the message + back in three parts upon receipt. + + """ + + def __init__(self): + """Create a new instance of the conent frame assembler. + + """ + self._method_frame = None + self._header_frame = None + self._seen_so_far = 0 + self._body_fragments = list() + + def process(self, frame_value): + """Invoked by the Channel object when passed frames that are not + setup in the rpc process and that don't have explicit reply types + defined. This includes Basic.Publish, Basic.GetOk and Basic.Return + + :param Method|Header|Body frame_value: The frame to process + + """ + if (isinstance(frame_value, frame.Method) and + spec.has_content(frame_value.method.INDEX)): + self._method_frame = frame_value + elif isinstance(frame_value, frame.Header): + self._header_frame = frame_value + if frame_value.body_size == 0: + return self._finish() + elif isinstance(frame_value, frame.Body): + return self._handle_body_frame(frame_value) + else: + raise exceptions.UnexpectedFrameError(frame_value) + + def _finish(self): + """Invoked when all of the message has been received + + :rtype: tuple(pika.frame.Method, pika.frame.Header, str) + + """ + content = (self._method_frame, self._header_frame, + b''.join(self._body_fragments)) + self._reset() + return content + + def _handle_body_frame(self, body_frame): + """Receive body frames and append them to the stack. When the body size + matches, call the finish method. + + :param Body body_frame: The body frame + :raises: pika.exceptions.BodyTooLongError + :rtype: tuple(pika.frame.Method, pika.frame.Header, str)|None + + """ + self._seen_so_far += len(body_frame.fragment) + self._body_fragments.append(body_frame.fragment) + if self._seen_so_far == self._header_frame.body_size: + return self._finish() + elif self._seen_so_far > self._header_frame.body_size: + raise exceptions.BodyTooLongError(self._seen_so_far, + self._header_frame.body_size) + return None + + def _reset(self): + """Reset the values for processing frames""" + self._method_frame = None + self._header_frame = None + self._seen_so_far = 0 + self._body_fragments = list() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/compat.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/compat.py new file mode 100644 index 000000000..ad14aaa88 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/compat.py @@ -0,0 +1,210 @@ +import errno +import os +import platform +import re +import socket +import sys as _sys + +PY2 = _sys.version_info < (3,) +PY3 = not PY2 +RE_NUM = re.compile(r'(\d+).+') + +if _sys.version_info[:2] < (3, 3): + SOCKET_ERROR = socket.error +else: + # socket.error was deprecated and replaced by OSError in python 3.3 + SOCKET_ERROR = OSError + +try: + SOL_TCP = socket.SOL_TCP +except AttributeError: + SOL_TCP = socket.IPPROTO_TCP + +if not PY2: + # these were moved around for Python 3 + from urllib.parse import (quote as url_quote, unquote as url_unquote, + urlencode) + + # Python 3 does not have basestring anymore; we include + # *only* the str here as this is used for textual data. + basestring = (str,) + + # for assertions that the data is either encoded or non-encoded text + str_or_bytes = (str, bytes) + + # xrange is gone, replace it with range + xrange = range + + # the unicode type is str + unicode_type = str + + def dictkeys(dct): + """ + Returns a list of keys of dictionary + + dict.keys returns a view that works like .keys in Python 2 + *except* any modifications in the dictionary will be visible + (and will cause errors if the view is being iterated over while + it is modified). + """ + + return list(dct.keys()) + + def dictvalues(dct): + """ + Returns a list of values of a dictionary + + dict.values returns a view that works like .values in Python 2 + *except* any modifications in the dictionary will be visible + (and will cause errors if the view is being iterated over while + it is modified). + """ + return list(dct.values()) + + def dict_iteritems(dct): + """ + Returns an iterator of items (key/value pairs) of a dictionary + + dict.items returns a view that works like .items in Python 2 + *except* any modifications in the dictionary will be visible + (and will cause errors if the view is being iterated over while + it is modified). + """ + return dct.items() + + def dict_itervalues(dct): + """ + :param dict dct: + :returns: an iterator of the values of a dictionary + """ + return dct.values() + + def byte(*args): + """ + This is the same as Python 2 `chr(n)` for bytes in Python 3 + + Returns a single byte `bytes` for the given int argument (we + optimize it a bit here by passing the positional argument tuple + directly to the bytes constructor. + """ + return bytes(args) + + class long(int): + """ + A marker class that signifies that the integer value should be + serialized as `l` instead of `I` + """ + + def __repr__(self): + return str(self) + 'L' + + def canonical_str(value): + """ + Return the canonical str value for the string. + In both Python 3 and Python 2 this is str. + """ + + return str(value) + + def is_integer(value): + return isinstance(value, int) +else: + from urllib import quote as url_quote, unquote as url_unquote, urlencode + + basestring = basestring + str_or_bytes = basestring + xrange = xrange + unicode_type = unicode + dictkeys = dict.keys + dictvalues = dict.values + dict_iteritems = dict.iteritems + dict_itervalues = dict.itervalues + byte = chr + long = long + + def canonical_str(value): + """ + Returns the canonical string value of the given string. + In Python 2 this is the value unchanged if it is an str, otherwise + it is the unicode value encoded as UTF-8. + """ + + try: + return str(value) + except UnicodeEncodeError: + return str(value.encode('utf-8')) + + def is_integer(value): + return isinstance(value, (int, long)) + + +def as_bytes(value): + if not isinstance(value, bytes): + return value.encode('UTF-8') + return value + + +def to_digit(value): + if value.isdigit(): + return int(value) + match = RE_NUM.match(value) + return int(match.groups()[0]) if match else 0 + + +def get_linux_version(release_str): + ver_str = release_str.split('-')[0] + return tuple(map(to_digit, ver_str.split('.')[:3])) + + +HAVE_SIGNAL = os.name == 'posix' + +EINTR_IS_EXPOSED = _sys.version_info[:2] <= (3, 4) + +LINUX_VERSION = None +if platform.system() == 'Linux': + LINUX_VERSION = get_linux_version(platform.release()) + +_LOCALHOST = '127.0.0.1' +_LOCALHOST_V6 = '::1' + +def _nonblocking_socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): + """ + Returns a pair of sockets in the manner of socketpair with the additional + feature that they will be non-blocking. Prior to Python 3.5, socketpair + did not exist on Windows at all. + """ + if family == socket.AF_INET: + host = _LOCALHOST + elif family == socket.AF_INET6: + host = _LOCALHOST_V6 + else: + raise ValueError( + 'Only AF_INET and AF_INET6 socket address families ' + 'are supported') + if type != socket.SOCK_STREAM: + raise ValueError('Only SOCK_STREAM socket type is supported') + if proto != 0: + raise ValueError('Only protocol zero is supported') + + lsock = socket.socket(family, type, proto) + try: + lsock.bind((host, 0)) + lsock.listen(min(socket.SOMAXCONN, 128)) + # On IPv6, ignore flow_info and scope_id + addr, port = lsock.getsockname()[:2] + csock = socket.socket(family, type, proto) + try: + csock.connect((addr, port)) + ssock, _ = lsock.accept() + except Exception: + csock.close() + raise + finally: + lsock.close() + + # Make sockets non-blocking to prevent deadlocks + # See https://github.com/pika/pika/issues/917 + csock.setblocking(False) + ssock.setblocking(False) + + return ssock, csock diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/connection.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/connection.py new file mode 100644 index 000000000..4cc1e3272 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/connection.py @@ -0,0 +1,2356 @@ +"""Core connection objects""" +import ast +import sys +import collections +import copy +import logging +import math +import numbers +import os +import platform +import warnings +import ssl + +if sys.version_info > (3,): + import urllib.parse as urlparse # pylint: disable=E0611,F0401 +else: + import urlparse + +from pika import __version__ +from pika import callback +import pika.channel +from pika import credentials as pika_credentials +from pika import exceptions +from pika import frame +from pika import heartbeat +from pika import utils + +from pika import spec + +from pika.compat import (xrange, basestring, # pylint: disable=W0622 + url_unquote, dictkeys, dict_itervalues, + dict_iteritems) + + +BACKPRESSURE_WARNING = ("Pika: Write buffer exceeded warning threshold at " + "%i bytes and an estimated %i frames behind") +PRODUCT = "Pika Python Client Library" + +LOGGER = logging.getLogger(__name__) + + +class InternalCloseReasons(object): + """Internal reason codes passed to the user's on_close_callback when the + connection is terminated abruptly, without reply code/text from the broker. + + AMQP 0.9.1 specification cites IETF RFC 821 for reply codes. To avoid + conflict, the `InternalCloseReasons` namespace uses negative integers. These + are invalid for sending to the broker. + """ + SOCKET_ERROR = -1 + BLOCKED_CONNECTION_TIMEOUT = -2 + + +class Parameters(object): # pylint: disable=R0902 + """Base connection parameters class definition + + :param bool backpressure_detection: `DEFAULT_BACKPRESSURE_DETECTION` + :param float|None blocked_connection_timeout: + `DEFAULT_BLOCKED_CONNECTION_TIMEOUT` + :param int channel_max: `DEFAULT_CHANNEL_MAX` + :param int connection_attempts: `DEFAULT_CONNECTION_ATTEMPTS` + :param credentials: `DEFAULT_CREDENTIALS` + :param int frame_max: `DEFAULT_FRAME_MAX` + :param int heartbeat: `DEFAULT_HEARTBEAT_TIMEOUT` + :param str host: `DEFAULT_HOST` + :param str locale: `DEFAULT_LOCALE` + :param int port: `DEFAULT_PORT` + :param float retry_delay: `DEFAULT_RETRY_DELAY` + :param float socket_timeout: `DEFAULT_SOCKET_TIMEOUT` + :param bool ssl: `DEFAULT_SSL` + :param dict ssl_options: `DEFAULT_SSL_OPTIONS` + :param str virtual_host: `DEFAULT_VIRTUAL_HOST` + :param int tcp_options: `DEFAULT_TCP_OPTIONS` + """ + + # Declare slots to protect against accidental assignment of an invalid + # attribute + __slots__ = ( + '_backpressure_detection', + '_blocked_connection_timeout', + '_channel_max', + '_client_properties', + '_connection_attempts', + '_credentials', + '_frame_max', + '_heartbeat', + '_host', + '_locale', + '_port', + '_retry_delay', + '_socket_timeout', + '_ssl', + '_ssl_options', + '_virtual_host', + '_tcp_options' + ) + + DEFAULT_USERNAME = 'guest' + DEFAULT_PASSWORD = 'guest' + + DEFAULT_BACKPRESSURE_DETECTION = False + DEFAULT_BLOCKED_CONNECTION_TIMEOUT = None + DEFAULT_CHANNEL_MAX = pika.channel.MAX_CHANNELS + DEFAULT_CLIENT_PROPERTIES = None + DEFAULT_CREDENTIALS = pika_credentials.PlainCredentials(DEFAULT_USERNAME, + DEFAULT_PASSWORD) + DEFAULT_CONNECTION_ATTEMPTS = 1 + DEFAULT_FRAME_MAX = spec.FRAME_MAX_SIZE + DEFAULT_HEARTBEAT_TIMEOUT = None # None accepts server's proposal + DEFAULT_HOST = 'localhost' + DEFAULT_LOCALE = 'en_US' + DEFAULT_PORT = 5672 + DEFAULT_RETRY_DELAY = 2.0 + DEFAULT_SOCKET_TIMEOUT = 10.0 + DEFAULT_SSL = False + DEFAULT_SSL_OPTIONS = None + DEFAULT_SSL_PORT = 5671 + DEFAULT_VIRTUAL_HOST = '/' + DEFAULT_TCP_OPTIONS = None + + DEFAULT_HEARTBEAT_INTERVAL = DEFAULT_HEARTBEAT_TIMEOUT # DEPRECATED + + def __init__(self): + self._backpressure_detection = None + self.backpressure_detection = self.DEFAULT_BACKPRESSURE_DETECTION + + # If not None, blocked_connection_timeout is the timeout, in seconds, + # for the connection to remain blocked; if the timeout expires, the + # connection will be torn down, triggering the connection's + # on_close_callback + self._blocked_connection_timeout = None + self.blocked_connection_timeout = ( + self.DEFAULT_BLOCKED_CONNECTION_TIMEOUT) + + self._channel_max = None + self.channel_max = self.DEFAULT_CHANNEL_MAX + + self._client_properties = None + self.client_properties = self.DEFAULT_CLIENT_PROPERTIES + + self._connection_attempts = None + self.connection_attempts = self.DEFAULT_CONNECTION_ATTEMPTS + + self._credentials = None + self.credentials = self.DEFAULT_CREDENTIALS + + self._frame_max = None + self.frame_max = self.DEFAULT_FRAME_MAX + + self._heartbeat = None + self.heartbeat = self.DEFAULT_HEARTBEAT_TIMEOUT + + self._host = None + self.host = self.DEFAULT_HOST + + self._locale = None + self.locale = self.DEFAULT_LOCALE + + self._port = None + self.port = self.DEFAULT_PORT + + self._retry_delay = None + self.retry_delay = self.DEFAULT_RETRY_DELAY + + self._socket_timeout = None + self.socket_timeout = self.DEFAULT_SOCKET_TIMEOUT + + self._ssl = None + self.ssl = self.DEFAULT_SSL + + self._ssl_options = None + self.ssl_options = self.DEFAULT_SSL_OPTIONS + + self._virtual_host = None + self.virtual_host = self.DEFAULT_VIRTUAL_HOST + + self._tcp_options = None + self.tcp_options = self.DEFAULT_TCP_OPTIONS + + def __repr__(self): + """Represent the info about the instance. + + :rtype: str + + """ + return ('<%s host=%s port=%s virtual_host=%s ssl=%s>' % + (self.__class__.__name__, self.host, self.port, + self.virtual_host, self.ssl)) + + @property + def backpressure_detection(self): + """ + :returns: boolean indicating whether backpressure detection is + enabled. Defaults to `DEFAULT_BACKPRESSURE_DETECTION`. + + """ + return self._backpressure_detection + + @backpressure_detection.setter + def backpressure_detection(self, value): + """ + :param bool value: boolean indicating whether to enable backpressure + detection + + """ + if not isinstance(value, bool): + raise TypeError('backpressure_detection must be a bool, ' + 'but got %r' % (value,)) + self._backpressure_detection = value + + @property + def blocked_connection_timeout(self): + """ + :returns: None or float blocked connection timeout. Defaults to + `DEFAULT_BLOCKED_CONNECTION_TIMEOUT`. + + """ + return self._blocked_connection_timeout + + @blocked_connection_timeout.setter + def blocked_connection_timeout(self, value): + """ + :param value: If not None, blocked_connection_timeout is the timeout, in + seconds, for the connection to remain blocked; if the timeout + expires, the connection will be torn down, triggering the + connection's on_close_callback + + """ + if value is not None: + if not isinstance(value, numbers.Real): + raise TypeError('blocked_connection_timeout must be a Real ' + 'number, but got %r' % (value,)) + if value < 0: + raise ValueError('blocked_connection_timeout must be >= 0, but ' + 'got %r' % (value,)) + self._blocked_connection_timeout = value + + @property + def channel_max(self): + """ + :returns: max preferred number of channels. Defaults to + `DEFAULT_CHANNEL_MAX`. + :rtype: int + + """ + return self._channel_max + + @channel_max.setter + def channel_max(self, value): + """ + :param int value: max preferred number of channels, between 1 and + `channel.MAX_CHANNELS`, inclusive + + """ + if not isinstance(value, numbers.Integral): + raise TypeError('channel_max must be an int, but got %r' % (value,)) + if value < 1 or value > pika.channel.MAX_CHANNELS: + raise ValueError('channel_max must be <= %i and > 0, but got %r' % + (pika.channel.MAX_CHANNELS, value)) + self._channel_max = value + + @property + def client_properties(self): + """ + :returns: None or dict of client properties used to override the fields + in the default client poperties reported to RabbitMQ via + `Connection.StartOk` method. Defaults to + `DEFAULT_CLIENT_PROPERTIES`. + + """ + return self._client_properties + + @client_properties.setter + def client_properties(self, value): + """ + :param value: None or dict of client properties used to override the + fields in the default client poperties reported to RabbitMQ via + `Connection.StartOk` method. + """ + if not isinstance(value, (dict, type(None),)): + raise TypeError('client_properties must be dict or None, ' + 'but got %r' % (value,)) + # Copy the mutable object to avoid accidental side-effects + self._client_properties = copy.deepcopy(value) + + @property + def connection_attempts(self): + """ + :returns: number of socket connection attempts. Defaults to + `DEFAULT_CONNECTION_ATTEMPTS`. + + """ + return self._connection_attempts + + @connection_attempts.setter + def connection_attempts(self, value): + """ + :param int value: number of socket connection attempts of at least 1 + + """ + if not isinstance(value, numbers.Integral): + raise TypeError('connection_attempts must be an int') + if value < 1: + raise ValueError('connection_attempts must be > 0, but got %r' % + (value,)) + self._connection_attempts = value + + @property + def credentials(self): + """ + :rtype: one of the classes from `pika.credentials.VALID_TYPES`. Defaults + to `DEFAULT_CREDENTIALS`. + + """ + return self._credentials + + @credentials.setter + def credentials(self, value): + """ + :param value: authentication credential object of one of the classes + from `pika.credentials.VALID_TYPES` + + """ + if not isinstance(value, tuple(pika_credentials.VALID_TYPES)): + raise TypeError('Credentials must be an object of type: %r, but ' + 'got %r' % (pika_credentials.VALID_TYPES, value)) + # Copy the mutable object to avoid accidental side-effects + self._credentials = copy.deepcopy(value) + + @property + def frame_max(self): + """ + :returns: desired maximum AMQP frame size to use. Defaults to + `DEFAULT_FRAME_MAX`. + + """ + return self._frame_max + + @frame_max.setter + def frame_max(self, value): + """ + :param int value: desired maximum AMQP frame size to use between + `spec.FRAME_MIN_SIZE` and `spec.FRAME_MAX_SIZE`, inclusive + + """ + if not isinstance(value, numbers.Integral): + raise TypeError('frame_max must be an int, but got %r' % (value,)) + if value < spec.FRAME_MIN_SIZE: + raise ValueError('Min AMQP 0.9.1 Frame Size is %i, but got %r', + (spec.FRAME_MIN_SIZE, value,)) + elif value > spec.FRAME_MAX_SIZE: + raise ValueError('Max AMQP 0.9.1 Frame Size is %i, but got %r', + (spec.FRAME_MAX_SIZE, value,)) + self._frame_max = value + + @property + def heartbeat(self): + """ + :returns: AMQP connection heartbeat timeout value for negotiation during + connection tuning or callable which is invoked during connection tuning. + None to accept broker's value. 0 turns heartbeat off. Defaults to + `DEFAULT_HEARTBEAT_TIMEOUT`. + :rtype: integer, None or callable + + """ + return self._heartbeat + + @heartbeat.setter + def heartbeat(self, value): + """ + :param int|None|callable value: Controls AMQP heartbeat timeout negotiation + during connection tuning. An integer value always overrides the value + proposed by broker. Use 0 to deactivate heartbeats and None to always + accept the broker's proposal. If a callable is given, it will be called + with the connection instance and the heartbeat timeout proposed by broker + as its arguments. The callback should return a non-negative integer that + will be used to override the broker's proposal. + """ + if value is not None: + if not isinstance(value, numbers.Integral) and not callable(value): + raise TypeError('heartbeat must be an int or a callable function, but got %r' % + (value,)) + if not callable(value) and value < 0: + raise ValueError('heartbeat must >= 0, but got %r' % (value,)) + self._heartbeat = value + + @property + def host(self): + """ + :returns: hostname or ip address of broker. Defaults to `DEFAULT_HOST`. + :rtype: str + + """ + return self._host + + @host.setter + def host(self, value): + """ + :param str value: hostname or ip address of broker + + """ + if not isinstance(value, basestring): + raise TypeError('host must be a str or unicode str, but got %r' % + (value,)) + self._host = value + + @property + def locale(self): + """ + :returns: locale value to pass to broker; e.g., 'en_US'. Defaults to + `DEFAULT_LOCALE`. + :rtype: str + + """ + return self._locale + + @locale.setter + def locale(self, value): + """ + :param str value: locale value to pass to broker; e.g., "en_US" + + """ + if not isinstance(value, basestring): + raise TypeError('locale must be a str, but got %r' % (value,)) + self._locale = value + + @property + def port(self): + """ + :returns: port number of broker's listening socket. Defaults to + `DEFAULT_PORT`. + :rtype: int + + """ + return self._port + + @port.setter + def port(self, value): + """ + :param int value: port number of broker's listening socket + + """ + try: + self._port = int(value) + except (TypeError, ValueError): + raise TypeError('port must be an int, but got %r' % (value,)) + + @property + def retry_delay(self): + """ + :returns: interval between socket connection attempts; see also + `connection_attempts`. Defaults to `DEFAULT_RETRY_DELAY`. + :rtype: float + + """ + return self._retry_delay + + @retry_delay.setter + def retry_delay(self, value): + """ + :param float value: interval between socket connection attempts; see + also `connection_attempts`. + + """ + if not isinstance(value, numbers.Real): + raise TypeError('retry_delay must be a float or int, but got %r' % + (value,)) + self._retry_delay = value + + @property + def socket_timeout(self): + """ + :returns: socket timeout value. Defaults to `DEFAULT_SOCKET_TIMEOUT`. + :rtype: float + + """ + return self._socket_timeout + + @socket_timeout.setter + def socket_timeout(self, value): + """ + :param float value: socket timeout value; NOTE: this is mostly unused + now, owing to switchover to to non-blocking socket setting after + initial socket connection establishment. + + """ + if value is not None: + if not isinstance(value, numbers.Real): + raise TypeError('socket_timeout must be a float or int, ' + 'but got %r' % (value,)) + if not value > 0: + raise ValueError('socket_timeout must be > 0, but got %r' % + (value,)) + self._socket_timeout = value + + @property + def ssl(self): + """ + :returns: boolean indicating whether to connect via SSL. Defaults to + `DEFAULT_SSL`. + + """ + return self._ssl + + @ssl.setter + def ssl(self, value): + """ + :param bool value: boolean indicating whether to connect via SSL + + """ + if not isinstance(value, bool): + raise TypeError('ssl must be a bool, but got %r' % (value,)) + self._ssl = value + + @property + def ssl_options(self): + """ + :returns: None or a dict of options to pass to `ssl.wrap_socket`. + Defaults to `DEFAULT_SSL_OPTIONS`. + + """ + return self._ssl_options + + @ssl_options.setter + def ssl_options(self, value): + """ + :param value: None, a dict of options to pass to `ssl.wrap_socket` or + a SSLOptions object for advanced setup. + + """ + if not isinstance(value, (dict, SSLOptions, type(None))): + raise TypeError( + 'ssl_options must be a dict, None or an SSLOptions but got %r' + % (value, )) + # Copy the mutable object to avoid accidental side-effects + self._ssl_options = copy.deepcopy(value) + + + @property + def virtual_host(self): + """ + :returns: rabbitmq virtual host name. Defaults to + `DEFAULT_VIRTUAL_HOST`. + + """ + return self._virtual_host + + @virtual_host.setter + def virtual_host(self, value): + """ + :param str value: rabbitmq virtual host name + + """ + if not isinstance(value, basestring): + raise TypeError('virtual_host must be a str, but got %r' % (value,)) + self._virtual_host = value + + @property + def tcp_options(self): + """ + :returns: None or a dict of options to pass to the underlying socket + """ + return self._tcp_options + + @tcp_options.setter + def tcp_options(self, value): + """ + :param bool value: None or a dict of options to pass to the underlying + socket. Currently supported are TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT + and TCP_USER_TIMEOUT. Availability of these may depend on your platform. + """ + if not isinstance(value, (dict, type(None))): + raise TypeError('tcp_options must be a dict or None, but got %r' % + (value,)) + self._tcp_options = value + + +class ConnectionParameters(Parameters): + """Connection parameters object that is passed into the connection adapter + upon construction. + + """ + + # Protect against accidental assignment of an invalid attribute + __slots__ = () + + class _DEFAULT(object): + """Designates default parameter value; internal use""" + pass + + def __init__(self, # pylint: disable=R0913,R0914,R0912 + host=_DEFAULT, + port=_DEFAULT, + virtual_host=_DEFAULT, + credentials=_DEFAULT, + channel_max=_DEFAULT, + frame_max=_DEFAULT, + heartbeat=_DEFAULT, + ssl=_DEFAULT, + ssl_options=_DEFAULT, + connection_attempts=_DEFAULT, + retry_delay=_DEFAULT, + socket_timeout=_DEFAULT, + locale=_DEFAULT, + backpressure_detection=_DEFAULT, + blocked_connection_timeout=_DEFAULT, + client_properties=_DEFAULT, + tcp_options=_DEFAULT, + **kwargs): + """Create a new ConnectionParameters instance. See `Parameters` for + default values. + + :param str host: Hostname or IP Address to connect to + :param int port: TCP port to connect to + :param str virtual_host: RabbitMQ virtual host to use + :param pika.credentials.Credentials credentials: auth credentials + :param int channel_max: Maximum number of channels to allow + :param int frame_max: The maximum byte size for an AMQP frame + :param int|None|callable value: Controls AMQP heartbeat timeout negotiation + during connection tuning. An integer value always overrides the value + proposed by broker. Use 0 to deactivate heartbeats and None to always + accept the broker's proposal. If a callable is given, it will be called + with the connection instance and the heartbeat timeout proposed by broker + as its arguments. The callback should return a non-negative integer that + will be used to override the broker's proposal. + :param bool ssl: Enable SSL + :param dict ssl_options: None or a dict of arguments to be passed to + ssl.wrap_socket + :param int connection_attempts: Maximum number of retry attempts + :param int|float retry_delay: Time to wait in seconds, before the next + :param int|float socket_timeout: Use for high latency networks + :param str locale: Set the locale value + :param bool backpressure_detection: DEPRECATED in favor of + `Connection.Blocked` and `Connection.Unblocked`. See + `Connection.add_on_connection_blocked_callback`. + :param blocked_connection_timeout: If not None, + the value is a non-negative timeout, in seconds, for the + connection to remain blocked (triggered by Connection.Blocked from + broker); if the timeout expires before connection becomes unblocked, + the connection will be torn down, triggering the adapter-specific + mechanism for informing client app about the closed connection ( + e.g., on_close_callback or ConnectionClosed exception) with + `reason_code` of `InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT`. + :type blocked_connection_timeout: None, int, float + :param client_properties: None or dict of client properties used to + override the fields in the default client properties reported to + RabbitMQ via `Connection.StartOk` method. + :param heartbeat_interval: DEPRECATED; use `heartbeat` instead, and + don't pass both + :param tcp_options: None or a dict of TCP options to set for socket + """ + super(ConnectionParameters, self).__init__() + + if backpressure_detection is not self._DEFAULT: + self.backpressure_detection = backpressure_detection + + if blocked_connection_timeout is not self._DEFAULT: + self.blocked_connection_timeout = blocked_connection_timeout + + if channel_max is not self._DEFAULT: + self.channel_max = channel_max + + if client_properties is not self._DEFAULT: + self.client_properties = client_properties + + if connection_attempts is not self._DEFAULT: + self.connection_attempts = connection_attempts + + if credentials is not self._DEFAULT: + self.credentials = credentials + + if frame_max is not self._DEFAULT: + self.frame_max = frame_max + + if heartbeat is not self._DEFAULT: + self.heartbeat = heartbeat + + try: + heartbeat_interval = kwargs.pop('heartbeat_interval') + except KeyError: + # Good, this one is deprecated + pass + else: + warnings.warn('heartbeat_interval is deprecated, use heartbeat', + DeprecationWarning, stacklevel=2) + if heartbeat is not self._DEFAULT: + raise TypeError('heartbeat and deprecated heartbeat_interval ' + 'are mutually-exclusive') + self.heartbeat = heartbeat_interval + + if host is not self._DEFAULT: + self.host = host + + if locale is not self._DEFAULT: + self.locale = locale + + if retry_delay is not self._DEFAULT: + self.retry_delay = retry_delay + + if socket_timeout is not self._DEFAULT: + self.socket_timeout = socket_timeout + + if ssl is not self._DEFAULT: + self.ssl = ssl + + if ssl_options is not self._DEFAULT: + self.ssl_options = ssl_options + + # Set port after SSL status is known + if port is not self._DEFAULT: + self.port = port + elif ssl is not self._DEFAULT: + self.port = self.DEFAULT_SSL_PORT if self.ssl else self.DEFAULT_PORT + + if virtual_host is not self._DEFAULT: + self.virtual_host = virtual_host + + if tcp_options is not self._DEFAULT: + self.tcp_options = tcp_options + + if kwargs: + raise TypeError('Unexpected kwargs: %r' % (kwargs,)) + + +class URLParameters(Parameters): + """Connect to RabbitMQ via an AMQP URL in the format:: + + amqp://username:password@host:port/[?query-string] + + Ensure that the virtual host is URI encoded when specified. For example if + you are using the default "/" virtual host, the value should be `%2f`. + + See `Parameters` for default values. + + Valid query string values are: + + - backpressure_detection: + DEPRECATED in favor of + `Connection.Blocked` and `Connection.Unblocked`. See + `Connection.add_on_connection_blocked_callback`. + - channel_max: + Override the default maximum channel count value + - client_properties: + dict of client properties used to override the fields in the default + client properties reported to RabbitMQ via `Connection.StartOk` + method + - connection_attempts: + Specify how many times pika should try and reconnect before it gives up + - frame_max: + Override the default maximum frame size for communication + - heartbeat: + Desired connection heartbeat timeout for negotiation. If not present + the broker's value is accepted. 0 turns heartbeat off. + - locale: + Override the default `en_US` locale value + - ssl: + Toggle SSL, possible values are `t`, `f` + - ssl_options: + Arguments passed to :meth:`ssl.wrap_socket` + - retry_delay: + The number of seconds to sleep before attempting to connect on + connection failure. + - socket_timeout: + Override low level socket timeout value + - blocked_connection_timeout: + Set the timeout, in seconds, that the connection may remain blocked + (triggered by Connection.Blocked from broker); if the timeout + expires before connection becomes unblocked, the connection will be + torn down, triggering the connection's on_close_callback + - tcp_options: + Set the tcp options for the underlying socket. + + :param str url: The AMQP URL to connect to + + """ + + # Protect against accidental assignment of an invalid attribute + __slots__ = ('_all_url_query_values',) + + + # The name of the private function for parsing and setting a given URL query + # arg is constructed by catenating the query arg's name to this prefix + _SETTER_PREFIX = '_set_url_' + + def __init__(self, url): + """Create a new URLParameters instance. + + :param str url: The URL value + + """ + super(URLParameters, self).__init__() + + self._all_url_query_values = None + + # Handle the Protocol scheme + # + # Fix up scheme amqp(s) to http(s) so urlparse won't barf on python + # prior to 2.7. On Python 2.6.9, + # `urlparse('amqp://127.0.0.1/%2f?socket_timeout=1')` produces an + # incorrect path='/%2f?socket_timeout=1' + if url[0:4].lower() == 'amqp': + url = 'http' + url[4:] + + # TODO Is support for the alternative http(s) schemes intentional? + + parts = urlparse.urlparse(url) + + if parts.scheme == 'https': + self.ssl = True + elif parts.scheme == 'http': + self.ssl = False + elif parts.scheme: + raise ValueError('Unexpected URL scheme %r; supported scheme ' + 'values: amqp, amqps' % (parts.scheme,)) + + if parts.hostname is not None: + self.host = parts.hostname + + # Take care of port after SSL status is known + if parts.port is not None: + self.port = parts.port + else: + self.port = self.DEFAULT_SSL_PORT if self.ssl else self.DEFAULT_PORT + + if parts.username is not None: + self.credentials = pika_credentials.PlainCredentials(url_unquote(parts.username), + url_unquote(parts.password)) + + # Get the Virtual Host + if len(parts.path) > 1: + self.virtual_host = url_unquote(parts.path.split('/')[1]) + + # Handle query string values, validating and assigning them + self._all_url_query_values = urlparse.parse_qs(parts.query) + + for name, value in dict_iteritems(self._all_url_query_values): + try: + set_value = getattr(self, self._SETTER_PREFIX + name) + except AttributeError: + raise ValueError('Unknown URL parameter: %r' % (name,)) + + try: + (value,) = value + except ValueError: + raise ValueError('Expected exactly one value for URL parameter ' + '%s, but got %i values: %s' % ( + name, len(value), value)) + + set_value(value) + + def _set_url_backpressure_detection(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + backpressure_detection = {'t': True, 'f': False}[value] + except KeyError: + raise ValueError('Invalid backpressure_detection value: %r' % + (value,)) + self.backpressure_detection = backpressure_detection + + def _set_url_blocked_connection_timeout(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + blocked_connection_timeout = float(value) + except ValueError as exc: + raise ValueError('Invalid blocked_connection_timeout value %r: %r' % + (value, exc,)) + self.blocked_connection_timeout = blocked_connection_timeout + + def _set_url_channel_max(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + channel_max = int(value) + except ValueError as exc: + raise ValueError('Invalid channel_max value %r: %r' % (value, exc,)) + self.channel_max = channel_max + + def _set_url_client_properties(self, value): + """Deserialize and apply the corresponding query string arg""" + self.client_properties = ast.literal_eval(value) + + def _set_url_connection_attempts(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + connection_attempts = int(value) + except ValueError as exc: + raise ValueError('Invalid connection_attempts value %r: %r' % + (value, exc,)) + self.connection_attempts = connection_attempts + + def _set_url_frame_max(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + frame_max = int(value) + except ValueError as exc: + raise ValueError('Invalid frame_max value %r: %r' % (value, exc,)) + self.frame_max = frame_max + + def _set_url_heartbeat(self, value): + """Deserialize and apply the corresponding query string arg""" + if 'heartbeat_interval' in self._all_url_query_values: + raise ValueError('Deprecated URL parameter heartbeat_interval must ' + 'not be specified together with heartbeat') + + try: + heartbeat_timeout = int(value) + except ValueError as exc: + raise ValueError('Invalid heartbeat value %r: %r' % (value, exc,)) + self.heartbeat = heartbeat_timeout + + def _set_url_heartbeat_interval(self, value): + """Deserialize and apply the corresponding query string arg""" + warnings.warn('heartbeat_interval is deprecated, use heartbeat', + DeprecationWarning, stacklevel=2) + + if 'heartbeat' in self._all_url_query_values: + raise ValueError('Deprecated URL parameter heartbeat_interval must ' + 'not be specified together with heartbeat') + + try: + heartbeat_timeout = int(value) + except ValueError as exc: + raise ValueError('Invalid heartbeat_interval value %r: %r' % + (value, exc,)) + self.heartbeat = heartbeat_timeout + + def _set_url_locale(self, value): + """Deserialize and apply the corresponding query string arg""" + self.locale = value + + def _set_url_retry_delay(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + retry_delay = float(value) + except ValueError as exc: + raise ValueError('Invalid retry_delay value %r: %r' % (value, exc,)) + self.retry_delay = retry_delay + + def _set_url_socket_timeout(self, value): + """Deserialize and apply the corresponding query string arg""" + try: + socket_timeout = float(value) + except ValueError as exc: + raise ValueError('Invalid socket_timeout value %r: %r' % + (value, exc,)) + self.socket_timeout = socket_timeout + + def _set_url_ssl_options(self, value): + """Deserialize and apply the corresponding query string arg + + """ + opts = ast.literal_eval(value) + if opts is None: + if self.ssl_options is not None: + raise ValueError( + 'Specified ssl_options=None URL arg is inconsistent with ' + 'the specified https URL scheme.') + else: + self.ssl_options = pika.SSLOptions( + keyfile=opts.get('keyfile'), + key_password=opts.get('key_password') or opts.get('password'), + certfile=opts.get('certfile'), + verify_mode=opts.get('verify_mode') or ssl.CERT_NONE, + ssl_version=opts.get('ssl_version') or ssl.PROTOCOL_TLSv1, + cafile=opts.get('cafile'), + capath=opts.get('capath'), + cadata=opts.get('cadata'), + ciphers=opts.get('ciphers'), + server_hostname=opts.get('server_hostname')) + + def _set_url_tcp_options(self, value): + """Deserialize and apply the corresponding query string arg""" + self.tcp_options = ast.literal_eval(value) + +class SSLOptions(object): + """Class used to provide parameters for optional fine grained control of SSL + socket wrapping. + + :param string keyfile: The key file to pass to SSLContext.load_cert_chain + :param string key_password: The key password to passed to + SSLContext.load_cert_chain + :param string certfile: The certificate file to passed to + SSLContext.load_cert_chain + :param bool server_side: Passed to SSLContext.wrap_socket + :param verify_mode: Passed to SSLContext.wrap_socket + :param ssl_version: Passed to SSLContext init, defines the ssl + version to use + :param string cafile: The CA file passed to + SSLContext.load_verify_locations + :param string capath: The CA path passed to + SSLContext.load_verify_locations + :param string cadata: The CA data passed to + SSLContext.load_verify_locations + :param do_handshake_on_connect: Passed to SSLContext.wrap_socket + :param suppress_ragged_eofs: Passed to SSLContext.wrap_socket + :param ciphers: Passed to SSLContext.set_ciphers + :param server_hostname: SSLContext.wrap_socket, used to enable SNI + """ + + def __init__(self, + keyfile=None, + key_password=None, + certfile=None, + server_side=False, + verify_mode=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_TLSv1, + cafile=None, + capath=None, + cadata=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + ciphers=None, + server_hostname=None): + self.keyfile = keyfile + self.key_password = key_password + self.certfile = certfile + self.server_side = server_side + self.verify_mode = verify_mode + self.ssl_version = ssl_version + self.cafile = cafile + self.capath = capath + self.cadata = cadata + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs + self.ciphers = ciphers + self.server_hostname = server_hostname + +class Connection(object): + """This is the core class that implements communication with RabbitMQ. This + class should not be invoked directly but rather through the use of an + adapter such as SelectConnection or BlockingConnection. + + :param pika.connection.Parameters parameters: Connection parameters + :param method on_open_callback: Called when the connection is opened + :param method on_open_error_callback: Called if the connection cant + be opened + :param method on_close_callback: Called when the connection is closed + + """ + + # Disable pylint messages concerning "method could be a funciton" + # pylint: disable=R0201 + + ON_CONNECTION_BACKPRESSURE = '_on_connection_backpressure' + ON_CONNECTION_BLOCKED = '_on_connection_blocked' + ON_CONNECTION_CLOSED = '_on_connection_closed' + ON_CONNECTION_ERROR = '_on_connection_error' + ON_CONNECTION_OPEN = '_on_connection_open' + ON_CONNECTION_UNBLOCKED = '_on_connection_unblocked' + CONNECTION_CLOSED = 0 + CONNECTION_INIT = 1 + CONNECTION_PROTOCOL = 2 + CONNECTION_START = 3 + CONNECTION_TUNE = 4 + CONNECTION_OPEN = 5 + CONNECTION_CLOSING = 6 # client-initiated close in progress + + _STATE_NAMES = { + CONNECTION_CLOSED: 'CLOSED', + CONNECTION_INIT: 'INIT', + CONNECTION_PROTOCOL: 'PROTOCOL', + CONNECTION_START: 'START', + CONNECTION_TUNE: 'TUNE', + CONNECTION_OPEN: 'OPEN', + CONNECTION_CLOSING: 'CLOSING' + } + + def __init__(self, + parameters=None, + on_open_callback=None, + on_open_error_callback=None, + on_close_callback=None): + """Connection initialization expects an object that has implemented the + Parameters class and a callback function to notify when we have + successfully connected to the AMQP Broker. + + Available Parameters classes are the ConnectionParameters class and + URLParameters class. + + :param pika.connection.Parameters parameters: Connection parameters + :param method on_open_callback: Called when the connection is opened + :param method on_open_error_callback: Called if the connection can't + be established: on_open_error_callback(connection, str|exception) + :param method on_close_callback: Called when the connection is closed: + `on_close_callback(connection, reason_code, reason_text)`, where + `reason_code` is either an IETF RFC 821 reply code for AMQP-level + closures or a value from `pika.connection.InternalCloseReasons` for + internal causes, such as socket errors. + + """ + self.connection_state = self.CONNECTION_CLOSED + + # Holds timer when the initial connect or reconnect is scheduled + self._connection_attempt_timer = None + + # Used to hold timer if configured for Connection.Blocked timeout + self._blocked_conn_timer = None + + self.heartbeat = None + + # Set our configuration options + self.params = (copy.deepcopy(parameters) if parameters is not None else + ConnectionParameters()) + + # Define our callback dictionary + self.callbacks = callback.CallbackManager() + + # Attributes that will be properly initialized by _init_connection_state + # and/or during connection handshake. + self.server_capabilities = None + self.server_properties = None + self._body_max_length = None + self.known_hosts = None + self.closing = None + self._frame_buffer = None + self._channels = None + self._backpressure_multiplier = None + self.remaining_connection_attempts = None + + self._init_connection_state() + + + # Add the on connection error callback + self.callbacks.add(0, self.ON_CONNECTION_ERROR, + on_open_error_callback or self._on_connection_error, + False) + + # On connection callback + if on_open_callback: + self.add_on_open_callback(on_open_callback) + + # On connection callback + if on_close_callback: + self.add_on_close_callback(on_close_callback) + + self.connect() + + def add_backpressure_callback(self, callback_method): + """Call method "callback" when pika believes backpressure is being + applied. + + :param method callback_method: The method to call + + """ + self.callbacks.add(0, self.ON_CONNECTION_BACKPRESSURE, callback_method, + False) + + def add_on_close_callback(self, callback_method): + """Add a callback notification when the connection has closed. The + callback will be passed the connection, the reply_code (int) and the + reply_text (str), if sent by the remote server. + + :param method callback_method: Callback to call on close + + """ + self.callbacks.add(0, self.ON_CONNECTION_CLOSED, callback_method, False) + + def add_on_connection_blocked_callback(self, callback_method): + """Add a callback to be notified when RabbitMQ has sent a + ``Connection.Blocked`` frame indicating that RabbitMQ is low on + resources. Publishers can use this to voluntarily suspend publishing, + instead of relying on back pressure throttling. The callback + will be passed the ``Connection.Blocked`` method frame. + + See also `ConnectionParameters.blocked_connection_timeout`. + + :param method callback_method: Callback to call on `Connection.Blocked`, + having the signature `callback_method(pika.frame.Method)`, where the + method frame's `method` member is of type + `pika.spec.Connection.Blocked` + + """ + self.callbacks.add(0, spec.Connection.Blocked, callback_method, False) + + def add_on_connection_unblocked_callback(self, callback_method): + """Add a callback to be notified when RabbitMQ has sent a + ``Connection.Unblocked`` frame letting publishers know it's ok + to start publishing again. The callback will be passed the + ``Connection.Unblocked`` method frame. + + :param method callback_method: Callback to call on + `Connection.Unblocked`, having the signature + `callback_method(pika.frame.Method)`, where the method frame's + `method` member is of type `pika.spec.Connection.Unblocked` + + """ + self.callbacks.add(0, spec.Connection.Unblocked, callback_method, False) + + def add_on_open_callback(self, callback_method): + """Add a callback notification when the connection has opened. + + :param method callback_method: Callback to call when open + + """ + self.callbacks.add(0, self.ON_CONNECTION_OPEN, callback_method, False) + + def add_on_open_error_callback(self, callback_method, remove_default=True): + """Add a callback notification when the connection can not be opened. + + The callback method should accept the connection object that could not + connect, and an optional error message. + + :param method callback_method: Callback to call when can't connect + :param bool remove_default: Remove default exception raising callback + + """ + if remove_default: + self.callbacks.remove(0, self.ON_CONNECTION_ERROR, + self._on_connection_error) + self.callbacks.add(0, self.ON_CONNECTION_ERROR, callback_method, False) + + def add_timeout(self, deadline, callback_method): + """Adapters should override to call the callback after the + specified number of seconds have elapsed, using a timer, or a + thread, or similar. + + :param int deadline: The number of seconds to wait to call callback + :param method callback_method: The callback method + + """ + raise NotImplementedError + + def channel(self, on_open_callback, channel_number=None): + """Create a new channel with the next available channel number or pass + in a channel number to use. Must be non-zero if you would like to + specify but it is recommended that you let Pika manage the channel + numbers. + + :param method on_open_callback: The callback when the channel is opened + :param int channel_number: The channel number to use, defaults to the + next available. + :rtype: pika.channel.Channel + + """ + if not self.is_open: + # TODO if state is OPENING, then ConnectionClosed might be wrong + raise exceptions.ConnectionClosed( + 'Channel allocation requires an open connection: %s' % self) + + if not channel_number: + channel_number = self._next_channel_number() + self._channels[channel_number] = self._create_channel(channel_number, + on_open_callback) + self._add_channel_callbacks(channel_number) + self._channels[channel_number].open() + return self._channels[channel_number] + + def close(self, reply_code=200, reply_text='Normal shutdown'): + """Disconnect from RabbitMQ. If there are any open channels, it will + attempt to close them prior to fully disconnecting. Channels which + have active consumers will attempt to send a Basic.Cancel to RabbitMQ + to cleanly stop the delivery of messages prior to closing the channel. + + :param int reply_code: The code number for the close + :param str reply_text: The text reason for the close + + """ + if self.is_closing or self.is_closed: + LOGGER.warning('Suppressing close request on %s', self) + return + + # NOTE The connection is either in opening or open state + + # Initiate graceful closing of channels that are OPEN or OPENING + if self._channels: + self._close_channels(reply_code, reply_text) + + # Set our connection state + self._set_connection_state(self.CONNECTION_CLOSING) + LOGGER.info("Closing connection (%s): %s", reply_code, reply_text) + self.closing = reply_code, reply_text + + # If there are channels that haven't finished closing yet, then + # _on_close_ready will finally be called from _on_channel_cleanup once + # all channels have been closed + if not self._channels: + # We can initiate graceful closing of the connection right away, + # since no more channels remain + self._on_close_ready() + else: + LOGGER.info('Connection.close is waiting for ' + '%d channels to close: %s', len(self._channels), self) + + def connect(self): + """Invoke if trying to reconnect to a RabbitMQ server. Constructing the + Connection object should connect on its own. + + """ + assert self._connection_attempt_timer is None, ( + 'connect timer was already scheduled') + + assert self.is_closed, ( + 'connect expected CLOSED state, but got: {}'.format( + self._STATE_NAMES[self.connection_state])) + + self._set_connection_state(self.CONNECTION_INIT) + + # Schedule a timer callback to start the actual connection logic from + # event loop's context, thus avoiding error callbacks in the context of + # the caller, which could be the constructor. + self._connection_attempt_timer = self.add_timeout( + 0, + self._on_connect_timer) + + + def remove_timeout(self, timeout_id): + """Adapters should override: Remove a timeout + + :param str timeout_id: The timeout id to remove + + """ + raise NotImplementedError + + def set_backpressure_multiplier(self, value=10): + """Alter the backpressure multiplier value. We set this to 10 by default. + This value is used to raise warnings and trigger the backpressure + callback. + + :param int value: The multiplier value to set + + """ + self._backpressure_multiplier = value + + # + # Connection state properties + # + + @property + def is_closed(self): + """ + Returns a boolean reporting the current connection state. + """ + return self.connection_state == self.CONNECTION_CLOSED + + @property + def is_closing(self): + """ + Returns True if connection is in the process of closing due to + client-initiated `close` request, but closing is not yet complete. + """ + return self.connection_state == self.CONNECTION_CLOSING + + @property + def is_open(self): + """ + Returns a boolean reporting the current connection state. + """ + return self.connection_state == self.CONNECTION_OPEN + + # + # Properties that reflect server capabilities for the current connection + # + + @property + def basic_nack(self): + """Specifies if the server supports basic.nack on the active connection. + + :rtype: bool + + """ + return self.server_capabilities.get('basic.nack', False) + + @property + def consumer_cancel_notify(self): + """Specifies if the server supports consumer cancel notification on the + active connection. + + :rtype: bool + + """ + return self.server_capabilities.get('consumer_cancel_notify', False) + + @property + def exchange_exchange_bindings(self): + """Specifies if the active connection supports exchange to exchange + bindings. + + :rtype: bool + + """ + return self.server_capabilities.get('exchange_exchange_bindings', False) + + @property + def publisher_confirms(self): + """Specifies if the active connection can use publisher confirmations. + + :rtype: bool + + """ + return self.server_capabilities.get('publisher_confirms', False) + + # + # Internal methods for managing the communication process + # + + def _adapter_connect(self): + """Subclasses should override to set up the outbound socket connection. + + :raises: NotImplementedError + + """ + raise NotImplementedError + + def _adapter_disconnect(self): + """Subclasses should override this to cause the underlying transport + (socket) to close. + + :raises: NotImplementedError + + """ + raise NotImplementedError + + def _add_channel_callbacks(self, channel_number): + """Add the appropriate callbacks for the specified channel number. + + :param int channel_number: The channel number for the callbacks + + """ + # pylint: disable=W0212 + + # This permits us to garbage-collect our reference to the channel + # regardless of whether it was closed by client or broker, and do so + # after all channel-close callbacks. + self._channels[channel_number]._add_on_cleanup_callback( + self._on_channel_cleanup) + + def _add_connection_start_callback(self): + """Add a callback for when a Connection.Start frame is received from + the broker. + + """ + self.callbacks.add(0, spec.Connection.Start, self._on_connection_start) + + def _add_connection_tune_callback(self): + """Add a callback for when a Connection.Tune frame is received.""" + self.callbacks.add(0, spec.Connection.Tune, self._on_connection_tune) + + def _append_frame_buffer(self, value): + """Append the bytes to the frame buffer. + + :param str value: The bytes to append to the frame buffer + + """ + self._frame_buffer += value + + @property + def _buffer_size(self): + """Return the suggested buffer size from the connection state/tune or + the default if that is None. + + :rtype: int + + """ + return self.params.frame_max or spec.FRAME_MAX_SIZE + + def _check_for_protocol_mismatch(self, value): + """Invoked when starting a connection to make sure it's a supported + protocol. + + :param pika.frame.Method value: The frame to check + :raises: ProtocolVersionMismatch + + """ + if (value.method.version_major, + value.method.version_minor) != spec.PROTOCOL_VERSION[0:2]: + # TODO This should call _on_terminate for proper callbacks and + # cleanup + raise exceptions.ProtocolVersionMismatch(frame.ProtocolHeader(), + value) + + @property + def _client_properties(self): + """Return the client properties dictionary. + + :rtype: dict + + """ + properties = { + 'product': PRODUCT, + 'platform': 'Python %s' % platform.python_version(), + 'capabilities': { + 'authentication_failure_close': True, + 'basic.nack': True, + 'connection.blocked': True, + 'consumer_cancel_notify': True, + 'publisher_confirms': True + }, + 'information': 'See http://pika.rtfd.org', + 'version': __version__ + } + + if self.params.client_properties: + properties.update(self.params.client_properties) + + return properties + + def _close_channels(self, reply_code, reply_text): + """Initiate graceful closing of channels that are in OPEN or OPENING + states, passing reply_code and reply_text. + + :param int reply_code: The code for why the channels are being closed + :param str reply_text: The text reason for why the channels are closing + + """ + assert self.is_open, str(self) + + for channel_number in dictkeys(self._channels): + chan = self._channels[channel_number] + if not (chan.is_closing or chan.is_closed): + chan.close(reply_code, reply_text) + + def _connect(self): + """Attempt to connect to RabbitMQ + + :rtype: bool + + """ + warnings.warn('This method is deprecated, use Connection.connect', + DeprecationWarning) + + def _create_channel(self, channel_number, on_open_callback): + """Create a new channel using the specified channel number and calling + back the method specified by on_open_callback + + :param int channel_number: The channel number to use + :param method on_open_callback: The callback when the channel is opened + + """ + LOGGER.debug('Creating channel %s', channel_number) + return pika.channel.Channel(self, channel_number, on_open_callback) + + def _create_heartbeat_checker(self): + """Create a heartbeat checker instance if there is a heartbeat interval + set. + + :rtype: pika.heartbeat.Heartbeat|None + + """ + if self.params.heartbeat is not None and self.params.heartbeat > 0: + LOGGER.debug('Creating a HeartbeatChecker: %r', + self.params.heartbeat) + return heartbeat.HeartbeatChecker(self, self.params.heartbeat) + + return None + + def _remove_heartbeat(self): + """Stop the heartbeat checker if it exists + + """ + if self.heartbeat: + self.heartbeat.stop() + self.heartbeat = None + + def _deliver_frame_to_channel(self, value): + """Deliver the frame to the channel specified in the frame. + + :param pika.frame.Method value: The frame to deliver + + """ + if not value.channel_number in self._channels: + # This should never happen and would constitute breach of the + # protocol + LOGGER.critical( + 'Received %s frame for unregistered channel %i on %s', + value.NAME, value.channel_number, self) + return + + # pylint: disable=W0212 + self._channels[value.channel_number]._handle_content_frame(value) + + def _detect_backpressure(self): + """Attempt to calculate if TCP backpressure is being applied due to + our outbound buffer being larger than the average frame size over + a window of frames. + + """ + avg_frame_size = self.bytes_sent / self.frames_sent + buffer_size = sum([len(f) for f in self.outbound_buffer]) + if buffer_size > (avg_frame_size * self._backpressure_multiplier): + LOGGER.warning(BACKPRESSURE_WARNING, buffer_size, + int(buffer_size / avg_frame_size)) + self.callbacks.process(0, self.ON_CONNECTION_BACKPRESSURE, self) + + def _ensure_closed(self): + """If the connection is not closed, close it.""" + if self.is_open: + self.close() + + def _flush_outbound(self): + """Adapters should override to flush the contents of outbound_buffer + out along the socket. + + :raises: NotImplementedError + + """ + raise NotImplementedError + + def _get_body_frame_max_length(self): + """Calculate the maximum amount of bytes that can be in a body frame. + + :rtype: int + + """ + return ( + self.params.frame_max - spec.FRAME_HEADER_SIZE - spec.FRAME_END_SIZE + ) + + def _get_credentials(self, method_frame): + """Get credentials for authentication. + + :param pika.frame.MethodFrame method_frame: The Connection.Start frame + :rtype: tuple(str, str) + + """ + (auth_type, + response) = self.params.credentials.response_for(method_frame.method) + if not auth_type: + # TODO this should call _on_terminate for proper callbacks and + # cleanup instead + raise exceptions.AuthenticationError(self.params.credentials.TYPE) + self.params.credentials.erase_credentials() + return auth_type, response + + def _has_pending_callbacks(self, value): + """Return true if there are any callbacks pending for the specified + frame. + + :param pika.frame.Method value: The frame to check + :rtype: bool + + """ + return self.callbacks.pending(value.channel_number, value.method) + + def _init_connection_state(self): + """Initialize or reset all of the internal state variables for a given + connection. On disconnect or reconnect all of the state needs to + be wiped. + + """ + # Connection state + self._set_connection_state(self.CONNECTION_CLOSED) + + # Negotiated server properties + self.server_properties = None + + # Outbound buffer for buffering writes until we're able to send them + self.outbound_buffer = collections.deque([]) + + # Inbound buffer for decoding frames + self._frame_buffer = bytes() + + # Dict of open channels + self._channels = dict() + + # Remaining connection attempts + self.remaining_connection_attempts = self.params.connection_attempts + + # Data used for Heartbeat checking and back-pressure detection + self.bytes_sent = 0 + self.bytes_received = 0 + self.frames_sent = 0 + self.frames_received = 0 + self.heartbeat = None + + # Default back-pressure multiplier value + self._backpressure_multiplier = 10 + + # When closing, hold reason why + self.closing = 0, 'Not specified' + + # Our starting point once connected, first frame received + self._add_connection_start_callback() + + # Add a callback handler for the Broker telling us to disconnect. + # NOTE: As of RabbitMQ 3.6.0, RabbitMQ broker may send Connection.Close + # to signal error during connection setup (and wait a longish time + # before closing the TCP/IP stream). Earlier RabbitMQ versions + # simply closed the TCP/IP stream. + self.callbacks.add(0, spec.Connection.Close, self._on_connection_close) + + if self._connection_attempt_timer is not None: + # Connection attempt timer was active when teardown was initiated + self.remove_timeout(self._connection_attempt_timer) + self._connection_attempt_timer = None + + if self.params.blocked_connection_timeout is not None: + if self._blocked_conn_timer is not None: + # Blocked connection timer was active when teardown was + # initiated + self.remove_timeout(self._blocked_conn_timer) + self._blocked_conn_timer = None + + self.add_on_connection_blocked_callback( + self._on_connection_blocked) + self.add_on_connection_unblocked_callback( + self._on_connection_unblocked) + + def _is_method_frame(self, value): + """Returns true if the frame is a method frame. + + :param pika.frame.Frame value: The frame to evaluate + :rtype: bool + + """ + return isinstance(value, frame.Method) + + def _is_protocol_header_frame(self, value): + """Returns True if it's a protocol header frame. + + :rtype: bool + + """ + return isinstance(value, frame.ProtocolHeader) + + def _next_channel_number(self): + """Return the next available channel number or raise an exception. + + :rtype: int + + """ + limit = self.params.channel_max or pika.channel.MAX_CHANNELS + if len(self._channels) >= limit: + raise exceptions.NoFreeChannels() + + for num in xrange(1, len(self._channels) + 1): + if num not in self._channels: + return num + return len(self._channels) + 1 + + def _on_channel_cleanup(self, channel): + """Remove the channel from the dict of channels when Channel.CloseOk is + sent. If connection is closing and no more channels remain, proceed to + `_on_close_ready`. + + :param pika.channel.Channel channel: channel instance + + """ + try: + del self._channels[channel.channel_number] + LOGGER.debug('Removed channel %s', channel.channel_number) + except KeyError: + LOGGER.error('Channel %r not in channels', + channel.channel_number) + if self.is_closing: + if not self._channels: + # Initiate graceful closing of the connection + self._on_close_ready() + else: + # Once Connection enters CLOSING state, all remaining channels + # should also be in CLOSING state. Deviation from this would + # prevent Connection from completing its closing procedure. + channels_not_in_closing_state = [ + chan for chan in dict_itervalues(self._channels) + if not chan.is_closing] + if channels_not_in_closing_state: + LOGGER.critical( + 'Connection in CLOSING state has non-CLOSING ' + 'channels: %r', channels_not_in_closing_state) + + def _on_close_ready(self): + """Called when the Connection is in a state that it can close after + a close has been requested. This happens, for example, when all of the + channels are closed that were open when the close request was made. + + """ + if self.is_closed: + LOGGER.warning('_on_close_ready invoked when already closed') + return + + self._send_connection_close(self.closing[0], self.closing[1]) + + def _on_connected(self): + """Invoked when the socket is connected and it's time to start speaking + AMQP with the broker. + + """ + self._set_connection_state(self.CONNECTION_PROTOCOL) + + # Start the communication with the RabbitMQ Broker + self._send_frame(frame.ProtocolHeader()) + + def _on_blocked_connection_timeout(self): + """ Called when the "connection blocked timeout" expires. When this + happens, we tear down the connection + + """ + self._blocked_conn_timer = None + self._on_terminate(InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT, + 'Blocked connection timeout expired') + + def _on_connection_blocked(self, method_frame): + """Handle Connection.Blocked notification from RabbitMQ broker + + :param pika.frame.Method method_frame: method frame having `method` + member of type `pika.spec.Connection.Blocked` + """ + LOGGER.warning('Received %s from broker', method_frame) + + if self._blocked_conn_timer is not None: + # RabbitMQ is not supposed to repeat Connection.Blocked, but it + # doesn't hurt to be careful + LOGGER.warning('_blocked_conn_timer %s already set when ' + '_on_connection_blocked is called', + self._blocked_conn_timer) + else: + self._blocked_conn_timer = self.add_timeout( + self.params.blocked_connection_timeout, + self._on_blocked_connection_timeout) + + def _on_connection_unblocked(self, method_frame): + """Handle Connection.Unblocked notification from RabbitMQ broker + + :param pika.frame.Method method_frame: method frame having `method` + member of type `pika.spec.Connection.Blocked` + """ + LOGGER.info('Received %s from broker', method_frame) + + if self._blocked_conn_timer is None: + # RabbitMQ is supposed to pair Connection.Blocked/Unblocked, but it + # doesn't hurt to be careful + LOGGER.warning('_blocked_conn_timer was not active when ' + '_on_connection_unblocked called') + else: + self.remove_timeout(self._blocked_conn_timer) + self._blocked_conn_timer = None + + def _on_connection_close(self, method_frame): + """Called when the connection is closed remotely via Connection.Close + frame from broker. + + :param pika.frame.Method method_frame: The Connection.Close frame + + """ + LOGGER.debug('_on_connection_close: frame=%s', method_frame) + + self.closing = (method_frame.method.reply_code, + method_frame.method.reply_text) + + self._on_terminate(self.closing[0], self.closing[1]) + + def _on_connection_close_ok(self, method_frame): + """Called when Connection.CloseOk is received from remote. + + :param pika.frame.Method method_frame: The Connection.CloseOk frame + + """ + LOGGER.debug('_on_connection_close_ok: frame=%s', method_frame) + + self._on_terminate(self.closing[0], self.closing[1]) + + def _on_connection_error(self, _connection_unused, error_message=None): + """Default behavior when the connecting connection can not connect. + + :raises: exceptions.AMQPConnectionError + + """ + raise exceptions.AMQPConnectionError(error_message or + self.params.connection_attempts) + + def _on_connection_open(self, method_frame): + """ + This is called once we have tuned the connection with the server and + called the Connection.Open on the server and it has replied with + Connection.Ok. + """ + # TODO _on_connection_open - what if user started closing it already? + # It shouldn't transition to OPEN if in closing state. Just log and skip + # the rest. + + self.known_hosts = method_frame.method.known_hosts + + # We're now connected at the AMQP level + self._set_connection_state(self.CONNECTION_OPEN) + + # Call our initial callback that we're open + self.callbacks.process(0, self.ON_CONNECTION_OPEN, self, self) + + def _on_connection_start(self, method_frame): + """This is called as a callback once we have received a Connection.Start + from the server. + + :param pika.frame.Method method_frame: The frame received + :raises: UnexpectedFrameError + + """ + self._set_connection_state(self.CONNECTION_START) + if self._is_protocol_header_frame(method_frame): + raise exceptions.UnexpectedFrameError + self._check_for_protocol_mismatch(method_frame) + self._set_server_information(method_frame) + self._add_connection_tune_callback() + self._send_connection_start_ok(*self._get_credentials(method_frame)) + + def _on_connect_timer(self): + """Callback for self._connection_attempt_timer: initiate connection + attempt in the context of the event loop + + """ + self._connection_attempt_timer = None + + error = self._adapter_connect() + if not error: + self._on_connected() + return + + self.remaining_connection_attempts -= 1 + LOGGER.warning('Could not connect, %i attempts left', + self.remaining_connection_attempts) + if self.remaining_connection_attempts > 0: + LOGGER.info('Retrying in %i seconds', self.params.retry_delay) + self._connection_attempt_timer = self.add_timeout( + self.params.retry_delay, + self._on_connect_timer) + else: + # TODO connect must not call failure callback from constructor. The + # current behavior is error-prone, because the user code may get a + # callback upon socket connection failure before user's other state + # may be sufficiently initialized. Constructors must either succeed + # or raise an exception. To be forward-compatible with failure + # reporting from fully non-blocking connection establishment, + # connect() should set INIT state and schedule a 0-second timer to + # continue the rest of the logic in a private method. The private + # method should use itself instead of connect() as the callback for + # scheduling retries. + + # TODO This should use _on_terminate for consistent behavior/cleanup + self.callbacks.process(0, self.ON_CONNECTION_ERROR, self, self, + error) + self.remaining_connection_attempts = self.params.connection_attempts + self._set_connection_state(self.CONNECTION_CLOSED) + + @staticmethod + def _negotiate_integer_value(client_value, server_value): + """Negotiates two values. If either of them is 0 or None, + returns the other one. If both are positive integers, returns the + smallest one. + + :param int client_value: The client value + :param int server_value: The server value + :rtype: int + + """ + if client_value == None: + client_value = 0 + if server_value == None: + server_value = 0 + + # this is consistent with how Java client and Bunny + # perform negotiation, see pika/pika#874 + if client_value == 0 or server_value == 0: + val = max(client_value, server_value) + else: + val = min(client_value, server_value) + + return val + + @staticmethod + def _tune_heartbeat_timeout(client_value, server_value): + """ Determine heartbeat timeout per AMQP 0-9-1 rules + + Per https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf, + + > Both peers negotiate the limits to the lowest agreed value as follows: + > - The server MUST tell the client what limits it proposes. + > - The client responds and **MAY reduce those limits** for its + connection + + If the client specifies a value, it always takes precedence. + + :param client_value: None to accept server_value; otherwise, an integral + number in seconds; 0 (zero) to disable heartbeat. + :param server_value: integral value of the heartbeat timeout proposed by + broker; 0 (zero) to disable heartbeat. + + :returns: the value of the heartbeat timeout to use and return to broker + """ + if client_value is None: + # Accept server's limit + timeout = server_value + else: + timeout = client_value + + return timeout + + def _on_connection_tune(self, method_frame): + """Once the Broker sends back a Connection.Tune, we will set our tuning + variables that have been returned to us and kick off the Heartbeat + monitor if required, send our TuneOk and then the Connection. Open rpc + call on channel 0. + + :param pika.frame.Method method_frame: The frame received + + """ + self._set_connection_state(self.CONNECTION_TUNE) + + # Get our max channels, frames and heartbeat interval + self.params.channel_max = Connection._negotiate_integer_value(self.params.channel_max, + method_frame.method.channel_max) + self.params.frame_max = Connection._negotiate_integer_value(self.params.frame_max, + method_frame.method.frame_max) + + if callable(self.params.heartbeat): + ret_heartbeat = self.params.heartbeat(self, method_frame.method.heartbeat) + if ret_heartbeat is None or callable(ret_heartbeat): + # Enforce callback-specific restrictions on callback's return value + raise TypeError('heartbeat callback must not return None ' + 'or callable, but got %r' % (ret_heartbeat,)) + + # Leave it to hearbeat setter deal with the rest of the validation + self.params.heartbeat = ret_heartbeat + + # Negotiate heatbeat timeout + self.params.heartbeat = self._tune_heartbeat_timeout( + client_value=self.params.heartbeat, + server_value=method_frame.method.heartbeat) + + # Calculate the maximum pieces for body frames + self._body_max_length = self._get_body_frame_max_length() + + # Create a new heartbeat checker if needed + self.heartbeat = self._create_heartbeat_checker() + + # Send the TuneOk response with what we've agreed upon + self._send_connection_tune_ok() + + # Send the Connection.Open RPC call for the vhost + self._send_connection_open() + + def _on_data_available(self, data_in): + """This is called by our Adapter, passing in the data from the socket. + As long as we have buffer try and map out frame data. + + :param str data_in: The data that is available to read + + """ + self._append_frame_buffer(data_in) + while self._frame_buffer: + consumed_count, frame_value = self._read_frame() + if not frame_value: + return + self._trim_frame_buffer(consumed_count) + self._process_frame(frame_value) + + def _on_terminate(self, reason_code, reason_text): + """Terminate the connection and notify registered ON_CONNECTION_ERROR + and/or ON_CONNECTION_CLOSED callbacks + + :param integer reason_code: either IETF RFC 821 reply code for + AMQP-level closures or a value from `InternalCloseReasons` for + internal causes, such as socket errors + :param str reason_text: human-readable text message describing the error + """ + LOGGER.info( + 'Disconnected from RabbitMQ at %s:%i (%s): %s', + self.params.host, self.params.port, reason_code, + reason_text) + + if not isinstance(reason_code, numbers.Integral): + raise TypeError('reason_code must be an integer, but got %r' + % (reason_code,)) + + # Stop the heartbeat checker if it exists + self._remove_heartbeat() + + # Remove connection management callbacks + # TODO This call was moved here verbatim from legacy code and the + # following doesn't seem to be right: `Connection.Open` here is + # unexpected, we don't appear to ever register it, and the broker + # shouldn't be sending `Connection.Open` to us, anyway. + self._remove_callbacks(0, [spec.Connection.Close, spec.Connection.Start, + spec.Connection.Open]) + + if self.params.blocked_connection_timeout is not None: + self._remove_callbacks(0, [spec.Connection.Blocked, + spec.Connection.Unblocked]) + + # Close the socket + self._adapter_disconnect() + + # Determine whether this was an error during connection setup + connection_error = None + + if self.connection_state == self.CONNECTION_PROTOCOL: + LOGGER.error('Incompatible Protocol Versions') + connection_error = exceptions.IncompatibleProtocolError( + reason_code, + reason_text) + elif self.connection_state == self.CONNECTION_START: + LOGGER.error('Connection closed while authenticating indicating a ' + 'probable authentication error') + connection_error = exceptions.ProbableAuthenticationError( + reason_code, + reason_text) + elif self.connection_state == self.CONNECTION_TUNE: + LOGGER.error('Connection closed while tuning the connection ' + 'indicating a probable permission error when ' + 'accessing a virtual host') + connection_error = exceptions.ProbableAccessDeniedError( + reason_code, + reason_text) + elif self.connection_state not in [self.CONNECTION_OPEN, + self.CONNECTION_CLOSED, + self.CONNECTION_CLOSING]: + LOGGER.warning('Unexpected connection state on disconnect: %i', + self.connection_state) + + # Transition to closed state + self._set_connection_state(self.CONNECTION_CLOSED) + + # Inform our channel proxies + for channel in dictkeys(self._channels): + if channel not in self._channels: + continue + # pylint: disable=W0212 + self._channels[channel]._on_close_meta(reason_code, reason_text) + + # Inform interested parties + if connection_error is not None: + LOGGER.error('Connection setup failed due to %r', connection_error) + self.callbacks.process(0, + self.ON_CONNECTION_ERROR, + self, self, + connection_error) + + self.callbacks.process(0, self.ON_CONNECTION_CLOSED, self, self, + reason_code, reason_text) + + # Reset connection properties + self._init_connection_state() + + def _process_callbacks(self, frame_value): + """Process the callbacks for the frame if the frame is a method frame + and if it has any callbacks pending. + + :param pika.frame.Method frame_value: The frame to process + :rtype: bool + + """ + if (self._is_method_frame(frame_value) and + self._has_pending_callbacks(frame_value)): + self.callbacks.process(frame_value.channel_number, # Prefix + frame_value.method, # Key + self, # Caller + frame_value) # Args + return True + return False + + def _process_frame(self, frame_value): + """Process an inbound frame from the socket. + + :param frame_value: The frame to process + :type frame_value: pika.frame.Frame | pika.frame.Method + + """ + # Will receive a frame type of -1 if protocol version mismatch + if frame_value.frame_type < 0: + return + + # Keep track of how many frames have been read + self.frames_received += 1 + + # Process any callbacks, if True, exit method + if self._process_callbacks(frame_value): + return + + # If a heartbeat is received, update the checker + if isinstance(frame_value, frame.Heartbeat): + if self.heartbeat: + self.heartbeat.received() + else: + LOGGER.warning('Received heartbeat frame without a heartbeat ' + 'checker') + + # If the frame has a channel number beyond the base channel, deliver it + elif frame_value.channel_number > 0: + self._deliver_frame_to_channel(frame_value) + + def _read_frame(self): + """Try and read from the frame buffer and decode a frame. + + :rtype tuple: (int, pika.frame.Frame) + + """ + return frame.decode_frame(self._frame_buffer) + + def _remove_callback(self, channel_number, method_class): + """Remove the specified method_frame callback if it is set for the + specified channel number. + + :param int channel_number: The channel number to remove the callback on + :param pika.amqp_object.Method method_class: The method class for the + callback + + """ + self.callbacks.remove(str(channel_number), method_class) + + def _remove_callbacks(self, channel_number, method_classes): + """Remove the callbacks for the specified channel number and list of + method frames. + + :param int channel_number: The channel number to remove the callback on + :param sequence method_classes: The method classes (derived from + `pika.amqp_object.Method`) for the callbacks + + """ + for method_frame in method_classes: + self._remove_callback(channel_number, method_frame) + + def _rpc(self, channel_number, method, + callback_method=None, + acceptable_replies=None): + """Make an RPC call for the given callback, channel number and method. + acceptable_replies lists out what responses we'll process from the + server with the specified callback. + + :param int channel_number: The channel number for the RPC call + :param pika.amqp_object.Method method: The method frame to call + :param method callback_method: The callback for the RPC response + :param list acceptable_replies: The replies this RPC call expects + + """ + # Validate that acceptable_replies is a list or None + if acceptable_replies and not isinstance(acceptable_replies, list): + raise TypeError('acceptable_replies should be list or None') + + # Validate the callback is callable + if callback_method: + if not utils.is_callable(callback_method): + raise TypeError('callback should be None, function or method.') + + for reply in acceptable_replies: + self.callbacks.add(channel_number, reply, callback_method) + + # Send the rpc call to RabbitMQ + self._send_method(channel_number, method) + + def _send_connection_close(self, reply_code, reply_text): + """Send a Connection.Close method frame. + + :param int reply_code: The reason for the close + :param str reply_text: The text reason for the close + + """ + self._rpc(0, spec.Connection.Close(reply_code, reply_text, 0, 0), + self._on_connection_close_ok, [spec.Connection.CloseOk]) + + def _send_connection_open(self): + """Send a Connection.Open frame""" + self._rpc(0, spec.Connection.Open(self.params.virtual_host, + insist=True), + self._on_connection_open, [spec.Connection.OpenOk]) + + def _send_connection_start_ok(self, authentication_type, response): + """Send a Connection.StartOk frame + + :param str authentication_type: The auth type value + :param str response: The encoded value to send + + """ + self._send_method(0, + spec.Connection.StartOk(self._client_properties, + authentication_type, response, + self.params.locale)) + + def _send_connection_tune_ok(self): + """Send a Connection.TuneOk frame""" + self._send_method(0, spec.Connection.TuneOk(self.params.channel_max, + self.params.frame_max, + self.params.heartbeat)) + + def _send_frame(self, frame_value): + """This appends the fully generated frame to send to the broker to the + output buffer which will be then sent via the connection adapter. + + :param frame_value: The frame to write + :type frame_value: pika.frame.Frame|pika.frame.ProtocolHeader + :raises: exceptions.ConnectionClosed + + """ + if self.is_closed: + LOGGER.error('Attempted to send frame when closed') + raise exceptions.ConnectionClosed + + marshaled_frame = frame_value.marshal() + self.bytes_sent += len(marshaled_frame) + self.frames_sent += 1 + self.outbound_buffer.append(marshaled_frame) + self._flush_outbound() + if self.params.backpressure_detection: + self._detect_backpressure() + + def _send_method(self, channel_number, method, content=None): + """Constructs a RPC method frame and then sends it to the broker. + + :param int channel_number: The channel number for the frame + :param pika.amqp_object.Method method: The method to send + :param tuple content: If set, is a content frame, is tuple of + properties and body. + + """ + if content: + self._send_message(channel_number, method, content) + else: + self._send_frame(frame.Method(channel_number, method)) + + def _send_message(self, channel_number, method_frame, content): + """Publish a message. + + :param int channel_number: The channel number for the frame + :param pika.object.Method method_frame: The method frame to send + :param tuple content: A content frame, which is tuple of properties and + body. + + """ + length = len(content[1]) + self._send_frame(frame.Method(channel_number, method_frame)) + self._send_frame(frame.Header(channel_number, length, content[0])) + + if content[1]: + chunks = int(math.ceil(float(length) / self._body_max_length)) + for chunk in xrange(0, chunks): + s = chunk * self._body_max_length + e = s + self._body_max_length + if e > length: + e = length + self._send_frame(frame.Body(channel_number, content[1][s:e])) + + def _set_connection_state(self, connection_state): + """Set the connection state. + + :param int connection_state: The connection state to set + + """ + self.connection_state = connection_state + + def _set_server_information(self, method_frame): + """Set the server properties and capabilities + + :param spec.connection.Start method_frame: The Connection.Start frame + + """ + self.server_properties = method_frame.method.server_properties + self.server_capabilities = self.server_properties.get('capabilities', + dict()) + if hasattr(self.server_properties, 'capabilities'): + del self.server_properties['capabilities'] + + def _trim_frame_buffer(self, byte_count): + """Trim the leading N bytes off the frame buffer and increment the + counter that keeps track of how many bytes have been read/used from the + socket. + + :param int byte_count: The number of bytes consumed + + """ + self._frame_buffer = self._frame_buffer[byte_count:] + self.bytes_received += byte_count diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/credentials.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/credentials.py new file mode 100644 index 000000000..09e0cae46 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/credentials.py @@ -0,0 +1,120 @@ +"""The credentials classes are used to encapsulate all authentication +information for the :class:`~pika.connection.ConnectionParameters` class. + +The :class:`~pika.credentials.PlainCredentials` class returns the properly +formatted username and password to the :class:`~pika.connection.Connection`. + +To authenticate with Pika, create a :class:`~pika.credentials.PlainCredentials` +object passing in the username and password and pass it as the credentials +argument value to the :class:`~pika.connection.ConnectionParameters` object. + +If you are using :class:`~pika.connection.URLParameters` you do not need a +credentials object, one will automatically be created for you. + +If you are looking to implement SSL certificate style authentication, you would +extend the :class:`~pika.credentials.ExternalCredentials` class implementing +the required behavior. + +""" +from .compat import as_bytes +import logging + +LOGGER = logging.getLogger(__name__) + + +class PlainCredentials(object): + """A credentials object for the default authentication methodology with + RabbitMQ. + + If you do not pass in credentials to the ConnectionParameters object, it + will create credentials for 'guest' with the password of 'guest'. + + If you pass True to erase_on_connect the credentials will not be stored + in memory after the Connection attempt has been made. + + :param str username: The username to authenticate with + :param str password: The password to authenticate with + :param bool erase_on_connect: erase credentials on connect. + + """ + TYPE = 'PLAIN' + + def __init__(self, username, password, erase_on_connect=False): + """Create a new instance of PlainCredentials + + :param str username: The username to authenticate with + :param str password: The password to authenticate with + :param bool erase_on_connect: erase credentials on connect. + + """ + self.username = username + self.password = password + self.erase_on_connect = erase_on_connect + + def __eq__(self, other): + return (isinstance(other, PlainCredentials) and + other.username == self.username and + other.password == self.password and + other.erase_on_connect == self.erase_on_connect) + + def __ne__(self, other): + return not self == other + + def response_for(self, start): + """Validate that this type of authentication is supported + + :param spec.Connection.Start start: Connection.Start method + :rtype: tuple(str|None, str|None) + + """ + if as_bytes(PlainCredentials.TYPE) not in\ + as_bytes(start.mechanisms).split(): + return None, None + return (PlainCredentials.TYPE, + b'\0' + as_bytes(self.username) + + b'\0' + as_bytes(self.password)) + + def erase_credentials(self): + """Called by Connection when it no longer needs the credentials""" + if self.erase_on_connect: + LOGGER.info("Erasing stored credential values") + self.username = None + self.password = None + + +class ExternalCredentials(object): + """The ExternalCredentials class allows the connection to use EXTERNAL + authentication, generally with a client SSL certificate. + + """ + TYPE = 'EXTERNAL' + + def __init__(self): + """Create a new instance of ExternalCredentials""" + self.erase_on_connect = False + + def __eq__(self, other): + return (isinstance(other, ExternalCredentials) and + other.erase_on_connect == self.erase_on_connect) + + def __ne__(self, other): + return not self == other + + def response_for(self, start): + """Validate that this type of authentication is supported + + :param spec.Connection.Start start: Connection.Start method + :rtype: tuple(str or None, str or None) + + """ + if as_bytes(ExternalCredentials.TYPE) not in\ + as_bytes(start.mechanisms).split(): + return None, None + return ExternalCredentials.TYPE, b'' + + def erase_credentials(self): + """Called by Connection when it no longer needs the credentials""" + LOGGER.debug('Not supported by this Credentials type') + +# Append custom credential types to this list for validation support +VALID_TYPES = [PlainCredentials, ExternalCredentials] diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/data.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/data.py new file mode 100644 index 000000000..b5bc5d92d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/data.py @@ -0,0 +1,322 @@ +"""AMQP Table Encoding/Decoding""" +import struct +import decimal +import calendar +import warnings + +from datetime import datetime + +from pika import exceptions +from pika.compat import PY2, PY3 +from pika.compat import unicode_type, long, as_bytes + + +def encode_short_string(pieces, value): + """Encode a string value as short string and append it to pieces list + returning the size of the encoded value. + + :param list pieces: Already encoded values + :param value: String value to encode + :type value: str or unicode + :rtype: int + + """ + encoded_value = as_bytes(value) + length = len(encoded_value) + + # 4.2.5.3 + # Short strings, stored as an 8-bit unsigned integer length followed by zero + # or more octets of data. Short strings can carry up to 255 octets of UTF-8 + # data, but may not contain binary zero octets. + # ... + # 4.2.5.5 + # The server SHOULD validate field names and upon receiving an invalid field + # name, it SHOULD signal a connection exception with reply code 503 (syntax + # error). + # -> validate length (avoid truncated utf-8 / corrupted data), but skip null + # byte check. + if length > 255: + raise exceptions.ShortStringTooLong(encoded_value) + + pieces.append(struct.pack('B', length)) + pieces.append(encoded_value) + return 1 + length + + +if PY2: + def decode_short_string(encoded, offset): + """Decode a short string value from ``encoded`` data at ``offset``. + """ + length = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + # Purely for compatibility with original python2 code. No idea what + # and why this does. + value = encoded[offset:offset + length] + try: + value = bytes(value) + except UnicodeEncodeError: + pass + offset += length + return value, offset + +else: + def decode_short_string(encoded, offset): + """Decode a short string value from ``encoded`` data at ``offset``. + """ + length = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + value = encoded[offset:offset + length] + try: + value = value.decode('utf8') + except UnicodeDecodeError: + pass + offset += length + return value, offset + + +def encode_table(pieces, table): + """Encode a dict as an AMQP table appending the encded table to the + pieces list passed in. + + :param list pieces: Already encoded frame pieces + :param dict table: The dict to encode + :rtype: int + + """ + table = table or {} + length_index = len(pieces) + pieces.append(None) # placeholder + tablesize = 0 + for (key, value) in table.items(): + tablesize += encode_short_string(pieces, key) + tablesize += encode_value(pieces, value) + + pieces[length_index] = struct.pack('>I', tablesize) + return tablesize + 4 + + +def encode_value(pieces, value): + """Encode the value passed in and append it to the pieces list returning + the the size of the encoded value. + + :param list pieces: Already encoded values + :param any value: The value to encode + :rtype: int + + """ + + if PY2: + if isinstance(value, basestring): + if isinstance(value, unicode_type): + value = value.encode('utf-8') + pieces.append(struct.pack('>cI', b'S', len(value))) + pieces.append(value) + return 5 + len(value) + else: + # support only str on Python 3 + if isinstance(value, str): + value = value.encode('utf-8') + pieces.append(struct.pack('>cI', b'S', len(value))) + pieces.append(value) + return 5 + len(value) + + if isinstance(value, bytes): + pieces.append(struct.pack('>cI', b'x', len(value))) + pieces.append(value) + return 5 + len(value) + + if isinstance(value, bool): + pieces.append(struct.pack('>cB', b't', int(value))) + return 2 + if isinstance(value, long): + pieces.append(struct.pack('>cq', b'l', value)) + return 9 + elif isinstance(value, int): + with warnings.catch_warnings(): + warnings.filterwarnings('error') + try: + p = struct.pack('>ci', b'I', value) + pieces.append(p) + return 5 + except (struct.error, DeprecationWarning): + p = struct.pack('>cq', b'l', long(value)) + pieces.append(p) + return 9 + elif isinstance(value, decimal.Decimal): + value = value.normalize() + if value.as_tuple().exponent < 0: + decimals = -value.as_tuple().exponent + raw = int(value * (decimal.Decimal(10) ** decimals)) + pieces.append(struct.pack('>cBi', b'D', decimals, raw)) + else: + # per spec, the "decimals" octet is unsigned (!) + pieces.append(struct.pack('>cBi', b'D', 0, int(value))) + return 6 + elif isinstance(value, datetime): + pieces.append(struct.pack('>cQ', b'T', + calendar.timegm(value.utctimetuple()))) + return 9 + elif isinstance(value, dict): + pieces.append(struct.pack('>c', b'F')) + return 1 + encode_table(pieces, value) + elif isinstance(value, list): + p = [] + for v in value: + encode_value(p, v) + piece = b''.join(p) + pieces.append(struct.pack('>cI', b'A', len(piece))) + pieces.append(piece) + return 5 + len(piece) + elif value is None: + pieces.append(struct.pack('>c', b'V')) + return 1 + else: + raise exceptions.UnsupportedAMQPFieldException(pieces, value) + + +def decode_table(encoded, offset): + """Decode the AMQP table passed in from the encoded value returning the + decoded result and the number of bytes read plus the offset. + + :param str encoded: The binary encoded data to decode + :param int offset: The starting byte offset + :rtype: tuple + + """ + result = {} + tablesize = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + limit = offset + tablesize + while offset < limit: + key, offset = decode_short_string(encoded, offset) + value, offset = decode_value(encoded, offset) + result[key] = value + return result, offset + + +def decode_value(encoded, offset): + """Decode the value passed in returning the decoded value and the number + of bytes read in addition to the starting offset. + + :param str encoded: The binary encoded data to decode + :param int offset: The starting byte offset + :rtype: tuple + :raises: pika.exceptions.InvalidFieldTypeException + + """ + # slice to get bytes in Python 3 and str in Python 2 + kind = encoded[offset:offset + 1] + offset += 1 + + # Bool + if kind == b't': + value = struct.unpack_from('>B', encoded, offset)[0] + value = bool(value) + offset += 1 + + # Short-Short Int + elif kind == b'b': + value = struct.unpack_from('>B', encoded, offset)[0] + offset += 1 + + # Short-Short Unsigned Int + elif kind == b'B': + value = struct.unpack_from('>b', encoded, offset)[0] + offset += 1 + + # Short Int + elif kind == b'U': + value = struct.unpack_from('>h', encoded, offset)[0] + offset += 2 + + # Short Unsigned Int + elif kind == b'u': + value = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + + # Long Int + elif kind == b'I': + value = struct.unpack_from('>i', encoded, offset)[0] + offset += 4 + + # Long Unsigned Int + elif kind == b'i': + value = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + + # Long-Long Int + elif kind == b'L': + value = long(struct.unpack_from('>q', encoded, offset)[0]) + offset += 8 + + # Long-Long Unsigned Int + elif kind == b'l': + value = long(struct.unpack_from('>Q', encoded, offset)[0]) + offset += 8 + + # Float + elif kind == b'f': + value = long(struct.unpack_from('>f', encoded, offset)[0]) + offset += 4 + + # Double + elif kind == b'd': + value = long(struct.unpack_from('>d', encoded, offset)[0]) + offset += 8 + + # Decimal + elif kind == b'D': + decimals = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + raw = struct.unpack_from('>i', encoded, offset)[0] + offset += 4 + value = decimal.Decimal(raw) * (decimal.Decimal(10) ** -decimals) + + # Short String + elif kind == b's': + value, offset = decode_short_string(encoded, offset) + + # Long String + elif kind == b'S': + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + value = encoded[offset:offset + length] + try: + value = value.decode('utf8') + except UnicodeDecodeError: + pass + offset += length + + elif kind == b'x': + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + value = encoded[offset:offset + length] + offset += length + + # Field Array + elif kind == b'A': + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + offset_end = offset + length + value = [] + while offset < offset_end: + v, offset = decode_value(encoded, offset) + value.append(v) + + # Timestamp + elif kind == b'T': + value = datetime.utcfromtimestamp(struct.unpack_from('>Q', encoded, + offset)[0]) + offset += 8 + + # Field Table + elif kind == b'F': + (value, offset) = decode_table(encoded, offset) + + # Null / Void + elif kind == b'V': + value = None + else: + raise exceptions.InvalidFieldTypeException(kind) + + return value, offset diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/exceptions.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/exceptions.py new file mode 100644 index 000000000..7daef48a4 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/exceptions.py @@ -0,0 +1,257 @@ +"""Pika specific exceptions""" + + +class AMQPError(Exception): + + def __repr__(self): + return 'An unspecified AMQP error has occurred' + + +class AMQPConnectionError(AMQPError): + + def __repr__(self): + if len(self.args) == 1: + if self.args[0] == 1: + return ('No connection could be opened after 1 ' + 'connection attempt') + elif isinstance(self.args[0], int): + return ('No connection could be opened after %s ' + 'connection attempts' % self.args[0]) + else: + return 'No connection could be opened: %s' % self.args[0] + elif len(self.args) == 2: + return '%s: %s' % (self.args[0], self.args[1]) + + +class IncompatibleProtocolError(AMQPConnectionError): + + def __repr__(self): + return ('The protocol returned by the server is not supported: %s' % + (self.args,)) + + +class AuthenticationError(AMQPConnectionError): + + def __repr__(self): + return ('Server and client could not negotiate use of the %s ' + 'authentication mechanism' % self.args[0]) + + +class ProbableAuthenticationError(AMQPConnectionError): + + def __repr__(self): + return ('Client was disconnected at a connection stage indicating a ' + 'probable authentication error: %s' % (self.args,)) + + +class ProbableAccessDeniedError(AMQPConnectionError): + + def __repr__(self): + return ('Client was disconnected at a connection stage indicating a ' + 'probable denial of access to the specified virtual host: %s' % + (self.args,)) + + +class NoFreeChannels(AMQPConnectionError): + + def __repr__(self): + return 'The connection has run out of free channels' + + +class ConnectionClosed(AMQPConnectionError): + + def __repr__(self): + if len(self.args) == 2: + return 'The AMQP connection was closed (%s) %s' % (self.args[0], + self.args[1]) + else: + return 'The AMQP connection was closed: %s' % (self.args,) + + +class AMQPChannelError(AMQPError): + + def __repr__(self): + return 'An unspecified AMQP channel error has occurred' + + +class ChannelClosed(AMQPChannelError): + + def __repr__(self): + if len(self.args) == 2: + return 'The channel was closed (%s) %s' % (self.args[0], + self.args[1]) + else: + return 'The channel was closed: %s' % (self.args,) + + +class ChannelAlreadyClosing(AMQPChannelError): + """Raised when `Channel.close` is called while channel is already closing""" + pass + + +class DuplicateConsumerTag(AMQPChannelError): + + def __repr__(self): + return ('The consumer tag specified already exists for this ' + 'channel: %s' % self.args[0]) + + +class ConsumerCancelled(AMQPChannelError): + + def __repr__(self): + return 'Server cancelled consumer' + + +class UnroutableError(AMQPChannelError): + """Exception containing one or more unroutable messages returned by broker + via Basic.Return. + + Used by BlockingChannel. + + In publisher-acknowledgements mode, this is raised upon receipt of Basic.Ack + from broker; in the event of Basic.Nack from broker, `NackError` is raised + instead + """ + + def __init__(self, messages): + """ + :param messages: sequence of returned unroutable messages + :type messages: sequence of `blocking_connection.ReturnedMessage` + objects + """ + super(UnroutableError, self).__init__( + "%s unroutable message(s) returned" % (len(messages))) + + self.messages = messages + + def __repr__(self): + return '%s: %i unroutable messages returned by broker' % ( + self.__class__.__name__, len(self.messages)) + + +class NackError(AMQPChannelError): + """This exception is raised when a message published in + publisher-acknowledgements mode is Nack'ed by the broker. + + Used by BlockingChannel. + """ + + def __init__(self, messages): + """ + :param messages: sequence of returned unroutable messages + :type messages: sequence of `blocking_connection.ReturnedMessage` + objects + """ + super(NackError, self).__init__( + "%s message(s) NACKed" % (len(messages))) + + self.messages = messages + + def __repr__(self): + return '%s: %i unroutable messages returned by broker' % ( + self.__class__.__name__, len(self.messages)) + + +class InvalidChannelNumber(AMQPError): + + def __repr__(self): + return 'An invalid channel number has been specified: %s' % self.args[0] + + +class ProtocolSyntaxError(AMQPError): + + def __repr__(self): + return 'An unspecified protocol syntax error occurred' + + +class UnexpectedFrameError(ProtocolSyntaxError): + + def __repr__(self): + return 'Received a frame out of sequence: %r' % self.args[0] + + +class ProtocolVersionMismatch(ProtocolSyntaxError): + + def __repr__(self): + return 'Protocol versions did not match: %r vs %r' % (self.args[0], + self.args[1]) + + +class BodyTooLongError(ProtocolSyntaxError): + + def __repr__(self): + return ('Received too many bytes for a message delivery: ' + 'Received %i, expected %i' % (self.args[0], self.args[1])) + + +class InvalidFrameError(ProtocolSyntaxError): + + def __repr__(self): + return 'Invalid frame received: %r' % self.args[0] + + +class InvalidFieldTypeException(ProtocolSyntaxError): + + def __repr__(self): + return 'Unsupported field kind %s' % self.args[0] + + +class UnsupportedAMQPFieldException(ProtocolSyntaxError): + + def __repr__(self): + return 'Unsupported field kind %s' % type(self.args[1]) + + +class UnspportedAMQPFieldException(UnsupportedAMQPFieldException): + """Deprecated version of UnsupportedAMQPFieldException""" + + +class MethodNotImplemented(AMQPError): + pass + + +class ChannelError(Exception): + + def __repr__(self): + return 'An unspecified error occurred with the Channel' + + +class InvalidMinimumFrameSize(ProtocolSyntaxError): + """ DEPRECATED; pika.connection.Parameters.frame_max property setter now + raises the standard `ValueError` exception when the value is out of bounds. + """ + + def __repr__(self): + return 'AMQP Minimum Frame Size is 4096 Bytes' + + +class InvalidMaximumFrameSize(ProtocolSyntaxError): + """ DEPRECATED; pika.connection.Parameters.frame_max property setter now + raises the standard `ValueError` exception when the value is out of bounds. + """ + + def __repr__(self): + return 'AMQP Maximum Frame Size is 131072 Bytes' + + +class RecursionError(Exception): + """The requested operation would result in unsupported recursion or + reentrancy. + + Used by BlockingConnection/BlockingChannel + + """ + + +class ShortStringTooLong(AMQPError): + + def __repr__(self): + return ('AMQP Short String can contain up to 255 bytes: ' + '%.300s' % self.args[0]) + + +class DuplicateGetOkCallback(ChannelError): + + def __repr__(self): + return ('basic_get can only be called again after the callback for the' + 'previous basic_get is executed') diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/frame.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/frame.py new file mode 100644 index 000000000..9a07ec36a --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/frame.py @@ -0,0 +1,265 @@ +"""Frame objects that do the frame demarshaling and marshaling.""" +import logging +import struct + +from pika import amqp_object +from pika import exceptions +from pika import spec +from pika.compat import byte + + +LOGGER = logging.getLogger(__name__) + + +class Frame(amqp_object.AMQPObject): + """Base Frame object mapping. Defines a behavior for all child classes for + assignment of core attributes and implementation of the a core _marshal + method which child classes use to create the binary AMQP frame. + + """ + NAME = 'Frame' + + def __init__(self, frame_type, channel_number): + """Create a new instance of a frame + + :param int frame_type: The frame type + :param int channel_number: The channel number for the frame + + """ + self.frame_type = frame_type + self.channel_number = channel_number + + def _marshal(self, pieces): + """Create the full AMQP wire protocol frame data representation + + :rtype: bytes + + """ + payload = b''.join(pieces) + return struct.pack('>BHI', self.frame_type, self.channel_number, + len(payload)) + payload + byte(spec.FRAME_END) + + def marshal(self): + """To be ended by child classes + + :raises NotImplementedError + + """ + raise NotImplementedError + + +class Method(Frame): + """Base Method frame object mapping. AMQP method frames are mapped on top + of this class for creating or accessing their data and attributes. + + """ + NAME = 'METHOD' + + def __init__(self, channel_number, method): + """Create a new instance of a frame + + :param int channel_number: The frame type + :param pika.Spec.Class.Method method: The AMQP Class.Method + + """ + Frame.__init__(self, spec.FRAME_METHOD, channel_number) + self.method = method + + def marshal(self): + """Return the AMQP binary encoded value of the frame + + :rtype: str + + """ + pieces = self.method.encode() + pieces.insert(0, struct.pack('>I', self.method.INDEX)) + return self._marshal(pieces) + + +class Header(Frame): + """Header frame object mapping. AMQP content header frames are mapped + on top of this class for creating or accessing their data and attributes. + + """ + NAME = 'Header' + + def __init__(self, channel_number, body_size, props): + """Create a new instance of a AMQP ContentHeader object + + :param int channel_number: The channel number for the frame + :param int body_size: The number of bytes for the body + :param pika.spec.BasicProperties props: Basic.Properties object + + """ + Frame.__init__(self, spec.FRAME_HEADER, channel_number) + self.body_size = body_size + self.properties = props + + def marshal(self): + """Return the AMQP binary encoded value of the frame + + :rtype: str + + """ + pieces = self.properties.encode() + pieces.insert(0, struct.pack('>HxxQ', self.properties.INDEX, + self.body_size)) + return self._marshal(pieces) + + +class Body(Frame): + """Body frame object mapping class. AMQP content body frames are mapped on + to this base class for getting/setting of attributes/data. + + """ + NAME = 'Body' + + def __init__(self, channel_number, fragment): + """ + Parameters: + + - channel_number: int + - fragment: unicode or str + """ + Frame.__init__(self, spec.FRAME_BODY, channel_number) + self.fragment = fragment + + def marshal(self): + """Return the AMQP binary encoded value of the frame + + :rtype: str + + """ + return self._marshal([self.fragment]) + + +class Heartbeat(Frame): + """Heartbeat frame object mapping class. AMQP Heartbeat frames are mapped + on to this class for a common access structure to the attributes/data + values. + + """ + NAME = 'Heartbeat' + + def __init__(self): + """Create a new instance of the Heartbeat frame""" + Frame.__init__(self, spec.FRAME_HEARTBEAT, 0) + + def marshal(self): + """Return the AMQP binary encoded value of the frame + + :rtype: str + + """ + return self._marshal(list()) + + +class ProtocolHeader(amqp_object.AMQPObject): + """AMQP Protocol header frame class which provides a pythonic interface + for creating AMQP Protocol headers + + """ + NAME = 'ProtocolHeader' + + def __init__(self, major=None, minor=None, revision=None): + """Construct a Protocol Header frame object for the specified AMQP + version + + :param int major: Major version number + :param int minor: Minor version number + :param int revision: Revision + + """ + self.frame_type = -1 + self.major = major or spec.PROTOCOL_VERSION[0] + self.minor = minor or spec.PROTOCOL_VERSION[1] + self.revision = revision or spec.PROTOCOL_VERSION[2] + + def marshal(self): + """Return the full AMQP wire protocol frame data representation of the + ProtocolHeader frame + + :rtype: str + + """ + return b'AMQP' + struct.pack('BBBB', 0, self.major, self.minor, + self.revision) + + +def decode_frame(data_in): + """Receives raw socket data and attempts to turn it into a frame. + Returns bytes used to make the frame and the frame + + :param str data_in: The raw data stream + :rtype: tuple(bytes consumed, frame) + :raises: pika.exceptions.InvalidFrameError + + """ + # Look to see if it's a protocol header frame + try: + if data_in[0:4] == b'AMQP': + major, minor, revision = struct.unpack_from('BBB', data_in, 5) + return 8, ProtocolHeader(major, minor, revision) + except (IndexError, struct.error): + return 0, None + + # Get the Frame Type, Channel Number and Frame Size + try: + (frame_type, channel_number, + frame_size) = struct.unpack('>BHL', data_in[0:7]) + except struct.error: + return 0, None + + # Get the frame data + frame_end = spec.FRAME_HEADER_SIZE + frame_size + spec.FRAME_END_SIZE + + # We don't have all of the frame yet + if frame_end > len(data_in): + return 0, None + + # The Frame termination chr is wrong + if data_in[frame_end - 1:frame_end] != byte(spec.FRAME_END): + raise exceptions.InvalidFrameError("Invalid FRAME_END marker") + + # Get the raw frame data + frame_data = data_in[spec.FRAME_HEADER_SIZE:frame_end - 1] + + if frame_type == spec.FRAME_METHOD: + + # Get the Method ID from the frame data + method_id = struct.unpack_from('>I', frame_data)[0] + + # Get a Method object for this method_id + method = spec.methods[method_id]() + + # Decode the content + method.decode(frame_data, 4) + + # Return the amount of data consumed and the Method object + return frame_end, Method(channel_number, method) + + elif frame_type == spec.FRAME_HEADER: + + # Return the header class and body size + class_id, weight, body_size = struct.unpack_from('>HHQ', frame_data) + + # Get the Properties type + properties = spec.props[class_id]() + + # Decode the properties + out = properties.decode(frame_data[12:]) + + # Return a Header frame + return frame_end, Header(channel_number, body_size, properties) + + elif frame_type == spec.FRAME_BODY: + + # Return the amount of data consumed and the Body frame w/ data + return frame_end, Body(channel_number, frame_data) + + elif frame_type == spec.FRAME_HEARTBEAT: + + # Return the amount of data and a Heartbeat frame + return frame_end, Heartbeat() + + raise exceptions.InvalidFrameError("Unknown frame type: %i" % frame_type) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/heartbeat.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/heartbeat.py new file mode 100644 index 000000000..cb9a1db05 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/heartbeat.py @@ -0,0 +1,214 @@ +"""Handle AMQP Heartbeats""" +import logging + +from pika import frame + +LOGGER = logging.getLogger(__name__) + + +class HeartbeatChecker(object): + """Sends heartbeats to the broker. The provided timeout is used to + determine if the connection is stale - no received heartbeats or + other activity will close the connection. See the parameter list for more + details. + + """ + _CONNECTION_FORCED = 320 + _STALE_CONNECTION = "No activity or too many missed heartbeats in the last %i seconds" + + def __init__(self, connection, timeout): + """Create an object that will check for activity on the provided + connection as well as receive heartbeat frames from the broker. The + timeout parameter defines a window within which this activity must + happen. If not, the connection is considered dead and closed. + + The value passed for timeout is also used to calculate an interval + at which a heartbeat frame is sent to the broker. The interval is + equal to the timeout value divided by two. + + :param pika.connection.Connection: Connection object + :param int timeout: Connection idle timeout. If no activity occurs on the + connection nor heartbeat frames received during the + timeout window the connection will be closed. The + interval used to send heartbeats is calculated from + this value by dividing it by two. + + """ + if timeout < 1: + raise ValueError('timeout must >= 0, but got %r' % (timeout,)) + + self._connection = connection + + # Note: see the following documents: + # https://www.rabbitmq.com/heartbeats.html#heartbeats-timeout + # https://github.com/pika/pika/pull/1072 + # https://groups.google.com/d/topic/rabbitmq-users/Fmfeqe5ocTY/discussion + # There is a certain amount of confusion around how client developers + # interpret the spec. The spec talks about 2 missed heartbeats as a + # *timeout*, plus that any activity on the connection counts for a + # heartbeat. This is to avoid edge cases and not to depend on network + # latency. + self._timeout = timeout + + self._send_interval = float(timeout) / 2 + + # Note: Pika will calculate the heartbeat / connectivity check interval + # by adding 5 seconds to the negotiated timeout to leave a bit of room + # for broker heartbeats that may be right at the edge of the timeout + # window. This is different behavior from the RabbitMQ Java client and + # the spec that suggests a check interval equivalent to two times the + # heartbeat timeout value. But, one advantage of adding a small amount + # is that bad connections will be detected faster. + # https://github.com/pika/pika/pull/1072#issuecomment-397850795 + # https://github.com/rabbitmq/rabbitmq-java-client/blob/b55bd20a1a236fc2d1ea9369b579770fa0237615/src/main/java/com/rabbitmq/client/impl/AMQConnection.java#L773-L780 + # https://github.com/ruby-amqp/bunny/blob/3259f3af2e659a49c38c2470aa565c8fb825213c/lib/bunny/session.rb#L1187-L1192 + self._check_interval = timeout + 5 + + LOGGER.debug('timeout: %f send_interval: %f check_interval: %f', + self._timeout, + self._send_interval, + self._check_interval) + + # Initialize counters + self._bytes_received = 0 + self._bytes_sent = 0 + self._heartbeat_frames_received = 0 + self._heartbeat_frames_sent = 0 + self._idle_byte_intervals = 0 + + self._send_timer = None + self._check_timer = None + self._start_send_timer() + self._start_check_timer() + + @property + def bytes_received_on_connection(self): + """Return the number of bytes received by the connection bytes object. + + :rtype int + + """ + return self._connection.bytes_received + + @property + def connection_is_idle(self): + """Returns true if the byte count hasn't changed in enough intervals + to trip the max idle threshold. + + """ + return self._idle_byte_intervals > 0 + + def received(self): + """Called when a heartbeat is received""" + LOGGER.debug('Received heartbeat frame') + self._heartbeat_frames_received += 1 + + def _send_heartbeat(self): + """Invoked by a timer to send a heartbeat when we need to. + + """ + LOGGER.debug('Sending heartbeat frame') + self._send_heartbeat_frame() + self._start_send_timer() + + def _check_heartbeat(self): + """Invoked by a timer to check for broker heartbeats. Checks to see + if we've missed any heartbeats and disconnect our connection if it's + been idle too long. + + """ + if self._has_received_data: + self._idle_byte_intervals = 0 + else: + # Connection has not received any data, increment the counter + self._idle_byte_intervals += 1 + + LOGGER.debug('Received %i heartbeat frames, sent %i, ' + 'idle intervals %i', + self._heartbeat_frames_received, + self._heartbeat_frames_sent, + self._idle_byte_intervals) + + if self.connection_is_idle: + self._close_connection() + return + + self._start_check_timer() + + def stop(self): + """Stop the heartbeat checker""" + if self._send_timer: + LOGGER.debug('Removing timer for next heartbeat send interval') + self._connection.remove_timeout(self._send_timer) # pylint: disable=W0212 + self._send_timer = None + if self._check_timer: + LOGGER.debug('Removing timer for next heartbeat check interval') + self._connection.remove_timeout(self._check_timer) # pylint: disable=W0212 + self._check_timer = None + + def _close_connection(self): + """Close the connection with the AMQP Connection-Forced value.""" + LOGGER.info('Connection is idle, %i stale byte intervals', + self._idle_byte_intervals) + text = HeartbeatChecker._STALE_CONNECTION % self._timeout + + # NOTE: this won't achieve the perceived effect of sending + # Connection.Close to broker, because the frame will only get buffered + # in memory before the next statement terminates the connection. + self._connection.close(HeartbeatChecker._CONNECTION_FORCED, text) + + self._connection._on_terminate(HeartbeatChecker._CONNECTION_FORCED, # pylint: disable=W0212 + text) + + @property + def _has_received_data(self): + """Returns True if the connection has received data. + + :rtype: bool + + """ + return self._bytes_received != self.bytes_received_on_connection + + @staticmethod + def _new_heartbeat_frame(): + """Return a new heartbeat frame. + + :rtype pika.frame.Heartbeat + + """ + return frame.Heartbeat() + + def _send_heartbeat_frame(self): + """Send a heartbeat frame on the connection. + + """ + LOGGER.debug('Sending heartbeat frame') + self._connection._send_frame( # pylint: disable=W0212 + self._new_heartbeat_frame()) + self._heartbeat_frames_sent += 1 + + def _start_send_timer(self): + """Start a new heartbeat send timer.""" + self._send_timer = self._connection.add_timeout( # pylint: disable=W0212 + self._send_interval, + self._send_heartbeat) + + def _start_check_timer(self): + """Start a new heartbeat check timer.""" + # Note: update counters now to get current values + # at the start of the timeout window. Values will be + # checked against the connection's byte count at the + # end of the window + self._update_counters() + + self._check_timer = self._connection.add_timeout( # pylint: disable=W0212 + self._check_interval, + self._check_heartbeat) + + def _update_counters(self): + """Update the internal counters for bytes sent and received and the + number of frames received + + """ + self._bytes_sent = self._connection.bytes_sent + self._bytes_received = self._connection.bytes_received diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/spec.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/spec.py new file mode 100644 index 000000000..f1fe225dc --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/spec.py @@ -0,0 +1,2319 @@ +""" +AMQP Specification +================== +This module implements the constants and classes that comprise AMQP protocol +level constructs. It should rarely be directly referenced outside of Pika's +own internal use. + +.. note:: Auto-generated code by codegen.py, do not edit directly. Pull +requests to this file without accompanying ``utils/codegen.py`` changes will be +rejected. + +""" + +import struct +from pika import amqp_object +from pika import data +from pika.compat import str_or_bytes, unicode_type + +# Python 3 support for str object +str = bytes + +PROTOCOL_VERSION = (0, 9, 1) +PORT = 5672 + +ACCESS_REFUSED = 403 +CHANNEL_ERROR = 504 +COMMAND_INVALID = 503 +CONNECTION_FORCED = 320 +CONTENT_TOO_LARGE = 311 +FRAME_BODY = 3 +FRAME_END = 206 +FRAME_END_SIZE = 1 +FRAME_ERROR = 501 +FRAME_HEADER = 2 +FRAME_HEADER_SIZE = 7 +FRAME_HEARTBEAT = 8 +FRAME_MAX_SIZE = 131072 +FRAME_METHOD = 1 +FRAME_MIN_SIZE = 4096 +INTERNAL_ERROR = 541 +INVALID_PATH = 402 +NOT_ALLOWED = 530 +NOT_FOUND = 404 +NOT_IMPLEMENTED = 540 +NO_CONSUMERS = 313 +NO_ROUTE = 312 +PERSISTENT_DELIVERY_MODE = 2 +PRECONDITION_FAILED = 406 +REPLY_SUCCESS = 200 +RESOURCE_ERROR = 506 +RESOURCE_LOCKED = 405 +SYNTAX_ERROR = 502 +TRANSIENT_DELIVERY_MODE = 1 +UNEXPECTED_FRAME = 505 + + +class Connection(amqp_object.Class): + + INDEX = 0x000A # 10 + NAME = 'Connection' + + class Start(amqp_object.Method): + + INDEX = 0x000A000A # 10, 10; 655370 + NAME = 'Connection.Start' + + def __init__(self, version_major=0, version_minor=9, server_properties=None, mechanisms='PLAIN', locales='en_US'): + self.version_major = version_major + self.version_minor = version_minor + self.server_properties = server_properties + self.mechanisms = mechanisms + self.locales = locales + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.version_major = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.version_minor = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + (self.server_properties, offset) = data.decode_table(encoded, offset) + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.mechanisms = encoded[offset:offset + length] + try: + self.mechanisms = str(self.mechanisms) + except UnicodeEncodeError: + pass + offset += length + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.locales = encoded[offset:offset + length] + try: + self.locales = str(self.locales) + except UnicodeEncodeError: + pass + offset += length + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('B', self.version_major)) + pieces.append(struct.pack('B', self.version_minor)) + data.encode_table(pieces, self.server_properties) + assert isinstance(self.mechanisms, str_or_bytes),\ + 'A non-string value was supplied for self.mechanisms' + value = self.mechanisms.encode('utf-8') if isinstance(self.mechanisms, unicode_type) else self.mechanisms + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + assert isinstance(self.locales, str_or_bytes),\ + 'A non-string value was supplied for self.locales' + value = self.locales.encode('utf-8') if isinstance(self.locales, unicode_type) else self.locales + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + return pieces + + class StartOk(amqp_object.Method): + + INDEX = 0x000A000B # 10, 11; 655371 + NAME = 'Connection.StartOk' + + def __init__(self, client_properties=None, mechanism='PLAIN', response=None, locale='en_US'): + self.client_properties = client_properties + self.mechanism = mechanism + self.response = response + self.locale = locale + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + (self.client_properties, offset) = data.decode_table(encoded, offset) + self.mechanism, offset = data.decode_short_string(encoded, offset) + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.response = encoded[offset:offset + length] + try: + self.response = str(self.response) + except UnicodeEncodeError: + pass + offset += length + self.locale, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + data.encode_table(pieces, self.client_properties) + assert isinstance(self.mechanism, str_or_bytes),\ + 'A non-string value was supplied for self.mechanism' + data.encode_short_string(pieces, self.mechanism) + assert isinstance(self.response, str_or_bytes),\ + 'A non-string value was supplied for self.response' + value = self.response.encode('utf-8') if isinstance(self.response, unicode_type) else self.response + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + assert isinstance(self.locale, str_or_bytes),\ + 'A non-string value was supplied for self.locale' + data.encode_short_string(pieces, self.locale) + return pieces + + class Secure(amqp_object.Method): + + INDEX = 0x000A0014 # 10, 20; 655380 + NAME = 'Connection.Secure' + + def __init__(self, challenge=None): + self.challenge = challenge + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.challenge = encoded[offset:offset + length] + try: + self.challenge = str(self.challenge) + except UnicodeEncodeError: + pass + offset += length + return self + + def encode(self): + pieces = list() + assert isinstance(self.challenge, str_or_bytes),\ + 'A non-string value was supplied for self.challenge' + value = self.challenge.encode('utf-8') if isinstance(self.challenge, unicode_type) else self.challenge + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + return pieces + + class SecureOk(amqp_object.Method): + + INDEX = 0x000A0015 # 10, 21; 655381 + NAME = 'Connection.SecureOk' + + def __init__(self, response=None): + self.response = response + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.response = encoded[offset:offset + length] + try: + self.response = str(self.response) + except UnicodeEncodeError: + pass + offset += length + return self + + def encode(self): + pieces = list() + assert isinstance(self.response, str_or_bytes),\ + 'A non-string value was supplied for self.response' + value = self.response.encode('utf-8') if isinstance(self.response, unicode_type) else self.response + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + return pieces + + class Tune(amqp_object.Method): + + INDEX = 0x000A001E # 10, 30; 655390 + NAME = 'Connection.Tune' + + def __init__(self, channel_max=0, frame_max=0, heartbeat=0): + self.channel_max = channel_max + self.frame_max = frame_max + self.heartbeat = heartbeat + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.channel_max = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.frame_max = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.heartbeat = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.channel_max)) + pieces.append(struct.pack('>I', self.frame_max)) + pieces.append(struct.pack('>H', self.heartbeat)) + return pieces + + class TuneOk(amqp_object.Method): + + INDEX = 0x000A001F # 10, 31; 655391 + NAME = 'Connection.TuneOk' + + def __init__(self, channel_max=0, frame_max=0, heartbeat=0): + self.channel_max = channel_max + self.frame_max = frame_max + self.heartbeat = heartbeat + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.channel_max = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.frame_max = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.heartbeat = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.channel_max)) + pieces.append(struct.pack('>I', self.frame_max)) + pieces.append(struct.pack('>H', self.heartbeat)) + return pieces + + class Open(amqp_object.Method): + + INDEX = 0x000A0028 # 10, 40; 655400 + NAME = 'Connection.Open' + + def __init__(self, virtual_host='/', capabilities='', insist=False): + self.virtual_host = virtual_host + self.capabilities = capabilities + self.insist = insist + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.virtual_host, offset = data.decode_short_string(encoded, offset) + self.capabilities, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.insist = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + assert isinstance(self.virtual_host, str_or_bytes),\ + 'A non-string value was supplied for self.virtual_host' + data.encode_short_string(pieces, self.virtual_host) + assert isinstance(self.capabilities, str_or_bytes),\ + 'A non-string value was supplied for self.capabilities' + data.encode_short_string(pieces, self.capabilities) + bit_buffer = 0 + if self.insist: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class OpenOk(amqp_object.Method): + + INDEX = 0x000A0029 # 10, 41; 655401 + NAME = 'Connection.OpenOk' + + def __init__(self, known_hosts=''): + self.known_hosts = known_hosts + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.known_hosts, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.known_hosts, str_or_bytes),\ + 'A non-string value was supplied for self.known_hosts' + data.encode_short_string(pieces, self.known_hosts) + return pieces + + class Close(amqp_object.Method): + + INDEX = 0x000A0032 # 10, 50; 655410 + NAME = 'Connection.Close' + + def __init__(self, reply_code=None, reply_text='', class_id=None, method_id=None): + self.reply_code = reply_code + self.reply_text = reply_text + self.class_id = class_id + self.method_id = method_id + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.reply_code = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.reply_text, offset = data.decode_short_string(encoded, offset) + self.class_id = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.method_id = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.reply_code)) + assert isinstance(self.reply_text, str_or_bytes),\ + 'A non-string value was supplied for self.reply_text' + data.encode_short_string(pieces, self.reply_text) + pieces.append(struct.pack('>H', self.class_id)) + pieces.append(struct.pack('>H', self.method_id)) + return pieces + + class CloseOk(amqp_object.Method): + + INDEX = 0x000A0033 # 10, 51; 655411 + NAME = 'Connection.CloseOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Blocked(amqp_object.Method): + + INDEX = 0x000A003C # 10, 60; 655420 + NAME = 'Connection.Blocked' + + def __init__(self, reason=''): + self.reason = reason + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.reason, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.reason, str_or_bytes),\ + 'A non-string value was supplied for self.reason' + data.encode_short_string(pieces, self.reason) + return pieces + + class Unblocked(amqp_object.Method): + + INDEX = 0x000A003D # 10, 61; 655421 + NAME = 'Connection.Unblocked' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class Channel(amqp_object.Class): + + INDEX = 0x0014 # 20 + NAME = 'Channel' + + class Open(amqp_object.Method): + + INDEX = 0x0014000A # 20, 10; 1310730 + NAME = 'Channel.Open' + + def __init__(self, out_of_band=''): + self.out_of_band = out_of_band + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.out_of_band, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.out_of_band, str_or_bytes),\ + 'A non-string value was supplied for self.out_of_band' + data.encode_short_string(pieces, self.out_of_band) + return pieces + + class OpenOk(amqp_object.Method): + + INDEX = 0x0014000B # 20, 11; 1310731 + NAME = 'Channel.OpenOk' + + def __init__(self, channel_id=''): + self.channel_id = channel_id + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + length = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.channel_id = encoded[offset:offset + length] + try: + self.channel_id = str(self.channel_id) + except UnicodeEncodeError: + pass + offset += length + return self + + def encode(self): + pieces = list() + assert isinstance(self.channel_id, str_or_bytes),\ + 'A non-string value was supplied for self.channel_id' + value = self.channel_id.encode('utf-8') if isinstance(self.channel_id, unicode_type) else self.channel_id + pieces.append(struct.pack('>I', len(value))) + pieces.append(value) + return pieces + + class Flow(amqp_object.Method): + + INDEX = 0x00140014 # 20, 20; 1310740 + NAME = 'Channel.Flow' + + def __init__(self, active=None): + self.active = active + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.active = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + bit_buffer = 0 + if self.active: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class FlowOk(amqp_object.Method): + + INDEX = 0x00140015 # 20, 21; 1310741 + NAME = 'Channel.FlowOk' + + def __init__(self, active=None): + self.active = active + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.active = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + bit_buffer = 0 + if self.active: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class Close(amqp_object.Method): + + INDEX = 0x00140028 # 20, 40; 1310760 + NAME = 'Channel.Close' + + def __init__(self, reply_code=None, reply_text='', class_id=None, method_id=None): + self.reply_code = reply_code + self.reply_text = reply_text + self.class_id = class_id + self.method_id = method_id + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.reply_code = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.reply_text, offset = data.decode_short_string(encoded, offset) + self.class_id = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.method_id = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.reply_code)) + assert isinstance(self.reply_text, str_or_bytes),\ + 'A non-string value was supplied for self.reply_text' + data.encode_short_string(pieces, self.reply_text) + pieces.append(struct.pack('>H', self.class_id)) + pieces.append(struct.pack('>H', self.method_id)) + return pieces + + class CloseOk(amqp_object.Method): + + INDEX = 0x00140029 # 20, 41; 1310761 + NAME = 'Channel.CloseOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class Access(amqp_object.Class): + + INDEX = 0x001E # 30 + NAME = 'Access' + + class Request(amqp_object.Method): + + INDEX = 0x001E000A # 30, 10; 1966090 + NAME = 'Access.Request' + + def __init__(self, realm='/data', exclusive=False, passive=True, active=True, write=True, read=True): + self.realm = realm + self.exclusive = exclusive + self.passive = passive + self.active = active + self.write = write + self.read = read + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.realm, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.exclusive = (bit_buffer & (1 << 0)) != 0 + self.passive = (bit_buffer & (1 << 1)) != 0 + self.active = (bit_buffer & (1 << 2)) != 0 + self.write = (bit_buffer & (1 << 3)) != 0 + self.read = (bit_buffer & (1 << 4)) != 0 + return self + + def encode(self): + pieces = list() + assert isinstance(self.realm, str_or_bytes),\ + 'A non-string value was supplied for self.realm' + data.encode_short_string(pieces, self.realm) + bit_buffer = 0 + if self.exclusive: + bit_buffer = bit_buffer | (1 << 0) + if self.passive: + bit_buffer = bit_buffer | (1 << 1) + if self.active: + bit_buffer = bit_buffer | (1 << 2) + if self.write: + bit_buffer = bit_buffer | (1 << 3) + if self.read: + bit_buffer = bit_buffer | (1 << 4) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class RequestOk(amqp_object.Method): + + INDEX = 0x001E000B # 30, 11; 1966091 + NAME = 'Access.RequestOk' + + def __init__(self, ticket=1): + self.ticket = ticket + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + return pieces + + +class Exchange(amqp_object.Class): + + INDEX = 0x0028 # 40 + NAME = 'Exchange' + + class Declare(amqp_object.Method): + + INDEX = 0x0028000A # 40, 10; 2621450 + NAME = 'Exchange.Declare' + + def __init__(self, ticket=0, exchange=None, type='direct', passive=False, durable=False, auto_delete=False, internal=False, nowait=False, arguments={}): + self.ticket = ticket + self.exchange = exchange + self.type = type + self.passive = passive + self.durable = durable + self.auto_delete = auto_delete + self.internal = internal + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.exchange, offset = data.decode_short_string(encoded, offset) + self.type, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.passive = (bit_buffer & (1 << 0)) != 0 + self.durable = (bit_buffer & (1 << 1)) != 0 + self.auto_delete = (bit_buffer & (1 << 2)) != 0 + self.internal = (bit_buffer & (1 << 3)) != 0 + self.nowait = (bit_buffer & (1 << 4)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.type, str_or_bytes),\ + 'A non-string value was supplied for self.type' + data.encode_short_string(pieces, self.type) + bit_buffer = 0 + if self.passive: + bit_buffer = bit_buffer | (1 << 0) + if self.durable: + bit_buffer = bit_buffer | (1 << 1) + if self.auto_delete: + bit_buffer = bit_buffer | (1 << 2) + if self.internal: + bit_buffer = bit_buffer | (1 << 3) + if self.nowait: + bit_buffer = bit_buffer | (1 << 4) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class DeclareOk(amqp_object.Method): + + INDEX = 0x0028000B # 40, 11; 2621451 + NAME = 'Exchange.DeclareOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Delete(amqp_object.Method): + + INDEX = 0x00280014 # 40, 20; 2621460 + NAME = 'Exchange.Delete' + + def __init__(self, ticket=0, exchange=None, if_unused=False, nowait=False): + self.ticket = ticket + self.exchange = exchange + self.if_unused = if_unused + self.nowait = nowait + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.exchange, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.if_unused = (bit_buffer & (1 << 0)) != 0 + self.nowait = (bit_buffer & (1 << 1)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + bit_buffer = 0 + if self.if_unused: + bit_buffer = bit_buffer | (1 << 0) + if self.nowait: + bit_buffer = bit_buffer | (1 << 1) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class DeleteOk(amqp_object.Method): + + INDEX = 0x00280015 # 40, 21; 2621461 + NAME = 'Exchange.DeleteOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Bind(amqp_object.Method): + + INDEX = 0x0028001E # 40, 30; 2621470 + NAME = 'Exchange.Bind' + + def __init__(self, ticket=0, destination=None, source=None, routing_key='', nowait=False, arguments={}): + self.ticket = ticket + self.destination = destination + self.source = source + self.routing_key = routing_key + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.destination, offset = data.decode_short_string(encoded, offset) + self.source, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.destination, str_or_bytes),\ + 'A non-string value was supplied for self.destination' + data.encode_short_string(pieces, self.destination) + assert isinstance(self.source, str_or_bytes),\ + 'A non-string value was supplied for self.source' + data.encode_short_string(pieces, self.source) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class BindOk(amqp_object.Method): + + INDEX = 0x0028001F # 40, 31; 2621471 + NAME = 'Exchange.BindOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Unbind(amqp_object.Method): + + INDEX = 0x00280028 # 40, 40; 2621480 + NAME = 'Exchange.Unbind' + + def __init__(self, ticket=0, destination=None, source=None, routing_key='', nowait=False, arguments={}): + self.ticket = ticket + self.destination = destination + self.source = source + self.routing_key = routing_key + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.destination, offset = data.decode_short_string(encoded, offset) + self.source, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.destination, str_or_bytes),\ + 'A non-string value was supplied for self.destination' + data.encode_short_string(pieces, self.destination) + assert isinstance(self.source, str_or_bytes),\ + 'A non-string value was supplied for self.source' + data.encode_short_string(pieces, self.source) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class UnbindOk(amqp_object.Method): + + INDEX = 0x00280033 # 40, 51; 2621491 + NAME = 'Exchange.UnbindOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class Queue(amqp_object.Class): + + INDEX = 0x0032 # 50 + NAME = 'Queue' + + class Declare(amqp_object.Method): + + INDEX = 0x0032000A # 50, 10; 3276810 + NAME = 'Queue.Declare' + + def __init__(self, ticket=0, queue='', passive=False, durable=False, exclusive=False, auto_delete=False, nowait=False, arguments={}): + self.ticket = ticket + self.queue = queue + self.passive = passive + self.durable = durable + self.exclusive = exclusive + self.auto_delete = auto_delete + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.passive = (bit_buffer & (1 << 0)) != 0 + self.durable = (bit_buffer & (1 << 1)) != 0 + self.exclusive = (bit_buffer & (1 << 2)) != 0 + self.auto_delete = (bit_buffer & (1 << 3)) != 0 + self.nowait = (bit_buffer & (1 << 4)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + bit_buffer = 0 + if self.passive: + bit_buffer = bit_buffer | (1 << 0) + if self.durable: + bit_buffer = bit_buffer | (1 << 1) + if self.exclusive: + bit_buffer = bit_buffer | (1 << 2) + if self.auto_delete: + bit_buffer = bit_buffer | (1 << 3) + if self.nowait: + bit_buffer = bit_buffer | (1 << 4) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class DeclareOk(amqp_object.Method): + + INDEX = 0x0032000B # 50, 11; 3276811 + NAME = 'Queue.DeclareOk' + + def __init__(self, queue=None, message_count=None, consumer_count=None): + self.queue = queue + self.message_count = message_count + self.consumer_count = consumer_count + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.queue, offset = data.decode_short_string(encoded, offset) + self.message_count = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.consumer_count = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + return self + + def encode(self): + pieces = list() + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + pieces.append(struct.pack('>I', self.message_count)) + pieces.append(struct.pack('>I', self.consumer_count)) + return pieces + + class Bind(amqp_object.Method): + + INDEX = 0x00320014 # 50, 20; 3276820 + NAME = 'Queue.Bind' + + def __init__(self, ticket=0, queue='', exchange=None, routing_key='', nowait=False, arguments={}): + self.ticket = ticket + self.queue = queue + self.exchange = exchange + self.routing_key = routing_key + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class BindOk(amqp_object.Method): + + INDEX = 0x00320015 # 50, 21; 3276821 + NAME = 'Queue.BindOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Purge(amqp_object.Method): + + INDEX = 0x0032001E # 50, 30; 3276830 + NAME = 'Queue.Purge' + + def __init__(self, ticket=0, queue='', nowait=False): + self.ticket = ticket + self.queue = queue + self.nowait = nowait + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class PurgeOk(amqp_object.Method): + + INDEX = 0x0032001F # 50, 31; 3276831 + NAME = 'Queue.PurgeOk' + + def __init__(self, message_count=None): + self.message_count = message_count + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.message_count = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>I', self.message_count)) + return pieces + + class Delete(amqp_object.Method): + + INDEX = 0x00320028 # 50, 40; 3276840 + NAME = 'Queue.Delete' + + def __init__(self, ticket=0, queue='', if_unused=False, if_empty=False, nowait=False): + self.ticket = ticket + self.queue = queue + self.if_unused = if_unused + self.if_empty = if_empty + self.nowait = nowait + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.if_unused = (bit_buffer & (1 << 0)) != 0 + self.if_empty = (bit_buffer & (1 << 1)) != 0 + self.nowait = (bit_buffer & (1 << 2)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + bit_buffer = 0 + if self.if_unused: + bit_buffer = bit_buffer | (1 << 0) + if self.if_empty: + bit_buffer = bit_buffer | (1 << 1) + if self.nowait: + bit_buffer = bit_buffer | (1 << 2) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class DeleteOk(amqp_object.Method): + + INDEX = 0x00320029 # 50, 41; 3276841 + NAME = 'Queue.DeleteOk' + + def __init__(self, message_count=None): + self.message_count = message_count + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.message_count = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>I', self.message_count)) + return pieces + + class Unbind(amqp_object.Method): + + INDEX = 0x00320032 # 50, 50; 3276850 + NAME = 'Queue.Unbind' + + def __init__(self, ticket=0, queue='', exchange=None, routing_key='', arguments={}): + self.ticket = ticket + self.queue = queue + self.exchange = exchange + self.routing_key = routing_key + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + data.encode_table(pieces, self.arguments) + return pieces + + class UnbindOk(amqp_object.Method): + + INDEX = 0x00320033 # 50, 51; 3276851 + NAME = 'Queue.UnbindOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class Basic(amqp_object.Class): + + INDEX = 0x003C # 60 + NAME = 'Basic' + + class Qos(amqp_object.Method): + + INDEX = 0x003C000A # 60, 10; 3932170 + NAME = 'Basic.Qos' + + def __init__(self, prefetch_size=0, prefetch_count=0, global_=False): + self.prefetch_size = prefetch_size + self.prefetch_count = prefetch_count + self.global_ = global_ + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.prefetch_size = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + self.prefetch_count = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.global_ = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>I', self.prefetch_size)) + pieces.append(struct.pack('>H', self.prefetch_count)) + bit_buffer = 0 + if self.global_: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class QosOk(amqp_object.Method): + + INDEX = 0x003C000B # 60, 11; 3932171 + NAME = 'Basic.QosOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Consume(amqp_object.Method): + + INDEX = 0x003C0014 # 60, 20; 3932180 + NAME = 'Basic.Consume' + + def __init__(self, ticket=0, queue='', consumer_tag='', no_local=False, no_ack=False, exclusive=False, nowait=False, arguments={}): + self.ticket = ticket + self.queue = queue + self.consumer_tag = consumer_tag + self.no_local = no_local + self.no_ack = no_ack + self.exclusive = exclusive + self.nowait = nowait + self.arguments = arguments + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + self.consumer_tag, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.no_local = (bit_buffer & (1 << 0)) != 0 + self.no_ack = (bit_buffer & (1 << 1)) != 0 + self.exclusive = (bit_buffer & (1 << 2)) != 0 + self.nowait = (bit_buffer & (1 << 3)) != 0 + (self.arguments, offset) = data.decode_table(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + assert isinstance(self.consumer_tag, str_or_bytes),\ + 'A non-string value was supplied for self.consumer_tag' + data.encode_short_string(pieces, self.consumer_tag) + bit_buffer = 0 + if self.no_local: + bit_buffer = bit_buffer | (1 << 0) + if self.no_ack: + bit_buffer = bit_buffer | (1 << 1) + if self.exclusive: + bit_buffer = bit_buffer | (1 << 2) + if self.nowait: + bit_buffer = bit_buffer | (1 << 3) + pieces.append(struct.pack('B', bit_buffer)) + data.encode_table(pieces, self.arguments) + return pieces + + class ConsumeOk(amqp_object.Method): + + INDEX = 0x003C0015 # 60, 21; 3932181 + NAME = 'Basic.ConsumeOk' + + def __init__(self, consumer_tag=None): + self.consumer_tag = consumer_tag + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.consumer_tag, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.consumer_tag, str_or_bytes),\ + 'A non-string value was supplied for self.consumer_tag' + data.encode_short_string(pieces, self.consumer_tag) + return pieces + + class Cancel(amqp_object.Method): + + INDEX = 0x003C001E # 60, 30; 3932190 + NAME = 'Basic.Cancel' + + def __init__(self, consumer_tag=None, nowait=False): + self.consumer_tag = consumer_tag + self.nowait = nowait + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.consumer_tag, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + assert isinstance(self.consumer_tag, str_or_bytes),\ + 'A non-string value was supplied for self.consumer_tag' + data.encode_short_string(pieces, self.consumer_tag) + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class CancelOk(amqp_object.Method): + + INDEX = 0x003C001F # 60, 31; 3932191 + NAME = 'Basic.CancelOk' + + def __init__(self, consumer_tag=None): + self.consumer_tag = consumer_tag + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.consumer_tag, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.consumer_tag, str_or_bytes),\ + 'A non-string value was supplied for self.consumer_tag' + data.encode_short_string(pieces, self.consumer_tag) + return pieces + + class Publish(amqp_object.Method): + + INDEX = 0x003C0028 # 60, 40; 3932200 + NAME = 'Basic.Publish' + + def __init__(self, ticket=0, exchange='', routing_key='', mandatory=False, immediate=False): + self.ticket = ticket + self.exchange = exchange + self.routing_key = routing_key + self.mandatory = mandatory + self.immediate = immediate + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.mandatory = (bit_buffer & (1 << 0)) != 0 + self.immediate = (bit_buffer & (1 << 1)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + bit_buffer = 0 + if self.mandatory: + bit_buffer = bit_buffer | (1 << 0) + if self.immediate: + bit_buffer = bit_buffer | (1 << 1) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class Return(amqp_object.Method): + + INDEX = 0x003C0032 # 60, 50; 3932210 + NAME = 'Basic.Return' + + def __init__(self, reply_code=None, reply_text='', exchange=None, routing_key=None): + self.reply_code = reply_code + self.reply_text = reply_text + self.exchange = exchange + self.routing_key = routing_key + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.reply_code = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.reply_text, offset = data.decode_short_string(encoded, offset) + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.reply_code)) + assert isinstance(self.reply_text, str_or_bytes),\ + 'A non-string value was supplied for self.reply_text' + data.encode_short_string(pieces, self.reply_text) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + return pieces + + class Deliver(amqp_object.Method): + + INDEX = 0x003C003C # 60, 60; 3932220 + NAME = 'Basic.Deliver' + + def __init__(self, consumer_tag=None, delivery_tag=None, redelivered=False, exchange=None, routing_key=None): + self.consumer_tag = consumer_tag + self.delivery_tag = delivery_tag + self.redelivered = redelivered + self.exchange = exchange + self.routing_key = routing_key + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.consumer_tag, offset = data.decode_short_string(encoded, offset) + self.delivery_tag = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.redelivered = (bit_buffer & (1 << 0)) != 0 + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.consumer_tag, str_or_bytes),\ + 'A non-string value was supplied for self.consumer_tag' + data.encode_short_string(pieces, self.consumer_tag) + pieces.append(struct.pack('>Q', self.delivery_tag)) + bit_buffer = 0 + if self.redelivered: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + return pieces + + class Get(amqp_object.Method): + + INDEX = 0x003C0046 # 60, 70; 3932230 + NAME = 'Basic.Get' + + def __init__(self, ticket=0, queue='', no_ack=False): + self.ticket = ticket + self.queue = queue + self.no_ack = no_ack + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + self.ticket = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + self.queue, offset = data.decode_short_string(encoded, offset) + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.no_ack = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>H', self.ticket)) + assert isinstance(self.queue, str_or_bytes),\ + 'A non-string value was supplied for self.queue' + data.encode_short_string(pieces, self.queue) + bit_buffer = 0 + if self.no_ack: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class GetOk(amqp_object.Method): + + INDEX = 0x003C0047 # 60, 71; 3932231 + NAME = 'Basic.GetOk' + + def __init__(self, delivery_tag=None, redelivered=False, exchange=None, routing_key=None, message_count=None): + self.delivery_tag = delivery_tag + self.redelivered = redelivered + self.exchange = exchange + self.routing_key = routing_key + self.message_count = message_count + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.delivery_tag = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.redelivered = (bit_buffer & (1 << 0)) != 0 + self.exchange, offset = data.decode_short_string(encoded, offset) + self.routing_key, offset = data.decode_short_string(encoded, offset) + self.message_count = struct.unpack_from('>I', encoded, offset)[0] + offset += 4 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>Q', self.delivery_tag)) + bit_buffer = 0 + if self.redelivered: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + assert isinstance(self.exchange, str_or_bytes),\ + 'A non-string value was supplied for self.exchange' + data.encode_short_string(pieces, self.exchange) + assert isinstance(self.routing_key, str_or_bytes),\ + 'A non-string value was supplied for self.routing_key' + data.encode_short_string(pieces, self.routing_key) + pieces.append(struct.pack('>I', self.message_count)) + return pieces + + class GetEmpty(amqp_object.Method): + + INDEX = 0x003C0048 # 60, 72; 3932232 + NAME = 'Basic.GetEmpty' + + def __init__(self, cluster_id=''): + self.cluster_id = cluster_id + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.cluster_id, offset = data.decode_short_string(encoded, offset) + return self + + def encode(self): + pieces = list() + assert isinstance(self.cluster_id, str_or_bytes),\ + 'A non-string value was supplied for self.cluster_id' + data.encode_short_string(pieces, self.cluster_id) + return pieces + + class Ack(amqp_object.Method): + + INDEX = 0x003C0050 # 60, 80; 3932240 + NAME = 'Basic.Ack' + + def __init__(self, delivery_tag=0, multiple=False): + self.delivery_tag = delivery_tag + self.multiple = multiple + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.delivery_tag = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.multiple = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>Q', self.delivery_tag)) + bit_buffer = 0 + if self.multiple: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class Reject(amqp_object.Method): + + INDEX = 0x003C005A # 60, 90; 3932250 + NAME = 'Basic.Reject' + + def __init__(self, delivery_tag=None, requeue=True): + self.delivery_tag = delivery_tag + self.requeue = requeue + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.delivery_tag = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.requeue = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>Q', self.delivery_tag)) + bit_buffer = 0 + if self.requeue: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class RecoverAsync(amqp_object.Method): + + INDEX = 0x003C0064 # 60, 100; 3932260 + NAME = 'Basic.RecoverAsync' + + def __init__(self, requeue=False): + self.requeue = requeue + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.requeue = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + bit_buffer = 0 + if self.requeue: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class Recover(amqp_object.Method): + + INDEX = 0x003C006E # 60, 110; 3932270 + NAME = 'Basic.Recover' + + def __init__(self, requeue=False): + self.requeue = requeue + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.requeue = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + bit_buffer = 0 + if self.requeue: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class RecoverOk(amqp_object.Method): + + INDEX = 0x003C006F # 60, 111; 3932271 + NAME = 'Basic.RecoverOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Nack(amqp_object.Method): + + INDEX = 0x003C0078 # 60, 120; 3932280 + NAME = 'Basic.Nack' + + def __init__(self, delivery_tag=0, multiple=False, requeue=True): + self.delivery_tag = delivery_tag + self.multiple = multiple + self.requeue = requeue + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + self.delivery_tag = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.multiple = (bit_buffer & (1 << 0)) != 0 + self.requeue = (bit_buffer & (1 << 1)) != 0 + return self + + def encode(self): + pieces = list() + pieces.append(struct.pack('>Q', self.delivery_tag)) + bit_buffer = 0 + if self.multiple: + bit_buffer = bit_buffer | (1 << 0) + if self.requeue: + bit_buffer = bit_buffer | (1 << 1) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + +class Tx(amqp_object.Class): + + INDEX = 0x005A # 90 + NAME = 'Tx' + + class Select(amqp_object.Method): + + INDEX = 0x005A000A # 90, 10; 5898250 + NAME = 'Tx.Select' + + def __init__(self): + pass + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class SelectOk(amqp_object.Method): + + INDEX = 0x005A000B # 90, 11; 5898251 + NAME = 'Tx.SelectOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Commit(amqp_object.Method): + + INDEX = 0x005A0014 # 90, 20; 5898260 + NAME = 'Tx.Commit' + + def __init__(self): + pass + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class CommitOk(amqp_object.Method): + + INDEX = 0x005A0015 # 90, 21; 5898261 + NAME = 'Tx.CommitOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class Rollback(amqp_object.Method): + + INDEX = 0x005A001E # 90, 30; 5898270 + NAME = 'Tx.Rollback' + + def __init__(self): + pass + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + class RollbackOk(amqp_object.Method): + + INDEX = 0x005A001F # 90, 31; 5898271 + NAME = 'Tx.RollbackOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class Confirm(amqp_object.Class): + + INDEX = 0x0055 # 85 + NAME = 'Confirm' + + class Select(amqp_object.Method): + + INDEX = 0x0055000A # 85, 10; 5570570 + NAME = 'Confirm.Select' + + def __init__(self, nowait=False): + self.nowait = nowait + + @property + def synchronous(self): + return True + + def decode(self, encoded, offset=0): + bit_buffer = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + self.nowait = (bit_buffer & (1 << 0)) != 0 + return self + + def encode(self): + pieces = list() + bit_buffer = 0 + if self.nowait: + bit_buffer = bit_buffer | (1 << 0) + pieces.append(struct.pack('B', bit_buffer)) + return pieces + + class SelectOk(amqp_object.Method): + + INDEX = 0x0055000B # 85, 11; 5570571 + NAME = 'Confirm.SelectOk' + + def __init__(self): + pass + + @property + def synchronous(self): + return False + + def decode(self, encoded, offset=0): + return self + + def encode(self): + pieces = list() + return pieces + + +class BasicProperties(amqp_object.Properties): + + CLASS = Basic + INDEX = 0x003C # 60 + NAME = 'BasicProperties' + + FLAG_CONTENT_TYPE = (1 << 15) + FLAG_CONTENT_ENCODING = (1 << 14) + FLAG_HEADERS = (1 << 13) + FLAG_DELIVERY_MODE = (1 << 12) + FLAG_PRIORITY = (1 << 11) + FLAG_CORRELATION_ID = (1 << 10) + FLAG_REPLY_TO = (1 << 9) + FLAG_EXPIRATION = (1 << 8) + FLAG_MESSAGE_ID = (1 << 7) + FLAG_TIMESTAMP = (1 << 6) + FLAG_TYPE = (1 << 5) + FLAG_USER_ID = (1 << 4) + FLAG_APP_ID = (1 << 3) + FLAG_CLUSTER_ID = (1 << 2) + + def __init__(self, content_type=None, content_encoding=None, headers=None, delivery_mode=None, priority=None, correlation_id=None, reply_to=None, expiration=None, message_id=None, timestamp=None, type=None, user_id=None, app_id=None, cluster_id=None): + self.content_type = content_type + self.content_encoding = content_encoding + self.headers = headers + self.delivery_mode = delivery_mode + self.priority = priority + self.correlation_id = correlation_id + self.reply_to = reply_to + self.expiration = expiration + self.message_id = message_id + self.timestamp = timestamp + self.type = type + self.user_id = user_id + self.app_id = app_id + self.cluster_id = cluster_id + + def decode(self, encoded, offset=0): + flags = 0 + flagword_index = 0 + while True: + partial_flags = struct.unpack_from('>H', encoded, offset)[0] + offset += 2 + flags = flags | (partial_flags << (flagword_index * 16)) + if not (partial_flags & 1): + break + flagword_index += 1 + if flags & BasicProperties.FLAG_CONTENT_TYPE: + self.content_type, offset = data.decode_short_string(encoded, offset) + else: + self.content_type = None + if flags & BasicProperties.FLAG_CONTENT_ENCODING: + self.content_encoding, offset = data.decode_short_string(encoded, offset) + else: + self.content_encoding = None + if flags & BasicProperties.FLAG_HEADERS: + (self.headers, offset) = data.decode_table(encoded, offset) + else: + self.headers = None + if flags & BasicProperties.FLAG_DELIVERY_MODE: + self.delivery_mode = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + else: + self.delivery_mode = None + if flags & BasicProperties.FLAG_PRIORITY: + self.priority = struct.unpack_from('B', encoded, offset)[0] + offset += 1 + else: + self.priority = None + if flags & BasicProperties.FLAG_CORRELATION_ID: + self.correlation_id, offset = data.decode_short_string(encoded, offset) + else: + self.correlation_id = None + if flags & BasicProperties.FLAG_REPLY_TO: + self.reply_to, offset = data.decode_short_string(encoded, offset) + else: + self.reply_to = None + if flags & BasicProperties.FLAG_EXPIRATION: + self.expiration, offset = data.decode_short_string(encoded, offset) + else: + self.expiration = None + if flags & BasicProperties.FLAG_MESSAGE_ID: + self.message_id, offset = data.decode_short_string(encoded, offset) + else: + self.message_id = None + if flags & BasicProperties.FLAG_TIMESTAMP: + self.timestamp = struct.unpack_from('>Q', encoded, offset)[0] + offset += 8 + else: + self.timestamp = None + if flags & BasicProperties.FLAG_TYPE: + self.type, offset = data.decode_short_string(encoded, offset) + else: + self.type = None + if flags & BasicProperties.FLAG_USER_ID: + self.user_id, offset = data.decode_short_string(encoded, offset) + else: + self.user_id = None + if flags & BasicProperties.FLAG_APP_ID: + self.app_id, offset = data.decode_short_string(encoded, offset) + else: + self.app_id = None + if flags & BasicProperties.FLAG_CLUSTER_ID: + self.cluster_id, offset = data.decode_short_string(encoded, offset) + else: + self.cluster_id = None + return self + + def encode(self): + pieces = list() + flags = 0 + if self.content_type is not None: + flags = flags | BasicProperties.FLAG_CONTENT_TYPE + assert isinstance(self.content_type, str_or_bytes),\ + 'A non-string value was supplied for self.content_type' + data.encode_short_string(pieces, self.content_type) + if self.content_encoding is not None: + flags = flags | BasicProperties.FLAG_CONTENT_ENCODING + assert isinstance(self.content_encoding, str_or_bytes),\ + 'A non-string value was supplied for self.content_encoding' + data.encode_short_string(pieces, self.content_encoding) + if self.headers is not None: + flags = flags | BasicProperties.FLAG_HEADERS + data.encode_table(pieces, self.headers) + if self.delivery_mode is not None: + flags = flags | BasicProperties.FLAG_DELIVERY_MODE + pieces.append(struct.pack('B', self.delivery_mode)) + if self.priority is not None: + flags = flags | BasicProperties.FLAG_PRIORITY + pieces.append(struct.pack('B', self.priority)) + if self.correlation_id is not None: + flags = flags | BasicProperties.FLAG_CORRELATION_ID + assert isinstance(self.correlation_id, str_or_bytes),\ + 'A non-string value was supplied for self.correlation_id' + data.encode_short_string(pieces, self.correlation_id) + if self.reply_to is not None: + flags = flags | BasicProperties.FLAG_REPLY_TO + assert isinstance(self.reply_to, str_or_bytes),\ + 'A non-string value was supplied for self.reply_to' + data.encode_short_string(pieces, self.reply_to) + if self.expiration is not None: + flags = flags | BasicProperties.FLAG_EXPIRATION + assert isinstance(self.expiration, str_or_bytes),\ + 'A non-string value was supplied for self.expiration' + data.encode_short_string(pieces, self.expiration) + if self.message_id is not None: + flags = flags | BasicProperties.FLAG_MESSAGE_ID + assert isinstance(self.message_id, str_or_bytes),\ + 'A non-string value was supplied for self.message_id' + data.encode_short_string(pieces, self.message_id) + if self.timestamp is not None: + flags = flags | BasicProperties.FLAG_TIMESTAMP + pieces.append(struct.pack('>Q', self.timestamp)) + if self.type is not None: + flags = flags | BasicProperties.FLAG_TYPE + assert isinstance(self.type, str_or_bytes),\ + 'A non-string value was supplied for self.type' + data.encode_short_string(pieces, self.type) + if self.user_id is not None: + flags = flags | BasicProperties.FLAG_USER_ID + assert isinstance(self.user_id, str_or_bytes),\ + 'A non-string value was supplied for self.user_id' + data.encode_short_string(pieces, self.user_id) + if self.app_id is not None: + flags = flags | BasicProperties.FLAG_APP_ID + assert isinstance(self.app_id, str_or_bytes),\ + 'A non-string value was supplied for self.app_id' + data.encode_short_string(pieces, self.app_id) + if self.cluster_id is not None: + flags = flags | BasicProperties.FLAG_CLUSTER_ID + assert isinstance(self.cluster_id, str_or_bytes),\ + 'A non-string value was supplied for self.cluster_id' + data.encode_short_string(pieces, self.cluster_id) + flag_pieces = list() + while True: + remainder = flags >> 16 + partial_flags = flags & 0xFFFE + if remainder != 0: + partial_flags |= 1 + flag_pieces.append(struct.pack('>H', partial_flags)) + flags = remainder + if not flags: + break + return flag_pieces + pieces + +methods = { + 0x000A000A: Connection.Start, + 0x000A000B: Connection.StartOk, + 0x000A0014: Connection.Secure, + 0x000A0015: Connection.SecureOk, + 0x000A001E: Connection.Tune, + 0x000A001F: Connection.TuneOk, + 0x000A0028: Connection.Open, + 0x000A0029: Connection.OpenOk, + 0x000A0032: Connection.Close, + 0x000A0033: Connection.CloseOk, + 0x000A003C: Connection.Blocked, + 0x000A003D: Connection.Unblocked, + 0x0014000A: Channel.Open, + 0x0014000B: Channel.OpenOk, + 0x00140014: Channel.Flow, + 0x00140015: Channel.FlowOk, + 0x00140028: Channel.Close, + 0x00140029: Channel.CloseOk, + 0x001E000A: Access.Request, + 0x001E000B: Access.RequestOk, + 0x0028000A: Exchange.Declare, + 0x0028000B: Exchange.DeclareOk, + 0x00280014: Exchange.Delete, + 0x00280015: Exchange.DeleteOk, + 0x0028001E: Exchange.Bind, + 0x0028001F: Exchange.BindOk, + 0x00280028: Exchange.Unbind, + 0x00280033: Exchange.UnbindOk, + 0x0032000A: Queue.Declare, + 0x0032000B: Queue.DeclareOk, + 0x00320014: Queue.Bind, + 0x00320015: Queue.BindOk, + 0x0032001E: Queue.Purge, + 0x0032001F: Queue.PurgeOk, + 0x00320028: Queue.Delete, + 0x00320029: Queue.DeleteOk, + 0x00320032: Queue.Unbind, + 0x00320033: Queue.UnbindOk, + 0x003C000A: Basic.Qos, + 0x003C000B: Basic.QosOk, + 0x003C0014: Basic.Consume, + 0x003C0015: Basic.ConsumeOk, + 0x003C001E: Basic.Cancel, + 0x003C001F: Basic.CancelOk, + 0x003C0028: Basic.Publish, + 0x003C0032: Basic.Return, + 0x003C003C: Basic.Deliver, + 0x003C0046: Basic.Get, + 0x003C0047: Basic.GetOk, + 0x003C0048: Basic.GetEmpty, + 0x003C0050: Basic.Ack, + 0x003C005A: Basic.Reject, + 0x003C0064: Basic.RecoverAsync, + 0x003C006E: Basic.Recover, + 0x003C006F: Basic.RecoverOk, + 0x003C0078: Basic.Nack, + 0x005A000A: Tx.Select, + 0x005A000B: Tx.SelectOk, + 0x005A0014: Tx.Commit, + 0x005A0015: Tx.CommitOk, + 0x005A001E: Tx.Rollback, + 0x005A001F: Tx.RollbackOk, + 0x0055000A: Confirm.Select, + 0x0055000B: Confirm.SelectOk +} + +props = { + 0x003C: BasicProperties +} + + +def has_content(methodNumber): + return methodNumber in ( + Basic.Publish.INDEX, + Basic.Return.INDEX, + Basic.Deliver.INDEX, + Basic.GetOk.INDEX, + ) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/tcp_socket_opts.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/tcp_socket_opts.py new file mode 100644 index 000000000..73d2062b1 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/tcp_socket_opts.py @@ -0,0 +1,43 @@ +import logging +import socket +import pika.compat + +LOGGER = logging.getLogger(__name__) + +_SUPPORTED_TCP_OPTIONS = {} + +try: + _SUPPORTED_TCP_OPTIONS['TCP_USER_TIMEOUT'] = socket.TCP_USER_TIMEOUT +except AttributeError: + if pika.compat.LINUX_VERSION and pika.compat.LINUX_VERSION >= (2, 6, 37): + # NB: this is not the timeout value, but the number corresponding + # to the constant in tcp.h + # https://github.com/torvalds/linux/blob/master/include/uapi/linux/tcp.h# + # #define TCP_USER_TIMEOUT 18 /* How long for loss retry before timeout */ + _SUPPORTED_TCP_OPTIONS['TCP_USER_TIMEOUT'] = 18 + +try: + _SUPPORTED_TCP_OPTIONS['TCP_KEEPIDLE'] = socket.TCP_KEEPIDLE + _SUPPORTED_TCP_OPTIONS['TCP_KEEPCNT'] = socket.TCP_KEEPCNT + _SUPPORTED_TCP_OPTIONS['TCP_KEEPINTVL'] = socket.TCP_KEEPINTVL +except AttributeError: + pass + + +def socket_requires_keepalive(tcp_options): + return 'TCP_KEEPIDLE' in tcp_options or 'TCP_KEEPCNT' in tcp_options or 'TCP_KEEPINTVL' in tcp_options + + +def set_sock_opts(tcp_options, sock): + if not tcp_options: + return + + if socket_requires_keepalive(tcp_options): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + + for key, value in tcp_options.items(): + option = _SUPPORTED_TCP_OPTIONS.get(key) + if option: + sock.setsockopt(pika.compat.SOL_TCP, option, value) + else: + LOGGER.warning('Unsupported TCP option %s:%s', key, value) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/utils.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/utils.py new file mode 100644 index 000000000..57f93b0a6 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pika/utils.py @@ -0,0 +1,16 @@ +""" +Non-module specific functions shared by modules in the pika package + +""" +import collections + + +def is_callable(handle): + """Returns a bool value if the handle passed in is a callable + method/function + + :param any handle: The object to check + :rtype: bool + + """ + return isinstance(handle, collections.Callable) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pylintrc b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pylintrc new file mode 100644 index 000000000..4f96c7920 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/pylintrc @@ -0,0 +1,391 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=no + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Deprecated. It was used to include message's id in output. Use --msg-template +# instead. +#include-ids=no + +# Deprecated. It was used to include symbolic ids of messages in output. Use +# --msg-template instead. +#symbols=no + +# Use multiple processes to speed up Pylint. +#jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +#unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +#extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +#optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=R1705 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +msg-template={msg_id}, {line:3d}:{column:2d} - {msg} ({symbol}) +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,fd,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_|_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=10 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=20 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=20 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=40 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.cfg b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.cfg new file mode 100644 index 000000000..3bac5afc9 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.cfg @@ -0,0 +1,16 @@ +[bdist_wheel] +universal = 1 + +[nosetests] +cover-branches = 1 +cover-erase = 1 +cover-html = 1 +cover-html-dir = build/coverage +cover-package = pika +cover-tests = 1 +logging-level = DEBUG +stop = 1 +tests=tests/unit,tests/acceptance +verbosity = 3 +with-coverage = 1 +detailed-errors = 1 diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.py new file mode 100644 index 000000000..e64f5997d --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/setup.py @@ -0,0 +1,51 @@ +import setuptools +import os + +# Conditionally include additional modules for docs +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +requirements = list() +if on_rtd: + requirements.append('tornado') + requirements.append('twisted') + +long_description = ('Pika is a pure-Python implementation of the AMQP 0-9-1 ' + 'protocol that tries to stay fairly independent of the ' + 'underlying network support library. Pika was developed ' + 'primarily for use with RabbitMQ, but should also work ' + 'with other AMQP 0-9-1 brokers.') + +setuptools.setup( + name='pika', + version='0.13.1', + description='Pika Python AMQP Client Library', + long_description=open('README.rst').read(), + maintainer='Gavin M. Roy', + maintainer_email='gavinmroy@gmail.com', + url='https://pika.readthedocs.io', + packages=setuptools.find_packages(include=['pika', 'pika.*']), + license='BSD', + install_requires=requirements, + package_data={'': ['LICENSE', 'README.rst']}, + extras_require={'tornado': ['tornado'], + 'twisted': ['twisted']}, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: Jython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Communications', 'Topic :: Internet', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking' + ], + zip_safe=True) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/test-requirements.txt b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/test-requirements.txt new file mode 100644 index 000000000..ced2cc220 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/test-requirements.txt @@ -0,0 +1,6 @@ +coverage +codecov +mock +nose +tornado +twisted diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_certificate.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_certificate.pem new file mode 100644 index 000000000..a59971792 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_certificate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDAjCCAeqgAwIBAgIJAIwXYB8fddi4MA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV +BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE4 +MDIyNzIyMjkyMFoXDTI4MDIyNTIyMjkyMFowMTEgMB4GA1UEAwwXVExTR2VuU2Vs +ZlNpZ25lZHRSb290Q0ExDTALBgNVBAcMBCQkJCQwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDMHFlBCaj/wr9AToBRiEx4N8b+lLYyTgp2BA4j/+WyNXyQ +BlGOpqBiJ2XDyzykoQGeNDMao8JjhMOZVuZ/I9LxwRNDOuA5aTAOknuPf/M6VigW +OawsyZblcHW8X2QnX9UJi+MCDaoSFGDRfXwkXFkfEvkYYfXl7lrEdWXg0LsiphPJ +RflqJMhfM1R/b+NvJ6OJs7rM15J1V1AkQAqKWygM79FQzVOUb1tFVZfy/OrIqd4Z +3dDfqF1JqnLoGIoLWxXeheqiRgDYzM6hegDefWZtKmBFR2ZIL3ZRYP4A9Ftx1gan +Gi6u8J05sjDOHyrzg/pZbrK/8Nqc3QRlSxhbwG+hAgMBAAGjHTAbMAwGA1UdEwQF +MAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAMx1i0VogTZPYA +OJh/MdUSVNpI+DGZlId6ZPxglWDOQa53+DtjHlB/ur77RLnT89w3/HV2+xC+0yXP +W+MxRxbYe++Ja7mcByHHjEXMNT8GvffXrCSTT7hAUoQVlVX3VwCNLgjSY3NA6jw+ +Xhd/aOGDD8TmgNFdL2PbOiwbiiMP2nRmb11pxwvPUmGu4o2fn5biy8v47eq3zkvv +Wc64R0mrelVcEn2noFZBU9yfK4aCDD5kMG463UYZpj5/v20tPO44hYEc48MDMXOt +8jCaYaoh4P9zm1TK4syqY/b1fcCW44ri/HrZMfAoUGNktlbkBpnusMW7zwDg7ygG +gBBQadQA +-----END CERTIFICATE----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_key.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_key.pem new file mode 100644 index 000000000..8fbc4e144 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/ca_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMHFlBCaj/wr9A +ToBRiEx4N8b+lLYyTgp2BA4j/+WyNXyQBlGOpqBiJ2XDyzykoQGeNDMao8JjhMOZ +VuZ/I9LxwRNDOuA5aTAOknuPf/M6VigWOawsyZblcHW8X2QnX9UJi+MCDaoSFGDR +fXwkXFkfEvkYYfXl7lrEdWXg0LsiphPJRflqJMhfM1R/b+NvJ6OJs7rM15J1V1Ak +QAqKWygM79FQzVOUb1tFVZfy/OrIqd4Z3dDfqF1JqnLoGIoLWxXeheqiRgDYzM6h +egDefWZtKmBFR2ZIL3ZRYP4A9Ftx1ganGi6u8J05sjDOHyrzg/pZbrK/8Nqc3QRl +SxhbwG+hAgMBAAECggEBAKpjO+r8GnUvaKJRJfNqGZP91AOvtwz1cuwj5H4O3/vN +hKdibvDrjlWB2AUDgyNXONeNcd/aNqsHKJW4IoCeMjkYWXE1E/s5ISH5DGa1hCD9 +zUcIa/+TZYExSUewRhZMfLYUbbPIHmIrWstmupxL7yXum7zMAg8o0+LOsfZjqOR5 +SmbKja6nk+ywzTyVOSX4qJbfbxc+qswno1B7vGXgBXOjXR1jgfAqMjI3yaE5qyKy +GpGnHdYow5st8Yikjg91OoDjSaLPtm3+clkYpUl/oTh8g8wTFRrphrJjfWk/io/3 +IkNUpKuI/jRr3Br4Fx8tOfDC9pX4+nYcFjcc7Z5b/cECgYEA+LxFRWqI3RJQQTzE +8MQNbk8QdBKHtxziKUX8t3Skbe9s2/Fo4O5sQSfQ/+UtGlQP2LbJVJDiPYAEP2zu +ncmbXVmHgEWd9PcosN3li28Vk7d5Z5RGhb0BNyzslnUOF06xV84pDWRBKqTfk/iz +jpGwG70oz5K06jF8rJ81mwJni4kCgYEA0hJuUqw0NYNJAm/ALd64ffnHIkqjBsxi +pJruqGUf0DLROcVqv9SYDUmS23DHE4p7H1NXHwneTaBcdOUgTIdcjqJv3Nv+IDDi +unnaVAsBvtJ4YiCdXvNNMog2Ptx1Zcdy93fRmKHBNO349QGvhp4RPPMuoTCAmLXN +gm3qdJG9RVkCgYB671pxV5kzYmTGCYnw0YUt2ufv5mSrTRw+d/fSmFTYfPhZsHQ8 +j7pzbYFvqE5lb9yxKI5TPJSE/uuaiXVaCRW/yosdC8tpEmtLFzDnfEy2yHy5g+Bj +IyohohOAvXPscxlVo3BIzR8kO37BPK9KLJlU7GXqScEe6ryK+Nleto6EuQKBgFnH +qMDKehG4xzY0XnT8L+sfGh0sutoH9cyVrpPnjB4l6cd/+Ox9RnK/U/VXEK3oTFCK +BLzuMcUJWRpYZmJuo38OKzLADq7hVbUOqOGsRpWwS+TcPYW8A+0py8032TCjeh4L +ZleOIg2l+vVzP/oPihx7bh1TplIduPQaV850DukZAoGAOHvComT/TeGIHwTp1KyD +7w8uXfRmoo5nk+x8GnfbTi0+1XG79LA4061gtvvQkDPlkdZjUwJcz6bhXQVMlBwo +1ILIAj5mm89G0n5wbmshui6yCEs82fpYpg8UhXIq8/LmvdsJZjcJ7ySQxpuNqxCi +Q/L5/WmjSY+1bvvLkkUOF7k= +-----END PRIVATE KEY----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_certificate.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_certificate.pem new file mode 100644 index 000000000..43dd98c70 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_certificate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDADCCAeigAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH +ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xODAyMjcyMjI5 +MjBaFw0yODAyMjUyMjI5MjBaMCUxEjAQBgNVBAMMCWxvY2FsaG9zdDEPMA0GA1UE +CgwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4wBF9bFL +nRZlRdUWQ9M6R2lxwJvIfL4O0UT3opqc95RGaPuqJjWgc3kFkFmsJMI7cSgiDcDY +XKYNp/Z/l1BOYrwwbuNX2V8V2SFbP5ksUU75jxwKJV8/bNnHSMfOrZ0wlq2jIrEA +hvLC38ujrNZsSzWusjhESKy8rNgAqq+Xyfy/R+MIVrHfD6wjxyGDGctj0z6bYDai +LPR5AVgzGHNUTLWSsyS9KoeuOXFOWht7+iEj/eNbU8bA29oOnVyb52o5OSx/E/Nx +qV7z0tv+LL8LTs4iXo4DwfrYB6BRwM2Qwzma4tmpZG2uvwwq/xIStMSfUIIOIUeZ +WLUs2V8d0mUslQIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNV +HSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAmvYPZjAFUvknD4Cm +AkTu7ZAbsXkMYgFn74wxIr9gXbRswWJDsPmVYDHDtFbynBmLTVOciqS2P3ccnbOQ +sy8lLQeZ4YNcTcTs8GONyo3G6SDUTTF19dB/uIUbi9mzxfufhFg/UcGMSy6bAW/J +uEQi9RrfwlJAHkce39ZHxu8/xsHDiiEFQLK907kXLzZixQ0bonjbejgKQGiCX5oB +/JQiaEmREmwyENFSmEyNEprzorkquzB6HoqAIxQxNQ+4xuEmy2wr+jecK9X4cGU9 +uPdPlF+9GSY8qIomxvTBOuXOCyY+ULl3zZZhRjjh6c4Nj8YlUyBOoQW4XOM5R8DM +I8gRgA== +-----END CERTIFICATE----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_key.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_key.pem new file mode 100644 index 000000000..b95461488 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/client_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA4wBF9bFLnRZlRdUWQ9M6R2lxwJvIfL4O0UT3opqc95RGaPuq +JjWgc3kFkFmsJMI7cSgiDcDYXKYNp/Z/l1BOYrwwbuNX2V8V2SFbP5ksUU75jxwK +JV8/bNnHSMfOrZ0wlq2jIrEAhvLC38ujrNZsSzWusjhESKy8rNgAqq+Xyfy/R+MI +VrHfD6wjxyGDGctj0z6bYDaiLPR5AVgzGHNUTLWSsyS9KoeuOXFOWht7+iEj/eNb +U8bA29oOnVyb52o5OSx/E/NxqV7z0tv+LL8LTs4iXo4DwfrYB6BRwM2Qwzma4tmp +ZG2uvwwq/xIStMSfUIIOIUeZWLUs2V8d0mUslQIDAQABAoIBAQDcr81T+VPLmpRG +ec01j0tfVdHzMQCO1a9OIECn4qyrvYleUxyuHQCqgoO4PJWw/uwPLLc+q7ctC3rH +Skvs7XPnZZGonHkxqNFy0I0HnYCKY78XNGlgv+LHjknCJg52lxU/x/uLpI0gpS5z +qGStiaCq1bvLJAyuotCjMTQkvKVTBgHm70lC3ZNt4I8OaocyfGF4BJqbUxyjXFKz +sYxgAXssAdMPqKsSH5du2ii4YyAZvzYO+RJ4OByPaZZA3OfsKddsnGDFPqfw3+Aa +8QBCFbf/uGUepOL0P4rWDp7XhHDwCJS/kiP3qTUZ37dh660arIeXCN4qcI6ZUjMo +nJp8tjvBAoGBAP9huBC79WWid9CYskCO9m+zIk4RTe07FNQW46Kxpva9lULqmSj+ +MpB47kSvjCNnVYdn0FOxi3ipsTeM7R0+fZz1LM5vdwRWc7pZ5U3HvrB2zBCaftSL +gWlvxup02eXR2TrPwNzKd1iR4GoPlLXc8FxMSZLHzcxFZYWh42RAKV8NAoGBAOOM +9ubZVSW8GdviZobFalNG7HhAn06U3LPrN71vxFdE3tKePLxNI/RNDbUHo/ji5muV +Hx45uvIFT36BtH74DgiORHjLYdTaOF+TVUJEHEmPkBGPj4kdLu9MRAEYba7lBvbw +rvv1NI49GEUB1NhzNoYaHdyw98afgq3mO7C59+GpAoGAKykUtp9NhfPDVBm6ZnbC +53Xa4l7CUMmfZ4jGyARGN4Uq6LhFUkxDt274tdsFUCZyqgO9jad/7tCfBshmen7J +M/GrtOfZsX366Q+wVUjgzWoGavfxX3KEWJFviMhe2xxwHiRmb+o36VweFU60z1eb +t72c4ZWrDk7cLY2CpLrDQzECgYEAuQDr4GZEfwh+sbJRjzNELe7U5TQNEZOgGLud +tuv+4sEAtJaoZKQHHmHjShKrMvgvRLqZ8TYYkxrUNdN4j15X8obQjrE0mhzNho8/ +2z+LDBenl09z8JOEjFQiWg2lZ3OXhP/MFNeYYiiz6a8CgPSzGLZ1Hu/Wk2sukPpF +/3pDWfkCgYBIpMV122EYgKqfk7yayJUDez69X+/JmUR4mBtkI9CZoYM8xrft7Coi +VzsjRc+2yS21ZFRuB4g4xO2ViEVLqM8/g2tFDuuUkGb+vdBsolx3j64xYqoBs20T +0JjnFaxl2MCpn2y44bJ9ht134q5/Mm762rPyNuqdm4R7rhAi7g/7+w== +-----END RSA PRIVATE KEY----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_certificate.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_certificate.pem new file mode 100644 index 000000000..55b0703be --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_certificate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDADCCAeigAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH +ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xODAyMjcyMjI5 +MjBaFw0yODAyMjUyMjI5MjBaMCUxEjAQBgNVBAMMCWxvY2FsaG9zdDEPMA0GA1UE +CgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA14xcFsbL +bhw2qunzyhzBXQs5xxi+DQKqK9CjVVEmBrUVR2qwzS2P3IFdkVzegRa9jZgsgVSI +TrbsCgF2xkTy0Bsz5sbasaFVQQHDI8PjZ82vmK5MAXyUzR3HQ4iBzyNIpmSNhKHK +qWQuXhqN/D1wgkl08/B0xlx+jaFDIy+hdbuLpB5+NXgHbKWqYq1jrKomnZucKbm7 +dahXvbomshF6fe6xWNU4CxCaC7+snym3Zug6kkXSgI5NEYQzh93RjTXbV8m4q8Az +8nwWZ3vWcIdtcYURHZ+wP/DdGQmObItZjVrTmQ6OI9750MCtGfDXv+IX/Iipcp7W +0OYkNPPPJCowMwIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNV +HSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAcqduPpka+3O29X3U +BGKAdKGJjSoqHrn+K7QbQ9A/BbCJC9htZ8cUtmVgRm0Aw82x94zMREG+YF7jQhXM +qSoYVxLbqPWxMCRx2FmQihByHvGusCKfu2yBAHo6rBfFWitc2KHVQs2Tyku65bsa +3WnyRkRBW92UBdzM6aEjFeqzzDQaBezHT8DkKjj7NFsxI+lKuopInJWs1SckMmcT +yWat5d7hDTiNwObjaD9vsKH4/k11ftksBmmxvd+F/AIL1f4leU5hBGcfmoHyjyBU +FQlbOimj/YsGHB09ClH1/avqBqoHEVMzK61o7aZstDKSs7RIX2Iip1erhgC8YN8C +dkFM6g== +-----END CERTIFICATE----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_key.pem b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_key.pem new file mode 100644 index 000000000..b65df5094 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/certs/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA14xcFsbLbhw2qunzyhzBXQs5xxi+DQKqK9CjVVEmBrUVR2qw +zS2P3IFdkVzegRa9jZgsgVSITrbsCgF2xkTy0Bsz5sbasaFVQQHDI8PjZ82vmK5M +AXyUzR3HQ4iBzyNIpmSNhKHKqWQuXhqN/D1wgkl08/B0xlx+jaFDIy+hdbuLpB5+ +NXgHbKWqYq1jrKomnZucKbm7dahXvbomshF6fe6xWNU4CxCaC7+snym3Zug6kkXS +gI5NEYQzh93RjTXbV8m4q8Az8nwWZ3vWcIdtcYURHZ+wP/DdGQmObItZjVrTmQ6O +I9750MCtGfDXv+IX/Iipcp7W0OYkNPPPJCowMwIDAQABAoIBAEgT1Ht9UGtqndE7 +WF1ejj5p0ZFFMdAx8uuh4exWrWuSxCHUiHij4rZuv/Yq4vCxxQMDPuakeMwxmO6j +nK8iQTvbzNg/6MN7WoHZcAKWr4V3n19j8WCR40RPz+FCVX4Jo3KYr0YJwDYynNq0 +8IPHm8bFf4lPAA2QwnTj1wp+E/TE+/48c+p5YiVpZZSv9LNvErUtWSXO7atbKA0+ +IlmfipdzC+u+8NW9a9CX5uM5UB05TLydsbnpwCsxptuPJqIuOO3oI6ByL6QJsqU1 +mk7/s5Vofb4JJna1xgo1NigsQBJf2NQCDDi5C0D6tnBPnZGBPp1QWbUp6xnzSEd8 +BMqxXQkCgYEA8RMCAX7Ol6akHSxZZDnDSyxHAdgERhwsSLBBqAUjn4iOEaP+KIjY +qElqT+vJSiHls1O0vKySotNH0UbOg6CmogRSat7CoOCzS0J+XLjqmPh+4VGdTU7q +/UKV60YzJA/LZBdZF/dNByN5lKEQVMxQRHglwvRM/kK5NXvyFONCWvcCgYEA5OTE +2c3hlRMq7XSAoNj3NarrazexeCxTcvuwdDYzHMWsp4YUpnvrUacAhoTbzYfPv9Gh +ujb0bubZRAUcOMzPk3V+UXk8g2ziZy/f2FqLx6Uq7Fp9YwZfuzplScUpVAfA24zv +qvcowaBA1FVvSMC45kJo3o5B9LuQuZAbOZvMKaUCgYEApyq8GFdbYNOgN86aCiL3 +5nfEoWWuyQMePiVi0eUGZ8jkYx0pz+fc/Q0zmEnzYeGRi8F+sdqlMB18ToVgDOxo +wC5pDEx9/9rw2T45q4havUqLiSj0ADi6QHZcyTH7ooUFT9nU4QaOtmWGGGd7kKHB +A0mhPcf0X9fa4FibbJqOV88CgYEAjQ01CYtH0hfnwkCi63wIizfyrzW41XdrTVYw +nMyxnq9qACouG5INp1mkh0DkOrnQmkJSyXAIHTeA99u2UoJUAGjNGOP/GHZG5pOn ++6mArdzooJH65sUMxVHtDRLErxXAEQu+vbplkTxx9udXFpw81Rhji5JarrfPLarS +PCP4IkkCgYBDHG+sNpdPridbD9paTFg53sG0zu0Yv66vubFJvWJSxMNHsTJsyaZv +R5K+VTL4+l/4WkbJRk8pR4SWtETCsPSXxUgnoJ99GtDSxX2J+LmzkiyR5N8k14st +6rtIxC259xAEYYAsu0pXly33pMlZUr2aIGykQOSnMIhwdE1PvWkSmg== +-----END RSA PRIVATE KEY----- diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/rabbitmq.conf.in b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/rabbitmq.conf.in new file mode 100644 index 000000000..fb6e64afb --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/rabbitmq.conf.in @@ -0,0 +1,13 @@ +listeners.tcp.default = 5672 +listeners.ssl.default = 5671 +num_acceptors.tcp = 10 +num_acceptors.ssl = 10 +reverse_dns_lookups = false +loopback_users.guest = true +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = true +ssl_options.cacertfile = PIKA_DIR/testdata/certs/ca_certificate.pem +ssl_options.certfile = PIKA_DIR/testdata/certs/server_certificate.pem +ssl_options.keyfile = PIKA_DIR/testdata/certs/server_key.pem +log.console = false +log.console.level = debug diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-epmd.ps1 b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-epmd.ps1 new file mode 100644 index 000000000..8971914c8 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-epmd.ps1 @@ -0,0 +1,21 @@ +$running = $false +[int]$count = 1 + +$epmd = [System.IO.Path]::Combine($env:ERLANG_HOME, $env:erlang_erts_version, "bin", "epmd.exe") + +Do { + $running = & $epmd -names | Select-String -CaseSensitive -SimpleMatch -Quiet -Pattern 'name rabbit at port 25672' + if ($running -eq $true) { + Write-Host '[INFO] epmd reports that RabbitMQ is at port 25672' + break + } + + if ($count -gt 120) { + throw '[ERROR] too many tries waiting for epmd to report RabbitMQ on port 25672' + } + + Write-Host "[INFO] epmd NOT reporting yet that RabbitMQ is at port 25672, count: $count" + $count = $count + 1 + Start-Sleep -Seconds 5 + +} While ($true) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-rabbitmq.ps1 b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-rabbitmq.ps1 new file mode 100644 index 000000000..1f602c307 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/testdata/wait-rabbitmq.ps1 @@ -0,0 +1,21 @@ +[int]$count = 1 + +Do { + $proc_id = (Get-Process -Name erl).Id + if (-Not ($proc_id -is [array])) { + & "C:\Program Files\RabbitMQ Server\rabbitmq_server-$env:rabbitmq_version\sbin\rabbitmqctl.bat" wait -t 300000 -P $proc_id + if ($LASTEXITCODE -ne 0) { + throw "[ERROR] rabbitmqctl wait returned error: $LASTEXITCODE" + } + break + } + + if ($count -gt 120) { + throw '[ERROR] too many tries waiting for just one erl process to be running' + } + + Write-Host '[INFO] multiple erl instances running still' + $count = $count + 1 + Start-Sleep -Seconds 5 + +} While ($true) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_adapter_tests.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_adapter_tests.py new file mode 100644 index 000000000..b9946e10b --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_adapter_tests.py @@ -0,0 +1,587 @@ +# Suppress pylint messages concerning missing class and method docstrings +# pylint: disable=C0111 + +# Suppress pylint warning about attribute defined outside __init__ +# pylint: disable=W0201 + +# Suppress pylint warning about access to protected member +# pylint: disable=W0212 + +# Suppress pylint warning about unused argument +# pylint: disable=W0613 + +import functools +import threading +import time +import uuid + +from pika import spec +from pika.compat import as_bytes +import pika.connection +import pika.frame +import pika.spec + +from async_test_base import (AsyncTestCase, BoundQueueTestCase, AsyncAdapters) + + +class TestA_Connect(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Connect, open channel and disconnect" + + def begin(self, channel): + self.stop() + + +class TestConfirmSelect(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Receive confirmation of Confirm.Select" + + def begin(self, channel): + channel._on_selectok = self.on_complete + channel.confirm_delivery() + + def on_complete(self, frame): + self.assertIsInstance(frame.method, spec.Confirm.SelectOk) + self.stop() + + +class TestBlockingNonBlockingBlockingRPCWontStall(AsyncTestCase, AsyncAdapters): + DESCRIPTION = ("Verify that a sequence of blocking, non-blocking, blocking " + "RPC requests won't stall") + + def begin(self, channel): + # Queue declaration params table: queue name, nowait value + self._expected_queue_params = ( + ("blocking-non-blocking-stall-check-" + uuid.uuid1().hex, False), + ("blocking-non-blocking-stall-check-" + uuid.uuid1().hex, True), + ("blocking-non-blocking-stall-check-" + uuid.uuid1().hex, False) + ) + + self._declared_queue_names = [] + + for queue, nowait in self._expected_queue_params: + channel.queue_declare(callback=self._queue_declare_ok_cb + if not nowait else None, + queue=queue, + auto_delete=True, + nowait=nowait, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def _queue_declare_ok_cb(self, declare_ok_frame): + self._declared_queue_names.append(declare_ok_frame.method.queue) + + if len(self._declared_queue_names) == 2: + # Initiate check for creation of queue declared with nowait=True + self.channel.queue_declare(callback=self._queue_declare_ok_cb, + queue=self._expected_queue_params[1][0], + passive=True, + nowait=False) + elif len(self._declared_queue_names) == 3: + self.assertSequenceEqual( + sorted(self._declared_queue_names), + sorted(item[0] for item in self._expected_queue_params)) + self.stop() + + +class TestConsumeCancel(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Consume and cancel" + + def begin(self, channel): + self.queue_name = self.__class__.__name__ + ':' + uuid.uuid1().hex + channel.queue_declare(self.on_queue_declared, queue=self.queue_name) + + def on_queue_declared(self, frame): + for i in range(0, 100): + msg_body = '{}:{}:{}'.format(self.__class__.__name__, i, + time.time()) + self.channel.basic_publish('', self.queue_name, msg_body) + self.ctag = self.channel.basic_consume(self.on_message, + queue=self.queue_name, + no_ack=True) + + def on_message(self, _channel, _frame, _header, body): + self.channel.basic_cancel(self.on_cancel, self.ctag) + + def on_cancel(self, _frame): + self.channel.queue_delete(self.on_deleted, self.queue_name) + + def on_deleted(self, _frame): + self.stop() + + +class TestExchangeDeclareAndDelete(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Create and delete and exchange" + + X_TYPE = 'direct' + + def begin(self, channel): + self.name = self.__class__.__name__ + ':' + uuid.uuid1().hex + channel.exchange_declare(self.on_exchange_declared, self.name, + exchange_type=self.X_TYPE, + passive=False, + durable=False, + auto_delete=True) + + def on_exchange_declared(self, frame): + self.assertIsInstance(frame.method, spec.Exchange.DeclareOk) + self.channel.exchange_delete(self.on_exchange_delete, self.name) + + def on_exchange_delete(self, frame): + self.assertIsInstance(frame.method, spec.Exchange.DeleteOk) + self.stop() + + +class TestExchangeRedeclareWithDifferentValues(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "should close chan: re-declared exchange w/ diff params" + + X_TYPE1 = 'direct' + X_TYPE2 = 'topic' + + def begin(self, channel): + self.name = self.__class__.__name__ + ':' + uuid.uuid1().hex + self.channel.add_on_close_callback(self.on_channel_closed) + channel.exchange_declare(self.on_exchange_declared, self.name, + exchange_type=self.X_TYPE1, + passive=False, + durable=False, + auto_delete=True) + + def on_cleanup_channel(self, channel): + channel.exchange_delete(None, self.name, nowait=True) + self.stop() + + def on_channel_closed(self, channel, reply_code, reply_text): + self.connection.channel(self.on_cleanup_channel) + + def on_exchange_declared(self, frame): + self.channel.exchange_declare(self.on_bad_result, self.name, + exchange_type=self.X_TYPE2, + passive=False, + durable=False, + auto_delete=True) + + def on_bad_result(self, frame): + self.channel.exchange_delete(None, self.name, nowait=True) + raise AssertionError("Should not have received a Queue.DeclareOk") + + +class TestQueueDeclareAndDelete(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Create and delete a queue" + + def begin(self, channel): + channel.queue_declare(self.on_queue_declared, + passive=False, + durable=False, + exclusive=True, + auto_delete=False, + nowait=False, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def on_queue_declared(self, frame): + self.assertIsInstance(frame.method, spec.Queue.DeclareOk) + self.channel.queue_delete(self.on_queue_delete, frame.method.queue) + + def on_queue_delete(self, frame): + self.assertIsInstance(frame.method, spec.Queue.DeleteOk) + self.stop() + + + +class TestQueueNameDeclareAndDelete(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Create and delete a named queue" + + def begin(self, channel): + self._q_name = self.__class__.__name__ + ':' + uuid.uuid1().hex + channel.queue_declare(self.on_queue_declared, self._q_name, + passive=False, + durable=False, + exclusive=True, + auto_delete=True, + nowait=False, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def on_queue_declared(self, frame): + self.assertIsInstance(frame.method, spec.Queue.DeclareOk) + # Frame's method's queue is encoded (impl detail) + self.assertEqual(frame.method.queue, self._q_name) + self.channel.queue_delete(self.on_queue_delete, frame.method.queue) + + def on_queue_delete(self, frame): + self.assertIsInstance(frame.method, spec.Queue.DeleteOk) + self.stop() + + + +class TestQueueRedeclareWithDifferentValues(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Should close chan: re-declared queue w/ diff params" + + def begin(self, channel): + self._q_name = self.__class__.__name__ + ':' + uuid.uuid1().hex + self.channel.add_on_close_callback(self.on_channel_closed) + channel.queue_declare(self.on_queue_declared, self._q_name, + passive=False, + durable=False, + exclusive=True, + auto_delete=True, + nowait=False, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def on_channel_closed(self, channel, reply_code, reply_text): + self.stop() + + def on_queue_declared(self, frame): + self.channel.queue_declare(self.on_bad_result, self._q_name, + passive=False, + durable=True, + exclusive=False, + auto_delete=True, + nowait=False, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def on_bad_result(self, frame): + self.channel.queue_delete(None, self._q_name, nowait=True) + raise AssertionError("Should not have received a Queue.DeclareOk") + + + +class TestTX1_Select(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Receive confirmation of Tx.Select" + + def begin(self, channel): + channel.tx_select(self.on_complete) + + def on_complete(self, frame): + self.assertIsInstance(frame.method, spec.Tx.SelectOk) + self.stop() + + + +class TestTX2_Commit(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Start a transaction, and commit it" + + def begin(self, channel): + channel.tx_select(self.on_selectok) + + def on_selectok(self, frame): + self.assertIsInstance(frame.method, spec.Tx.SelectOk) + self.channel.tx_commit(self.on_commitok) + + def on_commitok(self, frame): + self.assertIsInstance(frame.method, spec.Tx.CommitOk) + self.stop() + + +class TestTX2_CommitFailure(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Close the channel: commit without a TX" + + def begin(self, channel): + self.channel.add_on_close_callback(self.on_channel_closed) + self.channel.tx_commit(self.on_commitok) + + def on_channel_closed(self, channel, reply_code, reply_text): + self.stop() + + def on_selectok(self, frame): + self.assertIsInstance(frame.method, spec.Tx.SelectOk) + + @staticmethod + def on_commitok(frame): + raise AssertionError("Should not have received a Tx.CommitOk") + + +class TestTX3_Rollback(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Start a transaction, then rollback" + + def begin(self, channel): + channel.tx_select(self.on_selectok) + + def on_selectok(self, frame): + self.assertIsInstance(frame.method, spec.Tx.SelectOk) + self.channel.tx_rollback(self.on_rollbackok) + + def on_rollbackok(self, frame): + self.assertIsInstance(frame.method, spec.Tx.RollbackOk) + self.stop() + + + +class TestTX3_RollbackFailure(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Close the channel: rollback without a TX" + + def begin(self, channel): + self.channel.add_on_close_callback(self.on_channel_closed) + self.channel.tx_rollback(self.on_commitok) + + def on_channel_closed(self, channel, reply_code, reply_text): + self.stop() + + @staticmethod + def on_commitok(frame): + raise AssertionError("Should not have received a Tx.RollbackOk") + + +class TestZ_PublishAndConsume(BoundQueueTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Publish a message and consume it" + + def on_ready(self, frame): + self.ctag = self.channel.basic_consume(self.on_message, self.queue) + self.msg_body = "%s: %i" % (self.__class__.__name__, time.time()) + self.channel.basic_publish(self.exchange, self.routing_key, + self.msg_body) + + def on_cancelled(self, frame): + self.assertIsInstance(frame.method, spec.Basic.CancelOk) + self.stop() + + def on_message(self, channel, method, header, body): + self.assertIsInstance(method, spec.Basic.Deliver) + self.assertEqual(body, as_bytes(self.msg_body)) + self.channel.basic_ack(method.delivery_tag) + self.channel.basic_cancel(self.on_cancelled, self.ctag) + + + +class TestZ_PublishAndConsumeBig(BoundQueueTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Publish a big message and consume it" + + @staticmethod + def _get_msg_body(): + return '\n'.join(["%s" % i for i in range(0, 2097152)]) + + def on_ready(self, frame): + self.ctag = self.channel.basic_consume(self.on_message, self.queue) + self.msg_body = self._get_msg_body() + self.channel.basic_publish(self.exchange, self.routing_key, + self.msg_body) + + def on_cancelled(self, frame): + self.assertIsInstance(frame.method, spec.Basic.CancelOk) + self.stop() + + def on_message(self, channel, method, header, body): + self.assertIsInstance(method, spec.Basic.Deliver) + self.assertEqual(body, as_bytes(self.msg_body)) + self.channel.basic_ack(method.delivery_tag) + self.channel.basic_cancel(self.on_cancelled, self.ctag) + + +class TestZ_PublishAndGet(BoundQueueTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Publish a message and get it" + + def on_ready(self, frame): + self.msg_body = "%s: %i" % (self.__class__.__name__, time.time()) + self.channel.basic_publish(self.exchange, self.routing_key, + self.msg_body) + self.channel.basic_get(self.on_get, self.queue) + + def on_get(self, channel, method, header, body): + self.assertIsInstance(method, spec.Basic.GetOk) + self.assertEqual(body, as_bytes(self.msg_body)) + self.channel.basic_ack(method.delivery_tag) + self.stop() + + +class TestZ_AccessDenied(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Verify that access denied invokes on open error callback" + + def start(self, *args, **kwargs): + self.parameters.virtual_host = str(uuid.uuid4()) + self.error_captured = False + super(TestZ_AccessDenied, self).start(*args, **kwargs) + self.assertTrue(self.error_captured) + + def on_open_error(self, connection, error): + self.error_captured = True + self.stop() + + def on_open(self, connection): + super(TestZ_AccessDenied, self).on_open(connection) + self.stop() + + +class TestBlockedConnectionTimesOut(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Verify that blocked connection terminates on timeout" + + def start(self, *args, **kwargs): + self.parameters.blocked_connection_timeout = 0.001 + self.on_closed_pair = None + super(TestBlockedConnectionTimesOut, self).start(*args, **kwargs) + self.assertEqual( + self.on_closed_pair, + (pika.connection.InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT, + 'Blocked connection timeout expired')) + + def begin(self, channel): + + # Simulate Connection.Blocked + channel.connection._on_connection_blocked(pika.frame.Method( + 0, + pika.spec.Connection.Blocked('Testing blocked connection timeout'))) + + def on_closed(self, connection, reply_code, reply_text): + """called when the connection has finished closing""" + self.on_closed_pair = (reply_code, reply_text) + super(TestBlockedConnectionTimesOut, self).on_closed(connection, + reply_code, + reply_text) + + +class TestBlockedConnectionUnblocks(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Verify that blocked-unblocked connection closes normally" + + def start(self, *args, **kwargs): + self.parameters.blocked_connection_timeout = 0.001 + self.on_closed_pair = None + super(TestBlockedConnectionUnblocks, self).start(*args, **kwargs) + self.assertEqual( + self.on_closed_pair, + (200, 'Normal shutdown')) + + def begin(self, channel): + + # Simulate Connection.Blocked + channel.connection._on_connection_blocked(pika.frame.Method( + 0, + pika.spec.Connection.Blocked( + 'Testing blocked connection unblocks'))) + + # Simulate Connection.Unblocked + channel.connection._on_connection_unblocked(pika.frame.Method( + 0, + pika.spec.Connection.Unblocked())) + + # Schedule shutdown after blocked connection timeout would expire + channel.connection.add_timeout(0.005, self.on_cleanup_timer) + + def on_cleanup_timer(self): + self.stop() + + def on_closed(self, connection, reply_code, reply_text): + """called when the connection has finished closing""" + self.on_closed_pair = (reply_code, reply_text) + super(TestBlockedConnectionUnblocks, self).on_closed(connection, + reply_code, + reply_text) + + +class TestAddCallbackThreadsafeRequestBeforeIOLoopStarts(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Test add_callback_threadsafe request before ioloop starts." + + def _run_ioloop(self, *args, **kwargs): # pylint: disable=W0221 + """We intercept this method from AsyncTestCase in order to call + add_callback_threadsafe before AsyncTestCase starts the ioloop. + + """ + self.my_start_time = time.time() + # Request a callback from our current (ioloop's) thread + self.connection.add_callback_threadsafe(self.on_requested_callback) + + return super( + TestAddCallbackThreadsafeRequestBeforeIOLoopStarts, self)._run_ioloop( + *args, **kwargs) + + def start(self, *args, **kwargs): # pylint: disable=W0221 + self.loop_thread_ident = threading.current_thread().ident + self.my_start_time = None + self.got_callback = False + super(TestAddCallbackThreadsafeRequestBeforeIOLoopStarts, self).start(*args, **kwargs) + self.assertTrue(self.got_callback) + + def begin(self, channel): + self.stop() + + def on_requested_callback(self): + self.assertEqual(threading.current_thread().ident, + self.loop_thread_ident) + self.assertLess(time.time() - self.my_start_time, 0.25) + self.got_callback = True + + +class TestAddCallbackThreadsafeFromIOLoopThread(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Test add_callback_threadsafe request from same thread." + + def start(self, *args, **kwargs): + self.loop_thread_ident = threading.current_thread().ident + self.my_start_time = None + self.got_callback = False + super(TestAddCallbackThreadsafeFromIOLoopThread, self).start(*args, **kwargs) + self.assertTrue(self.got_callback) + + def begin(self, channel): + self.my_start_time = time.time() + # Request a callback from our current (ioloop's) thread + channel.connection.add_callback_threadsafe(self.on_requested_callback) + + def on_requested_callback(self): + self.assertEqual(threading.current_thread().ident, + self.loop_thread_ident) + self.assertLess(time.time() - self.my_start_time, 0.25) + self.got_callback = True + self.stop() + + +class TestAddCallbackThreadsafeFromAnotherThread(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Test add_callback_threadsafe request from another thread." + + def start(self, *args, **kwargs): + self.loop_thread_ident = threading.current_thread().ident + self.my_start_time = None + self.got_callback = False + super(TestAddCallbackThreadsafeFromAnotherThread, self).start(*args, **kwargs) + self.assertTrue(self.got_callback) + + def begin(self, channel): + self.my_start_time = time.time() + # Request a callback from ioloop while executing in another thread + timer = threading.Timer( + 0, + lambda: channel.connection.add_callback_threadsafe( + self.on_requested_callback)) + self.addCleanup(timer.cancel) + timer.start() + + def on_requested_callback(self): + self.assertEqual(threading.current_thread().ident, + self.loop_thread_ident) + self.assertLess(time.time() - self.my_start_time, 0.25) + self.got_callback = True + self.stop() + + +class TestIOLoopStopBeforeIOLoopStarts(AsyncTestCase, AsyncAdapters): + DESCRIPTION = "Test ioloop.stop() before ioloop starts causes ioloop to exit quickly." + + def _run_ioloop(self, *args, **kwargs): # pylint: disable=W0221 + """We intercept this method from AsyncTestCase in order to call + ioloop.stop() before AsyncTestCase starts the ioloop. + """ + # Request ioloop to stop before it starts + self.my_start_time = time.time() + self.stop_ioloop_only() + + return super( + TestIOLoopStopBeforeIOLoopStarts, self)._run_ioloop(*args, **kwargs) + + def start(self, *args, **kwargs): # pylint: disable=W0221 + self.loop_thread_ident = threading.current_thread().ident + self.my_start_time = None + super(TestIOLoopStopBeforeIOLoopStarts, self).start(*args, **kwargs) + self.assertLess(time.time() - self.my_start_time, 0.25) + + def begin(self, channel): + pass + + +class TestViabilityOfMultipleTimeoutsWithSameDeadlineAndCallback(AsyncTestCase, AsyncAdapters): # pylint: disable=C0103 + DESCRIPTION = "Test viability of multiple timeouts with same deadline and callback" + + def begin(self, channel): + timer1 = channel.connection.add_timeout(0, self.on_my_timer) + timer2 = channel.connection.add_timeout(0, self.on_my_timer) + + self.assertIsNot(timer1, timer2) + + channel.connection.remove_timeout(timer1) + + # Wait for timer2 to fire + + def on_my_timer(self): + self.stop() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_test_base.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_test_base.py new file mode 100644 index 000000000..bbe8ec63f --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/async_test_base.py @@ -0,0 +1,247 @@ +# Suppress pylint warnings concerning attribute defined outside __init__ +# pylint: disable=W0201 + +# Suppress pylint messages concerning missing docstrings +# pylint: disable=C0111 + +import datetime +import os +import select +import ssl +import sys +import logging +import unittest +import uuid + +try: + from unittest import mock +except ImportError: + import mock + + +import pika +from pika import adapters +from pika.adapters import select_connection + + +def enable_tls(): + if 'PIKA_TEST_TLS' in os.environ and \ + os.environ['PIKA_TEST_TLS'].lower() == 'true': + return True + return False + + +class AsyncTestCase(unittest.TestCase): + DESCRIPTION = "" + ADAPTER = None + TIMEOUT = 15 + + def setUp(self): + self.logger = logging.getLogger(self.__class__.__name__) + if enable_tls(): + self.logger.info('testing using TLS/SSL connection to port 5671') + url = 'amqps://localhost:5671/%2F?ssl_options=%7B%27ca_certs%27%3A%27testdata%2Fcerts%2Fca_certificate.pem%27%2C%27keyfile%27%3A%27testdata%2Fcerts%2Fclient_key.pem%27%2C%27certfile%27%3A%27testdata%2Fcerts%2Fclient_certificate.pem%27%7D' + self.parameters = pika.URLParameters(url) + else: + self.parameters = pika.ConnectionParameters(host='localhost', port=5672) + self._timed_out = False + super(AsyncTestCase, self).setUp() + + def tearDown(self): + self._stop() + + def shortDescription(self): + method_desc = super(AsyncTestCase, self).shortDescription() + if self.DESCRIPTION: + return "%s (%s)" % (self.DESCRIPTION, method_desc) + else: + return method_desc + + def begin(self, channel): # pylint: disable=R0201,W0613 + """Extend to start the actual tests on the channel""" + self.fail("AsyncTestCase.begin_test not extended") + + def start(self, adapter, ioloop_factory): + self.logger.info('start at %s', datetime.datetime.utcnow()) + self.adapter = adapter or self.ADAPTER + + self.connection = self.adapter(self.parameters, + self.on_open, + self.on_open_error, + self.on_closed, + custom_ioloop=ioloop_factory()) + try: + self.timeout = self.connection.add_timeout(self.TIMEOUT, + self.on_timeout) + self._run_ioloop() + self.assertFalse(self._timed_out) + finally: + self.connection.ioloop.close() + self.connection = None + + def stop_ioloop_only(self): + """Request stopping of the connection's ioloop to end the test without + closing the connection + """ + self._safe_remove_test_timeout() + self.connection.ioloop.stop() + + def stop(self): + """close the connection and stop the ioloop""" + self.logger.info("Stopping test") + self._safe_remove_test_timeout() + self.connection.close() # NOTE: on_closed() will stop the ioloop + + def _run_ioloop(self): + """Some tests need to subclass this in order to bootstrap their test + logic after we instantiate the connection and assign it to + `self.connection`, but before we run the ioloop + """ + self.connection.ioloop.start() + + def _safe_remove_test_timeout(self): + if hasattr(self, 'timeout') and self.timeout is not None: + self.logger.info("Removing timeout") + if hasattr(self, 'connection') and self.connection is not None: + self.connection.remove_timeout(self.timeout) + self.timeout = None + + def _stop(self): + self._safe_remove_test_timeout() + if hasattr(self, 'connection') and self.connection is not None: + self.logger.info("Stopping ioloop") + self.connection.ioloop.stop() + + def on_closed(self, connection, reply_code, reply_text): + """called when the connection has finished closing""" + self.logger.info('on_closed: %r %r %r', connection, + reply_code, reply_text) + self._stop() + + def on_open(self, connection): + self.logger.debug('on_open: %r', connection) + self.channel = connection.channel(self.begin) + + def on_open_error(self, connection, error): + self.logger.error('on_open_error: %r %r', connection, error) + connection.ioloop.stop() + raise AssertionError('Error connecting to RabbitMQ') + + def on_timeout(self): + """called when stuck waiting for connection to close""" + self.logger.error('%s timed out; on_timeout called at %s', + self, datetime.datetime.utcnow()) + self.timeout = None # the dispatcher should have removed it + self._timed_out = True + # initiate cleanup + self.stop() + + +class BoundQueueTestCase(AsyncTestCase): + + def start(self, adapter, ioloop_factory): + # PY3 compat encoding + self.exchange = 'e-' + self.__class__.__name__ + ':' + uuid.uuid1().hex + self.queue = 'q-' + self.__class__.__name__ + ':' + uuid.uuid1().hex + self.routing_key = self.__class__.__name__ + super(BoundQueueTestCase, self).start(adapter, ioloop_factory) + + def begin(self, channel): + self.channel.exchange_declare(self.on_exchange_declared, self.exchange, + exchange_type='direct', + passive=False, + durable=False, + auto_delete=True) + + def on_exchange_declared(self, frame): # pylint: disable=W0613 + self.channel.queue_declare(self.on_queue_declared, self.queue, + passive=False, + durable=False, + exclusive=True, + auto_delete=True, + nowait=False, + arguments={'x-expires': self.TIMEOUT * 1000}) + + def on_queue_declared(self, frame): # pylint: disable=W0613 + self.channel.queue_bind(self.on_ready, self.queue, self.exchange, + self.routing_key) + + def on_ready(self, frame): + raise NotImplementedError + + +# +# In order to write test cases that will tested using all the Async Adapters +# write a class that inherits both from one of TestCase classes above and +# from the AsyncAdapters class below. This allows you to avoid duplicating the +# test methods for each adapter in each test class. +# + +class AsyncAdapters(object): + + def start(self, adapter_class, ioloop_factory): + """ + + :param adapter_class: pika connection adapter class to test. + :param ioloop_factory: to be called without args to instantiate a + non-shared ioloop to be passed as the `custom_ioloop` arg to the + `adapter_class` constructor. This is needed because some of the + adapters default to using a singleton ioloop, which results in + tests errors after prior tests close the ioloop to release resources, + in order to eliminate ResourceWarning warnings concerning unclosed + sockets from our adapters. + :return: + """ + raise NotImplementedError + + def select_default_test(self): + """SelectConnection:DefaultPoller""" + with mock.patch.multiple(select_connection, SELECT_TYPE=None): + self.start(adapters.SelectConnection, select_connection.IOLoop) + + def select_select_test(self): + """SelectConnection:select""" + + with mock.patch.multiple(select_connection, SELECT_TYPE='select'): + self.start(adapters.SelectConnection, select_connection.IOLoop) + + @unittest.skipIf( + not hasattr(select, 'poll') or + not hasattr(select.poll(), 'modify'), "poll not supported") # pylint: disable=E1101 + def select_poll_test(self): + """SelectConnection:poll""" + + with mock.patch.multiple(select_connection, SELECT_TYPE='poll'): + self.start(adapters.SelectConnection, select_connection.IOLoop) + + @unittest.skipIf(not hasattr(select, 'epoll'), "epoll not supported") + def select_epoll_test(self): + """SelectConnection:epoll""" + + with mock.patch.multiple(select_connection, SELECT_TYPE='epoll'): + self.start(adapters.SelectConnection, select_connection.IOLoop) + + @unittest.skipIf(not hasattr(select, 'kqueue'), "kqueue not supported") + def select_kqueue_test(self): + """SelectConnection:kqueue""" + + with mock.patch.multiple(select_connection, SELECT_TYPE='kqueue'): + self.start(adapters.SelectConnection, select_connection.IOLoop) + + def tornado_test(self): + """TornadoConnection""" + ioloop_factory = None + if adapters.tornado_connection.TornadoConnection is not None: + import tornado.ioloop + ioloop_factory = tornado.ioloop.IOLoop + self.start(adapters.tornado_connection.TornadoConnection, ioloop_factory) + + @unittest.skipIf(sys.version_info < (3, 4), "Asyncio available for Python 3.4+") + def asyncio_test(self): + """AsyncioConnection""" + ioloop_factory = None + if adapters.asyncio_connection.AsyncioConnection is not None: + import asyncio + ioloop_factory = asyncio.new_event_loop + + self.start(adapters.asyncio_connection.AsyncioConnection, ioloop_factory) diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/blocking_adapter_test.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/blocking_adapter_test.py new file mode 100644 index 000000000..749e812ce --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/blocking_adapter_test.py @@ -0,0 +1,2791 @@ +"""blocking adapter test""" +from datetime import datetime +import functools +import logging +import socket +import threading +import time +import unittest +import uuid + +from forward_server import ForwardServer +from test_utils import retry_assertion + +import pika +from pika.adapters import blocking_connection +from pika.compat import as_bytes +import pika.connection +import pika.exceptions + +# Disable warning about access to protected member +# pylint: disable=W0212 + +# Disable warning Attribute defined outside __init__ +# pylint: disable=W0201 + +# Disable warning Missing docstring +# pylint: disable=C0111 + +# Disable warning Too many public methods +# pylint: disable=R0904 + +# Disable warning Invalid variable name +# pylint: disable=C0103 + + +LOGGER = logging.getLogger(__name__) + +PARAMS_URL_TEMPLATE = ( + 'amqp://guest:guest@127.0.0.1:%(port)s/%%2f?socket_timeout=1') +DEFAULT_URL = PARAMS_URL_TEMPLATE % {'port': 5672} +DEFAULT_PARAMS = pika.URLParameters(DEFAULT_URL) +DEFAULT_TIMEOUT = 15 + + + +def setUpModule(): + logging.basicConfig(level=logging.DEBUG) + + +class BlockingTestCaseBase(unittest.TestCase): + + TIMEOUT = DEFAULT_TIMEOUT + + def _connect(self, + url=DEFAULT_URL, + connection_class=pika.BlockingConnection, + impl_class=None): + parameters = pika.URLParameters(url) + connection = connection_class(parameters, _impl_class=impl_class) + self.addCleanup(lambda: connection.close() + if connection.is_open else None) + + connection._impl.add_timeout( + self.TIMEOUT, # pylint: disable=E1101 + self._on_test_timeout) + + return connection + + def _on_test_timeout(self): + """Called when test times out""" + LOGGER.info('%s TIMED OUT (%s)', datetime.utcnow(), self) + self.fail('Test timed out') + + @retry_assertion(TIMEOUT/2) + def _assert_exact_message_count_with_retries(self, + channel, + queue, + expected_count): + frame = channel.queue_declare(queue, passive=True) + self.assertEqual(frame.method.message_count, expected_count) + + +class TestCreateAndCloseConnection(BlockingTestCaseBase): + + def test(self): + """BlockingConnection: Create and close connection""" + connection = self._connect() + self.assertIsInstance(connection, pika.BlockingConnection) + self.assertTrue(connection.is_open) + self.assertFalse(connection.is_closed) + self.assertFalse(connection.is_closing) + + connection.close() + self.assertTrue(connection.is_closed) + self.assertFalse(connection.is_open) + self.assertFalse(connection.is_closing) + + +class TestMultiCloseConnection(BlockingTestCaseBase): + + def test(self): + """BlockingConnection: Close connection twice""" + connection = self._connect() + self.assertIsInstance(connection, pika.BlockingConnection) + self.assertTrue(connection.is_open) + self.assertFalse(connection.is_closed) + self.assertFalse(connection.is_closing) + + connection.close() + self.assertTrue(connection.is_closed) + self.assertFalse(connection.is_open) + self.assertFalse(connection.is_closing) + + # Second close call shouldn't crash + connection.close() + + +class TestConnectionContextManagerClosesConnection(BlockingTestCaseBase): + def test(self): + """BlockingConnection: connection context manager closes connection""" + with self._connect() as connection: + self.assertIsInstance(connection, pika.BlockingConnection) + self.assertTrue(connection.is_open) + + self.assertTrue(connection.is_closed) + + +class TestConnectionContextManagerClosesConnectionAndPassesOriginalException(BlockingTestCaseBase): + def test(self): + """BlockingConnection: connection context manager closes connection and passes original exception""" # pylint: disable=C0301 + class MyException(Exception): + pass + + with self.assertRaises(MyException): + with self._connect() as connection: + self.assertTrue(connection.is_open) + + raise MyException() + + self.assertTrue(connection.is_closed) + + +class TestConnectionContextManagerClosesConnectionAndPassesSystemException(BlockingTestCaseBase): + def test(self): + """BlockingConnection: connection context manager closes connection and passes system exception""" # pylint: disable=C0301 + with self.assertRaises(SystemExit): + with self._connect() as connection: + self.assertTrue(connection.is_open) + raise SystemExit() + + self.assertTrue(connection.is_closed) + + +class TestLostConnectionResultsInIsClosedConnectionAndChannel(BlockingTestCaseBase): + def test(self): + connection = self._connect() + channel = connection.channel() + + # Simulate the server dropping the socket connection + connection._impl.socket.shutdown(socket.SHUT_RDWR) + + with self.assertRaises(pika.exceptions.ConnectionClosed): + # Changing QoS should result in ConnectionClosed + channel.basic_qos() + + # Now check is_open/is_closed on channel and connection + self.assertFalse(channel.is_open) + self.assertTrue(channel.is_closed) + self.assertFalse(connection.is_open) + self.assertTrue(connection.is_closed) + + +class TestInvalidExchangeTypeRaisesConnectionClosed(BlockingTestCaseBase): + def test(self): + """BlockingConnection: ConnectionClosed raised when creating exchange with invalid type""" # pylint: disable=C0301 + # This test exploits behavior specific to RabbitMQ whereby the broker + # closes the connection if an attempt is made to declare an exchange + # with an invalid exchange type + connection = self._connect() + ch = connection.channel() + + exg_name = ("TestInvalidExchangeTypeRaisesConnectionClosed_" + + uuid.uuid1().hex) + + with self.assertRaises(pika.exceptions.ConnectionClosed) as ex_cm: + # Attempt to create an exchange with invalid exchange type + ch.exchange_declare(exg_name, exchange_type='ZZwwInvalid') + + self.assertEqual(ex_cm.exception.args[0], 503) + + +class TestCreateAndCloseConnectionWithChannelAndConsumer(BlockingTestCaseBase): + + def test(self): + """BlockingConnection: Create and close connection with channel and consumer""" # pylint: disable=C0301 + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestCreateAndCloseConnectionWithChannelAndConsumer_q' + + uuid.uuid1().hex) + + body1 = 'a' * 1024 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish the message to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body1) + + # Create a non-ackable consumer + ch.basic_consume(lambda *x: None, q_name, no_ack=True, + exclusive=False, arguments=None) + + connection.close() + self.assertTrue(connection.is_closed) + self.assertFalse(connection.is_open) + self.assertFalse(connection.is_closing) + + self.assertFalse(connection._impl._channels) + + self.assertFalse(ch._consumer_infos) + self.assertFalse(ch._impl._consumers) + + +class TestSuddenBrokerDisconnectBeforeChannel(BlockingTestCaseBase): + + def test(self): + """BlockingConnection resets properly on TCP/IP drop during channel() + """ + with ForwardServer( + remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), + local_linger_args=(1, 0)) as fwd: + + self.connection = self._connect( + PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}) + + # Once outside the context, the connection is broken + + # BlockingConnection should raise ConnectionClosed + with self.assertRaises(pika.exceptions.ConnectionClosed): + self.connection.channel() + + self.assertTrue(self.connection.is_closed) + self.assertFalse(self.connection.is_open) + self.assertIsNone(self.connection._impl.socket) + + +class TestNoAccessToFileDescriptorAfterConnectionClosed(BlockingTestCaseBase): + + def test(self): + """BlockingConnection no access file descriptor after ConnectionClosed + """ + with ForwardServer( + remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), + local_linger_args=(1, 0)) as fwd: + + self.connection = self._connect( + PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}) + + # Once outside the context, the connection is broken + + # BlockingConnection should raise ConnectionClosed + with self.assertRaises(pika.exceptions.ConnectionClosed): + self.connection.channel() + + self.assertTrue(self.connection.is_closed) + self.assertFalse(self.connection.is_open) + self.assertIsNone(self.connection._impl.socket) + + # Attempt to operate on the connection once again after ConnectionClosed + self.assertIsNone(self.connection._impl.socket) + with self.assertRaises(pika.exceptions.ConnectionClosed): + self.connection.channel() + + +class TestConnectWithDownedBroker(BlockingTestCaseBase): + + def test(self): + """ BlockingConnection to downed broker results in AMQPConnectionError + + """ + # Reserve a port for use in connect + sock = socket.socket() + self.addCleanup(sock.close) + + sock.bind(("127.0.0.1", 0)) + + port = sock.getsockname()[1] + + sock.close() + + with self.assertRaises(pika.exceptions.AMQPConnectionError): + self.connection = self._connect( + PARAMS_URL_TEMPLATE % {"port": port}) + + +class TestDisconnectDuringConnectionStart(BlockingTestCaseBase): + + def test(self): + """ BlockingConnection TCP/IP connection loss in CONNECTION_START + """ + fwd = ForwardServer( + remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), + local_linger_args=(1, 0)) + + fwd.start() + self.addCleanup(lambda: fwd.stop() if fwd.running else None) + + class MySelectConnection(pika.SelectConnection): + assert hasattr(pika.SelectConnection, '_on_connection_start') + + def _on_connection_start(self, *args, **kwargs): + fwd.stop() + return super(MySelectConnection, self)._on_connection_start( + *args, **kwargs) + + with self.assertRaises(pika.exceptions.ProbableAuthenticationError): + self._connect( + PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, + impl_class=MySelectConnection) + + +class TestDisconnectDuringConnectionTune(BlockingTestCaseBase): + + def test(self): + """ BlockingConnection TCP/IP connection loss in CONNECTION_TUNE + """ + fwd = ForwardServer( + remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), + local_linger_args=(1, 0)) + fwd.start() + self.addCleanup(lambda: fwd.stop() if fwd.running else None) + + class MySelectConnection(pika.SelectConnection): + assert hasattr(pika.SelectConnection, '_on_connection_tune') + + def _on_connection_tune(self, *args, **kwargs): + fwd.stop() + return super(MySelectConnection, self)._on_connection_tune( + *args, **kwargs) + + with self.assertRaises(pika.exceptions.ProbableAccessDeniedError): + self._connect( + PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, + impl_class=MySelectConnection) + + +class TestDisconnectDuringConnectionProtocol(BlockingTestCaseBase): + + def test(self): + """ BlockingConnection TCP/IP connection loss in CONNECTION_PROTOCOL + """ + fwd = ForwardServer( + remote_addr=(DEFAULT_PARAMS.host, DEFAULT_PARAMS.port), + local_linger_args=(1, 0)) + + fwd.start() + self.addCleanup(lambda: fwd.stop() if fwd.running else None) + + class MySelectConnection(pika.SelectConnection): + assert hasattr(pika.SelectConnection, '_on_connected') + + def _on_connected(self, *args, **kwargs): + fwd.stop() + return super(MySelectConnection, self)._on_connected( + *args, **kwargs) + + with self.assertRaises(pika.exceptions.IncompatibleProtocolError): + self._connect(PARAMS_URL_TEMPLATE % {"port": fwd.server_address[1]}, + impl_class=MySelectConnection) + + +class TestProcessDataEvents(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.process_data_events""" + connection = self._connect() + + # Try with time_limit=0 + start_time = time.time() + connection.process_data_events(time_limit=0) + elapsed = time.time() - start_time + self.assertLess(elapsed, 0.25) + + # Try with time_limit=0.005 + start_time = time.time() + connection.process_data_events(time_limit=0.005) + elapsed = time.time() - start_time + self.assertGreaterEqual(elapsed, 0.005) + self.assertLess(elapsed, 0.25) + + +class TestConnectionRegisterForBlockAndUnblock(BlockingTestCaseBase): + + def test(self): + """BlockingConnection register for Connection.Blocked/Unblocked""" + connection = self._connect() + + # NOTE: I haven't figured out yet how to coerce RabbitMQ to emit + # Connection.Block and Connection.Unblock from the test, so we'll + # just call the registration functions for now, to make sure that + # registration doesn't crash + + connection.add_on_connection_blocked_callback(lambda frame: None) + + blocked_buffer = [] + evt = blocking_connection._ConnectionBlockedEvt( + lambda f: blocked_buffer.append("blocked"), + pika.frame.Method(1, pika.spec.Connection.Blocked('reason'))) + repr(evt) + evt.dispatch() + self.assertEqual(blocked_buffer, ["blocked"]) + + unblocked_buffer = [] + connection.add_on_connection_unblocked_callback(lambda frame: None) + evt = blocking_connection._ConnectionUnblockedEvt( + lambda f: unblocked_buffer.append("unblocked"), + pika.frame.Method(1, pika.spec.Connection.Unblocked())) + repr(evt) + evt.dispatch() + self.assertEqual(unblocked_buffer, ["unblocked"]) + + +class TestBlockedConnectionTimeout(BlockingTestCaseBase): + + def test(self): + """BlockingConnection Connection.Blocked timeout """ + url = DEFAULT_URL + '&blocked_connection_timeout=0.001' + conn = self._connect(url=url) + + # NOTE: I haven't figured out yet how to coerce RabbitMQ to emit + # Connection.Block and Connection.Unblock from the test, so we'll + # simulate it for now + + # Simulate Connection.Blocked + conn._impl._on_connection_blocked(pika.frame.Method( + 0, + pika.spec.Connection.Blocked('TestBlockedConnectionTimeout'))) + + # Wait for connection teardown + with self.assertRaises(pika.exceptions.ConnectionClosed) as excCtx: + while True: + conn.process_data_events(time_limit=1) + + self.assertEqual( + excCtx.exception.args, + (pika.connection.InternalCloseReasons.BLOCKED_CONNECTION_TIMEOUT, + 'Blocked connection timeout expired')) + + +class TestAddCallbackThreadsafeFromSameThread(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.add_callback_threadsafe from same thread""" + connection = self._connect() + + # Test timer completion + start_time = time.time() + rx_callback = [] + connection.add_callback_threadsafe( + lambda: rx_callback.append(time.time())) + while not rx_callback: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_callback), 1) + elapsed = time.time() - start_time + self.assertLess(elapsed, 0.25) + + +class TestAddCallbackThreadsafeFromAnotherThread(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.add_callback_threadsafe from another thread""" + connection = self._connect() + + # Test timer completion + start_time = time.time() + rx_callback = [] + timer = threading.Timer( + 0, + functools.partial(connection.add_callback_threadsafe, + lambda: rx_callback.append(time.time()))) + self.addCleanup(timer.cancel) + timer.start() + while not rx_callback: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_callback), 1) + elapsed = time.time() - start_time + self.assertLess(elapsed, 0.25) + + +class TestAddTimeoutRemoveTimeout(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.add_timeout and remove_timeout""" + connection = self._connect() + + # Test timer completion + start_time = time.time() + rx_callback = [] + timer_id = connection.add_timeout( + 0.005, + lambda: rx_callback.append(time.time())) + while not rx_callback: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_callback), 1) + elapsed = time.time() - start_time + self.assertLess(elapsed, 0.25) + + # Test removing triggered timeout + connection.remove_timeout(timer_id) + + + # Test aborted timer + rx_callback = [] + timer_id = connection.add_timeout( + 0.001, + lambda: rx_callback.append(time.time())) + connection.remove_timeout(timer_id) + connection.process_data_events(time_limit=0.1) + self.assertFalse(rx_callback) + + # Make sure _TimerEvt repr doesn't crash + evt = blocking_connection._TimerEvt(lambda: None) + repr(evt) + + +class TestViabilityOfMultipleTimeoutsWithSameDeadlineAndCallback(BlockingTestCaseBase): + + def test(self): + """BlockingConnection viability of multiple timeouts with same deadline and callback""" + connection = self._connect() + + rx_callback = [] + + def callback(): + rx_callback.append(1) + + timer1 = connection.add_timeout(0, callback) + timer2 = connection.add_timeout(0, callback) + + self.assertIsNot(timer1, timer2) + + connection.remove_timeout(timer1) + + # Wait for second timer to fire + start_wait_time = time.time() + while not rx_callback and time.time() - start_wait_time < 0.25: + connection.process_data_events(time_limit=0.001) + + self.assertListEqual(rx_callback, [1]) + + +class TestRemoveTimeoutFromTimeoutCallback(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.remove_timeout from timeout callback""" + connection = self._connect() + + # Test timer completion + timer_id1 = connection.add_timeout(5, lambda: 0/0) + + rx_timer2 = [] + def on_timer2(): + connection.remove_timeout(timer_id1) + connection.remove_timeout(timer_id2) + rx_timer2.append(1) + + timer_id2 = connection.add_timeout(0, on_timer2) + + while not rx_timer2: + connection.process_data_events(time_limit=None) + + self.assertIsNone(timer_id1.callback) + self.assertFalse(connection._ready_events) + + +class TestSleep(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.sleep""" + connection = self._connect() + + # Try with duration=0 + start_time = time.time() + connection.sleep(duration=0) + elapsed = time.time() - start_time + self.assertLess(elapsed, 0.25) + + # Try with duration=0.005 + start_time = time.time() + connection.sleep(duration=0.005) + elapsed = time.time() - start_time + self.assertGreaterEqual(elapsed, 0.005) + self.assertLess(elapsed, 0.25) + + +class TestConnectionProperties(BlockingTestCaseBase): + + def test(self): + """Test BlockingConnection properties""" + connection = self._connect() + + self.assertTrue(connection.is_open) + self.assertFalse(connection.is_closing) + self.assertFalse(connection.is_closed) + + self.assertTrue(connection.basic_nack_supported) + self.assertTrue(connection.consumer_cancel_notify_supported) + self.assertTrue(connection.exchange_exchange_bindings_supported) + self.assertTrue(connection.publisher_confirms_supported) + + connection.close() + self.assertFalse(connection.is_open) + self.assertFalse(connection.is_closing) + self.assertTrue(connection.is_closed) + + + +class TestCreateAndCloseChannel(BlockingTestCaseBase): + + def test(self): + """BlockingChannel: Create and close channel""" + connection = self._connect() + + ch = connection.channel() + self.assertIsInstance(ch, blocking_connection.BlockingChannel) + self.assertTrue(ch.is_open) + self.assertFalse(ch.is_closed) + self.assertFalse(ch.is_closing) + self.assertIs(ch.connection, connection) + + ch.close() + self.assertTrue(ch.is_closed) + self.assertFalse(ch.is_open) + self.assertFalse(ch.is_closing) + + +class TestExchangeDeclareAndDelete(BlockingTestCaseBase): + + def test(self): + """BlockingChannel: Test exchange_declare and exchange_delete""" + connection = self._connect() + + ch = connection.channel() + + name = "TestExchangeDeclareAndDelete_" + uuid.uuid1().hex + + # Declare a new exchange + frame = ch.exchange_declare(name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, name) + + self.assertIsInstance(frame.method, pika.spec.Exchange.DeclareOk) + + # Check if it exists by declaring it passively + frame = ch.exchange_declare(name, passive=True) + self.assertIsInstance(frame.method, pika.spec.Exchange.DeclareOk) + + # Delete the exchange + frame = ch.exchange_delete(name) + self.assertIsInstance(frame.method, pika.spec.Exchange.DeleteOk) + + # Verify that it's been deleted + with self.assertRaises(pika.exceptions.ChannelClosed) as cm: + ch.exchange_declare(name, passive=True) + + self.assertEqual(cm.exception.args[0], 404) + + +class TestExchangeBindAndUnbind(BlockingTestCaseBase): + + def test(self): + """BlockingChannel: Test exchange_bind and exchange_unbind""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestExchangeBindAndUnbind_q' + uuid.uuid1().hex + src_exg_name = 'TestExchangeBindAndUnbind_src_exg_' + uuid.uuid1().hex + dest_exg_name = 'TestExchangeBindAndUnbind_dest_exg_' + uuid.uuid1().hex + routing_key = 'TestExchangeBindAndUnbind' + + # Place channel in publisher-acknowledgments mode so that we may test + # whether the queue is reachable by publishing with mandatory=True + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare both exchanges + ch.exchange_declare(src_exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, src_exg_name) + ch.exchange_declare(dest_exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, dest_exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the destination exchange + ch.queue_bind(q_name, exchange=dest_exg_name, routing_key=routing_key) + + + # Verify that the queue is unreachable without exchange-exchange binding + with self.assertRaises(pika.exceptions.UnroutableError): + ch.publish(src_exg_name, routing_key, body='', mandatory=True) + + # Bind the exchanges + frame = ch.exchange_bind(destination=dest_exg_name, source=src_exg_name, + routing_key=routing_key) + self.assertIsInstance(frame.method, pika.spec.Exchange.BindOk) + + # Publish a message via the source exchange + ch.publish(src_exg_name, routing_key, body='TestExchangeBindAndUnbind', + mandatory=True) + + # Check that the queue now has one message + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=1) + + # Unbind the exchanges + frame = ch.exchange_unbind(destination=dest_exg_name, + source=src_exg_name, + routing_key=routing_key) + self.assertIsInstance(frame.method, pika.spec.Exchange.UnbindOk) + + # Verify that the queue is now unreachable via the source exchange + with self.assertRaises(pika.exceptions.UnroutableError): + ch.publish(src_exg_name, routing_key, body='', mandatory=True) + + +class TestQueueDeclareAndDelete(BlockingTestCaseBase): + + def test(self): + """BlockingChannel: Test queue_declare and queue_delete""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestQueueDeclareAndDelete_' + uuid.uuid1().hex + + # Declare a new queue + frame = ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + self.assertIsInstance(frame.method, pika.spec.Queue.DeclareOk) + + # Check if it exists by declaring it passively + frame = ch.queue_declare(q_name, passive=True) + self.assertIsInstance(frame.method, pika.spec.Queue.DeclareOk) + + # Delete the queue + frame = ch.queue_delete(q_name) + self.assertIsInstance(frame.method, pika.spec.Queue.DeleteOk) + + # Verify that it's been deleted + with self.assertRaises(pika.exceptions.ChannelClosed) as cm: + ch.queue_declare(q_name, passive=True) + + self.assertEqual(cm.exception.args[0], 404) + + +class TestPassiveQueueDeclareOfUnknownQueueRaisesChannelClosed( + BlockingTestCaseBase): + def test(self): + """BlockingChannel: ChannelClosed raised when passive-declaring unknown queue""" # pylint: disable=C0301 + connection = self._connect() + ch = connection.channel() + + q_name = ("TestPassiveQueueDeclareOfUnknownQueueRaisesChannelClosed_q_" + + uuid.uuid1().hex) + + with self.assertRaises(pika.exceptions.ChannelClosed) as ex_cm: + ch.queue_declare(q_name, passive=True) + + self.assertEqual(ex_cm.exception.args[0], 404) + + +class TestQueueBindAndUnbindAndPurge(BlockingTestCaseBase): + + def test(self): + """BlockingChannel: Test queue_bind and queue_unbind""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestQueueBindAndUnbindAndPurge_q' + uuid.uuid1().hex + exg_name = 'TestQueueBindAndUnbindAndPurge_exg_' + uuid.uuid1().hex + routing_key = 'TestQueueBindAndUnbindAndPurge' + + # Place channel in publisher-acknowledgments mode so that we may test + # whether the queue is reachable by publishing with mandatory=True + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + frame = ch.queue_bind(q_name, exchange=exg_name, + routing_key=routing_key) + self.assertIsInstance(frame.method, pika.spec.Queue.BindOk) + + # Check that the queue is empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + # Deposit a message in the queue + ch.publish(exg_name, routing_key, body='TestQueueBindAndUnbindAndPurge', + mandatory=True) + + # Check that the queue now has one message + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 1) + + # Unbind the queue + frame = ch.queue_unbind(queue=q_name, exchange=exg_name, + routing_key=routing_key) + self.assertIsInstance(frame.method, pika.spec.Queue.UnbindOk) + + # Verify that the queue is now unreachable via that binding + with self.assertRaises(pika.exceptions.UnroutableError): + ch.publish(exg_name, routing_key, + body='TestQueueBindAndUnbindAndPurge-2', + mandatory=True) + + # Purge the queue and verify that 1 message was purged + frame = ch.queue_purge(q_name) + self.assertIsInstance(frame.method, pika.spec.Queue.PurgeOk) + self.assertEqual(frame.method.message_count, 1) + + # Verify that the queue is now empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + +class TestBasicGet(BlockingTestCaseBase): + + def tearDown(self): + LOGGER.info('%s TEARING DOWN (%s)', datetime.utcnow(), self) + + def test(self): + """BlockingChannel.basic_get""" + LOGGER.info('%s STARTED (%s)', datetime.utcnow(), self) + + connection = self._connect() + LOGGER.info('%s CONNECTED (%s)', datetime.utcnow(), self) + + ch = connection.channel() + LOGGER.info('%s CREATED CHANNEL (%s)', datetime.utcnow(), self) + + q_name = 'TestBasicGet_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + LOGGER.info('%s ENABLED PUB-ACKS (%s)', datetime.utcnow(), self) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + LOGGER.info('%s DECLARED QUEUE (%s)', datetime.utcnow(), self) + + # Verify result of getting a message from an empty queue + msg = ch.basic_get(q_name, no_ack=False) + self.assertTupleEqual(msg, (None, None, None)) + LOGGER.info('%s GOT FROM EMPTY QUEUE (%s)', datetime.utcnow(), self) + + body = 'TestBasicGet' + # Deposit a message in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body=body, + mandatory=True) + LOGGER.info('%s PUBLISHED (%s)', datetime.utcnow(), self) + + # Get the message + (method, properties, body) = ch.basic_get(q_name, no_ack=False) + LOGGER.info('%s GOT FROM NON-EMPTY QUEUE (%s)', datetime.utcnow(), self) + self.assertIsInstance(method, pika.spec.Basic.GetOk) + self.assertEqual(method.delivery_tag, 1) + self.assertFalse(method.redelivered) + self.assertEqual(method.exchange, '') + self.assertEqual(method.routing_key, q_name) + self.assertEqual(method.message_count, 0) + + self.assertIsInstance(properties, pika.BasicProperties) + self.assertIsNone(properties.headers) + self.assertEqual(body, as_bytes(body)) + + # Ack it + ch.basic_ack(delivery_tag=method.delivery_tag) + LOGGER.info('%s ACKED (%s)', datetime.utcnow(), self) + + # Verify that the queue is now empty + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestBasicReject(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_reject""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicReject_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicReject1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicReject2', + mandatory=True) + + # Get the messages + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicReject1')) + + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicReject2')) + + # Nack the second message + ch.basic_reject(rx_method.delivery_tag, requeue=True) + + # Verify that exactly one message is present in the queue, namely the + # second one + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=1) + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicReject2')) + + +class TestBasicRejectNoRequeue(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_reject with requeue=False""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicRejectNoRequeue_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicRejectNoRequeue1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicRejectNoRequeue2', + mandatory=True) + + # Get the messages + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicRejectNoRequeue1')) + + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicRejectNoRequeue2')) + + # Nack the second message + ch.basic_reject(rx_method.delivery_tag, requeue=False) + + # Verify that no messages are present in the queue + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestBasicNack(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_nack single message""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicNack_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNack1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNack2', + mandatory=True) + + # Get the messages + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicNack1')) + + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicNack2')) + + # Nack the second message + ch.basic_nack(rx_method.delivery_tag, multiple=False, requeue=True) + + # Verify that exactly one message is present in the queue, namely the + # second one + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=1) + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestBasicNack2')) + + +class TestBasicNackNoRequeue(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_nack with requeue=False""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicNackNoRequeue_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNackNoRequeue1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNackNoRequeue2', + mandatory=True) + + # Get the messages + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackNoRequeue1')) + + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackNoRequeue2')) + + # Nack the second message + ch.basic_nack(rx_method.delivery_tag, requeue=False) + + # Verify that no messages are present in the queue + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestBasicNackMultiple(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_nack multiple messages""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicNackMultiple_q' + uuid.uuid1().hex + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNackMultiple1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicNackMultiple2', + mandatory=True) + + # Get the messages + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackMultiple1')) + + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackMultiple2')) + + # Nack both messages via the "multiple" option + ch.basic_nack(rx_method.delivery_tag, multiple=True, requeue=True) + + # Verify that both messages are present in the queue + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackMultiple1')) + (rx_method, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, + as_bytes('TestBasicNackMultiple2')) + + +class TestBasicRecoverWithRequeue(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_recover with requeue=True. + + NOTE: the requeue=False option is not supported by RabbitMQ broker as + of this writing (using RabbitMQ 3.5.1) + """ + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestBasicRecoverWithRequeue_q' + uuid.uuid1().hex) + + # Place channel in publisher-acknowledgments mode so that the message + # may be delivered synchronously to the queue by publishing it with + # mandatory=True + ch.confirm_delivery() + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestBasicRecoverWithRequeue1', + mandatory=True) + ch.publish(exchange='', routing_key=q_name, + body='TestBasicRecoverWithRequeue2', + mandatory=True) + + rx_messages = [] + num_messages = 0 + for msg in ch.consume(q_name, no_ack=False): + num_messages += 1 + + if num_messages == 2: + ch.basic_recover(requeue=True) + + if num_messages > 2: + rx_messages.append(msg) + + if num_messages == 4: + break + else: + self.fail('consumer aborted prematurely') + + # Get the messages + (_, _, rx_body) = rx_messages[0] + self.assertEqual(rx_body, + as_bytes('TestBasicRecoverWithRequeue1')) + + (_, _, rx_body) = rx_messages[1] + self.assertEqual(rx_body, + as_bytes('TestBasicRecoverWithRequeue2')) + + +class TestTxCommit(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.tx_commit""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestTxCommit_q' + uuid.uuid1().hex + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Select standard transaction mode + frame = ch.tx_select() + self.assertIsInstance(frame.method, pika.spec.Tx.SelectOk) + + # Deposit a message in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestTxCommit1', + mandatory=True) + + # Verify that queue is still empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + # Commit the transaction + ch.tx_commit() + + # Verify that the queue has the expected message + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 1) + + (_, _, rx_body) = ch.basic_get(q_name, no_ack=False) + self.assertEqual(rx_body, as_bytes('TestTxCommit1')) + + +class TestTxRollback(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.tx_commit""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestTxRollback_q' + uuid.uuid1().hex + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Select standard transaction mode + frame = ch.tx_select() + self.assertIsInstance(frame.method, pika.spec.Tx.SelectOk) + + # Deposit a message in the queue via default exchange + ch.publish(exchange='', routing_key=q_name, + body='TestTxRollback1', + mandatory=True) + + # Verify that queue is still empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + # Roll back the transaction + ch.tx_rollback() + + # Verify that the queue continues to be empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + +class TestBasicConsumeFromUnknownQueueRaisesChannelClosed(BlockingTestCaseBase): + def test(self): + """ChannelClosed raised when consuming from unknown queue""" + connection = self._connect() + ch = connection.channel() + + q_name = ("TestBasicConsumeFromUnknownQueueRaisesChannelClosed_q_" + + uuid.uuid1().hex) + + with self.assertRaises(pika.exceptions.ChannelClosed) as ex_cm: + ch.basic_consume(lambda *args: None, q_name) + + self.assertEqual(ex_cm.exception.args[0], 404) + + +class TestPublishAndBasicPublishWithPubacksUnroutable(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel.publish amd basic_publish unroutable message with pubacks""" # pylint: disable=C0301 + connection = self._connect() + + ch = connection.channel() + + exg_name = ('TestPublishAndBasicPublishUnroutable_exg_' + + uuid.uuid1().hex) + routing_key = 'TestPublishAndBasicPublishUnroutable' + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Verify unroutable message handling using basic_publish + res = ch.basic_publish(exg_name, routing_key=routing_key, body='', + mandatory=True) + self.assertEqual(res, False) + + # Verify unroutable message handling using publish + msg2_headers = dict( + test_name='TestPublishAndBasicPublishWithPubacksUnroutable') + msg2_properties = pika.spec.BasicProperties(headers=msg2_headers) + with self.assertRaises(pika.exceptions.UnroutableError) as cm: + ch.publish(exg_name, routing_key=routing_key, body='', + properties=msg2_properties, mandatory=True) + (msg,) = cm.exception.messages + self.assertIsInstance(msg, blocking_connection.ReturnedMessage) + self.assertIsInstance(msg.method, pika.spec.Basic.Return) + self.assertEqual(msg.method.reply_code, 312) + self.assertEqual(msg.method.exchange, exg_name) + self.assertEqual(msg.method.routing_key, routing_key) + self.assertIsInstance(msg.properties, pika.BasicProperties) + self.assertEqual(msg.properties.headers, msg2_headers) + self.assertEqual(msg.body, as_bytes('')) + + +class TestConfirmDeliveryAfterUnroutableMessage(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel.confirm_delivery following unroutable message""" + connection = self._connect() + + ch = connection.channel() + + exg_name = ('TestConfirmDeliveryAfterUnroutableMessage_exg_' + + uuid.uuid1().hex) + routing_key = 'TestConfirmDeliveryAfterUnroutableMessage' + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Register on-return callback + returned_messages = [] + ch.add_on_return_callback(lambda *args: returned_messages.append(args)) + + # Emit unroutable message without pubacks + res = ch.basic_publish(exg_name, routing_key=routing_key, body='', + mandatory=True) + self.assertEqual(res, True) + + # Select delivery confirmations + ch.confirm_delivery() + + # Verify that unroutable message is in pending events + self.assertEqual(len(ch._pending_events), 1) + self.assertIsInstance(ch._pending_events[0], + blocking_connection._ReturnedMessageEvt) + # Verify that repr of _ReturnedMessageEvt instance does crash + repr(ch._pending_events[0]) + + # Dispach events + connection.process_data_events() + + self.assertEqual(len(ch._pending_events), 0) + + # Verify that unroutable message was dispatched + ((channel, method, properties, body,),) = returned_messages + self.assertIs(channel, ch) + self.assertIsInstance(method, pika.spec.Basic.Return) + self.assertEqual(method.reply_code, 312) + self.assertEqual(method.exchange, exg_name) + self.assertEqual(method.routing_key, routing_key) + self.assertIsInstance(properties, pika.BasicProperties) + self.assertEqual(body, as_bytes('')) + + +class TestUnroutableMessagesReturnedInNonPubackMode(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel: unroutable messages is returned in non-puback mode""" # pylint: disable=C0301 + connection = self._connect() + + ch = connection.channel() + + exg_name = ( + 'TestUnroutableMessageReturnedInNonPubackMode_exg_' + + uuid.uuid1().hex) + routing_key = 'TestUnroutableMessageReturnedInNonPubackMode' + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Register on-return callback + returned_messages = [] + ch.add_on_return_callback( + lambda *args: returned_messages.append(args)) + + # Emit unroutable messages without pubacks + ch.publish(exg_name, routing_key=routing_key, body='msg1', + mandatory=True) + + ch.publish(exg_name, routing_key=routing_key, body='msg2', + mandatory=True) + + # Process I/O until Basic.Return are dispatched + while len(returned_messages) < 2: + connection.process_data_events() + + self.assertEqual(len(returned_messages), 2) + + self.assertEqual(len(ch._pending_events), 0) + + # Verify returned messages + (channel, method, properties, body,) = returned_messages[0] + self.assertIs(channel, ch) + self.assertIsInstance(method, pika.spec.Basic.Return) + self.assertEqual(method.reply_code, 312) + self.assertEqual(method.exchange, exg_name) + self.assertEqual(method.routing_key, routing_key) + self.assertIsInstance(properties, pika.BasicProperties) + self.assertEqual(body, as_bytes('msg1')) + + (channel, method, properties, body,) = returned_messages[1] + self.assertIs(channel, ch) + self.assertIsInstance(method, pika.spec.Basic.Return) + self.assertEqual(method.reply_code, 312) + self.assertEqual(method.exchange, exg_name) + self.assertEqual(method.routing_key, routing_key) + self.assertIsInstance(properties, pika.BasicProperties) + self.assertEqual(body, as_bytes('msg2')) + + +class TestUnroutableMessageReturnedInPubackMode(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel: unroutable messages is returned in puback mode""" + connection = self._connect() + + ch = connection.channel() + + exg_name = ( + 'TestUnroutableMessageReturnedInPubackMode_exg_' + + uuid.uuid1().hex) + routing_key = 'TestUnroutableMessageReturnedInPubackMode' + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Select delivery confirmations + ch.confirm_delivery() + + # Register on-return callback + returned_messages = [] + ch.add_on_return_callback( + lambda *args: returned_messages.append(args)) + + # Emit unroutable messages with pubacks + res = ch.basic_publish(exg_name, routing_key=routing_key, body='msg1', + mandatory=True) + self.assertEqual(res, False) + + res = ch.basic_publish(exg_name, routing_key=routing_key, body='msg2', + mandatory=True) + self.assertEqual(res, False) + + # Verify that unroutable messages are already in pending events + self.assertEqual(len(ch._pending_events), 2) + self.assertIsInstance(ch._pending_events[0], + blocking_connection._ReturnedMessageEvt) + self.assertIsInstance(ch._pending_events[1], + blocking_connection._ReturnedMessageEvt) + # Verify that repr of _ReturnedMessageEvt instance does crash + repr(ch._pending_events[0]) + repr(ch._pending_events[1]) + + # Dispatch events + connection.process_data_events() + + self.assertEqual(len(ch._pending_events), 0) + + # Verify returned messages + (channel, method, properties, body,) = returned_messages[0] + self.assertIs(channel, ch) + self.assertIsInstance(method, pika.spec.Basic.Return) + self.assertEqual(method.reply_code, 312) + self.assertEqual(method.exchange, exg_name) + self.assertEqual(method.routing_key, routing_key) + self.assertIsInstance(properties, pika.BasicProperties) + self.assertEqual(body, as_bytes('msg1')) + + (channel, method, properties, body,) = returned_messages[1] + self.assertIs(channel, ch) + self.assertIsInstance(method, pika.spec.Basic.Return) + self.assertEqual(method.reply_code, 312) + self.assertEqual(method.exchange, exg_name) + self.assertEqual(method.routing_key, routing_key) + self.assertIsInstance(properties, pika.BasicProperties) + self.assertEqual(body, as_bytes('msg2')) + + +class TestBasicPublishDeliveredWhenPendingUnroutable(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel.basic_publish msg delivered despite pending unroutable message""" # pylint: disable=C0301 + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestBasicPublishDeliveredWhenPendingUnroutable_q' + + uuid.uuid1().hex) + exg_name = ('TestBasicPublishDeliveredWhenPendingUnroutable_exg_' + + uuid.uuid1().hex) + routing_key = 'TestBasicPublishDeliveredWhenPendingUnroutable' + + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) + + # Attempt to send an unroutable message in the queue via basic_publish + res = ch.basic_publish(exg_name, routing_key='', + body='unroutable-message', + mandatory=True) + self.assertEqual(res, True) + + # Flush connection to force Basic.Return + connection.channel().close() + + # Deposit a routable message in the queue + res = ch.basic_publish(exg_name, routing_key=routing_key, + body='routable-message', + mandatory=True) + self.assertEqual(res, True) + + # Wait for the queue to get the routable message + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=1) + + msg = ch.basic_get(q_name) + + # Check the first message + self.assertIsInstance(msg, tuple) + rx_method, rx_properties, rx_body = msg + self.assertIsInstance(rx_method, pika.spec.Basic.GetOk) + self.assertEqual(rx_method.delivery_tag, 1) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes('routable-message')) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + # Ack the message + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + # Verify that the queue is now empty + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestPublishAndConsumeWithPubacksAndQosOfOne(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914,R0915 + """BlockingChannel.basic_publish, publish, basic_consume, QoS, \ + Basic.Cancel from broker + """ + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestPublishAndConsumeAndQos_q' + uuid.uuid1().hex + exg_name = 'TestPublishAndConsumeAndQos_exg_' + uuid.uuid1().hex + routing_key = 'TestPublishAndConsumeAndQos' + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) + + # Deposit a message in the queue via basic_publish + msg1_headers = dict( + test_name='TestPublishAndConsumeWithPubacksAndQosOfOne') + msg1_properties = pika.spec.BasicProperties(headers=msg1_headers) + res = ch.basic_publish(exg_name, routing_key=routing_key, + body='via-basic_publish', + properties=msg1_properties, + mandatory=True) + self.assertEqual(res, True) + + # Deposit another message in the queue via publish + ch.publish(exg_name, routing_key, body='via-publish', + mandatory=True) + + # Check that the queue now has two messages + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 2) + + # Configure QoS for one message + ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) + + # Create a consumer + rx_messages = [] + consumer_tag = ch.basic_consume( + lambda *args: rx_messages.append(args), + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Wait for first message to arrive + while not rx_messages: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_messages), 1) + + # Check the first message + msg = rx_messages[0] + self.assertIsInstance(msg, tuple) + rx_ch, rx_method, rx_properties, rx_body = msg + self.assertIs(rx_ch, ch) + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.consumer_tag, consumer_tag) + self.assertEqual(rx_method.delivery_tag, 1) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_properties.headers, msg1_headers) + self.assertEqual(rx_body, as_bytes('via-basic_publish')) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + # Ack the message so that the next one can arrive (we configured QoS + # with prefetch_count=1) + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + # Get the second message + while len(rx_messages) < 2: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_messages), 2) + + msg = rx_messages[1] + self.assertIsInstance(msg, tuple) + rx_ch, rx_method, rx_properties, rx_body = msg + self.assertIs(rx_ch, ch) + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.consumer_tag, consumer_tag) + self.assertEqual(rx_method.delivery_tag, 2) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes('via-publish')) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + # Verify that the queue is now empty + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + # Attempt to consume again with a short timeout + connection.process_data_events(time_limit=0.005) + self.assertEqual(len(rx_messages), 2) + + # Delete the queue and wait for consumer cancellation + rx_cancellations = [] + ch.add_on_cancel_callback(rx_cancellations.append) + ch.queue_delete(q_name) + ch.start_consuming() + + self.assertEqual(len(rx_cancellations), 1) + frame, = rx_cancellations + self.assertEqual(frame.method.consumer_tag, consumer_tag) + + +class TestBasicConsumeWithAckFromAnotherThread(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914,R0915 + """BlockingChannel.basic_consume with ack from another thread and \ + requesting basic_ack via add_callback_threadsafe + """ + # This test simulates processing of a message on another thread and + # then requesting an ACK to be dispatched on the connection's thread + # via BlockingConnection.add_callback_threadsafe + + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicConsumeWithAckFromAnotherThread_q' + uuid.uuid1().hex + exg_name = ('TestBasicConsumeWithAckFromAnotherThread_exg' + + uuid.uuid1().hex) + routing_key = 'TestBasicConsumeWithAckFromAnotherThread' + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous (for convenience) + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) + + # Publish 2 messages with mandatory=True for synchronous processing + ch.publish(exg_name, routing_key, body='msg1', mandatory=True) + ch.publish(exg_name, routing_key, body='last-msg', mandatory=True) + + # Configure QoS for one message so that the 2nd message will be + # delivered only after the 1st one is ACKed + ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) + + # Create a consumer + rx_messages = [] + def ackAndEnqueueMessageViaAnotherThread(rx_ch, + rx_method, + rx_properties, # pylint: disable=W0613 + rx_body): + LOGGER.debug( + '%s: Got message body=%r; delivery-tag=%r', + datetime.now(), rx_body, rx_method.delivery_tag) + + # Request ACK dispatch via add_callback_threadsafe from other + # thread; if last message, cancel consumer so that start_consuming + # can return + + def processOnConnectionThread(): + LOGGER.debug('%s: ACKing message body=%r; delivery-tag=%r', + datetime.now(), + rx_body, + rx_method.delivery_tag) + ch.basic_ack(delivery_tag=rx_method.delivery_tag, + multiple=False) + rx_messages.append(rx_body) + + # NOTE on python3, `b'last-msg' != 'last-msg'` + if rx_body == b'last-msg': + LOGGER.debug('%s: Canceling consumer consumer-tag=%r', + datetime.now(), + rx_method.consumer_tag) + rx_ch.basic_cancel(rx_method.consumer_tag) + + # Spawn a thread to initiate ACKing + timer = threading.Timer(0, + lambda: connection.add_callback_threadsafe( + processOnConnectionThread)) + self.addCleanup(timer.cancel) + timer.start() + + consumer_tag = ch.basic_consume( + ackAndEnqueueMessageViaAnotherThread, + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Wait for both messages + LOGGER.debug('%s: calling start_consuming(); consumer tag=%r', + datetime.now(), + consumer_tag) + ch.start_consuming() + LOGGER.debug('%s: Returned from start_consuming(); consumer tag=%r', + datetime.now(), + consumer_tag) + + self.assertEqual(len(rx_messages), 2) + self.assertEqual(rx_messages[0], b'msg1') + self.assertEqual(rx_messages[1], b'last-msg') + + +class TestConsumeGeneratorWithAckFromAnotherThread(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914,R0915 + """BlockingChannel.consume and requesting basic_ack from another \ + thread via add_callback_threadsafe + """ + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestConsumeGeneratorWithAckFromAnotherThread_q' + + uuid.uuid1().hex) + exg_name = ('TestConsumeGeneratorWithAckFromAnotherThread_exg' + + uuid.uuid1().hex) + routing_key = 'TestConsumeGeneratorWithAckFromAnotherThread' + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous (for convenience) + res = ch.confirm_delivery() + self.assertIsNone(res) + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) + + # Publish 2 messages with mandatory=True for synchronous processing + ch.publish(exg_name, routing_key, body='msg1', mandatory=True) + ch.publish(exg_name, routing_key, body='last-msg', mandatory=True) + + # Configure QoS for one message so that the 2nd message will be + # delivered only after the 1st one is ACKed + ch.basic_qos(prefetch_size=0, prefetch_count=1, all_channels=False) + + # Create a consumer + rx_messages = [] + def ackAndEnqueueMessageViaAnotherThread(rx_ch, + rx_method, + rx_properties, # pylint: disable=W0613 + rx_body): + LOGGER.debug( + '%s: Got message body=%r; delivery-tag=%r', + datetime.now(), rx_body, rx_method.delivery_tag) + + # Request ACK dispatch via add_callback_threadsafe from other + # thread; if last message, cancel consumer so that consumer + # generator completes + + def processOnConnectionThread(): + LOGGER.debug('%s: ACKing message body=%r; delivery-tag=%r', + datetime.now(), + rx_body, + rx_method.delivery_tag) + ch.basic_ack(delivery_tag=rx_method.delivery_tag, + multiple=False) + rx_messages.append(rx_body) + + # NOTE on python3, `b'last-msg' != 'last-msg'` + if rx_body == b'last-msg': + LOGGER.debug('%s: Canceling consumer consumer-tag=%r', + datetime.now(), + rx_method.consumer_tag) + # NOTE Need to use cancel() for the consumer generator + # instead of basic_cancel() + rx_ch.cancel() + + # Spawn a thread to initiate ACKing + timer = threading.Timer(0, + lambda: connection.add_callback_threadsafe( + processOnConnectionThread)) + self.addCleanup(timer.cancel) + timer.start() + + for method, properties, body in ch.consume(q_name, no_ack=False): + ackAndEnqueueMessageViaAnotherThread(rx_ch=ch, + rx_method=method, + rx_properties=properties, + rx_body=body) + + self.assertEqual(len(rx_messages), 2) + self.assertEqual(rx_messages[0], b'msg1') + self.assertEqual(rx_messages[1], b'last-msg') + + +class TestTwoBasicConsumersOnSameChannel(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914 + """BlockingChannel: two basic_consume consumers on same channel + """ + connection = self._connect() + + ch = connection.channel() + + exg_name = 'TestPublishAndConsumeAndQos_exg_' + uuid.uuid1().hex + q1_name = 'TestTwoBasicConsumersOnSameChannel_q1' + uuid.uuid1().hex + q2_name = 'TestTwoBasicConsumersOnSameChannel_q2' + uuid.uuid1().hex + q1_routing_key = 'TestTwoBasicConsumersOnSameChannel1' + q2_routing_key = 'TestTwoBasicConsumersOnSameChannel2' + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + ch.confirm_delivery() + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare the two new queues and bind them to the exchange + ch.queue_declare(q1_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q1_name) + ch.queue_bind(q1_name, exchange=exg_name, routing_key=q1_routing_key) + + ch.queue_declare(q2_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q2_name) + ch.queue_bind(q2_name, exchange=exg_name, routing_key=q2_routing_key) + + # Deposit messages in the queues + q1_tx_message_bodies = ['q1_message+%s' % (i,) + for i in pika.compat.xrange(100)] + for message_body in q1_tx_message_bodies: + ch.publish(exg_name, q1_routing_key, body=message_body, + mandatory=True) + + q2_tx_message_bodies = ['q2_message+%s' % (i,) + for i in pika.compat.xrange(150)] + for message_body in q2_tx_message_bodies: + ch.publish(exg_name, q2_routing_key, body=message_body, + mandatory=True) + + # Create the consumers + q1_rx_messages = [] + q1_consumer_tag = ch.basic_consume( + lambda *args: q1_rx_messages.append(args), + q1_name, + no_ack=False, + exclusive=False, + arguments=None) + + q2_rx_messages = [] + q2_consumer_tag = ch.basic_consume( + lambda *args: q2_rx_messages.append(args), + q2_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Wait for all messages to be delivered + while (len(q1_rx_messages) < len(q1_tx_message_bodies) or + len(q2_rx_messages) < len(q2_tx_message_bodies)): + connection.process_data_events(time_limit=None) + + self.assertEqual(len(q2_rx_messages), len(q2_tx_message_bodies)) + + # Verify the messages + def validate_messages(rx_messages, + routing_key, + consumer_tag, + tx_message_bodies): + self.assertEqual(len(rx_messages), len(tx_message_bodies)) + + for msg, expected_body in zip(rx_messages, tx_message_bodies): + self.assertIsInstance(msg, tuple) + rx_ch, rx_method, rx_properties, rx_body = msg + self.assertIs(rx_ch, ch) + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.consumer_tag, consumer_tag) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes(expected_body)) + + # Validate q1 consumed messages + validate_messages(rx_messages=q1_rx_messages, + routing_key=q1_routing_key, + consumer_tag=q1_consumer_tag, + tx_message_bodies=q1_tx_message_bodies) + + # Validate q2 consumed messages + validate_messages(rx_messages=q2_rx_messages, + routing_key=q2_routing_key, + consumer_tag=q2_consumer_tag, + tx_message_bodies=q2_tx_message_bodies) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + +class TestBasicCancelPurgesPendingConsumerCancellationEvt(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_cancel purges pending _ConsumerCancellationEvt""" # pylint: disable=C0301 + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestBasicCancelPurgesPendingConsumerCancellationEvt_q' + + uuid.uuid1().hex) + + ch.queue_declare(q_name) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + ch.publish('', routing_key=q_name, body='via-publish', mandatory=True) + + # Create a consumer + rx_messages = [] + consumer_tag = ch.basic_consume( + lambda *args: rx_messages.append(args), + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Wait for the published message to arrive, but don't consume it + while not ch._pending_events: + # Issue synchronous command that forces processing of incoming I/O + connection.channel().close() + + self.assertEqual(len(ch._pending_events), 1) + self.assertIsInstance(ch._pending_events[0], + blocking_connection._ConsumerDeliveryEvt) + + # Delete the queue and wait for broker-initiated consumer cancellation + ch.queue_delete(q_name) + while len(ch._pending_events) < 2: + # Issue synchronous command that forces processing of incoming I/O + connection.channel().close() + + self.assertEqual(len(ch._pending_events), 2) + self.assertIsInstance(ch._pending_events[1], + blocking_connection._ConsumerCancellationEvt) + + # Issue consumer cancellation and verify that the pending + # _ConsumerCancellationEvt instance was removed + messages = ch.basic_cancel(consumer_tag) + self.assertEqual(messages, []) + + self.assertEqual(len(ch._pending_events), 0) + + +class TestBasicPublishWithoutPubacks(BlockingTestCaseBase): + + def test(self): # pylint: disable=R0914,R0915 + """BlockingChannel.basic_publish without pubacks""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestBasicPublishWithoutPubacks_q' + uuid.uuid1().hex + exg_name = 'TestBasicPublishWithoutPubacks_exg_' + uuid.uuid1().hex + routing_key = 'TestBasicPublishWithoutPubacks' + + # Declare a new exchange + ch.exchange_declare(exg_name, exchange_type='direct') + self.addCleanup(connection.channel().exchange_delete, exg_name) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Bind the queue to the exchange using routing key + ch.queue_bind(q_name, exchange=exg_name, routing_key=routing_key) + + # Deposit a message in the queue via basic_publish and mandatory=True + msg1_headers = dict( + test_name='TestBasicPublishWithoutPubacks') + msg1_properties = pika.spec.BasicProperties(headers=msg1_headers) + res = ch.basic_publish(exg_name, routing_key=routing_key, + body='via-basic_publish_mandatory=True', + properties=msg1_properties, + mandatory=True) + self.assertEqual(res, True) + + # Deposit a message in the queue via basic_publish and mandatory=False + res = ch.basic_publish(exg_name, routing_key=routing_key, + body='via-basic_publish_mandatory=False', + mandatory=False) + self.assertEqual(res, True) + + # Wait for the messages to arrive in queue + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + + # Create a consumer + rx_messages = [] + consumer_tag = ch.basic_consume( + lambda *args: rx_messages.append(args), + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Wait for first message to arrive + while not rx_messages: + connection.process_data_events(time_limit=None) + + self.assertGreaterEqual(len(rx_messages), 1) + + # Check the first message + msg = rx_messages[0] + self.assertIsInstance(msg, tuple) + rx_ch, rx_method, rx_properties, rx_body = msg + self.assertIs(rx_ch, ch) + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.consumer_tag, consumer_tag) + self.assertEqual(rx_method.delivery_tag, 1) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_properties.headers, msg1_headers) + self.assertEqual(rx_body, as_bytes('via-basic_publish_mandatory=True')) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + # Ack the message so that the next one can arrive (we configured QoS + # with prefetch_count=1) + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + # Get the second message + while len(rx_messages) < 2: + connection.process_data_events(time_limit=None) + + self.assertEqual(len(rx_messages), 2) + + msg = rx_messages[1] + self.assertIsInstance(msg, tuple) + rx_ch, rx_method, rx_properties, rx_body = msg + self.assertIs(rx_ch, ch) + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.consumer_tag, consumer_tag) + self.assertEqual(rx_method.delivery_tag, 2) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, exg_name) + self.assertEqual(rx_method.routing_key, routing_key) + + self.assertIsInstance(rx_properties, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes('via-basic_publish_mandatory=False')) + + # There shouldn't be any more events now + self.assertFalse(ch._pending_events) + + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + # Verify that the queue is now empty + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + # Attempt to consume again with a short timeout + connection.process_data_events(time_limit=0.005) + self.assertEqual(len(rx_messages), 2) + + +class TestPublishFromBasicConsumeCallback(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.basic_publish from basic_consume callback + """ + connection = self._connect() + + ch = connection.channel() + + src_q_name = ( + 'TestPublishFromBasicConsumeCallback_src_q' + uuid.uuid1().hex) + dest_q_name = ( + 'TestPublishFromBasicConsumeCallback_dest_q' + uuid.uuid1().hex) + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + ch.confirm_delivery() + + # Declare source and destination queues + ch.queue_declare(src_q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, src_q_name) + ch.queue_declare(dest_q_name, auto_delete=True) + self.addCleanup(self._connect().channel().queue_delete, dest_q_name) + + # Deposit a message in the source queue + ch.publish('', + routing_key=src_q_name, + body='via-publish', + mandatory=True) + + # Create a consumer + def on_consume(channel, method, props, body): + channel.publish( + '', routing_key=dest_q_name, body=body, + properties=props, mandatory=True) + channel.basic_ack(method.delivery_tag) + + ch.basic_consume(on_consume, + src_q_name, + no_ack=False, + exclusive=False, + arguments=None) + + # Consume from destination queue + for _, _, rx_body in ch.consume(dest_q_name, no_ack=True): + self.assertEqual(rx_body, as_bytes('via-publish')) + break + else: + self.fail('failed to consume a messages from destination q') + + +class TestStopConsumingFromBasicConsumeCallback(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.stop_consuming from basic_consume callback + """ + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestStopConsumingFromBasicConsumeCallback_q' + uuid.uuid1().hex) + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + ch.confirm_delivery() + + # Declare the queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(connection.channel().queue_delete, q_name) + + # Deposit two messages in the queue + ch.publish('', + routing_key=q_name, + body='via-publish1', + mandatory=True) + + ch.publish('', + routing_key=q_name, + body='via-publish2', + mandatory=True) + + # Create a consumer + def on_consume(channel, method, props, body): # pylint: disable=W0613 + channel.stop_consuming() + channel.basic_ack(method.delivery_tag) + + ch.basic_consume(on_consume, + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + ch.start_consuming() + + ch.close() + + ch = connection.channel() + + # Verify that only the second message is present in the queue + _, _, rx_body = ch.basic_get(q_name) + self.assertEqual(rx_body, as_bytes('via-publish2')) + + msg = ch.basic_get(q_name) + self.assertTupleEqual(msg, (None, None, None)) + + +class TestCloseChannelFromBasicConsumeCallback(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.close from basic_consume callback + """ + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestCloseChannelFromBasicConsumeCallback_q' + uuid.uuid1().hex) + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + ch.confirm_delivery() + + # Declare the queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(connection.channel().queue_delete, q_name) + + # Deposit two messages in the queue + ch.publish('', + routing_key=q_name, + body='via-publish1', + mandatory=True) + + ch.publish('', + routing_key=q_name, + body='via-publish2', + mandatory=True) + + # Create a consumer + def on_consume(channel, method, props, body): # pylint: disable=W0613 + channel.close() + + ch.basic_consume(on_consume, + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + ch.start_consuming() + + self.assertTrue(ch.is_closed) + + + # Verify that both messages are present in the queue + ch = connection.channel() + _, _, rx_body = ch.basic_get(q_name) + self.assertEqual(rx_body, as_bytes('via-publish1')) + _, _, rx_body = ch.basic_get(q_name) + self.assertEqual(rx_body, as_bytes('via-publish2')) + + +class TestCloseConnectionFromBasicConsumeCallback(BlockingTestCaseBase): + + def test(self): + """BlockingConnection.close from basic_consume callback + """ + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestCloseConnectionFromBasicConsumeCallback_q' + uuid.uuid1().hex) + + # Place channel in publisher-acknowledgments mode so that publishing + # with mandatory=True will be synchronous + ch.confirm_delivery() + + # Declare the queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Deposit two messages in the queue + ch.publish('', + routing_key=q_name, + body='via-publish1', + mandatory=True) + + ch.publish('', + routing_key=q_name, + body='via-publish2', + mandatory=True) + + # Create a consumer + def on_consume(channel, method, props, body): # pylint: disable=W0613 + connection.close() + + ch.basic_consume(on_consume, + q_name, + no_ack=False, + exclusive=False, + arguments=None) + + ch.start_consuming() + + self.assertTrue(ch.is_closed) + self.assertTrue(connection.is_closed) + + + # Verify that both messages are present in the queue + ch = self._connect().channel() + _, _, rx_body = ch.basic_get(q_name) + self.assertEqual(rx_body, as_bytes('via-publish1')) + _, _, rx_body = ch.basic_get(q_name) + self.assertEqual(rx_body, as_bytes('via-publish2')) + + +class TestNonPubAckPublishAndConsumeHugeMessage(BlockingTestCaseBase): + + def test(self): + """BlockingChannel.publish/consume huge message""" + connection = self._connect() + + ch = connection.channel() + + q_name = 'TestPublishAndConsumeHugeMessage_q' + uuid.uuid1().hex + body = 'a' * 1000000 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish a message to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body) + LOGGER.info('Published message body size=%s', len(body)) + + # Consume the message + for rx_method, rx_props, rx_body in ch.consume(q_name, no_ack=False, + exclusive=False, + arguments=None): + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.delivery_tag, 1) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, '') + self.assertEqual(rx_method.routing_key, q_name) + + self.assertIsInstance(rx_props, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes(body)) + + # Ack the message + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + break + + # There shouldn't be any more events now + self.assertFalse(ch._queue_consumer_generator.pending_events) + + # Verify that the queue is now empty + ch.close() + ch = connection.channel() + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestNonPubackPublishAndConsumeManyMessages(BlockingTestCaseBase): + + def test(self): + """BlockingChannel non-pub-ack publish/consume many messages""" + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestNonPubackPublishAndConsumeManyMessages_q' + + uuid.uuid1().hex) + body = 'b' * 1024 + + num_messages_to_publish = 500 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + for _ in pika.compat.xrange(num_messages_to_publish): + # Publish a message to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body) + + # Consume the messages + num_consumed = 0 + for rx_method, rx_props, rx_body in ch.consume(q_name, + no_ack=False, + exclusive=False, + arguments=None): + num_consumed += 1 + self.assertIsInstance(rx_method, pika.spec.Basic.Deliver) + self.assertEqual(rx_method.delivery_tag, num_consumed) + self.assertFalse(rx_method.redelivered) + self.assertEqual(rx_method.exchange, '') + self.assertEqual(rx_method.routing_key, q_name) + + self.assertIsInstance(rx_props, pika.BasicProperties) + self.assertEqual(rx_body, as_bytes(body)) + + # Ack the message + ch.basic_ack(delivery_tag=rx_method.delivery_tag, multiple=False) + + if num_consumed >= num_messages_to_publish: + break + + # There shouldn't be any more events now + self.assertFalse(ch._queue_consumer_generator.pending_events) + + ch.close() + + self.assertIsNone(ch._queue_consumer_generator) + + # Verify that the queue is now empty + ch = connection.channel() + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + +class TestBasicCancelWithNonAckableConsumer(BlockingTestCaseBase): + + def test(self): + """BlockingChannel user cancels non-ackable consumer via basic_cancel""" + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestBasicCancelWithNonAckableConsumer_q' + uuid.uuid1().hex) + + body1 = 'a' * 1024 + body2 = 'b' * 2048 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish two messages to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body1) + ch.publish(exchange='', routing_key=q_name, body=body2) + + # Wait for queue to contain both messages + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + + # Create a non-ackable consumer + consumer_tag = ch.basic_consume(lambda *x: None, q_name, no_ack=True, + exclusive=False, arguments=None) + + # Wait for all messages to be sent by broker to client + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + # Cancel the consumer + messages = ch.basic_cancel(consumer_tag) + + # Both messages should have been on their way when we cancelled + self.assertEqual(len(messages), 2) + + _, _, rx_body1 = messages[0] + self.assertEqual(rx_body1, as_bytes(body1)) + + _, _, rx_body2 = messages[1] + self.assertEqual(rx_body2, as_bytes(body2)) + + ch.close() + + ch = connection.channel() + + # Verify that the queue is now empty + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + +class TestBasicCancelWithAckableConsumer(BlockingTestCaseBase): + + def test(self): + """BlockingChannel user cancels ackable consumer via basic_cancel""" + connection = self._connect() + + ch = connection.channel() + + q_name = ( + 'TestBasicCancelWithAckableConsumer_q' + uuid.uuid1().hex) + + body1 = 'a' * 1024 + body2 = 'b' * 2048 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish two messages to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body1) + ch.publish(exchange='', routing_key=q_name, body=body2) + + # Wait for queue to contain both messages + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + + # Create an ackable consumer + consumer_tag = ch.basic_consume(lambda *x: None, q_name, no_ack=False, + exclusive=False, arguments=None) + + # Wait for all messages to be sent by broker to client + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=0) + + # Cancel the consumer + messages = ch.basic_cancel(consumer_tag) + + # Both messages should have been on their way when we cancelled + self.assertEqual(len(messages), 0) + + ch.close() + + ch = connection.channel() + + # Verify that canceling the ackable consumer restored both messages + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + + +class TestUnackedMessageAutoRestoredToQueueOnChannelClose(BlockingTestCaseBase): + + def test(self): + """BlockingChannel unacked message restored to q on channel close """ + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestUnackedMessageAutoRestoredToQueueOnChannelClose_q' + + uuid.uuid1().hex) + + body1 = 'a' * 1024 + body2 = 'b' * 2048 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish two messages to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body1) + ch.publish(exchange='', routing_key=q_name, body=body2) + + # Consume the events, but don't ack + rx_messages = [] + ch.basic_consume(lambda *args: rx_messages.append(args), + q_name, + no_ack=False, + exclusive=False, + arguments=None) + while len(rx_messages) != 2: + connection.process_data_events(time_limit=None) + + self.assertEqual(rx_messages[0][1].delivery_tag, 1) + self.assertEqual(rx_messages[1][1].delivery_tag, 2) + + # Verify no more ready messages in queue + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + # Closing channel should restore messages back to queue + ch.close() + + # Verify that there are two messages in q now + ch = connection.channel() + + self._assert_exact_message_count_with_retries(channel=ch, + queue=q_name, + expected_count=2) + + +class TestNoAckMessageNotRestoredToQueueOnChannelClose(BlockingTestCaseBase): + + def test(self): + """BlockingChannel unacked message restored to q on channel close """ + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestNoAckMessageNotRestoredToQueueOnChannelClose_q' + + uuid.uuid1().hex) + + body1 = 'a' * 1024 + body2 = 'b' * 2048 + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Publish two messages to the queue by way of default exchange + ch.publish(exchange='', routing_key=q_name, body=body1) + ch.publish(exchange='', routing_key=q_name, body=body2) + + # Consume, but don't ack + num_messages = 0 + for rx_method, _, _ in ch.consume(q_name, no_ack=True, exclusive=False): + num_messages += 1 + + self.assertEqual(rx_method.delivery_tag, num_messages) + + if num_messages == 2: + break + else: + self.fail('expected 2 messages, but consumed %i' % (num_messages,)) + + # Verify no more ready messages in queue + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + # Closing channel should not restore no-ack messages back to queue + ch.close() + + # Verify that there are no messages in q now + ch = connection.channel() + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.message_count, 0) + + +class TestConsumeInactivityTimeout(BlockingTestCaseBase): + + def test(self): + """BlockingChannel consume returns 3-tuple on inactivity timeout """ + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestConsumeInactivityTimeout_q' + + uuid.uuid1().hex) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=True) + + # Consume, but don't ack + for msg in ch.consume(q_name, inactivity_timeout=0.1): + a, b, c = msg + self.assertIsNone(a) + self.assertIsNone(b) + self.assertIsNone(c) + break + else: + self.fail('expected (None, None, None), but got %s' % msg) + + ch.close() + + +class TestChannelFlow(BlockingTestCaseBase): + + def test(self): + """BlockingChannel Channel.Flow activate and deactivate """ + connection = self._connect() + + ch = connection.channel() + + q_name = ('TestChannelFlow_q' + uuid.uuid1().hex) + + # Declare a new queue + ch.queue_declare(q_name, auto_delete=False) + self.addCleanup(self._connect().channel().queue_delete, q_name) + + # Verify zero active consumers on the queue + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.consumer_count, 0) + + # Create consumer + ch.basic_consume(lambda *args: None, q_name) + + # Verify one active consumer on the queue now + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.consumer_count, 1) + + # Activate flow from default state (active by default) + active = ch.flow(True) + self.assertEqual(active, True) + + # Verify still one active consumer on the queue now + frame = ch.queue_declare(q_name, passive=True) + self.assertEqual(frame.method.consumer_count, 1) + + # active=False is not supported by RabbitMQ per + # https://www.rabbitmq.com/specification.html: + # "active=false is not supported by the server. Limiting prefetch with + # basic.qos provides much better control" +## # Deactivate flow +## active = ch.flow(False) +## self.assertEqual(active, False) +## +## # Verify zero active consumers on the queue now +## frame = ch.queue_declare(q_name, passive=True) +## self.assertEqual(frame.method.consumer_count, 0) +## +## # Re-activate flow +## active = ch.flow(True) +## self.assertEqual(active, True) +## +## # Verify one active consumers on the queue once again +## frame = ch.queue_declare(q_name, passive=True) +## self.assertEqual(frame.method.consumer_count, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/enforce_one_basicget_test.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/enforce_one_basicget_test.py new file mode 100644 index 000000000..8a65ac3d7 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/enforce_one_basicget_test.py @@ -0,0 +1,29 @@ +import unittest + +from mock import MagicMock +from pika.frame import Method, Header +from pika.exceptions import DuplicateGetOkCallback +from pika.channel import Channel +from pika.connection import Connection + + +class OnlyOneBasicGetTestCase(unittest.TestCase): + def setUp(self): + self.channel = Channel(MagicMock(Connection)(), 0, None) + self.channel._state = Channel.OPEN + self.callback = MagicMock() + + def test_two_basic_get_with_callback(self): + self.channel.basic_get(self.callback) + self.channel._on_getok(MagicMock(Method)(), MagicMock(Header)(), '') + self.channel.basic_get(self.callback) + self.channel._on_getok(MagicMock(Method)(), MagicMock(Header)(), '') + self.assertEqual(self.callback.call_count, 2) + + def test_two_basic_get_without_callback(self): + self.channel.basic_get(self.callback) + with self.assertRaises(DuplicateGetOkCallback): + self.channel.basic_get(self.callback) + +if __name__ == '__main__': + unittest.main() diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/forward_server.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/forward_server.py new file mode 100644 index 000000000..a2ea48261 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/forward_server.py @@ -0,0 +1,526 @@ +"""TCP/IP forwarding/echo service for testing.""" + +from __future__ import print_function + +import array +from datetime import datetime +import errno +from functools import partial +import logging +import multiprocessing +import os +import socket +import struct +import sys +import threading +import traceback + +import pika.compat + +if pika.compat.PY3: + + def buffer(object, offset, size): # pylint: disable=W0622 + """array etc. have the buffer protocol""" + return object[offset:offset + size] + + +try: + import SocketServer +except ImportError: + import socketserver as SocketServer # pylint: disable=F0401 + + +def _trace(fmt, *args): + """Format and output the text to stderr""" + print((fmt % args) + "\n", end="", file=sys.stderr) + + +class ForwardServer(object): # pylint: disable=R0902 + """ Implement a TCP/IP forwarding/echo service for testing. Listens for + an incoming TCP/IP connection, accepts it, then connects to the given + remote address and forwards data back and forth between the two + endpoints. + + This is similar to the subset of `netcat` functionality, but without + dependency on any specific flavor of netcat + + Connection forwarding example; forward local connection to default + rabbitmq addr, connect to rabbit via forwarder, then disconnect + forwarder, then attempt another pika operation to see what happens + + with ForwardServer(("localhost", 5672)) as fwd: + params = pika.ConnectionParameters( + host=fwd.server_address[0], + port=fwd.server_address[1]) + conn = pika.BlockingConnection(params) + + # Once outside the context, the forwarder is disconnected + + # Let's see what happens in pika with a disconnected server + channel = conn.channel() + + Echo server example + def produce(sock): + sock.sendall("12345") + sock.shutdown(socket.SHUT_WR) + + with ForwardServer(None) as fwd: + sock = socket.socket() + sock.connect(fwd.server_address) + + worker = threading.Thread(target=produce, + args=[sock]) + worker.start() + + data = sock.makefile().read() + assert data == "12345", data + + worker.join() + + """ + # Amount of time, in seconds, we're willing to wait for the subprocess + _SUBPROC_TIMEOUT = 10 + + def __init__( + self, # pylint: disable=R0913 + remote_addr, + remote_addr_family=socket.AF_INET, + remote_socket_type=socket.SOCK_STREAM, + server_addr=("127.0.0.1", 0), + server_addr_family=socket.AF_INET, + server_socket_type=socket.SOCK_STREAM, + local_linger_args=None): + """ + :param tuple remote_addr: remote server's IP address, whose structure + depends on remote_addr_family; pair (host-or-ip-addr, port-number). + Pass None to have ForwardServer behave as echo server. + :param remote_addr_family: socket.AF_INET (the default), socket.AF_INET6 + or socket.AF_UNIX. + :param remote_socket_type: only socket.SOCK_STREAM is supported at this + time + :param server_addr: optional address for binding this server's listening + socket; the format depends on server_addr_family; defaults to + ("127.0.0.1", 0) + :param server_addr_family: Address family for this server's listening + socket; socket.AF_INET (the default), socket.AF_INET6 or + socket.AF_UNIX; defaults to socket.AF_INET + :param server_socket_type: only socket.SOCK_STREAM is supported at this + time + :param tuple local_linger_args: SO_LINGER sockoverride for the local + connection sockets, to be configured after connection is accepted. + None for default, which is to not change the SO_LINGER option. + Otherwise, its a two-tuple, where the first element is the `l_onoff` + switch, and the second element is the `l_linger` value, in seconds + """ + self._logger = logging.getLogger(__name__) + + self._remote_addr = remote_addr + self._remote_addr_family = remote_addr_family + assert remote_socket_type == socket.SOCK_STREAM, remote_socket_type + self._remote_socket_type = remote_socket_type + + assert server_addr is not None + self._server_addr = server_addr + + assert server_addr_family is not None + self._server_addr_family = server_addr_family + + assert server_socket_type == socket.SOCK_STREAM, server_socket_type + self._server_socket_type = server_socket_type + + self._local_linger_args = local_linger_args + + self._subproc = None + + @property + def running(self): + """Property: True if ForwardServer is active""" + return self._subproc is not None + + @property + def server_address_family(self): + """Property: Get listening socket's address family + + NOTE: undefined before server starts and after it shuts down + """ + assert self._server_addr_family is not None, "Not in context" + + return self._server_addr_family + + @property + def server_address(self): + """ Property: Get listening socket's address; the returned value + depends on the listening socket's address family + + NOTE: undefined before server starts and after it shuts down + """ + assert self._server_addr is not None, "Not in context" + + return self._server_addr + + def __enter__(self): + """ Context manager entry. Starts the forwarding server + + :returns: self + """ + return self.start() + + def __exit__(self, *args): + """ Context manager exit; stops the forwarding server + """ + self.stop() + + def start(self): + """ Start the server + + NOTE: The context manager is the recommended way to use + ForwardServer. start()/stop() are alternatives to the context manager + use case and are mutually exclusive with it. + + :returns: self + """ + queue = multiprocessing.Queue() + + self._subproc = multiprocessing.Process( + target=_run_server, + kwargs=dict( + local_addr=self._server_addr, + local_addr_family=self._server_addr_family, + local_socket_type=self._server_socket_type, + local_linger_args=self._local_linger_args, + remote_addr=self._remote_addr, + remote_addr_family=self._remote_addr_family, + remote_socket_type=self._remote_socket_type, + queue=queue)) + self._subproc.daemon = True + self._subproc.start() + + try: + # Get server socket info from subprocess + self._server_addr_family, self._server_addr = queue.get( + block=True, timeout=self._SUBPROC_TIMEOUT) + queue.close() + except Exception: # pylint: disable=W0703 + try: + self._logger.exception( + "Failed while waiting for local socket info") + # Preserve primary exception and traceback + raise + finally: + # Clean up + try: + self.stop() + except Exception: # pylint: disable=W0703 + # Suppress secondary exception in favor of the primary + self._logger.exception( + "Emergency subprocess shutdown failed") + + return self + + def stop(self): + """Stop the server + + NOTE: The context manager is the recommended way to use + ForwardServer. start()/stop() are alternatives to the context manager + use case and are mutually exclusive with it. + """ + self._logger.info("ForwardServer STOPPING") + + try: + self._subproc.terminate() + self._subproc.join(timeout=self._SUBPROC_TIMEOUT) + if self._subproc.is_alive(): + self._logger.error( + "ForwardServer failed to terminate, killing it") + os.kill(self._subproc.pid) + self._subproc.join(timeout=self._SUBPROC_TIMEOUT) + assert not self._subproc.is_alive(), self._subproc + + # Log subprocess's exit code; NOTE: negative signal.SIGTERM (usually + # -15) is normal on POSIX systems - it corresponds to SIGTERM + exit_code = self._subproc.exitcode + self._logger.info("ForwardServer terminated with exitcode=%s", + exit_code) + finally: + self._subproc = None + + +def _run_server( + local_addr, + local_addr_family, + local_socket_type, # pylint: disable=R0913 + local_linger_args, + remote_addr, + remote_addr_family, + remote_socket_type, + queue): + """ Run the server; executed in the subprocess + + :param local_addr: listening address + :param local_addr_family: listening address family; one of socket.AF_* + :param local_socket_type: listening socket type; typically + socket.SOCK_STREAM + :param tuple local_linger_args: SO_LINGER sockoverride for the local + connection sockets, to be configured after connection is accepted. + Pass None to not change SO_LINGER. Otherwise, its a two-tuple, where the + first element is the `l_onoff` switch, and the second element is the + `l_linger` value in seconds + :param remote_addr: address of the target server. Pass None to have + ForwardServer behave as echo server + :param remote_addr_family: address family for connecting to target server; + one of socket.AF_* + :param remote_socket_type: socket type for connecting to target server; + typically socket.SOCK_STREAM + :param multiprocessing.Queue queue: queue for depositing the forwarding + server's actual listening socket address family and bound address. The + parent process waits for this. + """ + + # NOTE: We define _ThreadedTCPServer class as a closure in order to + # override some of its class members dynamically + # NOTE: we add `object` to the base classes because `_ThreadedTCPServer` + # isn't derived from `object`, which prevents `super` from working properly + class _ThreadedTCPServer(SocketServer.ThreadingMixIn, + SocketServer.TCPServer, object): + """Threaded streaming server for forwarding""" + + # Override TCPServer's class members + address_family = local_addr_family + socket_type = local_socket_type + allow_reuse_address = True + + def __init__(self): + + handler_class_factory = partial( + _TCPHandler, + local_linger_args=local_linger_args, + remote_addr=remote_addr, + remote_addr_family=remote_addr_family, + remote_socket_type=remote_socket_type) + + super(_ThreadedTCPServer, self).__init__( + local_addr, handler_class_factory, bind_and_activate=True) + + server = _ThreadedTCPServer() + + # Send server socket info back to parent process + queue.put([server.socket.family, server.server_address]) + + queue.close() + + server.serve_forever() + + +# NOTE: we add `object` to the base classes because `StreamRequestHandler` isn't +# derived from `object`, which prevents `super` from working properly +class _TCPHandler(SocketServer.StreamRequestHandler, object): + """TCP/IP session handler instantiated by TCPServer upon incoming + connection. Implements forwarding/echo of the incoming connection. + """ + + _SOCK_RX_BUF_SIZE = 16 * 1024 + + def __init__( + self, # pylint: disable=R0913 + request, + client_address, + server, + local_linger_args, + remote_addr, + remote_addr_family, + remote_socket_type): + """ + :param request: for super + :param client_address: for super + "paarm server: for super + :param tuple local_linger_args: SO_LINGER sockoverride for the local + connection sockets, to be configured after connection is accepted. + Pass None to not change SO_LINGER. Otherwise, its a two-tuple, where + the first element is the `l_onoff` switch, and the second element is + the `l_linger` value in seconds + :param remote_addr: address of the target server. Pass None to have + ForwardServer behave as echo server. + :param remote_addr_family: address family for connecting to target + server; one of socket.AF_* + :param remote_socket_type: socket type for connecting to target server; + typically socket.SOCK_STREAM + :param **kwargs: kwargs for super class + """ + self._local_linger_args = local_linger_args + self._remote_addr = remote_addr + self._remote_addr_family = remote_addr_family + self._remote_socket_type = remote_socket_type + + super(_TCPHandler, self).__init__( + request=request, client_address=client_address, server=server) + + def handle(self): # pylint: disable=R0912 + """Connect to remote and forward data between local and remote""" + local_sock = self.connection + + if self._local_linger_args is not None: + # Set SO_LINGER socket options on local socket + l_onoff, l_linger = self._local_linger_args + local_sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, + struct.pack('ii', l_onoff, l_linger)) + + if self._remote_addr is not None: + # Forwarding set-up + remote_dest_sock = remote_src_sock = socket.socket( + family=self._remote_addr_family, + type=self._remote_socket_type, + proto=socket.IPPROTO_IP) + remote_dest_sock.connect(self._remote_addr) + _trace("%s _TCPHandler connected to remote %s", datetime.utcnow(), + remote_dest_sock.getpeername()) + else: + # Echo set-up + remote_dest_sock, remote_src_sock = \ + pika.compat._nonblocking_socketpair() + + try: + local_forwarder = threading.Thread( + target=self._forward, args=( + local_sock, + remote_dest_sock, + )) + local_forwarder.setDaemon(True) + local_forwarder.start() + + try: + self._forward(remote_src_sock, local_sock) + finally: + # Wait for local forwarder thread to exit + local_forwarder.join() + finally: + try: + try: + _safe_shutdown_socket(remote_dest_sock, socket.SHUT_RDWR) + finally: + if remote_src_sock is not remote_dest_sock: + _safe_shutdown_socket(remote_src_sock, socket.SHUT_RDWR) + finally: + remote_dest_sock.close() + if remote_src_sock is not remote_dest_sock: + remote_src_sock.close() + + def _forward(self, src_sock, dest_sock): # pylint: disable=R0912 + """Forward from src_sock to dest_sock""" + src_peername = src_sock.getpeername() + + _trace("%s forwarding from %s to %s", datetime.utcnow(), src_peername, + dest_sock.getpeername()) + try: + # NOTE: python 2.6 doesn't support bytearray with recv_into, so + # we use array.array instead; this is only okay as long as the + # array instance isn't shared across threads. See + # http://bugs.python.org/issue7827 and + # groups.google.com/forum/#!topic/comp.lang.python/M6Pqr-KUjQw + rx_buf = array.array("B", [0] * self._SOCK_RX_BUF_SIZE) + + while True: + try: + nbytes = src_sock.recv_into(rx_buf) + except pika.compat.SOCKET_ERROR as exc: + if exc.errno == errno.EINTR: + continue + elif exc.errno == errno.ECONNRESET: + # Source peer forcibly closed connection + _trace("%s errno.ECONNRESET from %s", datetime.utcnow(), + src_peername) + break + else: + _trace("%s Unexpected errno=%s from %s\n%s", + datetime.utcnow(), exc.errno, src_peername, + "".join(traceback.format_stack())) + raise + + if not nbytes: + # Source input EOF + _trace("%s EOF on %s", datetime.utcnow(), src_peername) + break + + try: + dest_sock.sendall(buffer(rx_buf, 0, nbytes)) + except pika.compat.SOCKET_ERROR as exc: + if exc.errno == errno.EPIPE: + # Destination peer closed its end of the connection + _trace("%s Destination peer %s closed its end of " + "the connection: errno.EPIPE", datetime.utcnow(), + dest_sock.getpeername()) + break + elif exc.errno == errno.ECONNRESET: + # Destination peer forcibly closed connection + _trace("%s Destination peer %s forcibly closed " + "connection: errno.ECONNRESET", + datetime.utcnow(), dest_sock.getpeername()) + break + else: + _trace("%s Unexpected errno=%s in sendall to %s\n%s", + datetime.utcnow(), exc.errno, + dest_sock.getpeername(), "".join( + traceback.format_stack())) + raise + except: + _trace("forward failed\n%s", "".join(traceback.format_exc())) + raise + finally: + _trace("%s done forwarding from %s", datetime.utcnow(), + src_peername) + try: + # Let source peer know we're done receiving + _safe_shutdown_socket(src_sock, socket.SHUT_RD) + finally: + # Let destination peer know we're done sending + _safe_shutdown_socket(dest_sock, socket.SHUT_WR) + + +def echo(port=0): + """ This function implements a simple echo server for testing the + Forwarder class. + + :param int port: port number on which to listen + + We run this function and it prints out the listening socket binding. + Then, we run Forwarder and point it at this echo "server". + Then, we run telnet and point it at forwarder and see if whatever we + type gets echoed back to us. + + This function exits when the remote end connects, then closes connection + """ + lsock = socket.socket() + lsock.bind(("", port)) + lsock.listen(1) + _trace("Listening on sockname=%s", lsock.getsockname()) + + sock, remote_addr = lsock.accept() + try: + _trace("Connection from peer=%s", remote_addr) + while True: + try: + data = sock.recv(4 * 1024) # pylint: disable=E1101 + except pika.compat.SOCKET_ERROR as exc: + if exc.errno == errno.EINTR: + continue + else: + raise + + if not data: + break + + sock.sendall(data) # pylint: disable=E1101 + finally: + try: + _safe_shutdown_socket(sock, socket.SHUT_RDWR) + finally: + sock.close() + + +def _safe_shutdown_socket(sock, how=socket.SHUT_RDWR): + """ Shutdown a socket, suppressing ENOTCONN + """ + try: + sock.shutdown(how) + except pika.compat.SOCKET_ERROR as exc: + if exc.errno != errno.ENOTCONN: + raise diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/test_utils.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/test_utils.py new file mode 100644 index 000000000..adf5934cc --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/acceptance/test_utils.py @@ -0,0 +1,73 @@ +"""Acceptance test utils""" + +import functools +import logging +import time +import traceback + + +def retry_assertion(timeout_sec, retry_interval_sec=0.1): + """Creates a decorator that retries the decorated function or + method only upon `AssertionError` exception at the given retry interval + not to exceed the overall given timeout. + + :param float timeout_sec: overall timeout in seconds + :param float retry_interval_sec: amount of time to sleep + between retries in seconds. + + :returns: decorator that implements the following behavior + + 1. This decorator guarantees to call the decorated function or method at + least once. + 2. It passes through all exceptions besides `AssertionError`, preserving the + original exception and its traceback. + 3. If no exception, it returns the return value from the decorated function/method. + 4. It sleeps `time.sleep(retry_interval_sec)` between retries. + 5. It checks for expiry of the overall timeout before sleeping. + 6. If the overall timeout is exceeded, it re-raises the latest `AssertionError`, + preserving its original traceback + """ + + def retry_assertion_decorator(func): + """Decorator""" + + @functools.wraps(func) + def retry_assertion_wrap(*args, **kwargs): + """The wrapper""" + + num_attempts = 0 + start_time = time.time() + + while True: + num_attempts += 1 + + try: + result = func(*args, **kwargs) + except AssertionError: + + now = time.time() + # Compensate for time adjustment + if now < start_time: + start_time = now + + if (now - start_time) > timeout_sec: + logging.exception( + 'Exceeded retry timeout of %s sec in %s attempts ' + 'with func %r. Caller\'s stack:\n%s', + timeout_sec, num_attempts, func, + ''.join(traceback.format_stack())) + raise + + logging.debug('Attempt %s failed; retrying %r in %s sec.', + num_attempts, func, retry_interval_sec) + + time.sleep(retry_interval_sec) + else: + logging.debug('%r succeeded at attempt %s', + func, num_attempts) + return result + + return retry_assertion_wrap + + return retry_assertion_decorator + diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/amqp_object_tests.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/amqp_object_tests.py new file mode 100644 index 000000000..998a95b83 --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/amqp_object_tests.py @@ -0,0 +1,62 @@ +import unittest + +from pika import amqp_object + + +class AMQPObjectTests(unittest.TestCase): + def test_base_name(self): + self.assertEqual(amqp_object.AMQPObject().NAME, 'AMQPObject') + + def test_repr_no_items(self): + obj = amqp_object.AMQPObject() + self.assertEqual(repr(obj), '') + + def test_repr_items(self): + obj = amqp_object.AMQPObject() + setattr(obj, 'foo', 'bar') + setattr(obj, 'baz', 'qux') + self.assertEqual(repr(obj), "") + + +class ClassTests(unittest.TestCase): + def test_base_name(self): + self.assertEqual(amqp_object.Class().NAME, 'Unextended Class') + + +class MethodTests(unittest.TestCase): + def test_base_name(self): + self.assertEqual(amqp_object.Method().NAME, 'Unextended Method') + + def test_set_content_body(self): + properties = amqp_object.Properties() + body = 'This is a test' + obj = amqp_object.Method() + obj._set_content(properties, body) + self.assertEqual(obj._body, body) + + def test_set_content_properties(self): + properties = amqp_object.Properties() + body = 'This is a test' + obj = amqp_object.Method() + obj._set_content(properties, body) + self.assertEqual(obj._properties, properties) + + def test_get_body(self): + properties = amqp_object.Properties() + body = 'This is a test' + obj = amqp_object.Method() + obj._set_content(properties, body) + self.assertEqual(obj.get_body(), body) + + def test_get_properties(self): + properties = amqp_object.Properties() + body = 'This is a test' + obj = amqp_object.Method() + obj._set_content(properties, body) + self.assertEqual(obj.get_properties(), properties) + + +class PropertiesTests(unittest.TestCase): + def test_base_name(self): + self.assertEqual(amqp_object.Properties().NAME, + 'Unextended Properties') diff --git a/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/base_connection_tests.py b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/base_connection_tests.py new file mode 100644 index 000000000..b010d729f --- /dev/null +++ b/firmware/Venus_Release/VenusReleaseFiles/pika-0.13.1/tests/unit/base_connection_tests.py @@ -0,0 +1,85 @@ +""" +Tests for pika.base_connection.BaseConnection + +""" + +import socket +import unittest + +import mock + +import pika +import sys +import ssl + +import pika.tcp_socket_opts +from pika.adapters import base_connection + + +# If this is missing, set it manually. We need it to test tcp opt setting. +try: + socket.TCP_KEEPIDLE +except AttributeError: + socket.TCP_KEEPIDLE = 4 + + +class BaseConnectionTests(unittest.TestCase): + def setUp(self): + with mock.patch('pika.connection.Connection.connect'): + self.connection = base_connection.BaseConnection() + self.connection._set_connection_state( + base_connection.BaseConnection.CONNECTION_OPEN) + + def test_repr(self): + text = repr(self.connection) + self.assertTrue(text.startswith('I', encoded, offset)[0]") + print(prefix + "offset += 4") + print(prefix + "%s = encoded[offset:offset + length]" % cLvalue) + print(prefix + "try:") + print(prefix + " %s = str(%s)" % (cLvalue, cLvalue)) + print(prefix + "except UnicodeEncodeError:") + print(prefix + " pass") + print(prefix + "offset += length") + elif type == 'octet': + print(prefix + "%s = struct.unpack_from('B', encoded, offset)[0]" % + cLvalue) + print(prefix + "offset += 1") + elif type == 'short': + print(prefix + "%s = struct.unpack_from('>H', encoded, offset)[0]" % + cLvalue) + print(prefix + "offset += 2") + elif type == 'long': + print(prefix + "%s = struct.unpack_from('>I', encoded, offset)[0]" % + cLvalue) + print(prefix + "offset += 4") + elif type == 'longlong': + print(prefix + "%s = struct.unpack_from('>Q', encoded, offset)[0]" % + cLvalue) + print(prefix + "offset += 8") + elif type == 'timestamp': + print(prefix + "%s = struct.unpack_from('>Q', encoded, offset)[0]" % + cLvalue) + print(prefix + "offset += 8") + elif type == 'bit': + raise Exception("Can't decode bit in genSingleDecode") + elif type == 'table': + print(Exception(prefix + "(%s, offset) = data.decode_table(encoded, offset)" % + cLvalue)) + else: + raise Exception("Illegal domain in genSingleDecode", type) + + def genSingleEncode(prefix, cValue, unresolved_domain): + type = spec.resolveDomain(unresolved_domain) + if type == 'shortstr': + print(prefix + + "assert isinstance(%s, str_or_bytes),\\\n%s 'A non-string value was supplied for %s'" + % (cValue, prefix, cValue)) + print(prefix + "data.encode_short_string(pieces, %s)" % cValue) + elif type == 'longstr': + print(prefix + + "assert isinstance(%s, str_or_bytes),\\\n%s 'A non-string value was supplied for %s'" + % (cValue, prefix, cValue)) + print( + prefix + + "value = %s.encode('utf-8') if isinstance(%s, unicode_type) else %s" + % (cValue, cValue, cValue)) + print(prefix + "pieces.append(struct.pack('>I', len(value)))") + print(prefix + "pieces.append(value)") + elif type == 'octet': + print(prefix + "pieces.append(struct.pack('B', %s))" % cValue) + elif type == 'short': + print(prefix + "pieces.append(struct.pack('>H', %s))" % cValue) + elif type == 'long': + print(prefix + "pieces.append(struct.pack('>I', %s))" % cValue) + elif type == 'longlong': + print(prefix + "pieces.append(struct.pack('>Q', %s))" % cValue) + elif type == 'timestamp': + print(prefix + "pieces.append(struct.pack('>Q', %s))" % cValue) + elif type == 'bit': + raise Exception("Can't encode bit in genSingleEncode") + elif type == 'table': + print(Exception(prefix + "data.encode_table(pieces, %s)" % cValue)) + else: + raise Exception("Illegal domain in genSingleEncode", type) + + def genDecodeMethodFields(m): + print(" def decode(self, encoded, offset=0):") + bitindex = None + for f in m.arguments: + if spec.resolveDomain(f.domain) == 'bit': + if bitindex is None: + bitindex = 0 + if bitindex >= 8: + bitindex = 0 + if not bitindex: + print( + " bit_buffer = struct.unpack_from('B', encoded, offset)[0]") + print(" offset += 1") + print(" self.%s = (bit_buffer & (1 << %d)) != 0" % + (pyize(f.name), bitindex)) + bitindex += 1 + else: + bitindex = None + genSingleDecode(" ", "self.%s" % (pyize(f.name),), + f.domain) + print(" return self") + print('') + + def genDecodeProperties(c): + print(" def decode(self, encoded, offset=0):") + print(" flags = 0") + print(" flagword_index = 0") + print(" while True:") + print( + " partial_flags = struct.unpack_from('>H', encoded, offset)[0]") + print(" offset += 2") + print( + " flags = flags | (partial_flags << (flagword_index * 16))") + print(" if not (partial_flags & 1):") + print(" break") + print(" flagword_index += 1") + for f in c.fields: + if spec.resolveDomain(f.domain) == 'bit': + print(" self.%s = (flags & %s) != 0" % + (pyize(f.name), flagName(c, f))) + else: + print(" if flags & %s:" % (flagName(c, f),)) + genSingleDecode(" ", "self.%s" % (pyize(f.name),), + f.domain) + print(" else:") + print(" self.%s = None" % (pyize(f.name),)) + print(" return self") + print('') + + def genEncodeMethodFields(m): + print(" def encode(self):") + print(" pieces = list()") + bitindex = None + + def finishBits(): + if bitindex is not None: + print(" pieces.append(struct.pack('B', bit_buffer))") + + for f in m.arguments: + if spec.resolveDomain(f.domain) == 'bit': + if bitindex is None: + bitindex = 0 + print(" bit_buffer = 0") + if bitindex >= 8: + finishBits() + print(" bit_buffer = 0") + bitindex = 0 + print(" if self.%s:" % pyize(f.name)) + print(" bit_buffer = bit_buffer | (1 << %d)" % + bitindex) + bitindex += 1 + else: + finishBits() + bitindex = None + genSingleEncode(" ", "self.%s" % (pyize(f.name),), + f.domain) + finishBits() + print(" return pieces") + print('') + + def genEncodeProperties(c): + print(" def encode(self):") + print(" pieces = list()") + print(" flags = 0") + for f in c.fields: + if spec.resolveDomain(f.domain) == 'bit': + print(" if self.%s: flags = flags | %s" % + (pyize(f.name), flagName(c, f))) + else: + print(" if self.%s is not None:" % (pyize(f.name),)) + print(" flags = flags | %s" % (flagName(c, f),)) + genSingleEncode(" ", "self.%s" % (pyize(f.name),), + f.domain) + print(" flag_pieces = list()") + print(" while True:") + print(" remainder = flags >> 16") + print(" partial_flags = flags & 0xFFFE") + print(" if remainder != 0:") + print(" partial_flags |= 1") + print( + " flag_pieces.append(struct.pack('>H', partial_flags))") + print(" flags = remainder") + print(" if not flags:") + print(" break") + print(" return flag_pieces + pieces") + print('') + + def fieldDeclList(fields): + return ''.join([", %s=%s" % (pyize(f.name), fieldvalue(f.defaultvalue)) + for f in fields]) + + def fieldInitList(prefix, fields): + if fields: + return ''.join(["%sself.%s = %s\n" % (prefix, pyize(f.name), pyize(f.name)) \ + for f in fields]) + else: + return '%spass\n' % (prefix,) + + print("""\"\"\" +AMQP Specification +================== +This module implements the constants and classes that comprise AMQP protocol +level constructs. It should rarely be directly referenced outside of Pika's +own internal use. + +.. note:: Auto-generated code by codegen.py, do not edit directly. Pull +requests to this file without accompanying ``utils/codegen.py`` changes will be +rejected. + +\"\"\" + +import struct +from pika import amqp_object +from pika import data +from pika.compat import str_or_bytes, unicode_type + +# Python 3 support for str object +str = bytes +""") + + print("PROTOCOL_VERSION = (%d, %d, %d)" % (spec.major, spec.minor, + spec.revision)) + print("PORT = %d" % spec.port) + print('') + + # Append some constants that arent in the spec json file + spec.constants.append(('FRAME_MAX_SIZE', 131072, '')) + spec.constants.append(('FRAME_HEADER_SIZE', 7, '')) + spec.constants.append(('FRAME_END_SIZE', 1, '')) + spec.constants.append(('TRANSIENT_DELIVERY_MODE', 1, '')) + spec.constants.append(('PERSISTENT_DELIVERY_MODE', 2, '')) + + constants = {} + for c, v, cls in spec.constants: + constants[constantName(c)] = v + + for key in sorted(constants.keys()): + print("%s = %s" % (key, constants[key])) + print('') + + for c in spec.allClasses(): + print('') + print('class %s(amqp_object.Class):' % (camel(c.name),)) + print('') + print(" INDEX = 0x%.04X # %d" % (c.index, c.index)) + print(" NAME = %s" % (fieldvalue(camel(c.name)),)) + print('') + + for m in c.allMethods(): + print(' class %s(amqp_object.Method):' % (camel(m.name),)) + print('') + methodid = m.klass.index << 16 | m.index + print(" INDEX = 0x%.08X # %d, %d; %d" % + (methodid, + m.klass.index, + m.index, + methodid)) + print(" NAME = %s" % (fieldvalue(m.structName(),))) + print('') + print(" def __init__(self%s):" % + (fieldDeclList(m.arguments),)) + print(fieldInitList(' ', m.arguments)) + print(" @property") + print(" def synchronous(self):") + print(" return %s" % m.isSynchronous) + print('') + genDecodeMethodFields(m) + genEncodeMethodFields(m) + + for c in spec.allClasses(): + if c.fields: + print('') + print('class %s(amqp_object.Properties):' % (c.structName(),)) + print('') + print(" CLASS = %s" % (camel(c.name),)) + print(" INDEX = 0x%.04X # %d" % (c.index, c.index)) + print(" NAME = %s" % (fieldvalue(c.structName(),))) + print('') + + index = 0 + if c.fields: + for f in c.fields: + if index % 16 == 15: + index += 1 + shortnum = index / 16 + partialindex = 15 - (index % 16) + bitindex = shortnum * 16 + partialindex + print(' %s = (1 << %d)' % (flagName(None, f), bitindex)) + index += 1 + print('') + + print(" def __init__(self%s):" % (fieldDeclList(c.fields),)) + print(fieldInitList(' ', c.fields)) + genDecodeProperties(c) + genEncodeProperties(c) + + print("methods = {") + print(',\n'.join([" 0x%08X: %s" % (m.klass.index << 16 | m.index, m.structName()) + for m in spec.allMethods()])) + print("}") + print('') + + print("props = {") + print(',\n'.join([" 0x%04X: %s" % (c.index, c.structName()) + for c in spec.allClasses() + if c.fields])) + print("}") + print('') + print('') + + print("def has_content(methodNumber):") + print(' return methodNumber in (') + for m in spec.allMethods(): + if m.hasContent: + print(' %s.INDEX,' % m.structName()) + print(' )') + + +if __name__ == "__main__": + with open(PIKA_SPEC, 'w') as handle: + sys.stdout = handle + generate(['%s/amqp-rabbitmq-0.9.1.json' % CODEGEN_PATH]) diff --git a/firmware/Venus_Release/rc.local b/firmware/Venus_Release/VenusReleaseFiles/rc.local similarity index 100% rename from firmware/Venus_Release/rc.local rename to firmware/Venus_Release/VenusReleaseFiles/rc.local diff --git a/firmware/Venus_Release/Vernus Rollout Guidance.pdf b/firmware/Venus_Release/Vernus Rollout Guidance.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6d73e16e3408c60a43338dead423f5c485d42c0f GIT binary patch literal 194481 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

{&|O zX84hV1lV(Wy5Mi>u?{PSK<}&?IlC(RBIPD^$Vv{udAnN;IE{jx($E<-tC0NVNl^x+ zkR)B{wvXKF^j%A=v(IF37Qzn0uFAx{o$aSrwS>8W_+w)5!(4=%uQy{NBJWw37V6*2{r01gt5W*8dw;cE zAO#GKtToF*;EU2})c9Rooy89fh>_B#{M)g;gl1+Sh!dO11yBhiPj_2HA@xw)H@v-(eCi}w`dI0HrU*Z_B(B%8E&|x7udH@RgW*F^;+VTg+jtxOy!d9Yx6=5Ou%j|AWT0y-6HzV}F z`NHBMpz7AYPYh(Kqu{S}p`ljGA(5m$M)<+UJoHomE#J_C(Z18uDE58%gR&qV;df_4 zEN59%pJ6@|26V|bgLCCKgi+iQ%ll+aj__0-$RMh4U9Sy&7vOr%>7eD4nMe4H`S_$f zqMc$~$95EpH{rqK*Sh7e@X)N&xw~;+zKlMRO-jV3JUgQrp;TXXwqPA2RVBCt_vVI* z#EunmMUFs^e7b2D>R#b& z@y)v(_TmWA8iZ~Jt@F2Q&rB&XlLu`h&n?MonTZ-_U!qkmyQSE!WMg$kQJ+7nq^ zt}9lS_vr}Hm}sIj=p=}CrgJE?_!4X#iX+L60jokl^v&b5GNZK&+}$o;Zzntuz$CFX z1GNYrS_$vIc%rgwqa=f0)-kN6z|j7)RsV#ls?gUwiqyKdt`<)$M z_WO=%z6=gHY^8#D?AmRFRz6E+2^W^!`Fr$d%CIniW60P~!B&@|Ny_K-jW*H-qsFZC zFC81XDGsb(vmT08Gys^A`&UeV{k{d)$ES+v$HKzYT56~QGf|~sX5i-`)OJ*D&YAaC z?G-5)ucMRi++-I7CpW0sRy}cYTHz#M_ck{J^2+q#zPw)0+Prq-UGvT|S~}oo65}P` z5gNGLk#U@%{VddggGF9g@T>5+gZ*?}GBA9%oU>9w!&P^;f-fz92BsHS^BS{!9?$G< zb-`~vChL&g~h+yIrLRke;m1iA@lVeQJT{G zwTO`7?8c6c7uDU)?ANzlL5z+6Ds@=@L+Ge^I+~L)D;WbJvPQ++#lh9t)ZB#(B8`-s z9ZXftL1Ykcub2cGvzoaFh>Tg<4pLF%pGVPu9;NjWnZ-RolBytx-1^g0l8uZF(t`>j zvm_T8*Pk;%K}2RPMH6dtQ_!CZTHIvZ|5hyFVE?yFf}M>0AB`k9$Tfh^≀1fP)P3{U^EpPw0Zk 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нbCyun??OI1$l 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