DefStream-RabbitMQ 0.1.1__py3-none-any.whl
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.
- DefStream/Broker/Implements/RabbitMQ/Configuration.py +48 -0
- DefStream/Broker/Implements/RabbitMQ/Connection.py +449 -0
- DefStream/Broker/Implements/RabbitMQ/Consumer.py +244 -0
- DefStream/Broker/Implements/RabbitMQ/Event.py +106 -0
- DefStream/Broker/Implements/RabbitMQ/Exchange.py +222 -0
- DefStream/Broker/Implements/RabbitMQ/Policies.py +9 -0
- DefStream/Broker/Implements/RabbitMQ/Queue.py +248 -0
- DefStream/Broker/Implements/RabbitMQ/Serializer.py +61 -0
- DefStream/Broker/Implements/RabbitMQ/Stream.py +119 -0
- DefStream/Broker/Implements/RabbitMQ/Subscription.py +47 -0
- DefStream/Broker/Implements/RabbitMQ/__init__.py +47 -0
- defstream_rabbitmq-0.1.1.dist-info/METADATA +143 -0
- defstream_rabbitmq-0.1.1.dist-info/RECORD +16 -0
- defstream_rabbitmq-0.1.1.dist-info/WHEEL +5 -0
- defstream_rabbitmq-0.1.1.dist-info/licenses/LICENSE +674 -0
- defstream_rabbitmq-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ... import (
|
|
4
|
+
Sendable,
|
|
5
|
+
Readable,
|
|
6
|
+
)
|
|
7
|
+
from .Serializer import (
|
|
8
|
+
Encoder,
|
|
9
|
+
Decoder,
|
|
10
|
+
)
|
|
11
|
+
from .Event import Event
|
|
12
|
+
|
|
13
|
+
from pika import (
|
|
14
|
+
BlockingConnection,
|
|
15
|
+
BasicProperties,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from time import time
|
|
19
|
+
from uuid import uuid4
|
|
20
|
+
|
|
21
|
+
from typing import (
|
|
22
|
+
Dict,
|
|
23
|
+
Iterable,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = (
|
|
27
|
+
'Queue'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Stream(
|
|
32
|
+
Sendable,
|
|
33
|
+
Readable,
|
|
34
|
+
):
|
|
35
|
+
"""Stream of Event Broker for RabbitMQ"""
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
connection: BlockingConnection,
|
|
39
|
+
encode: Encoder,
|
|
40
|
+
decode: Decoder,
|
|
41
|
+
name: str = None,
|
|
42
|
+
):
|
|
43
|
+
self.connection = connection
|
|
44
|
+
self.encode = encode
|
|
45
|
+
self.decode = decode
|
|
46
|
+
self.channel = self.connection.channel()
|
|
47
|
+
self.channel.auto_decode = False
|
|
48
|
+
self.queue = name
|
|
49
|
+
self.event = None
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
def __del__(self):
|
|
53
|
+
if self.channel and self.channel.is_open:
|
|
54
|
+
self.channel.close()
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
def __str__(self): return self.queue
|
|
58
|
+
|
|
59
|
+
def send(
|
|
60
|
+
self,
|
|
61
|
+
body,
|
|
62
|
+
event: str = None,
|
|
63
|
+
headers: Dict = {},
|
|
64
|
+
timestamp: int = None,
|
|
65
|
+
id: str = None,
|
|
66
|
+
):
|
|
67
|
+
if not timestamp: timestamp = int(time())
|
|
68
|
+
if not id: id = uuid4().hex
|
|
69
|
+
properties = BasicProperties(
|
|
70
|
+
type=event if event else '',
|
|
71
|
+
headers=headers,
|
|
72
|
+
content_type=self.encode.format,
|
|
73
|
+
content_encoding=self.encode.charset,
|
|
74
|
+
timestamp=timestamp,
|
|
75
|
+
message_id=id,
|
|
76
|
+
)
|
|
77
|
+
self.channel.basic_publish(
|
|
78
|
+
exchange='',
|
|
79
|
+
routing_key=self.queue,
|
|
80
|
+
properties=properties,
|
|
81
|
+
body=self.encode(body)
|
|
82
|
+
)
|
|
83
|
+
return id
|
|
84
|
+
|
|
85
|
+
def read(self, offset: int = 0, timeout: int = None) -> Iterable[Event]:
|
|
86
|
+
channel = self.channel
|
|
87
|
+
channel.basic_qos(0, 1, False)
|
|
88
|
+
|
|
89
|
+
deadline = time() + (timeout / 1000) if timeout else None
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
for method, properties, body in channel.consume(
|
|
93
|
+
queue=self.queue,
|
|
94
|
+
auto_ack=False,
|
|
95
|
+
inactivity_timeout=1,
|
|
96
|
+
arguments={'x-stream-offset': offset},
|
|
97
|
+
):
|
|
98
|
+
if method is None:
|
|
99
|
+
if deadline and time() >= deadline:
|
|
100
|
+
break
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
event = Event(
|
|
104
|
+
channel,
|
|
105
|
+
self.queue,
|
|
106
|
+
method.consumer_tag,
|
|
107
|
+
method.delivery_tag,
|
|
108
|
+
properties,
|
|
109
|
+
self.decode(body, properties.content_type, properties.content_encoding),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
yield event
|
|
114
|
+
channel.basic_ack(delivery_tag=method.delivery_tag)
|
|
115
|
+
except Exception:
|
|
116
|
+
channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
|
|
117
|
+
raise
|
|
118
|
+
finally:
|
|
119
|
+
channel.cancel()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Optional,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
'Subscription',
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Subscription:
|
|
15
|
+
queue: str
|
|
16
|
+
offset: Optional[int] = None
|
|
17
|
+
qos: Optional[int] = None
|
|
18
|
+
error: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
def __post_init__(self):
|
|
21
|
+
if not isinstance(self.queue, str) or not self.queue:
|
|
22
|
+
raise ValueError('Subscription queue must be a non-empty string')
|
|
23
|
+
if self.error is not None:
|
|
24
|
+
if not isinstance(
|
|
25
|
+
self.error,
|
|
26
|
+
str,
|
|
27
|
+
) or not self.error:
|
|
28
|
+
raise ValueError('Subscription error must be a non-empty string or None')
|
|
29
|
+
if self.offset is not None and (isinstance(self.offset, bool) or not isinstance(self.offset, int)):
|
|
30
|
+
raise ValueError('Subscription offset must be an integer')
|
|
31
|
+
if self.qos is not None:
|
|
32
|
+
if isinstance(
|
|
33
|
+
self.qos,
|
|
34
|
+
bool,
|
|
35
|
+
) or not isinstance(
|
|
36
|
+
self.qos,
|
|
37
|
+
int,
|
|
38
|
+
) or not 0 <= self.qos <= 65535:
|
|
39
|
+
raise ValueError('Subscription qos must be an integer between 0 and 65535')
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def parse(cls, parameters: Any) -> 'Subscription':
|
|
43
|
+
if isinstance(parameters, cls):
|
|
44
|
+
return parameters
|
|
45
|
+
if isinstance(parameters, Mapping):
|
|
46
|
+
return cls(**dict(parameters))
|
|
47
|
+
raise TypeError('RabbitMQ subscriptions require a Subscription or mapping')
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from .Configuration import Configuration
|
|
4
|
+
from .Connection import (
|
|
5
|
+
Connection,
|
|
6
|
+
ExchangeType,
|
|
7
|
+
Parameters,
|
|
8
|
+
All,
|
|
9
|
+
Any,
|
|
10
|
+
)
|
|
11
|
+
from .Exchange import Exchange
|
|
12
|
+
from .Queue import Queue
|
|
13
|
+
from .Stream import Stream
|
|
14
|
+
from .Consumer import Consumer
|
|
15
|
+
from .Subscription import Subscription
|
|
16
|
+
from .Policies import (
|
|
17
|
+
Requeue,
|
|
18
|
+
Reject,
|
|
19
|
+
)
|
|
20
|
+
from .Event import (
|
|
21
|
+
Event,
|
|
22
|
+
EventHandler,
|
|
23
|
+
)
|
|
24
|
+
from .Serializer import (
|
|
25
|
+
Encoder,
|
|
26
|
+
Decoder,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = (
|
|
30
|
+
'Configuration',
|
|
31
|
+
'Connection',
|
|
32
|
+
'ExchangeType',
|
|
33
|
+
'Parameters',
|
|
34
|
+
'All',
|
|
35
|
+
'Any',
|
|
36
|
+
'Exchange',
|
|
37
|
+
'Queue',
|
|
38
|
+
'Stream',
|
|
39
|
+
'Consumer',
|
|
40
|
+
'Subscription',
|
|
41
|
+
'Requeue',
|
|
42
|
+
'Reject',
|
|
43
|
+
'Event',
|
|
44
|
+
'EventHandler',
|
|
45
|
+
'Encoder',
|
|
46
|
+
'Decoder',
|
|
47
|
+
)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: DefStream-RabbitMQ
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: RabbitMQ broker adapter for DefStream
|
|
5
|
+
Home-page: https://github.com/yong5eon/DefStream-RabbitMQ
|
|
6
|
+
Author: Heo Yongseon
|
|
7
|
+
Project-URL: Source, https://github.com/yong5eon/DefStream-RabbitMQ
|
|
8
|
+
Project-URL: Issues, https://github.com/yong5eon/DefStream-RabbitMQ/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: DefStream>=0.1.0
|
|
19
|
+
Requires-Dist: pika>=1.3.2
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
Dynamic: project-url
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# DefStream-RabbitMQ
|
|
32
|
+
|
|
33
|
+
## CI and PyPI releases
|
|
34
|
+
|
|
35
|
+
The `CI` workflow tests PRs targeting `master` and pushes to `master` on Python
|
|
36
|
+
3.9 through 3.14. It builds all three DefStream packages from source, installs
|
|
37
|
+
their wheels in a clean environment, and runs this repository's tests with
|
|
38
|
+
RabbitMQ 4.1 and Redis 7.4 service containers. The current repository uses the
|
|
39
|
+
event commit; sibling repositories use their `master` branches, with resolved
|
|
40
|
+
commit IDs recorded in the log. All three GitHub repositories must be accessible.
|
|
41
|
+
This bootstrap setup does not require existing PyPI releases and does not
|
|
42
|
+
certify compatibility with previously published dependencies.
|
|
43
|
+
|
|
44
|
+
On a `master` push, the build uses `major.minor.<github.run_number>` from the
|
|
45
|
+
checked-in `VERSION` (for example, `0.1.42`). PR builds retain `VERSION`. No
|
|
46
|
+
version commit, PR, or tag is created. Change major/minor to start a new series;
|
|
47
|
+
run numbers can have gaps. Check for PyPI version collisions before replacing
|
|
48
|
+
or renaming the workflow.
|
|
49
|
+
|
|
50
|
+
Only after all six Python checks pass does a separate publish job upload the
|
|
51
|
+
exact Python 3.13 build artifacts. A fully published version is skipped on a
|
|
52
|
+
rerun; first-attempt collisions, incomplete releases, and unexpected network
|
|
53
|
+
errors fail. Active runs are not cancelled; older pending runs may be superseded.
|
|
54
|
+
|
|
55
|
+
Configure `PYPI_API_TOKEN` as a repository Actions secret or a secret in the
|
|
56
|
+
`pypi` environment. Restrict that environment to `master`; omit approval rules
|
|
57
|
+
for fully automatic releases. Protect `master` with required PRs and all six
|
|
58
|
+
`Test Python 3.x` checks, not the push-only `Publish PyPI` job. Never commit the
|
|
59
|
+
token. Publish the core before this adapter so users can resolve its dependency.
|
|
60
|
+
|
|
61
|
+
RabbitMQ broker adapter for DefStream, distributed separately from the core.
|
|
62
|
+
Requires Python 3.9 or newer.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
pip install DefStream-RabbitMQ
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This installs `DefStream>=0.1.0` and `pika>=1.3.2`. The distribution name is
|
|
71
|
+
`DefStream-RabbitMQ`; the Python import path remains
|
|
72
|
+
`DefStream.Broker.Implements.RabbitMQ`.
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from DefStream.Broker.Implements.RabbitMQ import (
|
|
78
|
+
Configuration,
|
|
79
|
+
Connection,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
broker = Connection(Configuration(
|
|
83
|
+
host='127.0.0.1',
|
|
84
|
+
port=5672,
|
|
85
|
+
username='guest',
|
|
86
|
+
password='guest',
|
|
87
|
+
))
|
|
88
|
+
broker.connect()
|
|
89
|
+
try:
|
|
90
|
+
broker.create_queue('tasks')
|
|
91
|
+
broker.queue('tasks').send({'message': 'hello'})
|
|
92
|
+
finally:
|
|
93
|
+
broker.close()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A reachable RabbitMQ server is required. Use connections and their resources
|
|
97
|
+
on the thread that owns them.
|
|
98
|
+
|
|
99
|
+
The adapter provides queues, exchanges, streams, subscriptions, error policies,
|
|
100
|
+
and synchronous queue/exchange RPC. Use its `Connection` with
|
|
101
|
+
`DefStream.Worker.Worker` for worker execution.
|
|
102
|
+
|
|
103
|
+
## Source layout
|
|
104
|
+
|
|
105
|
+
The contents of the former `DefStream/src/Broker/Implements/RabbitMQ` directory
|
|
106
|
+
now live directly in this project's [src](./src) directory. Packaging maps that
|
|
107
|
+
directory to `DefStream.Broker.Implements.RabbitMQ`; this distribution does not
|
|
108
|
+
own or overwrite the parent packages supplied by DefStream.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
With the core and both adapters checked out as sibling directories, run this
|
|
113
|
+
from their common parent using your virtual environment's Python:
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
python -m pip install --config-settings editable_mode=strict -e .\DefStream
|
|
117
|
+
python -m pip install -e .\DefStream-RabbitMQ -e .\DefStream-Redis
|
|
118
|
+
python -m unittest discover -s .\DefStream-RabbitMQ\test -p "test_*.py"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The core uses strict editable mode to prevent the adapters' editable namespace
|
|
122
|
+
entries from masking its packages. Keep the core's generated `build` link tree;
|
|
123
|
+
reinstall the core after adding or removing its modules. Adapters use default
|
|
124
|
+
editable mode.
|
|
125
|
+
|
|
126
|
+
RabbitMQ-specific tests live in this project's `test` directory, covering
|
|
127
|
+
resource methods, subscriptions, consumers, RPC, and broker integration.
|
|
128
|
+
Broker examples live in `sample`. Shared Worker, Inspector, and cross-adapter
|
|
129
|
+
contract tests remain in the DefStream repository and require both adapters.
|
|
130
|
+
|
|
131
|
+
Run `python -m unittest discover -s test -p "test_*.py"` from this project
|
|
132
|
+
directory after installation. Integration tests are opt-in via
|
|
133
|
+
`DEFSTREAM_RABBITMQ_TESTS=1`; ordinary unit tests do not require a running broker
|
|
134
|
+
or the Redis adapter.
|
|
135
|
+
|
|
136
|
+
Do not combine editable installs with a `site-packages/DefStream` link pointing
|
|
137
|
+
directly at the core source: installing adapter files through that link can
|
|
138
|
+
write into the core checkout. Remove such a link before installing the projects.
|
|
139
|
+
Reinstall after changing package metadata or dependencies.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
See [LICENSE](./LICENSE), copied unchanged from DefStream.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
DefStream/Broker/Implements/RabbitMQ/Configuration.py,sha256=UFE3BCny4KXmLqEUuyDNXUvYcy9yleqCNio1LF1u_y8,1046
|
|
2
|
+
DefStream/Broker/Implements/RabbitMQ/Connection.py,sha256=cDCfzKmYHs5WMQZvmvNSq7EAuLVRtHVhSBuYHLmDNks,8926
|
|
3
|
+
DefStream/Broker/Implements/RabbitMQ/Consumer.py,sha256=KUBhqiDujMKzcFuyPyzsbDv-5uMmrIYFAgRJ9KdWBwk,5182
|
|
4
|
+
DefStream/Broker/Implements/RabbitMQ/Event.py,sha256=LmSiaSUqJF_kx7v78tt-bawy797z5Q-lM5OO0hnIQcc,1747
|
|
5
|
+
DefStream/Broker/Implements/RabbitMQ/Exchange.py,sha256=MjOdtn1Aeh20mzGZOsa0MxMlpePpyQkz49_VDlf_7oM,4815
|
|
6
|
+
DefStream/Broker/Implements/RabbitMQ/Policies.py,sha256=kT7vreCclwsu5hMAFkDldsNBktuOMB611biOePk7Lr4,85
|
|
7
|
+
DefStream/Broker/Implements/RabbitMQ/Queue.py,sha256=cQTO988qlL0K-ZMgW71jYFoGigcu0KVXhGnjltHMJSU,5325
|
|
8
|
+
DefStream/Broker/Implements/RabbitMQ/Serializer.py,sha256=ylwIBJ0blftq2W9usuYvjS3xfXovR8_9kP3O9DlXYxI,1575
|
|
9
|
+
DefStream/Broker/Implements/RabbitMQ/Stream.py,sha256=9E7R_bX_kDZ3VIH7hkMpP-Z7zGbJTonU8V6q_KT3YRI,2283
|
|
10
|
+
DefStream/Broker/Implements/RabbitMQ/Subscription.py,sha256=0ACs8skbn_s9UfricBDf59hT0F6G3ljO4aUrkpLuI9g,1284
|
|
11
|
+
DefStream/Broker/Implements/RabbitMQ/__init__.py,sha256=Ndt5yNXcgecN1mj0Qv7M32LUFK96WKmSi88wKMB1ZdU,673
|
|
12
|
+
defstream_rabbitmq-0.1.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
+
defstream_rabbitmq-0.1.1.dist-info/METADATA,sha256=aXWKLYLX7ygLs5KI_94b6fKBU5qqL3NC_0Y9ENc1pV0,5569
|
|
14
|
+
defstream_rabbitmq-0.1.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
15
|
+
defstream_rabbitmq-0.1.1.dist-info/top_level.txt,sha256=zsnisIu5wHL4b7VM8e9AggG9KTSdUU4morFqpb8YWFs,10
|
|
16
|
+
defstream_rabbitmq-0.1.1.dist-info/RECORD,,
|