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,474 @@
1
+ """
2
+ Data Fetching Feature - HTML Adapter
3
+ Generates JavaScript fetch() code with state management for the HTML target.
4
+ """
5
+
6
+ from typing import Optional
7
+ from .ast_node import FetchNode, FetchLoadingNode, FetchErrorNode, FetchSuccessNode
8
+
9
+
10
+ # JavaScript code for the fetch state management system
11
+ FETCH_STATE_JS = '''
12
+ <!-- Quantum Fetch State Management -->
13
+ <script>
14
+ (function() {
15
+ // Fetch cache storage
16
+ window.__qFetchCache = window.__qFetchCache || {};
17
+
18
+ // Fetch state storage
19
+ window.__qFetchState = window.__qFetchState || {};
20
+
21
+ // Active abort controllers
22
+ window.__qFetchControllers = window.__qFetchControllers || {};
23
+
24
+ // Polling intervals
25
+ window.__qFetchIntervals = window.__qFetchIntervals || {};
26
+
27
+ /**
28
+ * Execute a fetch request with state management
29
+ * @param {string} fetchId - Unique ID for this fetch
30
+ * @param {Object} options - Fetch configuration
31
+ */
32
+ window.__qFetch = function(fetchId, options) {
33
+ const {
34
+ name,
35
+ url,
36
+ method = 'GET',
37
+ headers = {},
38
+ body = null,
39
+ cache = null,
40
+ cacheKey = null,
41
+ transform = null,
42
+ timeout = 30000,
43
+ retry = 0,
44
+ retryDelay = 1000,
45
+ interval = null,
46
+ onSuccess = null,
47
+ onError = null,
48
+ credentials = null,
49
+ responseFormat = 'auto'
50
+ } = options;
51
+
52
+ // Generate cache key
53
+ const cKey = cacheKey || `${method}:${url}`;
54
+
55
+ // Check cache first
56
+ if (cache && window.__qFetchCache[cKey]) {
57
+ const cached = window.__qFetchCache[cKey];
58
+ if (Date.now() < cached.expires) {
59
+ __qFetchSetState(fetchId, name, { loading: false, error: null, data: cached.data });
60
+ __qFetchUpdateUI(fetchId, 'success');
61
+ return Promise.resolve(cached.data);
62
+ }
63
+ }
64
+
65
+ // Abort any existing request for this fetch
66
+ if (window.__qFetchControllers[fetchId]) {
67
+ window.__qFetchControllers[fetchId].abort();
68
+ }
69
+
70
+ // Create abort controller
71
+ const controller = new AbortController();
72
+ window.__qFetchControllers[fetchId] = controller;
73
+
74
+ // Set loading state
75
+ __qFetchSetState(fetchId, name, { loading: true, error: null });
76
+ __qFetchUpdateUI(fetchId, 'loading');
77
+
78
+ // Build fetch options
79
+ const fetchOptions = {
80
+ method: method,
81
+ headers: { 'Content-Type': 'application/json', ...headers },
82
+ signal: controller.signal
83
+ };
84
+
85
+ if (body && method !== 'GET' && method !== 'HEAD') {
86
+ fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
87
+ }
88
+
89
+ if (credentials) {
90
+ fetchOptions.credentials = credentials;
91
+ }
92
+
93
+ // Timeout promise
94
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
95
+
96
+ // Execute fetch with retry support
97
+ const doFetch = (attempt = 0) => {
98
+ return fetch(url, fetchOptions)
99
+ .then(response => {
100
+ clearTimeout(timeoutId);
101
+ if (!response.ok) {
102
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
103
+ }
104
+
105
+ // Parse response based on format
106
+ if (responseFormat === 'text') {
107
+ return response.text();
108
+ } else if (responseFormat === 'blob') {
109
+ return response.blob();
110
+ } else {
111
+ // Auto-detect: try JSON first
112
+ const contentType = response.headers.get('content-type');
113
+ if (contentType && contentType.includes('application/json')) {
114
+ return response.json();
115
+ }
116
+ return response.text().then(text => {
117
+ try { return JSON.parse(text); } catch { return text; }
118
+ });
119
+ }
120
+ })
121
+ .then(data => {
122
+ // Apply transform if specified
123
+ if (transform && typeof transform === 'function') {
124
+ data = transform(data);
125
+ }
126
+
127
+ // Cache the result
128
+ if (cache) {
129
+ const cacheSeconds = __qFetchParseDuration(cache);
130
+ if (cacheSeconds > 0) {
131
+ window.__qFetchCache[cKey] = {
132
+ data: data,
133
+ expires: Date.now() + (cacheSeconds * 1000)
134
+ };
135
+ }
136
+ }
137
+
138
+ // Update state
139
+ __qFetchSetState(fetchId, name, { loading: false, error: null, data: data });
140
+ __qFetchUpdateUI(fetchId, 'success');
141
+
142
+ // Call success callback
143
+ if (onSuccess && typeof window[onSuccess] === 'function') {
144
+ window[onSuccess](data);
145
+ }
146
+
147
+ return data;
148
+ })
149
+ .catch(error => {
150
+ clearTimeout(timeoutId);
151
+
152
+ // Handle abort
153
+ if (error.name === 'AbortError') {
154
+ return;
155
+ }
156
+
157
+ // Retry logic
158
+ if (attempt < retry) {
159
+ return new Promise(resolve => {
160
+ setTimeout(() => resolve(doFetch(attempt + 1)), retryDelay);
161
+ });
162
+ }
163
+
164
+ // Update error state
165
+ const errorMsg = error.message || 'Fetch failed';
166
+ __qFetchSetState(fetchId, name, { loading: false, error: errorMsg, data: null });
167
+ __qFetchUpdateUI(fetchId, 'error');
168
+
169
+ // Call error callback
170
+ if (onError && typeof window[onError] === 'function') {
171
+ window[onError](errorMsg);
172
+ }
173
+
174
+ throw error;
175
+ });
176
+ };
177
+
178
+ const fetchPromise = doFetch();
179
+
180
+ // Set up polling if interval specified
181
+ if (interval) {
182
+ const intervalMs = __qFetchParseDuration(interval) * 1000;
183
+ if (intervalMs > 0) {
184
+ // Clear existing interval
185
+ if (window.__qFetchIntervals[fetchId]) {
186
+ clearInterval(window.__qFetchIntervals[fetchId]);
187
+ }
188
+ window.__qFetchIntervals[fetchId] = setInterval(() => {
189
+ __qFetch(fetchId, options);
190
+ }, intervalMs);
191
+ }
192
+ }
193
+
194
+ return fetchPromise;
195
+ };
196
+
197
+ /**
198
+ * Set fetch state and update global variable
199
+ */
200
+ function __qFetchSetState(fetchId, name, state) {
201
+ window.__qFetchState[fetchId] = { ...window.__qFetchState[fetchId], ...state };
202
+
203
+ // Update global variable with data
204
+ if (state.data !== undefined) {
205
+ window[name] = state.data;
206
+ }
207
+
208
+ // Update error variable
209
+ window[name + '_error'] = state.error;
210
+ window[name + '_loading'] = state.loading;
211
+ }
212
+
213
+ /**
214
+ * Update UI based on fetch state
215
+ */
216
+ function __qFetchUpdateUI(fetchId, state) {
217
+ const loadingEl = document.getElementById(fetchId + '-loading');
218
+ const errorEl = document.getElementById(fetchId + '-error');
219
+ const successEl = document.getElementById(fetchId + '-success');
220
+
221
+ if (loadingEl) loadingEl.style.display = state === 'loading' ? '' : 'none';
222
+ if (errorEl) errorEl.style.display = state === 'error' ? '' : 'none';
223
+ if (successEl) successEl.style.display = state === 'success' ? '' : 'none';
224
+ }
225
+
226
+ /**
227
+ * Parse duration string to seconds
228
+ */
229
+ function __qFetchParseDuration(duration) {
230
+ if (!duration) return 0;
231
+ const str = String(duration).toLowerCase().trim();
232
+ const match = str.match(/^(\\d+)(ms|s|m|h)?$/);
233
+ if (match) {
234
+ const value = parseInt(match[1]);
235
+ const unit = match[2] || 's';
236
+ const multipliers = { ms: 0.001, s: 1, m: 60, h: 3600 };
237
+ return value * (multipliers[unit] || 1);
238
+ }
239
+ return parseInt(str) || 0;
240
+ }
241
+
242
+ /**
243
+ * Abort a fetch request
244
+ */
245
+ window.__qFetchAbort = function(fetchId) {
246
+ if (window.__qFetchControllers[fetchId]) {
247
+ window.__qFetchControllers[fetchId].abort();
248
+ delete window.__qFetchControllers[fetchId];
249
+ }
250
+ if (window.__qFetchIntervals[fetchId]) {
251
+ clearInterval(window.__qFetchIntervals[fetchId]);
252
+ delete window.__qFetchIntervals[fetchId];
253
+ }
254
+ };
255
+
256
+ /**
257
+ * Refetch data for a specific fetch
258
+ */
259
+ window.__qFetchRefetch = function(fetchId) {
260
+ const state = window.__qFetchState[fetchId];
261
+ if (state && state._options) {
262
+ return __qFetch(fetchId, state._options);
263
+ }
264
+ };
265
+
266
+ })();
267
+ </script>
268
+ '''
269
+
270
+
271
+ class FetchHtmlAdapter:
272
+ """Generates HTML/JS for q:fetch nodes."""
273
+
274
+ def __init__(self, html_adapter=None):
275
+ """
276
+ Initialize the fetch HTML adapter.
277
+
278
+ Args:
279
+ html_adapter: Reference to the parent UIHtmlAdapter for rendering children
280
+ """
281
+ self._html_adapter = html_adapter
282
+ self._fetch_counter = 0
283
+ self._has_fetch = False
284
+
285
+ def render_fetch(self, node: FetchNode, builder) -> str:
286
+ """
287
+ Render a FetchNode to HTML with JavaScript fetch code.
288
+
289
+ Args:
290
+ node: FetchNode to render
291
+ builder: HtmlBuilder instance
292
+
293
+ Returns:
294
+ Generated HTML string
295
+ """
296
+ self._has_fetch = True
297
+ self._fetch_counter += 1
298
+
299
+ fetch_id = f"qfetch-{self._fetch_counter}"
300
+
301
+ # Build container div
302
+ builder.open_tag('div', {'class': 'q-fetch', 'id': fetch_id, 'data-fetch-name': node.name})
303
+ builder.indent()
304
+
305
+ # Render loading state
306
+ loading_id = f"{fetch_id}-loading"
307
+ builder.open_tag('div', {'id': loading_id, 'class': 'q-fetch-loading', 'style': 'display: none'})
308
+ builder.indent()
309
+ if node.loading_node:
310
+ self._render_children(node.loading_node.children, builder)
311
+ else:
312
+ # Default loading indicator
313
+ builder.open_tag('div', {'class': 'q-loading-spinner'})
314
+ builder.text('Loading...')
315
+ builder.close_tag('div')
316
+ builder.dedent()
317
+ builder.close_tag('div')
318
+
319
+ # Render error state
320
+ error_id = f"{fetch_id}-error"
321
+ builder.open_tag('div', {'id': error_id, 'class': 'q-fetch-error', 'style': 'display: none'})
322
+ builder.indent()
323
+ if node.error_node:
324
+ self._render_children(node.error_node.children, builder)
325
+ else:
326
+ # Default error display
327
+ builder.open_tag('div', {'class': 'q-error-message'})
328
+ builder.text('{' + node.name + '_error}')
329
+ builder.close_tag('div')
330
+ builder.dedent()
331
+ builder.close_tag('div')
332
+
333
+ # Render success state
334
+ success_id = f"{fetch_id}-success"
335
+ builder.open_tag('div', {'id': success_id, 'class': 'q-fetch-success', 'style': 'display: none'})
336
+ builder.indent()
337
+ if node.success_node:
338
+ self._render_children(node.success_node.children, builder)
339
+ elif node.fallback_children:
340
+ self._render_children(node.fallback_children, builder)
341
+ builder.dedent()
342
+ builder.close_tag('div')
343
+
344
+ builder.dedent()
345
+ builder.close_tag('div')
346
+
347
+ # Generate fetch invocation script
348
+ builder.raw_html(self._generate_fetch_script(fetch_id, node))
349
+
350
+ def _render_children(self, children, builder):
351
+ """Render child nodes using the parent HTML adapter."""
352
+ if self._html_adapter:
353
+ for child in children:
354
+ self._html_adapter._render_node(child, builder)
355
+ else:
356
+ # Fallback: render as text
357
+ for child in children:
358
+ if hasattr(child, 'content'):
359
+ builder.text(child.content)
360
+
361
+ def _generate_fetch_script(self, fetch_id: str, node: FetchNode) -> str:
362
+ """Generate JavaScript to execute the fetch on load."""
363
+ # Build headers object
364
+ headers_js = '{'
365
+ header_parts = []
366
+ for header in node.headers:
367
+ header_parts.append(f"'{header.name}': '{header.value}'")
368
+ headers_js += ', '.join(header_parts) + '}'
369
+
370
+ # Build options object
371
+ options = []
372
+ options.append(f"name: '{node.name}'")
373
+ options.append(f"url: '{node.url}'")
374
+ options.append(f"method: '{node.method}'")
375
+ options.append(f"headers: {headers_js}")
376
+
377
+ if node.body:
378
+ options.append(f"body: {node.body}")
379
+
380
+ if node.cache:
381
+ options.append(f"cache: '{node.cache}'")
382
+
383
+ if node.cache_key:
384
+ options.append(f"cacheKey: '{node.cache_key}'")
385
+
386
+ if node.interval:
387
+ options.append(f"interval: '{node.interval}'")
388
+
389
+ if node.timeout != 30000:
390
+ options.append(f"timeout: {node.timeout}")
391
+
392
+ if node.retry > 0:
393
+ options.append(f"retry: {node.retry}")
394
+ options.append(f"retryDelay: {node.retry_delay}")
395
+
396
+ if node.transform:
397
+ options.append(f"transform: {node.transform}")
398
+
399
+ if node.on_success:
400
+ options.append(f"onSuccess: '{node.on_success}'")
401
+
402
+ if node.on_error:
403
+ options.append(f"onError: '{node.on_error}'")
404
+
405
+ if node.credentials:
406
+ options.append(f"credentials: '{node.credentials}'")
407
+
408
+ if node.response_format != 'auto':
409
+ options.append(f"responseFormat: '{node.response_format}'")
410
+
411
+ options_js = '{' + ', '.join(options) + '}'
412
+
413
+ # Generate script
414
+ script = f'''
415
+ <script>
416
+ document.addEventListener('DOMContentLoaded', function() {{
417
+ __qFetch('{fetch_id}', {options_js});
418
+ }});
419
+ </script>
420
+ '''
421
+ return script
422
+
423
+ def get_fetch_js(self) -> str:
424
+ """Get the fetch state management JavaScript if needed."""
425
+ if self._has_fetch:
426
+ return FETCH_STATE_JS
427
+ return ''
428
+
429
+
430
+ # CSS for fetch states
431
+ FETCH_CSS = '''
432
+ /* Quantum Fetch Styles */
433
+ .q-fetch {
434
+ position: relative;
435
+ }
436
+
437
+ .q-fetch-loading {
438
+ opacity: 0.7;
439
+ }
440
+
441
+ .q-loading-spinner {
442
+ display: flex;
443
+ align-items: center;
444
+ justify-content: center;
445
+ padding: 1rem;
446
+ color: var(--q-text-secondary, #666);
447
+ }
448
+
449
+ .q-loading-spinner::before {
450
+ content: '';
451
+ width: 1rem;
452
+ height: 1rem;
453
+ margin-right: 0.5rem;
454
+ border: 2px solid currentColor;
455
+ border-top-color: transparent;
456
+ border-radius: 50%;
457
+ animation: q-spin 0.75s linear infinite;
458
+ }
459
+
460
+ @keyframes q-spin {
461
+ to { transform: rotate(360deg); }
462
+ }
463
+
464
+ .q-fetch-error {
465
+ color: var(--q-error, #dc2626);
466
+ padding: 0.5rem;
467
+ background: var(--q-error-bg, #fef2f2);
468
+ border-radius: var(--q-radius, 4px);
469
+ }
470
+
471
+ .q-fetch-success {
472
+ /* Success state - no specific styles needed */
473
+ }
474
+ '''
@@ -0,0 +1,225 @@
1
+ """
2
+ Data Fetching Feature - Parser
3
+ Parses q:fetch elements from XML into FetchNode AST
4
+ """
5
+
6
+ from xml.etree import ElementTree as ET
7
+ from typing import Optional, List
8
+
9
+ from .ast_node import (
10
+ FetchNode,
11
+ FetchLoadingNode,
12
+ FetchErrorNode,
13
+ FetchSuccessNode,
14
+ FetchHeaderNode,
15
+ )
16
+
17
+
18
+ class FetchParseError(Exception):
19
+ """Error during fetch element parsing."""
20
+ pass
21
+
22
+
23
+ def parse_fetch(element: ET.Element, parent_parser) -> FetchNode:
24
+ """
25
+ Parse a <q:fetch> element into a FetchNode.
26
+
27
+ Args:
28
+ element: XML element for q:fetch
29
+ parent_parser: Reference to QuantumParser for parsing child elements
30
+
31
+ Returns:
32
+ FetchNode AST node
33
+
34
+ Raises:
35
+ FetchParseError: If required attributes are missing or invalid
36
+ """
37
+ # Required attributes
38
+ name = element.get('name')
39
+ url = element.get('url')
40
+
41
+ if not name:
42
+ raise FetchParseError("q:fetch requires 'name' attribute")
43
+ if not url:
44
+ raise FetchParseError("q:fetch requires 'url' attribute")
45
+
46
+ fetch_node = FetchNode(name, url)
47
+
48
+ # HTTP method
49
+ method = element.get('method', 'GET').upper()
50
+ fetch_node.method = method
51
+
52
+ # Request body
53
+ fetch_node.body = element.get('body')
54
+
55
+ # Content type
56
+ content_type = element.get('contentType')
57
+ if content_type:
58
+ fetch_node.content_type = content_type
59
+
60
+ # Caching
61
+ fetch_node.cache = element.get('cache')
62
+ fetch_node.cache_key = element.get('cacheKey')
63
+
64
+ # Polling
65
+ fetch_node.interval = element.get('interval')
66
+ fetch_node.refetch_on = element.get('refetchOn')
67
+
68
+ # Timeout
69
+ timeout_attr = element.get('timeout')
70
+ if timeout_attr:
71
+ try:
72
+ fetch_node.timeout = int(timeout_attr)
73
+ except ValueError:
74
+ pass
75
+
76
+ # Retry
77
+ retry_attr = element.get('retry')
78
+ if retry_attr:
79
+ try:
80
+ fetch_node.retry = int(retry_attr)
81
+ except ValueError:
82
+ pass
83
+
84
+ retry_delay_attr = element.get('retryDelay')
85
+ if retry_delay_attr:
86
+ try:
87
+ fetch_node.retry_delay = int(retry_delay_attr)
88
+ except ValueError:
89
+ pass
90
+
91
+ # Response handling
92
+ fetch_node.transform = element.get('transform')
93
+ response_format = element.get('responseFormat')
94
+ if response_format:
95
+ fetch_node.response_format = response_format
96
+
97
+ # Callbacks
98
+ fetch_node.on_success = element.get('onSuccess')
99
+ fetch_node.on_error = element.get('onError')
100
+
101
+ # Abort on unmount
102
+ abort_attr = element.get('abortOnUnmount', 'true').lower()
103
+ fetch_node.abort_on_unmount = abort_attr in ['true', '1', 'yes']
104
+
105
+ # Lazy loading
106
+ lazy_attr = element.get('lazy', 'false').lower()
107
+ fetch_node.lazy = lazy_attr in ['true', '1', 'yes']
108
+
109
+ # Credentials mode
110
+ fetch_node.credentials = element.get('credentials')
111
+
112
+ # Parse child elements
113
+ for child in element:
114
+ child_type = _get_element_name(child)
115
+
116
+ if child_type == 'header':
117
+ header = _parse_fetch_header(child)
118
+ fetch_node.add_header(header)
119
+ elif child_type == 'loading':
120
+ loading = _parse_fetch_loading(child, parent_parser)
121
+ fetch_node.set_loading(loading)
122
+ elif child_type == 'error':
123
+ error = _parse_fetch_error(child, parent_parser)
124
+ fetch_node.set_error(error)
125
+ elif child_type == 'success':
126
+ success = _parse_fetch_success(child, parent_parser)
127
+ fetch_node.set_success(success)
128
+ else:
129
+ # Parse other children as fallback content
130
+ if parent_parser:
131
+ child_node = parent_parser._parse_statement(child)
132
+ if child_node:
133
+ fetch_node.add_fallback_child(child_node)
134
+
135
+ return fetch_node
136
+
137
+
138
+ def _get_element_name(element: ET.Element) -> str:
139
+ """Extract element name removing namespace."""
140
+ return element.tag.split('}')[-1] if '}' in element.tag else element.tag.split(':')[-1]
141
+
142
+
143
+ def _parse_fetch_header(element: ET.Element) -> FetchHeaderNode:
144
+ """Parse <q:header> within q:fetch."""
145
+ name = element.get('name')
146
+ value = element.get('value')
147
+
148
+ if not name:
149
+ raise FetchParseError("q:header requires 'name' attribute")
150
+ if value is None:
151
+ raise FetchParseError(f"q:header '{name}' requires 'value' attribute")
152
+
153
+ return FetchHeaderNode(name=name, value=value)
154
+
155
+
156
+ def _parse_fetch_loading(element: ET.Element, parent_parser) -> FetchLoadingNode:
157
+ """Parse <q:loading> within q:fetch."""
158
+ loading_node = FetchLoadingNode()
159
+
160
+ # Parse text content
161
+ if element.text and element.text.strip():
162
+ from quantum.core.ast_nodes import TextNode
163
+ loading_node.add_child(TextNode(element.text.strip()))
164
+
165
+ # Parse child elements
166
+ for child in element:
167
+ if parent_parser:
168
+ child_node = parent_parser._parse_statement(child)
169
+ if child_node:
170
+ loading_node.add_child(child_node)
171
+
172
+ # Add tail text after child
173
+ if child.tail and child.tail.strip():
174
+ from quantum.core.ast_nodes import TextNode
175
+ loading_node.add_child(TextNode(child.tail.strip()))
176
+
177
+ return loading_node
178
+
179
+
180
+ def _parse_fetch_error(element: ET.Element, parent_parser) -> FetchErrorNode:
181
+ """Parse <q:error> within q:fetch."""
182
+ error_node = FetchErrorNode()
183
+
184
+ # Parse text content
185
+ if element.text and element.text.strip():
186
+ from quantum.core.ast_nodes import TextNode
187
+ error_node.add_child(TextNode(element.text.strip()))
188
+
189
+ # Parse child elements
190
+ for child in element:
191
+ if parent_parser:
192
+ child_node = parent_parser._parse_statement(child)
193
+ if child_node:
194
+ error_node.add_child(child_node)
195
+
196
+ # Add tail text after child
197
+ if child.tail and child.tail.strip():
198
+ from quantum.core.ast_nodes import TextNode
199
+ error_node.add_child(TextNode(child.tail.strip()))
200
+
201
+ return error_node
202
+
203
+
204
+ def _parse_fetch_success(element: ET.Element, parent_parser) -> FetchSuccessNode:
205
+ """Parse <q:success> within q:fetch."""
206
+ success_node = FetchSuccessNode()
207
+
208
+ # Parse text content
209
+ if element.text and element.text.strip():
210
+ from quantum.core.ast_nodes import TextNode
211
+ success_node.add_child(TextNode(element.text.strip()))
212
+
213
+ # Parse child elements
214
+ for child in element:
215
+ if parent_parser:
216
+ child_node = parent_parser._parse_statement(child)
217
+ if child_node:
218
+ success_node.add_child(child_node)
219
+
220
+ # Add tail text after child
221
+ if child.tail and child.tail.strip():
222
+ from quantum.core.ast_nodes import TextNode
223
+ success_node.add_child(TextNode(child.tail.strip()))
224
+
225
+ return success_node
File without changes