ipc-framework 1.0.0__py3-none-any.whl → 1.1.0__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.
- ipc_framework/client.py +278 -273
- ipc_framework/core.py +364 -352
- ipc_framework-1.1.0.dist-info/METADATA +273 -0
- {ipc_framework-1.0.0.dist-info → ipc_framework-1.1.0.dist-info}/RECORD +8 -8
- ipc_framework-1.0.0.dist-info/METADATA +0 -512
- {ipc_framework-1.0.0.dist-info → ipc_framework-1.1.0.dist-info}/WHEEL +0 -0
- {ipc_framework-1.0.0.dist-info → ipc_framework-1.1.0.dist-info}/entry_points.txt +0 -0
- {ipc_framework-1.0.0.dist-info → ipc_framework-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {ipc_framework-1.0.0.dist-info → ipc_framework-1.1.0.dist-info}/top_level.txt +0 -0
ipc_framework/core.py
CHANGED
@@ -1,353 +1,365 @@
|
|
1
|
-
"""
|
2
|
-
Core classes for the IPC Framework
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import uuid
|
7
|
-
import time
|
8
|
-
import threading
|
9
|
-
from typing import Dict, List, Optional, Callable, Any
|
10
|
-
from dataclasses import dataclass, asdict
|
11
|
-
from enum import Enum
|
12
|
-
|
13
|
-
|
14
|
-
class MessageType(Enum):
|
15
|
-
"""Types of messages in the IPC system"""
|
16
|
-
REQUEST = "request"
|
17
|
-
RESPONSE = "response"
|
18
|
-
NOTIFICATION = "notification"
|
19
|
-
SUBSCRIBE = "subscribe"
|
20
|
-
UNSUBSCRIBE = "unsubscribe"
|
21
|
-
PUBLISH = "publish"
|
22
|
-
|
23
|
-
|
24
|
-
@dataclass
|
25
|
-
class Message:
|
26
|
-
"""Represents a message in the IPC system"""
|
27
|
-
message_id: str
|
28
|
-
app_id: str
|
29
|
-
channel_id: str
|
30
|
-
message_type: MessageType
|
31
|
-
payload: Any
|
32
|
-
timestamp: float
|
33
|
-
reply_to: Optional[str] = None
|
34
|
-
|
35
|
-
def __post_init__(self):
|
36
|
-
if not self.message_id:
|
37
|
-
self.message_id = str(uuid.uuid4())
|
38
|
-
if not self.timestamp:
|
39
|
-
self.timestamp = time.time()
|
40
|
-
|
41
|
-
def
|
42
|
-
"""
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def
|
54
|
-
"""
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
self.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def
|
87
|
-
"""
|
88
|
-
with self._lock:
|
89
|
-
self.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
self.
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
self.
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
def
|
169
|
-
"""
|
170
|
-
with self._lock:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
self.
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
app =
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
return
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
def
|
273
|
-
"""
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
def
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
)
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
1
|
+
"""
|
2
|
+
Core classes for the IPC Framework
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import uuid
|
7
|
+
import time
|
8
|
+
import threading
|
9
|
+
from typing import Dict, List, Optional, Callable, Any
|
10
|
+
from dataclasses import dataclass, asdict
|
11
|
+
from enum import Enum
|
12
|
+
|
13
|
+
|
14
|
+
class MessageType(Enum):
|
15
|
+
"""Types of messages in the IPC system"""
|
16
|
+
REQUEST = "request"
|
17
|
+
RESPONSE = "response"
|
18
|
+
NOTIFICATION = "notification"
|
19
|
+
SUBSCRIBE = "subscribe"
|
20
|
+
UNSUBSCRIBE = "unsubscribe"
|
21
|
+
PUBLISH = "publish"
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class Message:
|
26
|
+
"""Represents a message in the IPC system"""
|
27
|
+
message_id: str
|
28
|
+
app_id: str
|
29
|
+
channel_id: str
|
30
|
+
message_type: MessageType
|
31
|
+
payload: Any
|
32
|
+
timestamp: float
|
33
|
+
reply_to: Optional[str] = None
|
34
|
+
|
35
|
+
def __post_init__(self):
|
36
|
+
if not self.message_id:
|
37
|
+
self.message_id = str(uuid.uuid4())
|
38
|
+
if not self.timestamp:
|
39
|
+
self.timestamp = time.time()
|
40
|
+
|
41
|
+
def create_response(self, payload: Any) -> 'Message':
|
42
|
+
"""Create a response message to this message"""
|
43
|
+
return Message(
|
44
|
+
message_id=str(uuid.uuid4()),
|
45
|
+
app_id=self.app_id,
|
46
|
+
channel_id=self.channel_id,
|
47
|
+
message_type=MessageType.RESPONSE,
|
48
|
+
payload=payload,
|
49
|
+
timestamp=time.time(),
|
50
|
+
reply_to=self.message_id
|
51
|
+
)
|
52
|
+
|
53
|
+
def to_dict(self) -> Dict:
|
54
|
+
"""Convert message to dictionary for serialization"""
|
55
|
+
data = asdict(self)
|
56
|
+
data['message_type'] = self.message_type.value
|
57
|
+
return data
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def from_dict(cls, data: Dict) -> 'Message':
|
61
|
+
"""Create message from dictionary"""
|
62
|
+
data['message_type'] = MessageType(data['message_type'])
|
63
|
+
return cls(**data)
|
64
|
+
|
65
|
+
def to_json(self) -> str:
|
66
|
+
"""Serialize message to JSON"""
|
67
|
+
return json.dumps(self.to_dict())
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def from_json(cls, json_str: str) -> 'Message':
|
71
|
+
"""Deserialize message from JSON"""
|
72
|
+
return cls.from_dict(json.loads(json_str))
|
73
|
+
|
74
|
+
|
75
|
+
class Channel:
|
76
|
+
"""Represents a communication channel within an application"""
|
77
|
+
|
78
|
+
def __init__(self, channel_id: str, app_id: str):
|
79
|
+
self.channel_id = channel_id
|
80
|
+
self.app_id = app_id
|
81
|
+
self.subscribers: List[str] = [] # Connection IDs
|
82
|
+
self.handlers: Dict[MessageType, Callable] = {}
|
83
|
+
self.created_at = time.time()
|
84
|
+
self._lock = threading.RLock()
|
85
|
+
|
86
|
+
def add_subscriber(self, connection_id: str):
|
87
|
+
"""Add a subscriber to this channel"""
|
88
|
+
with self._lock:
|
89
|
+
if connection_id not in self.subscribers:
|
90
|
+
self.subscribers.append(connection_id)
|
91
|
+
|
92
|
+
def remove_subscriber(self, connection_id: str):
|
93
|
+
"""Remove a subscriber from this channel"""
|
94
|
+
with self._lock:
|
95
|
+
if connection_id in self.subscribers:
|
96
|
+
self.subscribers.remove(connection_id)
|
97
|
+
|
98
|
+
def set_handler(self, message_type: MessageType, handler: Callable):
|
99
|
+
"""Set a message handler for this channel"""
|
100
|
+
with self._lock:
|
101
|
+
self.handlers[message_type] = handler
|
102
|
+
|
103
|
+
def get_handler(self, message_type: MessageType) -> Optional[Callable]:
|
104
|
+
"""Get the handler for a message type"""
|
105
|
+
with self._lock:
|
106
|
+
return self.handlers.get(message_type)
|
107
|
+
|
108
|
+
def get_subscribers(self) -> List[str]:
|
109
|
+
"""Get list of current subscribers"""
|
110
|
+
with self._lock:
|
111
|
+
return self.subscribers.copy()
|
112
|
+
|
113
|
+
|
114
|
+
class Application:
|
115
|
+
"""Represents an application with multiple channels"""
|
116
|
+
|
117
|
+
def __init__(self, app_id: str, name: str = None):
|
118
|
+
self.app_id = app_id
|
119
|
+
self.name = name or app_id
|
120
|
+
self.channels: Dict[str, Channel] = {}
|
121
|
+
self.created_at = time.time()
|
122
|
+
self.last_activity = time.time()
|
123
|
+
self._lock = threading.RLock()
|
124
|
+
|
125
|
+
def create_channel(self, channel_id: str) -> Channel:
|
126
|
+
"""Create a new channel in this application"""
|
127
|
+
with self._lock:
|
128
|
+
if channel_id in self.channels:
|
129
|
+
return self.channels[channel_id]
|
130
|
+
|
131
|
+
channel = Channel(channel_id, self.app_id)
|
132
|
+
self.channels[channel_id] = channel
|
133
|
+
self.last_activity = time.time()
|
134
|
+
return channel
|
135
|
+
|
136
|
+
def get_channel(self, channel_id: str) -> Optional[Channel]:
|
137
|
+
"""Get a channel by ID"""
|
138
|
+
with self._lock:
|
139
|
+
return self.channels.get(channel_id)
|
140
|
+
|
141
|
+
def remove_channel(self, channel_id: str) -> bool:
|
142
|
+
"""Remove a channel"""
|
143
|
+
with self._lock:
|
144
|
+
if channel_id in self.channels:
|
145
|
+
del self.channels[channel_id]
|
146
|
+
self.last_activity = time.time()
|
147
|
+
return True
|
148
|
+
return False
|
149
|
+
|
150
|
+
def list_channels(self) -> List[str]:
|
151
|
+
"""List all channel IDs in this application"""
|
152
|
+
with self._lock:
|
153
|
+
return list(self.channels.keys())
|
154
|
+
|
155
|
+
def update_activity(self):
|
156
|
+
"""Update the last activity timestamp"""
|
157
|
+
self.last_activity = time.time()
|
158
|
+
|
159
|
+
|
160
|
+
class ConnectionManager:
|
161
|
+
"""Manages client connections"""
|
162
|
+
|
163
|
+
def __init__(self):
|
164
|
+
self.connections: Dict[str, Any] = {} # connection_id -> connection object
|
165
|
+
self.connection_apps: Dict[str, str] = {} # connection_id -> app_id
|
166
|
+
self._lock = threading.RLock()
|
167
|
+
|
168
|
+
def add_connection(self, connection_id: str, connection_obj: Any, app_id: str):
|
169
|
+
"""Add a new connection"""
|
170
|
+
with self._lock:
|
171
|
+
self.connections[connection_id] = connection_obj
|
172
|
+
self.connection_apps[connection_id] = app_id
|
173
|
+
|
174
|
+
def remove_connection(self, connection_id: str):
|
175
|
+
"""Remove a connection"""
|
176
|
+
with self._lock:
|
177
|
+
self.connections.pop(connection_id, None)
|
178
|
+
self.connection_apps.pop(connection_id, None)
|
179
|
+
|
180
|
+
def get_connection(self, connection_id: str) -> Optional[Any]:
|
181
|
+
"""Get a connection object"""
|
182
|
+
with self._lock:
|
183
|
+
return self.connections.get(connection_id)
|
184
|
+
|
185
|
+
def get_connections_for_app(self, app_id: str) -> List[str]:
|
186
|
+
"""Get all connection IDs for an application"""
|
187
|
+
with self._lock:
|
188
|
+
return [conn_id for conn_id, app in self.connection_apps.items() if app == app_id]
|
189
|
+
|
190
|
+
def list_connections(self) -> List[str]:
|
191
|
+
"""List all connection IDs"""
|
192
|
+
with self._lock:
|
193
|
+
return list(self.connections.keys())
|
194
|
+
|
195
|
+
|
196
|
+
class IPCServer:
|
197
|
+
"""Base IPC Server class"""
|
198
|
+
|
199
|
+
def __init__(self, host: str = "localhost", port: int = 8888):
|
200
|
+
self.host = host
|
201
|
+
self.port = port
|
202
|
+
self.applications: Dict[str, Application] = {}
|
203
|
+
self.connection_manager = ConnectionManager()
|
204
|
+
self.running = False
|
205
|
+
self._lock = threading.RLock()
|
206
|
+
|
207
|
+
def create_application(self, app_id: str, name: str = None) -> Application:
|
208
|
+
"""Create a new application"""
|
209
|
+
with self._lock:
|
210
|
+
if app_id in self.applications:
|
211
|
+
return self.applications[app_id]
|
212
|
+
|
213
|
+
app = Application(app_id, name)
|
214
|
+
self.applications[app_id] = app
|
215
|
+
return app
|
216
|
+
|
217
|
+
def get_application(self, app_id: str) -> Optional[Application]:
|
218
|
+
"""Get an application by ID"""
|
219
|
+
with self._lock:
|
220
|
+
return self.applications.get(app_id)
|
221
|
+
|
222
|
+
def route_message(self, message: Message) -> bool:
|
223
|
+
"""Route a message to the appropriate handlers"""
|
224
|
+
with self._lock:
|
225
|
+
app = self.get_application(message.app_id)
|
226
|
+
if not app:
|
227
|
+
return False
|
228
|
+
|
229
|
+
channel = app.get_channel(message.channel_id)
|
230
|
+
if not channel:
|
231
|
+
return False
|
232
|
+
|
233
|
+
app.update_activity()
|
234
|
+
|
235
|
+
# Handle different message types
|
236
|
+
if message.message_type == MessageType.PUBLISH:
|
237
|
+
return self._handle_publish(message, channel)
|
238
|
+
elif message.message_type == MessageType.SUBSCRIBE:
|
239
|
+
return self._handle_subscribe(message, channel)
|
240
|
+
elif message.message_type == MessageType.UNSUBSCRIBE:
|
241
|
+
return self._handle_unsubscribe(message, channel)
|
242
|
+
else:
|
243
|
+
handler = channel.get_handler(message.message_type)
|
244
|
+
if handler:
|
245
|
+
try:
|
246
|
+
handler(message)
|
247
|
+
return True
|
248
|
+
except Exception as e:
|
249
|
+
print(f"Handler error: {e}")
|
250
|
+
return False
|
251
|
+
|
252
|
+
return False
|
253
|
+
|
254
|
+
def _handle_publish(self, message: Message, channel: Channel) -> bool:
|
255
|
+
"""Handle publish message by broadcasting to subscribers"""
|
256
|
+
subscribers = channel.get_subscribers()
|
257
|
+
for subscriber_id in subscribers:
|
258
|
+
connection = self.connection_manager.get_connection(subscriber_id)
|
259
|
+
if connection:
|
260
|
+
self.send_to_connection(connection, message)
|
261
|
+
return True
|
262
|
+
|
263
|
+
def _handle_subscribe(self, message: Message, channel: Channel) -> bool:
|
264
|
+
"""Handle subscribe message"""
|
265
|
+
# Extract connection ID from message payload
|
266
|
+
connection_id = message.payload.get('connection_id')
|
267
|
+
if connection_id:
|
268
|
+
channel.add_subscriber(connection_id)
|
269
|
+
return True
|
270
|
+
return False
|
271
|
+
|
272
|
+
def _handle_unsubscribe(self, message: Message, channel: Channel) -> bool:
|
273
|
+
"""Handle unsubscribe message"""
|
274
|
+
connection_id = message.payload.get('connection_id')
|
275
|
+
if connection_id:
|
276
|
+
channel.remove_subscriber(connection_id)
|
277
|
+
return True
|
278
|
+
return False
|
279
|
+
|
280
|
+
def send_to_connection(self, connection, message: Message):
|
281
|
+
"""Send message to a specific connection - to be implemented by subclasses"""
|
282
|
+
raise NotImplementedError("Subclasses must implement send_to_connection")
|
283
|
+
|
284
|
+
def start(self):
|
285
|
+
"""Start the server - to be implemented by subclasses"""
|
286
|
+
raise NotImplementedError("Subclasses must implement start")
|
287
|
+
|
288
|
+
def stop(self):
|
289
|
+
"""Stop the server - to be implemented by subclasses"""
|
290
|
+
raise NotImplementedError("Subclasses must implement stop")
|
291
|
+
|
292
|
+
|
293
|
+
class IPCClient:
|
294
|
+
"""Base IPC Client class"""
|
295
|
+
|
296
|
+
def __init__(self, app_id: str, host: str = "localhost", port: int = 8888):
|
297
|
+
self.app_id = app_id
|
298
|
+
self.host = host
|
299
|
+
self.port = port
|
300
|
+
self.connection_id = str(uuid.uuid4())
|
301
|
+
self.connected = False
|
302
|
+
self.message_handlers: Dict[str, Callable] = {} # channel_id -> handler
|
303
|
+
self._lock = threading.RLock()
|
304
|
+
|
305
|
+
def connect(self) -> bool:
|
306
|
+
"""Connect to the server - to be implemented by subclasses"""
|
307
|
+
raise NotImplementedError("Subclasses must implement connect")
|
308
|
+
|
309
|
+
def disconnect(self):
|
310
|
+
"""Disconnect from the server - to be implemented by subclasses"""
|
311
|
+
raise NotImplementedError("Subclasses must implement disconnect")
|
312
|
+
|
313
|
+
def send_message(self, channel_id: str, message_type: MessageType, payload: Any, reply_to: str = None) -> str:
|
314
|
+
"""Send a message to a specific channel"""
|
315
|
+
message = Message(
|
316
|
+
message_id=str(uuid.uuid4()),
|
317
|
+
app_id=self.app_id,
|
318
|
+
channel_id=channel_id,
|
319
|
+
message_type=message_type,
|
320
|
+
payload=payload,
|
321
|
+
timestamp=time.time(),
|
322
|
+
reply_to=reply_to
|
323
|
+
)
|
324
|
+
return self._send_message(message)
|
325
|
+
|
326
|
+
def _send_message(self, message: Message) -> str:
|
327
|
+
"""Internal method to send message - to be implemented by subclasses"""
|
328
|
+
raise NotImplementedError("Subclasses must implement _send_message")
|
329
|
+
|
330
|
+
def subscribe(self, channel_id: str, handler: Callable = None) -> bool:
|
331
|
+
"""Subscribe to a channel"""
|
332
|
+
if handler:
|
333
|
+
with self._lock:
|
334
|
+
self.message_handlers[channel_id] = handler
|
335
|
+
|
336
|
+
payload = {'connection_id': self.connection_id}
|
337
|
+
self.send_message(channel_id, MessageType.SUBSCRIBE, payload)
|
338
|
+
return True
|
339
|
+
|
340
|
+
def unsubscribe(self, channel_id: str) -> bool:
|
341
|
+
"""Unsubscribe from a channel"""
|
342
|
+
with self._lock:
|
343
|
+
self.message_handlers.pop(channel_id, None)
|
344
|
+
|
345
|
+
payload = {'connection_id': self.connection_id}
|
346
|
+
self.send_message(channel_id, MessageType.UNSUBSCRIBE, payload)
|
347
|
+
return True
|
348
|
+
|
349
|
+
def publish(self, channel_id: str, data: Any) -> str:
|
350
|
+
"""Publish data to a channel"""
|
351
|
+
return self.send_message(channel_id, MessageType.PUBLISH, data)
|
352
|
+
|
353
|
+
def request(self, channel_id: str, data: Any) -> str:
|
354
|
+
"""Send a request message"""
|
355
|
+
return self.send_message(channel_id, MessageType.REQUEST, data)
|
356
|
+
|
357
|
+
def handle_message(self, message: Message):
|
358
|
+
"""Handle incoming message"""
|
359
|
+
with self._lock:
|
360
|
+
handler = self.message_handlers.get(message.channel_id)
|
361
|
+
if handler:
|
362
|
+
try:
|
363
|
+
handler(message)
|
364
|
+
except Exception as e:
|
353
365
|
print(f"Message handler error: {e}")
|