rabbitmq-amqp-python-client 2.0.0a1__tar.gz → 2.0.0a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.github/workflows/publish-pypi.yaml +6 -7
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/Makefile +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/PKG-INFO +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/README.md +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/README.md +3 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/auto_reconnection.py +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/basic_connection.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/console_application.py +8 -8
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/consumer_example.py +2 -2
- rabbitmq_amqp_python_client-2.0.0a3/docs/examples/consumer_timeout_example.py +135 -0
- rabbitmq_amqp_python_client-2.0.0a3/docs/examples/delayed_retry_context_example.py +101 -0
- rabbitmq_amqp_python_client-2.0.0a3/docs/examples/delayed_retry_example.py +146 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/direct_reply_to_example.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/management_example.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/performance_test.py +6 -6
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/presettled_consumer_example.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/publisher_example.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/quorum_single_active_consumer.py +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/rejection_reason_example.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/stream_filtering.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/tls_connection.py +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/pyproject.toml +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/__init__.py +5 -1
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/constants.py +6 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/consumer.py +280 -2
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/link.py +41 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/logging_utils.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/management.py +16 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/__init__.py +2 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_client_interpolation_integration.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_client_stream_interpolation_integration.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_connection_integration.py +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_consumer_integration.py +307 -9
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_management_integration.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_performance_test_integration.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_publisher_integration.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_reconnection_integration.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_session_link_integration.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_tls_integration.py +1 -1
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/conftest.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/fake_broker.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_amqp10_validation.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_connection.py +7 -7
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_console_application.py +5 -5
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_consumer.py +301 -11
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_delivery_state.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_encoding.py +2 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_frames.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_link.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_management.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_management_builders.py +35 -2
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_message.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_package_exports.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_performance_test.py +5 -5
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_performatives.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_publisher.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_reconnection.py +10 -10
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_sasl.py +3 -3
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/test_session.py +4 -4
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/ca.p12 +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/ca_certificate.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/ca_key.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/client_localhost.p12 +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/client_localhost_certificate.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/client_localhost_key.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/server_localhost.p12 +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/server_localhost_certificate.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/certs/server_localhost_key.pem +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/advanced.config +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/enabled_plugins +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/gha-log-check.sh +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/gha-setup.sh +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/log/.gitkeep +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.ci/ubuntu/rabbitmq.conf +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.github/release.yml +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.github/workflows/build-test.yaml +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.github/workflows/publish_release.yaml +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/.gitignore +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/create_tag.sh +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/connection.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/exceptions.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/publisher.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/reconnection.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/session.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/delivery_state.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/encoding.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/frames.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/message.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/performatives.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1/src → rabbitmq_amqp_python_client-2.0.0a3/rabbitmq_amqp_python_client}/wire/sasl.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/__init__.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/__init__.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/conftest.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/integration/test_console_application_integration.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/__init__.py +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/empty_message.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/header_amqpvalue_message.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_body_250.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_body_700.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_from_version_1_0_0.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_random_application_properties_300.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_random_application_properties_500.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_random_application_properties_properties_900.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/message_unicode_message.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/nil_and_types.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/shovel_annotations.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/static_test_message_compare.amqp +0 -0
- {rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/tests/unit/resources/amqp10_validation/uuid_message.amqp +0 -0
|
@@ -25,14 +25,13 @@ jobs:
|
|
|
25
25
|
id-token: write
|
|
26
26
|
steps:
|
|
27
27
|
- uses: actions/checkout@v3
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- name: build
|
|
31
32
|
run: |
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
poetry config repositories.test-pypi $REPO
|
|
35
|
-
poetry build
|
|
33
|
+
python3 -m pip install build
|
|
34
|
+
python3 -m build
|
|
36
35
|
# retrieve your distributions here
|
|
37
36
|
- name: Publish package distributions to PyPI
|
|
38
37
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rabbitmq-amqp-python-client
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a3
|
|
4
4
|
Summary: A from-scratch AMQP 1.0 client for RabbitMQ, built directly against the wire protocol.
|
|
5
5
|
Author: RabbitMQ Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -42,7 +42,7 @@ pip install -e ".[dev]"
|
|
|
42
42
|
## Quick start
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
|
-
from
|
|
45
|
+
from rabbitmq_amqp_python_client import Connection, ConnectionParameters, Context, Message
|
|
46
46
|
|
|
47
47
|
connection = Connection(ConnectionParameters()) # localhost:5672, guest/guest
|
|
48
48
|
try:
|
|
@@ -84,7 +84,7 @@ PYTHONPATH=. .venv/bin/python docs/examples/basic_connection.py
|
|
|
84
84
|
make install # create .venv and install the package with its dev extras
|
|
85
85
|
make format # ruff format, then ruff check --fix
|
|
86
86
|
make lint # ruff check and ruff format --check
|
|
87
|
-
make typecheck # mypy
|
|
87
|
+
make typecheck # mypy rabbitmq_amqp_python_client, in strict mode
|
|
88
88
|
make test-unit # the unit suite; no broker needed
|
|
89
89
|
make test-integration # the integration suite; needs a broker on localhost:5672
|
|
90
90
|
make test # both suites
|
|
@@ -98,7 +98,7 @@ MYPYPATH=. .venv/bin/python -m mypy docs/examples/*.py
|
|
|
98
98
|
|
|
99
99
|
## Project layout
|
|
100
100
|
|
|
101
|
-
`
|
|
101
|
+
`rabbitmq_amqp_python_client/` holds the client: `wire/` is the protocol
|
|
102
102
|
layer (type codec, frames, performatives, messages, SASL) and knows nothing
|
|
103
103
|
above itself, and `connection.py`, `session.py`, `link.py`, `management.py`,
|
|
104
104
|
`publisher.py`, `consumer.py` and `reconnection.py` build the public API on top
|
|
@@ -26,7 +26,7 @@ pip install -e ".[dev]"
|
|
|
26
26
|
## Quick start
|
|
27
27
|
|
|
28
28
|
```python
|
|
29
|
-
from
|
|
29
|
+
from rabbitmq_amqp_python_client import Connection, ConnectionParameters, Context, Message
|
|
30
30
|
|
|
31
31
|
connection = Connection(ConnectionParameters()) # localhost:5672, guest/guest
|
|
32
32
|
try:
|
|
@@ -68,7 +68,7 @@ PYTHONPATH=. .venv/bin/python docs/examples/basic_connection.py
|
|
|
68
68
|
make install # create .venv and install the package with its dev extras
|
|
69
69
|
make format # ruff format, then ruff check --fix
|
|
70
70
|
make lint # ruff check and ruff format --check
|
|
71
|
-
make typecheck # mypy
|
|
71
|
+
make typecheck # mypy rabbitmq_amqp_python_client, in strict mode
|
|
72
72
|
make test-unit # the unit suite; no broker needed
|
|
73
73
|
make test-integration # the integration suite; needs a broker on localhost:5672
|
|
74
74
|
make test # both suites
|
|
@@ -82,7 +82,7 @@ MYPYPATH=. .venv/bin/python -m mypy docs/examples/*.py
|
|
|
82
82
|
|
|
83
83
|
## Project layout
|
|
84
84
|
|
|
85
|
-
`
|
|
85
|
+
`rabbitmq_amqp_python_client/` holds the client: `wire/` is the protocol
|
|
86
86
|
layer (type codec, frames, performatives, messages, SASL) and knows nothing
|
|
87
87
|
above itself, and `connection.py`, `session.py`, `link.py`, `management.py`,
|
|
88
88
|
`publisher.py`, `consumer.py` and `reconnection.py` build the public API on top
|
{rabbitmq_amqp_python_client-2.0.0a1 → rabbitmq_amqp_python_client-2.0.0a3}/docs/examples/README.md
RENAMED
|
@@ -24,5 +24,8 @@ one layer at a time, and the rest build on them.
|
|
|
24
24
|
| [`direct_reply_to_example.py`](direct_reply_to_example.py) | Request/reply without a dedicated reply queue: `settle_strategy(ConsumerSettleStrategy.DIRECT_REPLY_TO)` attaches to no queue at all and reads back a broker-generated pseudo-queue address from `consumer.queue`; a request naming it as `properties.reply_to` gets answered by an ordinary queue-bound consumer publishing to that address. The pseudo-queue is session-scoped, so a forced disconnect and auto-reconnection leave the requester with a brand new address, which the script re-reads before sending its second request. |
|
|
25
25
|
| [`rejection_reason_example.py`](rejection_reason_example.py) | Why a publish was refused: a quorum queue with `max_length(5)` and the `reject-publish` overflow strategy returns a `REJECTED` outcome carrying `RejectionDetails.reason` and `.rejected_by_queue` (needs RabbitMQ 4.3+; the script says so when the broker supplies neither). |
|
|
26
26
|
| [`quorum_single_active_consumer.py`](quorum_single_active_consumer.py) | Single active consumer notifications on a quorum queue (needs RabbitMQ 4.3+): two consumers on a queue declared with `single_active_consumer(True)` each register a `single_active_consumer_state_changed` handler, and the broker tells one of them it is active and the other that it is standby. Only the active one receives messages; closing it promotes the standby one, which then starts receiving. |
|
|
27
|
+
| [`delayed_retry_example.py`](delayed_retry_example.py) | Delaying a redelivery on a quorum queue (needs RabbitMQ 4.3+): a queue declared with `delayed_retry_type(QuorumQueueDelayedRetryType.FAILED)`, `delayed_retry_min(...)` and `delayed_retry_max(...)` holds a `context.discard()`-rejected message back for that window before redelivering it, while a `context.requeue()`-released one — a *returned*, not a *failed*, delivery, so `FAILED` does not cover it — comes back immediately. |
|
|
28
|
+
| [`delayed_retry_context_example.py`](delayed_retry_context_example.py) | Per-message delayed retry (needs RabbitMQ 4.3+): `context.delayed_retry(delay)` stamps an `x-opt-delivery-time` annotation on one redelivery, holding it back for that message alone — no queue-level `x-delayed-retry-*` configuration required at all, unlike `delayed_retry_example.py` above. |
|
|
29
|
+
| [`consumer_timeout_example.py`](consumer_timeout_example.py) | Consumer timeout (needs RabbitMQ 4.3+): `quorum().consumer_timeout(...)` sets the `rabbitmq:consumer-timeout` attach property, and `quorum().on_delivery_release(handler)` is called with a `TimeoutContext` when the broker force-releases a delivery held past that timeout — accepting it there is what lifts the broker's block on further delivery to that consumer. |
|
|
27
30
|
| [`stream_filtering.py`](stream_filtering.py) | Reading a stream queue from a chosen offset and narrowing what arrives (needs RabbitMQ 4.2+ for the SQL filter): the same batch of messages is read back three times from `offset(FIRST)` — once with `filter().subject(...).property("region", ...)`, once with the equivalent `filter().sql(...)`, and once with the cheap, probabilistic `filter_values(...)` bloom filter, whose publisher side is just an `x-stream-filter-value` message annotation. The script reports whether the broker really enforces the SQL filter. |
|
|
28
31
|
| [`tls_connection.py`](tls_connection.py) | TLS transport and certificate-based authentication (needs the broker's TLS listener enabled, `.ci/ubuntu/rabbitmq.conf`): one-way TLS with the broker's certificate verified against `.ci/certs/ca_certificate.pem`, then mutual TLS where a client certificate authenticates via SASL EXTERNAL (`sasl_external=True`) instead of a username/password, and finally — clearly separated and explicitly discouraged — a connection with verification disabled entirely. |
|
|
@@ -4,12 +4,12 @@ Run against a local broker::
|
|
|
4
4
|
|
|
5
5
|
PYTHONPATH=. .venv/bin/python docs/examples/basic_connection.py
|
|
6
6
|
|
|
7
|
-
Constructing a :class:`~
|
|
7
|
+
Constructing a :class:`~rabbitmq_amqp_python_client.Connection` dials the TCP
|
|
8
8
|
socket, exchanges the protocol header, runs SASL and the ``open`` handshake, and
|
|
9
9
|
starts the frame reader — all before ``__init__`` returns. There is no separate
|
|
10
10
|
``connect()`` step: a constructed connection is already ``OPEN`` and usable.
|
|
11
11
|
|
|
12
|
-
The default :class:`~
|
|
12
|
+
The default :class:`~rabbitmq_amqp_python_client.ConnectionParameters` describe
|
|
13
13
|
exactly what a stock broker offers — ``localhost:5672``, ``guest``/``guest``, the
|
|
14
14
|
``/`` virtual host — so the no-argument form below is the shortest connection
|
|
15
15
|
this client can make. What the broker sent back in its own ``open`` is readable
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import logging
|
|
26
26
|
|
|
27
|
-
from
|
|
27
|
+
from rabbitmq_amqp_python_client import Connection, ConnectionParameters
|
|
28
28
|
|
|
29
29
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-7s %(name)s: %(message)s")
|
|
30
30
|
logger = logging.getLogger("example")
|
|
@@ -11,11 +11,11 @@ both confirmed and consumed, so this doubles as a scriptable smoke test::
|
|
|
11
11
|
python3 docs/examples/console_application.py --help
|
|
12
12
|
|
|
13
13
|
This program adds nothing to the client's public surface: it is built entirely
|
|
14
|
-
out of :class:`~
|
|
15
|
-
:class:`~
|
|
16
|
-
:class:`~
|
|
17
|
-
:class:`~
|
|
18
|
-
:class:`~
|
|
14
|
+
out of :class:`~rabbitmq_amqp_python_client.Connection`,
|
|
15
|
+
:class:`~rabbitmq_amqp_python_client.Management`,
|
|
16
|
+
:class:`~rabbitmq_amqp_python_client.Publisher`,
|
|
17
|
+
:class:`~rabbitmq_amqp_python_client.Consumer` and
|
|
18
|
+
:class:`~rabbitmq_amqp_python_client.RecoveryConfiguration`. It owns exactly one
|
|
19
19
|
connection: it is a correctness/smoke-test tool, not a load generator.
|
|
20
20
|
|
|
21
21
|
Exit codes:
|
|
@@ -49,7 +49,7 @@ _SOURCE_ROOT = Path(__file__).resolve().parents[2]
|
|
|
49
49
|
if _SOURCE_ROOT.is_dir() and str(_SOURCE_ROOT) not in sys.path:
|
|
50
50
|
sys.path.insert(0, str(_SOURCE_ROOT))
|
|
51
51
|
|
|
52
|
-
from
|
|
52
|
+
from rabbitmq_amqp_python_client import ( # noqa: E402 - after the sys.path bootstrap above
|
|
53
53
|
AMQPError,
|
|
54
54
|
Connection,
|
|
55
55
|
ConnectionParameters,
|
|
@@ -64,7 +64,7 @@ from src import ( # noqa: E402 - after the sys.path bootstrap above
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
if TYPE_CHECKING:
|
|
67
|
-
from
|
|
67
|
+
from rabbitmq_amqp_python_client.connection import ConnectionState
|
|
68
68
|
|
|
69
69
|
# --- exit codes (§7) ----------------------------------------------------
|
|
70
70
|
|
|
@@ -475,7 +475,7 @@ def map_queue_type(options: Options, specification: QueueSpecification) -> Queue
|
|
|
475
475
|
specification: The builder from ``management.queue(name)``.
|
|
476
476
|
|
|
477
477
|
Returns:
|
|
478
|
-
The same builder, ready to :meth:`~
|
|
478
|
+
The same builder, ready to :meth:`~rabbitmq_amqp_python_client.QueueSpecification.declare`.
|
|
479
479
|
|
|
480
480
|
Raises:
|
|
481
481
|
OptionsError: If ``queue_type`` is not one of :data:`QUEUE_TYPES`.
|
|
@@ -8,7 +8,7 @@ A consumer is a receiver link plus a ``message_handler``. The link is granted
|
|
|
8
8
|
``initial_credits`` when it attaches, and the client re-grants one credit every
|
|
9
9
|
time a delivery is settled, so at most that many messages are ever outstanding.
|
|
10
10
|
The handler runs on the client's delivery thread and is handed a
|
|
11
|
-
:class:`~
|
|
11
|
+
:class:`~rabbitmq_amqp_python_client.Context` alongside the message; calling
|
|
12
12
|
``context.accept()`` settles the delivery as ``accepted``, which is what tells
|
|
13
13
|
the broker it may drop it. ``discard()`` and ``requeue()`` are the other two
|
|
14
14
|
choices, and doing nothing leaves the delivery unsettled — visible as
|
|
@@ -28,7 +28,7 @@ import logging
|
|
|
28
28
|
import queue
|
|
29
29
|
import uuid
|
|
30
30
|
|
|
31
|
-
from
|
|
31
|
+
from rabbitmq_amqp_python_client import (
|
|
32
32
|
Connection,
|
|
33
33
|
ConnectionParameters,
|
|
34
34
|
Context,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Consumer timeout: ``rabbitmq:consumer-timeout`` and ``on_delivery_release`` (step_130).
|
|
2
|
+
|
|
3
|
+
Run against a local RabbitMQ 4.3+ broker::
|
|
4
|
+
|
|
5
|
+
PYTHONPATH=. .venv/bin/python docs/examples/consumer_timeout_example.py
|
|
6
|
+
|
|
7
|
+
RabbitMQ limits how long a consumer may hold a delivery unsettled before the
|
|
8
|
+
broker takes it back and blocks further delivery to that link, until the
|
|
9
|
+
client acknowledges the release. This client surfaces that as
|
|
10
|
+
``quorum().consumer_timeout(...)`` (the ``rabbitmq:consumer-timeout`` attach
|
|
11
|
+
property) plus ``quorum().on_delivery_release(handler)``, which is called once
|
|
12
|
+
per delivery the broker force-releases this way — ``handler(context, message)``
|
|
13
|
+
is given a :class:`TimeoutContext`, whose only valid settlement is
|
|
14
|
+
``accept()``, and calling it is what lifts the broker's block.
|
|
15
|
+
|
|
16
|
+
This script attaches a consumer with a short ``consumer_timeout`` and an
|
|
17
|
+
``on_delivery_release`` handler that logs the release and accepts it,
|
|
18
|
+
publishes one message whose handler deliberately sleeps past that timeout on
|
|
19
|
+
its first delivery (settling normally afterward), observes the release fire
|
|
20
|
+
and unlock the consumer, then publishes a second message and confirms it
|
|
21
|
+
still arrives before closing everything down.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import time
|
|
28
|
+
import uuid
|
|
29
|
+
|
|
30
|
+
from rabbitmq_amqp_python_client import (
|
|
31
|
+
Connection,
|
|
32
|
+
ConnectionParameters,
|
|
33
|
+
ConsumerError,
|
|
34
|
+
Context,
|
|
35
|
+
Message,
|
|
36
|
+
QueueType,
|
|
37
|
+
TimeoutContext,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
#: The consumer's own rabbitmq:consumer-timeout, in milliseconds and seconds.
|
|
41
|
+
CONSUMER_TIMEOUT_MS = 3_000
|
|
42
|
+
CONSUMER_TIMEOUT_SECONDS = CONSUMER_TIMEOUT_MS / 1_000
|
|
43
|
+
|
|
44
|
+
#: How long the example waits for something it expects to happen.
|
|
45
|
+
TIMEOUT_SECONDS = 20.0
|
|
46
|
+
|
|
47
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-7s %(name)s: %(message)s")
|
|
48
|
+
logger = logging.getLogger("example")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def consumer_timeout() -> None:
|
|
52
|
+
"""Show a timed-out delivery released by the broker, and the consumer unlocked afterward."""
|
|
53
|
+
connection = Connection(ConnectionParameters(container_id=f"example-consumer-timeout-{uuid.uuid4().hex[:8]}"))
|
|
54
|
+
name = f"example-consumer-timeout-{uuid.uuid4().hex[:8]}"
|
|
55
|
+
try:
|
|
56
|
+
connection.management().queue(name).type(QueueType.QUORUM).declare()
|
|
57
|
+
logger.info("declared quorum queue %r", name)
|
|
58
|
+
|
|
59
|
+
released: list[tuple[TimeoutContext, Message]] = []
|
|
60
|
+
second_message: list[Message] = []
|
|
61
|
+
timeout_triggered = False
|
|
62
|
+
|
|
63
|
+
def on_delivery_release(context: TimeoutContext, message: Message) -> None:
|
|
64
|
+
logger.info(
|
|
65
|
+
"broker released %r past the %.1fs consumer-timeout; accepting",
|
|
66
|
+
message.body_as_string(),
|
|
67
|
+
CONSUMER_TIMEOUT_SECONDS,
|
|
68
|
+
)
|
|
69
|
+
context.accept()
|
|
70
|
+
released.append((context, message))
|
|
71
|
+
|
|
72
|
+
def on_message(context: Context, message: Message) -> None:
|
|
73
|
+
nonlocal timeout_triggered
|
|
74
|
+
if not timeout_triggered:
|
|
75
|
+
timeout_triggered = True
|
|
76
|
+
logger.info(
|
|
77
|
+
"holding %r unsettled for %.1fs to trigger the consumer-timeout",
|
|
78
|
+
message.body_as_string(),
|
|
79
|
+
CONSUMER_TIMEOUT_SECONDS + 2,
|
|
80
|
+
)
|
|
81
|
+
time.sleep(CONSUMER_TIMEOUT_SECONDS + 2)
|
|
82
|
+
try:
|
|
83
|
+
context.accept()
|
|
84
|
+
except ConsumerError:
|
|
85
|
+
logger.info("as expected: the broker had already released this delivery")
|
|
86
|
+
return
|
|
87
|
+
context.accept()
|
|
88
|
+
second_message.append(message)
|
|
89
|
+
logger.info("second message %r arrived and was accepted normally", message.body_as_string())
|
|
90
|
+
|
|
91
|
+
consumer = (
|
|
92
|
+
connection.consumer_builder()
|
|
93
|
+
.queue(name)
|
|
94
|
+
.quorum()
|
|
95
|
+
.consumer_timeout(CONSUMER_TIMEOUT_MS)
|
|
96
|
+
.on_delivery_release(on_delivery_release)
|
|
97
|
+
.builder()
|
|
98
|
+
.message_handler(on_message)
|
|
99
|
+
.initial_credits(1)
|
|
100
|
+
.build()
|
|
101
|
+
)
|
|
102
|
+
logger.info("consumer attached with rabbitmq:consumer-timeout=%dms", CONSUMER_TIMEOUT_MS)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
publisher = connection.publisher_builder().queue(name).build()
|
|
106
|
+
try:
|
|
107
|
+
publisher.publish(Message("held-too-long"), timeout=TIMEOUT_SECONDS)
|
|
108
|
+
|
|
109
|
+
deadline = time.monotonic() + TIMEOUT_SECONDS
|
|
110
|
+
while not released and time.monotonic() < deadline:
|
|
111
|
+
time.sleep(0.05)
|
|
112
|
+
if not released:
|
|
113
|
+
raise TimeoutError(f"the broker never released the timed-out delivery within {TIMEOUT_SECONDS:g}s")
|
|
114
|
+
logger.info("on_delivery_release fired; the broker's block on this link should now be lifted")
|
|
115
|
+
|
|
116
|
+
publisher.publish(Message("second"), timeout=TIMEOUT_SECONDS)
|
|
117
|
+
deadline = time.monotonic() + TIMEOUT_SECONDS
|
|
118
|
+
while not second_message and time.monotonic() < deadline:
|
|
119
|
+
time.sleep(0.05)
|
|
120
|
+
if not second_message:
|
|
121
|
+
raise TimeoutError("the consumer was never unlocked: the second message never arrived")
|
|
122
|
+
logger.info("confirmed: the consumer is unlocked and receiving normally again")
|
|
123
|
+
finally:
|
|
124
|
+
publisher.close()
|
|
125
|
+
finally:
|
|
126
|
+
consumer.close()
|
|
127
|
+
|
|
128
|
+
connection.management().queue(name).delete()
|
|
129
|
+
logger.info("deleted the queue %r", name)
|
|
130
|
+
finally:
|
|
131
|
+
connection.close()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
consumer_timeout()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Per-message delayed retry: ``context.delayed_retry(...)`` (step_120 §1-§2).
|
|
2
|
+
|
|
3
|
+
Run against a local RabbitMQ 4.3+ broker::
|
|
4
|
+
|
|
5
|
+
PYTHONPATH=. .venv/bin/python docs/examples/delayed_retry_context_example.py
|
|
6
|
+
|
|
7
|
+
``context.delayed_retry(delay, delivery_failed=False)`` is sugar over
|
|
8
|
+
``context.requeue({"x-opt-delivery-time": now_ms + delay}, delivery_failed)``:
|
|
9
|
+
it stamps a per-message redelivery hint directly on the message being
|
|
10
|
+
requeued, so the queue holds that one redelivery back for at least ``delay``
|
|
11
|
+
before trying again. Unlike ``delayed_retry_example.py``'s queue-level
|
|
12
|
+
``x-delayed-retry-type``/``delayed_retry_min``/``delayed_retry_max`` (which
|
|
13
|
+
apply to every redelivery on that queue, and require the queue to be declared
|
|
14
|
+
with them up front), this mechanism needs **no** queue-level configuration at
|
|
15
|
+
all — it works on a plain quorum queue, and the delay is chosen fresh on each
|
|
16
|
+
call, message by message.
|
|
17
|
+
|
|
18
|
+
This script declares a plain quorum queue (no ``x-delayed-retry-*``
|
|
19
|
+
arguments), publishes one message, and has the handler call
|
|
20
|
+
``delayed_retry(...)`` on the first delivery and ``accept()`` on the second —
|
|
21
|
+
then measures and reports the elapsed time between the two, which must be at
|
|
22
|
+
least the requested delay.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
import time
|
|
29
|
+
import uuid
|
|
30
|
+
|
|
31
|
+
from rabbitmq_amqp_python_client import (
|
|
32
|
+
Connection,
|
|
33
|
+
ConnectionParameters,
|
|
34
|
+
Context,
|
|
35
|
+
Message,
|
|
36
|
+
QueueType,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
#: How long the second delivery is held back, in milliseconds and in seconds.
|
|
40
|
+
DELAY_MS = 3_000
|
|
41
|
+
DELAY_SECONDS = DELAY_MS / 1_000
|
|
42
|
+
|
|
43
|
+
#: How long the example waits for a delivery it expects.
|
|
44
|
+
TIMEOUT_SECONDS = 15.0
|
|
45
|
+
|
|
46
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-7s %(name)s: %(message)s")
|
|
47
|
+
logger = logging.getLogger("example")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def delayed_retry_context() -> None:
|
|
51
|
+
"""Show one message's redelivery held back by its own ``delayed_retry`` call."""
|
|
52
|
+
connection = Connection(ConnectionParameters(container_id=f"example-delayed-retry-context-{uuid.uuid4().hex[:8]}"))
|
|
53
|
+
name = f"example-delayed-retry-context-{uuid.uuid4().hex[:8]}"
|
|
54
|
+
try:
|
|
55
|
+
# A plain quorum queue: no x-delayed-retry-* arguments at all — the
|
|
56
|
+
# delay this example asks for comes entirely from the per-message
|
|
57
|
+
# x-opt-delivery-time annotation delayed_retry() stamps on.
|
|
58
|
+
connection.management().queue(name).type(QueueType.QUORUM).declare()
|
|
59
|
+
logger.info("declared plain quorum queue %r (no queue-level delayed-retry configuration)", name)
|
|
60
|
+
|
|
61
|
+
arrived_at: list[float] = []
|
|
62
|
+
|
|
63
|
+
def on_message(context: Context, message: Message) -> None:
|
|
64
|
+
arrived_at.append(time.monotonic())
|
|
65
|
+
if len(arrived_at) == 1:
|
|
66
|
+
logger.info("first delivery %r; delaying its retry by %dms", message.body_as_string(), DELAY_MS)
|
|
67
|
+
context.delayed_retry(DELAY_MS)
|
|
68
|
+
else:
|
|
69
|
+
logger.info("redelivery %r arrived; accepting", message.body_as_string())
|
|
70
|
+
context.accept()
|
|
71
|
+
|
|
72
|
+
consumer = connection.consumer_builder().queue(name).message_handler(on_message).initial_credits(1).build()
|
|
73
|
+
try:
|
|
74
|
+
publisher = connection.publisher_builder().queue(name).build()
|
|
75
|
+
try:
|
|
76
|
+
publisher.publish(Message("retry-me"), timeout=TIMEOUT_SECONDS)
|
|
77
|
+
finally:
|
|
78
|
+
publisher.close()
|
|
79
|
+
|
|
80
|
+
deadline = time.monotonic() + TIMEOUT_SECONDS
|
|
81
|
+
while len(arrived_at) < 2 and time.monotonic() < deadline:
|
|
82
|
+
time.sleep(0.05)
|
|
83
|
+
if len(arrived_at) < 2:
|
|
84
|
+
raise TimeoutError(f"the redelivery never arrived within {TIMEOUT_SECONDS:g}s")
|
|
85
|
+
|
|
86
|
+
elapsed = arrived_at[1] - arrived_at[0]
|
|
87
|
+
logger.info("the redelivery arrived after %.2fs (requested delay was %.2fs)", elapsed, DELAY_SECONDS)
|
|
88
|
+
assert elapsed >= DELAY_SECONDS * 0.5, (
|
|
89
|
+
f"delayed_retry should have held the redelivery back, took {elapsed:.2f}s"
|
|
90
|
+
)
|
|
91
|
+
finally:
|
|
92
|
+
consumer.close()
|
|
93
|
+
|
|
94
|
+
connection.management().queue(name).delete()
|
|
95
|
+
logger.info("deleted the queue %r", name)
|
|
96
|
+
finally:
|
|
97
|
+
connection.close()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
delayed_retry_context()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Delayed retry on a quorum queue: a redelivery held back instead of instant
|
|
2
|
+
(step_001_management.md §5.4).
|
|
3
|
+
|
|
4
|
+
Run against a local RabbitMQ 4.3+ broker::
|
|
5
|
+
|
|
6
|
+
PYTHONPATH=. .venv/bin/python docs/examples/delayed_retry_example.py
|
|
7
|
+
|
|
8
|
+
``QuorumQueueSpecification.delayed_retry_type(QuorumQueueDelayedRetryType.FAILED)``,
|
|
9
|
+
paired with ``delayed_retry_min``/``delayed_retry_max``, sets ``x-delayed-retry-type``,
|
|
10
|
+
``x-delayed-retry-min`` and ``x-delayed-retry-max`` on the declare. Once set, the
|
|
11
|
+
queue holds a message aside for that window before redelivering it, rather than
|
|
12
|
+
redelivering it the instant it comes back — but only for the *kind* of return
|
|
13
|
+
the configured type names. Of the outcomes ``Context`` can send
|
|
14
|
+
(step_030_consumers.md §4), only the two that leave a message eligible for
|
|
15
|
+
redelivery at all take part in this: ``context.requeue(delivery_failed=True)``,
|
|
16
|
+
which sends ``modified{delivery-failed=true, undeliverable-here=false}``, is a
|
|
17
|
+
*failed* return; ``context.requeue()`` (``delivery_failed=False``), which sends
|
|
18
|
+
``released``, is a *returned* one. (A terminal ``context.discard()`` — the
|
|
19
|
+
``rejected`` outcome — is always dropped outright and plays no part in this;
|
|
20
|
+
delayed retry only ever holds back a message that was going to be redelivered
|
|
21
|
+
anyway.)
|
|
22
|
+
|
|
23
|
+
* ``DISABLED`` (the default) never delays either kind;
|
|
24
|
+
* ``ALL`` delays both kinds;
|
|
25
|
+
* ``FAILED`` delays only a *failed* return;
|
|
26
|
+
* ``RETURNED`` delays only a *returned* one.
|
|
27
|
+
|
|
28
|
+
This script declares a queue with ``FAILED``, and shows both halves of that
|
|
29
|
+
distinction on the same queue: a failed return comes back only after
|
|
30
|
+
``delayed_retry_min`` has passed, while a returned one — not covered by
|
|
31
|
+
``FAILED`` — comes back right away, exactly as it would with delayed retry
|
|
32
|
+
disabled altogether.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
import time
|
|
39
|
+
import uuid
|
|
40
|
+
from collections.abc import Callable
|
|
41
|
+
|
|
42
|
+
from rabbitmq_amqp_python_client import (
|
|
43
|
+
Connection,
|
|
44
|
+
ConnectionParameters,
|
|
45
|
+
Context,
|
|
46
|
+
ManagementError,
|
|
47
|
+
Message,
|
|
48
|
+
QuorumQueueDelayedRetryType,
|
|
49
|
+
)
|
|
50
|
+
from rabbitmq_amqp_python_client.management import STATUS_BAD_REQUEST
|
|
51
|
+
|
|
52
|
+
#: How long a redelivery is held back, in milliseconds and in seconds.
|
|
53
|
+
DELAYED_RETRY_MS = 3_000
|
|
54
|
+
DELAYED_RETRY_SECONDS = DELAYED_RETRY_MS / 1_000
|
|
55
|
+
|
|
56
|
+
#: How long the example waits for a delivery it expects.
|
|
57
|
+
TIMEOUT_SECONDS = 15.0
|
|
58
|
+
|
|
59
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-7s %(name)s: %(message)s")
|
|
60
|
+
logger = logging.getLogger("example")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _consume_one_and_settle(
|
|
64
|
+
connection: Connection, name: str, settle: Callable[[Context], None]
|
|
65
|
+
) -> tuple[Context, float]:
|
|
66
|
+
"""Attach a fresh consumer, receive the queue's one message, settle it, and
|
|
67
|
+
return the context that was used plus when the delivery arrived.
|
|
68
|
+
"""
|
|
69
|
+
delivered: list[tuple[Context, Message, float]] = []
|
|
70
|
+
|
|
71
|
+
def on_message(context: Context, message: Message) -> None:
|
|
72
|
+
delivered.append((context, message, time.monotonic()))
|
|
73
|
+
|
|
74
|
+
consumer = connection.consumer_builder().queue(name).message_handler(on_message).initial_credits(1).build()
|
|
75
|
+
try:
|
|
76
|
+
deadline = time.monotonic() + TIMEOUT_SECONDS
|
|
77
|
+
while not delivered and time.monotonic() < deadline:
|
|
78
|
+
time.sleep(0.05)
|
|
79
|
+
if not delivered:
|
|
80
|
+
raise TimeoutError(f"no delivery arrived within {TIMEOUT_SECONDS:g}s")
|
|
81
|
+
context, message, arrived_at = delivered[0]
|
|
82
|
+
settle(context)
|
|
83
|
+
return context, arrived_at
|
|
84
|
+
finally:
|
|
85
|
+
consumer.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def delayed_retry() -> None:
|
|
89
|
+
"""Show a failed return delayed by FAILED, and a returned one that is not."""
|
|
90
|
+
connection = Connection(ConnectionParameters(container_id=f"example-delayed-retry-{uuid.uuid4().hex[:8]}"))
|
|
91
|
+
name = f"example-delayed-retry-{uuid.uuid4().hex[:8]}"
|
|
92
|
+
try:
|
|
93
|
+
try:
|
|
94
|
+
(
|
|
95
|
+
connection.management()
|
|
96
|
+
.queue(name)
|
|
97
|
+
.quorum()
|
|
98
|
+
.delayed_retry_type(QuorumQueueDelayedRetryType.FAILED)
|
|
99
|
+
.delayed_retry_min(DELAYED_RETRY_MS)
|
|
100
|
+
.delayed_retry_max(DELAYED_RETRY_MS)
|
|
101
|
+
.queue()
|
|
102
|
+
.declare()
|
|
103
|
+
)
|
|
104
|
+
except ManagementError as error:
|
|
105
|
+
if error.status_code != STATUS_BAD_REQUEST:
|
|
106
|
+
raise
|
|
107
|
+
logger.warning("this broker does not support x-delayed-retry-* (needs RabbitMQ 4.3+); stopping here")
|
|
108
|
+
return
|
|
109
|
+
logger.info("declared %r with delayed_retry_type=FAILED, min=max=%dms", name, DELAYED_RETRY_MS)
|
|
110
|
+
|
|
111
|
+
publisher = connection.publisher_builder().queue(name).build()
|
|
112
|
+
try:
|
|
113
|
+
publisher.publish(Message("failed-return"), timeout=TIMEOUT_SECONDS)
|
|
114
|
+
_, failed_at = _consume_one_and_settle(
|
|
115
|
+
connection, name, lambda context: context.requeue(delivery_failed=True)
|
|
116
|
+
)
|
|
117
|
+
logger.info("requeued %r as a failed return; waiting for the delayed redelivery", "failed-return")
|
|
118
|
+
_, redelivered_at = _consume_one_and_settle(connection, name, lambda context: context.accept())
|
|
119
|
+
elapsed = redelivered_at - failed_at
|
|
120
|
+
logger.info(
|
|
121
|
+
"the failed return came back after %.2fs (delayed_retry_min=%.2fs)", elapsed, DELAYED_RETRY_SECONDS
|
|
122
|
+
)
|
|
123
|
+
assert elapsed >= DELAYED_RETRY_SECONDS * 0.5, (
|
|
124
|
+
f"FAILED should have delayed the failed return, took {elapsed:.2f}s"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
publisher.publish(Message("returned"), timeout=TIMEOUT_SECONDS)
|
|
128
|
+
_, returned_at = _consume_one_and_settle(connection, name, lambda context: context.requeue())
|
|
129
|
+
logger.info("requeued %r as a returned delivery; not covered by FAILED", "returned")
|
|
130
|
+
_, redelivered_at = _consume_one_and_settle(connection, name, lambda context: context.accept())
|
|
131
|
+
elapsed = redelivered_at - returned_at
|
|
132
|
+
logger.info("the returned delivery came back after %.2fs — not delayed", elapsed)
|
|
133
|
+
assert elapsed < DELAYED_RETRY_SECONDS * 0.5, (
|
|
134
|
+
f"FAILED should not have delayed the returned delivery, took {elapsed:.2f}s"
|
|
135
|
+
)
|
|
136
|
+
finally:
|
|
137
|
+
publisher.close()
|
|
138
|
+
|
|
139
|
+
connection.management().queue(name).delete()
|
|
140
|
+
logger.info("deleted the queue %r", name)
|
|
141
|
+
finally:
|
|
142
|
+
connection.close()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
delayed_retry()
|
|
@@ -12,7 +12,7 @@ dynamically generates a private, per-attach pseudo-queue address
|
|
|
12
12
|
reply puts that address in an outgoing request's ``properties.reply_to``;
|
|
13
13
|
whoever answers just needs to publish to it — no dedicated, exclusive reply
|
|
14
14
|
queue to declare and clean up per requester. Like a presettled consumer, its
|
|
15
|
-
``Context`` methods all raise :class:`~
|
|
15
|
+
``Context`` methods all raise :class:`~rabbitmq_amqp_python_client.ConsumerError`: the broker considers
|
|
16
16
|
every delivery on this link already settled the instant it sends it.
|
|
17
17
|
|
|
18
18
|
This script plays both roles, on two separate connections:
|
|
@@ -22,7 +22,7 @@ This script plays both roles, on two separate connections:
|
|
|
22
22
|
* the **responder** (``responder_connection``) declares an ordinary request
|
|
23
23
|
queue, consumes it with a normal ``ExplicitSettle`` consumer, and replies to
|
|
24
24
|
whatever address arrived in the request's ``reply_to`` — an ordinary
|
|
25
|
-
anonymous :class:`~
|
|
25
|
+
anonymous :class:`~rabbitmq_amqp_python_client.Publisher` (step_020_publishers.md §3.3) also sends
|
|
26
26
|
the request itself, targeting the request queue.
|
|
27
27
|
|
|
28
28
|
Because the pseudo-queue is scoped to the exact connection and session that
|
|
@@ -43,7 +43,7 @@ import socket
|
|
|
43
43
|
import time
|
|
44
44
|
import uuid
|
|
45
45
|
|
|
46
|
-
from
|
|
46
|
+
from rabbitmq_amqp_python_client import (
|
|
47
47
|
Connection,
|
|
48
48
|
ConnectionParameters,
|
|
49
49
|
ConnectionState,
|
|
@@ -55,7 +55,7 @@ from src import (
|
|
|
55
55
|
RecoveryConfiguration,
|
|
56
56
|
queue_address,
|
|
57
57
|
)
|
|
58
|
-
from
|
|
58
|
+
from rabbitmq_amqp_python_client.wire import Properties
|
|
59
59
|
|
|
60
60
|
#: How long the example waits for a reply it expects.
|
|
61
61
|
TIMEOUT_SECONDS = 15.0
|
|
@@ -14,7 +14,7 @@ request message out and one response message back, correlated by
|
|
|
14
14
|
The script walks the whole surface once:
|
|
15
15
|
|
|
16
16
|
1. ``queue(name).declare()`` declares a classic queue and returns a
|
|
17
|
-
:class:`~
|
|
17
|
+
:class:`~rabbitmq_amqp_python_client.QueueInfo` parsed from the broker's
|
|
18
18
|
answer, so the queue's real settings are readable straight away;
|
|
19
19
|
2. ``exchange(name).type(ExchangeType.TOPIC).declare()`` declares a topic
|
|
20
20
|
exchange;
|
|
@@ -26,7 +26,7 @@ The script walks the whole surface once:
|
|
|
26
26
|
|
|
27
27
|
Declaring is idempotent as long as the arguments match: re-declaring the same
|
|
28
28
|
queue is a successful no-op, while re-declaring it with different arguments is a
|
|
29
|
-
:class:`~
|
|
29
|
+
:class:`~rabbitmq_amqp_python_client.ManagementError`.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
from __future__ import annotations
|
|
@@ -34,7 +34,7 @@ from __future__ import annotations
|
|
|
34
34
|
import logging
|
|
35
35
|
import uuid
|
|
36
36
|
|
|
37
|
-
from
|
|
37
|
+
from rabbitmq_amqp_python_client import (
|
|
38
38
|
Connection,
|
|
39
39
|
ConnectionParameters,
|
|
40
40
|
ExchangeType,
|