quantum-framework 0.9.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 (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -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 +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,571 @@
1
+ """
2
+ Message Queue Service
3
+
4
+ High-level service for Quantum message queue operations.
5
+ Wraps the message broker abstraction for use in the ComponentRuntime.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ import threading
11
+ import json
12
+ from typing import Dict, Any, Optional, List, Callable
13
+ from dataclasses import dataclass
14
+
15
+ from quantum.runtime.message_broker import Message, QueueInfo, MessageBrokerError
16
+ from quantum.runtime.adapters import get_adapter, MemoryAdapter
17
+
18
+ logger = logging.getLogger('quantum.messaging')
19
+
20
+
21
+ class MessageQueueError(Exception):
22
+ """Raised when message queue operation fails"""
23
+ pass
24
+
25
+
26
+ @dataclass
27
+ class MessageResult:
28
+ """Result of a message operation"""
29
+ success: bool
30
+ message_id: Optional[str] = None
31
+ error: Optional[str] = None
32
+ data: Optional[Any] = None
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ return {
36
+ 'success': self.success,
37
+ 'message_id': self.message_id,
38
+ 'error': self.error,
39
+ 'data': self.data
40
+ }
41
+
42
+
43
+ class MessageQueueService:
44
+ """
45
+ Service for message queue operations in Quantum components.
46
+
47
+ Provides a high-level interface for:
48
+ - Publishing messages to topics
49
+ - Sending messages to queues
50
+ - Request/reply pattern
51
+ - Queue management
52
+ - Subscriptions
53
+
54
+ Configuration (environment variables):
55
+ MESSAGE_BROKER_TYPE: 'memory', 'sqlite', 'redis' or 'rabbitmq'
56
+ (default: memory — in ONE process only; nothing published
57
+ crosses a process boundary or survives a restart. 'sqlite'
58
+ is the durable option that needs no server.)
59
+ QUANTUM_MQ_PATH: sqlite database file (default: quantum_messages.db)
60
+ REDIS_HOST: Redis host (default: localhost)
61
+ REDIS_PORT: Redis port (default: 6379)
62
+ RABBITMQ_HOST: RabbitMQ host (default: localhost)
63
+ RABBITMQ_PORT: RabbitMQ port (default: 5672)
64
+ RABBITMQ_USER: RabbitMQ username (default: guest)
65
+ RABBITMQ_PASSWORD: RabbitMQ password (default: guest)
66
+ """
67
+
68
+ def __init__(self, config: Dict[str, Any] = None):
69
+ """
70
+ Initialize the message queue service.
71
+
72
+ Args:
73
+ config: Optional configuration (overrides environment)
74
+ """
75
+ self._config = config or {}
76
+ self._broker = None
77
+ self._subscriptions: Dict[str, str] = {} # name -> subscription_id
78
+ self._handlers: Dict[str, Callable] = {} # name -> handler function
79
+ # A mensagem em processamento, POR THREAD.
80
+ #
81
+ # Era um atributo de instancia unico, escrito pelo callback de TODA
82
+ # assinatura — e cada assinatura roda na propria thread de polling.
83
+ # Com duas assinaturas ativas, o `ack()` chamado dentro do handler A
84
+ # confirmava a mensagem que o handler B tinha acabado de reivindicar:
85
+ # a de A voltava para a fila e a de B sumia sem ter sido processada.
86
+ # Quanto mais lento o handler, maior a janela.
87
+ self._local = threading.local()
88
+
89
+ def _get_broker_type(self) -> str:
90
+ """Get broker type from config or environment.
91
+
92
+ O padrao e `sqlite`, nao `memory`. O broker em memoria vive dentro de
93
+ UM interpretador: uma publicacao neste processo e invisivel para
94
+ qualquer outro, e tudo enfileirado morre com ele — sob gunicorn, uma
95
+ fila privada por worker. Isso e perda silenciosa de dados como
96
+ default.
97
+
98
+ sqlite e o unico ponto do espectro que e duravel E sem servidor. Nao
99
+ adiciona dependencia (esta na stdlib) e nao adiciona processo; o
100
+ arquivo fica ao lado de `quantum_jobs.db`, que a fila de jobs ja usa
101
+ do mesmo jeito.
102
+
103
+ `MESSAGE_BROKER_TYPE=memory` volta ao anterior — legitimo para teste e
104
+ para um app de processo unico que nao quer arquivo nenhum.
105
+ """
106
+ return self._config.get(
107
+ 'broker_type',
108
+ os.getenv('MESSAGE_BROKER_TYPE', 'sqlite')
109
+ )
110
+
111
+ def _get_broker_config(self) -> Dict[str, Any]:
112
+ """Build broker configuration from environment."""
113
+ broker_type = self._get_broker_type()
114
+
115
+ if broker_type == 'sqlite':
116
+ return {
117
+ 'path': os.getenv('QUANTUM_MQ_PATH', 'quantum_messages.db'),
118
+ }
119
+ elif broker_type == 'redis':
120
+ return {
121
+ 'host': os.getenv('REDIS_HOST', 'localhost'),
122
+ 'port': int(os.getenv('REDIS_PORT', '6379')),
123
+ 'db': int(os.getenv('REDIS_DB', '0')),
124
+ 'password': os.getenv('REDIS_PASSWORD'),
125
+ 'prefix': os.getenv('REDIS_PREFIX', 'quantum:')
126
+ }
127
+ elif broker_type == 'rabbitmq':
128
+ return {
129
+ 'host': os.getenv('RABBITMQ_HOST', 'localhost'),
130
+ 'port': int(os.getenv('RABBITMQ_PORT', '5672')),
131
+ 'virtual_host': os.getenv('RABBITMQ_VHOST', '/'),
132
+ 'username': os.getenv('RABBITMQ_USER', 'guest'),
133
+ 'password': os.getenv('RABBITMQ_PASSWORD', 'guest'),
134
+ 'exchange': os.getenv('RABBITMQ_EXCHANGE', 'quantum')
135
+ }
136
+ else:
137
+ return {}
138
+
139
+ def connect(self) -> None:
140
+ """Connect to the message broker."""
141
+ if self._broker is not None:
142
+ return
143
+
144
+ broker_type = self._get_broker_type()
145
+ broker_config = self._get_broker_config()
146
+
147
+ try:
148
+ self._broker = get_adapter(broker_type)
149
+ self._broker.connect(broker_config)
150
+ except ImportError as e:
151
+ # Fall back to memory adapter if required package not available
152
+ print(f"[MessageQueue] {e}. Falling back to in-memory broker.")
153
+ self._broker = MemoryAdapter()
154
+ self._broker.connect({})
155
+ broker_type = 'memory'
156
+ except Exception as e:
157
+ raise MessageQueueError(f"Failed to connect to message broker: {e}")
158
+
159
+ if broker_type == 'memory':
160
+ # The in-memory broker lives inside ONE interpreter. A publish in
161
+ # this process is invisible to any other, and everything queued is
162
+ # gone when it exits — under gunicorn that means one private queue
163
+ # per worker. It is the right default for a single-process app and
164
+ # a silent data loss anywhere else, so say which one this is.
165
+ logger.warning(
166
+ "q:message is using the in-memory broker: messages do not "
167
+ "cross processes and do not survive a restart. Set "
168
+ "MESSAGE_BROKER_TYPE=sqlite (with QUANTUM_MQ_PATH) for a "
169
+ "durable one, or redis/rabbitmq."
170
+ )
171
+
172
+ def disconnect(self) -> None:
173
+ """Disconnect from the message broker."""
174
+ if self._broker:
175
+ self._broker.disconnect()
176
+ self._broker = None
177
+ self._subscriptions.clear()
178
+
179
+ def _ensure_connected(self) -> None:
180
+ """Ensure broker is connected."""
181
+ if self._broker is None or not self._broker.is_connected():
182
+ self.connect()
183
+
184
+ def publish(
185
+ self,
186
+ topic: str,
187
+ body: Any,
188
+ headers: Dict[str, str] = None
189
+ ) -> MessageResult:
190
+ """
191
+ Publish a message to a topic.
192
+
193
+ Args:
194
+ topic: Topic name
195
+ body: Message body (will be JSON serialized if not string)
196
+ headers: Optional message headers
197
+
198
+ Returns:
199
+ MessageResult with operation status
200
+ """
201
+ self._ensure_connected()
202
+
203
+ try:
204
+ # Serialize body if needed
205
+ if not isinstance(body, str):
206
+ body = json.dumps(body)
207
+
208
+ message = Message(
209
+ topic=topic,
210
+ body=body,
211
+ headers=headers or {}
212
+ )
213
+
214
+ self._broker.publish(topic, message)
215
+
216
+ return MessageResult(
217
+ success=True,
218
+ message_id=message.id
219
+ )
220
+
221
+ except Exception as e:
222
+ return MessageResult(
223
+ success=False,
224
+ error=str(e)
225
+ )
226
+
227
+ def send(
228
+ self,
229
+ queue: str,
230
+ body: Any,
231
+ headers: Dict[str, str] = None
232
+ ) -> MessageResult:
233
+ """
234
+ Send a message to a queue.
235
+
236
+ Args:
237
+ queue: Queue name
238
+ body: Message body
239
+ headers: Optional message headers
240
+
241
+ Returns:
242
+ MessageResult with operation status
243
+ """
244
+ self._ensure_connected()
245
+
246
+ try:
247
+ if not isinstance(body, str):
248
+ body = json.dumps(body)
249
+
250
+ message = Message(
251
+ queue=queue,
252
+ body=body,
253
+ headers=headers or {}
254
+ )
255
+
256
+ self._broker.send(queue, message)
257
+
258
+ return MessageResult(
259
+ success=True,
260
+ message_id=message.id
261
+ )
262
+
263
+ except Exception as e:
264
+ return MessageResult(
265
+ success=False,
266
+ error=str(e)
267
+ )
268
+
269
+ def request(
270
+ self,
271
+ queue: str,
272
+ body: Any,
273
+ headers: Dict[str, str] = None,
274
+ timeout: int = 5000
275
+ ) -> MessageResult:
276
+ """
277
+ Send a request and wait for a reply.
278
+
279
+ Args:
280
+ queue: Queue name
281
+ body: Request body
282
+ headers: Optional headers
283
+ timeout: Timeout in milliseconds
284
+
285
+ Returns:
286
+ MessageResult with response data
287
+ """
288
+ self._ensure_connected()
289
+
290
+ try:
291
+ if not isinstance(body, str):
292
+ body = json.dumps(body)
293
+
294
+ message = Message(
295
+ queue=queue,
296
+ body=body,
297
+ headers=headers or {}
298
+ )
299
+
300
+ response = self._broker.request(queue, message, timeout)
301
+
302
+ # Parse response body
303
+ response_body = response.body
304
+ if isinstance(response_body, str):
305
+ try:
306
+ response_body = json.loads(response_body)
307
+ except json.JSONDecodeError:
308
+ pass
309
+
310
+ return MessageResult(
311
+ success=True,
312
+ message_id=response.id,
313
+ data=response_body
314
+ )
315
+
316
+ except TimeoutError as e:
317
+ return MessageResult(
318
+ success=False,
319
+ error=f"Request timed out: {e}"
320
+ )
321
+ except Exception as e:
322
+ return MessageResult(
323
+ success=False,
324
+ error=str(e)
325
+ )
326
+
327
+ def subscribe(
328
+ self,
329
+ name: str,
330
+ topic: str = None,
331
+ topics: str = None,
332
+ queue: str = None,
333
+ handler: Callable = None,
334
+ ack: str = 'auto',
335
+ prefetch: int = 1
336
+ ) -> str:
337
+ """
338
+ Subscribe to a topic or start consuming from a queue.
339
+
340
+ Args:
341
+ name: Subscription name
342
+ topic: Single topic pattern
343
+ topics: Comma-separated topic patterns
344
+ queue: Queue name (for consumption)
345
+ handler: Message handler function
346
+ ack: Ack mode ('auto' or 'manual')
347
+ prefetch: Number of messages to prefetch
348
+
349
+ Returns:
350
+ Subscription ID
351
+ """
352
+ self._ensure_connected()
353
+
354
+ # Store handler for this subscription
355
+ self._handlers[name] = handler
356
+
357
+ def message_callback(msg: Message):
358
+ """Internal callback that wraps the user handler."""
359
+ self._local.current_message = msg
360
+
361
+ # Parse message body
362
+ body = msg.body
363
+ if isinstance(body, str):
364
+ try:
365
+ body = json.loads(body)
366
+ except json.JSONDecodeError:
367
+ pass
368
+
369
+ # Create message context for handler
370
+ message_context = {
371
+ 'id': msg.id,
372
+ 'topic': msg.topic,
373
+ 'queue': msg.queue,
374
+ 'body': body,
375
+ 'headers': msg.headers,
376
+ 'timestamp': msg.timestamp
377
+ }
378
+
379
+ try:
380
+ if handler:
381
+ handler(message_context)
382
+
383
+ # Auto-ack if configured
384
+ if ack == 'auto':
385
+ self._broker.ack(msg)
386
+
387
+ except Exception as e:
388
+ print(f"[MessageQueue] Handler error: {e}")
389
+ if ack == 'manual':
390
+ self._broker.nack(msg, requeue=True)
391
+ finally:
392
+ self._local.current_message = None
393
+
394
+ # Subscribe to topic(s) or consume from queue
395
+ if queue:
396
+ sub_id = self._broker.consume(queue, message_callback, prefetch)
397
+ elif topic:
398
+ sub_id = self._broker.subscribe(topic, message_callback)
399
+ elif topics:
400
+ # Subscribe to multiple topics
401
+ topic_list = [t.strip() for t in topics.split(',')]
402
+ sub_ids = []
403
+ for t in topic_list:
404
+ sid = self._broker.subscribe(t, message_callback)
405
+ sub_ids.append(sid)
406
+ sub_id = ','.join(sub_ids)
407
+ else:
408
+ raise MessageQueueError("Subscribe requires 'topic', 'topics', or 'queue'")
409
+
410
+ self._subscriptions[name] = sub_id
411
+ return sub_id
412
+
413
+ def unsubscribe(self, name: str) -> None:
414
+ """
415
+ Unsubscribe from a topic or stop consuming.
416
+
417
+ Args:
418
+ name: Subscription name
419
+ """
420
+ if name in self._subscriptions:
421
+ sub_id = self._subscriptions[name]
422
+
423
+ # Handle multiple subscription IDs
424
+ for sid in sub_id.split(','):
425
+ self._broker.unsubscribe(sid.strip())
426
+
427
+ del self._subscriptions[name]
428
+
429
+ if name in self._handlers:
430
+ del self._handlers[name]
431
+
432
+ @property
433
+ def _current_message(self) -> Optional[Message]:
434
+ """A mensagem desta thread. Mantido como propriedade porque o nome
435
+ e usado por testes e por codigo que le o estado do servico."""
436
+ return getattr(self._local, 'current_message', None)
437
+
438
+ def ack(self) -> None:
439
+ """Acknowledge the current message (for manual ack mode)."""
440
+ mensagem = self._current_message
441
+ if mensagem and self._broker:
442
+ self._broker.ack(mensagem)
443
+
444
+ def nack(self, requeue: bool = True) -> None:
445
+ """
446
+ Negative acknowledge the current message.
447
+
448
+ Args:
449
+ requeue: If True, requeue the message
450
+ """
451
+ mensagem = self._current_message
452
+ if mensagem and self._broker:
453
+ self._broker.nack(mensagem, requeue)
454
+
455
+ def declare_queue(
456
+ self,
457
+ name: str,
458
+ durable: bool = True,
459
+ exclusive: bool = False,
460
+ auto_delete: bool = False,
461
+ dead_letter_queue: str = None,
462
+ ttl: int = None
463
+ ) -> MessageResult:
464
+ """
465
+ Declare a queue.
466
+
467
+ Args:
468
+ name: Queue name
469
+ durable: Survive broker restart
470
+ exclusive: Only this connection can access
471
+ auto_delete: Delete when last consumer disconnects
472
+ dead_letter_queue: Queue for rejected messages
473
+ ttl: Message TTL in milliseconds
474
+
475
+ Returns:
476
+ MessageResult with operation status
477
+ """
478
+ self._ensure_connected()
479
+
480
+ try:
481
+ self._broker.declare_queue(
482
+ name=name,
483
+ durable=durable,
484
+ exclusive=exclusive,
485
+ auto_delete=auto_delete,
486
+ dead_letter_queue=dead_letter_queue,
487
+ ttl=ttl
488
+ )
489
+
490
+ return MessageResult(success=True)
491
+
492
+ except Exception as e:
493
+ return MessageResult(
494
+ success=False,
495
+ error=str(e)
496
+ )
497
+
498
+ def purge_queue(self, name: str) -> MessageResult:
499
+ """
500
+ Remove all messages from a queue.
501
+
502
+ Args:
503
+ name: Queue name
504
+
505
+ Returns:
506
+ MessageResult with count of purged messages
507
+ """
508
+ self._ensure_connected()
509
+
510
+ try:
511
+ count = self._broker.purge_queue(name)
512
+ return MessageResult(
513
+ success=True,
514
+ data={'purged_count': count}
515
+ )
516
+ except Exception as e:
517
+ return MessageResult(
518
+ success=False,
519
+ error=str(e)
520
+ )
521
+
522
+ def delete_queue(self, name: str) -> MessageResult:
523
+ """
524
+ Delete a queue.
525
+
526
+ Args:
527
+ name: Queue name
528
+
529
+ Returns:
530
+ MessageResult with operation status
531
+ """
532
+ self._ensure_connected()
533
+
534
+ try:
535
+ self._broker.delete_queue(name)
536
+ return MessageResult(success=True)
537
+ except Exception as e:
538
+ return MessageResult(
539
+ success=False,
540
+ error=str(e)
541
+ )
542
+
543
+ def get_queue_info(self, name: str) -> MessageResult:
544
+ """
545
+ Get queue information.
546
+
547
+ Args:
548
+ name: Queue name
549
+
550
+ Returns:
551
+ MessageResult with queue info
552
+ """
553
+ self._ensure_connected()
554
+
555
+ try:
556
+ info = self._broker.get_queue_info(name)
557
+ return MessageResult(
558
+ success=True,
559
+ data={
560
+ 'name': info.name,
561
+ 'message_count': info.message_count,
562
+ 'consumer_count': info.consumer_count,
563
+ 'durable': info.durable,
564
+ 'auto_delete': info.auto_delete
565
+ }
566
+ )
567
+ except Exception as e:
568
+ return MessageResult(
569
+ success=False,
570
+ error=str(e)
571
+ )