amqp-client-python 0.2.1.dev1__tar.gz → 0.2.2.dev1__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.
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.github/workflows/lint.yml +3 -3
- amqp_client_python-0.2.2.dev1/.github/workflows/publish.yml +22 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.github/workflows/test.yml +3 -3
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/PKG-INFO +15 -20
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/README.md +12 -16
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/models/options.py +18 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/async_channel.py +110 -39
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/async_connection.py +86 -73
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py +31 -1
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/eventbus_wrapper_rabbitmq.py +29 -3
- amqp_client_python-0.2.2.dev1/docs/templates/installation.md +11 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/pyproject.toml +8 -10
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/channels/test_async_rpc_client.py +37 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/channels/test_publish.py +29 -0
- amqp_client_python-0.2.2.dev1/tests/unit/channels/test_subscribe.py +120 -0
- amqp_client_python-0.2.2.dev1/tests/unit/eventbus/test_topology_options.py +310 -0
- amqp_client_python-0.2.2.dev1/uv.lock +1659 -0
- amqp_client_python-0.2.1.dev1/.github/workflows/publish.yml +0 -24
- amqp_client_python-0.2.1.dev1/docs/templates/installation.md +0 -9
- amqp_client_python-0.2.1.dev1/tests/unit/channels/test_subscribe.py +0 -30
- amqp_client_python-0.2.1.dev1/uv.lock +0 -1624
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.env.example +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.flake8 +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.github/workflows/type.yml +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.gitignore +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.pre-commit-config.yaml +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.readthedocs.yaml +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/.vscode/settings.json +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/CODE_OF_CONDUCT.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/LICENSE +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/models/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/models/config.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/models/ssl_options.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/utils/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/utils/connection_type.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/domain/utils/types.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/async_provider_handler.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/async_subscriber_handler.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/event.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/integration_event.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/integration_event_handler.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/provider_handler.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/event/subscriber_handler.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/auto_reconnect_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/blocking_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/eventbus_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/nack_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/publish_timeout_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/response_timeout_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/rpc_provider_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/thread_unsafe_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/exceptions/timeout_exception.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/py.typed +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/async_channel_factory.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/async_connection_factory.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/channel_factory_rabbitmq.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/channel_rabbitmq.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/connection_factory_rabbitmq.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/connection_rabbitmq.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/eventbus_rabbitmq.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/rabbitmq/ioloop_factory.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/amqp_client_python/signals.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/create-self-signed-certs.sh +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/dev_requirements.txt +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docker-compose.yml +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/API/domain/config.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/API/domain/options.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/API/domain/ssl_options.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/API/rabbitmq/async_eventbus.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/contribute.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/index.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/stylesheets/extra.css +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/templates/cards.html +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/templates/sponsors.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/templates/user_guide.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/docs/tutorial.md +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/async_case.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/default.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/multprocess.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/publish.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/rpc_publish.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/rpc_subscribe.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/sanic_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/ssl_connection.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/subscribe.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/sync_case.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/sync_wrapper_case.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/examples/thread.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/external_config/rabbitmq/rabbitmq.conf +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/mkdocs.yml +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/mypy.ini +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/auto_recovery_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/conftest.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/rpc_provider_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/signals_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/ssl_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus/subscribe_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/conftest.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/rpc_provider_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/signals_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/ssl_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/async_eventbus_wrapper/subscribe_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/conftest.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/publish_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/rpc_publish_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/rpc_subscribe_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/integration/eventbus/subscribe_test.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/__init__.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/channels/test_async_channel.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/channels/test_channel_factory.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/channels/test_rpc_subscribe.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/config/test_uri.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/conftest.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/default.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_async_eventbus.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_async_eventbus_provide_resource.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_async_eventbus_publish.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_async_eventbus_rpc_client.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_async_eventbus_subscribe.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/eventbus/test_auto_recovery.py +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/utils/ca.pem +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/utils/rabbitmq_cert.pem +0 -0
- {amqp_client_python-0.2.1.dev1 → amqp_client_python-0.2.2.dev1}/tests/unit/utils/rabbitmq_key.pem +0 -0
|
@@ -19,10 +19,10 @@ jobs:
|
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- uses: actions/checkout@v3
|
|
22
|
-
- name: Set up Python 3.
|
|
23
|
-
uses: actions/setup-python@
|
|
22
|
+
- name: Set up Python 3.10
|
|
23
|
+
uses: actions/setup-python@v4
|
|
24
24
|
with:
|
|
25
|
-
python-version: "3.
|
|
25
|
+
python-version: "3.10"
|
|
26
26
|
- name: Install dependencies
|
|
27
27
|
run: |
|
|
28
28
|
python -m pip install --upgrade pip
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
deploy:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
|
|
17
|
+
- name: Build and publish
|
|
18
|
+
env:
|
|
19
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
20
|
+
run: |
|
|
21
|
+
uv build
|
|
22
|
+
uv publish
|
|
@@ -42,10 +42,10 @@ jobs:
|
|
|
42
42
|
echo "Waiting for RabbitMQ to initialize..."
|
|
43
43
|
# Give the container time to start up and apply the configuration.
|
|
44
44
|
sleep 8
|
|
45
|
-
- name: Set up Python 3.
|
|
46
|
-
uses: actions/setup-python@
|
|
45
|
+
- name: Set up Python 3.10
|
|
46
|
+
uses: actions/setup-python@v4
|
|
47
47
|
with:
|
|
48
|
-
python-version: "3.
|
|
48
|
+
python-version: "3.10"
|
|
49
49
|
- name: Install dependencies
|
|
50
50
|
run: |
|
|
51
51
|
python -m pip install --upgrade pip
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: amqp-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2.dev1
|
|
4
4
|
Summary: Python AMQP Client Library
|
|
5
5
|
Project-URL: Homepage, https://github.com/nutes-uepb/amqp-client-python
|
|
6
6
|
Project-URL: Repository, https://github.com/nutes-uepb/amqp-client-python
|
|
@@ -17,7 +17,6 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -28,7 +27,7 @@ Classifier: Topic :: Internet
|
|
|
28
27
|
Classifier: Topic :: Software Development :: Libraries
|
|
29
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
29
|
Classifier: Topic :: System :: Networking
|
|
31
|
-
Requires-Python: <4.0,>=3.
|
|
30
|
+
Requires-Python: <4.0,>=3.10
|
|
32
31
|
Requires-Dist: pika<2.0.0,>=1.3.0
|
|
33
32
|
Description-Content-Type: text/markdown
|
|
34
33
|
|
|
@@ -57,16 +56,6 @@ A Python client providing a high-level abstraction for interacting with RabbitMQ
|
|
|
57
56
|
|
|
58
57
|
**Discord Server**: <a href="https://discord.gg/RkXNeZpNZk" target="_blank">https://discord.gg/RkXNeZpNZk</a>
|
|
59
58
|
|
|
60
|
-
### 🦀 Looking for higher performance? Try `amqp-rs`!
|
|
61
|
-
If you need higher throughput, thread safety, and built-in compression, check out [**amqp-rs**](https://pypi.org/project/amqp-rs/).
|
|
62
|
-
|
|
63
|
-
It is a high-performance Python extension developed in Rust using PyO3 and `tokio`. It shares a similar API and design philosophy with this library, but adds native thread safety, `zstd`/`zlib`/`lz4` compression, robust TLS/SSL support, and graceful shutdowns.
|
|
64
|
-
|
|
65
|
-
You can install it via `pip` or `uv`:
|
|
66
|
-
```bash
|
|
67
|
-
pip install amqp-rs
|
|
68
|
-
```
|
|
69
|
-
|
|
70
59
|
---
|
|
71
60
|
### Features:
|
|
72
61
|
- Automatic creation and management of queues, exchanges and channels;
|
|
@@ -80,19 +69,22 @@ pip install amqp-rs
|
|
|
80
69
|
### Table of Compatibility
|
|
81
70
|
| version | compatible with |
|
|
82
71
|
| ---- | ---- |
|
|
72
|
+
| 0.2.1 | ~0.2.0 |
|
|
83
73
|
| 0.2.0 | 0.2.0 |
|
|
84
74
|
| 0.1.14 | ~0.1.12 |
|
|
85
75
|
|
|
86
76
|
### Installation
|
|
87
77
|
|
|
88
|
-
You can install `amqp-client-python` using pip:
|
|
78
|
+
You can install `amqp-client-python` using uv or pip:
|
|
89
79
|
```bash
|
|
80
|
+
uv add amqp-client-python
|
|
81
|
+
# or
|
|
90
82
|
pip install amqp-client-python
|
|
91
83
|
```
|
|
92
84
|
|
|
93
85
|
### Prerequisites
|
|
94
86
|
|
|
95
|
-
* Python 3.
|
|
87
|
+
* Python 3.9+
|
|
96
88
|
* A running RabbitMQ instance.
|
|
97
89
|
|
|
98
90
|
### Examples:
|
|
@@ -109,15 +101,18 @@ from amqp_client_python import (
|
|
|
109
101
|
)
|
|
110
102
|
config = Config(Options("queue", "rpc_queue", "rpc_exchange"))
|
|
111
103
|
eventbus = AsyncEventbusRabbitMQ(config)
|
|
112
|
-
# publish
|
|
104
|
+
# publish (supports dict/str with auto-JSON, or raw bytes/bytearray without overhead)
|
|
105
|
+
await eventbus.publish("rpc_exchange", "routing.key", {"message": "content"})
|
|
106
|
+
await eventbus.publish("rpc_exchange", "routing.key", b"raw_binary_payload")
|
|
113
107
|
|
|
114
|
-
|
|
115
|
-
# subscribe
|
|
108
|
+
# subscribe (auto_decode=True delivers parsed JSON dict; auto_decode=False delivers raw bytes)
|
|
116
109
|
async def subscribe_handler(body) -> None:
|
|
117
110
|
print(body, type(body), flush=True) # handle messages
|
|
118
111
|
await eventbus.subscribe("rpc_exchange", "routing.key", subscribe_handler)
|
|
112
|
+
|
|
119
113
|
# rpc_publish
|
|
120
|
-
response = await eventbus.rpc_client("rpc_exchange", "user.find", "
|
|
114
|
+
response = await eventbus.rpc_client("rpc_exchange", "user.find", {"name": "alice"})
|
|
115
|
+
|
|
121
116
|
# provider
|
|
122
117
|
async def rpc_provider_handler(body) -> bytes:
|
|
123
118
|
print(f"body: {body}")
|
|
@@ -23,16 +23,6 @@ A Python client providing a high-level abstraction for interacting with RabbitMQ
|
|
|
23
23
|
|
|
24
24
|
**Discord Server**: <a href="https://discord.gg/RkXNeZpNZk" target="_blank">https://discord.gg/RkXNeZpNZk</a>
|
|
25
25
|
|
|
26
|
-
### 🦀 Looking for higher performance? Try `amqp-rs`!
|
|
27
|
-
If you need higher throughput, thread safety, and built-in compression, check out [**amqp-rs**](https://pypi.org/project/amqp-rs/).
|
|
28
|
-
|
|
29
|
-
It is a high-performance Python extension developed in Rust using PyO3 and `tokio`. It shares a similar API and design philosophy with this library, but adds native thread safety, `zstd`/`zlib`/`lz4` compression, robust TLS/SSL support, and graceful shutdowns.
|
|
30
|
-
|
|
31
|
-
You can install it via `pip` or `uv`:
|
|
32
|
-
```bash
|
|
33
|
-
pip install amqp-rs
|
|
34
|
-
```
|
|
35
|
-
|
|
36
26
|
---
|
|
37
27
|
### Features:
|
|
38
28
|
- Automatic creation and management of queues, exchanges and channels;
|
|
@@ -46,19 +36,22 @@ pip install amqp-rs
|
|
|
46
36
|
### Table of Compatibility
|
|
47
37
|
| version | compatible with |
|
|
48
38
|
| ---- | ---- |
|
|
39
|
+
| 0.2.1 | ~0.2.0 |
|
|
49
40
|
| 0.2.0 | 0.2.0 |
|
|
50
41
|
| 0.1.14 | ~0.1.12 |
|
|
51
42
|
|
|
52
43
|
### Installation
|
|
53
44
|
|
|
54
|
-
You can install `amqp-client-python` using pip:
|
|
45
|
+
You can install `amqp-client-python` using uv or pip:
|
|
55
46
|
```bash
|
|
47
|
+
uv add amqp-client-python
|
|
48
|
+
# or
|
|
56
49
|
pip install amqp-client-python
|
|
57
50
|
```
|
|
58
51
|
|
|
59
52
|
### Prerequisites
|
|
60
53
|
|
|
61
|
-
* Python 3.
|
|
54
|
+
* Python 3.9+
|
|
62
55
|
* A running RabbitMQ instance.
|
|
63
56
|
|
|
64
57
|
### Examples:
|
|
@@ -75,15 +68,18 @@ from amqp_client_python import (
|
|
|
75
68
|
)
|
|
76
69
|
config = Config(Options("queue", "rpc_queue", "rpc_exchange"))
|
|
77
70
|
eventbus = AsyncEventbusRabbitMQ(config)
|
|
78
|
-
# publish
|
|
71
|
+
# publish (supports dict/str with auto-JSON, or raw bytes/bytearray without overhead)
|
|
72
|
+
await eventbus.publish("rpc_exchange", "routing.key", {"message": "content"})
|
|
73
|
+
await eventbus.publish("rpc_exchange", "routing.key", b"raw_binary_payload")
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
# subscribe
|
|
75
|
+
# subscribe (auto_decode=True delivers parsed JSON dict; auto_decode=False delivers raw bytes)
|
|
82
76
|
async def subscribe_handler(body) -> None:
|
|
83
77
|
print(body, type(body), flush=True) # handle messages
|
|
84
78
|
await eventbus.subscribe("rpc_exchange", "routing.key", subscribe_handler)
|
|
79
|
+
|
|
85
80
|
# rpc_publish
|
|
86
|
-
response = await eventbus.rpc_client("rpc_exchange", "user.find", "
|
|
81
|
+
response = await eventbus.rpc_client("rpc_exchange", "user.find", {"name": "alice"})
|
|
82
|
+
|
|
87
83
|
# provider
|
|
88
84
|
async def rpc_provider_handler(body) -> bytes:
|
|
89
85
|
print(f"body: {body}")
|
|
@@ -15,6 +15,12 @@ class Options:
|
|
|
15
15
|
vhost: str = "/",
|
|
16
16
|
heartbeat: Optional[int] = 60,
|
|
17
17
|
publisher_confirms: bool = False,
|
|
18
|
+
rpc_exchange_type: str = "topic",
|
|
19
|
+
rpc_queue_durable: bool = True,
|
|
20
|
+
rpc_queue_auto_delete: bool = False,
|
|
21
|
+
exchange_type: str = "topic",
|
|
22
|
+
queue_durable: bool = True,
|
|
23
|
+
queue_auto_delete: bool = False,
|
|
18
24
|
**kwargs: Dict[str, Any]
|
|
19
25
|
) -> None:
|
|
20
26
|
"""
|
|
@@ -32,6 +38,12 @@ class Options:
|
|
|
32
38
|
vhost: vhost information
|
|
33
39
|
heartbeat: interval between heartbeat
|
|
34
40
|
publisher_confirms: wait for publisher confirmations
|
|
41
|
+
rpc_exchange_type: exchange type for RPC server (default 'topic')
|
|
42
|
+
rpc_queue_durable: durability for RPC server queue (default True)
|
|
43
|
+
rpc_queue_auto_delete: auto_delete for RPC server queue (default False)
|
|
44
|
+
exchange_type: exchange type for pub/sub subscriptions (default 'topic')
|
|
45
|
+
queue_durable: durability for pub/sub subscriptions queue (default True)
|
|
46
|
+
queue_auto_delete: auto_delete for pub/sub subscriptions queue (default False)
|
|
35
47
|
**kwargs: pika options
|
|
36
48
|
|
|
37
49
|
Examples:
|
|
@@ -56,4 +68,10 @@ class Options:
|
|
|
56
68
|
self.vhost = vhost
|
|
57
69
|
self.heartbeat = heartbeat
|
|
58
70
|
self.publisher_confirms = publisher_confirms
|
|
71
|
+
self.rpc_exchange_type = kwargs.pop("rpc_exchange_type", rpc_exchange_type)
|
|
72
|
+
self.rpc_queue_durable = kwargs.pop("rpc_queue_durable", rpc_queue_durable)
|
|
73
|
+
self.rpc_queue_auto_delete = kwargs.pop("rpc_queue_auto_delete", rpc_queue_auto_delete)
|
|
74
|
+
self.exchange_type = kwargs.pop("exchange_type", exchange_type)
|
|
75
|
+
self.queue_durable = kwargs.pop("queue_durable", queue_durable)
|
|
76
|
+
self.queue_auto_delete = kwargs.pop("queue_auto_delete", queue_auto_delete)
|
|
59
77
|
self.kwargs = kwargs
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import deque
|
|
2
|
-
from typing import MutableMapping, Mapping, Optional, Union, Dict, List
|
|
2
|
+
from typing import MutableMapping, Mapping, Optional, Union, Dict, List, Tuple, Any
|
|
3
3
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
4
4
|
from amqp_client_python.exceptions import (
|
|
5
5
|
NackException,
|
|
@@ -54,11 +54,20 @@ class AsyncChannel:
|
|
|
54
54
|
self.auto_ack = auto_ack
|
|
55
55
|
self.subscribes: Dict[str, Dict[str, HandlerType]] = {}
|
|
56
56
|
self.re_subscribes: List[str] = []
|
|
57
|
+
self._compiled_patterns: Dict[str, List[Tuple[str, Any]]] = {}
|
|
58
|
+
self._queue_exchange_type: Dict[str, str] = {}
|
|
57
59
|
self.consumers: Dict[str, bool] = {}
|
|
58
60
|
self.publisher_confirms = False
|
|
59
61
|
self._message_number = 0
|
|
60
62
|
self._deliveries: MutableMapping[int, Future] = {}
|
|
61
63
|
self.response_timeout = 60
|
|
64
|
+
self._declared_exchanges: Dict[str, Tuple[str, bool]] = {}
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _serialize_payload(body, content_type: str = "application/json"):
|
|
68
|
+
if isinstance(body, (bytes, bytearray, memoryview)):
|
|
69
|
+
return body
|
|
70
|
+
return dumps(body)
|
|
62
71
|
|
|
63
72
|
@property
|
|
64
73
|
def is_open(self) -> Optional[bool]:
|
|
@@ -81,6 +90,7 @@ class AsyncChannel:
|
|
|
81
90
|
"""
|
|
82
91
|
LOGGER.info("Channel opened")
|
|
83
92
|
self._channel = channel
|
|
93
|
+
self._declared_exchanges.clear()
|
|
84
94
|
self.add_on_channel_close_callback()
|
|
85
95
|
self.publisher_confirms and self.add_publish_confirms()
|
|
86
96
|
self._prefetch_count and self.set_qos(self._prefetch_count)
|
|
@@ -114,12 +124,14 @@ class AsyncChannel:
|
|
|
114
124
|
:param pika.channel.Channel: The closed channel
|
|
115
125
|
:param Exception reason: why the channel was closed
|
|
116
126
|
"""
|
|
117
|
-
LOGGER.
|
|
127
|
+
LOGGER.warning(f"Channel {channel} was closed by RabbitMQ: {reason}")
|
|
118
128
|
self._consuming = False
|
|
129
|
+
if hasattr(self, "_declared_exchanges"):
|
|
130
|
+
self._declared_exchanges.clear()
|
|
119
131
|
if self._connection.is_closing or self._connection.is_closed:
|
|
120
132
|
LOGGER.info("Connection is closing or already closed")
|
|
121
133
|
else:
|
|
122
|
-
LOGGER.info("Closing connection")
|
|
134
|
+
LOGGER.info(f"Closing connection due to channel closure: {reason}")
|
|
123
135
|
self._connection.close()
|
|
124
136
|
|
|
125
137
|
def add_publish_confirms(self):
|
|
@@ -154,14 +166,36 @@ class AsyncChannel:
|
|
|
154
166
|
be invoked by pika.
|
|
155
167
|
:param str|unicode exchange_name: The name of the exchange to declare
|
|
156
168
|
"""
|
|
157
|
-
|
|
169
|
+
if not exchange_name:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
exchange_type_str = str(getattr(exchange_type, "value", exchange_type))
|
|
173
|
+
durable_bool = bool(durable)
|
|
174
|
+
|
|
175
|
+
if not hasattr(self, "_declared_exchanges"):
|
|
176
|
+
self._declared_exchanges = {}
|
|
177
|
+
|
|
178
|
+
if exchange_name in self._declared_exchanges:
|
|
179
|
+
prev_type, prev_durable = self._declared_exchanges[exchange_name]
|
|
180
|
+
if (prev_type, prev_durable) == (exchange_type_str, durable_bool):
|
|
181
|
+
return
|
|
182
|
+
LOGGER.warning(
|
|
183
|
+
f"Exchange '{exchange_name}' was already declared as type='{prev_type}', durable={prev_durable}. "
|
|
184
|
+
f"Re-declaring with type='{exchange_type_str}', durable={durable_bool} on the same channel may "
|
|
185
|
+
f"trigger PRECONDITION_FAILED."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self._declared_exchanges[exchange_name] = (exchange_type_str, durable_bool)
|
|
189
|
+
LOGGER.info(
|
|
190
|
+
f"Declaring exchange: {exchange_name} (type={exchange_type_str}, durable={durable_bool})"
|
|
191
|
+
)
|
|
158
192
|
# Note: using functools.partial is not required, it is demonstrating
|
|
159
193
|
# how arbitrary data can be passed to the callback when it is called
|
|
160
194
|
cb = partial(self.on_exchange_declareok, userdata=exchange_name)
|
|
161
195
|
self._channel.exchange_declare(
|
|
162
196
|
exchange=exchange_name,
|
|
163
|
-
exchange_type=
|
|
164
|
-
durable=
|
|
197
|
+
exchange_type=exchange_type_str,
|
|
198
|
+
durable=durable_bool,
|
|
165
199
|
callback=cb,
|
|
166
200
|
)
|
|
167
201
|
|
|
@@ -361,7 +395,7 @@ class AsyncChannel:
|
|
|
361
395
|
self._channel.basic_publish(
|
|
362
396
|
exchange_name,
|
|
363
397
|
routing_key,
|
|
364
|
-
|
|
398
|
+
self._serialize_payload(body, content_type),
|
|
365
399
|
properties=BasicProperties(
|
|
366
400
|
reply_to=self._callback_queue, # Response queue address
|
|
367
401
|
correlation_id=corr_id, # Request/response matching ID
|
|
@@ -436,7 +470,7 @@ class AsyncChannel:
|
|
|
436
470
|
self._channel.basic_publish(
|
|
437
471
|
exchange_name,
|
|
438
472
|
routing_key,
|
|
439
|
-
|
|
473
|
+
self._serialize_payload(body, content_type),
|
|
440
474
|
properties=BasicProperties(
|
|
441
475
|
reply_to=self._callback_queue,
|
|
442
476
|
content_type=content_type,
|
|
@@ -491,13 +525,14 @@ class AsyncChannel:
|
|
|
491
525
|
exchange_type="topic",
|
|
492
526
|
durable=True,
|
|
493
527
|
auto_delete=False,
|
|
528
|
+
auto_decode=True,
|
|
494
529
|
):
|
|
495
530
|
await self.start_rpc_publisher()
|
|
496
531
|
self.register_queue(
|
|
497
532
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
498
533
|
)
|
|
499
534
|
self.register_handler(
|
|
500
|
-
queue_name, routing_key, callback, content_type, timeout
|
|
535
|
+
queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
|
|
501
536
|
)
|
|
502
537
|
|
|
503
538
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -518,30 +553,52 @@ class AsyncChannel:
|
|
|
518
553
|
self,
|
|
519
554
|
exchange_name,
|
|
520
555
|
queue_name: str,
|
|
521
|
-
exchange_type="
|
|
556
|
+
exchange_type="topic",
|
|
522
557
|
durable=True,
|
|
523
558
|
auto_delete=False,
|
|
524
559
|
):
|
|
525
560
|
if queue_name not in self.subscribes:
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
self
|
|
561
|
+
self.subscribes[queue_name] = {}
|
|
562
|
+
exchange_type_str = str(getattr(exchange_type, "value", exchange_type))
|
|
563
|
+
if not hasattr(self, "_queue_exchange_type"):
|
|
564
|
+
self._queue_exchange_type = {}
|
|
565
|
+
self._queue_exchange_type[queue_name] = exchange_type_str
|
|
566
|
+
|
|
567
|
+
self.setup_exchange(exchange_name, exchange_type, durable=durable)
|
|
529
568
|
self.queue_declare(queue_name, durable=durable, auto_delete=auto_delete)
|
|
530
569
|
|
|
531
570
|
if queue_name not in self.consumers:
|
|
532
571
|
self.consumers[queue_name] = False
|
|
533
572
|
|
|
573
|
+
def _compile_routing_pattern(self, queue_name: str, routing_key: str):
|
|
574
|
+
if not hasattr(self, "_compiled_patterns"):
|
|
575
|
+
self._compiled_patterns = {}
|
|
576
|
+
if queue_name not in self._compiled_patterns:
|
|
577
|
+
self._compiled_patterns[queue_name] = []
|
|
578
|
+
|
|
579
|
+
if "*" in routing_key or "#" in routing_key:
|
|
580
|
+
# AMQP topic wildcard pattern (* = single word [^.]+, # = zero or more words .*)
|
|
581
|
+
amqp_regex = "^" + re.escape(routing_key).replace(r"\*", r"[^.]+").replace(r"\#", r".*") + "$"
|
|
582
|
+
try:
|
|
583
|
+
self._compiled_patterns[queue_name].append((routing_key, re.compile(amqp_regex)))
|
|
584
|
+
except re.error:
|
|
585
|
+
pass
|
|
586
|
+
|
|
534
587
|
def register_handler(
|
|
535
|
-
self, queue_name, routing_key, callback, content_type, response_timeout
|
|
588
|
+
self, queue_name, routing_key, callback, content_type, response_timeout, auto_decode=True
|
|
536
589
|
):
|
|
590
|
+
if queue_name not in self.subscribes:
|
|
591
|
+
self.subscribes[queue_name] = {}
|
|
537
592
|
if routing_key not in self.subscribes[queue_name]:
|
|
538
593
|
self.subscribes[queue_name][routing_key] = {
|
|
539
594
|
"handle": callback,
|
|
540
595
|
"content_type": content_type,
|
|
541
596
|
"response_timeout": response_timeout or self.response_timeout,
|
|
597
|
+
"auto_decode": auto_decode,
|
|
542
598
|
}
|
|
543
599
|
if "*" in routing_key and routing_key not in self.re_subscribes:
|
|
544
600
|
self.re_subscribes.append(routing_key)
|
|
601
|
+
self._compile_routing_pattern(queue_name, routing_key)
|
|
545
602
|
|
|
546
603
|
async def subscribe(
|
|
547
604
|
self,
|
|
@@ -554,12 +611,13 @@ class AsyncChannel:
|
|
|
554
611
|
exchange_type: str = "topic",
|
|
555
612
|
durable: bool = True,
|
|
556
613
|
auto_delete: bool = False,
|
|
614
|
+
auto_decode: bool = True,
|
|
557
615
|
) -> None:
|
|
558
616
|
self.register_queue(
|
|
559
617
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
560
618
|
)
|
|
561
619
|
self.register_handler(
|
|
562
|
-
queue_name, routing_key, callback, content_type, timeout
|
|
620
|
+
queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
|
|
563
621
|
)
|
|
564
622
|
|
|
565
623
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -587,7 +645,11 @@ class AsyncChannel:
|
|
|
587
645
|
|
|
588
646
|
async def process_message(handler: HandlerType):
|
|
589
647
|
nonlocal body
|
|
590
|
-
|
|
648
|
+
if handler.get("auto_decode", True) and handler.get("content_type") == "application/json":
|
|
649
|
+
try:
|
|
650
|
+
body = loads(body)
|
|
651
|
+
except Exception:
|
|
652
|
+
pass
|
|
591
653
|
response = await wait_for(
|
|
592
654
|
handler["handle"](body),
|
|
593
655
|
timeout=handler["response_timeout"],
|
|
@@ -609,30 +671,39 @@ class AsyncChannel:
|
|
|
609
671
|
|
|
610
672
|
async def handle_message(queue_name, basic_deliver, props):
|
|
611
673
|
try:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
674
|
+
handlers = self.subscribes.get(queue_name, {})
|
|
675
|
+
handler = handlers.get(basic_deliver.routing_key)
|
|
676
|
+
if handler:
|
|
677
|
+
return await process_message(handler)
|
|
678
|
+
|
|
679
|
+
queue_type = getattr(self, "_queue_exchange_type", {}).get(queue_name, "topic")
|
|
680
|
+
if queue_type != "direct":
|
|
681
|
+
patterns = getattr(self, "_compiled_patterns", {}).get(queue_name)
|
|
682
|
+
if patterns:
|
|
683
|
+
for pattern_key, pattern in patterns:
|
|
684
|
+
if pattern.match(basic_deliver.routing_key):
|
|
685
|
+
pattern_handler = handlers.get(pattern_key)
|
|
686
|
+
if pattern_handler:
|
|
687
|
+
return await process_message(pattern_handler)
|
|
688
|
+
elif self.re_subscribes:
|
|
689
|
+
for routing_key in self.re_subscribes:
|
|
690
|
+
if routing_key in handlers and re.match(routing_key, basic_deliver.routing_key):
|
|
691
|
+
return await process_message(handlers[routing_key])
|
|
692
|
+
|
|
693
|
+
if self.rpc_publisher_started and props.reply_to:
|
|
694
|
+
self._channel_rpc.basic_publish(
|
|
695
|
+
"",
|
|
696
|
+
props.reply_to,
|
|
697
|
+
"Provider Error: routing_key not binded",
|
|
698
|
+
properties=BasicProperties(
|
|
699
|
+
correlation_id=props.correlation_id,
|
|
700
|
+
content_type="text/plain",
|
|
701
|
+
type="error",
|
|
702
|
+
),
|
|
635
703
|
)
|
|
704
|
+
return not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
705
|
+
basic_deliver.delivery_tag, requeue=False
|
|
706
|
+
)
|
|
636
707
|
|
|
637
708
|
except BaseException as err:
|
|
638
709
|
if self.rpc_publisher_started and props.reply_to:
|