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,444 @@
1
+ """
2
+ Service Container - Dependency injection for runtime services
3
+
4
+ Centralizes initialization and access to all runtime services
5
+ (database, email, LLM, etc.) instead of having them scattered
6
+ across ComponentRuntime.__init__.
7
+ """
8
+
9
+ import copy
10
+ from typing import Any, Dict, Optional, TYPE_CHECKING
11
+ import logging
12
+
13
+ if TYPE_CHECKING:
14
+ from quantum.runtime.database_service import DatabaseService
15
+ from quantum.runtime.llm_service import LLMService
16
+ from quantum.runtime.knowledge_service import KnowledgeService
17
+ from quantum.runtime.message_queue_service import MessageQueueService
18
+ from quantum.runtime.job_executor import JobExecutor
19
+ from quantum.runtime.file_upload_service import FileUploadService
20
+ from quantum.runtime.email_service import EmailService
21
+ from quantum.runtime.function_registry import FunctionRegistry
22
+ from quantum.core.features.invocation.src.runtime import InvocationService
23
+ from quantum.core.features.data_import.src.runtime import DataImportService
24
+ from quantum.core.features.logging.src import LoggingService
25
+ from quantum.core.features.dump.src import DumpService
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ _DEFAULT_CONFIG_FILE = 'quantum.config.yaml'
30
+
31
+
32
+ _config_cache: Dict[str, Any] = {}
33
+
34
+
35
+ def load_project_config(config_path: str = _DEFAULT_CONFIG_FILE) -> Dict[str, Any]:
36
+ """Read quantum.config.yaml from the working directory, if it is there.
37
+
38
+ Missing file: empty configuration, no complaint — a .q file that uses no
39
+ datasource needs none. Unreadable or invalid file: empty configuration
40
+ and a warning, because silence there is how a typo in the YAML turns
41
+ into "datasource not declared" three layers down.
42
+
43
+ Cached by (path, mtime, size). Every ComponentRuntime() built without a
44
+ config re-read and re-parsed the file — 200 runtimes meant 200 disk reads
45
+ — and the suite builds thousands. Keying on mtime keeps the property that
46
+ matters: edit the file and the next runtime sees the change.
47
+
48
+ A copy is returned, not the cached dict: services mutate their config,
49
+ and handing out the same object would let one runtime's changes leak into
50
+ the next.
51
+ """
52
+ import os
53
+ try:
54
+ stat = os.stat(config_path)
55
+ except OSError:
56
+ return {}
57
+
58
+ key = (os.path.abspath(config_path), stat.st_mtime_ns, stat.st_size)
59
+ cached = _config_cache.get(key)
60
+ if cached is not None:
61
+ return copy.deepcopy(cached)
62
+
63
+ try:
64
+ import yaml
65
+ with open(config_path, 'r', encoding='utf-8') as handle:
66
+ loaded = yaml.safe_load(handle)
67
+ if loaded is None:
68
+ loaded = {}
69
+ if not isinstance(loaded, dict):
70
+ logger.warning("%s does not contain a mapping; ignoring it",
71
+ config_path)
72
+ loaded = {}
73
+ except Exception as exc:
74
+ logger.warning("could not read %s: %s", config_path, exc)
75
+ return {}
76
+
77
+ _config_cache.clear() # uma entrada basta; o arquivo e um so
78
+ _config_cache[key] = loaded
79
+ return copy.deepcopy(loaded)
80
+
81
+
82
+ class ServiceContainer:
83
+ """
84
+ Container for all runtime services.
85
+
86
+ Provides lazy initialization and centralized access to services.
87
+ Services are initialized on first access to avoid unnecessary overhead.
88
+
89
+ Example:
90
+ services = ServiceContainer(config)
91
+ db = services.database # Initialized on first access
92
+ llm = services.llm
93
+ """
94
+
95
+ def __init__(self, config: Dict[str, Any] = None):
96
+ """
97
+ Initialize service container.
98
+
99
+ Args:
100
+ config: Configuration dictionary with service-specific settings.
101
+ Omit it (None) to read quantum.config.yaml from the working
102
+ directory; pass {} for a deliberately empty configuration.
103
+
104
+ `None` used to mean "no configuration", so anything that built a
105
+ runtime without passing one — the .q test plugin, library callers,
106
+ the admin — could not see the `datasources:` declared in
107
+ quantum.config.yaml, and every q:query and q:transaction fell through
108
+ to the optional Admin API at localhost:8000 and failed with a
109
+ connection error. `quantum run` was fixed to load the file; that fix
110
+ did not reach anybody else. Now the default is the same either way,
111
+ and passing {} still means empty.
112
+ """
113
+ self._config = load_project_config() if config is None else config
114
+ self._services: Dict[str, Any] = {}
115
+
116
+ # ==========================================================================
117
+ # Core Services
118
+ # ==========================================================================
119
+
120
+ @property
121
+ def database(self) -> 'DatabaseService':
122
+ """Database service for query execution"""
123
+ if 'database' not in self._services:
124
+ from quantum.runtime.database_service import DatabaseService
125
+ local_ds = self._config.get('datasources', {})
126
+ self._services['database'] = DatabaseService(local_datasources=local_ds)
127
+ logger.debug("Initialized DatabaseService")
128
+ return self._services['database']
129
+
130
+ @property
131
+ def function_registry(self) -> 'FunctionRegistry':
132
+ """Function registry for q:function"""
133
+ if 'function_registry' not in self._services:
134
+ from quantum.runtime.function_registry import FunctionRegistry
135
+ self._services['function_registry'] = FunctionRegistry()
136
+ logger.debug("Initialized FunctionRegistry")
137
+ return self._services['function_registry']
138
+
139
+ # ==========================================================================
140
+ # Invocation & Data Services
141
+ # ==========================================================================
142
+
143
+ @property
144
+ def invocation(self) -> 'InvocationService':
145
+ """Invocation service for q:invoke"""
146
+ if 'invocation' not in self._services:
147
+ from quantum.core.features.invocation.src.runtime import InvocationService
148
+ self._services['invocation'] = InvocationService()
149
+ logger.debug("Initialized InvocationService")
150
+ return self._services['invocation']
151
+
152
+ @property
153
+ def data_import(self) -> 'DataImportService':
154
+ """Data import service for q:data"""
155
+ if 'data_import' not in self._services:
156
+ from quantum.core.features.data_import.src.runtime import DataImportService
157
+ self._services['data_import'] = DataImportService()
158
+ logger.debug("Initialized DataImportService")
159
+ return self._services['data_import']
160
+
161
+ # ==========================================================================
162
+ # AI Services
163
+ # ==========================================================================
164
+
165
+ @property
166
+ def llm(self) -> 'LLMService':
167
+ """Ollama-only LLM service, used by q:knowledge and the RAG path.
168
+
169
+ It used to be constructed with NO arguments, so it fell back to its own
170
+ default of http://localhost:11434 and ignored llm.base_url entirely —
171
+ while multi_llm (q:llm, q:agent, q:team) honoured it. The two halves of
172
+ the AI stack therefore pointed at DIFFERENT Ollama servers.
173
+
174
+ Measured on this machine: config says http://localhost:11434, q:llm
175
+ reached that host and answered, and q:knowledge silently went to
176
+ localhost. In a deployment where only the configured host has models,
177
+ q:llm works and RAG fails, with nothing explaining why.
178
+ """
179
+ if 'llm' not in self._services:
180
+ from quantum.runtime.llm_service import LLMService
181
+ llm_cfg = self._config.get('llm', {}) or {}
182
+ self._services['llm'] = LLMService(
183
+ base_url=llm_cfg.get('base_url'),
184
+ default_model=llm_cfg.get('default_model'),
185
+ timeout=llm_cfg.get('timeout', 60),
186
+ )
187
+ logger.debug("Initialized LLMService")
188
+ return self._services['llm']
189
+
190
+ @property
191
+ def knowledge(self) -> 'KnowledgeService':
192
+ """Knowledge service for RAG/ChromaDB"""
193
+ if 'knowledge' not in self._services:
194
+ from quantum.runtime.knowledge_service import KnowledgeService
195
+ self._services['knowledge'] = KnowledgeService(self.llm)
196
+ logger.debug("Initialized KnowledgeService")
197
+ return self._services['knowledge']
198
+
199
+ @property
200
+ def agent(self):
201
+ """Agent service for q:agent"""
202
+ if 'agent' not in self._services:
203
+ from quantum.runtime.agent_service import get_agent_service
204
+ self._services['agent'] = get_agent_service()
205
+ logger.debug("Initialized AgentService")
206
+ return self._services['agent']
207
+
208
+ @property
209
+ def multi_agent(self):
210
+ """Multi-agent service for q:team"""
211
+ if 'multi_agent' not in self._services:
212
+ from quantum.runtime.agent_service import get_multi_agent_service
213
+ self._services['multi_agent'] = get_multi_agent_service()
214
+ logger.debug("Initialized MultiAgentService")
215
+ return self._services['multi_agent']
216
+
217
+ # ==========================================================================
218
+ # Communication Services
219
+ # ==========================================================================
220
+
221
+ @property
222
+ def message_queue(self) -> 'MessageQueueService':
223
+ """Message queue service for q:message, q:subscribe"""
224
+ if 'message_queue' not in self._services:
225
+ from quantum.runtime.message_queue_service import MessageQueueService
226
+ mq_config = self._config.get('message_queue', {})
227
+ self._services['message_queue'] = MessageQueueService(mq_config)
228
+ logger.debug("Initialized MessageQueueService")
229
+ return self._services['message_queue']
230
+
231
+ @property
232
+ def multi_llm(self) -> 'MultiProviderLLMService':
233
+ """Multi-provider LLM service (Ollama, OpenAI, Anthropic, LM Studio).
234
+
235
+ q:agent already used this; q:llm was stuck on the Ollama-only
236
+ LLMService, which is why 'multi-provider' was only half true.
237
+ """
238
+ if 'multi_llm' not in self._services:
239
+ from quantum.runtime.llm_providers import MultiProviderLLMService
240
+ llm_cfg = self._config.get('llm', {}) or {}
241
+ self._services['multi_llm'] = MultiProviderLLMService(
242
+ default_endpoint=llm_cfg.get('base_url'),
243
+ default_model=llm_cfg.get('default_model'),
244
+ timeout=llm_cfg.get('timeout', 60),
245
+ )
246
+ logger.debug("Initialized MultiProviderLLMService")
247
+ return self._services['multi_llm']
248
+
249
+ @property
250
+ def llm_cache(self) -> 'LLMCache':
251
+ """TTL cache for q:llm responses (the `cache` attribute's backing)."""
252
+ if 'llm_cache' not in self._services:
253
+ from quantum.runtime.llm_cache import get_llm_cache
254
+ self._services['llm_cache'] = get_llm_cache()
255
+ return self._services['llm_cache']
256
+
257
+ @property
258
+ def config(self) -> Dict[str, Any]:
259
+ """Raw configuration dict (quantum.config.yaml), for executors that
260
+ need policy settings rather than a service."""
261
+ return self._config
262
+
263
+ @property
264
+ def python_scripting_enabled(self) -> bool:
265
+ """Whether q:python / q:pyclass / q:pyimport may execute.
266
+
267
+ q:python is a full-trust escape hatch: it runs arbitrary Python in
268
+ the server process with no sandbox. That is fine for code the
269
+ application author wrote, and unacceptable for code anything else
270
+ wrote — an LLM-generated or user-submitted template, a component
271
+ from a marketplace, a .q file someone sent you.
272
+
273
+ **Desligado por padrão.** Era ligado, o que fazia dele a última
274
+ falha-aberta de configuração do framework: bastava executar um .q
275
+ que você não escreveu para executar o Python dele. Um default
276
+ precisa ser seguro para quem não leu a documentação — e quem usa
277
+ q:python leu, porque teve de escrever a tag.
278
+
279
+ Ligue com `security.python_scripting: true` no quantum.config.yaml,
280
+ ou QUANTUM_PYTHON_SCRIPTING=1 no ambiente.
281
+ """
282
+ security = self._config.get('security', {}) or {}
283
+ configured = security.get('python_scripting')
284
+ if configured is not None:
285
+ return configured is not False
286
+
287
+ import os
288
+ env = os.environ.get('QUANTUM_PYTHON_SCRIPTING', '').strip().lower()
289
+ return env in ('1', 'true', 'yes', 'on')
290
+
291
+ @property
292
+ def websocket(self) -> 'WebSocketService':
293
+ """WebSocket service for q:websocket, q:send, q:close.
294
+
295
+ The service existed but was never exposed here, so every
296
+ q:websocket tag raised AttributeError on services.websocket.
297
+ """
298
+ if 'websocket' not in self._services:
299
+ from quantum.runtime.websocket_service import get_websocket_service
300
+ self._services['websocket'] = get_websocket_service()
301
+ logger.debug("Initialized WebSocketService")
302
+ return self._services['websocket']
303
+
304
+ @property
305
+ def email(self) -> 'EmailService':
306
+ """Email service for q:mail"""
307
+ if 'email' not in self._services:
308
+ from quantum.runtime.email_service import EmailService
309
+ self._services['email'] = EmailService()
310
+ logger.debug("Initialized EmailService")
311
+ return self._services['email']
312
+
313
+ # ==========================================================================
314
+ # Job & Scheduling Services
315
+ # ==========================================================================
316
+
317
+ @property
318
+ def job_executor(self) -> 'JobExecutor':
319
+ """Job executor for q:schedule, q:thread, q:job"""
320
+ if 'job_executor' not in self._services:
321
+ from quantum.runtime.job_executor import JobExecutor
322
+ job_db_path = self._config.get('job_db_path', 'quantum_jobs.db')
323
+ max_workers = self._config.get('max_thread_workers', 10)
324
+ self._services['job_executor'] = JobExecutor(
325
+ max_thread_workers=max_workers,
326
+ job_db_path=job_db_path
327
+ )
328
+ logger.debug("Initialized JobExecutor")
329
+ return self._services['job_executor']
330
+
331
+ # ==========================================================================
332
+ # File & Upload Services
333
+ # ==========================================================================
334
+
335
+ @property
336
+ def file_upload(self) -> 'FileUploadService':
337
+ """File upload service for q:file"""
338
+ if 'file_upload' not in self._services:
339
+ from quantum.runtime.file_upload_service import FileUploadService
340
+ # Every path q:file touches is confined to this root. Configurable
341
+ # via paths.uploads; ./uploads by default, which is where every
342
+ # destination= in this repo already points.
343
+ root = (self._config.get('paths', {}) or {}).get('uploads', 'uploads')
344
+ self._services['file_upload'] = FileUploadService(root=root)
345
+ logger.debug("Initialized FileUploadService")
346
+ return self._services['file_upload']
347
+
348
+ # ==========================================================================
349
+ # Developer Experience Services
350
+ # ==========================================================================
351
+
352
+ @property
353
+ def logging(self) -> 'LoggingService':
354
+ """Logging service for q:log"""
355
+ if 'logging' not in self._services:
356
+ from quantum.core.features.logging.src import LoggingService
357
+ self._services['logging'] = LoggingService()
358
+ logger.debug("Initialized LoggingService")
359
+ return self._services['logging']
360
+
361
+ @property
362
+ def dump(self) -> 'DumpService':
363
+ """Dump service for q:dump"""
364
+ if 'dump' not in self._services:
365
+ from quantum.core.features.dump.src import DumpService
366
+ self._services['dump'] = DumpService()
367
+ logger.debug("Initialized DumpService")
368
+ return self._services['dump']
369
+
370
+ # ==========================================================================
371
+ # Cache Services
372
+ # ==========================================================================
373
+
374
+ @property
375
+ def expression_cache(self):
376
+ """Expression cache for performance"""
377
+ if 'expression_cache' not in self._services:
378
+ from quantum.runtime.expression_cache import get_expression_cache
379
+ self._services['expression_cache'] = get_expression_cache()
380
+ logger.debug("Initialized ExpressionCache")
381
+ return self._services['expression_cache']
382
+
383
+ # ==========================================================================
384
+ # Utility Methods
385
+ # ==========================================================================
386
+
387
+ def get(self, name: str, default: Any = None) -> Any:
388
+ """
389
+ Get a service by name.
390
+
391
+ Args:
392
+ name: Service name (e.g., 'database', 'llm')
393
+ default: Default value if not found
394
+
395
+ Returns:
396
+ Service instance or default
397
+ """
398
+ # Try property access first
399
+ if hasattr(self, name):
400
+ return getattr(self, name)
401
+ return self._services.get(name, default)
402
+
403
+ def register(self, name: str, service: Any) -> 'ServiceContainer':
404
+ """
405
+ Register a custom service.
406
+
407
+ Args:
408
+ name: Service name
409
+ service: Service instance
410
+
411
+ Returns:
412
+ Self for chaining
413
+ """
414
+ self._services[name] = service
415
+ return self
416
+
417
+ def is_initialized(self, name: str) -> bool:
418
+ """Check if a service has been initialized"""
419
+ return name in self._services
420
+
421
+ @property
422
+ def initialized_services(self) -> list:
423
+ """Get list of initialized service names"""
424
+ return list(self._services.keys())
425
+
426
+ def shutdown(self):
427
+ """
428
+ Shutdown all services that support it.
429
+
430
+ Call this when the runtime is being destroyed.
431
+ """
432
+ for name, service in self._services.items():
433
+ if hasattr(service, 'shutdown'):
434
+ try:
435
+ service.shutdown()
436
+ logger.debug(f"Shutdown {name}")
437
+ except Exception as e:
438
+ logger.warning(f"Error shutting down {name}: {e}")
439
+
440
+ self._services.clear()
441
+
442
+ def __repr__(self) -> str:
443
+ initialized = list(self._services.keys())
444
+ return f"ServiceContainer(initialized={initialized})"
@@ -0,0 +1,76 @@
1
+ """
2
+ Terminal Engine - Builder/Orchestrator
3
+
4
+ Orchestrates the compilation pipeline:
5
+ ApplicationNode (type="terminal") -> extract screens/keybindings/css -> TerminalCodeGenerator -> .py file
6
+
7
+ Usage:
8
+ builder = TerminalBuilder()
9
+ python_code = builder.build(app_node)
10
+ builder.build_to_file(app_node, "output.py")
11
+ """
12
+
13
+ import os
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ from quantum.core.ast_nodes import ApplicationNode
18
+ from quantum.core.features.terminal_engine.src.ast_nodes import (
19
+ ScreenNode, KeybindingNode, ServiceNode, CssNode,
20
+ )
21
+ from quantum.runtime.terminal_code_generator import TerminalCodeGenerator
22
+
23
+
24
+ class TerminalBuildError(Exception):
25
+ """Error during terminal app build."""
26
+ pass
27
+
28
+
29
+ class TerminalBuilder:
30
+ """Builds a standalone Python TUI app from a Quantum terminal ApplicationNode."""
31
+
32
+ def build(self, app: ApplicationNode) -> str:
33
+ """Build Python source string from an ApplicationNode with type='terminal'.
34
+
35
+ The ApplicationNode is expected to have:
36
+ - app.screens: list of ScreenNode
37
+ - app.terminal_css: str (optional)
38
+ - app.keybindings: list of KeybindingNode (optional)
39
+ - app.services: list of ServiceNode (optional)
40
+ """
41
+ screens = getattr(app, 'screens', [])
42
+ terminal_css = getattr(app, 'terminal_css', '')
43
+ keybindings = getattr(app, 'keybindings', [])
44
+ services = getattr(app, 'services', [])
45
+
46
+ if not screens:
47
+ raise TerminalBuildError("No screens found in terminal application")
48
+
49
+ valid_screens = [s for s in screens if isinstance(s, ScreenNode)]
50
+ if not valid_screens:
51
+ raise TerminalBuildError("No valid ScreenNode found in terminal application")
52
+
53
+ generator = TerminalCodeGenerator()
54
+ python_code = generator.generate(
55
+ screens=valid_screens,
56
+ title=app.app_id,
57
+ terminal_css=terminal_css,
58
+ keybindings=[k for k in keybindings if isinstance(k, KeybindingNode)],
59
+ services=[s for s in services if isinstance(s, ServiceNode)],
60
+ functions=getattr(app, 'functions', None) or [],
61
+ state_vars=getattr(app, 'state_vars', None) or [],
62
+ )
63
+ return python_code
64
+
65
+ def build_to_file(self, app: ApplicationNode, output_path: Optional[str] = None) -> str:
66
+ """Build and write .py file. Returns the output file path."""
67
+ python_code = self.build(app)
68
+
69
+ if output_path is None:
70
+ output_path = f"{app.app_id}.py"
71
+
72
+ path = Path(output_path)
73
+ path.parent.mkdir(parents=True, exist_ok=True)
74
+ path.write_text(python_code, encoding='utf-8')
75
+
76
+ return str(path.resolve())