quantum-framework 0.18.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.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,582 @@
1
+ """
2
+ Redis Message Broker Adapter
3
+
4
+ Provides a Redis-based implementation of the MessageBroker interface.
5
+
6
+ Uses:
7
+ - Redis Pub/Sub for topic-based messaging
8
+ - Redis Lists for queue-based messaging (LPUSH/BRPOP)
9
+ - JSON serialization for messages
10
+
11
+ Requires: redis package (pip install redis)
12
+ """
13
+
14
+ import threading
15
+ import json
16
+ import time
17
+ import uuid
18
+ from typing import Dict, Any, Optional, List
19
+ from dataclasses import dataclass, field
20
+
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ try:
25
+ import redis
26
+ REDIS_AVAILABLE = True
27
+ except ImportError:
28
+ REDIS_AVAILABLE = False
29
+
30
+ from message_broker import (
31
+ MessageBroker, Message, QueueInfo, MessageHandler,
32
+ MessageBrokerError, ConnectionError, PublishError,
33
+ SubscribeError, QueueError
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class RedisSubscription:
39
+ """Internal state for a Redis subscription"""
40
+ id: str
41
+ pattern: str
42
+ handler: MessageHandler
43
+ is_queue: bool = False
44
+ prefetch: int = 1
45
+ active: bool = True
46
+ thread: Optional[threading.Thread] = None
47
+
48
+
49
+ class RedisAdapter(MessageBroker):
50
+ """
51
+ Redis-based implementation of MessageBroker.
52
+
53
+ Features:
54
+ - Redis Pub/Sub for topic messaging
55
+ - Redis Lists (LPUSH/BRPOP) for queue messaging
56
+ - Pattern-based topic subscriptions
57
+ - Persistent queues (Redis persistence settings apply)
58
+
59
+ Configuration:
60
+ {
61
+ 'host': 'localhost',
62
+ 'port': 6379,
63
+ 'db': 0,
64
+ 'password': None,
65
+ 'decode_responses': True,
66
+ 'prefix': 'quantum:' # Key prefix for namespacing
67
+ }
68
+ """
69
+
70
+ def __init__(self):
71
+ if not REDIS_AVAILABLE:
72
+ raise ImportError(
73
+ "Redis adapter requires 'redis' package. "
74
+ "Install with: pip install redis"
75
+ )
76
+
77
+ self._client: Optional[redis.Redis] = None
78
+ self._pubsub: Optional[redis.client.PubSub] = None
79
+ self._connected = False
80
+ self._config: Dict[str, Any] = {}
81
+ self._prefix = 'quantum:'
82
+
83
+ self._lock = threading.RLock()
84
+ self._subscriptions: Dict[str, RedisSubscription] = {}
85
+ self._pending_acks: Dict[str, Message] = {}
86
+ self._reply_queues: Dict[str, Any] = {}
87
+
88
+ self._shutdown = threading.Event()
89
+ self._workers: List[threading.Thread] = []
90
+
91
+ def connect(self, config: Dict[str, Any] = None) -> None:
92
+ """
93
+ Connect to Redis.
94
+
95
+ Args:
96
+ config: Redis connection configuration
97
+ """
98
+ if config is None:
99
+ config = {}
100
+
101
+ self._config = config
102
+ self._prefix = config.get('prefix', 'quantum:')
103
+
104
+ try:
105
+ self._client = redis.Redis(
106
+ host=config.get('host', 'localhost'),
107
+ port=config.get('port', 6379),
108
+ db=config.get('db', 0),
109
+ password=config.get('password'),
110
+ decode_responses=True
111
+ )
112
+
113
+ # Test connection
114
+ self._client.ping()
115
+
116
+ self._pubsub = self._client.pubsub()
117
+ self._connected = True
118
+ self._shutdown.clear()
119
+
120
+ except redis.ConnectionError as e:
121
+ raise ConnectionError(f"Failed to connect to Redis: {e}")
122
+
123
+ def disconnect(self) -> None:
124
+ """Disconnect from Redis and clean up resources."""
125
+ with self._lock:
126
+ if not self._connected:
127
+ return
128
+
129
+ self._shutdown.set()
130
+
131
+ # Stop subscription threads
132
+ for sub in self._subscriptions.values():
133
+ sub.active = False
134
+ if sub.thread and sub.thread.is_alive():
135
+ sub.thread.join(timeout=1.0)
136
+
137
+ self._subscriptions.clear()
138
+ self._workers.clear()
139
+
140
+ if self._pubsub:
141
+ self._pubsub.close()
142
+ self._pubsub = None
143
+
144
+ if self._client:
145
+ self._client.close()
146
+ self._client = None
147
+
148
+ self._connected = False
149
+
150
+ def is_connected(self) -> bool:
151
+ """Check if connected to Redis."""
152
+ if not self._connected or not self._client:
153
+ return False
154
+
155
+ try:
156
+ self._client.ping()
157
+ return True
158
+ except redis.ConnectionError:
159
+ return False
160
+
161
+ def publish(self, topic: str, message: Message) -> None:
162
+ """
163
+ Publish a message to a topic using Redis Pub/Sub.
164
+
165
+ Args:
166
+ topic: Topic name
167
+ message: Message to publish
168
+ """
169
+ if not self._connected:
170
+ raise ConnectionError("Not connected to Redis")
171
+
172
+ message.topic = topic
173
+ channel = f"{self._prefix}topic:{topic}"
174
+
175
+ try:
176
+ self._client.publish(channel, message.to_json())
177
+ except redis.RedisError as e:
178
+ raise PublishError(f"Failed to publish message: {e}")
179
+
180
+ def send(self, queue_name: str, message: Message) -> None:
181
+ """
182
+ Send a message to a queue using Redis Lists.
183
+
184
+ Uses LPUSH to add message to the left of the list.
185
+
186
+ Args:
187
+ queue_name: Queue name
188
+ message: Message to send
189
+ """
190
+ if not self._connected:
191
+ raise ConnectionError("Not connected to Redis")
192
+
193
+ message.queue = queue_name
194
+ key = f"{self._prefix}queue:{queue_name}"
195
+
196
+ try:
197
+ # Store message with metadata
198
+ self._client.lpush(key, message.to_json())
199
+
200
+ # Set TTL if queue has one configured
201
+ ttl_key = f"{self._prefix}queue_ttl:{queue_name}"
202
+ ttl = self._client.get(ttl_key)
203
+ if ttl:
204
+ # Set expiry on individual message (stored separately)
205
+ msg_key = f"{self._prefix}msg:{message.id}"
206
+ self._client.setex(msg_key, int(int(ttl) / 1000), message.to_json())
207
+
208
+ except redis.RedisError as e:
209
+ raise PublishError(f"Failed to send message: {e}")
210
+
211
+ def request(self, queue_name: str, message: Message, timeout: int = 5000) -> Message:
212
+ """
213
+ Send a request and wait for a reply.
214
+
215
+ Args:
216
+ queue_name: Queue to send request to
217
+ message: Request message
218
+ timeout: Timeout in milliseconds
219
+
220
+ Returns:
221
+ Response message
222
+ """
223
+ if not self._connected:
224
+ raise ConnectionError("Not connected to Redis")
225
+
226
+ # Create unique reply queue
227
+ reply_queue = f"_reply_{uuid.uuid4().hex}"
228
+ correlation_id = str(uuid.uuid4())
229
+
230
+ message.reply_to = reply_queue
231
+ message.correlation_id = correlation_id
232
+
233
+ reply_key = f"{self._prefix}queue:{reply_queue}"
234
+
235
+ try:
236
+ # Send request
237
+ self.send(queue_name, message)
238
+
239
+ # Wait for reply using BRPOP
240
+ timeout_seconds = timeout / 1000.0
241
+ result = self._client.brpop(reply_key, timeout=timeout_seconds)
242
+
243
+ if result is None:
244
+ raise TimeoutError(f"Request timed out after {timeout}ms")
245
+
246
+ _, reply_json = result
247
+ reply = Message.from_json(reply_json)
248
+
249
+ return reply
250
+
251
+ except redis.RedisError as e:
252
+ raise PublishError(f"Request failed: {e}")
253
+ finally:
254
+ # Clean up reply queue
255
+ self._client.delete(reply_key)
256
+
257
+ def subscribe(self, topic: str, handler: MessageHandler) -> str:
258
+ """
259
+ Subscribe to a topic pattern.
260
+
261
+ Args:
262
+ topic: Topic pattern (supports * wildcards)
263
+ handler: Callback for received messages
264
+
265
+ Returns:
266
+ Subscription ID
267
+ """
268
+ if not self._connected:
269
+ raise ConnectionError("Not connected to Redis")
270
+
271
+ sub_id = f"sub_{uuid.uuid4().hex}"
272
+ pattern = f"{self._prefix}topic:{topic}"
273
+
274
+ subscription = RedisSubscription(
275
+ id=sub_id,
276
+ pattern=topic,
277
+ handler=handler,
278
+ is_queue=False
279
+ )
280
+
281
+ with self._lock:
282
+ self._subscriptions[sub_id] = subscription
283
+
284
+ # Start subscription thread
285
+ thread = threading.Thread(
286
+ target=self._pubsub_worker,
287
+ args=(subscription, pattern),
288
+ daemon=True,
289
+ name=f"Redis-Sub-{sub_id}"
290
+ )
291
+ subscription.thread = thread
292
+ thread.start()
293
+
294
+ return sub_id
295
+
296
+ def consume(self, queue_name: str, handler: MessageHandler, prefetch: int = 1) -> str:
297
+ """
298
+ Start consuming messages from a queue.
299
+
300
+ Args:
301
+ queue_name: Queue name
302
+ handler: Callback for received messages
303
+ prefetch: Number of messages to prefetch (not fully supported in Redis)
304
+
305
+ Returns:
306
+ Consumer ID
307
+ """
308
+ if not self._connected:
309
+ raise ConnectionError("Not connected to Redis")
310
+
311
+ consumer_id = f"consumer_{uuid.uuid4().hex}"
312
+
313
+ subscription = RedisSubscription(
314
+ id=consumer_id,
315
+ pattern=queue_name,
316
+ handler=handler,
317
+ is_queue=True,
318
+ prefetch=prefetch
319
+ )
320
+
321
+ with self._lock:
322
+ self._subscriptions[consumer_id] = subscription
323
+
324
+ # Start consumer thread
325
+ thread = threading.Thread(
326
+ target=self._queue_worker,
327
+ args=(subscription,),
328
+ daemon=True,
329
+ name=f"Redis-Consumer-{consumer_id}"
330
+ )
331
+ subscription.thread = thread
332
+ thread.start()
333
+
334
+ return consumer_id
335
+
336
+ def unsubscribe(self, subscription_id: str) -> None:
337
+ """
338
+ Unsubscribe or stop consuming.
339
+
340
+ Args:
341
+ subscription_id: Subscription or consumer ID
342
+ """
343
+ with self._lock:
344
+ if subscription_id in self._subscriptions:
345
+ sub = self._subscriptions[subscription_id]
346
+ sub.active = False
347
+
348
+ if sub.thread and sub.thread.is_alive():
349
+ sub.thread.join(timeout=1.0)
350
+
351
+ del self._subscriptions[subscription_id]
352
+
353
+ def ack(self, message: Message) -> None:
354
+ """
355
+ Acknowledge message processing.
356
+
357
+ For Redis, this removes the message from pending.
358
+ """
359
+ with self._lock:
360
+ self._pending_acks.pop(message.id, None)
361
+
362
+ def nack(self, message: Message, requeue: bool = True) -> None:
363
+ """
364
+ Negative acknowledge - reject a message.
365
+
366
+ Args:
367
+ message: Message to reject
368
+ requeue: If True, requeue the message
369
+ """
370
+ with self._lock:
371
+ self._pending_acks.pop(message.id, None)
372
+
373
+ if requeue and message.queue:
374
+ # Requeue by pushing back to the queue
375
+ key = f"{self._prefix}queue:{message.queue}"
376
+ try:
377
+ self._client.rpush(key, message.to_json())
378
+ except redis.RedisError:
379
+ pass
380
+
381
+ def declare_queue(
382
+ self,
383
+ name: str,
384
+ durable: bool = True,
385
+ exclusive: bool = False,
386
+ auto_delete: bool = False,
387
+ dead_letter_queue: Optional[str] = None,
388
+ ttl: Optional[int] = None
389
+ ) -> None:
390
+ """
391
+ Declare a queue.
392
+
393
+ For Redis, queues are created implicitly. This stores metadata.
394
+ """
395
+ if not self._connected:
396
+ raise ConnectionError("Not connected to Redis")
397
+
398
+ meta_key = f"{self._prefix}queue_meta:{name}"
399
+
400
+ try:
401
+ meta = {
402
+ 'durable': durable,
403
+ 'exclusive': exclusive,
404
+ 'auto_delete': auto_delete,
405
+ 'dead_letter_queue': dead_letter_queue,
406
+ 'ttl': ttl
407
+ }
408
+ self._client.hset(meta_key, mapping={
409
+ k: json.dumps(v) for k, v in meta.items()
410
+ })
411
+
412
+ if ttl:
413
+ ttl_key = f"{self._prefix}queue_ttl:{name}"
414
+ self._client.set(ttl_key, str(ttl))
415
+
416
+ except redis.RedisError as e:
417
+ raise QueueError(f"Failed to declare queue: {e}")
418
+
419
+ def purge_queue(self, name: str) -> int:
420
+ """
421
+ Remove all messages from a queue.
422
+
423
+ Args:
424
+ name: Queue name
425
+
426
+ Returns:
427
+ Number of messages purged
428
+ """
429
+ if not self._connected:
430
+ raise ConnectionError("Not connected to Redis")
431
+
432
+ key = f"{self._prefix}queue:{name}"
433
+
434
+ try:
435
+ count = self._client.llen(key)
436
+ self._client.delete(key)
437
+ return count
438
+ except redis.RedisError as e:
439
+ raise QueueError(f"Failed to purge queue: {e}")
440
+
441
+ def delete_queue(self, name: str) -> None:
442
+ """
443
+ Delete a queue and its metadata.
444
+
445
+ Args:
446
+ name: Queue name
447
+ """
448
+ if not self._connected:
449
+ raise ConnectionError("Not connected to Redis")
450
+
451
+ keys = [
452
+ f"{self._prefix}queue:{name}",
453
+ f"{self._prefix}queue_meta:{name}",
454
+ f"{self._prefix}queue_ttl:{name}"
455
+ ]
456
+
457
+ try:
458
+ self._client.delete(*keys)
459
+ except redis.RedisError as e:
460
+ raise QueueError(f"Failed to delete queue: {e}")
461
+
462
+ def get_queue_info(self, name: str) -> QueueInfo:
463
+ """
464
+ Get queue statistics.
465
+
466
+ Args:
467
+ name: Queue name
468
+
469
+ Returns:
470
+ QueueInfo with statistics
471
+ """
472
+ if not self._connected:
473
+ raise ConnectionError("Not connected to Redis")
474
+
475
+ key = f"{self._prefix}queue:{name}"
476
+ meta_key = f"{self._prefix}queue_meta:{name}"
477
+
478
+ try:
479
+ message_count = self._client.llen(key)
480
+
481
+ # Get metadata
482
+ meta = self._client.hgetall(meta_key)
483
+ durable = json.loads(meta.get('durable', 'true'))
484
+ auto_delete = json.loads(meta.get('auto_delete', 'false'))
485
+
486
+ # Count active consumers for this queue
487
+ consumer_count = sum(
488
+ 1 for s in self._subscriptions.values()
489
+ if s.is_queue and s.pattern == name and s.active
490
+ )
491
+
492
+ return QueueInfo(
493
+ name=name,
494
+ message_count=message_count,
495
+ consumer_count=consumer_count,
496
+ durable=durable,
497
+ auto_delete=auto_delete
498
+ )
499
+
500
+ except redis.RedisError as e:
501
+ raise QueueError(f"Failed to get queue info: {e}")
502
+
503
+ def _pubsub_worker(self, subscription: RedisSubscription, pattern: str) -> None:
504
+ """Background worker for Redis Pub/Sub subscriptions."""
505
+ try:
506
+ # Create a new pubsub instance for this subscription
507
+ pubsub = self._client.pubsub()
508
+
509
+ if '*' in pattern or '?' in pattern:
510
+ pubsub.psubscribe(pattern)
511
+ else:
512
+ pubsub.subscribe(pattern)
513
+
514
+ for message in pubsub.listen():
515
+ if not subscription.active or self._shutdown.is_set():
516
+ break
517
+
518
+ if message['type'] in ('message', 'pmessage'):
519
+ try:
520
+ data = message['data']
521
+ if isinstance(data, str):
522
+ msg = Message.from_json(data)
523
+ subscription.handler(msg)
524
+ except Exception as e:
525
+ print(f"[Redis] Pub/Sub handler error: {e}")
526
+
527
+ pubsub.close()
528
+
529
+ except Exception as e:
530
+ print(f"[Redis] Pub/Sub worker error: {e}")
531
+
532
+ def _queue_worker(self, subscription: RedisSubscription) -> None:
533
+ """Background worker for Redis queue consumption."""
534
+ key = f"{self._prefix}queue:{subscription.pattern}"
535
+
536
+ while subscription.active and not self._shutdown.is_set():
537
+ try:
538
+ # BRPOP with 1 second timeout for graceful shutdown
539
+ result = self._client.brpop(key, timeout=1)
540
+
541
+ if result is None:
542
+ continue
543
+
544
+ _, msg_json = result
545
+ msg = Message.from_json(msg_json)
546
+
547
+ # Track pending ack
548
+ with self._lock:
549
+ self._pending_acks[msg.id] = msg
550
+
551
+ try:
552
+ subscription.handler(msg)
553
+ except Exception as e:
554
+ print(f"[Redis] Queue handler error: {e}")
555
+ # Requeue on error
556
+ self.nack(msg, requeue=True)
557
+
558
+ except Exception as e:
559
+ if subscription.active:
560
+ print(f"[Redis] Queue worker error: {e}")
561
+ time.sleep(1)
562
+
563
+ def reply(self, original_message: Message, response: Message) -> None:
564
+ """
565
+ Send a reply to a request message.
566
+
567
+ Args:
568
+ original_message: The request message
569
+ response: The response message
570
+ """
571
+ if not original_message.reply_to:
572
+ raise PublishError("Original message has no reply_to queue")
573
+
574
+ response.correlation_id = original_message.correlation_id
575
+ response.queue = original_message.reply_to
576
+
577
+ reply_key = f"{self._prefix}queue:{original_message.reply_to}"
578
+
579
+ try:
580
+ self._client.lpush(reply_key, response.to_json())
581
+ except redis.RedisError as e:
582
+ raise PublishError(f"Failed to send reply: {e}")