rabbitmq-amqp-python-client 0.2.0__tar.gz → 0.3.0__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-0.3.0/PKG-INFO +104 -0
- rabbitmq_amqp_python_client-0.3.0/README.md +82 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/pyproject.toml +5 -1
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/__init__.py +12 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/__init__.py +13 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/connection.py +317 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/consumer.py +339 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/environment.py +158 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/management.py +375 -0
- rabbitmq_amqp_python_client-0.3.0/rabbitmq_amqp_python_client/asyncio/publisher.py +157 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/management.py +16 -4
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/publisher.py +12 -1
- rabbitmq_amqp_python_client-0.2.0/PKG-INFO +0 -65
- rabbitmq_amqp_python_client-0.2.0/README.md +0 -43
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/LICENSE +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/address_helper.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/amqp_consumer_handler.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/common.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/connection.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/consumer.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/delivery_context.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/entities.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/environment.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/exceptions.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/options.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/py.typed +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/__init__.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_common.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_condition.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_data.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_delivery.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_endpoints.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_events.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_exceptions.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_handler.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_handlers.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_io.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_message.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_reactor.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_selectable.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_tracing.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_transport.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_url.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_utils.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/_wrapper.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/handlers.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/py.typed +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/reactor.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/tracing.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/qpid/proton/utils.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/queues.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/ssl_configuration.py +0 -0
- {rabbitmq_amqp_python_client-0.2.0 → rabbitmq_amqp_python_client-0.3.0}/rabbitmq_amqp_python_client/utils.py +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rabbitmq-amqp-python-client
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python RabbitMQ client for AMQP 1.0 protocol
|
|
5
|
+
License: Apache-2.0 license
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: RabbitMQ team
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: packaging (>=23.0,<24.0)
|
|
18
|
+
Requires-Dist: python-qpid-proton (>=0.39.0,<0.40.0)
|
|
19
|
+
Requires-Dist: typing-extensions (>=4.13.0,<5.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
## RabbitMQ AMQP 1.0 Python Client
|
|
23
|
+
This library is meant to be used with RabbitMQ `4.x`. Suitable for testing in pre-production environments.
|
|
24
|
+
|
|
25
|
+
The client is distributed via [`PIP`](https://pypi.org/project/rabbitmq-amqp-python-client/):
|
|
26
|
+
```bash
|
|
27
|
+
pip install rabbitmq-amqp-python-client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Getting Started
|
|
31
|
+
|
|
32
|
+
Inside the [examples](./examples) folder you can find a set of examples that show how to use the client.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
[Client Guide](https://www.rabbitmq.com/client-libraries/amqp-client-libraries) select the python section.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Build
|
|
41
|
+
|
|
42
|
+
- `make rabbitmq-server`: run the RabbitMQ server in a docker container
|
|
43
|
+
- `poetry build`: build the source project
|
|
44
|
+
- `poetry install`: resolves and install dependencies
|
|
45
|
+
- `make test`: run the tests
|
|
46
|
+
|
|
47
|
+
### Note for MAC users:
|
|
48
|
+
|
|
49
|
+
To run TLS you need to:
|
|
50
|
+
``` bash
|
|
51
|
+
- pip uninstall python-qpid-proton
|
|
52
|
+
- brew install swig
|
|
53
|
+
- brew install pkg-config
|
|
54
|
+
- export CFLAGS="-I/usr/local/opt/openssl/include"; pip install python-qpid-proton --verbose --no-cache-dir
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Read more about the issue [here](https://stackoverflow.com/questions/44979947/python-qpid-proton-for-mac-using-amqps)
|
|
58
|
+
|
|
59
|
+
### SSL Problems in local environment
|
|
60
|
+
|
|
61
|
+
If when running tests, this exceptions is raised by the proton library: `SSLUnavailable`:
|
|
62
|
+
``` bash
|
|
63
|
+
pip uninstall python-qpid-proton -y
|
|
64
|
+
|
|
65
|
+
sudo apt-get update
|
|
66
|
+
sudo apt-get install -y swig cmake build-essential libssl-dev pkg-config
|
|
67
|
+
|
|
68
|
+
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
|
|
69
|
+
export CFLAGS="-I/usr/include/openssl"
|
|
70
|
+
export LDFLAGS="-L/usr/lib/x86_64-linux-gnu"
|
|
71
|
+
|
|
72
|
+
pip install "python-qpid-proton>=0.39.0,<0.40.0" --no-binary python-qpid-proton --verbose --no-cache-dir
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Async Interface (Experimental)
|
|
76
|
+
|
|
77
|
+
The client provides an async interface via the `rabbitmq_amqp_python_client.asyncio` module. The async classes act as facades that:
|
|
78
|
+
|
|
79
|
+
- Wrap the corresponding synchronous classes
|
|
80
|
+
- Execute blocking operations in a thread pool executor using `run_in_executor`
|
|
81
|
+
- Coordinate concurrent access using `asyncio.Lock`
|
|
82
|
+
- Implement proper async context managers (`async with`) for resource management
|
|
83
|
+
- Maintain API compatibility with the synchronous version
|
|
84
|
+
|
|
85
|
+
**Key differences from the synchronous interface:**
|
|
86
|
+
|
|
87
|
+
1. Use `AsyncEnvironment` instead of `Environment`
|
|
88
|
+
2. All operations must be awaited with `await`
|
|
89
|
+
3. Use `async with` for resource management (connections, publishers, consumers, management)
|
|
90
|
+
4. Consumer signal handling uses `asyncio.Event` and `loop.add_signal_handler`
|
|
91
|
+
|
|
92
|
+
For a complete example showing proper consumer termination and signal handling, refer to:
|
|
93
|
+
|
|
94
|
+
- [examples/getting_started/getting_started_async.py](./examples/getting_started/getting_started_async.py)
|
|
95
|
+
|
|
96
|
+
Additional async examples are available in the [examples](./examples) folder:
|
|
97
|
+
|
|
98
|
+
- OAuth2: [examples/oauth/oAuth2_async.py](./examples/oauth/oAuth2_async.py)
|
|
99
|
+
- Reconnection: [examples/reconnection/reconnection_example_async.py](./examples/reconnection/reconnection_example_async.py)
|
|
100
|
+
- Streams: [examples/streams/example_with_streams_async.py](./examples/streams/example_with_streams_async.py)
|
|
101
|
+
- TLS: [examples/tls/tls_example_async.py](./examples/tls/tls_example_async.py)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
## RabbitMQ AMQP 1.0 Python Client
|
|
2
|
+
This library is meant to be used with RabbitMQ `4.x`. Suitable for testing in pre-production environments.
|
|
3
|
+
|
|
4
|
+
The client is distributed via [`PIP`](https://pypi.org/project/rabbitmq-amqp-python-client/):
|
|
5
|
+
```bash
|
|
6
|
+
pip install rabbitmq-amqp-python-client
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Getting Started
|
|
10
|
+
|
|
11
|
+
Inside the [examples](./examples) folder you can find a set of examples that show how to use the client.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
[Client Guide](https://www.rabbitmq.com/client-libraries/amqp-client-libraries) select the python section.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Build
|
|
20
|
+
|
|
21
|
+
- `make rabbitmq-server`: run the RabbitMQ server in a docker container
|
|
22
|
+
- `poetry build`: build the source project
|
|
23
|
+
- `poetry install`: resolves and install dependencies
|
|
24
|
+
- `make test`: run the tests
|
|
25
|
+
|
|
26
|
+
### Note for MAC users:
|
|
27
|
+
|
|
28
|
+
To run TLS you need to:
|
|
29
|
+
``` bash
|
|
30
|
+
- pip uninstall python-qpid-proton
|
|
31
|
+
- brew install swig
|
|
32
|
+
- brew install pkg-config
|
|
33
|
+
- export CFLAGS="-I/usr/local/opt/openssl/include"; pip install python-qpid-proton --verbose --no-cache-dir
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Read more about the issue [here](https://stackoverflow.com/questions/44979947/python-qpid-proton-for-mac-using-amqps)
|
|
37
|
+
|
|
38
|
+
### SSL Problems in local environment
|
|
39
|
+
|
|
40
|
+
If when running tests, this exceptions is raised by the proton library: `SSLUnavailable`:
|
|
41
|
+
``` bash
|
|
42
|
+
pip uninstall python-qpid-proton -y
|
|
43
|
+
|
|
44
|
+
sudo apt-get update
|
|
45
|
+
sudo apt-get install -y swig cmake build-essential libssl-dev pkg-config
|
|
46
|
+
|
|
47
|
+
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
|
|
48
|
+
export CFLAGS="-I/usr/include/openssl"
|
|
49
|
+
export LDFLAGS="-L/usr/lib/x86_64-linux-gnu"
|
|
50
|
+
|
|
51
|
+
pip install "python-qpid-proton>=0.39.0,<0.40.0" --no-binary python-qpid-proton --verbose --no-cache-dir
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Async Interface (Experimental)
|
|
55
|
+
|
|
56
|
+
The client provides an async interface via the `rabbitmq_amqp_python_client.asyncio` module. The async classes act as facades that:
|
|
57
|
+
|
|
58
|
+
- Wrap the corresponding synchronous classes
|
|
59
|
+
- Execute blocking operations in a thread pool executor using `run_in_executor`
|
|
60
|
+
- Coordinate concurrent access using `asyncio.Lock`
|
|
61
|
+
- Implement proper async context managers (`async with`) for resource management
|
|
62
|
+
- Maintain API compatibility with the synchronous version
|
|
63
|
+
|
|
64
|
+
**Key differences from the synchronous interface:**
|
|
65
|
+
|
|
66
|
+
1. Use `AsyncEnvironment` instead of `Environment`
|
|
67
|
+
2. All operations must be awaited with `await`
|
|
68
|
+
3. Use `async with` for resource management (connections, publishers, consumers, management)
|
|
69
|
+
4. Consumer signal handling uses `asyncio.Event` and `loop.add_signal_handler`
|
|
70
|
+
|
|
71
|
+
For a complete example showing proper consumer termination and signal handling, refer to:
|
|
72
|
+
|
|
73
|
+
- [examples/getting_started/getting_started_async.py](./examples/getting_started/getting_started_async.py)
|
|
74
|
+
|
|
75
|
+
Additional async examples are available in the [examples](./examples) folder:
|
|
76
|
+
|
|
77
|
+
- OAuth2: [examples/oauth/oAuth2_async.py](./examples/oauth/oAuth2_async.py)
|
|
78
|
+
- Reconnection: [examples/reconnection/reconnection_example_async.py](./examples/reconnection/reconnection_example_async.py)
|
|
79
|
+
- Streams: [examples/streams/example_with_streams_async.py](./examples/streams/example_with_streams_async.py)
|
|
80
|
+
- TLS: [examples/tls/tls_example_async.py](./examples/tls/tls_example_async.py)
|
|
81
|
+
|
|
82
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "rabbitmq-amqp-python-client"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Python RabbitMQ client for AMQP 1.0 protocol"
|
|
5
5
|
authors = ["RabbitMQ team"]
|
|
6
6
|
license = "Apache-2.0 license"
|
|
@@ -23,7 +23,11 @@ pytest = "^8.3.4"
|
|
|
23
23
|
black = "^24.3.0"
|
|
24
24
|
python-qpid-proton = "^0.39.0"
|
|
25
25
|
requests = "^2.31.0"
|
|
26
|
+
pytest-asyncio = "^1.2.0"
|
|
26
27
|
|
|
27
28
|
[build-system]
|
|
28
29
|
requires = ["poetry-core"]
|
|
29
30
|
build-backend = "poetry.core.masonry.api"
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
asyncio_mode = "auto"
|
|
@@ -2,6 +2,13 @@ from importlib import metadata
|
|
|
2
2
|
|
|
3
3
|
from .address_helper import AddressHelper
|
|
4
4
|
from .amqp_consumer_handler import AMQPMessagingHandler
|
|
5
|
+
from .asyncio import (
|
|
6
|
+
AsyncConnection,
|
|
7
|
+
AsyncConsumer,
|
|
8
|
+
AsyncEnvironment,
|
|
9
|
+
AsyncManagement,
|
|
10
|
+
AsyncPublisher,
|
|
11
|
+
)
|
|
5
12
|
from .common import ExchangeType, QueueType
|
|
6
13
|
from .connection import Connection
|
|
7
14
|
from .consumer import Consumer
|
|
@@ -99,4 +106,9 @@ __all__ = [
|
|
|
99
106
|
"RecoveryConfiguration",
|
|
100
107
|
"OAuth2Options",
|
|
101
108
|
"Converter",
|
|
109
|
+
"AsyncConnection",
|
|
110
|
+
"AsyncConsumer",
|
|
111
|
+
"AsyncPublisher",
|
|
112
|
+
"AsyncManagement",
|
|
113
|
+
"AsyncEnvironment",
|
|
102
114
|
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .connection import AsyncConnection
|
|
2
|
+
from .consumer import AsyncConsumer
|
|
3
|
+
from .environment import AsyncEnvironment
|
|
4
|
+
from .management import AsyncManagement
|
|
5
|
+
from .publisher import AsyncPublisher
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AsyncConnection",
|
|
9
|
+
"AsyncConsumer",
|
|
10
|
+
"AsyncManagement",
|
|
11
|
+
"AsyncPublisher",
|
|
12
|
+
"AsyncEnvironment",
|
|
13
|
+
]
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Callable, Optional, Union
|
|
6
|
+
|
|
7
|
+
from ..address_helper import validate_address
|
|
8
|
+
from ..connection import Connection
|
|
9
|
+
from ..consumer import Consumer
|
|
10
|
+
from ..entities import (
|
|
11
|
+
ConsumerOptions,
|
|
12
|
+
OAuth2Options,
|
|
13
|
+
RecoveryConfiguration,
|
|
14
|
+
)
|
|
15
|
+
from ..exceptions import ArgumentOutOfRangeException
|
|
16
|
+
from ..management import Management
|
|
17
|
+
from ..publisher import Publisher
|
|
18
|
+
from ..qpid.proton._handlers import MessagingHandler
|
|
19
|
+
from ..ssl_configuration import (
|
|
20
|
+
PosixSslConfigurationContext,
|
|
21
|
+
WinSslConfigurationContext,
|
|
22
|
+
)
|
|
23
|
+
from .consumer import AsyncConsumer
|
|
24
|
+
from .management import AsyncManagement
|
|
25
|
+
from .publisher import AsyncPublisher
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AsyncConnection:
|
|
31
|
+
"""
|
|
32
|
+
Asyncio-compatible facade around Connection.
|
|
33
|
+
|
|
34
|
+
This class manages the connection to RabbitMQ and provides factory methods for
|
|
35
|
+
creating publishers, consumers, and management interfaces. It supports both
|
|
36
|
+
single-node and multi-node configurations, as well as SSL/TLS connections.
|
|
37
|
+
|
|
38
|
+
Note:
|
|
39
|
+
The underlying Proton BlockingConnection is NOT thread-safe. A lock is used
|
|
40
|
+
to serialize all operations.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
_connection (Connection): The underlying synchronous Connection
|
|
44
|
+
_connection_lock (asyncio.Lock): Lock for coordinating access to the shared connection
|
|
45
|
+
_async_publishers (list[AsyncPublisher]): List of active async publishers
|
|
46
|
+
_async_consumers (list[AsyncConsumer]): List of active async consumers
|
|
47
|
+
_async_managements (list[AsyncManagement]): List of active async management interfaces
|
|
48
|
+
_remove_callback (Optional[Callable[[AsyncConnection], None]]): Callback on close
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
uri: Optional[str] = None,
|
|
54
|
+
uris: Optional[list[str]] = None,
|
|
55
|
+
ssl_context: Union[
|
|
56
|
+
PosixSslConfigurationContext, WinSslConfigurationContext, None
|
|
57
|
+
] = None,
|
|
58
|
+
oauth2_options: Optional[OAuth2Options] = None,
|
|
59
|
+
recovery_configuration: RecoveryConfiguration = RecoveryConfiguration(),
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Initialize AsyncConnection.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
uri: Optional single-node connection URI
|
|
66
|
+
uris: Optional multi-node connection URIs
|
|
67
|
+
ssl_context: Optional SSL/TLS configuration
|
|
68
|
+
oauth2_options: Optional OAuth2 configuration
|
|
69
|
+
recovery_configuration: Configuration for automatic recovery
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValidationCodeException: If recovery configuration is invalid
|
|
73
|
+
"""
|
|
74
|
+
self._connection = Connection(
|
|
75
|
+
uri=uri,
|
|
76
|
+
uris=uris,
|
|
77
|
+
ssl_context=ssl_context,
|
|
78
|
+
oauth2_options=oauth2_options,
|
|
79
|
+
recovery_configuration=recovery_configuration,
|
|
80
|
+
)
|
|
81
|
+
self._connection_lock = asyncio.Lock()
|
|
82
|
+
self._async_publishers: list[AsyncPublisher] = []
|
|
83
|
+
self._async_consumers: list[AsyncConsumer] = []
|
|
84
|
+
self._async_managements: list[AsyncManagement] = []
|
|
85
|
+
self._remove_callback: Optional[Callable[[AsyncConnection], None]] = None
|
|
86
|
+
|
|
87
|
+
async def dial(self) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Establish a connection to the AMQP server.
|
|
90
|
+
|
|
91
|
+
Configures SSL if specified and establishes the connection using the
|
|
92
|
+
provided URI(s). Also initializes the management interface.
|
|
93
|
+
"""
|
|
94
|
+
async with self._connection_lock:
|
|
95
|
+
await asyncio.to_thread(self._connection.dial)
|
|
96
|
+
|
|
97
|
+
def _set_remove_callback(
|
|
98
|
+
self, callback: Optional[Callable[[AsyncConnection], None]]
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Set callback to be called when connection is closed."""
|
|
101
|
+
self._remove_callback = callback
|
|
102
|
+
|
|
103
|
+
def _remove_publisher(self, publisher: AsyncPublisher) -> None:
|
|
104
|
+
"""Remove a publisher from the active list."""
|
|
105
|
+
if publisher in self._async_publishers:
|
|
106
|
+
self._async_publishers.remove(publisher)
|
|
107
|
+
|
|
108
|
+
def _remove_consumer(self, consumer: AsyncConsumer) -> None:
|
|
109
|
+
"""Remove a consumer from the active list."""
|
|
110
|
+
if consumer in self._async_consumers:
|
|
111
|
+
self._async_consumers.remove(consumer)
|
|
112
|
+
|
|
113
|
+
def _remove_management(self, management: AsyncManagement) -> None:
|
|
114
|
+
"""Remove a management interface from the active list."""
|
|
115
|
+
if management in self._async_managements:
|
|
116
|
+
self._async_managements.remove(management)
|
|
117
|
+
|
|
118
|
+
async def close(self) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Close the connection to the AMQP 1.0 server.
|
|
121
|
+
|
|
122
|
+
Closes the underlying connection and removes it from the connection list.
|
|
123
|
+
"""
|
|
124
|
+
logger.debug("Closing async connection")
|
|
125
|
+
try:
|
|
126
|
+
for async_publisher in self._async_publishers[:]:
|
|
127
|
+
await async_publisher.close()
|
|
128
|
+
for async_consumer in self._async_consumers[:]:
|
|
129
|
+
await async_consumer.close()
|
|
130
|
+
for async_management in self._async_managements[:]:
|
|
131
|
+
await async_management.close()
|
|
132
|
+
|
|
133
|
+
async with self._connection_lock:
|
|
134
|
+
await asyncio.to_thread(self._connection.close)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"Error closing async connections: {e}")
|
|
137
|
+
raise e
|
|
138
|
+
finally:
|
|
139
|
+
if self._remove_callback is not None:
|
|
140
|
+
self._remove_callback(self)
|
|
141
|
+
|
|
142
|
+
def _set_connection_managements(self, management: Management) -> None:
|
|
143
|
+
if len(self._connection._managements) == 0:
|
|
144
|
+
self._connection._managements = [management]
|
|
145
|
+
|
|
146
|
+
async def management(
|
|
147
|
+
self,
|
|
148
|
+
) -> AsyncManagement:
|
|
149
|
+
"""
|
|
150
|
+
Get the management interface for this connection.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
AsyncManagement: The management interface for performing administrative tasks
|
|
154
|
+
"""
|
|
155
|
+
if len(self._async_managements) > 0:
|
|
156
|
+
return self._async_managements[0]
|
|
157
|
+
|
|
158
|
+
async_management: Optional[AsyncManagement] = None
|
|
159
|
+
async with self._connection_lock:
|
|
160
|
+
if len(self._async_managements) == 0:
|
|
161
|
+
async_management = AsyncManagement(
|
|
162
|
+
self._connection._conn,
|
|
163
|
+
connection_lock=self._connection_lock,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self._set_connection_managements(
|
|
167
|
+
async_management._management
|
|
168
|
+
) # TODO: check this
|
|
169
|
+
self._async_managements.append(async_management)
|
|
170
|
+
|
|
171
|
+
async_management._set_remove_callback(self._remove_management)
|
|
172
|
+
|
|
173
|
+
if async_management is not None:
|
|
174
|
+
await async_management.open()
|
|
175
|
+
|
|
176
|
+
return self._async_managements[0]
|
|
177
|
+
|
|
178
|
+
def _set_connection_publishers(self, publisher: Publisher) -> None:
|
|
179
|
+
"""Set the list of publishers in the underlying connection."""
|
|
180
|
+
publisher._set_publishers_list(
|
|
181
|
+
[async_publisher._publisher for async_publisher in self._async_publishers]
|
|
182
|
+
)
|
|
183
|
+
self._connection._publishers.append(publisher)
|
|
184
|
+
|
|
185
|
+
async def publisher(self, destination: str = "") -> AsyncPublisher:
|
|
186
|
+
"""
|
|
187
|
+
Create a new publisher instance.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
destination: Optional default destination for published messages
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
AsyncPublisher: A new publisher instance
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
RuntimeError: If publisher creation fails
|
|
197
|
+
ArgumentOutOfRangeException: If destination address format is invalid
|
|
198
|
+
"""
|
|
199
|
+
if destination != "":
|
|
200
|
+
if not validate_address(destination):
|
|
201
|
+
raise ArgumentOutOfRangeException(
|
|
202
|
+
"destination address must start with /queues or /exchanges"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
async with self._connection_lock:
|
|
206
|
+
async_publisher = AsyncPublisher(
|
|
207
|
+
self._connection._conn,
|
|
208
|
+
destination,
|
|
209
|
+
connection_lock=self._connection_lock,
|
|
210
|
+
)
|
|
211
|
+
await async_publisher.open()
|
|
212
|
+
if async_publisher._publisher is None:
|
|
213
|
+
raise RuntimeError("Failed to create publisher")
|
|
214
|
+
self._set_connection_publishers(
|
|
215
|
+
async_publisher._publisher
|
|
216
|
+
) # TODO: check this
|
|
217
|
+
self._async_publishers.append(async_publisher)
|
|
218
|
+
|
|
219
|
+
async_publisher._set_remove_callback(self._remove_publisher)
|
|
220
|
+
|
|
221
|
+
return async_publisher
|
|
222
|
+
|
|
223
|
+
def _set_connection_consumers(self, consumer: Consumer) -> None:
|
|
224
|
+
"""Set the list of consumers in the underlying connection."""
|
|
225
|
+
self._connection._consumers.append(consumer)
|
|
226
|
+
|
|
227
|
+
async def consumer(
|
|
228
|
+
self,
|
|
229
|
+
destination: str,
|
|
230
|
+
message_handler: Optional[MessagingHandler] = None,
|
|
231
|
+
consumer_options: Optional[ConsumerOptions] = None,
|
|
232
|
+
credit: Optional[int] = None,
|
|
233
|
+
) -> AsyncConsumer:
|
|
234
|
+
"""
|
|
235
|
+
Create a new consumer instance.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
destination: The address to consume from
|
|
239
|
+
message_handler: Optional handler for processing messages
|
|
240
|
+
consumer_options: Optional configuration for queue consumption. Each queue has its own consumer options.
|
|
241
|
+
credit: Optional credit value for flow control
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
AsyncConsumer: A new consumer instance
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
RuntimeError: If consumer creation fails
|
|
248
|
+
ArgumentOutOfRangeException: If destination address format is invalid
|
|
249
|
+
"""
|
|
250
|
+
if not validate_address(destination):
|
|
251
|
+
raise ArgumentOutOfRangeException(
|
|
252
|
+
"destination address must start with /queues or /exchanges"
|
|
253
|
+
)
|
|
254
|
+
if consumer_options is not None:
|
|
255
|
+
consumer_options.validate(
|
|
256
|
+
{
|
|
257
|
+
"4.0.0": self._connection._is_server_version_gte("4.0.0"),
|
|
258
|
+
"4.1.0": self._connection._is_server_version_gte("4.1.0"),
|
|
259
|
+
"4.2.0": self._connection._is_server_version_gte("4.2.0"),
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
async with self._connection_lock:
|
|
264
|
+
async_consumer = AsyncConsumer(
|
|
265
|
+
self._connection._conn,
|
|
266
|
+
destination,
|
|
267
|
+
message_handler, # pyright: ignore[reportArgumentType]
|
|
268
|
+
consumer_options,
|
|
269
|
+
credit,
|
|
270
|
+
connection_lock=self._connection_lock,
|
|
271
|
+
)
|
|
272
|
+
await async_consumer.open()
|
|
273
|
+
if async_consumer._consumer is None:
|
|
274
|
+
raise RuntimeError("Failed to create consumer")
|
|
275
|
+
self._set_connection_consumers(async_consumer._consumer) # TODO: check this
|
|
276
|
+
self._async_consumers.append(async_consumer)
|
|
277
|
+
|
|
278
|
+
async_consumer._set_remove_callback(self._remove_consumer)
|
|
279
|
+
|
|
280
|
+
return async_consumer
|
|
281
|
+
|
|
282
|
+
async def refresh_token(self, token: str) -> None:
|
|
283
|
+
"""
|
|
284
|
+
Refresh the oauth token
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
token: the oauth token to refresh
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ValidationCodeException: If oauth is not enabled
|
|
291
|
+
"""
|
|
292
|
+
async with self._connection_lock:
|
|
293
|
+
await asyncio.to_thread(self._connection.refresh_token, token)
|
|
294
|
+
|
|
295
|
+
async def __aenter__(self) -> AsyncConnection:
|
|
296
|
+
""" "Async context manager entry."""
|
|
297
|
+
await self.dial()
|
|
298
|
+
return self
|
|
299
|
+
|
|
300
|
+
async def __aexit__(
|
|
301
|
+
self,
|
|
302
|
+
exc_type: Optional[type[BaseException]],
|
|
303
|
+
exc: Optional[BaseException],
|
|
304
|
+
tb: Optional[object],
|
|
305
|
+
) -> None:
|
|
306
|
+
"""Async context manager exit."""
|
|
307
|
+
await self.close()
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def active_producers(self) -> int:
|
|
311
|
+
"""Get the number of active producers of the connection."""
|
|
312
|
+
return len(self._async_publishers)
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def active_consumers(self) -> int:
|
|
316
|
+
"""Get the number of active consumers of the connection."""
|
|
317
|
+
return len(self._async_consumers)
|