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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- 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())
|