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,289 @@
1
+ """
2
+ The websocket transport that q:websocket never had.
3
+
4
+ Before this, `quantum/runtime/websocket_service.py` was pure bookkeeping: it
5
+ created a connection record in state CONNECTING, nothing ever moved it to
6
+ OPEN, and send_message — which only queues when the state is OPEN — always
7
+ returned False into a list nothing drained. The tag returned a
8
+ connection-shaped dict for a connection that did not exist.
9
+
10
+ This opens real client connections with the `websockets` library, on a
11
+ background asyncio loop, and drives the state machine and the handlers the
12
+ service already defines. It is deliberately a CLIENT transport: `q:websocket
13
+ url="wss://..."` connects a Quantum app to somewhere else, which is what the
14
+ tag's url= attribute always meant.
15
+
16
+ Optional by design: if `websockets` is not installed the service keeps its
17
+ honest stub behaviour and says so, rather than failing to import.
18
+ """
19
+
20
+ import asyncio
21
+ import json
22
+ import logging
23
+ import threading
24
+ from concurrent.futures import ThreadPoolExecutor
25
+ from typing import Any, Callable, Dict, Optional
26
+
27
+ logger = logging.getLogger('quantum.websocket')
28
+
29
+ try:
30
+ import websockets as _ws
31
+ HAS_WEBSOCKETS = True
32
+ except ImportError: # pragma: no cover - environment dependent
33
+ _ws = None
34
+ HAS_WEBSOCKETS = False
35
+
36
+
37
+ class WebSocketTransport:
38
+ """Owns one background event loop and the live client connections on it."""
39
+
40
+ def __init__(self):
41
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
42
+ self._thread: Optional[threading.Thread] = None
43
+ self._sockets: Dict[str, Any] = {} # connection id -> protocol
44
+ self._lock = threading.RLock()
45
+ # q:websocket handlers are Quantum bodies: they run a component
46
+ # context and can block for a while. Running them on the event loop
47
+ # would stall every other connection, so they go to a worker thread.
48
+ #
49
+ # UM executor POR CONEXAO, com um unico worker cada.
50
+ #
51
+ # Era um executor unico com max_workers=1 para o transporte inteiro,
52
+ # justificado por "one, so message order is preserved". A ordem por
53
+ # conexao esta certa; o alcance nao estava. Com um so worker, um
54
+ # handler on-message lento de UMA conexao segurava a fila de TODAS —
55
+ # dez conexoes, e um <q:query> de 2s numa delas atrasava as outras
56
+ # nove em 2s cada. Nada em q:websocket ordena mensagens de conexoes
57
+ # diferentes entre si, entao nao ha ordem a preservar ali.
58
+ #
59
+ # Um worker por conexao mantem a ordem onde ela existe e isola as
60
+ # conexoes umas das outras.
61
+ self._dispatchers: Dict[str, ThreadPoolExecutor] = {}
62
+ self._closed = False
63
+
64
+ def _dispatcher_for(self, conn_id: str) -> Optional[ThreadPoolExecutor]:
65
+ with self._lock:
66
+ pool = self._dispatchers.get(conn_id)
67
+ if pool is not None:
68
+ # Um worker que JA existe continua aceitando trabalho mesmo
69
+ # durante o shutdown: e por ele que passa o on_close de cada
70
+ # conexao sendo fechada. Recusar aqui faria o corpo
71
+ # `on-close` do app nunca rodar num encerramento — que e
72
+ # justamente quando ele importa.
73
+ return pool
74
+ if self._closed:
75
+ return None
76
+ pool = ThreadPoolExecutor(
77
+ max_workers=1,
78
+ thread_name_prefix=f'quantum-ws-{conn_id[:8]}')
79
+ self._dispatchers[conn_id] = pool
80
+ return pool
81
+
82
+ def _fire(self, conn_id: str, fn: Callable, *args) -> None:
83
+ """Run a user handler off the event loop, never letting it escape."""
84
+ def guarded():
85
+ try:
86
+ fn(*args)
87
+ except Exception:
88
+ logger.exception("q:websocket handler raised")
89
+
90
+ pool = self._dispatcher_for(conn_id)
91
+ if pool is None:
92
+ return
93
+ try:
94
+ pool.submit(guarded)
95
+ except RuntimeError: # pool already shut down
96
+ pass
97
+
98
+ # -- loop lifecycle ----------------------------------------------------
99
+
100
+ def _ensure_loop(self) -> asyncio.AbstractEventLoop:
101
+ with self._lock:
102
+ # Um shutdown() nao pode aposentar o transporte: get_transport()
103
+ # devolve um singleton de processo, e reconectar depois de fechar
104
+ # tudo e uso normal.
105
+ self._closed = False
106
+ if self._loop and self._loop.is_running():
107
+ return self._loop
108
+ self._loop = asyncio.new_event_loop()
109
+
110
+ def run():
111
+ asyncio.set_event_loop(self._loop)
112
+ self._loop.run_forever()
113
+
114
+ self._thread = threading.Thread(
115
+ target=run, name="quantum-websocket", daemon=True)
116
+ self._thread.start()
117
+ return self._loop
118
+
119
+ def shutdown(self) -> None:
120
+ # As conexoes sao fechadas FORA do lock.
121
+ #
122
+ # Antes o laco inteiro rodava com self._lock preso. close() espera o
123
+ # `sock.close()` terminar no event loop — e o pump() daquela conexao,
124
+ # ao terminar, faz `with self._lock` no finally, na thread do loop.
125
+ # Com o lock preso aqui, o loop parava no finally, `sock.close()`
126
+ # nunca completava, e cada close() gastava os 5s inteiros do timeout
127
+ # antes de desistir. Cinco conexoes abertas = 25 segundos de shutdown.
128
+ with self._lock:
129
+ self._closed = True
130
+ conn_ids = list(self._sockets)
131
+
132
+ # Fechar PRIMEIRO, com os workers ainda vivos: o on_close de cada
133
+ # conexao roda no worker dela, e o pump, ao terminar, aposenta esse
134
+ # worker sozinho. Recolher os executores antes deixaria o corpo
135
+ # `on-close` do app sem ninguem para executa-lo.
136
+ for conn_id in conn_ids:
137
+ self.close(conn_id)
138
+
139
+ with self._lock:
140
+ if self._loop and self._loop.is_running():
141
+ self._loop.call_soon_threadsafe(self._loop.stop)
142
+ self._loop = None
143
+ self._thread = None
144
+ restantes = list(self._dispatchers.values())
145
+ self._dispatchers.clear()
146
+
147
+ # E os workers de handler tambem morrem. Ficavam vivos para sempre:
148
+ # cada reset_transport() em teste deixava mais uma thread
149
+ # 'quantum-ws-handler' parada no processo, e um app que fechasse suas
150
+ # conexoes nunca recuperava a thread.
151
+ for pool in restantes:
152
+ pool.shutdown(wait=False)
153
+
154
+ # -- connecting --------------------------------------------------------
155
+
156
+ def connect(
157
+ self,
158
+ conn_id: str,
159
+ url: str,
160
+ on_open: Callable[[], None],
161
+ on_message: Callable[[Any], None],
162
+ on_close: Callable[[], None],
163
+ on_error: Callable[[Exception], None],
164
+ timeout: float = 10.0,
165
+ ):
166
+ """Open a real connection. Returns (ok, error).
167
+
168
+ Blocks up to `timeout` so q:websocket can report a truthful state
169
+ instead of leaving the caller with a record that says CONNECTING
170
+ forever — and returns the failure reason with the answer, because
171
+ the on_error callback lands on the handler thread later and the
172
+ caller needs it now.
173
+ """
174
+ if not HAS_WEBSOCKETS:
175
+ logger.warning(
176
+ "q:websocket needs the 'websockets' package to open a real "
177
+ "connection (pip install websockets). Leaving %s unconnected.",
178
+ url
179
+ )
180
+ return False, "the 'websockets' package is not installed"
181
+
182
+ loop = self._ensure_loop()
183
+ opened = threading.Event()
184
+ result = {'ok': False, 'error': None}
185
+
186
+ async def pump():
187
+ try:
188
+ async with _ws.connect(url, open_timeout=timeout) as sock:
189
+ with self._lock:
190
+ self._sockets[conn_id] = sock
191
+ result['ok'] = True
192
+ opened.set()
193
+ self._fire(conn_id, on_open)
194
+ async for raw in sock:
195
+ # JSON when it parses, raw text otherwise — the same
196
+ # rule the send side uses.
197
+ try:
198
+ payload = json.loads(raw)
199
+ except (ValueError, TypeError):
200
+ payload = raw
201
+ self._fire(conn_id, on_message, payload)
202
+ except Exception as exc:
203
+ result['error'] = exc
204
+ opened.set()
205
+ self._fire(conn_id, on_error, exc)
206
+ finally:
207
+ with self._lock:
208
+ self._sockets.pop(conn_id, None)
209
+ self._fire(conn_id, on_close)
210
+ # O worker desta conexao acaba com ela. shutdown(wait=False)
211
+ # nao cancela o que ja foi submetido, entao o on_close acima
212
+ # ainda roda; so nao fica uma thread parada por conexao ja
213
+ # encerrada.
214
+ with self._lock:
215
+ pool = self._dispatchers.pop(conn_id, None)
216
+ if pool is not None:
217
+ pool.shutdown(wait=False)
218
+
219
+ asyncio.run_coroutine_threadsafe(pump(), loop)
220
+ if not opened.wait(timeout=timeout + 1):
221
+ logger.warning("q:websocket: connecting to %s timed out", url)
222
+ return False, f"connection to {url} timed out"
223
+
224
+ if result['error'] is not None:
225
+ logger.warning("q:websocket could not connect to %s: %s",
226
+ url, result['error'])
227
+ return False, str(result['error']) or type(result['error']).__name__
228
+ return bool(result['ok']), None
229
+
230
+ # -- sending -----------------------------------------------------------
231
+
232
+ def send(self, conn_id: str, data: Any, timeout: float = 5.0) -> bool:
233
+ """Actually transmit. Returns True only when the frame was written."""
234
+ with self._lock:
235
+ sock = self._sockets.get(conn_id)
236
+ if sock is None or self._loop is None:
237
+ return False
238
+
239
+ payload = data if isinstance(data, (str, bytes)) else json.dumps(
240
+ data, default=str)
241
+ fut = asyncio.run_coroutine_threadsafe(sock.send(payload), self._loop)
242
+ try:
243
+ fut.result(timeout=timeout)
244
+ return True
245
+ except Exception as exc:
246
+ logger.warning("q:websocket send failed on %s: %s", conn_id, exc)
247
+ return False
248
+
249
+ # -- closing -----------------------------------------------------------
250
+
251
+ def close(self, conn_id: str, timeout: float = 5.0) -> bool:
252
+ with self._lock:
253
+ sock = self._sockets.get(conn_id)
254
+ if sock is None or self._loop is None:
255
+ return False
256
+ fut = asyncio.run_coroutine_threadsafe(sock.close(), self._loop)
257
+ try:
258
+ fut.result(timeout=timeout)
259
+ except Exception:
260
+ pass
261
+ with self._lock:
262
+ self._sockets.pop(conn_id, None)
263
+ return True
264
+
265
+ def is_open(self, conn_id: str) -> bool:
266
+ with self._lock:
267
+ return conn_id in self._sockets
268
+
269
+
270
+ _transport: Optional[WebSocketTransport] = None
271
+ _transport_lock = threading.Lock()
272
+
273
+
274
+ def get_transport() -> WebSocketTransport:
275
+ """The process-wide transport (one event loop for every connection)."""
276
+ global _transport
277
+ with _transport_lock:
278
+ if _transport is None:
279
+ _transport = WebSocketTransport()
280
+ return _transport
281
+
282
+
283
+ def reset_transport() -> None:
284
+ """Drop the transport, closing whatever it holds (tests)."""
285
+ global _transport
286
+ with _transport_lock:
287
+ if _transport is not None:
288
+ _transport.shutdown()
289
+ _transport = None
@@ -0,0 +1,101 @@
1
+ """
2
+ Quantum WSGI Entry Point
3
+
4
+ Factory for creating Flask app instances for Gunicorn deployment.
5
+ Loads configuration from environment or config file.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Ensure src directory is in path
13
+
14
+ from quantum.runtime.web_server import QuantumWebServer
15
+
16
+
17
+ def _run_app_startup():
18
+ """
19
+ Run app-specific startup.py if present.
20
+
21
+ This allows apps to initialize databases, apply runtime patches,
22
+ and perform other setup before the web server starts.
23
+ The startup module should define a run() function.
24
+
25
+ Note that this executes a Python file from `/app` or from the CURRENT
26
+ DIRECTORY, and that `app = create_app()` at the bottom of this module
27
+ runs it at IMPORT time. Importing this module therefore executes the
28
+ working directory's startup.py. That is the documented Docker convention
29
+ and it is kept, but it is loud now: silently executing a file because it
30
+ happens to sit in the directory you launched from is the kind of thing
31
+ that should say so.
32
+ """
33
+ startup_path = Path('/app/startup.py')
34
+ if not startup_path.exists():
35
+ # Also check current working directory
36
+ startup_path = Path('startup.py')
37
+ if startup_path.exists():
38
+ print(f"[WSGI] executing {startup_path.resolve()}", flush=True)
39
+ import importlib.util
40
+ spec = importlib.util.spec_from_file_location('app_startup', str(startup_path))
41
+ mod = importlib.util.module_from_spec(spec)
42
+ spec.loader.exec_module(mod)
43
+ if hasattr(mod, 'run'):
44
+ mod.run()
45
+ print("[WSGI] App startup.py executed", flush=True)
46
+
47
+
48
+ def create_app(config_path: str = None) -> 'Flask':
49
+ """
50
+ Create and configure the Quantum Flask application.
51
+
52
+ This is the factory function used by Gunicorn.
53
+
54
+ Args:
55
+ config_path: Path to quantum.config.yaml. If not provided,
56
+ uses QUANTUM_CONFIG env var or default path.
57
+
58
+ Returns:
59
+ Configured Flask application instance
60
+ """
61
+ # Run app startup hooks before creating the server
62
+ _run_app_startup()
63
+
64
+ # Determine config path
65
+ if config_path is None:
66
+ config_path = os.environ.get('QUANTUM_CONFIG', '/app/quantum.config.yaml')
67
+
68
+ # Check for app-specific config first
69
+ if os.path.exists(config_path):
70
+ server = QuantumWebServer(config_path)
71
+ else:
72
+ # Fall back to default config
73
+ server = QuantumWebServer()
74
+
75
+ return server.app
76
+
77
+
78
+ # Create app instance for Gunicorn
79
+ #
80
+ # Usage: gunicorn "quantum.runtime.wsgi:app"
81
+ #
82
+ # This said `src.runtime.wsgi:app` — the layout that stopped existing when
83
+ # `src/` became the `quantum/` package. The command in the one file whose
84
+ # entire purpose is being that command did not import. (web_server.py's
85
+ # docstring had the same defect; this file was missed.)
86
+ app = create_app()
87
+
88
+
89
+ # Also support: gunicorn "quantum.runtime.wsgi:create_app()"
90
+ def get_app():
91
+ """Alternative factory for Gunicorn."""
92
+ return create_app()
93
+
94
+
95
+ if __name__ == '__main__':
96
+ # For local testing
97
+ app.run(
98
+ host=os.environ.get('QUANTUM_HOST', '0.0.0.0'),
99
+ port=int(os.environ.get('QUANTUM_PORT', 8080)),
100
+ debug=os.environ.get('QUANTUM_DEBUG', 'false').lower() == 'true'
101
+ )
@@ -0,0 +1 @@
1
+ # Utils - placeholder para futuras utilidades
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: quantum-framework
3
+ Version: 0.9.0
4
+ Summary: Full-stack declarative framework for building web, desktop, and mobile applications
5
+ Author-email: Daniel Gregorio <danielgregorio2611@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://quantum.sargas.cloud
8
+ Project-URL: Documentation, https://quantum.sargas.cloud
9
+ Project-URL: Repository, https://github.com/danielgregorio/quantum
10
+ Keywords: framework,declarative,xml,web,desktop,mobile
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Flask
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: flask>=3.0.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: sqlparse>=0.4.4
22
+ Requires-Dist: gunicorn>=21.0.0
23
+ Requires-Dist: textual>=0.80.0
24
+ Requires-Dist: requests>=2.31.0
25
+ Requires-Dist: bcrypt>=4.1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
29
+ Requires-Dist: pytest-mock>=3.11.1; extra == "dev"
30
+ Requires-Dist: black>=23.0.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
32
+ Provides-Extra: db
33
+ Requires-Dist: psycopg2-binary>=2.9.9; extra == "db"
34
+ Requires-Dist: pymysql>=1.1.0; extra == "db"
35
+ Provides-Extra: rag
36
+ Requires-Dist: chromadb>=0.5.0; extra == "rag"
37
+ Provides-Extra: jobs
38
+ Requires-Dist: apscheduler>=3.10.0; extra == "jobs"
39
+ Provides-Extra: websocket
40
+ Requires-Dist: websockets>=12.0; extra == "websocket"
41
+ Dynamic: license-file
42
+
43
+ # Quantum
44
+
45
+ [![CI](https://github.com/danielgregorio/quantum/actions/workflows/ci.yml/badge.svg)](https://github.com/danielgregorio/quantum/actions/workflows/ci.yml)
46
+ [![Docker](https://github.com/danielgregorio/quantum/actions/workflows/docker.yml/badge.svg)](https://github.com/danielgregorio/quantum/actions/workflows/docker.yml)
47
+ [![Docs](https://img.shields.io/badge/docs-github.io-blue)](https://danielgregorio.github.io/quantum/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
49
+
50
+ > **Declarative web apps in XML, with AI and RAG built into the language.
51
+ > No build chain, no JavaScript, no frontend framework.**
52
+
53
+ Quantum is a full-stack framework whose language is markup. State, database queries,
54
+ forms, LLM calls and retrieval-augmented generation are all tags — not libraries you
55
+ wire together. It takes its philosophy from ColdFusion and Adobe Flex: the markup *is*
56
+ the app.
57
+
58
+ > ⚠️ **Pre-1.0, and honest about it.** See [Stability](#stability) — the table there
59
+ > reflects what has actually been executed end-to-end, not what is aspirational.
60
+
61
+ ---
62
+
63
+ ## The part that isn't like the others
64
+
65
+ Retrieval-augmented generation, as a language construct:
66
+
67
+ ```xml
68
+ <q:component name="DocsBot">
69
+ <q:knowledge name="docs" model="phi3" embedModel="nomic-embed-text">
70
+ <q:source type="directory" path="./docs/" pattern="*.md" />
71
+ </q:knowledge>
72
+
73
+ <q:query name="answer" datasource="knowledge:docs" mode="rag">
74
+ SELECT answer FROM chunks WHERE content SIMILAR TO :question
75
+ <q:param name="question" value="{form.question}" type="string" />
76
+ </q:query>
77
+
78
+ <p>{answer.answer}</p>
79
+ </q:component>
80
+ ```
81
+
82
+ That indexes a directory, embeds it, stores the vectors, retrieves the relevant chunks
83
+ and asks the model — in the markup. There is no Python file behind it.
84
+
85
+ An agent with its own tools, same idea:
86
+
87
+ ```xml
88
+ <q:agent name="calc" model="phi3" max_iterations="4">
89
+ <q:instruction>Use the add tool, then answer.</q:instruction>
90
+
91
+ <q:tool name="add" description="Add two numbers">
92
+ <q:param name="a" type="number" required="true" />
93
+ <q:param name="b" type="number" required="true" />
94
+ <q:function name="doAdd">
95
+ <q:set name="sum" value="{a + b}" type="number" />
96
+ <q:return value="{sum}" />
97
+ </q:function>
98
+ </q:tool>
99
+
100
+ <q:execute task="What is 17 plus 25?" />
101
+ </q:agent>
102
+ ```
103
+
104
+ The tool body is Quantum, not Python. The reasoning loop, the tool call and the type
105
+ coercion of the model's arguments are the runtime's job.
106
+
107
+ ---
108
+
109
+ ## The rest of the language
110
+
111
+ ```xml
112
+ <q:component name="Orders">
113
+ <q:query name="orders" datasource="db">
114
+ SELECT id, customer, total FROM orders WHERE total > :min
115
+ <q:param name="min" value="100" type="decimal" />
116
+ </q:query>
117
+
118
+ <table>
119
+ <q:loop query="orders">
120
+ <tr><td>{orders.customer}</td><td>{orders.total}</td></tr>
121
+ </q:loop>
122
+ </table>
123
+ </q:component>
124
+ ```
125
+
126
+ `q:query` refuses to run SQL with an undeclared `:param` — parameterised queries are
127
+ enforced by the parser, not by discipline.
128
+
129
+ Also core: `q:set` with `session.` / `application.` / `request.` scopes, `q:if`,
130
+ `q:function`, `q:action` for form handling, `q:data` for CSV/JSON/XML import,
131
+ `q:import` / `q:slot` for composition.
132
+
133
+ ---
134
+
135
+ ## Quick start
136
+
137
+ **Requirements:** Python 3.11+ and `pip`.
138
+
139
+ ```bash
140
+ git clone https://github.com/danielgregorio/quantum.git
141
+ cd quantum
142
+ pip install -e ".[dev]"
143
+
144
+ quantum run examples/hello.q
145
+ ```
146
+
147
+ ```
148
+ [EXEC] Executing component: HelloWorld
149
+ [SUCCESS] Result: Hello World!
150
+ ```
151
+
152
+ For the AI examples you also need an [Ollama](https://ollama.com) server and the RAG
153
+ extra:
154
+
155
+ ```bash
156
+ pip install -e ".[rag]"
157
+ ollama pull phi3 && ollama pull nomic-embed-text
158
+ export QUANTUM_LLM_BASE_URL=http://localhost:11434
159
+ ```
160
+
161
+ Declare datasources in `quantum.config.yaml` and `q:query` works with nothing else
162
+ running:
163
+
164
+ ```yaml
165
+ datasources:
166
+ db:
167
+ driver: sqlite
168
+ database: ./data/app.db
169
+ ```
170
+
171
+ ### CLI
172
+
173
+ | Command | What it does |
174
+ |---------|--------------|
175
+ | `run <file.q>` | Execute a component, app, or API |
176
+ | `start` | Start the web server |
177
+ | `deploy <dir>` | Deploy an application |
178
+ | `pkg` · `jobs` · `mq` · `migrate` | Packages, jobs, message queues, migrations |
179
+
180
+ ---
181
+
182
+ ## Documentation
183
+
184
+ Full docs (VitePress) at **[danielgregorio.github.io/quantum](https://danielgregorio.github.io/quantum/)** and in
185
+ [`docs/`](docs/):
186
+
187
+ - [Getting Started](docs/guide/getting-started.md) · [Installation](docs/guide/installation.md) · [Quick Start](docs/guide/quick-start.md)
188
+ - [Components](docs/guide/components.md) · [State](docs/guide/state-management.md) · [Loops](docs/guide/loops.md) · [Conditionals](docs/guide/conditionals.md)
189
+ - [Queries](docs/guide/query.md) · [Functions](docs/guide/functions.md) · [Data fetching](docs/guide/data-fetching.md)
190
+
191
+ ---
192
+
193
+ ## Stability
194
+
195
+ Support levels are defined in **[SUPPORT_TIERS.md](SUPPORT_TIERS.md)**. Short version:
196
+
197
+ | Surface | Status |
198
+ |---------|--------|
199
+ | Core language — `q:component`, `q:set`, `q:if`, `q:loop`, `q:function`, `q:query`, `q:action`, `q:invoke`, `q:data` | **Stable** |
200
+ | AI — `q:llm`, `q:knowledge`, `q:agent` | **Beta** — validated end-to-end against a live Ollama server |
201
+ | `q:team` (multi-agent handoff) | Beta, less exercised |
202
+ | Jobs, messaging, websockets, mail, file uploads, `ui:*`, terminal target | **Experimental** — they run, but no API stability promise |
203
+ | Python scripting (`q:python`, `q:pyclass`, `q:pyimport`) | Experimental, and a full-trust escape hatch — see [SECURITY.md](SECURITY.md) |
204
+
205
+ A functional audit in 2026-09 found that several of these surfaces had never been run
206
+ end-to-end despite being documented as complete. The findings and the fixes are in
207
+ [`FULL_AUDIT_2026-09.md`](FULL_AUDIT_2026-09.md) and
208
+ [`AUDIT_FIX_PLAN.md`](AUDIT_FIX_PLAN.md). Feature status is now verified by execution
209
+ rather than asserted by hand.
210
+
211
+ Pre-1.0: APIs may change between minor versions.
212
+
213
+ ---
214
+
215
+ ## Project layout
216
+
217
+ ```
218
+ quantum/
219
+ ├── quantum/
220
+ │ ├── core/ # Parser & AST (registry-based, modular)
221
+ │ ├── runtime/ # Execution engine, web server, renderer
222
+ │ └── cli/ # Command-line entry point
223
+ ├── examples/ # runnable .q examples
224
+ ├── tests/ # pytest suite (~2.4k tests)
225
+ ├── scripts/ # dev tools
226
+ └── docs/ # VitePress documentation
227
+ ```
228
+
229
+ Adding a tag is one parser + one executor + a registry entry — see
230
+ [CONTRIBUTING.md](CONTRIBUTING.md).
231
+
232
+ ---
233
+
234
+ ## Contributing
235
+
236
+ Read **[CONTRIBUTING.md](CONTRIBUTING.md)** for dev setup and how the modular
237
+ parser/executor architecture works. By participating you agree to the
238
+ [Code of Conduct](CODE_OF_CONDUCT.md).
239
+
240
+ Found a security issue? Follow [SECURITY.md](SECURITY.md) — **do not** open a public issue.
241
+
242
+ ## License
243
+
244
+ MIT — see [LICENSE](LICENSE).