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,863 @@
1
+ """
2
+ Quantum Runtime for Python
3
+ ==========================
4
+
5
+ Runtime support library for transpiled Quantum code.
6
+ Generated Python code imports this module for common utilities.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Union, Callable
10
+ from functools import wraps
11
+ import html as html_module
12
+ import json
13
+ import re
14
+
15
+ from quantum.core.expressions import (
16
+ ExpressionEvaluator, ExpressionError, is_regex_quantifier,
17
+ )
18
+
19
+ # The same evaluator the interpreter uses, so a {expression} means one thing
20
+ # whether the .q file is interpreted or compiled to Python. This replaced an
21
+ # eval(expr, {'__builtins__': {}}, context): emptying builtins removes names,
22
+ # not the object graph, so ().__class__.__mro__[1].__subclasses__() still
23
+ # reached every loaded class from a compiled template.
24
+ _EXPRESSIONS = ExpressionEvaluator()
25
+
26
+
27
+ # =============================================================================
28
+ # HTML Utilities
29
+ # =============================================================================
30
+
31
+ def escape(value: Any) -> str:
32
+ """
33
+ Escape HTML entities in a value.
34
+
35
+ Args:
36
+ value: Value to escape
37
+
38
+ Returns:
39
+ HTML-escaped string
40
+ """
41
+ if value is None:
42
+ return ''
43
+ return html_module.escape(str(value))
44
+
45
+
46
+ def raw(value: Any) -> 'RawHTML':
47
+ """
48
+ Mark a value as raw HTML (no escaping).
49
+
50
+ Args:
51
+ value: HTML string
52
+
53
+ Returns:
54
+ RawHTML wrapper
55
+ """
56
+ return RawHTML(value)
57
+
58
+
59
+ class RawHTML:
60
+ """Wrapper for raw HTML that should not be escaped."""
61
+
62
+ def __init__(self, content: Any):
63
+ self.content = str(content) if content is not None else ''
64
+
65
+ def __str__(self) -> str:
66
+ return self.content
67
+
68
+ def __repr__(self) -> str:
69
+ return f'RawHTML({self.content!r})'
70
+
71
+
72
+ # =============================================================================
73
+ # Databinding
74
+ # =============================================================================
75
+
76
+ def bind(template: str, context: Dict[str, Any]) -> str:
77
+ """
78
+ Apply databinding to a template string.
79
+
80
+ Args:
81
+ template: Template with {expression} placeholders
82
+ context: Variable context
83
+
84
+ Returns:
85
+ Resolved string
86
+ """
87
+ def replace(match):
88
+ expr = match.group(1).strip()
89
+ # {n}, {n,m} are regex quantifiers, not expressions.
90
+ if is_regex_quantifier(expr):
91
+ return match.group(0)
92
+ try:
93
+ result = _EXPRESSIONS.evaluate(expr, context)
94
+ return escape(result) if not isinstance(result, RawHTML) else str(result)
95
+ except ExpressionError:
96
+ return match.group(0)
97
+
98
+ return re.sub(r'\{([^}]+)\}', replace, template)
99
+
100
+
101
+ # =============================================================================
102
+ # Component Base
103
+ # =============================================================================
104
+
105
+ class Component:
106
+ """
107
+ Base class for Quantum components.
108
+
109
+ Generated component classes inherit from this.
110
+ """
111
+
112
+ def __init__(self, **props):
113
+ """
114
+ Initialize component with props.
115
+
116
+ Args:
117
+ **props: Component properties
118
+ """
119
+ self.props = props
120
+ self._html: List[str] = []
121
+ self._context: Dict[str, Any] = {}
122
+
123
+ def render(self) -> str:
124
+ """
125
+ Render the component.
126
+
127
+ Override in generated subclasses.
128
+
129
+ Returns:
130
+ HTML string
131
+ """
132
+ raise NotImplementedError("Component.render() must be implemented")
133
+
134
+ def __str__(self) -> str:
135
+ """String representation returns rendered HTML."""
136
+ return self.render()
137
+
138
+ def __html__(self) -> str:
139
+ """Jinja2/Flask compatible HTML method."""
140
+ return self.render()
141
+
142
+
143
+ # =============================================================================
144
+ # Flash Messages
145
+ # =============================================================================
146
+
147
+ _flash_messages: List[Dict[str, str]] = []
148
+
149
+
150
+ def flash(message: str, category: str = 'info'):
151
+ """
152
+ Add a flash message.
153
+
154
+ Args:
155
+ message: The message text
156
+ category: Message category (info, success, warning, error)
157
+ """
158
+ _flash_messages.append({
159
+ 'message': message,
160
+ 'category': category
161
+ })
162
+
163
+
164
+ def get_flashed_messages(with_categories: bool = False) -> Union[List[str], List[Dict[str, str]]]:
165
+ """
166
+ Get and clear flash messages.
167
+
168
+ Args:
169
+ with_categories: Include category with each message
170
+
171
+ Returns:
172
+ List of messages or dicts with message/category
173
+ """
174
+ global _flash_messages
175
+ messages = _flash_messages.copy()
176
+ _flash_messages = []
177
+
178
+ if with_categories:
179
+ return messages
180
+ return [m['message'] for m in messages]
181
+
182
+
183
+ # =============================================================================
184
+ # Session & Application Scope
185
+ # =============================================================================
186
+
187
+ class ScopeDict(dict):
188
+ """Dictionary with attribute access for session/application scope."""
189
+
190
+ def __getattr__(self, name: str) -> Any:
191
+ try:
192
+ return self[name]
193
+ except KeyError:
194
+ raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
195
+
196
+ def __setattr__(self, name: str, value: Any):
197
+ self[name] = value
198
+
199
+ def __delattr__(self, name: str):
200
+ try:
201
+ del self[name]
202
+ except KeyError:
203
+ raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
204
+
205
+
206
+ # Global session and application scopes
207
+ session = ScopeDict()
208
+ application = ScopeDict()
209
+
210
+
211
+ # =============================================================================
212
+ # Database Utilities
213
+ # =============================================================================
214
+
215
+ class Database:
216
+ """
217
+ Simple database wrapper.
218
+
219
+ For actual database access, integrate with SQLAlchemy or similar.
220
+ """
221
+
222
+ _connections: Dict[str, Any] = {}
223
+
224
+ @classmethod
225
+ def get_connection(cls, name: str = 'default') -> 'Database':
226
+ """Get or create a database connection."""
227
+ if name not in cls._connections:
228
+ cls._connections[name] = cls(name)
229
+ return cls._connections[name]
230
+
231
+ def __init__(self, name: str):
232
+ self.name = name
233
+ self._conn = None
234
+
235
+ def execute(self, sql: str, params: List[Any] = None):
236
+ """
237
+ Execute a SQL query.
238
+
239
+ Args:
240
+ sql: SQL query string
241
+ params: Query parameters
242
+
243
+ Returns:
244
+ Query result
245
+ """
246
+ # This is a placeholder - actual implementation depends on database
247
+ raise NotImplementedError("Database.execute() requires database configuration")
248
+
249
+
250
+ # =============================================================================
251
+ # Request Context (Flask Integration)
252
+ # =============================================================================
253
+
254
+ class RequestContext:
255
+ """
256
+ Request context for web applications.
257
+
258
+ In Flask apps, this wraps flask.request.
259
+ """
260
+
261
+ def __init__(self):
262
+ self._form: Dict[str, Any] = {}
263
+ self._args: Dict[str, Any] = {}
264
+ self._cookies: Dict[str, str] = {}
265
+ self._headers: Dict[str, str] = {}
266
+ self._method: str = 'GET'
267
+ self._path: str = '/'
268
+
269
+ @property
270
+ def form(self) -> Dict[str, Any]:
271
+ """Form data from POST request."""
272
+ try:
273
+ from flask import request as flask_request
274
+ return flask_request.form
275
+ except (ImportError, RuntimeError):
276
+ return self._form
277
+
278
+ @property
279
+ def args(self) -> Dict[str, Any]:
280
+ """Query string parameters."""
281
+ try:
282
+ from flask import request as flask_request
283
+ return flask_request.args
284
+ except (ImportError, RuntimeError):
285
+ return self._args
286
+
287
+ @property
288
+ def method(self) -> str:
289
+ """HTTP method."""
290
+ try:
291
+ from flask import request as flask_request
292
+ return flask_request.method
293
+ except (ImportError, RuntimeError):
294
+ return self._method
295
+
296
+ @property
297
+ def path(self) -> str:
298
+ """Request path."""
299
+ try:
300
+ from flask import request as flask_request
301
+ return flask_request.path
302
+ except (ImportError, RuntimeError):
303
+ return self._path
304
+
305
+
306
+ # Global request context
307
+ request = RequestContext()
308
+
309
+
310
+ # =============================================================================
311
+ # Redirect
312
+ # =============================================================================
313
+
314
+ def redirect(url: str, code: int = 302):
315
+ """
316
+ Redirect to a URL.
317
+
318
+ Args:
319
+ url: Target URL
320
+ code: HTTP status code (302 = temporary, 301 = permanent)
321
+
322
+ Returns:
323
+ Redirect response
324
+ """
325
+ try:
326
+ from flask import redirect as flask_redirect
327
+ return flask_redirect(url, code=code)
328
+ except ImportError:
329
+ # Fallback for non-Flask usage
330
+ return f'<meta http-equiv="refresh" content="0; url={url}">'
331
+
332
+
333
+ # =============================================================================
334
+ # Utilities
335
+ # =============================================================================
336
+
337
+ def to_json(value: Any) -> str:
338
+ """Convert value to JSON string."""
339
+ return json.dumps(value)
340
+
341
+
342
+ def from_json(value: str) -> Any:
343
+ """Parse JSON string."""
344
+ return json.loads(value)
345
+
346
+
347
+ def safe_get(obj: Any, *keys, default: Any = None) -> Any:
348
+ """
349
+ Safely get nested values.
350
+
351
+ Args:
352
+ obj: Object to get value from
353
+ *keys: Keys/indices to traverse
354
+ default: Default value if not found
355
+
356
+ Returns:
357
+ Value or default
358
+ """
359
+ current = obj
360
+ for key in keys:
361
+ try:
362
+ if isinstance(current, dict):
363
+ current = current[key]
364
+ elif isinstance(current, (list, tuple)):
365
+ current = current[int(key)]
366
+ else:
367
+ current = getattr(current, key)
368
+ except (KeyError, IndexError, AttributeError, TypeError, ValueError):
369
+ return default
370
+ return current
371
+
372
+
373
+ # =============================================================================
374
+ # Data Loading (Phase 2)
375
+ # =============================================================================
376
+
377
+ def load_data(source: str, type: str = 'json') -> Any:
378
+ """
379
+ Load data from a file or URL.
380
+
381
+ Args:
382
+ source: File path or URL
383
+ type: Data type (json, csv, xml)
384
+
385
+ Returns:
386
+ Parsed data
387
+ """
388
+ import os
389
+
390
+ # Check if URL
391
+ if source.startswith(('http://', 'https://')):
392
+ try:
393
+ import urllib.request
394
+ with urllib.request.urlopen(source) as response:
395
+ content = response.read().decode('utf-8')
396
+ except Exception as e:
397
+ raise RuntimeError(f"Failed to fetch {source}: {e}")
398
+ else:
399
+ # File path
400
+ if not os.path.exists(source):
401
+ raise FileNotFoundError(f"Data file not found: {source}")
402
+ with open(source, 'r', encoding='utf-8') as f:
403
+ content = f.read()
404
+
405
+ # Parse based on type
406
+ if type == 'json':
407
+ return json.loads(content)
408
+ elif type == 'csv':
409
+ import csv
410
+ import io
411
+ reader = csv.DictReader(io.StringIO(content))
412
+ return list(reader)
413
+ elif type == 'xml':
414
+ import xml.etree.ElementTree as ET
415
+ return ET.fromstring(content)
416
+ else:
417
+ return content
418
+
419
+
420
+ # =============================================================================
421
+ # HTTP Invocation (Phase 2)
422
+ # =============================================================================
423
+
424
+ def invoke(url: str, method: str = 'GET', headers: Dict[str, str] = None,
425
+ body: Any = None, timeout: int = 30) -> Any:
426
+ """
427
+ Make an HTTP request.
428
+
429
+ Args:
430
+ url: Target URL
431
+ method: HTTP method
432
+ headers: Request headers
433
+ body: Request body (will be JSON-encoded)
434
+ timeout: Timeout in seconds
435
+
436
+ Returns:
437
+ Response data (JSON-parsed if applicable)
438
+ """
439
+ import urllib.request
440
+ import urllib.error
441
+
442
+ headers = headers or {}
443
+
444
+ # Add JSON content type if body provided
445
+ if body is not None and 'Content-Type' not in headers:
446
+ headers['Content-Type'] = 'application/json'
447
+
448
+ # Encode body
449
+ data = None
450
+ if body is not None:
451
+ data = json.dumps(body).encode('utf-8')
452
+
453
+ # Create request
454
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
455
+
456
+ try:
457
+ with urllib.request.urlopen(req, timeout=timeout) as response:
458
+ content = response.read().decode('utf-8')
459
+ content_type = response.headers.get('Content-Type', '')
460
+
461
+ # Parse JSON if applicable
462
+ if 'application/json' in content_type:
463
+ return json.loads(content)
464
+ return content
465
+ except urllib.error.HTTPError as e:
466
+ raise RuntimeError(f"HTTP {e.code}: {e.reason}")
467
+ except urllib.error.URLError as e:
468
+ raise RuntimeError(f"Request failed: {e.reason}")
469
+
470
+
471
+ # =============================================================================
472
+ # File Handling (Phase 2)
473
+ # =============================================================================
474
+
475
+ def handle_file(file: Any, action: str = 'upload', destination: str = './uploads/') -> Dict[str, Any]:
476
+ """
477
+ Handle file operations.
478
+
479
+ Args:
480
+ file: File object or path
481
+ action: Action type (upload, read, delete)
482
+ destination: Destination directory for uploads
483
+
484
+ Returns:
485
+ Result dict with file info
486
+ """
487
+ import os
488
+ import shutil
489
+ import uuid
490
+
491
+ result = {
492
+ 'success': False,
493
+ 'message': '',
494
+ 'path': None,
495
+ 'filename': None,
496
+ 'size': 0
497
+ }
498
+
499
+ try:
500
+ if action == 'upload':
501
+ # Ensure destination exists
502
+ os.makedirs(destination, exist_ok=True)
503
+
504
+ # Handle werkzeug FileStorage (Flask uploads)
505
+ if hasattr(file, 'save') and hasattr(file, 'filename'):
506
+ filename = file.filename
507
+ # Generate unique name to avoid conflicts
508
+ ext = os.path.splitext(filename)[1]
509
+ unique_name = f"{uuid.uuid4().hex}{ext}"
510
+ filepath = os.path.join(destination, unique_name)
511
+ file.save(filepath)
512
+ result['success'] = True
513
+ result['path'] = filepath
514
+ result['filename'] = unique_name
515
+ result['original_name'] = filename
516
+ result['size'] = os.path.getsize(filepath)
517
+ elif isinstance(file, str) and os.path.exists(file):
518
+ # Copy existing file
519
+ filename = os.path.basename(file)
520
+ filepath = os.path.join(destination, filename)
521
+ shutil.copy2(file, filepath)
522
+ result['success'] = True
523
+ result['path'] = filepath
524
+ result['filename'] = filename
525
+ result['size'] = os.path.getsize(filepath)
526
+ else:
527
+ result['message'] = 'Invalid file input'
528
+
529
+ elif action == 'read':
530
+ if isinstance(file, str) and os.path.exists(file):
531
+ with open(file, 'rb') as f:
532
+ result['content'] = f.read()
533
+ result['success'] = True
534
+ result['size'] = len(result['content'])
535
+ else:
536
+ result['message'] = 'File not found'
537
+
538
+ elif action == 'delete':
539
+ if isinstance(file, str) and os.path.exists(file):
540
+ os.remove(file)
541
+ result['success'] = True
542
+ result['message'] = 'File deleted'
543
+ else:
544
+ result['message'] = 'File not found'
545
+
546
+ except Exception as e:
547
+ result['message'] = str(e)
548
+
549
+ return result
550
+
551
+
552
+ # =============================================================================
553
+ # Email Sending (Phase 2)
554
+ # =============================================================================
555
+
556
+ def send_mail(to: str, from_addr: str = None, subject: str = '',
557
+ body: str = '', html: bool = False, attachments: List[str] = None) -> bool:
558
+ """
559
+ Send an email.
560
+
561
+ Args:
562
+ to: Recipient email address(es)
563
+ from_addr: Sender email address
564
+ subject: Email subject
565
+ body: Email body
566
+ html: Whether body is HTML
567
+ attachments: List of file paths to attach
568
+
569
+ Returns:
570
+ True if sent successfully
571
+ """
572
+ import smtplib
573
+ from email.mime.text import MIMEText
574
+ from email.mime.multipart import MIMEMultipart
575
+ from email.mime.base import MIMEBase
576
+ from email import encoders
577
+ import os
578
+
579
+ # Get SMTP configuration from environment
580
+ smtp_host = os.environ.get('SMTP_HOST', 'localhost')
581
+ smtp_port = int(os.environ.get('SMTP_PORT', '25'))
582
+ smtp_user = os.environ.get('SMTP_USER', '')
583
+ smtp_pass = os.environ.get('SMTP_PASS', '')
584
+ smtp_ssl = os.environ.get('SMTP_SSL', 'false').lower() == 'true'
585
+
586
+ if not from_addr:
587
+ from_addr = os.environ.get('SMTP_FROM', 'noreply@localhost')
588
+
589
+ # Create message
590
+ if attachments:
591
+ msg = MIMEMultipart()
592
+ msg.attach(MIMEText(body, 'html' if html else 'plain'))
593
+ else:
594
+ msg = MIMEText(body, 'html' if html else 'plain')
595
+
596
+ msg['Subject'] = subject
597
+ msg['From'] = from_addr
598
+ msg['To'] = to if isinstance(to, str) else ', '.join(to)
599
+
600
+ # Add attachments
601
+ if attachments:
602
+ for filepath in attachments:
603
+ if os.path.exists(filepath):
604
+ with open(filepath, 'rb') as f:
605
+ part = MIMEBase('application', 'octet-stream')
606
+ part.set_payload(f.read())
607
+ encoders.encode_base64(part)
608
+ part.add_header('Content-Disposition', f'attachment; filename="{os.path.basename(filepath)}"')
609
+ msg.attach(part)
610
+
611
+ try:
612
+ if smtp_ssl:
613
+ server = smtplib.SMTP_SSL(smtp_host, smtp_port)
614
+ else:
615
+ server = smtplib.SMTP(smtp_host, smtp_port)
616
+
617
+ if smtp_user and smtp_pass:
618
+ server.login(smtp_user, smtp_pass)
619
+
620
+ recipients = [to] if isinstance(to, str) else list(to)
621
+ server.sendmail(from_addr, recipients, msg.as_string())
622
+ server.quit()
623
+ return True
624
+ except Exception as e:
625
+ print(f"Email send failed: {e}")
626
+ return False
627
+
628
+
629
+ # =============================================================================
630
+ # Job Queue (Phase 3)
631
+ # =============================================================================
632
+
633
+ class JobQueue:
634
+ """Simple job queue for background processing."""
635
+
636
+ _jobs: Dict[str, Callable] = {}
637
+ _pending: List[Dict[str, Any]] = []
638
+
639
+ @classmethod
640
+ def task(cls, queue: str = 'default', priority: int = 0):
641
+ """Decorator to register a job task."""
642
+ def decorator(func: Callable):
643
+ cls._jobs[func.__name__] = {
644
+ 'func': func,
645
+ 'queue': queue,
646
+ 'priority': priority
647
+ }
648
+ return func
649
+ return decorator
650
+
651
+ @classmethod
652
+ def dispatch(cls, name: str, delay: str = None, **params):
653
+ """Dispatch a job for execution."""
654
+ import threading
655
+
656
+ if name not in cls._jobs:
657
+ raise ValueError(f"Job not found: {name}")
658
+
659
+ job = cls._jobs[name]
660
+
661
+ def run_job():
662
+ if delay:
663
+ import time
664
+ # Parse delay (e.g., "5m", "1h", "30s")
665
+ seconds = cls._parse_delay(delay)
666
+ time.sleep(seconds)
667
+ job['func'](**params)
668
+
669
+ thread = threading.Thread(target=run_job, daemon=True)
670
+ thread.start()
671
+
672
+ @classmethod
673
+ def cancel(cls, name: str):
674
+ """Cancel pending jobs (best effort)."""
675
+ cls._pending = [j for j in cls._pending if j.get('name') != name]
676
+
677
+ @staticmethod
678
+ def _parse_delay(delay: str) -> float:
679
+ """Parse delay string to seconds."""
680
+ import re
681
+ match = re.match(r'(\d+)([smhd])', delay)
682
+ if not match:
683
+ return 0
684
+ value, unit = int(match.group(1)), match.group(2)
685
+ multipliers = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
686
+ return value * multipliers.get(unit, 1)
687
+
688
+
689
+ # =============================================================================
690
+ # Scheduler (Phase 3)
691
+ # =============================================================================
692
+
693
+ class Scheduler:
694
+ """Task scheduler for periodic and one-time tasks."""
695
+
696
+ _tasks: Dict[str, Dict[str, Any]] = {}
697
+ _running: bool = False
698
+
699
+ @classmethod
700
+ def schedule(cls, interval: str = None, cron: str = None, at: str = None, enabled: bool = True):
701
+ """Decorator to schedule a task."""
702
+ def decorator(func: Callable):
703
+ cls._tasks[func.__name__] = {
704
+ 'func': func,
705
+ 'interval': interval,
706
+ 'cron': cron,
707
+ 'at': at,
708
+ 'enabled': enabled
709
+ }
710
+ return func
711
+ return decorator
712
+
713
+ @classmethod
714
+ def start(cls):
715
+ """Start the scheduler."""
716
+ import threading
717
+
718
+ if cls._running:
719
+ return
720
+
721
+ cls._running = True
722
+
723
+ def scheduler_loop():
724
+ import time
725
+ while cls._running:
726
+ for name, task in cls._tasks.items():
727
+ if not task['enabled']:
728
+ continue
729
+ # Simple interval handling
730
+ if task.get('interval'):
731
+ # Would need proper timing logic
732
+ pass
733
+ time.sleep(1)
734
+
735
+ thread = threading.Thread(target=scheduler_loop, daemon=True)
736
+ thread.start()
737
+
738
+ @classmethod
739
+ def stop(cls):
740
+ """Stop the scheduler."""
741
+ cls._running = False
742
+
743
+
744
+ # =============================================================================
745
+ # Async Thread (Phase 3)
746
+ # =============================================================================
747
+
748
+ class AsyncThread:
749
+ """Wrapper for async thread execution."""
750
+
751
+ _threads: Dict[str, 'AsyncThread'] = {}
752
+
753
+ def __init__(self, name: str, target: Callable, timeout: str = None, on_complete: Callable = None):
754
+ self.name = name
755
+ self.target = target
756
+ self.timeout = timeout
757
+ self.on_complete = on_complete
758
+ self._thread = None
759
+ self._result = None
760
+ AsyncThread._threads[name] = self
761
+
762
+ def start(self):
763
+ """Start the thread."""
764
+ import threading
765
+
766
+ def wrapper():
767
+ try:
768
+ self._result = self.target()
769
+ if self.on_complete:
770
+ self.on_complete(self._result)
771
+ except Exception as e:
772
+ self._result = e
773
+
774
+ self._thread = threading.Thread(target=wrapper, daemon=True)
775
+ self._thread.start()
776
+
777
+ def join(self, timeout: float = None):
778
+ """Wait for thread completion."""
779
+ if self._thread:
780
+ timeout_seconds = None
781
+ if timeout:
782
+ timeout_seconds = float(timeout)
783
+ elif self.timeout:
784
+ timeout_seconds = JobQueue._parse_delay(self.timeout)
785
+ self._thread.join(timeout=timeout_seconds)
786
+
787
+ def terminate(self):
788
+ """Terminate the thread (best effort)."""
789
+ # Python threads can't be forcefully terminated
790
+ # This is a placeholder for cooperative termination
791
+ pass
792
+
793
+ @property
794
+ def result(self):
795
+ """Get thread result."""
796
+ return self._result
797
+
798
+
799
+ def parallel_execute(tasks: List[Callable], max_workers: int = 4) -> List[Any]:
800
+ """Execute tasks in parallel."""
801
+ from concurrent.futures import ThreadPoolExecutor, as_completed
802
+
803
+ results = []
804
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
805
+ futures = {executor.submit(task): idx for idx, task in enumerate(tasks)}
806
+ results = [None] * len(tasks)
807
+ for future in as_completed(futures):
808
+ idx = futures[future]
809
+ try:
810
+ results[idx] = future.result()
811
+ except Exception as e:
812
+ results[idx] = e
813
+ return results
814
+
815
+
816
+ # =============================================================================
817
+ # Exports
818
+ # =============================================================================
819
+
820
+ __all__ = [
821
+ # HTML
822
+ 'escape',
823
+ 'raw',
824
+ 'RawHTML',
825
+ 'bind',
826
+
827
+ # Component
828
+ 'Component',
829
+
830
+ # Flash
831
+ 'flash',
832
+ 'get_flashed_messages',
833
+
834
+ # Scope
835
+ 'session',
836
+ 'application',
837
+ 'ScopeDict',
838
+
839
+ # Database
840
+ 'Database',
841
+
842
+ # Request
843
+ 'request',
844
+ 'RequestContext',
845
+ 'redirect',
846
+
847
+ # Data (Phase 2)
848
+ 'load_data',
849
+ 'invoke',
850
+ 'handle_file',
851
+ 'send_mail',
852
+
853
+ # Async (Phase 3)
854
+ 'JobQueue',
855
+ 'Scheduler',
856
+ 'AsyncThread',
857
+ 'parallel_execute',
858
+
859
+ # Utilities
860
+ 'to_json',
861
+ 'from_json',
862
+ 'safe_get',
863
+ ]