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,249 @@
1
+ """
2
+ Python Executor - Execute q:python statements
3
+
4
+ Handles embedded Python code execution.
5
+ """
6
+
7
+ import logging
8
+ import textwrap
9
+ from typing import Any, List, Dict, Type
10
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
11
+ from quantum.core.ast_nodes import PythonNode
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def normalise_python_source(raw: str) -> str:
17
+ """Make XML-indented Python compile.
18
+
19
+ The code inside <q:python> is indented to match the surrounding markup,
20
+ which is what anyone writing XML does:
21
+
22
+ <q:python>
23
+ total = 0
24
+ for i in range(5):
25
+ total += i
26
+ </q:python>
27
+
28
+ This was passed through `.strip()`, which removes the leading whitespace
29
+ of the string — so the FIRST line lost its indentation and every other
30
+ line kept it. Python then said "unexpected indent (<string>, line 2)".
31
+ Every multi-line q:python block in the documentation fails that way.
32
+
33
+ Strategy: try the candidate forms in order and take the first that
34
+ compiles. The current behaviour is tried first, so nothing that worked
35
+ before changes; dedent covers the ordinary case; and dedenting the tail
36
+ separately covers code that starts on the tag's own line.
37
+ """
38
+ text = (raw or "").replace("\r\n", "\n").replace("\r", "\n")
39
+ if not text.strip():
40
+ return ""
41
+
42
+ candidates = [text.strip(), textwrap.dedent(text).strip("\n")]
43
+
44
+ lines = text.split("\n")
45
+ if lines and lines[0].strip() and len(lines) > 1:
46
+ tail = textwrap.dedent("\n".join(lines[1:]))
47
+ candidates.append((lines[0].strip() + "\n" + tail).strip("\n"))
48
+
49
+ for candidate in candidates:
50
+ try:
51
+ compile(candidate, "<q:python>", "exec")
52
+ return candidate
53
+ except SyntaxError:
54
+ continue
55
+
56
+ # None compile: the code is genuinely broken. Hand back the dedented
57
+ # form, so the error the user sees points at their code rather than at
58
+ # indentation this function should have handled.
59
+ return candidates[1]
60
+
61
+
62
+ class PythonExecutor(BaseExecutor):
63
+ """
64
+ Executor for q:python statements.
65
+
66
+ Supports:
67
+ - Inline Python code execution
68
+ - Component, isolated, and module scopes
69
+ - Async execution
70
+ - Timeout limits
71
+ - Bridge object for context access
72
+ """
73
+
74
+ @property
75
+ def handles(self) -> List[Type]:
76
+ return [PythonNode]
77
+
78
+ def execute(self, node: PythonNode, exec_context) -> Any:
79
+ """
80
+ Execute Python code block.
81
+
82
+ Args:
83
+ node: PythonNode with Python code
84
+ exec_context: Execution context
85
+
86
+ Returns:
87
+ Execution result (if result attribute set)
88
+ """
89
+ try:
90
+ self.require_python_scripting('q:python')
91
+
92
+ context = exec_context.get_all_variables()
93
+
94
+ # Build bridge object for q.variable access
95
+ bridge = QuantumBridge(exec_context, context)
96
+
97
+ # Build execution namespace
98
+ namespace = {
99
+ 'q': bridge,
100
+ '__quantum_context__': context
101
+ }
102
+
103
+ # Add component scope variables if not isolated
104
+ if node.scope == 'component':
105
+ namespace.update(context)
106
+
107
+ # Execute code (dedented — see normalise_python_source)
108
+ code = normalise_python_source(node.code)
109
+
110
+ if node.async_mode:
111
+ result = self._execute_async(code, namespace, node.timeout)
112
+ else:
113
+ result = self._execute_sync(code, namespace, node.timeout)
114
+
115
+ # Store result if requested
116
+ if node.result:
117
+ exec_context.set_variable(node.result, result, scope="component")
118
+
119
+ # Export bridge changes to context
120
+ for key, value in bridge._exports.items():
121
+ exec_context.set_variable(key, value, scope="component")
122
+
123
+ return result
124
+
125
+ except Exception as e:
126
+ raise ExecutorError(f"Python execution error: {e}")
127
+
128
+ def _execute_sync(self, code: str, namespace: Dict, timeout: str = None) -> Any:
129
+ """Execute Python code synchronously."""
130
+ # Handle return statements
131
+ if 'return ' in code:
132
+ # Wrap in function
133
+ wrapped = f"def __quantum_exec__():\n"
134
+ for line in code.split('\n'):
135
+ wrapped += f" {line}\n"
136
+ wrapped += "__quantum_result__ = __quantum_exec__()"
137
+ exec(wrapped, namespace)
138
+ return namespace.get('__quantum_result__')
139
+ else:
140
+ exec(code, namespace)
141
+ return None
142
+
143
+ def _execute_async(self, code: str, namespace: Dict, timeout: str = None) -> Any:
144
+ """Execute Python code asynchronously."""
145
+ import asyncio
146
+
147
+ async def run_async():
148
+ # Wrap in async function
149
+ wrapped = f"async def __quantum_async__():\n"
150
+ for line in code.split('\n'):
151
+ wrapped += f" {line}\n"
152
+ exec(wrapped, namespace)
153
+ return await namespace['__quantum_async__']()
154
+
155
+ # Parse timeout
156
+ timeout_seconds = None
157
+ if timeout:
158
+ timeout_seconds = self._parse_timeout(timeout)
159
+
160
+ return asyncio.run(asyncio.wait_for(run_async(), timeout=timeout_seconds))
161
+
162
+ def _parse_timeout(self, timeout: str) -> float:
163
+ """Parse timeout string to seconds."""
164
+ if timeout.endswith('s'):
165
+ return float(timeout[:-1])
166
+ elif timeout.endswith('m'):
167
+ return float(timeout[:-1]) * 60
168
+ elif timeout.endswith('h'):
169
+ return float(timeout[:-1]) * 3600
170
+ return float(timeout)
171
+
172
+
173
+ class QuantumBridge:
174
+ """Bridge object for accessing Quantum context from Python."""
175
+
176
+ def __init__(self, exec_context, context: Dict[str, Any]):
177
+ self._exec_context = exec_context
178
+ self._context = context
179
+ self._exports = {}
180
+ self._missing_reported = set()
181
+
182
+ def __getattr__(self, name: str) -> Any:
183
+ """Get variable from context.
184
+
185
+ A name that is not in the context returns None, as before — scripts
186
+ do test optional variables. But it now says so once: `q.info(...)`
187
+ in the shipped example resolved to None and then raised
188
+ "'NoneType' object is not callable" four frames away, with nothing
189
+ naming `info`. (q.info exists now; the diagnosis should still work
190
+ for the next typo.)
191
+ """
192
+ if name.startswith('_'):
193
+ return object.__getattribute__(self, name)
194
+ if name not in self._context:
195
+ if name not in self._missing_reported:
196
+ self._missing_reported.add(name)
197
+ logger.warning(
198
+ "q:python read q.%s, which is not defined in the "
199
+ "component — it is None. Calling it raises "
200
+ "\"'NoneType' object is not callable\".", name
201
+ )
202
+ return self._context.get(name)
203
+
204
+ # -- logging from a script --------------------------------------------
205
+ #
206
+ # The documented example calls q.info(...). There was no such method, so
207
+ # it resolved to None through __getattr__ above and the whole block died
208
+ # on the call. These are what that example always meant.
209
+
210
+ def log(self, *parts: Any) -> None:
211
+ """Log at info level."""
212
+ logger.info("q:python: %s", " ".join(str(p) for p in parts))
213
+
214
+ def info(self, *parts: Any) -> None:
215
+ self.log(*parts)
216
+
217
+ def debug(self, *parts: Any) -> None:
218
+ logger.debug("q:python: %s", " ".join(str(p) for p in parts))
219
+
220
+ def warn(self, *parts: Any) -> None:
221
+ logger.warning("q:python: %s", " ".join(str(p) for p in parts))
222
+
223
+ warning = warn
224
+
225
+ def error(self, *parts: Any) -> None:
226
+ logger.error("q:python: %s", " ".join(str(p) for p in parts))
227
+
228
+ def __setattr__(self, name: str, value: Any):
229
+ """Set variable in context."""
230
+ if name.startswith('_'):
231
+ object.__setattr__(self, name, value)
232
+ else:
233
+ self._exports[name] = value
234
+
235
+ def export(self, name: str, value: Any):
236
+ """Explicitly export a variable."""
237
+ self._exports[name] = value
238
+
239
+ def get(self, name: str, default: Any = None) -> Any:
240
+ """Get variable with default."""
241
+ return self._context.get(name, default)
242
+
243
+ def set(self, name: str, value: Any):
244
+ """Set variable."""
245
+ self._exports[name] = value
246
+
247
+ def has(self, name: str) -> bool:
248
+ """Check if variable exists."""
249
+ return name in self._context
@@ -0,0 +1,12 @@
1
+ """
2
+ Service Executors
3
+
4
+ Executors for service operations: log, dump, file, mail.
5
+ """
6
+
7
+ from .log_executor import LogExecutor
8
+ from .dump_executor import DumpExecutor
9
+ from .file_executor import FileExecutor
10
+ from .mail_executor import MailExecutor
11
+
12
+ __all__ = ['LogExecutor', 'DumpExecutor', 'FileExecutor', 'MailExecutor']
@@ -0,0 +1,72 @@
1
+ """
2
+ Dump Executor - Execute q:dump statements
3
+
4
+ Handles variable inspection and debugging output.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
9
+ from quantum.core.features.dump.src import DumpNode
10
+
11
+
12
+ class DumpExecutor(BaseExecutor):
13
+ """
14
+ Executor for q:dump statements.
15
+
16
+ Supports:
17
+ - Variable inspection with depth control
18
+ - Multiple output formats (text, json, html)
19
+ - Conditional dumping
20
+ - Labeled output
21
+ """
22
+
23
+ @property
24
+ def handles(self) -> List[Type]:
25
+ return [DumpNode]
26
+
27
+ def execute(self, node: DumpNode, exec_context) -> Any:
28
+ """
29
+ Execute variable dump/inspection.
30
+
31
+ Args:
32
+ node: DumpNode with dump configuration
33
+ exec_context: Execution context
34
+
35
+ Returns:
36
+ Formatted dump output string
37
+ """
38
+ try:
39
+ context = exec_context.get_all_variables()
40
+
41
+ # Check conditional execution
42
+ if node.when:
43
+ condition_result = self.apply_databinding(node.when, context)
44
+ if not self.services.dump.should_dump(condition_result):
45
+ return
46
+
47
+ # Resolve variable to dump
48
+ var_expr = node.var
49
+ try:
50
+ var_value = self.apply_databinding(var_expr, context)
51
+ except Exception as e:
52
+ var_value = f"<Variable '{var_expr}' not found: {e}>"
53
+
54
+ # Generate dump output
55
+ dump_output = self.services.dump.dump(
56
+ var=var_value,
57
+ label=node.label,
58
+ format=node.format,
59
+ depth=node.depth
60
+ )
61
+
62
+ # Print dump output
63
+ print(dump_output)
64
+
65
+ # Store in context for potential template rendering
66
+ dump_var_name = f"_dump_{node.label.replace(' ', '_')}"
67
+ exec_context.set_variable(dump_var_name, dump_output, scope="component")
68
+
69
+ return dump_output
70
+
71
+ except Exception as e:
72
+ raise ExecutorError(f"Dump error: {e}")
@@ -0,0 +1,89 @@
1
+ """
2
+ File Executor - Execute q:file statements
3
+
4
+ Handles file upload and delete operations.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
9
+ from quantum.core.ast_nodes import FileNode
10
+
11
+
12
+ class FileExecutor(BaseExecutor):
13
+ """
14
+ Executor for q:file statements.
15
+
16
+ Supports:
17
+ - File upload with conflict handling
18
+ - File deletion
19
+ - Result storage in context
20
+ """
21
+
22
+ @property
23
+ def handles(self) -> List[Type]:
24
+ return [FileNode]
25
+
26
+ def execute(self, node: FileNode, exec_context) -> Any:
27
+ """
28
+ Execute file operation.
29
+
30
+ Args:
31
+ node: FileNode with file operation configuration
32
+ exec_context: Execution context
33
+
34
+ Returns:
35
+ File operation result dict
36
+ """
37
+ try:
38
+ context = exec_context.get_all_variables()
39
+
40
+ if node.action == 'upload':
41
+ return self._execute_upload(node, context, exec_context)
42
+ elif node.action == 'delete':
43
+ return self._execute_delete(node, context, exec_context)
44
+ else:
45
+ raise ExecutorError(f"Unsupported file action: {node.action}")
46
+
47
+ except Exception as e:
48
+ raise ExecutorError(f"File execution error: {e}")
49
+
50
+ def _execute_upload(self, node: FileNode, context: Dict[str, Any], exec_context) -> Dict:
51
+ """Execute file upload"""
52
+ # Resolve file variable
53
+ file_var = node.file.strip('{}')
54
+ file_obj = context.get(file_var)
55
+
56
+ if not file_obj:
57
+ raise ExecutorError(f"File variable '{file_var}' not found")
58
+
59
+ # Resolve destination
60
+ destination = self.apply_databinding(node.destination, context)
61
+
62
+ # Upload file
63
+ result = self.services.file_upload.upload_file(
64
+ file=file_obj,
65
+ destination=destination,
66
+ name_conflict=node.name_conflict
67
+ )
68
+
69
+ # Store results
70
+ if node.result:
71
+ exec_context.set_variable(node.result, result, scope="component")
72
+
73
+ exec_context.set_variable(f"{file_var}_upload", result, scope="component")
74
+
75
+ return result
76
+
77
+ def _execute_delete(self, node: FileNode, context: Dict[str, Any], exec_context) -> Dict:
78
+ """Execute file deletion"""
79
+ # Resolve filepath
80
+ filepath = self.apply_databinding(node.file, context)
81
+
82
+ # Delete file
83
+ result = self.services.file_upload.delete_file(filepath)
84
+
85
+ # Store result
86
+ if node.result:
87
+ exec_context.set_variable(node.result, result, scope="component")
88
+
89
+ return result
@@ -0,0 +1,77 @@
1
+ """
2
+ Log Executor - Execute q:log statements
3
+
4
+ Handles structured logging with levels and context.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ import json
9
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
10
+ from quantum.core.features.logging.src import LogNode
11
+
12
+
13
+ class LogExecutor(BaseExecutor):
14
+ """
15
+ Executor for q:log statements.
16
+
17
+ Supports:
18
+ - Multiple log levels (debug, info, warn, error)
19
+ - Conditional logging (when attribute)
20
+ - Context data attachment
21
+ - Correlation IDs for tracing
22
+ """
23
+
24
+ @property
25
+ def handles(self) -> List[Type]:
26
+ return [LogNode]
27
+
28
+ def execute(self, node: LogNode, exec_context) -> Any:
29
+ """
30
+ Execute logging statement.
31
+
32
+ Args:
33
+ node: LogNode with logging configuration
34
+ exec_context: Execution context
35
+
36
+ Returns:
37
+ None (logging is side-effect only)
38
+ """
39
+ try:
40
+ context = exec_context.get_all_variables()
41
+
42
+ # Check conditional execution
43
+ if node.when:
44
+ condition_result = self.apply_databinding(node.when, context)
45
+ if not self.services.logging.should_log(condition_result):
46
+ return
47
+
48
+ # Resolve message
49
+ message = self.apply_databinding(node.message, context)
50
+
51
+ # Resolve context data
52
+ context_data = None
53
+ if node.context:
54
+ context_expr = self.apply_databinding(node.context, context)
55
+ if isinstance(context_expr, dict):
56
+ context_data = context_expr
57
+ elif isinstance(context_expr, str):
58
+ try:
59
+ context_data = json.loads(context_expr)
60
+ except:
61
+ context_data = {'value': context_expr}
62
+
63
+ # Resolve correlation_id
64
+ correlation_id = None
65
+ if node.correlation_id:
66
+ correlation_id = self.apply_databinding(node.correlation_id, context)
67
+
68
+ # Execute logging
69
+ self.services.logging.log(
70
+ level=node.level,
71
+ message=str(message),
72
+ context=context_data,
73
+ correlation_id=correlation_id
74
+ )
75
+
76
+ except Exception as e:
77
+ raise ExecutorError(f"Logging error: {e}")
@@ -0,0 +1,81 @@
1
+ """
2
+ Mail Executor - Execute q:mail statements
3
+
4
+ Handles email sending with HTML/text support.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
9
+ from quantum.core.ast_nodes import MailNode
10
+
11
+
12
+ class MailExecutor(BaseExecutor):
13
+ """
14
+ Executor for q:mail statements.
15
+
16
+ Supports:
17
+ - HTML and plain text emails
18
+ - CC, BCC, Reply-To
19
+ - Attachments
20
+ - Databinding in all fields
21
+ """
22
+
23
+ @property
24
+ def handles(self) -> List[Type]:
25
+ return [MailNode]
26
+
27
+ def execute(self, node: MailNode, exec_context) -> Any:
28
+ """
29
+ Execute email sending.
30
+
31
+ Args:
32
+ node: MailNode with email configuration
33
+ exec_context: Execution context
34
+
35
+ Returns:
36
+ Email send result dict
37
+ """
38
+ try:
39
+ context = exec_context.get_all_variables()
40
+
41
+ # Resolve all email properties
42
+ to = self.apply_databinding(node.to, context)
43
+ subject = self.apply_databinding(node.subject, context)
44
+ body = self.apply_databinding(node.body, context)
45
+
46
+ from_addr = None
47
+ if node.from_addr:
48
+ from_addr = self.apply_databinding(node.from_addr, context)
49
+
50
+ cc = None
51
+ if node.cc:
52
+ cc = self.apply_databinding(node.cc, context)
53
+
54
+ bcc = None
55
+ if node.bcc:
56
+ bcc = self.apply_databinding(node.bcc, context)
57
+
58
+ reply_to = None
59
+ if node.reply_to:
60
+ reply_to = self.apply_databinding(node.reply_to, context)
61
+
62
+ # Send email
63
+ result = self.services.email.send_email(
64
+ to=to,
65
+ subject=subject,
66
+ body=body,
67
+ from_addr=from_addr,
68
+ cc=cc,
69
+ bcc=bcc,
70
+ reply_to=reply_to,
71
+ email_type=node.type,
72
+ attachments=node.attachments
73
+ )
74
+
75
+ # Store result
76
+ exec_context.set_variable('_mail_result', result, scope="component")
77
+
78
+ return result
79
+
80
+ except Exception as e:
81
+ raise ExecutorError(f"Mail execution error: {e}")