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,89 @@
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
+ set_node.mask = self.get_attr(element, 'mask')
57
+ set_node.range = self.get_attr(element, 'range')
58
+ set_node.enum = self.get_attr(element, 'enum')
59
+ set_node.unique = self.get_attr(element, 'unique')
60
+ set_node.min = self.get_attr(element, 'min')
61
+ set_node.max = self.get_attr(element, 'max')
62
+ set_node.minlength = self.get_attr(element, 'minlength')
63
+ set_node.maxlength = self.get_attr(element, 'maxlength')
64
+
65
+ # Behavior
66
+ set_node.scope = self.get_attr(element, 'scope', 'local')
67
+ set_node.operation = self.get_attr(element, 'operation', 'assign')
68
+
69
+ # Step for increment/decrement
70
+ set_node.step = self.get_int_attr(element, 'step', 1)
71
+
72
+ # Collection operations
73
+ set_node.index = self.get_attr(element, 'index')
74
+ set_node.key = self.get_attr(element, 'key')
75
+ set_node.source = self.get_attr(element, 'source')
76
+
77
+ # State persistence
78
+ set_node.persist = self.get_attr(element, 'persist')
79
+ set_node.persist_key = self.get_attr(element, 'persistKey')
80
+ set_node.persist_encrypt = self.get_bool_attr(element, 'persistEncrypt', False)
81
+
82
+ persist_ttl = self.get_attr(element, 'persistTtl')
83
+ if persist_ttl:
84
+ try:
85
+ set_node.persist_ttl = int(persist_ttl)
86
+ except ValueError:
87
+ pass
88
+
89
+ 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,217 @@
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
+ # Parse child elements
71
+ for child in element:
72
+ child_type = self.get_element_name(child)
73
+
74
+ if child_type == 'column':
75
+ column = self._parse_column(child)
76
+ data_node.add_column(column)
77
+ elif child_type == 'field':
78
+ field = self._parse_field(child)
79
+ data_node.add_field(field)
80
+ elif child_type == 'transform':
81
+ transform = self._parse_transform(child)
82
+ data_node.add_transform(transform)
83
+ elif child_type == 'header':
84
+ header = self._parse_header(child)
85
+ data_node.add_header(header)
86
+
87
+ return data_node
88
+
89
+ def _parse_column(self, element: ET.Element) -> ColumnNode:
90
+ """Parse q:column for CSV."""
91
+ name = self.get_attr(element, 'name')
92
+ col_type = self.get_attr(element, 'type', 'string')
93
+
94
+ if not name:
95
+ raise ParserError("Column requires 'name' attribute")
96
+
97
+ column = ColumnNode(name, col_type)
98
+ column.required = self.get_bool_attr(element, 'required', False)
99
+ column.default = self.get_attr(element, 'default')
100
+ column.validate_rule = self.get_attr(element, 'validate')
101
+ column.pattern = self.get_attr(element, 'pattern')
102
+
103
+ # Numeric validation
104
+ min_val = self.get_attr(element, 'min')
105
+ if min_val:
106
+ try:
107
+ column.min = float(min_val)
108
+ except ValueError:
109
+ column.min = min_val
110
+
111
+ max_val = self.get_attr(element, 'max')
112
+ if max_val:
113
+ try:
114
+ column.max = float(max_val)
115
+ except ValueError:
116
+ column.max = max_val
117
+
118
+ column.minlength = self.get_int_attr(element, 'minlength', 0) or None
119
+ column.maxlength = self.get_int_attr(element, 'maxlength', 0) or None
120
+ column.range = self.get_attr(element, 'range')
121
+ column.enum = self.get_attr(element, 'enum')
122
+
123
+ return column
124
+
125
+ def _parse_field(self, element: ET.Element) -> FieldNode:
126
+ """Parse q:field for JSON/XML."""
127
+ name = self.get_attr(element, 'name')
128
+ path = self.get_attr(element, 'path')
129
+
130
+ if not name:
131
+ raise ParserError("Field requires 'name' attribute")
132
+
133
+ return FieldNode(name, path or name)
134
+
135
+ def _parse_transform(self, element: ET.Element) -> TransformNode:
136
+ """Parse q:transform — a container of operations.
137
+
138
+ This read operation=/field=/value= off the tag and called
139
+ TransformNode(operation, field, value). TransformNode takes NO
140
+ arguments: it holds a list of child operations, which is also what
141
+ the runtime iterates and what all four q:transform blocks in
142
+ examples/ are written as:
143
+
144
+ <q:transform>
145
+ <q:filter condition="{active} == True" />
146
+ </q:transform>
147
+
148
+ So the parser rejected the only form anybody writes — "Transform
149
+ requires 'operation' attribute" — and would have raised TypeError if
150
+ it had ever got past that. Four example tests failed here.
151
+ """
152
+ transform_node = TransformNode()
153
+
154
+ for child in element:
155
+ tag = self._local_name(child.tag)
156
+
157
+ if tag == 'filter':
158
+ condition = self.get_attr(child, 'condition')
159
+ if not condition:
160
+ raise ParserError("Filter requires 'condition' attribute")
161
+ transform_node.add_operation(FilterNode(condition))
162
+
163
+ elif tag == 'sort':
164
+ by = self.get_attr(child, 'by') or self.get_attr(child, 'field')
165
+ if not by:
166
+ raise ParserError("Sort requires 'by' attribute")
167
+ order = (self.get_attr(child, 'order') or 'asc').lower()
168
+ if order not in ('asc', 'desc'):
169
+ raise ParserError(
170
+ f"Invalid sort order: {order}. Must be 'asc' or 'desc'")
171
+ transform_node.add_operation(SortNode(by, order))
172
+
173
+ elif tag == 'limit':
174
+ raw = self.get_attr(child, 'value') or self.get_attr(child, 'count')
175
+ if raw is None or raw == '':
176
+ raise ParserError("Limit requires 'value' attribute")
177
+ try:
178
+ limit = int(str(raw).strip())
179
+ except ValueError:
180
+ raise ParserError(f"Limit value must be a number, got {raw!r}")
181
+ transform_node.add_operation(LimitNode(limit))
182
+
183
+ elif tag == 'compute':
184
+ field = self.get_attr(child, 'field') or self.get_attr(child, 'name')
185
+ expression = self.get_attr(child, 'expression')
186
+ if not field:
187
+ raise ParserError("Compute requires 'field' attribute")
188
+ if not expression:
189
+ raise ParserError("Compute requires 'expression' attribute")
190
+ transform_node.add_operation(ComputeNode(
191
+ field, expression, self.get_attr(child, 'type') or 'string'))
192
+
193
+ else:
194
+ raise ParserError(
195
+ f"Unknown transform operation 'q:{tag}'. Supported: "
196
+ f"filter, sort, limit, compute")
197
+
198
+ return transform_node
199
+
200
+ @staticmethod
201
+ def _local_name(tag: str) -> str:
202
+ """'{ns}filter' or 'q:filter' -> 'filter'."""
203
+ if '}' in tag:
204
+ tag = tag.split('}', 1)[1]
205
+ if ':' in tag:
206
+ tag = tag.split(':', 1)[1]
207
+ return tag
208
+
209
+ def _parse_header(self, element: ET.Element) -> HeaderNode:
210
+ """Parse q:header for HTTP headers."""
211
+ name = self.get_attr(element, 'name')
212
+ value = self.get_attr(element, 'value')
213
+
214
+ if not name:
215
+ raise ParserError("Header requires 'name' attribute")
216
+
217
+ return HeaderNode(name, value or '')
@@ -0,0 +1,116 @@
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
+ invoke_node.endpoint = self.get_attr(element, 'endpoint')
50
+ invoke_node.service = self.get_attr(element, 'service')
51
+
52
+ # HTTP attributes
53
+ invoke_node.method = self.get_attr(element, 'method', 'GET').upper()
54
+ invoke_node.content_type = self.get_attr(element, 'contentType', 'application/json')
55
+
56
+ # Authentication
57
+ invoke_node.auth_type = self.get_attr(element, 'authType')
58
+ invoke_node.auth_token = self.get_attr(element, 'authToken')
59
+ invoke_node.auth_header = self.get_attr(element, 'authHeader')
60
+ invoke_node.auth_username = self.get_attr(element, 'authUsername')
61
+ invoke_node.auth_password = self.get_attr(element, 'authPassword')
62
+
63
+ # Timeouts and retries
64
+ invoke_node.timeout = self.get_int_attr(element, 'timeout', 0) or None
65
+ invoke_node.retry = self.get_int_attr(element, 'retry', 0) or None
66
+ invoke_node.retry_delay = self.get_int_attr(element, 'retryDelay', 0) or None
67
+
68
+ # Response handling
69
+ invoke_node.response_format = self.get_attr(element, 'responseFormat', 'auto')
70
+ invoke_node.transform = self.get_attr(element, 'transform')
71
+ invoke_node.cache = self.get_bool_attr(element, 'cache', False)
72
+ invoke_node.ttl = self.get_int_attr(element, 'ttl', 0) or None
73
+ invoke_node.result = self.get_attr(element, 'result')
74
+
75
+ # Parse child elements
76
+ for child in element:
77
+ child_type = self.get_element_name(child)
78
+
79
+ if child_type == 'header':
80
+ header = self._parse_header(child)
81
+ invoke_node.add_header(header)
82
+ elif child_type == 'param':
83
+ param = self._parse_param(child)
84
+ invoke_node.add_param(param)
85
+ elif child_type == 'body':
86
+ invoke_node.body = child.text or ""
87
+
88
+ return invoke_node
89
+
90
+ def _parse_header(self, element: ET.Element) -> InvokeHeaderNode:
91
+ """Parse q:header within q:invoke."""
92
+ name = self.get_attr(element, 'name')
93
+ value = self.get_attr(element, 'value')
94
+
95
+ if not name:
96
+ raise ParserError("Invoke header requires 'name' attribute")
97
+ if value is None:
98
+ raise ParserError(f"Invoke header '{name}' requires 'value' attribute")
99
+
100
+ return InvokeHeaderNode(name, value)
101
+
102
+ def _parse_param(self, element: ET.Element) -> QuantumParam:
103
+ """Parse q:param within q:invoke."""
104
+ name = self.get_attr(element, 'name')
105
+ value = self.get_attr(element, 'value')
106
+ param_type = self.get_attr(element, 'type', 'string')
107
+
108
+ if not name:
109
+ raise ParserError("Param requires 'name' attribute")
110
+
111
+ param = QuantumParam(name, param_type)
112
+ param.value = value
113
+ param.required = self.get_bool_attr(element, 'required', False)
114
+ param.default = self.get_attr(element, 'default')
115
+
116
+ return param
@@ -0,0 +1,188 @@
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
+ class QueryParser(BaseTagParser):
15
+ """
16
+ Parser for q:query statements.
17
+
18
+ Supports:
19
+ - SQL queries with parameter binding
20
+ - Query of Queries (source attribute)
21
+ - Caching and pagination
22
+ - RAG/knowledge base queries
23
+ """
24
+
25
+ @property
26
+ def tag_names(self) -> List[str]:
27
+ return ['query']
28
+
29
+ def parse(self, element: ET.Element) -> QueryNode:
30
+ """
31
+ Parse q:query statement.
32
+
33
+ Args:
34
+ element: XML element for q:query
35
+
36
+ Returns:
37
+ QueryNode AST node
38
+ """
39
+ name = self.get_attr(element, 'name')
40
+ datasource = self.get_attr(element, 'datasource')
41
+ source = self.get_attr(element, 'source')
42
+
43
+ # A write query (UPDATE/INSERT/DELETE) has no result set to bind, so
44
+ # name is optional — requiring it rejected the ordinary CFML-style
45
+ # idiom used across the job examples. Reads still want a name; without
46
+ # one there is simply nothing to reference the rows by.
47
+ if not name:
48
+ name = ''
49
+ if not datasource and not source:
50
+ raise ParserError("Query requires either 'datasource' or 'source' attribute")
51
+
52
+ # Extract SQL content
53
+ sql_parts = []
54
+ if element.text:
55
+ sql_parts.append(element.text.strip())
56
+
57
+ for child in element:
58
+ if child.tail:
59
+ sql_parts.append(child.tail.strip())
60
+
61
+ sql = '\n'.join(part for part in sql_parts if part)
62
+
63
+ # Create query node
64
+ query_node = QueryNode(name, datasource or '', sql)
65
+
66
+ # Parse attributes
67
+ query_node.source = source
68
+ query_node.cache = self.get_bool_attr(element, 'cache', False)
69
+ query_node.ttl = self.get_int_attr(element, 'ttl', 0) or None
70
+ query_node.reactive = self.get_bool_attr(element, 'reactive', False)
71
+ query_node.interval = self.get_int_attr(element, 'interval', 0) or None
72
+ query_node.paginate = self.get_bool_attr(element, 'paginate', False)
73
+ query_node.page = self.get_int_attr(element, 'page', 0) or None
74
+ # Accept both spellings: the codebase convention is camelCase, but
75
+ # snake_case reads naturally in XML and the examples used it —
76
+ # the mismatch made page_size None and crashed pagination with
77
+ # 'unsupported operand type(s) for +: int and NoneType'.
78
+ query_node.page_size = (
79
+ self.get_int_attr(element, 'pageSize', 0)
80
+ or self.get_int_attr(element, 'page_size', 0)
81
+ or None
82
+ )
83
+ query_node.timeout = self.get_int_attr(element, 'timeout', 0) or None
84
+ query_node.maxrows = self.get_int_attr(element, 'maxrows', 0) or None
85
+ query_node.result = self.get_attr(element, 'result')
86
+
87
+ # RAG/Knowledge attributes
88
+ query_node.mode = self.get_attr(element, 'mode')
89
+ query_node.rag_model = self.get_attr(element, 'model')
90
+
91
+ # Parse q:param children
92
+ for child in element:
93
+ child_type = self.get_element_name(child)
94
+ if child_type == 'param':
95
+ param_node = self._parse_query_param(child)
96
+ query_node.add_param(param_node)
97
+
98
+ # Validate: reject direct {var} interpolation (SQL injection risk)
99
+ if sql and re.search(r'\{[a-zA-Z_]\w*\}', sql):
100
+ raise ParserError(
101
+ f"Query '{name}': direct {{var}} interpolation in SQL is not allowed. "
102
+ "Use :param_name with <q:param> for safe parameter binding"
103
+ )
104
+
105
+ # Validate: all :param references must have matching <q:param>
106
+ if sql:
107
+ sql_params = self._named_params(sql)
108
+ declared_params = {p.name for p in query_node.params}
109
+ missing = sql_params - declared_params
110
+ if missing:
111
+ raise ParserError(
112
+ f"Query '{name}': SQL parameter(s) {', '.join(':' + p for p in sorted(missing))} "
113
+ "declared in SQL but no matching <q:param> element provided"
114
+ )
115
+
116
+ return query_node
117
+
118
+ @staticmethod
119
+ def _named_params(sql: str) -> set:
120
+ """Os `:nome` que sao REALMENTE parametros de bind.
121
+
122
+ Isto era `re.findall(r':([a-zA-Z_]\\w*)', sql)` sobre o SQL cru, e
123
+ varria tambem o que esta dentro de aspas e de comentarios:
124
+
125
+ TO_CHAR(c.created_at, 'YYYY-MM-DD HH24:MI') -> achava `:MI`
126
+ SELECT x::text FROM t -> achava `:text`
127
+
128
+ As duas formas sao SQL correto e comum — a primeira aparece em
129
+ projects/blog/components/post.q, que por causa disto nao parseava:
130
+ o arquivo era recusado com "SQL parameter(s) :MI declared in SQL but
131
+ no matching <q:param>", e nao havia parametro nenhum para declarar.
132
+
133
+ A varredura pula literais de texto (com '' e "" escapados), o cast
134
+ `::` do PostgreSQL, comentarios de linha `--` e de bloco.
135
+ """
136
+ encontrados = set()
137
+ i, n = 0, len(sql)
138
+ while i < n:
139
+ c = sql[i]
140
+ if c in ("'", '"'):
141
+ aspas = c
142
+ i += 1
143
+ while i < n:
144
+ if sql[i] == aspas:
145
+ if i + 1 < n and sql[i + 1] == aspas:
146
+ i += 2 # '' escapado dentro do literal
147
+ continue
148
+ i += 1
149
+ break
150
+ i += 1
151
+ continue
152
+ if sql.startswith('--', i):
153
+ fim = sql.find('\n', i)
154
+ i = n if fim == -1 else fim + 1
155
+ continue
156
+ if sql.startswith('/*', i):
157
+ fim = sql.find('*/', i)
158
+ i = n if fim == -1 else fim + 2
159
+ continue
160
+ if c == ':':
161
+ if sql.startswith('::', i):
162
+ i += 2 # cast do PostgreSQL, nao parametro
163
+ continue
164
+ m = re.match(r':([a-zA-Z_]\w*)', sql[i:])
165
+ if m:
166
+ encontrados.add(m.group(1))
167
+ i += m.end()
168
+ continue
169
+ i += 1
170
+ return encontrados
171
+
172
+ def _parse_query_param(self, element: ET.Element) -> QueryParamNode:
173
+ """Parse q:param within q:query."""
174
+ name = self.get_attr(element, 'name')
175
+ value = self.get_attr(element, 'value')
176
+ param_type = self.get_attr(element, 'type', 'string')
177
+
178
+ if not name:
179
+ raise ParserError("Query parameter requires 'name' attribute")
180
+ if value is None:
181
+ raise ParserError(f"Query parameter '{name}' requires 'value' attribute")
182
+
183
+ param_node = QueryParamNode(name, value, param_type)
184
+ param_node.null = self.get_bool_attr(element, 'null', False)
185
+ param_node.max_length = self.get_int_attr(element, 'maxLength', 0) or None
186
+ param_node.scale = self.get_int_attr(element, 'scale', 0) or None
187
+
188
+ return param_node