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,93 @@
1
+ """
2
+ Set Parser - Parse q:set statements
3
+
4
+ Handles variable assignment with validation and persistence.
5
+ """
6
+
7
+ from typing import List
8
+ from xml.etree import ElementTree as ET
9
+ from quantum.core.parsers.base import BaseTagParser, ParserError
10
+ from quantum.core.features.state_management.src.ast_node import SetNode
11
+
12
+
13
+ class SetParser(BaseTagParser):
14
+ """
15
+ Parser for q:set statements.
16
+
17
+ Supports:
18
+ - Type-safe variable assignment
19
+ - Validation rules
20
+ - Scope management
21
+ - Operations (assign, append, increment, etc.)
22
+ - State persistence
23
+ """
24
+
25
+ @property
26
+ def tag_names(self) -> List[str]:
27
+ return ['set']
28
+
29
+ def parse(self, element: ET.Element) -> SetNode:
30
+ """
31
+ Parse q:set statement.
32
+
33
+ Args:
34
+ element: XML element for q:set
35
+
36
+ Returns:
37
+ SetNode AST node
38
+ """
39
+ name = self.get_attr(element, 'name')
40
+
41
+ if not name:
42
+ raise ParserError("Set requires 'name' attribute")
43
+
44
+ set_node = SetNode(name)
45
+
46
+ # Type and value
47
+ set_node.type = self.get_attr(element, 'type', 'string')
48
+ set_node.value = self.get_attr(element, 'value')
49
+ set_node.default = self.get_attr(element, 'default')
50
+
51
+ # Validation
52
+ set_node.required = self.get_bool_attr(element, 'required', False)
53
+ set_node.nullable = self.get_bool_attr(element, 'nullable', True)
54
+ set_node.validate_rule = self.get_attr(element, 'validate')
55
+ set_node.pattern = self.get_attr(element, 'pattern')
56
+ if element.get('mask') is not None:
57
+ # A2: accepted and never applied.
58
+ from quantum.core.parser import QuantumParseError
59
+ raise QuantumParseError(
60
+ f'<q:set name="{set_node.name}"> mask= is not supported: it was accepted and '
61
+ f'never did anything. To validate a format use pattern=.')
62
+ set_node.range = self.get_attr(element, 'range')
63
+ set_node.enum = self.get_attr(element, 'enum')
64
+ set_node.unique = self.get_attr(element, 'unique')
65
+ set_node.min = self.get_attr(element, 'min')
66
+ set_node.max = self.get_attr(element, 'max')
67
+ set_node.minlength = self.get_attr(element, 'minlength')
68
+ set_node.maxlength = self.get_attr(element, 'maxlength')
69
+
70
+ # Behavior
71
+ set_node.scope = self.get_attr(element, 'scope', 'local')
72
+ set_node.operation = self.get_attr(element, 'operation', 'assign')
73
+
74
+ # Step for increment/decrement
75
+ set_node.step = self.get_int_attr(element, 'step', 1)
76
+
77
+ # Collection operations
78
+ set_node.index = self.get_attr(element, 'index')
79
+ set_node.key = self.get_attr(element, 'key')
80
+ set_node.source = self.get_attr(element, 'source')
81
+
82
+ # SET-2: browser persistence never ran in a page.
83
+ persist = [a for a in ('persist', 'persistKey', 'persistTtl', 'persistEncrypt')
84
+ if element.get(a) is not None]
85
+ if persist:
86
+ from quantum.core.parser import QuantumParseError
87
+ raise QuantumParseError(
88
+ f'<q:set name="{set_node.name}"> {persist[0]}= was removed in Quantum 0.16: it '
89
+ f'kept the variable in the browser, and a page\'s variables live on the server, '
90
+ f'for one request. Keep what must last in session.{set_node.name} (per user) '
91
+ f'or in the database.')
92
+
93
+ return set_node
@@ -0,0 +1,12 @@
1
+ """
2
+ Data Parsers
3
+
4
+ Parsers for data operations: query, invoke, data, transaction.
5
+ """
6
+
7
+ from .query_parser import QueryParser
8
+ from .invoke_parser import InvokeParser
9
+ from .data_parser import DataParser
10
+ from .transaction_parser import TransactionParser
11
+
12
+ __all__ = ['QueryParser', 'InvokeParser', 'DataParser', 'TransactionParser']
@@ -0,0 +1,229 @@
1
+ """
2
+ Data Parser - Parse q:data statements
3
+
4
+ Handles data import from CSV, JSON, XML sources.
5
+ """
6
+
7
+ from typing import List
8
+ from xml.etree import ElementTree as ET
9
+ from quantum.core.parsers.base import BaseTagParser, ParserError
10
+ from quantum.core.ast_nodes import (
11
+ DataNode, ColumnNode, FieldNode, TransformNode, HeaderNode,
12
+ FilterNode, SortNode, LimitNode, ComputeNode,
13
+ )
14
+
15
+
16
+ class DataParser(BaseTagParser):
17
+ """
18
+ Parser for q:data statements.
19
+
20
+ Supports:
21
+ - CSV import with column definitions
22
+ - JSON import with field mappings
23
+ - XML import with XPath
24
+ - Data transformations
25
+ """
26
+
27
+ @property
28
+ def tag_names(self) -> List[str]:
29
+ return ['data']
30
+
31
+ def parse(self, element: ET.Element) -> DataNode:
32
+ """
33
+ Parse q:data statement.
34
+
35
+ Args:
36
+ element: XML element for q:data
37
+
38
+ Returns:
39
+ DataNode AST node
40
+ """
41
+ name = self.get_attr(element, 'name')
42
+ source = self.get_attr(element, 'source')
43
+ data_type = self.get_attr(element, 'type', 'csv')
44
+
45
+ if not name:
46
+ raise ParserError("Data requires 'name' attribute")
47
+ if not source:
48
+ raise ParserError("Data requires 'source' attribute")
49
+
50
+ data_node = DataNode(name, source, data_type)
51
+
52
+ # Caching
53
+ data_node.cache = self.get_bool_attr(element, 'cache', True)
54
+ data_node.ttl = self.get_int_attr(element, 'ttl', 0) or None
55
+
56
+ # CSV attributes
57
+ data_node.delimiter = self.get_attr(element, 'delimiter', ',')
58
+ data_node.quote = self.get_attr(element, 'quote', '"')
59
+ data_node.header = self.get_bool_attr(element, 'header', True)
60
+ data_node.encoding = self.get_attr(element, 'encoding', 'utf-8')
61
+ data_node.skip_rows = self.get_int_attr(element, 'skip_rows', 0)
62
+
63
+ # XML attributes
64
+ data_node.xpath = self.get_attr(element, 'xpath')
65
+ data_node.namespace = self.get_attr(element, 'namespace')
66
+
67
+ # Result
68
+ data_node.result = self.get_attr(element, 'result')
69
+
70
+ # DATA-4 / INV-2: a failure stops the component unless the program says
71
+ # it will look at <name>_result.
72
+ data_node.on_error = self.get_attr(element, 'onerror', 'fail')
73
+ if data_node.on_error not in ('fail', 'continue'):
74
+ from quantum.core.parser import QuantumParseError
75
+ raise QuantumParseError(
76
+ f"<q:data name=\"{data_node.name}\"> onerror must be \"fail\" or "
77
+ f"\"continue\", not \"{data_node.on_error}\"")
78
+
79
+ # Parse child elements
80
+ for child in element:
81
+ child_type = self.get_element_name(child)
82
+
83
+ if child_type == 'column':
84
+ column = self._parse_column(child)
85
+ data_node.add_column(column)
86
+ elif child_type == 'field':
87
+ field = self._parse_field(child)
88
+ data_node.add_field(field)
89
+ elif child_type == 'transform':
90
+ transform = self._parse_transform(child)
91
+ data_node.add_transform(transform)
92
+ elif child_type == 'header':
93
+ header = self._parse_header(child)
94
+ data_node.add_header(header)
95
+
96
+ return data_node
97
+
98
+ def _parse_column(self, element: ET.Element) -> ColumnNode:
99
+ """Parse q:column for CSV."""
100
+ name = self.get_attr(element, 'name')
101
+ col_type = self.get_attr(element, 'type', 'string')
102
+
103
+ if not name:
104
+ raise ParserError("Column requires 'name' attribute")
105
+
106
+ column = ColumnNode(name, col_type)
107
+ column.required = self.get_bool_attr(element, 'required', False)
108
+ column.default = self.get_attr(element, 'default')
109
+ column.validate_rule = self.get_attr(element, 'validate')
110
+ column.pattern = self.get_attr(element, 'pattern')
111
+
112
+ # Numeric validation
113
+ min_val = self.get_attr(element, 'min')
114
+ if min_val:
115
+ try:
116
+ column.min = float(min_val)
117
+ except ValueError:
118
+ column.min = min_val
119
+
120
+ max_val = self.get_attr(element, 'max')
121
+ if max_val:
122
+ try:
123
+ column.max = float(max_val)
124
+ except ValueError:
125
+ column.max = max_val
126
+
127
+ column.minlength = self.get_int_attr(element, 'minlength', 0) or None
128
+ column.maxlength = self.get_int_attr(element, 'maxlength', 0) or None
129
+ column.range = self.get_attr(element, 'range')
130
+ column.enum = self.get_attr(element, 'enum')
131
+
132
+ return column
133
+
134
+ def _parse_field(self, element: ET.Element) -> FieldNode:
135
+ """Parse q:field for JSON/XML."""
136
+ name = self.get_attr(element, 'name')
137
+ # `xpath` is the attribute every example and the reference use; only
138
+ # `path` was read, so `xpath="@id"` became the name "id" and `type` was
139
+ # dropped — attributes came back None and numbers came back as text.
140
+ path = self.get_attr(element, 'xpath') or self.get_attr(element, 'path')
141
+
142
+ if not name:
143
+ raise ParserError("Field requires 'name' attribute")
144
+
145
+ return FieldNode(name, path or name, self.get_attr(element, 'type') or 'string')
146
+
147
+ def _parse_transform(self, element: ET.Element) -> TransformNode:
148
+ """Parse q:transform — a container of operations.
149
+
150
+ This read operation=/field=/value= off the tag and called
151
+ TransformNode(operation, field, value). TransformNode takes NO
152
+ arguments: it holds a list of child operations, which is also what
153
+ the runtime iterates and what all four q:transform blocks in
154
+ examples/ are written as:
155
+
156
+ <q:transform>
157
+ <q:filter condition="{active} == True" />
158
+ </q:transform>
159
+
160
+ So the parser rejected the only form anybody writes — "Transform
161
+ requires 'operation' attribute" — and would have raised TypeError if
162
+ it had ever got past that. Four example tests failed here.
163
+ """
164
+ transform_node = TransformNode()
165
+
166
+ for child in element:
167
+ tag = self._local_name(child.tag)
168
+
169
+ if tag == 'filter':
170
+ condition = self.get_attr(child, 'condition')
171
+ if not condition:
172
+ raise ParserError("Filter requires 'condition' attribute")
173
+ transform_node.add_operation(FilterNode(condition))
174
+
175
+ elif tag == 'sort':
176
+ by = self.get_attr(child, 'by') or self.get_attr(child, 'field')
177
+ if not by:
178
+ raise ParserError("Sort requires 'by' attribute")
179
+ order = (self.get_attr(child, 'order') or 'asc').lower()
180
+ if order not in ('asc', 'desc'):
181
+ raise ParserError(
182
+ f"Invalid sort order: {order}. Must be 'asc' or 'desc'")
183
+ transform_node.add_operation(SortNode(by, order))
184
+
185
+ elif tag == 'limit':
186
+ raw = self.get_attr(child, 'value') or self.get_attr(child, 'count')
187
+ if raw is None or raw == '':
188
+ raise ParserError("Limit requires 'value' attribute")
189
+ try:
190
+ limit = int(str(raw).strip())
191
+ except ValueError:
192
+ raise ParserError(f"Limit value must be a number, got {raw!r}")
193
+ transform_node.add_operation(LimitNode(limit))
194
+
195
+ elif tag == 'compute':
196
+ field = self.get_attr(child, 'field') or self.get_attr(child, 'name')
197
+ expression = self.get_attr(child, 'expression')
198
+ if not field:
199
+ raise ParserError("Compute requires 'field' attribute")
200
+ if not expression:
201
+ raise ParserError("Compute requires 'expression' attribute")
202
+ transform_node.add_operation(ComputeNode(
203
+ field, expression, self.get_attr(child, 'type') or 'string'))
204
+
205
+ else:
206
+ raise ParserError(
207
+ f"Unknown transform operation 'q:{tag}'. Supported: "
208
+ f"filter, sort, limit, compute")
209
+
210
+ return transform_node
211
+
212
+ @staticmethod
213
+ def _local_name(tag: str) -> str:
214
+ """'{ns}filter' or 'q:filter' -> 'filter'."""
215
+ if '}' in tag:
216
+ tag = tag.split('}', 1)[1]
217
+ if ':' in tag:
218
+ tag = tag.split(':', 1)[1]
219
+ return tag
220
+
221
+ def _parse_header(self, element: ET.Element) -> HeaderNode:
222
+ """Parse q:header for HTTP headers."""
223
+ name = self.get_attr(element, 'name')
224
+ value = self.get_attr(element, 'value')
225
+
226
+ if not name:
227
+ raise ParserError("Header requires 'name' attribute")
228
+
229
+ return HeaderNode(name, value or '')
@@ -0,0 +1,133 @@
1
+ """
2
+ Invoke Parser - Parse q:invoke statements
3
+
4
+ Handles function, component, and HTTP invocations.
5
+ """
6
+
7
+ from typing import List
8
+ from xml.etree import ElementTree as ET
9
+ from quantum.core.parsers.base import BaseTagParser, ParserError
10
+ from quantum.core.features.invocation.src.ast_node import InvokeNode, InvokeHeaderNode
11
+ from quantum.core.ast_nodes import QuantumParam
12
+
13
+
14
+ class InvokeParser(BaseTagParser):
15
+ """
16
+ Parser for q:invoke statements.
17
+
18
+ Supports:
19
+ - Function invocation
20
+ - Component invocation
21
+ - HTTP requests (REST APIs)
22
+ - Service invocation
23
+ - Authentication (bearer, basic, custom)
24
+ """
25
+
26
+ @property
27
+ def tag_names(self) -> List[str]:
28
+ return ['invoke']
29
+
30
+ def parse(self, element: ET.Element) -> InvokeNode:
31
+ """
32
+ Parse q:invoke statement.
33
+
34
+ Args:
35
+ element: XML element for q:invoke
36
+
37
+ Returns:
38
+ InvokeNode AST node
39
+ """
40
+ name = self.get_attr(element, 'name')
41
+ # Fire-and-forget invocation (no result to bind) is legitimate — the
42
+ # job/thread examples all use it — so name is optional.
43
+ invoke_node = InvokeNode(name or '')
44
+
45
+ # Parse invocation target
46
+ invoke_node.function = self.get_attr(element, 'function')
47
+ invoke_node.component = self.get_attr(element, 'component')
48
+ invoke_node.url = self.get_attr(element, 'url')
49
+ if self.get_attr(element, 'endpoint') is not None:
50
+ raise ParserError(
51
+ f'<q:invoke name="{name}"> endpoint= is not supported: it was accepted and never '
52
+ f'did anything, and was removed in Quantum 0.12. Use url= or service=.')
53
+ invoke_node.service = self.get_attr(element, 'service')
54
+
55
+ # HTTP attributes
56
+ invoke_node.method = self.get_attr(element, 'method', 'GET').upper()
57
+ invoke_node.content_type = self.get_attr(element, 'contentType', 'application/json')
58
+
59
+ # Authentication
60
+ invoke_node.auth_type = self.get_attr(element, 'authType')
61
+ invoke_node.auth_token = self.get_attr(element, 'authToken')
62
+ invoke_node.auth_header = self.get_attr(element, 'authHeader')
63
+ invoke_node.auth_username = self.get_attr(element, 'authUsername')
64
+ invoke_node.auth_password = self.get_attr(element, 'authPassword')
65
+
66
+ # Timeouts and retries
67
+ invoke_node.timeout = self.get_int_attr(element, 'timeout', 0) or None
68
+ invoke_node.retry = self.get_int_attr(element, 'retry', 0) or None
69
+ invoke_node.retry_delay = self.get_int_attr(element, 'retryDelay', 0) or None
70
+
71
+ # Response handling
72
+ invoke_node.response_format = self.get_attr(element, 'responseFormat', 'auto')
73
+ if element.get('transform') is not None:
74
+ # A2: accepted and never applied.
75
+ from quantum.core.parser import QuantumParseError
76
+ raise QuantumParseError(
77
+ f'<q:invoke name="{invoke_node.name}"> transform= is not supported: it was '
78
+ f'accepted and never did anything. Transform the result with q:set.')
79
+ invoke_node.cache = self.get_bool_attr(element, 'cache', False)
80
+ invoke_node.ttl = self.get_int_attr(element, 'ttl', 0) or None
81
+ invoke_node.result = self.get_attr(element, 'result')
82
+
83
+ # DATA-4 / INV-2: a failure stops the component unless the program says
84
+ # it will look at <name>_result.
85
+ invoke_node.on_error = self.get_attr(element, 'onerror', 'fail')
86
+ if invoke_node.on_error not in ('fail', 'continue'):
87
+ from quantum.core.parser import QuantumParseError
88
+ raise QuantumParseError(
89
+ f"<q:invoke name=\"{invoke_node.name}\"> onerror must be \"fail\" or "
90
+ f"\"continue\", not \"{invoke_node.on_error}\"")
91
+
92
+ # Parse child elements
93
+ for child in element:
94
+ child_type = self.get_element_name(child)
95
+
96
+ if child_type == 'header':
97
+ header = self._parse_header(child)
98
+ invoke_node.add_header(header)
99
+ elif child_type == 'param':
100
+ param = self._parse_param(child)
101
+ invoke_node.add_param(param)
102
+ elif child_type == 'body':
103
+ invoke_node.body = child.text or ""
104
+
105
+ return invoke_node
106
+
107
+ def _parse_header(self, element: ET.Element) -> InvokeHeaderNode:
108
+ """Parse q:header within q:invoke."""
109
+ name = self.get_attr(element, 'name')
110
+ value = self.get_attr(element, 'value')
111
+
112
+ if not name:
113
+ raise ParserError("Invoke header requires 'name' attribute")
114
+ if value is None:
115
+ raise ParserError(f"Invoke header '{name}' requires 'value' attribute")
116
+
117
+ return InvokeHeaderNode(name, value)
118
+
119
+ def _parse_param(self, element: ET.Element) -> QuantumParam:
120
+ """Parse q:param within q:invoke."""
121
+ name = self.get_attr(element, 'name')
122
+ value = self.get_attr(element, 'value')
123
+ param_type = self.get_attr(element, 'type', 'string')
124
+
125
+ if not name:
126
+ raise ParserError("Param requires 'name' attribute")
127
+
128
+ param = QuantumParam(name, param_type)
129
+ param.value = value
130
+ param.required = self.get_bool_attr(element, 'required', False)
131
+ param.default = self.get_attr(element, 'default')
132
+
133
+ return param
@@ -0,0 +1,213 @@
1
+ """
2
+ Query Parser - Parse q:query statements
3
+
4
+ Handles SQL queries with parameter binding and RAG mode.
5
+ """
6
+
7
+ import re
8
+ from typing import List
9
+ from xml.etree import ElementTree as ET
10
+ from quantum.core.parsers.base import BaseTagParser, ParserError
11
+ from quantum.core.ast_nodes import QueryNode, QueryParamNode
12
+
13
+
14
+ _NEVER_IMPLEMENTED = ('cache', 'ttl', 'reactive', 'interval', 'timeout', 'maxrows', 'batch')
15
+
16
+
17
+ class QueryParser(BaseTagParser):
18
+ """
19
+ Parser for q:query statements.
20
+
21
+ Supports:
22
+ - SQL queries with parameter binding
23
+ - Query of Queries (source attribute)
24
+ - Caching and pagination
25
+ - RAG/knowledge base queries
26
+ """
27
+
28
+ @property
29
+ def tag_names(self) -> List[str]:
30
+ return ['query']
31
+
32
+ def parse(self, element: ET.Element) -> QueryNode:
33
+ """
34
+ Parse q:query statement.
35
+
36
+ Args:
37
+ element: XML element for q:query
38
+
39
+ Returns:
40
+ QueryNode AST node
41
+ """
42
+ name = self.get_attr(element, 'name')
43
+ datasource = self.get_attr(element, 'datasource')
44
+ source = self.get_attr(element, 'source')
45
+
46
+ # A write query (UPDATE/INSERT/DELETE) has no result set to bind, so
47
+ # name is optional — requiring it rejected the ordinary CFML-style
48
+ # idiom used across the job examples. Reads still want a name; without
49
+ # one there is simply nothing to reference the rows by.
50
+ if not name:
51
+ name = ''
52
+ if not datasource and not source:
53
+ raise ParserError("Query requires either 'datasource' or 'source' attribute")
54
+
55
+ # DB-5: attributes that were read into the AST and never used. The
56
+ # guide sold cache="true" ttl="3600" and reactive="true" as features.
57
+ for attr in _NEVER_IMPLEMENTED:
58
+ if element.get(attr) is not None:
59
+ raise ParserError(
60
+ f'<q:query name="{name}"> {attr}= is not supported: it was accepted '
61
+ f'and never did anything, and was removed in Quantum 0.11')
62
+
63
+ # Extract SQL content
64
+ sql_parts = []
65
+ if element.text:
66
+ sql_parts.append(element.text.strip())
67
+
68
+ for child in element:
69
+ if child.tail:
70
+ sql_parts.append(child.tail.strip())
71
+
72
+ sql = '\n'.join(part for part in sql_parts if part)
73
+
74
+ # Create query node
75
+ query_node = QueryNode(name, datasource or '', sql)
76
+
77
+ # Parse attributes
78
+ query_node.source = source
79
+ query_node.paginate = self.get_bool_attr(element, 'paginate', False)
80
+ # UI-13: ordered by the URL's ?sort=/&dir= (a <ui:table sort="true"> writes them)
81
+ query_node.sortable = self.get_bool_attr(element, 'sortable', False)
82
+ # DB-9: page= is an expression, resolved when the query runs; it was
83
+ # read as an int at parse time, so page="{query.page}" was always
84
+ # page 1. With paginate="true" and no page=, the page is the URL's
85
+ # `page` parameter — the one <ui:pager> writes.
86
+ pagina = element.get('page')
87
+ query_node.page = None
88
+ query_node.page_expr = None
89
+ if pagina is not None:
90
+ if '{' in pagina:
91
+ query_node.page_expr = pagina
92
+ elif pagina.strip().isdigit():
93
+ query_node.page = int(pagina)
94
+ else:
95
+ from quantum.core.parser import QuantumParseError
96
+ raise QuantumParseError(
97
+ f'<q:query name="{name}" page="{pagina}">: a page number or an expression, '
98
+ f'e.g. page="{{query.pagina}}"')
99
+ elif query_node.paginate:
100
+ query_node.page_expr = '{query.page}'
101
+ # Accept both spellings: the codebase convention is camelCase, but
102
+ # snake_case reads naturally in XML and the examples used it —
103
+ # the mismatch made page_size None and crashed pagination with
104
+ # 'unsupported operand type(s) for +: int and NoneType'.
105
+ query_node.page_size = (
106
+ self.get_int_attr(element, 'pageSize', 0)
107
+ or self.get_int_attr(element, 'page_size', 0)
108
+ or None
109
+ )
110
+ query_node.result = self.get_attr(element, 'result')
111
+
112
+ # RAG/Knowledge attributes
113
+ query_node.mode = self.get_attr(element, 'mode')
114
+ query_node.rag_model = self.get_attr(element, 'model')
115
+
116
+ # Parse q:param children
117
+ for child in element:
118
+ child_type = self.get_element_name(child)
119
+ if child_type == 'param':
120
+ param_node = self._parse_query_param(child)
121
+ query_node.add_param(param_node)
122
+
123
+ # Validate: reject direct {var} interpolation (SQL injection risk)
124
+ if sql and re.search(r'\{[a-zA-Z_]\w*\}', sql):
125
+ raise ParserError(
126
+ f"Query '{name}': direct {{var}} interpolation in SQL is not allowed. "
127
+ "Use :param_name with <q:param> for safe parameter binding"
128
+ )
129
+
130
+ # Validate: all :param references must have matching <q:param>
131
+ if sql:
132
+ sql_params = self._named_params(sql)
133
+ declared_params = {p.name for p in query_node.params}
134
+ missing = sql_params - declared_params
135
+ if missing:
136
+ raise ParserError(
137
+ f"Query '{name}': SQL parameter(s) {', '.join(':' + p for p in sorted(missing))} "
138
+ "declared in SQL but no matching <q:param> element provided"
139
+ )
140
+
141
+ return query_node
142
+
143
+ @staticmethod
144
+ def _named_params(sql: str) -> set:
145
+ """Os `:nome` que sao REALMENTE parametros de bind.
146
+
147
+ Isto era `re.findall(r':([a-zA-Z_]\\w*)', sql)` sobre o SQL cru, e
148
+ varria tambem o que esta dentro de aspas e de comentarios:
149
+
150
+ TO_CHAR(c.created_at, 'YYYY-MM-DD HH24:MI') -> achava `:MI`
151
+ SELECT x::text FROM t -> achava `:text`
152
+
153
+ As duas formas sao SQL correto e comum — a primeira aparece em
154
+ projects/blog/components/post.q, que por causa disto nao parseava:
155
+ o arquivo era recusado com "SQL parameter(s) :MI declared in SQL but
156
+ no matching <q:param>", e nao havia parametro nenhum para declarar.
157
+
158
+ A varredura pula literais de texto (com '' e "" escapados), o cast
159
+ `::` do PostgreSQL, comentarios de linha `--` e de bloco.
160
+ """
161
+ encontrados = set()
162
+ i, n = 0, len(sql)
163
+ while i < n:
164
+ c = sql[i]
165
+ if c in ("'", '"'):
166
+ aspas = c
167
+ i += 1
168
+ while i < n:
169
+ if sql[i] == aspas:
170
+ if i + 1 < n and sql[i + 1] == aspas:
171
+ i += 2 # '' escapado dentro do literal
172
+ continue
173
+ i += 1
174
+ break
175
+ i += 1
176
+ continue
177
+ if sql.startswith('--', i):
178
+ fim = sql.find('\n', i)
179
+ i = n if fim == -1 else fim + 1
180
+ continue
181
+ if sql.startswith('/*', i):
182
+ fim = sql.find('*/', i)
183
+ i = n if fim == -1 else fim + 2
184
+ continue
185
+ if c == ':':
186
+ if sql.startswith('::', i):
187
+ i += 2 # cast do PostgreSQL, nao parametro
188
+ continue
189
+ m = re.match(r':([a-zA-Z_]\w*)', sql[i:])
190
+ if m:
191
+ encontrados.add(m.group(1))
192
+ i += m.end()
193
+ continue
194
+ i += 1
195
+ return encontrados
196
+
197
+ def _parse_query_param(self, element: ET.Element) -> QueryParamNode:
198
+ """Parse q:param within q:query."""
199
+ name = self.get_attr(element, 'name')
200
+ value = self.get_attr(element, 'value')
201
+ param_type = self.get_attr(element, 'type', 'string')
202
+
203
+ if not name:
204
+ raise ParserError("Query parameter requires 'name' attribute")
205
+ if value is None:
206
+ raise ParserError(f"Query parameter '{name}' requires 'value' attribute")
207
+
208
+ param_node = QueryParamNode(name, value, param_type)
209
+ param_node.null = self.get_bool_attr(element, 'null', False)
210
+ param_node.max_length = self.get_int_attr(element, 'maxLength', 0) or None
211
+ param_node.scale = self.get_int_attr(element, 'scale', 0) or None
212
+
213
+ return param_node