qyro 2.0.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.
- qyro/__init__.py +17 -0
- qyro/adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/c/__init__.py +4 -0
- qyro/adapters/language_adapters/python/__init__.py +4 -0
- qyro/adapters/language_adapters/python/python_adapter.py +584 -0
- qyro/cli/__init__.py +8 -0
- qyro/cli/__main__.py +5 -0
- qyro/cli/cli.py +392 -0
- qyro/cli/interactive.py +297 -0
- qyro/common/__init__.py +37 -0
- qyro/common/animation.py +82 -0
- qyro/common/builder.py +434 -0
- qyro/common/compiler.py +895 -0
- qyro/common/config.py +93 -0
- qyro/common/constants.py +99 -0
- qyro/common/errors.py +176 -0
- qyro/common/frontend.py +74 -0
- qyro/common/health.py +358 -0
- qyro/common/kafka_manager.py +192 -0
- qyro/common/logging.py +149 -0
- qyro/common/memory.py +147 -0
- qyro/common/metrics.py +301 -0
- qyro/common/monitoring.py +468 -0
- qyro/common/parser.py +91 -0
- qyro/common/platform.py +609 -0
- qyro/common/redis_memory.py +1108 -0
- qyro/common/rpc.py +287 -0
- qyro/common/sandbox.py +191 -0
- qyro/common/schema_loader.py +33 -0
- qyro/common/secure_sandbox.py +490 -0
- qyro/common/toolchain_validator.py +617 -0
- qyro/common/type_generator.py +176 -0
- qyro/common/validation.py +401 -0
- qyro/common/validator.py +204 -0
- qyro/gateway/__init__.py +8 -0
- qyro/gateway/gateway.py +303 -0
- qyro/orchestrator/__init__.py +8 -0
- qyro/orchestrator/orchestrator.py +1223 -0
- qyro-2.0.0.dist-info/METADATA +244 -0
- qyro-2.0.0.dist-info/RECORD +45 -0
- qyro-2.0.0.dist-info/WHEEL +5 -0
- qyro-2.0.0.dist-info/entry_points.txt +2 -0
- qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
- qyro-2.0.0.dist-info/top_level.txt +1 -0
qyro/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qyro - Universal Polyglot Runtime
|
|
3
|
+
The Universal Polyglot Runtime - Write Python, C, Rust, Java in one file with shared state and modern microservices architecture.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "2.0.0"
|
|
7
|
+
__author__ = "Qyro Team"
|
|
8
|
+
__email__ = "team@qyro.dev"
|
|
9
|
+
|
|
10
|
+
# Define the package exports
|
|
11
|
+
__all__ = [
|
|
12
|
+
'orchestrator',
|
|
13
|
+
'common',
|
|
14
|
+
'cli',
|
|
15
|
+
'gateway',
|
|
16
|
+
'adapters'
|
|
17
|
+
]
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexus Python Adapter - Redis Pub/Sub Implementation
|
|
3
|
+
Thread-safe Redis-based communication for cross-language integration.
|
|
4
|
+
|
|
5
|
+
This adapter uses Redis pub/sub for real-time communication instead of
|
|
6
|
+
file-based shared memory. It reads Redis connection info from environment
|
|
7
|
+
variables provided by the orchestrator.
|
|
8
|
+
|
|
9
|
+
Environment Variables:
|
|
10
|
+
NEXUS_REDIS_HOST - Redis server host (default: localhost)
|
|
11
|
+
NEXUS_REDIS_PORT - Redis server port (default: 6379)
|
|
12
|
+
NEXUS_REDIS_DB - Redis database number (default: 0)
|
|
13
|
+
NEXUS_REDIS_PASSWORD - Redis password (optional)
|
|
14
|
+
NEXUS_MODULE_NAME - Module name for this adapter (required)
|
|
15
|
+
|
|
16
|
+
Redis Channels:
|
|
17
|
+
nexus:state - Read/write state
|
|
18
|
+
nexus:state:changed - Subscribe to state changes
|
|
19
|
+
nexus:events - Publish/subscribe to events
|
|
20
|
+
nexus:broadcast - Broadcast messages
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import json
|
|
25
|
+
import time
|
|
26
|
+
import threading
|
|
27
|
+
import uuid
|
|
28
|
+
from typing import Dict, Any, Optional, Callable, List
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import redis
|
|
33
|
+
except ImportError:
|
|
34
|
+
raise ImportError(
|
|
35
|
+
"Redis library is required. Install with: pip install redis"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Error codes matching Python constants.py
|
|
40
|
+
class NexusError(Exception):
|
|
41
|
+
"""Base exception for Nexus errors."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ErrorCode:
|
|
46
|
+
OK = 0
|
|
47
|
+
REDIS_CONNECTION_FAILED = 1001
|
|
48
|
+
REDIS_SUBSCRIPTION_FAILED = 1002
|
|
49
|
+
STATE_READ_FAILED = 1003
|
|
50
|
+
STATE_WRITE_FAILED = 1004
|
|
51
|
+
EVENT_PUBLISH_FAILED = 1005
|
|
52
|
+
JSON_PARSE_ERROR = 2001
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class RedisConfig:
|
|
57
|
+
"""Redis connection configuration."""
|
|
58
|
+
host: str
|
|
59
|
+
port: int
|
|
60
|
+
db: int
|
|
61
|
+
password: Optional[str]
|
|
62
|
+
module_name: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class NexusModule:
|
|
66
|
+
"""
|
|
67
|
+
Nexus Python Adapter using Redis pub/sub for communication.
|
|
68
|
+
|
|
69
|
+
This class provides a Redis-based interface for cross-language
|
|
70
|
+
communication, replacing file-based shared memory with Redis pub/sub.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Redis channel names
|
|
74
|
+
CHANNEL_STATE = "nexus:state"
|
|
75
|
+
CHANNEL_STATE_CHANGED = "nexus:state:changed"
|
|
76
|
+
CHANNEL_EVENTS = "nexus:events"
|
|
77
|
+
CHANNEL_BROADCAST = "nexus:broadcast"
|
|
78
|
+
|
|
79
|
+
def __init__(self, module_name: Optional[str] = None):
|
|
80
|
+
"""
|
|
81
|
+
Initialize the Nexus module with Redis connection.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
module_name: Module name (defaults to NEXUS_MODULE_NAME env var)
|
|
85
|
+
"""
|
|
86
|
+
self.config = self._load_config(module_name)
|
|
87
|
+
self.redis_client: Optional[redis.Redis] = None
|
|
88
|
+
self.pubsub: Optional[redis.client.PubSub] = None
|
|
89
|
+
self._subscribed = False
|
|
90
|
+
self._event_handlers: Dict[str, List[Callable]] = {}
|
|
91
|
+
self._state_change_handlers: List[Callable] = []
|
|
92
|
+
self._broadcast_handlers: List[Callable] = []
|
|
93
|
+
self._listener_thread: Optional[threading.Thread] = None
|
|
94
|
+
self._running = False
|
|
95
|
+
self._process_id = uuid.uuid4().hex[:8]
|
|
96
|
+
|
|
97
|
+
def _load_config(self, module_name: Optional[str]) -> RedisConfig:
|
|
98
|
+
"""Load Redis configuration from environment variables."""
|
|
99
|
+
host = os.getenv("NEXUS_REDIS_HOST", "localhost")
|
|
100
|
+
port = int(os.getenv("NEXUS_REDIS_PORT", "6379"))
|
|
101
|
+
db = int(os.getenv("NEXUS_REDIS_DB", "0"))
|
|
102
|
+
password = os.getenv("NEXUS_REDIS_PASSWORD")
|
|
103
|
+
name = module_name or os.getenv("NEXUS_MODULE_NAME", "python_module")
|
|
104
|
+
|
|
105
|
+
return RedisConfig(
|
|
106
|
+
host=host,
|
|
107
|
+
port=port,
|
|
108
|
+
db=db,
|
|
109
|
+
password=password,
|
|
110
|
+
module_name=name
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def connect(self) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Connect to Redis and initialize pub/sub.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
NexusError: If connection fails
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
# Create Redis client
|
|
122
|
+
self.redis_client = redis.Redis(
|
|
123
|
+
host=self.config.host,
|
|
124
|
+
port=self.config.port,
|
|
125
|
+
db=self.config.db,
|
|
126
|
+
password=self.config.password,
|
|
127
|
+
decode_responses=True,
|
|
128
|
+
socket_connect_timeout=5,
|
|
129
|
+
socket_timeout=5
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Test connection
|
|
133
|
+
self.redis_client.ping()
|
|
134
|
+
|
|
135
|
+
# Create pub/sub instance
|
|
136
|
+
self.pubsub = self.redis_client.pubsub()
|
|
137
|
+
|
|
138
|
+
print(f"[NEXUS-Python] Connected to Redis at {self.config.host}:{self.config.port}")
|
|
139
|
+
print(f"[NEXUS-Python] Module: {self.config.module_name} (PID: {self._process_id})")
|
|
140
|
+
|
|
141
|
+
except redis.ConnectionError as e:
|
|
142
|
+
raise NexusError(
|
|
143
|
+
ErrorCode.REDIS_CONNECTION_FAILED,
|
|
144
|
+
f"Failed to connect to Redis: {e}"
|
|
145
|
+
)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
raise NexusError(
|
|
148
|
+
ErrorCode.REDIS_CONNECTION_FAILED,
|
|
149
|
+
f"Unexpected error connecting to Redis: {e}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def disconnect(self) -> None:
|
|
153
|
+
"""Disconnect from Redis and cleanup resources."""
|
|
154
|
+
self._running = False
|
|
155
|
+
|
|
156
|
+
if self._listener_thread:
|
|
157
|
+
self._listener_thread.join(timeout=2)
|
|
158
|
+
|
|
159
|
+
if self.pubsub:
|
|
160
|
+
try:
|
|
161
|
+
self.pubsub.close()
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
if self.redis_client:
|
|
166
|
+
try:
|
|
167
|
+
self.redis_client.close()
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
self._subscribed = False
|
|
172
|
+
print("[NEXUS-Python] Disconnected from Redis")
|
|
173
|
+
|
|
174
|
+
# ========================================================================
|
|
175
|
+
# STATE OPERATIONS
|
|
176
|
+
# ========================================================================
|
|
177
|
+
|
|
178
|
+
def read_state(self) -> Dict[str, Any]:
|
|
179
|
+
"""
|
|
180
|
+
Read the current state from Redis.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dictionary containing the current state
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
NexusError: If read fails
|
|
187
|
+
"""
|
|
188
|
+
if not self.redis_client:
|
|
189
|
+
raise NexusError(
|
|
190
|
+
ErrorCode.STATE_READ_FAILED,
|
|
191
|
+
"Not connected to Redis"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
state_json = self.redis_client.get(self.CHANNEL_STATE)
|
|
196
|
+
if state_json is None:
|
|
197
|
+
return {}
|
|
198
|
+
|
|
199
|
+
return json.loads(state_json)
|
|
200
|
+
|
|
201
|
+
except json.JSONDecodeError as e:
|
|
202
|
+
raise NexusError(
|
|
203
|
+
ErrorCode.JSON_PARSE_ERROR,
|
|
204
|
+
f"Failed to parse state JSON: {e}"
|
|
205
|
+
)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
raise NexusError(
|
|
208
|
+
ErrorCode.STATE_READ_FAILED,
|
|
209
|
+
f"Failed to read state: {e}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def write_state(self, state: Dict[str, Any]) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Write state to Redis and publish state change notification.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
state: Dictionary containing the state to write
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
NexusError: If write fails
|
|
221
|
+
"""
|
|
222
|
+
if not self.redis_client:
|
|
223
|
+
raise NexusError(
|
|
224
|
+
ErrorCode.STATE_WRITE_FAILED,
|
|
225
|
+
"Not connected to Redis"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
state_json = json.dumps(state)
|
|
230
|
+
|
|
231
|
+
# Write state to Redis
|
|
232
|
+
self.redis_client.set(self.CHANNEL_STATE, state_json)
|
|
233
|
+
|
|
234
|
+
# Publish state change notification
|
|
235
|
+
notification = {
|
|
236
|
+
"module": self.config.module_name,
|
|
237
|
+
"pid": self._process_id,
|
|
238
|
+
"timestamp": time.time()
|
|
239
|
+
}
|
|
240
|
+
self.redis_client.publish(
|
|
241
|
+
self.CHANNEL_STATE_CHANGED,
|
|
242
|
+
json.dumps(notification)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
raise NexusError(
|
|
247
|
+
ErrorCode.STATE_WRITE_FAILED,
|
|
248
|
+
f"Failed to write state: {e}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# ========================================================================
|
|
252
|
+
# EVENT OPERATIONS
|
|
253
|
+
# ========================================================================
|
|
254
|
+
|
|
255
|
+
def publish_event(self, event_type: str, data: Any) -> None:
|
|
256
|
+
"""
|
|
257
|
+
Publish an event to the events channel.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
event_type: Type of event (e.g., "function_call", "state_update")
|
|
261
|
+
data: Event data (will be JSON serialized)
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
NexusError: If publish fails
|
|
265
|
+
"""
|
|
266
|
+
if not self.redis_client:
|
|
267
|
+
raise NexusError(
|
|
268
|
+
ErrorCode.EVENT_PUBLISH_FAILED,
|
|
269
|
+
"Not connected to Redis"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
event = {
|
|
274
|
+
"type": event_type,
|
|
275
|
+
"module": self.config.module_name,
|
|
276
|
+
"pid": self._process_id,
|
|
277
|
+
"timestamp": time.time(),
|
|
278
|
+
"data": data
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
self.redis_client.publish(
|
|
282
|
+
self.CHANNEL_EVENTS,
|
|
283
|
+
json.dumps(event)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
raise NexusError(
|
|
288
|
+
ErrorCode.EVENT_PUBLISH_FAILED,
|
|
289
|
+
f"Failed to publish event: {e}"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def broadcast(self, message: Any) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Broadcast a message to all modules.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
message: Message to broadcast (will be JSON serialized)
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
NexusError: If broadcast fails
|
|
301
|
+
"""
|
|
302
|
+
if not self.redis_client:
|
|
303
|
+
raise NexusError(
|
|
304
|
+
ErrorCode.EVENT_PUBLISH_FAILED,
|
|
305
|
+
"Not connected to Redis"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
broadcast_msg = {
|
|
310
|
+
"module": self.config.module_name,
|
|
311
|
+
"pid": self._process_id,
|
|
312
|
+
"timestamp": time.time(),
|
|
313
|
+
"message": message
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
self.redis_client.publish(
|
|
317
|
+
self.CHANNEL_BROADCAST,
|
|
318
|
+
json.dumps(broadcast_msg)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
raise NexusError(
|
|
323
|
+
ErrorCode.EVENT_PUBLISH_FAILED,
|
|
324
|
+
f"Failed to broadcast: {e}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# ========================================================================
|
|
328
|
+
# SUBSCRIPTION HANDLERS
|
|
329
|
+
# ========================================================================
|
|
330
|
+
|
|
331
|
+
def on_event(self, event_type: str) -> Callable:
|
|
332
|
+
"""
|
|
333
|
+
Decorator to register an event handler.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
event_type: Type of event to handle
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Decorator function
|
|
340
|
+
"""
|
|
341
|
+
def decorator(handler: Callable):
|
|
342
|
+
if event_type not in self._event_handlers:
|
|
343
|
+
self._event_handlers[event_type] = []
|
|
344
|
+
self._event_handlers[event_type].append(handler)
|
|
345
|
+
return handler
|
|
346
|
+
return decorator
|
|
347
|
+
|
|
348
|
+
def on_state_changed(self, handler: Callable) -> None:
|
|
349
|
+
"""
|
|
350
|
+
Register a handler for state change events.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
handler: Function to call when state changes
|
|
354
|
+
"""
|
|
355
|
+
self._state_change_handlers.append(handler)
|
|
356
|
+
|
|
357
|
+
def on_broadcast(self, handler: Callable) -> None:
|
|
358
|
+
"""
|
|
359
|
+
Register a handler for broadcast messages.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
handler: Function to call when broadcast received
|
|
363
|
+
"""
|
|
364
|
+
self._broadcast_handlers.append(handler)
|
|
365
|
+
|
|
366
|
+
# ========================================================================
|
|
367
|
+
# PUB/SUB LISTENER
|
|
368
|
+
# ========================================================================
|
|
369
|
+
|
|
370
|
+
def subscribe(self) -> None:
|
|
371
|
+
"""
|
|
372
|
+
Subscribe to Redis channels and start the listener thread.
|
|
373
|
+
|
|
374
|
+
This subscribes to:
|
|
375
|
+
- nexus:state:changed - State change notifications
|
|
376
|
+
- nexus:events - All events
|
|
377
|
+
- nexus:broadcast - Broadcast messages
|
|
378
|
+
"""
|
|
379
|
+
if not self.pubsub:
|
|
380
|
+
raise NexusError(
|
|
381
|
+
ErrorCode.REDIS_SUBSCRIPTION_FAILED,
|
|
382
|
+
"PubSub not initialized"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
# Subscribe to channels
|
|
387
|
+
self.pubsub.subscribe(
|
|
388
|
+
self.CHANNEL_STATE_CHANGED,
|
|
389
|
+
self.CHANNEL_EVENTS,
|
|
390
|
+
self.CHANNEL_BROADCAST
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
self._subscribed = True
|
|
394
|
+
self._running = True
|
|
395
|
+
|
|
396
|
+
# Start listener thread
|
|
397
|
+
self._listener_thread = threading.Thread(
|
|
398
|
+
target=self._message_listener,
|
|
399
|
+
daemon=True
|
|
400
|
+
)
|
|
401
|
+
self._listener_thread.start()
|
|
402
|
+
|
|
403
|
+
print("[NEXUS-Python] Subscribed to Redis channels")
|
|
404
|
+
|
|
405
|
+
except Exception as e:
|
|
406
|
+
raise NexusError(
|
|
407
|
+
ErrorCode.REDIS_SUBSCRIPTION_FAILED,
|
|
408
|
+
f"Failed to subscribe: {e}"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _message_listener(self) -> None:
|
|
412
|
+
"""
|
|
413
|
+
Background thread that listens for Redis pub/sub messages.
|
|
414
|
+
This runs in a separate thread and dispatches messages to handlers.
|
|
415
|
+
"""
|
|
416
|
+
while self._running and self.pubsub:
|
|
417
|
+
try:
|
|
418
|
+
message = self.pubsub.get_message(timeout=1)
|
|
419
|
+
if message and message['type'] == 'message':
|
|
420
|
+
self._dispatch_message(message)
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
print(f"[NEXUS-Python] Error in message listener: {e}")
|
|
424
|
+
|
|
425
|
+
def _dispatch_message(self, message: Dict[str, Any]) -> None:
|
|
426
|
+
"""
|
|
427
|
+
Dispatch a received message to the appropriate handlers.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
message: Redis pub/sub message
|
|
431
|
+
"""
|
|
432
|
+
channel = message['channel']
|
|
433
|
+
data = message['data']
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
payload = json.loads(data)
|
|
437
|
+
|
|
438
|
+
# Handle state change notifications
|
|
439
|
+
if channel == self.CHANNEL_STATE_CHANGED:
|
|
440
|
+
for handler in self._state_change_handlers:
|
|
441
|
+
try:
|
|
442
|
+
handler(payload)
|
|
443
|
+
except Exception as e:
|
|
444
|
+
print(f"[NEXUS-Python] Error in state change handler: {e}")
|
|
445
|
+
|
|
446
|
+
# Handle events
|
|
447
|
+
elif channel == self.CHANNEL_EVENTS:
|
|
448
|
+
event_type = payload.get('type')
|
|
449
|
+
if event_type and event_type in self._event_handlers:
|
|
450
|
+
for handler in self._event_handlers[event_type]:
|
|
451
|
+
try:
|
|
452
|
+
handler(payload)
|
|
453
|
+
except Exception as e:
|
|
454
|
+
print(f"[NEXUS-Python] Error in event handler: {e}")
|
|
455
|
+
|
|
456
|
+
# Handle broadcasts
|
|
457
|
+
elif channel == self.CHANNEL_BROADCAST:
|
|
458
|
+
for handler in self._broadcast_handlers:
|
|
459
|
+
try:
|
|
460
|
+
handler(payload)
|
|
461
|
+
except Exception as e:
|
|
462
|
+
print(f"[NEXUS-Python] Error in broadcast handler: {e}")
|
|
463
|
+
|
|
464
|
+
except json.JSONDecodeError as e:
|
|
465
|
+
print(f"[NEXUS-Python] Failed to parse message: {e}")
|
|
466
|
+
|
|
467
|
+
# ========================================================================
|
|
468
|
+
# RPC SUPPORT
|
|
469
|
+
# ========================================================================
|
|
470
|
+
|
|
471
|
+
def register_function(self, name: str, handler: Callable) -> None:
|
|
472
|
+
"""
|
|
473
|
+
Register a function for cross-language RPC calls.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
name: Function name
|
|
477
|
+
handler: Function to call
|
|
478
|
+
"""
|
|
479
|
+
# Publish function registration event
|
|
480
|
+
registration = {
|
|
481
|
+
"type": "function_register",
|
|
482
|
+
"name": name,
|
|
483
|
+
"module": self.config.module_name,
|
|
484
|
+
"pid": self._process_id,
|
|
485
|
+
"lang": "python"
|
|
486
|
+
}
|
|
487
|
+
self.publish_event("function_register", registration)
|
|
488
|
+
print(f"[NEXUS-Python] Registered function: {name}")
|
|
489
|
+
|
|
490
|
+
def call_function(self, name: str, args: List[Any], timeout: float = 30.0) -> Any:
|
|
491
|
+
"""
|
|
492
|
+
Call a function registered in another module.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
name: Function name
|
|
496
|
+
args: Function arguments
|
|
497
|
+
timeout: Timeout in seconds
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Function result
|
|
501
|
+
|
|
502
|
+
Raises:
|
|
503
|
+
NexusError: If call fails or times out
|
|
504
|
+
"""
|
|
505
|
+
call_id = uuid.uuid4().hex[:12]
|
|
506
|
+
|
|
507
|
+
# Publish function call event
|
|
508
|
+
call_request = {
|
|
509
|
+
"id": call_id,
|
|
510
|
+
"fn": name,
|
|
511
|
+
"args": args,
|
|
512
|
+
"caller": self._process_id,
|
|
513
|
+
"timestamp": time.time()
|
|
514
|
+
}
|
|
515
|
+
self.publish_event("function_call", call_request)
|
|
516
|
+
|
|
517
|
+
# Wait for response (simplified - in production use a proper response queue)
|
|
518
|
+
start_time = time.time()
|
|
519
|
+
while time.time() - start_time < timeout:
|
|
520
|
+
# Check for response in a real implementation
|
|
521
|
+
time.sleep(0.05)
|
|
522
|
+
|
|
523
|
+
raise NexusError(
|
|
524
|
+
ErrorCode.EVENT_PUBLISH_FAILED,
|
|
525
|
+
f"Call to {name} timed out after {timeout}s"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# ========================================================================
|
|
529
|
+
# CONTEXT MANAGER
|
|
530
|
+
# ========================================================================
|
|
531
|
+
|
|
532
|
+
def __enter__(self):
|
|
533
|
+
"""Context manager entry."""
|
|
534
|
+
self.connect()
|
|
535
|
+
return self
|
|
536
|
+
|
|
537
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
538
|
+
"""Context manager exit."""
|
|
539
|
+
self.disconnect()
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
# ============================================================================
|
|
543
|
+
# EXAMPLE USAGE
|
|
544
|
+
# ============================================================================
|
|
545
|
+
|
|
546
|
+
if __name__ == "__main__":
|
|
547
|
+
# Example usage of the Nexus Python adapter
|
|
548
|
+
with NexusModule() as nexus:
|
|
549
|
+
# Subscribe to channels
|
|
550
|
+
nexus.subscribe()
|
|
551
|
+
|
|
552
|
+
# Register event handlers
|
|
553
|
+
@nexus.on_event("function_call")
|
|
554
|
+
def handle_function_call(event):
|
|
555
|
+
print(f"[NEXUS-Python] Received function call: {event}")
|
|
556
|
+
|
|
557
|
+
nexus.on_state_changed(lambda e: print(f"[NEXUS-Python] State changed: {e}"))
|
|
558
|
+
nexus.on_broadcast(lambda e: print(f"[NEXUS-Python] Broadcast: {e}"))
|
|
559
|
+
|
|
560
|
+
# Register a function
|
|
561
|
+
def my_function(args):
|
|
562
|
+
return {"result": "Hello from Python!"}
|
|
563
|
+
|
|
564
|
+
nexus.register_function("my_function", my_function)
|
|
565
|
+
|
|
566
|
+
# Write state
|
|
567
|
+
nexus.write_state({"counter": 0, "message": "Hello from Python"})
|
|
568
|
+
|
|
569
|
+
# Read state
|
|
570
|
+
state = nexus.read_state()
|
|
571
|
+
print(f"[NEXUS-Python] Current state: {state}")
|
|
572
|
+
|
|
573
|
+
# Publish an event
|
|
574
|
+
nexus.publish_event("test_event", {"data": "test"})
|
|
575
|
+
|
|
576
|
+
# Broadcast a message
|
|
577
|
+
nexus.broadcast("Hello from Python module!")
|
|
578
|
+
|
|
579
|
+
# Keep running
|
|
580
|
+
try:
|
|
581
|
+
while True:
|
|
582
|
+
time.sleep(1)
|
|
583
|
+
except KeyboardInterrupt:
|
|
584
|
+
print("\n[NEXUS-Python] Shutting down...")
|
qyro/cli/__init__.py
ADDED