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,322 @@
1
+ """
2
+ Quantum Database Service - Manages database connections and query execution
3
+ """
4
+
5
+ import time
6
+ import requests
7
+ from typing import Dict, Any, List, Optional
8
+ from dataclasses import dataclass, asdict
9
+
10
+
11
+ @dataclass
12
+ class QueryResult:
13
+ """Container for query results and metadata"""
14
+ data: List[Dict[str, Any]]
15
+ column_list: List[str]
16
+ execution_time: float # in milliseconds
17
+ record_count: int
18
+ sql: Optional[str] = None # Set in debug mode
19
+ page: Optional[int] = None
20
+ total_pages: Optional[int] = None
21
+ has_more: bool = False
22
+ cached: bool = False
23
+
24
+ def to_dict(self):
25
+ """Convert to dictionary for template access"""
26
+ result = asdict(self)
27
+ # Rename column_list to columnList for JavaScript compatibility
28
+ result['columnList'] = result.pop('column_list')
29
+ result['recordCount'] = result.pop('record_count')
30
+ result['executionTime'] = result.pop('execution_time')
31
+ result['totalPages'] = result.pop('total_pages')
32
+ result['hasMore'] = result.pop('has_more')
33
+ return result
34
+
35
+
36
+ class DatabaseConnectionError(Exception):
37
+ """Raised when database connection fails"""
38
+ pass
39
+
40
+
41
+ class QueryExecutionError(Exception):
42
+ """Raised when query execution fails"""
43
+ pass
44
+
45
+
46
+ class DatabaseService:
47
+ """Manages database connections and query execution"""
48
+
49
+ def __init__(self, admin_api_url: str = "http://localhost:8000"):
50
+ self.admin_api_url = admin_api_url
51
+ self.connection_pool: Dict[str, Any] = {} # Cache connections by datasource name
52
+
53
+ def get_datasource_config(self, datasource_name: str) -> Dict[str, Any]:
54
+ """
55
+ Fetch datasource configuration from Quantum Admin API
56
+
57
+ Args:
58
+ datasource_name: Name of the datasource
59
+
60
+ Returns:
61
+ Dict with datasource configuration
62
+
63
+ Raises:
64
+ DatabaseConnectionError: If datasource not found or not ready
65
+ """
66
+ try:
67
+ url = f"{self.admin_api_url}/api/datasources/by-name/{datasource_name}"
68
+ response = requests.get(url, timeout=5)
69
+
70
+ if response.status_code == 404:
71
+ raise DatabaseConnectionError(f"Datasource '{datasource_name}' not found")
72
+ elif response.status_code == 503:
73
+ raise DatabaseConnectionError(
74
+ f"Datasource '{datasource_name}' is not ready. Please check Quantum Admin."
75
+ )
76
+ elif response.status_code != 200:
77
+ raise DatabaseConnectionError(
78
+ f"Failed to fetch datasource '{datasource_name}': {response.status_code}"
79
+ )
80
+
81
+ return response.json()
82
+
83
+ except requests.exceptions.ConnectionError:
84
+ raise DatabaseConnectionError(
85
+ f"Cannot connect to Quantum Admin API at {self.admin_api_url}. "
86
+ "Please ensure Quantum Admin is running."
87
+ )
88
+ except requests.exceptions.Timeout:
89
+ raise DatabaseConnectionError(
90
+ f"Timeout connecting to Quantum Admin API at {self.admin_api_url}"
91
+ )
92
+
93
+ def get_connection(self, datasource_config: Dict[str, Any]):
94
+ """
95
+ Get or create database connection
96
+
97
+ Args:
98
+ datasource_config: Datasource configuration from Admin API
99
+
100
+ Returns:
101
+ Database connection object
102
+
103
+ Raises:
104
+ DatabaseConnectionError: If connection fails
105
+ """
106
+ datasource_name = datasource_config['name']
107
+ db_type = datasource_config['type']
108
+
109
+ # Check if connection already exists in pool
110
+ if datasource_name in self.connection_pool:
111
+ conn = self.connection_pool[datasource_name]
112
+ # Test connection is still alive
113
+ if self._test_connection(conn, db_type):
114
+ return conn
115
+ else:
116
+ # Connection dead, remove from pool
117
+ del self.connection_pool[datasource_name]
118
+
119
+ # Create new connection
120
+ try:
121
+ if db_type == 'postgresql':
122
+ conn = self._create_postgres_connection(datasource_config)
123
+ elif db_type in ['mysql', 'mariadb']:
124
+ conn = self._create_mysql_connection(datasource_config)
125
+ else:
126
+ raise DatabaseConnectionError(f"Unsupported database type: {db_type}")
127
+
128
+ # Store in pool
129
+ self.connection_pool[datasource_name] = conn
130
+ return conn
131
+
132
+ except Exception as e:
133
+ raise DatabaseConnectionError(f"Failed to connect to database: {e}")
134
+
135
+ def _create_postgres_connection(self, config: Dict[str, Any]):
136
+ """Create PostgreSQL connection"""
137
+ try:
138
+ import psycopg2
139
+ return psycopg2.connect(
140
+ host=config['host'],
141
+ port=config['port'],
142
+ database=config['database_name'],
143
+ user=config['username'],
144
+ password=config['password']
145
+ )
146
+ except ImportError:
147
+ raise DatabaseConnectionError(
148
+ "psycopg2 not installed. Install with: pip install psycopg2-binary"
149
+ )
150
+
151
+ def _create_mysql_connection(self, config: Dict[str, Any]):
152
+ """Create MySQL/MariaDB connection"""
153
+ try:
154
+ import pymysql
155
+ return pymysql.connect(
156
+ host=config['host'],
157
+ port=config['port'],
158
+ database=config['database_name'],
159
+ user=config['username'],
160
+ password=config['password'],
161
+ cursorclass=pymysql.cursors.DictCursor # Return results as dictionaries
162
+ )
163
+ except ImportError:
164
+ raise DatabaseConnectionError(
165
+ "pymysql not installed. Install with: pip install pymysql"
166
+ )
167
+
168
+ def _test_connection(self, conn, db_type: str) -> bool:
169
+ """Test if connection is still alive"""
170
+ try:
171
+ cursor = conn.cursor()
172
+ if db_type == 'postgresql':
173
+ cursor.execute("SELECT 1")
174
+ elif db_type in ['mysql', 'mariadb']:
175
+ cursor.execute("SELECT 1")
176
+ cursor.close()
177
+ return True
178
+ except:
179
+ return False
180
+
181
+ def execute_query(self, datasource_name: str, sql: str, params: Dict[str, Any] = None) -> QueryResult:
182
+ """
183
+ Execute SQL with parameter binding
184
+
185
+ Args:
186
+ datasource_name: Name of the datasource
187
+ sql: SQL query with :param placeholders
188
+ params: Dictionary of parameter values
189
+
190
+ Returns:
191
+ QueryResult with data and metadata
192
+
193
+ Raises:
194
+ QueryExecutionError: If query execution fails
195
+ """
196
+ if params is None:
197
+ params = {}
198
+
199
+ # Get datasource config
200
+ config = self.get_datasource_config(datasource_name)
201
+ db_type = config['type']
202
+
203
+ # Get connection
204
+ conn = self.get_connection(config)
205
+
206
+ # Start timing
207
+ start_time = time.time()
208
+
209
+ try:
210
+ cursor = conn.cursor()
211
+
212
+ # Convert :param syntax to database-specific placeholder
213
+ prepared_sql, prepared_params = self._prepare_query(sql, params, db_type)
214
+
215
+ # Execute query
216
+ cursor.execute(prepared_sql, prepared_params)
217
+
218
+ # Check if it's a SELECT query (returns data)
219
+ if cursor.description:
220
+ # Fetch results
221
+ rows = cursor.fetchall()
222
+
223
+ # Get column names
224
+ if db_type == 'postgresql':
225
+ column_names = [desc[0] for desc in cursor.description]
226
+ # Convert rows to dictionaries for PostgreSQL
227
+ data = [dict(zip(column_names, row)) for row in rows]
228
+ elif db_type in ['mysql', 'mariadb']:
229
+ # pymysql with DictCursor already returns dictionaries
230
+ column_names = list(rows[0].keys()) if rows else []
231
+ data = rows
232
+ else:
233
+ column_names = []
234
+ data = []
235
+
236
+ record_count = len(data)
237
+ else:
238
+ # Non-SELECT query (INSERT, UPDATE, DELETE)
239
+ conn.commit()
240
+ data = []
241
+ column_names = []
242
+ record_count = cursor.rowcount
243
+
244
+ # For INSERT with RETURNING (PostgreSQL)
245
+ if db_type == 'postgresql' and cursor.description:
246
+ rows = cursor.fetchall()
247
+ column_names = [desc[0] for desc in cursor.description]
248
+ data = [dict(zip(column_names, row)) for row in rows]
249
+ record_count = len(data)
250
+
251
+ cursor.close()
252
+
253
+ # Calculate execution time
254
+ execution_time = (time.time() - start_time) * 1000 # Convert to ms
255
+
256
+ return QueryResult(
257
+ data=data,
258
+ column_list=column_names,
259
+ execution_time=execution_time,
260
+ record_count=record_count
261
+ )
262
+
263
+ except Exception as e:
264
+ # Rollback on error
265
+ conn.rollback()
266
+ raise QueryExecutionError(f"Query execution failed: {e}")
267
+
268
+ def _prepare_query(self, sql: str, params: Dict[str, Any], db_type: str) -> tuple:
269
+ """
270
+ Convert :param syntax to database-specific placeholders
271
+
272
+ Args:
273
+ sql: SQL with :param placeholders
274
+ params: Dictionary of parameters
275
+ db_type: Database type (postgresql, mysql, mariadb)
276
+
277
+ Returns:
278
+ Tuple of (prepared_sql, prepared_params)
279
+ """
280
+ import re
281
+
282
+ # Find all :param placeholders
283
+ param_pattern = r':(\w+)'
284
+ param_names = re.findall(param_pattern, sql)
285
+
286
+ if db_type == 'postgresql':
287
+ # PostgreSQL uses %s placeholders
288
+ prepared_sql = sql
289
+ for i, param_name in enumerate(param_names, 1):
290
+ # Replace :param with %s
291
+ prepared_sql = prepared_sql.replace(f':{param_name}', '%s', 1)
292
+
293
+ # Build parameter list in correct order
294
+ prepared_params = [params.get(name) for name in param_names]
295
+
296
+ elif db_type in ['mysql', 'mariadb']:
297
+ # MySQL uses %s placeholders
298
+ prepared_sql = sql
299
+ for param_name in param_names:
300
+ prepared_sql = prepared_sql.replace(f':{param_name}', '%s', 1)
301
+
302
+ # Build parameter list in correct order
303
+ prepared_params = [params.get(name) for name in param_names]
304
+
305
+ else:
306
+ raise QueryExecutionError(f"Unsupported database type: {db_type}")
307
+
308
+ return prepared_sql, prepared_params
309
+
310
+ def close_connection(self, datasource_name: str):
311
+ """Close database connection"""
312
+ if datasource_name in self.connection_pool:
313
+ try:
314
+ self.connection_pool[datasource_name].close()
315
+ except:
316
+ pass
317
+ del self.connection_pool[datasource_name]
318
+
319
+ def close_all_connections(self):
320
+ """Close all database connections"""
321
+ for datasource_name in list(self.connection_pool.keys()):
322
+ self.close_connection(datasource_name)
@@ -0,0 +1,20 @@
1
+ """
2
+ Quantum Query Validators — re-export of the canonical implementation.
3
+
4
+ This module used to hold a full 290-line COPY of QueryValidator. Nothing
5
+ imported it, so it quietly drifted out of step with the module the runtime
6
+ actually uses (quantum/runtime/query_validators.py) — and by the time the
7
+ audit looked, the two copies of a SECURITY validator disagreed with each
8
+ other. A stale duplicate of a validator is worse than no duplicate: whoever
9
+ reads this one believes they are reading what runs.
10
+
11
+ It re-exports instead of copying, so the two cannot diverge again. Import
12
+ from quantum.runtime.query_validators directly in new code.
13
+ """
14
+
15
+ from quantum.runtime.query_validators import ( # noqa: F401
16
+ QueryValidationError,
17
+ QueryValidator,
18
+ )
19
+
20
+ __all__ = ["QueryValidationError", "QueryValidator"]
@@ -0,0 +1,11 @@
1
+ """
2
+ State Management Feature (q:set)
3
+
4
+ Enables declaration and manipulation of component state variables
5
+ with type safety, validation, and scope management.
6
+ """
7
+
8
+ from .ast_node import SetNode
9
+
10
+ __all__ = ['SetNode']
11
+ __version__ = '1.0.0'
@@ -0,0 +1,228 @@
1
+ """
2
+ AST Node for State Management (q:set)
3
+
4
+ This module contains the SetNode class, which represents the <q:set> tag
5
+ for state management in Quantum Language.
6
+
7
+ State Persistence Support:
8
+ - persist="local" - localStorage (browser) or file (desktop)
9
+ - persist="session" - sessionStorage (browser) or memory (desktop)
10
+ - persist="sync" - synchronized across tabs/windows (browser) or network (desktop)
11
+ """
12
+
13
+ from typing import Dict, Any, List, Optional
14
+
15
+ # Import base class from parent AST nodes
16
+ import sys
17
+ from pathlib import Path
18
+ from quantum.core.ast_nodes import QuantumNode
19
+
20
+
21
+ class SetNode(QuantumNode):
22
+ """Represents a <q:set> - State Management"""
23
+
24
+ def __init__(self, name: str):
25
+ # Obrigatórios
26
+ self.name = name
27
+
28
+ # Tipo e valor
29
+ self.type = "string"
30
+ self.value = None
31
+ self.default = None
32
+
33
+ # Validação
34
+ self.required = False
35
+ self.nullable = True
36
+ self.validate_rule = None # Renomeado de 'validate' para não conflitar com o método
37
+ self.pattern = None
38
+ self.mask = None
39
+ self.range = None
40
+ self.enum = None
41
+ self.unique = None
42
+ self.min = None
43
+ self.max = None
44
+ self.minlength = None
45
+ self.maxlength = None
46
+
47
+ # Comportamento
48
+ self.scope = "local"
49
+ self.operation = "assign"
50
+ self.step = 1
51
+
52
+ # Para operações em collections
53
+ self.index = None
54
+ self.key = None
55
+ self.source = None
56
+
57
+ # State Persistence
58
+ self.persist: Optional[str] = None # None, "local", "session", "sync"
59
+ self.persist_key: Optional[str] = None # Custom storage key (defaults to name)
60
+ self.persist_encrypt: bool = False # Encrypt persisted data
61
+ self.persist_ttl: Optional[int] = None # TTL in seconds for cached persistence
62
+
63
+ def to_dict(self) -> Dict[str, Any]:
64
+ result = {
65
+ "type": "set",
66
+ "name": self.name,
67
+ "value_type": self.type,
68
+ "value": self.value,
69
+ "default": self.default,
70
+ "scope": self.scope,
71
+ "operation": self.operation,
72
+ "required": self.required,
73
+ "nullable": self.nullable,
74
+ "validation": {
75
+ "validate": self.validate_rule,
76
+ "pattern": self.pattern,
77
+ "mask": self.mask,
78
+ "range": self.range,
79
+ "enum": self.enum,
80
+ "unique": self.unique,
81
+ "min": self.min,
82
+ "max": self.max,
83
+ "minlength": self.minlength,
84
+ "maxlength": self.maxlength
85
+ }
86
+ }
87
+ # Add persistence info if enabled
88
+ if self.persist:
89
+ result["persistence"] = {
90
+ "scope": self.persist,
91
+ "key": self.persist_key or self.name,
92
+ "encrypt": self.persist_encrypt,
93
+ "ttl": self.persist_ttl
94
+ }
95
+ return result
96
+
97
+ def validate(self) -> List[str]:
98
+ errors = []
99
+
100
+ if not self.name:
101
+ errors.append("Set variable name is required")
102
+
103
+ # Validar tipo
104
+ # 'integer' was missing here even though SetExecutor._convert_to_type()
105
+ # already handles it (target_type in ("integer", "number")), and other
106
+ # AST nodes like AgentToolParamNode accept it — this rejected any
107
+ # q:set/q:param using type="integer" (test-query-loop.q,
108
+ # test-query-params.q, and every qg: sprite param in game_engine_2d).
109
+ valid_types = ['string', 'number', 'integer', 'decimal', 'boolean', 'date', 'datetime',
110
+ 'array', 'object', 'json', 'binary', 'null']
111
+ if self.type not in valid_types:
112
+ errors.append(f"Invalid type: {self.type}. Must be one of {valid_types}")
113
+
114
+ # Validar operação
115
+ valid_operations = ['assign', 'increment', 'decrement', 'add', 'multiply',
116
+ 'append', 'prepend', 'remove', 'removeAt', 'clear', 'sort',
117
+ 'reverse', 'unique', 'merge', 'setProperty', 'deleteProperty',
118
+ 'clone', 'uppercase', 'lowercase', 'trim', 'format']
119
+ if self.operation not in valid_operations:
120
+ errors.append(f"Invalid operation: {self.operation}")
121
+
122
+ # Validar valor obrigatório para assign
123
+ if self.operation == "assign" and not self.value and not self.default and self.required:
124
+ errors.append("Set value or default is required when required=true")
125
+
126
+ # Validar enum
127
+ if self.enum and self.value:
128
+ enum_values = [v.strip() for v in self.enum.split(',')]
129
+ if self.value not in enum_values:
130
+ errors.append(f"Value '{self.value}' not in enum: {enum_values}")
131
+
132
+ # Validar minlength/maxlength
133
+ if self.type == "string" and self.value:
134
+ if self.minlength and len(str(self.value)) < int(self.minlength):
135
+ errors.append(f"Value length {len(str(self.value))} is less than minlength {self.minlength}")
136
+ if self.maxlength and len(str(self.value)) > int(self.maxlength):
137
+ errors.append(f"Value length {len(str(self.value))} exceeds maxlength {self.maxlength}")
138
+
139
+ # Validar persist scope
140
+ if self.persist:
141
+ valid_persist_scopes = ['local', 'session', 'sync']
142
+ if self.persist not in valid_persist_scopes:
143
+ errors.append(f"Invalid persist scope: {self.persist}. Must be one of {valid_persist_scopes}")
144
+
145
+ return errors
146
+
147
+
148
+ class PersistNode(QuantumNode):
149
+ """
150
+ Represents a <q:persist> - Explicit persistence configuration.
151
+
152
+ Allows fine-grained control over state persistence beyond the persist attribute on q:set.
153
+
154
+ Examples:
155
+ <q:persist scope="local" prefix="myapp_">
156
+ <q:var name="theme" />
157
+ <q:var name="locale" />
158
+ </q:persist>
159
+
160
+ <q:persist scope="sync" key="user_preferences" encrypt="true">
161
+ <q:var name="darkMode" />
162
+ <q:var name="fontSize" />
163
+ </q:persist>
164
+
165
+ Attributes:
166
+ scope: "local" (localStorage), "session" (sessionStorage), "sync" (cross-tab/network)
167
+ prefix: Optional prefix for all storage keys
168
+ key: If set, all vars are stored under this single key as an object
169
+ encrypt: Whether to encrypt persisted data
170
+ ttl: Time-to-live in seconds (for cache invalidation)
171
+ storage: Override storage backend ("localStorage", "indexedDB", "file", "db")
172
+ """
173
+
174
+ def __init__(
175
+ self,
176
+ scope: str = "local",
177
+ prefix: Optional[str] = None,
178
+ key: Optional[str] = None,
179
+ encrypt: bool = False,
180
+ ttl: Optional[int] = None,
181
+ storage: Optional[str] = None
182
+ ):
183
+ self.scope = scope
184
+ self.prefix = prefix
185
+ self.key = key
186
+ self.encrypt = encrypt
187
+ self.ttl = ttl
188
+ self.storage = storage
189
+ self.variables: List[str] = [] # List of variable names to persist
190
+
191
+ def add_variable(self, name: str):
192
+ """Add a variable name to persist."""
193
+ self.variables.append(name)
194
+
195
+ def to_dict(self) -> Dict[str, Any]:
196
+ return {
197
+ "type": "persist",
198
+ "scope": self.scope,
199
+ "prefix": self.prefix,
200
+ "key": self.key,
201
+ "encrypt": self.encrypt,
202
+ "ttl": self.ttl,
203
+ "storage": self.storage,
204
+ "variables": self.variables
205
+ }
206
+
207
+ def validate(self) -> List[str]:
208
+ errors = []
209
+
210
+ # Validate scope
211
+ valid_scopes = ['local', 'session', 'sync']
212
+ if self.scope not in valid_scopes:
213
+ errors.append(f"Invalid persist scope: {self.scope}. Must be one of {valid_scopes}")
214
+
215
+ # Validate storage backend if specified
216
+ if self.storage:
217
+ valid_storage = ['localStorage', 'sessionStorage', 'indexedDB', 'file', 'db']
218
+ if self.storage not in valid_storage:
219
+ errors.append(f"Invalid storage backend: {self.storage}. Must be one of {valid_storage}")
220
+
221
+ # Validate variables
222
+ if not self.variables:
223
+ errors.append("q:persist must contain at least one q:var element")
224
+
225
+ return errors
226
+
227
+ def __repr__(self):
228
+ return f'<PersistNode scope={self.scope} vars={len(self.variables)}>'
@@ -0,0 +1,21 @@
1
+ """
2
+ Terminal Engine - AST nodes and parser for the qt: namespace.
3
+ """
4
+
5
+ from .ast_nodes import (
6
+ ScreenNode, PanelNode, LayoutNode, TableNode, ColumnNode,
7
+ TerminalInputNode, ButtonNode, MenuNode, OptionNode,
8
+ TextNode_Terminal, ProgressNode, TreeNode, TabsNode, TabNode,
9
+ LogNode_Terminal, HeaderNode_Terminal, FooterNode, StatusNode,
10
+ KeybindingNode, TimerNode_Terminal, ServiceNode, CssNode,
11
+ OnEventNode_Terminal, RawCodeNode_Terminal,
12
+ )
13
+
14
+ __all__ = [
15
+ 'ScreenNode', 'PanelNode', 'LayoutNode', 'TableNode', 'ColumnNode',
16
+ 'TerminalInputNode', 'ButtonNode', 'MenuNode', 'OptionNode',
17
+ 'TextNode_Terminal', 'ProgressNode', 'TreeNode', 'TabsNode', 'TabNode',
18
+ 'LogNode_Terminal', 'HeaderNode_Terminal', 'FooterNode', 'StatusNode',
19
+ 'KeybindingNode', 'TimerNode_Terminal', 'ServiceNode', 'CssNode',
20
+ 'OnEventNode_Terminal', 'RawCodeNode_Terminal',
21
+ ]