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,533 @@
1
+ """
2
+ Data Import Feature - Runtime
3
+ Handles execution of all data import and transformation operations
4
+ """
5
+
6
+ import time
7
+ import json
8
+ import csv
9
+ import requests
10
+ import xml.etree.ElementTree as ET
11
+ from pathlib import Path
12
+ from typing import Dict, Any, Optional, List
13
+ from dataclasses import dataclass
14
+ from io import StringIO
15
+ import re
16
+
17
+ from quantum.core.expressions import ExpressionEvaluator
18
+
19
+ # Shared with ComponentRuntime and HTMLRenderer, so a {field} expression in a
20
+ # q:data filter means the same thing it means anywhere else in the language.
21
+ _EXPRESSIONS = ExpressionEvaluator()
22
+ _BINDING = re.compile(r'\{([^}]+)\}')
23
+
24
+
25
+ @dataclass
26
+ class DataResult:
27
+ """Result object for all data operations"""
28
+ success: bool
29
+ data: Any = None
30
+ error: Optional[Dict[str, Any]] = None
31
+ recordCount: int = 0
32
+ loadTime: float = 0.0
33
+ cached: bool = False
34
+ source: str = ""
35
+
36
+
37
+ class DataImportService:
38
+ """Service to handle all types of data import and transformation"""
39
+
40
+ def __init__(self):
41
+ self.cache: Dict[str, Any] = {} # Simple in-memory cache
42
+
43
+ def import_data(
44
+ self,
45
+ data_type: str,
46
+ source: str,
47
+ params: Dict[str, Any],
48
+ context: Any = None
49
+ ) -> DataResult:
50
+ """Main data import dispatcher"""
51
+ start_time = time.time()
52
+
53
+ try:
54
+ # Check cache if enabled
55
+ cache_enabled = params.get('cache', True)
56
+ if cache_enabled:
57
+ cache_key = f"data_{source}_{hash(str(params))}"
58
+ cached_result = self.get_from_cache(cache_key)
59
+ if cached_result is not None:
60
+ cached_result.cached = True
61
+ return cached_result
62
+
63
+ # Dispatch based on type
64
+ if data_type == "csv":
65
+ result = self._import_csv(source, params)
66
+ elif data_type == "json":
67
+ result = self._import_json(source, params)
68
+ elif data_type == "xml":
69
+ result = self._import_xml(source, params)
70
+ elif data_type == "transform":
71
+ result = self._transform_data(source, params, context)
72
+ else:
73
+ return DataResult(
74
+ success=False,
75
+ error={"message": f"Unknown data type: {data_type}"},
76
+ source=source
77
+ )
78
+
79
+ # Calculate load time
80
+ load_time = (time.time() - start_time) * 1000 # Convert to ms
81
+ result.loadTime = load_time
82
+
83
+ # Apply transformations if specified
84
+ if 'transforms' in params and params['transforms']:
85
+ result = self._apply_transformations(result, params['transforms'], context)
86
+
87
+ # Cache result if enabled
88
+ if cache_enabled and result.success:
89
+ ttl = params.get('ttl')
90
+ self.put_in_cache(cache_key, result, ttl)
91
+
92
+ return result
93
+
94
+ except Exception as e:
95
+ load_time = (time.time() - start_time) * 1000
96
+ return DataResult(
97
+ success=False,
98
+ error={
99
+ "message": str(e),
100
+ "type": type(e).__name__
101
+ },
102
+ loadTime=load_time,
103
+ source=source
104
+ )
105
+
106
+ def _import_csv(self, source: str, params: Dict[str, Any]) -> DataResult:
107
+ """Import CSV data from file or URL"""
108
+ try:
109
+ # Get CSV content
110
+ content = self._get_source_content(source, params.get('headers', []))
111
+
112
+ # Parse CSV
113
+ delimiter = params.get('delimiter', ',')
114
+ quote = params.get('quote', '"')
115
+ has_header = params.get('header', True)
116
+ encoding = params.get('encoding', 'utf-8')
117
+ skip_rows = params.get('skip_rows', 0)
118
+ columns = params.get('columns', [])
119
+
120
+ # Parse CSV content
121
+ csv_reader = csv.DictReader(
122
+ StringIO(content),
123
+ delimiter=delimiter,
124
+ quotechar=quote
125
+ ) if has_header else csv.reader(StringIO(content), delimiter=delimiter, quotechar=quote)
126
+
127
+ # Skip rows if needed
128
+ for _ in range(skip_rows):
129
+ try:
130
+ next(csv_reader)
131
+ except StopIteration:
132
+ break
133
+
134
+ # Convert to list of dictionaries
135
+ data = []
136
+ for row in csv_reader:
137
+ if has_header:
138
+ # Row is already a dict
139
+ record = self._convert_csv_row(row, columns)
140
+ else:
141
+ # Row is a list - create dict from column definitions
142
+ if columns:
143
+ record = {}
144
+ for i, col_def in enumerate(columns):
145
+ if i < len(row):
146
+ col_name = col_def.get('name')
147
+ col_value = row[i]
148
+ record[col_name] = self._convert_type(col_value, col_def.get('type', 'string'))
149
+ else:
150
+ # No columns defined - use numeric keys
151
+ record = {str(i): val for i, val in enumerate(row)}
152
+
153
+ data.append(record)
154
+
155
+ return DataResult(
156
+ success=True,
157
+ data=data,
158
+ recordCount=len(data),
159
+ source=source
160
+ )
161
+
162
+ except Exception as e:
163
+ return DataResult(
164
+ success=False,
165
+ error={
166
+ "message": f"CSV import failed: {str(e)}",
167
+ "type": type(e).__name__
168
+ },
169
+ source=source
170
+ )
171
+
172
+ def _import_json(self, source: str, params: Dict[str, Any]) -> DataResult:
173
+ """Import JSON data from file or URL"""
174
+ try:
175
+ # Get JSON content
176
+ content = self._get_source_content(source, params.get('headers', []))
177
+
178
+ # Parse JSON
179
+ data = json.loads(content)
180
+
181
+ # Ensure data is a list
182
+ if not isinstance(data, list):
183
+ # If it's a dict, wrap it in a list
184
+ data = [data]
185
+
186
+ return DataResult(
187
+ success=True,
188
+ data=data,
189
+ recordCount=len(data) if isinstance(data, list) else 1,
190
+ source=source
191
+ )
192
+
193
+ except json.JSONDecodeError as e:
194
+ return DataResult(
195
+ success=False,
196
+ error={
197
+ "message": f"Invalid JSON: {str(e)}",
198
+ "type": "JSONDecodeError"
199
+ },
200
+ source=source
201
+ )
202
+ except Exception as e:
203
+ return DataResult(
204
+ success=False,
205
+ error={
206
+ "message": f"JSON import failed: {str(e)}",
207
+ "type": type(e).__name__
208
+ },
209
+ source=source
210
+ )
211
+
212
+ def _import_xml(self, source: str, params: Dict[str, Any]) -> DataResult:
213
+ """Import XML data from file or URL with XPath"""
214
+ try:
215
+ # Get XML content
216
+ content = self._get_source_content(source, params.get('headers', []))
217
+
218
+ # Parse XML
219
+ root = ET.fromstring(content)
220
+
221
+ # Get XPath expression
222
+ xpath = params.get('xpath', '.')
223
+ fields = params.get('fields', [])
224
+
225
+ # Find nodes using XPath (basic support - ET has limited XPath)
226
+ nodes = root.findall(xpath)
227
+
228
+ # Extract data from nodes
229
+ data = []
230
+ for node in nodes:
231
+ record = {}
232
+ for field_def in fields:
233
+ field_name = field_def.get('name')
234
+ field_xpath = field_def.get('xpath')
235
+ field_type = field_def.get('type', 'string')
236
+
237
+ # Extract value using XPath
238
+ value = self._extract_xml_value(node, field_xpath)
239
+ record[field_name] = self._convert_type(value, field_type)
240
+
241
+ data.append(record)
242
+
243
+ return DataResult(
244
+ success=True, # was False: every XML import "failed", so no transform ran on it
245
+ data=data,
246
+ recordCount=len(data),
247
+ source=source
248
+ )
249
+
250
+ except ET.ParseError as e:
251
+ return DataResult(
252
+ success=False,
253
+ error={
254
+ "message": f"Invalid XML: {str(e)}",
255
+ "type": "XMLParseError"
256
+ },
257
+ source=source
258
+ )
259
+ except Exception as e:
260
+ return DataResult(
261
+ success=False,
262
+ error={
263
+ "message": f"XML import failed: {str(e)}",
264
+ "type": type(e).__name__
265
+ },
266
+ source=source
267
+ )
268
+
269
+ def _transform_data(self, source: str, params: Dict[str, Any], context: Any) -> DataResult:
270
+ """Transform existing data (from variable reference)"""
271
+ try:
272
+ # Source should be a variable reference like "{users}"
273
+ # Extract variable name and get data from context
274
+ if source.startswith('{') and source.endswith('}'):
275
+ var_name = source[1:-1] # Remove braces
276
+ else:
277
+ var_name = source
278
+
279
+ # Get data from context
280
+ if hasattr(context, 'get_variable'):
281
+ data = context.get_variable(var_name)
282
+ elif isinstance(context, dict):
283
+ data = context.get(var_name)
284
+ else:
285
+ return DataResult(
286
+ success=False,
287
+ error={"message": f"Cannot access variable '{var_name}' from context"},
288
+ source=source
289
+ )
290
+
291
+ if data is None:
292
+ return DataResult(
293
+ success=False,
294
+ error={"message": f"Variable '{var_name}' not found"},
295
+ source=source
296
+ )
297
+
298
+ # Ensure data is a list
299
+ if not isinstance(data, list):
300
+ data = [data]
301
+
302
+ return DataResult(
303
+ success=True,
304
+ data=data,
305
+ recordCount=len(data),
306
+ source=source
307
+ )
308
+
309
+ except Exception as e:
310
+ return DataResult(
311
+ success=False,
312
+ error={
313
+ "message": f"Transform failed: {str(e)}",
314
+ "type": type(e).__name__
315
+ },
316
+ source=source
317
+ )
318
+
319
+ def _apply_transformations(self, result: DataResult, transforms: List[Dict[str, Any]], context: Any) -> DataResult:
320
+ """Apply transformation operations to data"""
321
+ if not result.success or not result.data:
322
+ return result
323
+
324
+ data = result.data
325
+
326
+ for transform_op in transforms:
327
+ op_type = transform_op.get('type')
328
+
329
+ if op_type == 'filter':
330
+ data = self._apply_filter(data, transform_op, context)
331
+ elif op_type == 'sort':
332
+ data = self._apply_sort(data, transform_op)
333
+ elif op_type == 'limit':
334
+ data = self._apply_limit(data, transform_op)
335
+ elif op_type == 'compute':
336
+ data = self._apply_compute(data, transform_op, context)
337
+
338
+ # Update result
339
+ result.data = data
340
+ result.recordCount = len(data)
341
+ return result
342
+
343
+ def _apply_filter(self, data: List[Dict[str, Any]], filter_op: Dict[str, Any], context: Any) -> List[Dict[str, Any]]:
344
+ """Filter data based on condition"""
345
+ condition = filter_op.get('condition', '')
346
+
347
+ filtered = []
348
+ for record in data:
349
+ # Evaluate condition for this record
350
+ if self._evaluate_condition(condition, record, context):
351
+ filtered.append(record)
352
+
353
+ return filtered
354
+
355
+ def _apply_sort(self, data: List[Dict[str, Any]], sort_op: Dict[str, Any]) -> List[Dict[str, Any]]:
356
+ """Sort data by field"""
357
+ by_field = sort_op.get('by')
358
+ order = sort_op.get('order', 'asc')
359
+
360
+ if not by_field:
361
+ return data
362
+
363
+ try:
364
+ # Sort data
365
+ reverse = (order == 'desc')
366
+ sorted_data = sorted(data, key=lambda x: x.get(by_field, ''), reverse=reverse)
367
+ return sorted_data
368
+ except Exception:
369
+ # If sorting fails, return original data
370
+ return data
371
+
372
+ def _apply_limit(self, data: List[Dict[str, Any]], limit_op: Dict[str, Any]) -> List[Dict[str, Any]]:
373
+ """Limit number of records"""
374
+ limit = limit_op.get('value', 0)
375
+
376
+ if isinstance(limit, str):
377
+ try:
378
+ limit = int(limit)
379
+ except ValueError:
380
+ return data
381
+
382
+ return data[:limit]
383
+
384
+ def _apply_compute(self, data: List[Dict[str, Any]], compute_op: Dict[str, Any], context: Any) -> List[Dict[str, Any]]:
385
+ """Compute derived field"""
386
+ field = compute_op.get('field')
387
+ expression = compute_op.get('expression')
388
+ comp_type = compute_op.get('comp_type', 'string')
389
+
390
+ if not field or not expression:
391
+ return data
392
+
393
+ # Add computed field to each record
394
+ for record in data:
395
+ value = self._evaluate_expression(expression, record, context)
396
+ record[field] = self._convert_type(value, comp_type)
397
+
398
+ return data
399
+
400
+ def _get_source_content(self, source: str, headers: List[Dict[str, str]] = None) -> str:
401
+ """Get content from file or URL"""
402
+ # Check if source is a URL
403
+ if source.startswith('http://') or source.startswith('https://'):
404
+ # Fetch from URL
405
+ headers_dict = {}
406
+ if headers:
407
+ for header in headers:
408
+ headers_dict[header.get('name')] = header.get('value')
409
+
410
+ response = requests.get(source, headers=headers_dict, timeout=30)
411
+ response.raise_for_status()
412
+ return response.text
413
+ else:
414
+ # Read from file
415
+ file_path = Path(source)
416
+ if not file_path.is_absolute():
417
+ # Relative path - resolve from current working directory
418
+ file_path = Path.cwd() / file_path
419
+
420
+ with open(file_path, 'r', encoding='utf-8') as f:
421
+ return f.read()
422
+
423
+ def _convert_csv_row(self, row: Dict[str, str], columns: List[Dict[str, Any]]) -> Dict[str, Any]:
424
+ """Convert CSV row values to proper types"""
425
+ if not columns:
426
+ return row
427
+
428
+ converted = {}
429
+ for col_def in columns:
430
+ col_name = col_def.get('name')
431
+ col_type = col_def.get('type', 'string')
432
+
433
+ if col_name in row:
434
+ converted[col_name] = self._convert_type(row[col_name], col_type)
435
+
436
+ # Include any columns not defined in schema
437
+ for key, value in row.items():
438
+ if key not in converted:
439
+ converted[key] = value
440
+
441
+ return converted
442
+
443
+ def _convert_type(self, value: Any, target_type: str) -> Any:
444
+ """Convert value to target type"""
445
+ if value is None or value == '':
446
+ return None
447
+
448
+ try:
449
+ if target_type == 'integer':
450
+ return int(value)
451
+ elif target_type == 'decimal':
452
+ return float(value)
453
+ elif target_type == 'boolean':
454
+ if isinstance(value, bool):
455
+ return value
456
+ if isinstance(value, str):
457
+ return value.lower() in ('true', '1', 'yes', 'on')
458
+ return bool(value)
459
+ elif target_type == 'json':
460
+ if isinstance(value, str):
461
+ return json.loads(value)
462
+ return value
463
+ elif target_type == 'array':
464
+ if isinstance(value, list):
465
+ return value
466
+ if isinstance(value, str):
467
+ return json.loads(value)
468
+ return [value]
469
+ else: # string
470
+ return str(value)
471
+ except (ValueError, json.JSONDecodeError):
472
+ return value
473
+
474
+ def _extract_xml_value(self, node: ET.Element, xpath: str) -> Optional[str]:
475
+ """Extract value from XML node using XPath"""
476
+ # Handle attribute access (@attr)
477
+ if xpath.startswith('@'):
478
+ attr_name = xpath[1:]
479
+ return node.get(attr_name)
480
+
481
+ # Handle text content (text())
482
+ if xpath == 'text()':
483
+ return node.text
484
+
485
+ # child/@attr — ElementTree's find() does not understand the @ step.
486
+ if '/@' in xpath:
487
+ child_path, attr = xpath.rsplit('/@', 1)
488
+ child = node.find(child_path)
489
+ return child.get(attr) if child is not None else None
490
+
491
+ # child/text() — nor text(); it raised SyntaxError on every such field.
492
+ if xpath.endswith('/text()'):
493
+ xpath = xpath[:-len('/text()')]
494
+
495
+ child = node.find(xpath)
496
+ return child.text if child is not None else None
497
+
498
+ def _evaluate_condition(self, condition: str, record: Dict[str, Any], context: Any) -> bool:
499
+ """Evaluate a q:data filter condition against one imported record.
500
+
501
+ `{field}` names the record's field. The braces are stripped so the
502
+ evaluator resolves the NAME; the record's values are never spliced
503
+ into the expression text.
504
+
505
+ That distinction is the whole fix. This used to build a string by
506
+ interpolating each field value into the condition — quoting strings as
507
+ f"'{value}'" — and then call a bare eval() on it, with full builtins.
508
+ A single quote anywhere in an imported CSV cell closed the literal and
509
+ the rest of the cell ran as Python. The input here is an imported file,
510
+ which is the least trusted thing the framework touches.
511
+ """
512
+ return bool(self._evaluate_expression(condition, record, context))
513
+
514
+ def _evaluate_expression(self, expression: str, record: Dict[str, Any], context: Any) -> Any:
515
+ """Evaluate a computed-field expression against one imported record.
516
+
517
+ See _evaluate_condition for why this no longer interpolates values.
518
+ """
519
+ expr = _BINDING.sub(lambda m: m.group(1), expression).strip()
520
+ return _EXPRESSIONS.try_evaluate(expr, record, default=None)
521
+
522
+ def get_from_cache(self, cache_key: str) -> Optional[DataResult]:
523
+ """Get value from cache"""
524
+ return self.cache.get(cache_key)
525
+
526
+ def put_in_cache(self, cache_key: str, value: DataResult, ttl: Optional[int] = None):
527
+ """Put value in cache"""
528
+ # Simple implementation - in production you'd use TTL properly
529
+ self.cache[cache_key] = value
530
+
531
+ def clear_cache(self):
532
+ """Clear all cache"""
533
+ self.cache.clear()
@@ -0,0 +1,12 @@
1
+ """
2
+ Dump feature for Quantum Language
3
+
4
+ Provides beautiful variable inspection for debugging,
5
+ like ColdFusion's cfdump but better.
6
+ """
7
+
8
+ from .ast_node import DumpNode
9
+ from .parser import parse_dump
10
+ from .runtime import DumpService
11
+
12
+ __all__ = ['DumpNode', 'parse_dump', 'DumpService']
@@ -0,0 +1,106 @@
1
+ """
2
+ AST Node for q:dump - Variable Inspection
3
+ """
4
+
5
+ from typing import Optional
6
+ from quantum.core.ast_nodes import QuantumNode
7
+
8
+
9
+ class DumpNode(QuantumNode):
10
+ """
11
+ Represents a <q:dump> - Variable inspection statement
12
+
13
+ Example:
14
+ <q:dump var="{user}" label="User Object" />
15
+ <q:dump var="{data}" format="json" depth="3" />
16
+ """
17
+
18
+ VALID_FORMATS = ['html', 'json', 'text']
19
+
20
+ def __init__(
21
+ self,
22
+ var: str,
23
+ label: Optional[str] = None,
24
+ format: str = 'html',
25
+ depth: int = 10,
26
+ when: Optional[str] = None,
27
+ expand: int = 2,
28
+ show_types: bool = True
29
+ ):
30
+ """
31
+ Initialize a DumpNode
32
+
33
+ Args:
34
+ var: Variable to inspect (databinding expression)
35
+ label: Optional identifying label
36
+ format: Output format (html, json, text)
37
+ depth: Max depth for nested structures
38
+ when: Optional conditional expression
39
+ expand: Auto-expand depth (Phase 2)
40
+ show_types: Show type information (Phase 2)
41
+ """
42
+ # Validate var
43
+ if not var:
44
+ raise ValueError("Dump requires 'var' attribute")
45
+
46
+ # Validate format
47
+ if format and format not in self.VALID_FORMATS:
48
+ raise ValueError(
49
+ f"Invalid dump format '{format}'. Must be one of: {', '.join(self.VALID_FORMATS)}"
50
+ )
51
+
52
+ # Validate depth
53
+ if depth is not None:
54
+ try:
55
+ depth_int = int(depth)
56
+ if depth_int < 0:
57
+ raise ValueError("Dump depth must be a positive integer")
58
+ depth = depth_int
59
+ except (ValueError, TypeError):
60
+ raise ValueError("Dump depth must be a positive integer")
61
+
62
+ self.var = var
63
+ self.label = label if label else var
64
+ self.format = format
65
+ self.depth = depth
66
+ self.when = when
67
+ self.expand = expand
68
+ self.show_types = show_types
69
+
70
+ def __repr__(self):
71
+ attrs = [f"var={self.var!r}"]
72
+ if self.label != self.var:
73
+ attrs.append(f"label={self.label!r}")
74
+ if self.format != 'html':
75
+ attrs.append(f"format={self.format!r}")
76
+ if self.when:
77
+ attrs.append(f"when={self.when!r}")
78
+ return f"DumpNode({', '.join(attrs)})"
79
+
80
+ def to_dict(self):
81
+ """Convert node to dictionary for debugging/serialization"""
82
+ return {
83
+ "type": "dump",
84
+ "var": self.var,
85
+ "label": self.label,
86
+ "format": self.format,
87
+ "depth": self.depth,
88
+ "when": self.when,
89
+ "expand": self.expand,
90
+ "show_types": self.show_types
91
+ }
92
+
93
+ def validate(self):
94
+ """Validate the node and return list of errors"""
95
+ errors = []
96
+
97
+ if not self.var:
98
+ errors.append("Dump requires 'var' attribute")
99
+
100
+ if self.format and self.format not in self.VALID_FORMATS:
101
+ errors.append(f"Invalid dump format '{self.format}'. Must be one of: {', '.join(self.VALID_FORMATS)}")
102
+
103
+ if self.depth is not None and (not isinstance(self.depth, int) or self.depth < 0):
104
+ errors.append("Dump depth must be a positive integer")
105
+
106
+ return errors