h-message-bus 0.0.3__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.
- h_message_bus-0.0.3/PKG-INFO +277 -0
- h_message_bus-0.0.3/README.md +262 -0
- h_message_bus-0.0.3/pyproject.toml +28 -0
- h_message_bus-0.0.3/setup.cfg +4 -0
- h_message_bus-0.0.3/src/h_message_bus/__init__.py +11 -0
- h_message_bus-0.0.3/src/h_message_bus/adapters/__init__.py +0 -0
- h_message_bus-0.0.3/src/h_message_bus/adapters/nats_message_adapter.py +36 -0
- h_message_bus-0.0.3/src/h_message_bus/adapters/nats_publisher_adapter.py +46 -0
- h_message_bus-0.0.3/src/h_message_bus/adapters/nats_subscriber_adapter.py +53 -0
- h_message_bus-0.0.3/src/h_message_bus/application/__init__.py +0 -0
- h_message_bus-0.0.3/src/h_message_bus/application/message_processor.py +21 -0
- h_message_bus-0.0.3/src/h_message_bus/application/message_publisher.py +25 -0
- h_message_bus-0.0.3/src/h_message_bus/application/message_subcriber.py +24 -0
- h_message_bus-0.0.3/src/h_message_bus/domain/__init__.py +0 -0
- h_message_bus-0.0.3/src/h_message_bus/domain/hai_message.py +65 -0
- h_message_bus-0.0.3/src/h_message_bus/domain/topics.py +24 -0
- h_message_bus-0.0.3/src/h_message_bus/infrastructure/__init__.py +0 -0
- h_message_bus-0.0.3/src/h_message_bus/infrastructure/nats_client_repository.py +86 -0
- h_message_bus-0.0.3/src/h_message_bus/infrastructure/nats_config.py +31 -0
- h_message_bus-0.0.3/src/h_message_bus.egg-info/PKG-INFO +277 -0
- h_message_bus-0.0.3/src/h_message_bus.egg-info/SOURCES.txt +22 -0
- h_message_bus-0.0.3/src/h_message_bus.egg-info/dependency_links.txt +1 -0
- h_message_bus-0.0.3/src/h_message_bus.egg-info/requires.txt +5 -0
- h_message_bus-0.0.3/src/h_message_bus.egg-info/top_level.txt +1 -0
@@ -0,0 +1,277 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: h_message_bus
|
3
|
+
Version: 0.0.3
|
4
|
+
Summary: Message bus integration for HAI
|
5
|
+
Author-email: shoebill <shoebill.hai@gmail.com>
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
8
|
+
Classifier: Operating System :: OS Independent
|
9
|
+
Requires-Python: >=3.10
|
10
|
+
Description-Content-Type: text/markdown
|
11
|
+
Requires-Dist: python-dotenv~=1.1.0
|
12
|
+
Requires-Dist: nats-py~=2.10.0
|
13
|
+
Provides-Extra: dev
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
15
|
+
|
16
|
+
# H Message Bus
|
17
|
+
|
18
|
+
A message bus integration for HAI applications based on NATS.io
|
19
|
+
|
20
|
+
## Overview
|
21
|
+
|
22
|
+
H Message Bus provides a robust, asynchronous messaging infrastructure built on NATS.io for HAI applications. It enables seamless communication between components through a publish-subscribe pattern, supporting both fire-and-forget messaging and request-response patterns.
|
23
|
+
|
24
|
+
## Features
|
25
|
+
|
26
|
+
- **Asynchronous Communication**: Built for modern, non-blocking I/O operations
|
27
|
+
- **Flexible Message Routing**: Publish and subscribe to specific topics
|
28
|
+
- **High Reliability**: Automatic reconnection handling and configurable timeouts
|
29
|
+
- **Simple API**: Focus on core messaging functionality with minimal dependencies
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
```bash
|
34
|
+
pip install h_message_bus
|
35
|
+
```
|
36
|
+
|
37
|
+
## Requirements
|
38
|
+
|
39
|
+
- Python 3.10+
|
40
|
+
- NATS.io server (can be run via Docker)
|
41
|
+
|
42
|
+
## Topics
|
43
|
+
|
44
|
+
H Message Bus includes predefined topics following the convention: `hai.[source].[destination].[action]`
|
45
|
+
|
46
|
+
Available topics:
|
47
|
+
|
48
|
+
| Topic Constant | Topic String | Description |
|
49
|
+
|---------------------------------|------------------------|-------------------------------------|
|
50
|
+
| `Topic.AI_SEND_TG_CHAT_MESSAGE` | `hai.ai.tg.chat.send` | AI sending message to Telegram chat |
|
51
|
+
| `Topic.AI_VECTORS_SAVE` | `hai.ai.vectors.save` | AI saving data to vector database |
|
52
|
+
| `Topic.AI_VECTORS_QUERY` | `hai.ai.vectors.query` | AI querying vector database |
|
53
|
+
| `Topic.TG_SEND_AI_CHAT_MESSAGE` | `hai.tg.ai.chat.send` | Telegram sending message to AI |
|
54
|
+
|
55
|
+
You can use these predefined topics or create your own topic strings.
|
56
|
+
|
57
|
+
## Quick Start
|
58
|
+
|
59
|
+
### Start a NATS Server
|
60
|
+
|
61
|
+
The easiest way to get started is with Docker:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
docker-compose up -d
|
65
|
+
```
|
66
|
+
|
67
|
+
### Create a Publisher
|
68
|
+
|
69
|
+
```python
|
70
|
+
import asyncio
|
71
|
+
import uuid
|
72
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
73
|
+
|
74
|
+
async def main():
|
75
|
+
# Configure NATS connection
|
76
|
+
config = NatsConfig(server="nats://localhost:4222")
|
77
|
+
|
78
|
+
# Create publisher adapter
|
79
|
+
publisher = NatsPublisherAdapter(config)
|
80
|
+
|
81
|
+
# Connect to NATS
|
82
|
+
await publisher.connect()
|
83
|
+
|
84
|
+
# Create and publish a message using a predefined topic
|
85
|
+
message = HaiMessage(
|
86
|
+
message_id=str(uuid.uuid4()),
|
87
|
+
sender="service-a",
|
88
|
+
topic=Topic.TG_SEND_AI_CHAT_MESSAGE,
|
89
|
+
payload={"text": "Hello AI, this is a message from Telegram", "chat_id": 12345}
|
90
|
+
)
|
91
|
+
|
92
|
+
# Publish message
|
93
|
+
await publisher.publish(message)
|
94
|
+
|
95
|
+
# Clean up
|
96
|
+
await publisher.close()
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
asyncio.run(main())
|
100
|
+
```
|
101
|
+
|
102
|
+
### Create a Subscriber
|
103
|
+
|
104
|
+
```python
|
105
|
+
import asyncio
|
106
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, HaiMessage, Topic
|
107
|
+
|
108
|
+
async def message_handler(message: HaiMessage):
|
109
|
+
print(f"Received message: {message.message_id}")
|
110
|
+
print(f"From: {message.sender}")
|
111
|
+
print(f"Topic: {message.topic}")
|
112
|
+
print(f"Payload: {message.payload}")
|
113
|
+
|
114
|
+
async def main():
|
115
|
+
# Configure NATS connection
|
116
|
+
config = NatsConfig(server="nats://localhost:4222")
|
117
|
+
|
118
|
+
# Create subscriber
|
119
|
+
subscriber = NatsSubscriberAdapter(config)
|
120
|
+
|
121
|
+
# Connect to NATS
|
122
|
+
await subscriber.connect()
|
123
|
+
|
124
|
+
# Subscribe to a topic
|
125
|
+
await subscriber.subscribe(Topic.TG_SEND_AI_CHAT_MESSAGE, message_handler)
|
126
|
+
|
127
|
+
# Keep the application running
|
128
|
+
try:
|
129
|
+
print("Subscriber running. Press Ctrl+C to exit.")
|
130
|
+
while True:
|
131
|
+
await asyncio.sleep(1)
|
132
|
+
except KeyboardInterrupt:
|
133
|
+
# Clean up
|
134
|
+
await subscriber.close()
|
135
|
+
|
136
|
+
if __name__ == "__main__":
|
137
|
+
asyncio.run(main())
|
138
|
+
```
|
139
|
+
|
140
|
+
## Advanced Usage
|
141
|
+
|
142
|
+
### Request-Response Pattern
|
143
|
+
|
144
|
+
```python
|
145
|
+
import asyncio
|
146
|
+
import uuid
|
147
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
148
|
+
|
149
|
+
async def main():
|
150
|
+
config = NatsConfig(server="nats://localhost:4222")
|
151
|
+
publisher = NatsPublisherAdapter(config)
|
152
|
+
|
153
|
+
# Connect to NATS
|
154
|
+
await publisher.connect()
|
155
|
+
|
156
|
+
request_message = HaiMessage(
|
157
|
+
message_id=str(uuid.uuid4()),
|
158
|
+
sender="client-service",
|
159
|
+
topic=Topic.AI_VECTORS_QUERY,
|
160
|
+
payload={"query": "find similar documents", "limit": 10}
|
161
|
+
)
|
162
|
+
|
163
|
+
# Send request and wait for response (with timeout)
|
164
|
+
response = await publisher.request(request_message, timeout=5.0)
|
165
|
+
|
166
|
+
if response:
|
167
|
+
print(f"Received response: {response.payload}")
|
168
|
+
else:
|
169
|
+
print("Request timed out")
|
170
|
+
|
171
|
+
await publisher.close()
|
172
|
+
|
173
|
+
if __name__ == "__main__":
|
174
|
+
asyncio.run(main())
|
175
|
+
```
|
176
|
+
|
177
|
+
### Creating a Service with Request Handler
|
178
|
+
|
179
|
+
```python
|
180
|
+
import asyncio
|
181
|
+
import uuid
|
182
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, NatsPublisherAdapter, HaiMessage, Topic
|
183
|
+
|
184
|
+
async def request_handler(request: HaiMessage):
|
185
|
+
print(f"Received request: {request.message_id}")
|
186
|
+
print(f"Payload: {request.payload}")
|
187
|
+
|
188
|
+
# Process the request
|
189
|
+
result = {"status": "success", "data": {"result": 42}}
|
190
|
+
|
191
|
+
# Create a response message
|
192
|
+
return HaiMessage(
|
193
|
+
message_id=str(uuid.uuid4()),
|
194
|
+
sender="service-b",
|
195
|
+
topic=f"{request.topic}.response",
|
196
|
+
payload=result,
|
197
|
+
correlation_id=request.message_id
|
198
|
+
)
|
199
|
+
|
200
|
+
async def main():
|
201
|
+
# Configure NATS connection
|
202
|
+
config = NatsConfig(server="nats://localhost:4222")
|
203
|
+
|
204
|
+
# Create subscriber for handling requests
|
205
|
+
subscriber = NatsSubscriberAdapter(config)
|
206
|
+
publisher = NatsPublisherAdapter(config)
|
207
|
+
|
208
|
+
# Connect to NATS
|
209
|
+
await subscriber.connect()
|
210
|
+
await publisher.connect()
|
211
|
+
|
212
|
+
# Register request handler for vector database queries
|
213
|
+
await subscriber.subscribe_with_response(Topic.AI_VECTORS_QUERY, request_handler, publisher)
|
214
|
+
|
215
|
+
# Keep the application running
|
216
|
+
try:
|
217
|
+
print("Service running. Press Ctrl+C to exit.")
|
218
|
+
while True:
|
219
|
+
await asyncio.sleep(1)
|
220
|
+
except KeyboardInterrupt:
|
221
|
+
# Clean up
|
222
|
+
await subscriber.close()
|
223
|
+
await publisher.close()
|
224
|
+
|
225
|
+
if __name__ == "__main__":
|
226
|
+
asyncio.run(main())
|
227
|
+
```
|
228
|
+
|
229
|
+
## Configuration Options
|
230
|
+
|
231
|
+
The `NatsConfig` class allows you to customize your NATS connection:
|
232
|
+
|
233
|
+
| Parameter | Description | Default |
|
234
|
+
|--------------------------|----------------------------------------------|---------------|
|
235
|
+
| `server` | NATS server address | Required |
|
236
|
+
| `max_reconnect_attempts` | Maximum reconnection attempts | 10 |
|
237
|
+
| `reconnect_time_wait` | Time between reconnection attempts (seconds) | 2 |
|
238
|
+
| `connection_timeout` | Connection timeout (seconds) | 2 |
|
239
|
+
| `ping_interval` | Interval for ping frames (seconds) | 20 |
|
240
|
+
| `max_outstanding_pings` | Maximum unanswered pings before disconnect | 5 |
|
241
|
+
| `max_payload` | Maximum size of the payload in bytes | 1048576 (1MB) |
|
242
|
+
|
243
|
+
## API Reference
|
244
|
+
|
245
|
+
### Exported Classes
|
246
|
+
|
247
|
+
The following classes are exported directly from the package:
|
248
|
+
|
249
|
+
- `NatsConfig` - Configuration for the NATS connection
|
250
|
+
- `HaiMessage` - Message structure for HAI communication
|
251
|
+
- `NatsPublisherAdapter` - Adapter for publishing messages
|
252
|
+
- `NatsSubscriberAdapter` - Adapter for subscribing to messages
|
253
|
+
- `MessageProcessor` - Processing incoming messages
|
254
|
+
- `NatsClientRepository` - Low-level NATS client operations
|
255
|
+
- `Topic` - Enumeration of predefined topic strings
|
256
|
+
|
257
|
+
### HaiMessage Structure
|
258
|
+
|
259
|
+
The `HaiMessage` class is the core data structure used for all messaging:
|
260
|
+
|
261
|
+
```python
|
262
|
+
class HaiMessage:
|
263
|
+
message_id: str # Unique identifier for the message
|
264
|
+
sender: str # Identifier of the sender
|
265
|
+
topic: str # The topic or channel for the message
|
266
|
+
payload: dict # Actual message data
|
267
|
+
correlation_id: str = None # Optional reference to a related message
|
268
|
+
timestamp: float = None # Optional message creation timestamp
|
269
|
+
```
|
270
|
+
|
271
|
+
## Contributing
|
272
|
+
|
273
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
274
|
+
|
275
|
+
## License
|
276
|
+
|
277
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# H Message Bus
|
2
|
+
|
3
|
+
A message bus integration for HAI applications based on NATS.io
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
H Message Bus provides a robust, asynchronous messaging infrastructure built on NATS.io for HAI applications. It enables seamless communication between components through a publish-subscribe pattern, supporting both fire-and-forget messaging and request-response patterns.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Asynchronous Communication**: Built for modern, non-blocking I/O operations
|
12
|
+
- **Flexible Message Routing**: Publish and subscribe to specific topics
|
13
|
+
- **High Reliability**: Automatic reconnection handling and configurable timeouts
|
14
|
+
- **Simple API**: Focus on core messaging functionality with minimal dependencies
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
```bash
|
19
|
+
pip install h_message_bus
|
20
|
+
```
|
21
|
+
|
22
|
+
## Requirements
|
23
|
+
|
24
|
+
- Python 3.10+
|
25
|
+
- NATS.io server (can be run via Docker)
|
26
|
+
|
27
|
+
## Topics
|
28
|
+
|
29
|
+
H Message Bus includes predefined topics following the convention: `hai.[source].[destination].[action]`
|
30
|
+
|
31
|
+
Available topics:
|
32
|
+
|
33
|
+
| Topic Constant | Topic String | Description |
|
34
|
+
|---------------------------------|------------------------|-------------------------------------|
|
35
|
+
| `Topic.AI_SEND_TG_CHAT_MESSAGE` | `hai.ai.tg.chat.send` | AI sending message to Telegram chat |
|
36
|
+
| `Topic.AI_VECTORS_SAVE` | `hai.ai.vectors.save` | AI saving data to vector database |
|
37
|
+
| `Topic.AI_VECTORS_QUERY` | `hai.ai.vectors.query` | AI querying vector database |
|
38
|
+
| `Topic.TG_SEND_AI_CHAT_MESSAGE` | `hai.tg.ai.chat.send` | Telegram sending message to AI |
|
39
|
+
|
40
|
+
You can use these predefined topics or create your own topic strings.
|
41
|
+
|
42
|
+
## Quick Start
|
43
|
+
|
44
|
+
### Start a NATS Server
|
45
|
+
|
46
|
+
The easiest way to get started is with Docker:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
docker-compose up -d
|
50
|
+
```
|
51
|
+
|
52
|
+
### Create a Publisher
|
53
|
+
|
54
|
+
```python
|
55
|
+
import asyncio
|
56
|
+
import uuid
|
57
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
58
|
+
|
59
|
+
async def main():
|
60
|
+
# Configure NATS connection
|
61
|
+
config = NatsConfig(server="nats://localhost:4222")
|
62
|
+
|
63
|
+
# Create publisher adapter
|
64
|
+
publisher = NatsPublisherAdapter(config)
|
65
|
+
|
66
|
+
# Connect to NATS
|
67
|
+
await publisher.connect()
|
68
|
+
|
69
|
+
# Create and publish a message using a predefined topic
|
70
|
+
message = HaiMessage(
|
71
|
+
message_id=str(uuid.uuid4()),
|
72
|
+
sender="service-a",
|
73
|
+
topic=Topic.TG_SEND_AI_CHAT_MESSAGE,
|
74
|
+
payload={"text": "Hello AI, this is a message from Telegram", "chat_id": 12345}
|
75
|
+
)
|
76
|
+
|
77
|
+
# Publish message
|
78
|
+
await publisher.publish(message)
|
79
|
+
|
80
|
+
# Clean up
|
81
|
+
await publisher.close()
|
82
|
+
|
83
|
+
if __name__ == "__main__":
|
84
|
+
asyncio.run(main())
|
85
|
+
```
|
86
|
+
|
87
|
+
### Create a Subscriber
|
88
|
+
|
89
|
+
```python
|
90
|
+
import asyncio
|
91
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, HaiMessage, Topic
|
92
|
+
|
93
|
+
async def message_handler(message: HaiMessage):
|
94
|
+
print(f"Received message: {message.message_id}")
|
95
|
+
print(f"From: {message.sender}")
|
96
|
+
print(f"Topic: {message.topic}")
|
97
|
+
print(f"Payload: {message.payload}")
|
98
|
+
|
99
|
+
async def main():
|
100
|
+
# Configure NATS connection
|
101
|
+
config = NatsConfig(server="nats://localhost:4222")
|
102
|
+
|
103
|
+
# Create subscriber
|
104
|
+
subscriber = NatsSubscriberAdapter(config)
|
105
|
+
|
106
|
+
# Connect to NATS
|
107
|
+
await subscriber.connect()
|
108
|
+
|
109
|
+
# Subscribe to a topic
|
110
|
+
await subscriber.subscribe(Topic.TG_SEND_AI_CHAT_MESSAGE, message_handler)
|
111
|
+
|
112
|
+
# Keep the application running
|
113
|
+
try:
|
114
|
+
print("Subscriber running. Press Ctrl+C to exit.")
|
115
|
+
while True:
|
116
|
+
await asyncio.sleep(1)
|
117
|
+
except KeyboardInterrupt:
|
118
|
+
# Clean up
|
119
|
+
await subscriber.close()
|
120
|
+
|
121
|
+
if __name__ == "__main__":
|
122
|
+
asyncio.run(main())
|
123
|
+
```
|
124
|
+
|
125
|
+
## Advanced Usage
|
126
|
+
|
127
|
+
### Request-Response Pattern
|
128
|
+
|
129
|
+
```python
|
130
|
+
import asyncio
|
131
|
+
import uuid
|
132
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
133
|
+
|
134
|
+
async def main():
|
135
|
+
config = NatsConfig(server="nats://localhost:4222")
|
136
|
+
publisher = NatsPublisherAdapter(config)
|
137
|
+
|
138
|
+
# Connect to NATS
|
139
|
+
await publisher.connect()
|
140
|
+
|
141
|
+
request_message = HaiMessage(
|
142
|
+
message_id=str(uuid.uuid4()),
|
143
|
+
sender="client-service",
|
144
|
+
topic=Topic.AI_VECTORS_QUERY,
|
145
|
+
payload={"query": "find similar documents", "limit": 10}
|
146
|
+
)
|
147
|
+
|
148
|
+
# Send request and wait for response (with timeout)
|
149
|
+
response = await publisher.request(request_message, timeout=5.0)
|
150
|
+
|
151
|
+
if response:
|
152
|
+
print(f"Received response: {response.payload}")
|
153
|
+
else:
|
154
|
+
print("Request timed out")
|
155
|
+
|
156
|
+
await publisher.close()
|
157
|
+
|
158
|
+
if __name__ == "__main__":
|
159
|
+
asyncio.run(main())
|
160
|
+
```
|
161
|
+
|
162
|
+
### Creating a Service with Request Handler
|
163
|
+
|
164
|
+
```python
|
165
|
+
import asyncio
|
166
|
+
import uuid
|
167
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, NatsPublisherAdapter, HaiMessage, Topic
|
168
|
+
|
169
|
+
async def request_handler(request: HaiMessage):
|
170
|
+
print(f"Received request: {request.message_id}")
|
171
|
+
print(f"Payload: {request.payload}")
|
172
|
+
|
173
|
+
# Process the request
|
174
|
+
result = {"status": "success", "data": {"result": 42}}
|
175
|
+
|
176
|
+
# Create a response message
|
177
|
+
return HaiMessage(
|
178
|
+
message_id=str(uuid.uuid4()),
|
179
|
+
sender="service-b",
|
180
|
+
topic=f"{request.topic}.response",
|
181
|
+
payload=result,
|
182
|
+
correlation_id=request.message_id
|
183
|
+
)
|
184
|
+
|
185
|
+
async def main():
|
186
|
+
# Configure NATS connection
|
187
|
+
config = NatsConfig(server="nats://localhost:4222")
|
188
|
+
|
189
|
+
# Create subscriber for handling requests
|
190
|
+
subscriber = NatsSubscriberAdapter(config)
|
191
|
+
publisher = NatsPublisherAdapter(config)
|
192
|
+
|
193
|
+
# Connect to NATS
|
194
|
+
await subscriber.connect()
|
195
|
+
await publisher.connect()
|
196
|
+
|
197
|
+
# Register request handler for vector database queries
|
198
|
+
await subscriber.subscribe_with_response(Topic.AI_VECTORS_QUERY, request_handler, publisher)
|
199
|
+
|
200
|
+
# Keep the application running
|
201
|
+
try:
|
202
|
+
print("Service running. Press Ctrl+C to exit.")
|
203
|
+
while True:
|
204
|
+
await asyncio.sleep(1)
|
205
|
+
except KeyboardInterrupt:
|
206
|
+
# Clean up
|
207
|
+
await subscriber.close()
|
208
|
+
await publisher.close()
|
209
|
+
|
210
|
+
if __name__ == "__main__":
|
211
|
+
asyncio.run(main())
|
212
|
+
```
|
213
|
+
|
214
|
+
## Configuration Options
|
215
|
+
|
216
|
+
The `NatsConfig` class allows you to customize your NATS connection:
|
217
|
+
|
218
|
+
| Parameter | Description | Default |
|
219
|
+
|--------------------------|----------------------------------------------|---------------|
|
220
|
+
| `server` | NATS server address | Required |
|
221
|
+
| `max_reconnect_attempts` | Maximum reconnection attempts | 10 |
|
222
|
+
| `reconnect_time_wait` | Time between reconnection attempts (seconds) | 2 |
|
223
|
+
| `connection_timeout` | Connection timeout (seconds) | 2 |
|
224
|
+
| `ping_interval` | Interval for ping frames (seconds) | 20 |
|
225
|
+
| `max_outstanding_pings` | Maximum unanswered pings before disconnect | 5 |
|
226
|
+
| `max_payload` | Maximum size of the payload in bytes | 1048576 (1MB) |
|
227
|
+
|
228
|
+
## API Reference
|
229
|
+
|
230
|
+
### Exported Classes
|
231
|
+
|
232
|
+
The following classes are exported directly from the package:
|
233
|
+
|
234
|
+
- `NatsConfig` - Configuration for the NATS connection
|
235
|
+
- `HaiMessage` - Message structure for HAI communication
|
236
|
+
- `NatsPublisherAdapter` - Adapter for publishing messages
|
237
|
+
- `NatsSubscriberAdapter` - Adapter for subscribing to messages
|
238
|
+
- `MessageProcessor` - Processing incoming messages
|
239
|
+
- `NatsClientRepository` - Low-level NATS client operations
|
240
|
+
- `Topic` - Enumeration of predefined topic strings
|
241
|
+
|
242
|
+
### HaiMessage Structure
|
243
|
+
|
244
|
+
The `HaiMessage` class is the core data structure used for all messaging:
|
245
|
+
|
246
|
+
```python
|
247
|
+
class HaiMessage:
|
248
|
+
message_id: str # Unique identifier for the message
|
249
|
+
sender: str # Identifier of the sender
|
250
|
+
topic: str # The topic or channel for the message
|
251
|
+
payload: dict # Actual message data
|
252
|
+
correlation_id: str = None # Optional reference to a related message
|
253
|
+
timestamp: float = None # Optional message creation timestamp
|
254
|
+
```
|
255
|
+
|
256
|
+
## Contributing
|
257
|
+
|
258
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
259
|
+
|
260
|
+
## License
|
261
|
+
|
262
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=61.0"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "h_message_bus"
|
7
|
+
version = "0.0.3"
|
8
|
+
authors = [
|
9
|
+
{name = "shoebill", email = "shoebill.hai@gmail.com"},
|
10
|
+
]
|
11
|
+
description = "Message bus integration for HAI"
|
12
|
+
readme = "README.md"
|
13
|
+
requires-python = ">=3.10"
|
14
|
+
classifiers = [
|
15
|
+
"Programming Language :: Python :: 3",
|
16
|
+
"License :: OSI Approved :: MIT License", # Choose appropriate license
|
17
|
+
"Operating System :: OS Independent",
|
18
|
+
]
|
19
|
+
dependencies = [
|
20
|
+
"python-dotenv~=1.1.0",
|
21
|
+
"nats-py~=2.10.0",
|
22
|
+
]
|
23
|
+
|
24
|
+
[project.optional-dependencies]
|
25
|
+
dev = [
|
26
|
+
"pytest",
|
27
|
+
# Other development dependencies
|
28
|
+
]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from .adapters.nats_publisher_adapter import NatsPublisherAdapter
|
2
|
+
from .adapters.nats_subscriber_adapter import NatsSubscriberAdapter
|
3
|
+
from .application.message_processor import MessageProcessor
|
4
|
+
from .infrastructure.nats_client_repository import NatsClientRepository
|
5
|
+
from .infrastructure.nats_config import NatsConfig
|
6
|
+
from .domain.hai_message import HaiMessage
|
7
|
+
|
8
|
+
|
9
|
+
__all__ = ['NatsConfig', 'HaiMessage', 'MessageProcessor', 'NatsClientRepository', 'NatsSubscriberAdapter', 'NatsPublisherAdapter']
|
10
|
+
|
11
|
+
|
File without changes
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from nats.aio.msg import Msg as NatsMsg
|
2
|
+
|
3
|
+
from src.h_message_bus.application.message_processor import MessageProcessor
|
4
|
+
from src.h_message_bus.domain.hai_message import HaiMessage
|
5
|
+
|
6
|
+
|
7
|
+
class NatsMessageAdapter:
|
8
|
+
"""
|
9
|
+
Adapter class to handle incoming NATS messages and process them using
|
10
|
+
a message processor.
|
11
|
+
|
12
|
+
The class serves as a bridge between NATS messaging and a domain-specific
|
13
|
+
message processor. It listens for incoming messages, converts them into
|
14
|
+
the appropriate domain object, processes them with the provided message
|
15
|
+
processor, and optionally sends a response if required.
|
16
|
+
|
17
|
+
:ivar processor: Instance of the message processor used to process domain
|
18
|
+
messages.
|
19
|
+
:type processor: MessageProcessor
|
20
|
+
"""
|
21
|
+
def __init__(self, message_processor: MessageProcessor):
|
22
|
+
self.processor = message_processor
|
23
|
+
|
24
|
+
async def handle_nats_message(self, msg: NatsMsg) -> None:
|
25
|
+
"""Handle incoming NATS message."""
|
26
|
+
try:
|
27
|
+
domain_message = HaiMessage.from_json(msg.data.decode())
|
28
|
+
|
29
|
+
response = await self.processor.process(domain_message)
|
30
|
+
|
31
|
+
# If there's a reply subject and a response, send it back
|
32
|
+
if msg.reply and response:
|
33
|
+
await msg.respond(response.to_json().encode())
|
34
|
+
|
35
|
+
except Exception as e:
|
36
|
+
print(f"Error processing message: {e}")
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from src.h_message_bus.application.message_publisher import MessagePublisher
|
4
|
+
from src.h_message_bus.domain.hai_message import HaiMessage
|
5
|
+
from src.h_message_bus.infrastructure.nats_client_repository import NatsClientRepository
|
6
|
+
|
7
|
+
|
8
|
+
class NatsPublisherAdapter(MessagePublisher):
|
9
|
+
"""
|
10
|
+
Adapter for publishing and requesting messages using a NATS client.
|
11
|
+
|
12
|
+
This class interacts with a NATS client to publish messages to specified
|
13
|
+
topics or send requests and wait for responses. It is designed to work
|
14
|
+
with the `HaiMessage` format for message objects. The adapter encapsulates
|
15
|
+
underlying communication details and provides an easy-to-use interface
|
16
|
+
for message-based interactions.
|
17
|
+
|
18
|
+
:ivar nats_client: Instance of NatsClientRepository used for interacting
|
19
|
+
with the NATS system.
|
20
|
+
:type nats_client: NatsClientRepository
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, nats_client: NatsClientRepository):
|
24
|
+
self.nats_client = nats_client
|
25
|
+
|
26
|
+
async def publish(self, message: HaiMessage) -> None:
|
27
|
+
"""Publish a message to NATS."""
|
28
|
+
await self.nats_client.publish(
|
29
|
+
message.topic,
|
30
|
+
message.to_json().encode()
|
31
|
+
)
|
32
|
+
|
33
|
+
async def request(self, message: HaiMessage, timeout: float = 2.0) -> Optional[HaiMessage]:
|
34
|
+
"""Send a request and wait for a response."""
|
35
|
+
try:
|
36
|
+
response = await self.nats_client.request(
|
37
|
+
message.topic,
|
38
|
+
message.to_json().encode(),
|
39
|
+
timeout=timeout
|
40
|
+
)
|
41
|
+
if response:
|
42
|
+
return HaiMessage.from_json(response.decode())
|
43
|
+
return None
|
44
|
+
except Exception as e:
|
45
|
+
print(f"Request failed: {e}")
|
46
|
+
return None
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
from src.h_message_bus.adapters.nats_message_adapter import NatsMessageAdapter
|
4
|
+
from src.h_message_bus.application.message_processor import MessageProcessor
|
5
|
+
from src.h_message_bus.application.message_subcriber import MessageSubscriber
|
6
|
+
from src.h_message_bus.infrastructure.nats_client_repository import NatsClientRepository
|
7
|
+
|
8
|
+
|
9
|
+
class NatsSubscriberAdapter(MessageSubscriber):
|
10
|
+
"""
|
11
|
+
Handles subscription to message topics using a NATS client.
|
12
|
+
|
13
|
+
The class provides methods to subscribe to specific topics with a
|
14
|
+
message processor and to manage subscriptions by unsubscribing from
|
15
|
+
all topics. It works with a NATS client repository and acts as a
|
16
|
+
bridge between the messaging system and the application.
|
17
|
+
|
18
|
+
:ivar nats_client: The NATS client repository used for managing
|
19
|
+
subscriptions.
|
20
|
+
:type nats_client: NatsClientRepository
|
21
|
+
:ivar subscriptions: A list of active subscription objects.
|
22
|
+
:type subscriptions: list
|
23
|
+
:ivar adapters: A dictionary mapping topic names to their respective
|
24
|
+
message adapters.
|
25
|
+
:type adapters: Dict[str, NatsMessageAdapter]
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, nats_client: NatsClientRepository):
|
29
|
+
self.nats_client = nats_client
|
30
|
+
self.subscriptions = []
|
31
|
+
self.adapters: Dict[str, NatsMessageAdapter] = {}
|
32
|
+
|
33
|
+
async def subscribe(self, topic: str, processor: MessageProcessor) -> None:
|
34
|
+
"""Subscribe to a topic with a message handler."""
|
35
|
+
# Create an adapter for this use case
|
36
|
+
message_adapter = NatsMessageAdapter(processor)
|
37
|
+
self.adapters[topic] = message_adapter
|
38
|
+
|
39
|
+
# Subscribe to the topic
|
40
|
+
subscription = await self.nats_client.subscribe(
|
41
|
+
topic,
|
42
|
+
callback=message_adapter.handle_nats_message
|
43
|
+
)
|
44
|
+
|
45
|
+
self.subscriptions.append(subscription)
|
46
|
+
|
47
|
+
async def unsubscribe_all(self) -> None:
|
48
|
+
"""Unsubscribe from all subscriptions."""
|
49
|
+
for subscription in self.subscriptions:
|
50
|
+
await subscription.unsubscribe()
|
51
|
+
|
52
|
+
self.subscriptions = []
|
53
|
+
self.adapters = {}
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from abc import abstractmethod, ABC
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from src.h_message_bus.domain.hai_message import HaiMessage
|
5
|
+
|
6
|
+
|
7
|
+
class MessageProcessor(ABC):
|
8
|
+
"""
|
9
|
+
Defines an abstract base class for processing messages.
|
10
|
+
|
11
|
+
This class serves as a blueprint for creating message processing
|
12
|
+
implementations. It enforces a concrete implementation of the
|
13
|
+
'process' method, which is expected to handle a given message
|
14
|
+
and optionally return a response. The specific logic of
|
15
|
+
processing must be defined in derived subclasses.
|
16
|
+
"""
|
17
|
+
|
18
|
+
@abstractmethod
|
19
|
+
async def process(self, message: HaiMessage) -> Optional[HaiMessage]:
|
20
|
+
"""Process a message and optionally return a response."""
|
21
|
+
pass
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from abc import abstractmethod, ABC
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from src.h_message_bus.domain.hai_message import HaiMessage
|
5
|
+
|
6
|
+
|
7
|
+
class MessagePublisher(ABC):
|
8
|
+
"""
|
9
|
+
Represents an abstract base class for a message publishing mechanism.
|
10
|
+
|
11
|
+
This class serves as a template for creating message publishing systems
|
12
|
+
that can either publish messages asynchronously or handle request-response
|
13
|
+
patterns with optional timeouts. Subclasses must implement the abstract
|
14
|
+
methods to provide specific functionality.
|
15
|
+
"""
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
async def publish(self, message: HaiMessage) -> None:
|
19
|
+
"""Publish a message."""
|
20
|
+
pass
|
21
|
+
|
22
|
+
@abstractmethod
|
23
|
+
async def request(self, message: HaiMessage, timeout: float) -> Optional[HaiMessage]:
|
24
|
+
"""Send a request and wait for response."""
|
25
|
+
pass
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Callable
|
3
|
+
|
4
|
+
|
5
|
+
class MessageSubscriber(ABC):
|
6
|
+
"""
|
7
|
+
Abstract base class representing a message subscriber.
|
8
|
+
|
9
|
+
This class defines the contract for subscribing to and unsubscribing from
|
10
|
+
message topics. Subclasses are required to implement the methods for
|
11
|
+
subscribing to topics with message handlers and unsubscribing from all
|
12
|
+
active subscriptions.
|
13
|
+
|
14
|
+
"""
|
15
|
+
|
16
|
+
@abstractmethod
|
17
|
+
async def subscribe(self, topic: str, handler: Callable) -> None:
|
18
|
+
"""Subscribe to a topic with a message handler."""
|
19
|
+
pass
|
20
|
+
|
21
|
+
@abstractmethod
|
22
|
+
async def unsubscribe_all(self) -> None:
|
23
|
+
"""Unsubscribe from all subscriptions."""
|
24
|
+
pass
|
File without changes
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import json
|
2
|
+
import uuid
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Dict, Any
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class HaiMessage:
|
10
|
+
"""
|
11
|
+
Represents a domain message with metadata and payload.
|
12
|
+
|
13
|
+
This class is used for handling messages with a specific topic and payload,
|
14
|
+
along with metadata such as an identifier and a timestamp. It provides
|
15
|
+
functionality for creation, serialization to JSON, and deserialization from JSON.
|
16
|
+
|
17
|
+
:ivar id: Unique identifier for the message.
|
18
|
+
:type id: str
|
19
|
+
:ivar topic: The topic associated with the message.
|
20
|
+
:type topic: str
|
21
|
+
:ivar payload: The payload of the message, represented as a dictionary.
|
22
|
+
:type payload: Dict[Any, Any]
|
23
|
+
:ivar timestamp: The timestamp indicating when the message was created.
|
24
|
+
:type timestamp: str
|
25
|
+
"""
|
26
|
+
id: str
|
27
|
+
topic: str
|
28
|
+
payload: Dict[Any, Any]
|
29
|
+
timestamp: str = None
|
30
|
+
|
31
|
+
def __post_init__(self):
|
32
|
+
if not self.id:
|
33
|
+
self.id = str(uuid.uuid4())
|
34
|
+
if not self.timestamp:
|
35
|
+
self.timestamp = datetime.utcnow().isoformat()
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def create(cls, topic: str, payload: Dict[Any, Any]) -> 'HaiMessage':
|
39
|
+
"""Factory method to create a new domain message."""
|
40
|
+
return cls(
|
41
|
+
id=str(uuid.uuid4()),
|
42
|
+
topic=topic,
|
43
|
+
payload=payload,
|
44
|
+
timestamp=datetime.utcnow().isoformat()
|
45
|
+
)
|
46
|
+
|
47
|
+
def to_json(self) -> str:
|
48
|
+
"""Convert message to JSON string."""
|
49
|
+
return json.dumps({
|
50
|
+
"id": self.id,
|
51
|
+
"topic": self.topic,
|
52
|
+
"payload": self.payload,
|
53
|
+
"timestamp": self.timestamp
|
54
|
+
})
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def from_json(cls, json_str: str) -> 'HaiMessage':
|
58
|
+
"""Create message from JSON string."""
|
59
|
+
data = json.loads(json_str)
|
60
|
+
return cls(
|
61
|
+
id=data.get("id", str(uuid.uuid4())),
|
62
|
+
topic=data["topic"],
|
63
|
+
payload=data["payload"],
|
64
|
+
timestamp=data.get("timestamp", datetime.utcnow().isoformat())
|
65
|
+
)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
|
4
|
+
class Topic(str, Enum):
|
5
|
+
"""
|
6
|
+
Represents a collection of predefined topics as an enumeration.
|
7
|
+
|
8
|
+
This class is an enumeration that defines constant string values for use
|
9
|
+
as topic identifiers. These topics represent specific actions or messages
|
10
|
+
within a messaging or vector database management context. It ensures
|
11
|
+
consistent usage of these predefined topics across the application.
|
12
|
+
|
13
|
+
syntax: [hai].[source].[destination].[action]
|
14
|
+
|
15
|
+
"""
|
16
|
+
# AI to Telegram
|
17
|
+
AI_SEND_TG_CHAT_MESSAGE = "hai.ai.tg.chat.send"
|
18
|
+
|
19
|
+
# AI to vector database
|
20
|
+
AI_VECTORS_SAVE = "hai.ai.vectors.save"
|
21
|
+
AI_VECTORS_QUERY = "hai.ai.vectors.query"
|
22
|
+
|
23
|
+
# TG to AI
|
24
|
+
TG_SEND_AI_CHAT_MESSAGE = "hai.tg.ai.chat.send"
|
File without changes
|
@@ -0,0 +1,86 @@
|
|
1
|
+
from typing import Optional, Callable, Any
|
2
|
+
|
3
|
+
import nats
|
4
|
+
from nats.aio.client import Client as NatsClient
|
5
|
+
|
6
|
+
from src.h_message_bus.infrastructure.nats_config import NatsConfig
|
7
|
+
|
8
|
+
|
9
|
+
class NatsClientRepository:
|
10
|
+
"""
|
11
|
+
Repository for managing connection and interaction with a NATS server.
|
12
|
+
|
13
|
+
This class provides methods to establish a connection with a NATS server,
|
14
|
+
publish and subscribe to subjects, send requests and handle responses, and
|
15
|
+
cleanly disconnect from the server. It abstracts the connection and ensures
|
16
|
+
seamless communication with the NATS server.
|
17
|
+
|
18
|
+
:ivar config: Configuration details for the NATS client, including server
|
19
|
+
connection parameters, timeouts, and limits.
|
20
|
+
:type config: NatsConfig
|
21
|
+
:ivar client: Instance of the NATS client used for communication. Initialized
|
22
|
+
as None and assigned upon connecting to a NATS server.
|
23
|
+
:type client: Optional[NatsClient]
|
24
|
+
:ivar subscriptions: List of active subscriptions for NATS subjects.
|
25
|
+
:type subscriptions: list
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, config: NatsConfig):
|
29
|
+
self.config = config
|
30
|
+
self.client: NatsClient | None = None
|
31
|
+
|
32
|
+
self.subscriptions = []
|
33
|
+
|
34
|
+
async def connect(self) -> None:
|
35
|
+
"""Connect to NATS server."""
|
36
|
+
if self.client and self.client.is_connected:
|
37
|
+
return
|
38
|
+
|
39
|
+
self.client = await nats.connect(
|
40
|
+
servers=self.config.server,
|
41
|
+
max_reconnect_attempts=self.config.max_reconnect_attempts,
|
42
|
+
reconnect_time_wait=self.config.reconnect_time_wait,
|
43
|
+
connect_timeout=self.config.connection_timeout,
|
44
|
+
ping_interval=self.config.ping_interval,
|
45
|
+
max_outstanding_pings=self.config.max_outstanding_pings
|
46
|
+
)
|
47
|
+
|
48
|
+
async def publish(self, subject: str, payload: bytes) -> None:
|
49
|
+
"""Publish raw message to NATS."""
|
50
|
+
if not self.client or not self.client.is_connected:
|
51
|
+
await self.connect()
|
52
|
+
|
53
|
+
await self.client.publish(subject, payload)
|
54
|
+
|
55
|
+
async def subscribe(self, subject: str, callback: Callable) -> Any:
|
56
|
+
"""Subscribe to a subject with a callback."""
|
57
|
+
if not self.client or not self.client.is_connected:
|
58
|
+
await self.connect()
|
59
|
+
|
60
|
+
subscription = await self.client.subscribe(subject, cb=callback)
|
61
|
+
self.subscriptions.append(subscription)
|
62
|
+
return subscription
|
63
|
+
|
64
|
+
async def request(self, subject: str, payload: bytes, timeout: float = 2.0) -> Optional[bytes]:
|
65
|
+
"""Send a request and get raw response."""
|
66
|
+
if not self.client or not self.client.is_connected:
|
67
|
+
await self.connect()
|
68
|
+
|
69
|
+
try:
|
70
|
+
response = await self.client.request(subject, payload, timeout=timeout)
|
71
|
+
return response.data
|
72
|
+
except Exception as e:
|
73
|
+
print(f"NATS request failed: {e}")
|
74
|
+
return None
|
75
|
+
|
76
|
+
async def close(self) -> None:
|
77
|
+
"""Close all subscriptions and NATS connection."""
|
78
|
+
if self.client and self.client.is_connected:
|
79
|
+
# Unsubscribe from all subscriptions
|
80
|
+
for sub in self.subscriptions:
|
81
|
+
await sub.unsubscribe()
|
82
|
+
|
83
|
+
# Drain and close connection
|
84
|
+
await self.client.drain()
|
85
|
+
self.client = None
|
86
|
+
self.subscriptions = []
|
@@ -0,0 +1,31 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
|
3
|
+
|
4
|
+
@dataclass
|
5
|
+
class NatsConfig:
|
6
|
+
"""
|
7
|
+
Configuration class for NATS client.
|
8
|
+
|
9
|
+
This class encapsulates the configuration options required for connecting and
|
10
|
+
interacting with a NATS (NATS.io) server. It includes options for setting
|
11
|
+
server details, reconnection behavior, timeouts, and ping settings.
|
12
|
+
|
13
|
+
:ivar server: The address of the NATS server to connect to.
|
14
|
+
:type server: str
|
15
|
+
:ivar max_reconnect_attempts: The maximum number of reconnection attempts allowed.
|
16
|
+
:type max_reconnect_attempts: int
|
17
|
+
:ivar reconnect_time_wait: The time duration to wait between reconnection attempts, in seconds.
|
18
|
+
:type reconnect_time_wait: int
|
19
|
+
:ivar connection_timeout: The timeout duration for establishing a connection, in seconds.
|
20
|
+
:type connection_timeout: int
|
21
|
+
:ivar ping_interval: The interval for sending ping frames to the server, in seconds.
|
22
|
+
:type ping_interval: int
|
23
|
+
:ivar max_outstanding_pings: The maximum number of ping requests allowed without receiving a response.
|
24
|
+
:type max_outstanding_pings: int
|
25
|
+
"""
|
26
|
+
server: str
|
27
|
+
max_reconnect_attempts: int = 10
|
28
|
+
reconnect_time_wait: int = 2
|
29
|
+
connection_timeout: int = 2
|
30
|
+
ping_interval: int = 20
|
31
|
+
max_outstanding_pings: int = 5
|
@@ -0,0 +1,277 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: h_message_bus
|
3
|
+
Version: 0.0.3
|
4
|
+
Summary: Message bus integration for HAI
|
5
|
+
Author-email: shoebill <shoebill.hai@gmail.com>
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
8
|
+
Classifier: Operating System :: OS Independent
|
9
|
+
Requires-Python: >=3.10
|
10
|
+
Description-Content-Type: text/markdown
|
11
|
+
Requires-Dist: python-dotenv~=1.1.0
|
12
|
+
Requires-Dist: nats-py~=2.10.0
|
13
|
+
Provides-Extra: dev
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
15
|
+
|
16
|
+
# H Message Bus
|
17
|
+
|
18
|
+
A message bus integration for HAI applications based on NATS.io
|
19
|
+
|
20
|
+
## Overview
|
21
|
+
|
22
|
+
H Message Bus provides a robust, asynchronous messaging infrastructure built on NATS.io for HAI applications. It enables seamless communication between components through a publish-subscribe pattern, supporting both fire-and-forget messaging and request-response patterns.
|
23
|
+
|
24
|
+
## Features
|
25
|
+
|
26
|
+
- **Asynchronous Communication**: Built for modern, non-blocking I/O operations
|
27
|
+
- **Flexible Message Routing**: Publish and subscribe to specific topics
|
28
|
+
- **High Reliability**: Automatic reconnection handling and configurable timeouts
|
29
|
+
- **Simple API**: Focus on core messaging functionality with minimal dependencies
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
```bash
|
34
|
+
pip install h_message_bus
|
35
|
+
```
|
36
|
+
|
37
|
+
## Requirements
|
38
|
+
|
39
|
+
- Python 3.10+
|
40
|
+
- NATS.io server (can be run via Docker)
|
41
|
+
|
42
|
+
## Topics
|
43
|
+
|
44
|
+
H Message Bus includes predefined topics following the convention: `hai.[source].[destination].[action]`
|
45
|
+
|
46
|
+
Available topics:
|
47
|
+
|
48
|
+
| Topic Constant | Topic String | Description |
|
49
|
+
|---------------------------------|------------------------|-------------------------------------|
|
50
|
+
| `Topic.AI_SEND_TG_CHAT_MESSAGE` | `hai.ai.tg.chat.send` | AI sending message to Telegram chat |
|
51
|
+
| `Topic.AI_VECTORS_SAVE` | `hai.ai.vectors.save` | AI saving data to vector database |
|
52
|
+
| `Topic.AI_VECTORS_QUERY` | `hai.ai.vectors.query` | AI querying vector database |
|
53
|
+
| `Topic.TG_SEND_AI_CHAT_MESSAGE` | `hai.tg.ai.chat.send` | Telegram sending message to AI |
|
54
|
+
|
55
|
+
You can use these predefined topics or create your own topic strings.
|
56
|
+
|
57
|
+
## Quick Start
|
58
|
+
|
59
|
+
### Start a NATS Server
|
60
|
+
|
61
|
+
The easiest way to get started is with Docker:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
docker-compose up -d
|
65
|
+
```
|
66
|
+
|
67
|
+
### Create a Publisher
|
68
|
+
|
69
|
+
```python
|
70
|
+
import asyncio
|
71
|
+
import uuid
|
72
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
73
|
+
|
74
|
+
async def main():
|
75
|
+
# Configure NATS connection
|
76
|
+
config = NatsConfig(server="nats://localhost:4222")
|
77
|
+
|
78
|
+
# Create publisher adapter
|
79
|
+
publisher = NatsPublisherAdapter(config)
|
80
|
+
|
81
|
+
# Connect to NATS
|
82
|
+
await publisher.connect()
|
83
|
+
|
84
|
+
# Create and publish a message using a predefined topic
|
85
|
+
message = HaiMessage(
|
86
|
+
message_id=str(uuid.uuid4()),
|
87
|
+
sender="service-a",
|
88
|
+
topic=Topic.TG_SEND_AI_CHAT_MESSAGE,
|
89
|
+
payload={"text": "Hello AI, this is a message from Telegram", "chat_id": 12345}
|
90
|
+
)
|
91
|
+
|
92
|
+
# Publish message
|
93
|
+
await publisher.publish(message)
|
94
|
+
|
95
|
+
# Clean up
|
96
|
+
await publisher.close()
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
asyncio.run(main())
|
100
|
+
```
|
101
|
+
|
102
|
+
### Create a Subscriber
|
103
|
+
|
104
|
+
```python
|
105
|
+
import asyncio
|
106
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, HaiMessage, Topic
|
107
|
+
|
108
|
+
async def message_handler(message: HaiMessage):
|
109
|
+
print(f"Received message: {message.message_id}")
|
110
|
+
print(f"From: {message.sender}")
|
111
|
+
print(f"Topic: {message.topic}")
|
112
|
+
print(f"Payload: {message.payload}")
|
113
|
+
|
114
|
+
async def main():
|
115
|
+
# Configure NATS connection
|
116
|
+
config = NatsConfig(server="nats://localhost:4222")
|
117
|
+
|
118
|
+
# Create subscriber
|
119
|
+
subscriber = NatsSubscriberAdapter(config)
|
120
|
+
|
121
|
+
# Connect to NATS
|
122
|
+
await subscriber.connect()
|
123
|
+
|
124
|
+
# Subscribe to a topic
|
125
|
+
await subscriber.subscribe(Topic.TG_SEND_AI_CHAT_MESSAGE, message_handler)
|
126
|
+
|
127
|
+
# Keep the application running
|
128
|
+
try:
|
129
|
+
print("Subscriber running. Press Ctrl+C to exit.")
|
130
|
+
while True:
|
131
|
+
await asyncio.sleep(1)
|
132
|
+
except KeyboardInterrupt:
|
133
|
+
# Clean up
|
134
|
+
await subscriber.close()
|
135
|
+
|
136
|
+
if __name__ == "__main__":
|
137
|
+
asyncio.run(main())
|
138
|
+
```
|
139
|
+
|
140
|
+
## Advanced Usage
|
141
|
+
|
142
|
+
### Request-Response Pattern
|
143
|
+
|
144
|
+
```python
|
145
|
+
import asyncio
|
146
|
+
import uuid
|
147
|
+
from h_message_bus import NatsConfig, NatsPublisherAdapter, HaiMessage, Topic
|
148
|
+
|
149
|
+
async def main():
|
150
|
+
config = NatsConfig(server="nats://localhost:4222")
|
151
|
+
publisher = NatsPublisherAdapter(config)
|
152
|
+
|
153
|
+
# Connect to NATS
|
154
|
+
await publisher.connect()
|
155
|
+
|
156
|
+
request_message = HaiMessage(
|
157
|
+
message_id=str(uuid.uuid4()),
|
158
|
+
sender="client-service",
|
159
|
+
topic=Topic.AI_VECTORS_QUERY,
|
160
|
+
payload={"query": "find similar documents", "limit": 10}
|
161
|
+
)
|
162
|
+
|
163
|
+
# Send request and wait for response (with timeout)
|
164
|
+
response = await publisher.request(request_message, timeout=5.0)
|
165
|
+
|
166
|
+
if response:
|
167
|
+
print(f"Received response: {response.payload}")
|
168
|
+
else:
|
169
|
+
print("Request timed out")
|
170
|
+
|
171
|
+
await publisher.close()
|
172
|
+
|
173
|
+
if __name__ == "__main__":
|
174
|
+
asyncio.run(main())
|
175
|
+
```
|
176
|
+
|
177
|
+
### Creating a Service with Request Handler
|
178
|
+
|
179
|
+
```python
|
180
|
+
import asyncio
|
181
|
+
import uuid
|
182
|
+
from h_message_bus import NatsConfig, NatsSubscriberAdapter, NatsPublisherAdapter, HaiMessage, Topic
|
183
|
+
|
184
|
+
async def request_handler(request: HaiMessage):
|
185
|
+
print(f"Received request: {request.message_id}")
|
186
|
+
print(f"Payload: {request.payload}")
|
187
|
+
|
188
|
+
# Process the request
|
189
|
+
result = {"status": "success", "data": {"result": 42}}
|
190
|
+
|
191
|
+
# Create a response message
|
192
|
+
return HaiMessage(
|
193
|
+
message_id=str(uuid.uuid4()),
|
194
|
+
sender="service-b",
|
195
|
+
topic=f"{request.topic}.response",
|
196
|
+
payload=result,
|
197
|
+
correlation_id=request.message_id
|
198
|
+
)
|
199
|
+
|
200
|
+
async def main():
|
201
|
+
# Configure NATS connection
|
202
|
+
config = NatsConfig(server="nats://localhost:4222")
|
203
|
+
|
204
|
+
# Create subscriber for handling requests
|
205
|
+
subscriber = NatsSubscriberAdapter(config)
|
206
|
+
publisher = NatsPublisherAdapter(config)
|
207
|
+
|
208
|
+
# Connect to NATS
|
209
|
+
await subscriber.connect()
|
210
|
+
await publisher.connect()
|
211
|
+
|
212
|
+
# Register request handler for vector database queries
|
213
|
+
await subscriber.subscribe_with_response(Topic.AI_VECTORS_QUERY, request_handler, publisher)
|
214
|
+
|
215
|
+
# Keep the application running
|
216
|
+
try:
|
217
|
+
print("Service running. Press Ctrl+C to exit.")
|
218
|
+
while True:
|
219
|
+
await asyncio.sleep(1)
|
220
|
+
except KeyboardInterrupt:
|
221
|
+
# Clean up
|
222
|
+
await subscriber.close()
|
223
|
+
await publisher.close()
|
224
|
+
|
225
|
+
if __name__ == "__main__":
|
226
|
+
asyncio.run(main())
|
227
|
+
```
|
228
|
+
|
229
|
+
## Configuration Options
|
230
|
+
|
231
|
+
The `NatsConfig` class allows you to customize your NATS connection:
|
232
|
+
|
233
|
+
| Parameter | Description | Default |
|
234
|
+
|--------------------------|----------------------------------------------|---------------|
|
235
|
+
| `server` | NATS server address | Required |
|
236
|
+
| `max_reconnect_attempts` | Maximum reconnection attempts | 10 |
|
237
|
+
| `reconnect_time_wait` | Time between reconnection attempts (seconds) | 2 |
|
238
|
+
| `connection_timeout` | Connection timeout (seconds) | 2 |
|
239
|
+
| `ping_interval` | Interval for ping frames (seconds) | 20 |
|
240
|
+
| `max_outstanding_pings` | Maximum unanswered pings before disconnect | 5 |
|
241
|
+
| `max_payload` | Maximum size of the payload in bytes | 1048576 (1MB) |
|
242
|
+
|
243
|
+
## API Reference
|
244
|
+
|
245
|
+
### Exported Classes
|
246
|
+
|
247
|
+
The following classes are exported directly from the package:
|
248
|
+
|
249
|
+
- `NatsConfig` - Configuration for the NATS connection
|
250
|
+
- `HaiMessage` - Message structure for HAI communication
|
251
|
+
- `NatsPublisherAdapter` - Adapter for publishing messages
|
252
|
+
- `NatsSubscriberAdapter` - Adapter for subscribing to messages
|
253
|
+
- `MessageProcessor` - Processing incoming messages
|
254
|
+
- `NatsClientRepository` - Low-level NATS client operations
|
255
|
+
- `Topic` - Enumeration of predefined topic strings
|
256
|
+
|
257
|
+
### HaiMessage Structure
|
258
|
+
|
259
|
+
The `HaiMessage` class is the core data structure used for all messaging:
|
260
|
+
|
261
|
+
```python
|
262
|
+
class HaiMessage:
|
263
|
+
message_id: str # Unique identifier for the message
|
264
|
+
sender: str # Identifier of the sender
|
265
|
+
topic: str # The topic or channel for the message
|
266
|
+
payload: dict # Actual message data
|
267
|
+
correlation_id: str = None # Optional reference to a related message
|
268
|
+
timestamp: float = None # Optional message creation timestamp
|
269
|
+
```
|
270
|
+
|
271
|
+
## Contributing
|
272
|
+
|
273
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
274
|
+
|
275
|
+
## License
|
276
|
+
|
277
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
README.md
|
2
|
+
pyproject.toml
|
3
|
+
src/h_message_bus/__init__.py
|
4
|
+
src/h_message_bus.egg-info/PKG-INFO
|
5
|
+
src/h_message_bus.egg-info/SOURCES.txt
|
6
|
+
src/h_message_bus.egg-info/dependency_links.txt
|
7
|
+
src/h_message_bus.egg-info/requires.txt
|
8
|
+
src/h_message_bus.egg-info/top_level.txt
|
9
|
+
src/h_message_bus/adapters/__init__.py
|
10
|
+
src/h_message_bus/adapters/nats_message_adapter.py
|
11
|
+
src/h_message_bus/adapters/nats_publisher_adapter.py
|
12
|
+
src/h_message_bus/adapters/nats_subscriber_adapter.py
|
13
|
+
src/h_message_bus/application/__init__.py
|
14
|
+
src/h_message_bus/application/message_processor.py
|
15
|
+
src/h_message_bus/application/message_publisher.py
|
16
|
+
src/h_message_bus/application/message_subcriber.py
|
17
|
+
src/h_message_bus/domain/__init__.py
|
18
|
+
src/h_message_bus/domain/hai_message.py
|
19
|
+
src/h_message_bus/domain/topics.py
|
20
|
+
src/h_message_bus/infrastructure/__init__.py
|
21
|
+
src/h_message_bus/infrastructure/nats_client_repository.py
|
22
|
+
src/h_message_bus/infrastructure/nats_config.py
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
h_message_bus
|