quantum-framework 0.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -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 +548 -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 +565 -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 +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -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_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -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 +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -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/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -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 +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -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 +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -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 +79 -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 +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -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/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 +164 -0
- quantum/core/xml_lines.py +81 -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 +25 -0
- quantum/runtime/action_handler.py +532 -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/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -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 +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -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 +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -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 +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -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/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -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/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python Executor - Execute q:python statements
|
|
3
|
+
|
|
4
|
+
Handles embedded Python code execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import textwrap
|
|
9
|
+
from typing import Any, List, Dict, Type
|
|
10
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
11
|
+
from quantum.core.ast_nodes import PythonNode
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def normalise_python_source(raw: str) -> str:
|
|
17
|
+
"""Make XML-indented Python compile.
|
|
18
|
+
|
|
19
|
+
The code inside <q:python> is indented to match the surrounding markup,
|
|
20
|
+
which is what anyone writing XML does:
|
|
21
|
+
|
|
22
|
+
<q:python>
|
|
23
|
+
total = 0
|
|
24
|
+
for i in range(5):
|
|
25
|
+
total += i
|
|
26
|
+
</q:python>
|
|
27
|
+
|
|
28
|
+
This was passed through `.strip()`, which removes the leading whitespace
|
|
29
|
+
of the string — so the FIRST line lost its indentation and every other
|
|
30
|
+
line kept it. Python then said "unexpected indent (<string>, line 2)".
|
|
31
|
+
Every multi-line q:python block in the documentation fails that way.
|
|
32
|
+
|
|
33
|
+
Strategy: try the candidate forms in order and take the first that
|
|
34
|
+
compiles. The current behaviour is tried first, so nothing that worked
|
|
35
|
+
before changes; dedent covers the ordinary case; and dedenting the tail
|
|
36
|
+
separately covers code that starts on the tag's own line.
|
|
37
|
+
"""
|
|
38
|
+
text = (raw or "").replace("\r\n", "\n").replace("\r", "\n")
|
|
39
|
+
if not text.strip():
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
candidates = [text.strip(), textwrap.dedent(text).strip("\n")]
|
|
43
|
+
|
|
44
|
+
lines = text.split("\n")
|
|
45
|
+
if lines and lines[0].strip() and len(lines) > 1:
|
|
46
|
+
tail = textwrap.dedent("\n".join(lines[1:]))
|
|
47
|
+
candidates.append((lines[0].strip() + "\n" + tail).strip("\n"))
|
|
48
|
+
|
|
49
|
+
for candidate in candidates:
|
|
50
|
+
try:
|
|
51
|
+
compile(candidate, "<q:python>", "exec")
|
|
52
|
+
return candidate
|
|
53
|
+
except SyntaxError:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# None compile: the code is genuinely broken. Hand back the dedented
|
|
57
|
+
# form, so the error the user sees points at their code rather than at
|
|
58
|
+
# indentation this function should have handled.
|
|
59
|
+
return candidates[1]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PythonExecutor(BaseExecutor):
|
|
63
|
+
"""
|
|
64
|
+
Executor for q:python statements.
|
|
65
|
+
|
|
66
|
+
Supports:
|
|
67
|
+
- Inline Python code execution
|
|
68
|
+
- Component, isolated, and module scopes
|
|
69
|
+
- Async execution
|
|
70
|
+
- Timeout limits
|
|
71
|
+
- Bridge object for context access
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def handles(self) -> List[Type]:
|
|
76
|
+
return [PythonNode]
|
|
77
|
+
|
|
78
|
+
def execute(self, node: PythonNode, exec_context) -> Any:
|
|
79
|
+
"""
|
|
80
|
+
Execute Python code block.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
node: PythonNode with Python code
|
|
84
|
+
exec_context: Execution context
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Execution result (if result attribute set)
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
self.require_python_scripting('q:python')
|
|
91
|
+
|
|
92
|
+
context = exec_context.get_all_variables()
|
|
93
|
+
|
|
94
|
+
# Build bridge object for q.variable access
|
|
95
|
+
bridge = QuantumBridge(exec_context, context)
|
|
96
|
+
|
|
97
|
+
# Build execution namespace
|
|
98
|
+
namespace = {
|
|
99
|
+
'q': bridge,
|
|
100
|
+
'__quantum_context__': context
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Add component scope variables if not isolated
|
|
104
|
+
if node.scope == 'component':
|
|
105
|
+
namespace.update(context)
|
|
106
|
+
|
|
107
|
+
# Execute code (dedented — see normalise_python_source)
|
|
108
|
+
code = normalise_python_source(node.code)
|
|
109
|
+
|
|
110
|
+
if node.async_mode:
|
|
111
|
+
result = self._execute_async(code, namespace, node.timeout)
|
|
112
|
+
else:
|
|
113
|
+
result = self._execute_sync(code, namespace, node.timeout)
|
|
114
|
+
|
|
115
|
+
# Store result if requested
|
|
116
|
+
if node.result:
|
|
117
|
+
exec_context.set_variable(node.result, result, scope="component")
|
|
118
|
+
|
|
119
|
+
# Export bridge changes to context
|
|
120
|
+
for key, value in bridge._exports.items():
|
|
121
|
+
exec_context.set_variable(key, value, scope="component")
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
raise ExecutorError(f"Python execution error: {e}")
|
|
127
|
+
|
|
128
|
+
def _execute_sync(self, code: str, namespace: Dict, timeout: str = None) -> Any:
|
|
129
|
+
"""Execute Python code synchronously."""
|
|
130
|
+
# Handle return statements
|
|
131
|
+
if 'return ' in code:
|
|
132
|
+
# Wrap in function
|
|
133
|
+
wrapped = f"def __quantum_exec__():\n"
|
|
134
|
+
for line in code.split('\n'):
|
|
135
|
+
wrapped += f" {line}\n"
|
|
136
|
+
wrapped += "__quantum_result__ = __quantum_exec__()"
|
|
137
|
+
exec(wrapped, namespace)
|
|
138
|
+
return namespace.get('__quantum_result__')
|
|
139
|
+
else:
|
|
140
|
+
exec(code, namespace)
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def _execute_async(self, code: str, namespace: Dict, timeout: str = None) -> Any:
|
|
144
|
+
"""Execute Python code asynchronously."""
|
|
145
|
+
import asyncio
|
|
146
|
+
|
|
147
|
+
async def run_async():
|
|
148
|
+
# Wrap in async function
|
|
149
|
+
wrapped = f"async def __quantum_async__():\n"
|
|
150
|
+
for line in code.split('\n'):
|
|
151
|
+
wrapped += f" {line}\n"
|
|
152
|
+
exec(wrapped, namespace)
|
|
153
|
+
return await namespace['__quantum_async__']()
|
|
154
|
+
|
|
155
|
+
# Parse timeout
|
|
156
|
+
timeout_seconds = None
|
|
157
|
+
if timeout:
|
|
158
|
+
timeout_seconds = self._parse_timeout(timeout)
|
|
159
|
+
|
|
160
|
+
return asyncio.run(asyncio.wait_for(run_async(), timeout=timeout_seconds))
|
|
161
|
+
|
|
162
|
+
def _parse_timeout(self, timeout: str) -> float:
|
|
163
|
+
"""Parse timeout string to seconds."""
|
|
164
|
+
if timeout.endswith('s'):
|
|
165
|
+
return float(timeout[:-1])
|
|
166
|
+
elif timeout.endswith('m'):
|
|
167
|
+
return float(timeout[:-1]) * 60
|
|
168
|
+
elif timeout.endswith('h'):
|
|
169
|
+
return float(timeout[:-1]) * 3600
|
|
170
|
+
return float(timeout)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class QuantumBridge:
|
|
174
|
+
"""Bridge object for accessing Quantum context from Python."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, exec_context, context: Dict[str, Any]):
|
|
177
|
+
self._exec_context = exec_context
|
|
178
|
+
self._context = context
|
|
179
|
+
self._exports = {}
|
|
180
|
+
self._missing_reported = set()
|
|
181
|
+
|
|
182
|
+
def __getattr__(self, name: str) -> Any:
|
|
183
|
+
"""Get variable from context.
|
|
184
|
+
|
|
185
|
+
A name that is not in the context returns None, as before — scripts
|
|
186
|
+
do test optional variables. But it now says so once: `q.info(...)`
|
|
187
|
+
in the shipped example resolved to None and then raised
|
|
188
|
+
"'NoneType' object is not callable" four frames away, with nothing
|
|
189
|
+
naming `info`. (q.info exists now; the diagnosis should still work
|
|
190
|
+
for the next typo.)
|
|
191
|
+
"""
|
|
192
|
+
if name.startswith('_'):
|
|
193
|
+
return object.__getattribute__(self, name)
|
|
194
|
+
if name not in self._context:
|
|
195
|
+
if name not in self._missing_reported:
|
|
196
|
+
self._missing_reported.add(name)
|
|
197
|
+
logger.warning(
|
|
198
|
+
"q:python read q.%s, which is not defined in the "
|
|
199
|
+
"component — it is None. Calling it raises "
|
|
200
|
+
"\"'NoneType' object is not callable\".", name
|
|
201
|
+
)
|
|
202
|
+
return self._context.get(name)
|
|
203
|
+
|
|
204
|
+
# -- logging from a script --------------------------------------------
|
|
205
|
+
#
|
|
206
|
+
# The documented example calls q.info(...). There was no such method, so
|
|
207
|
+
# it resolved to None through __getattr__ above and the whole block died
|
|
208
|
+
# on the call. These are what that example always meant.
|
|
209
|
+
|
|
210
|
+
def log(self, *parts: Any) -> None:
|
|
211
|
+
"""Log at info level."""
|
|
212
|
+
logger.info("q:python: %s", " ".join(str(p) for p in parts))
|
|
213
|
+
|
|
214
|
+
def info(self, *parts: Any) -> None:
|
|
215
|
+
self.log(*parts)
|
|
216
|
+
|
|
217
|
+
def debug(self, *parts: Any) -> None:
|
|
218
|
+
logger.debug("q:python: %s", " ".join(str(p) for p in parts))
|
|
219
|
+
|
|
220
|
+
def warn(self, *parts: Any) -> None:
|
|
221
|
+
logger.warning("q:python: %s", " ".join(str(p) for p in parts))
|
|
222
|
+
|
|
223
|
+
warning = warn
|
|
224
|
+
|
|
225
|
+
def error(self, *parts: Any) -> None:
|
|
226
|
+
logger.error("q:python: %s", " ".join(str(p) for p in parts))
|
|
227
|
+
|
|
228
|
+
def __setattr__(self, name: str, value: Any):
|
|
229
|
+
"""Set variable in context."""
|
|
230
|
+
if name.startswith('_'):
|
|
231
|
+
object.__setattr__(self, name, value)
|
|
232
|
+
else:
|
|
233
|
+
self._exports[name] = value
|
|
234
|
+
|
|
235
|
+
def export(self, name: str, value: Any):
|
|
236
|
+
"""Explicitly export a variable."""
|
|
237
|
+
self._exports[name] = value
|
|
238
|
+
|
|
239
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
240
|
+
"""Get variable with default."""
|
|
241
|
+
return self._context.get(name, default)
|
|
242
|
+
|
|
243
|
+
def set(self, name: str, value: Any):
|
|
244
|
+
"""Set variable."""
|
|
245
|
+
self._exports[name] = value
|
|
246
|
+
|
|
247
|
+
def has(self, name: str) -> bool:
|
|
248
|
+
"""Check if variable exists."""
|
|
249
|
+
return name in self._context
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service Executors
|
|
3
|
+
|
|
4
|
+
Executors for service operations: log, dump, file, mail.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .log_executor import LogExecutor
|
|
8
|
+
from .dump_executor import DumpExecutor
|
|
9
|
+
from .file_executor import FileExecutor
|
|
10
|
+
from .mail_executor import MailExecutor
|
|
11
|
+
|
|
12
|
+
__all__ = ['LogExecutor', 'DumpExecutor', 'FileExecutor', 'MailExecutor']
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dump Executor - Execute q:dump statements
|
|
3
|
+
|
|
4
|
+
Handles variable inspection and debugging output.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
9
|
+
from quantum.core.features.dump.src import DumpNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DumpExecutor(BaseExecutor):
|
|
13
|
+
"""
|
|
14
|
+
Executor for q:dump statements.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- Variable inspection with depth control
|
|
18
|
+
- Multiple output formats (text, json, html)
|
|
19
|
+
- Conditional dumping
|
|
20
|
+
- Labeled output
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def handles(self) -> List[Type]:
|
|
25
|
+
return [DumpNode]
|
|
26
|
+
|
|
27
|
+
def execute(self, node: DumpNode, exec_context) -> Any:
|
|
28
|
+
"""
|
|
29
|
+
Execute variable dump/inspection.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
node: DumpNode with dump configuration
|
|
33
|
+
exec_context: Execution context
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Formatted dump output string
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
context = exec_context.get_all_variables()
|
|
40
|
+
|
|
41
|
+
# Check conditional execution
|
|
42
|
+
if node.when:
|
|
43
|
+
condition_result = self.apply_databinding(node.when, context)
|
|
44
|
+
if not self.services.dump.should_dump(condition_result):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Resolve variable to dump
|
|
48
|
+
var_expr = node.var
|
|
49
|
+
try:
|
|
50
|
+
var_value = self.apply_databinding(var_expr, context)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
var_value = f"<Variable '{var_expr}' not found: {e}>"
|
|
53
|
+
|
|
54
|
+
# Generate dump output
|
|
55
|
+
dump_output = self.services.dump.dump(
|
|
56
|
+
var=var_value,
|
|
57
|
+
label=node.label,
|
|
58
|
+
format=node.format,
|
|
59
|
+
depth=node.depth
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Print dump output
|
|
63
|
+
print(dump_output)
|
|
64
|
+
|
|
65
|
+
# Store in context for potential template rendering
|
|
66
|
+
dump_var_name = f"_dump_{node.label.replace(' ', '_')}"
|
|
67
|
+
exec_context.set_variable(dump_var_name, dump_output, scope="component")
|
|
68
|
+
|
|
69
|
+
return dump_output
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise ExecutorError(f"Dump error: {e}")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Executor - Execute q:file statements
|
|
3
|
+
|
|
4
|
+
Handles file upload and delete operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
9
|
+
from quantum.core.ast_nodes import FileNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileExecutor(BaseExecutor):
|
|
13
|
+
"""
|
|
14
|
+
Executor for q:file statements.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- File upload with conflict handling
|
|
18
|
+
- File deletion
|
|
19
|
+
- Result storage in context
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def handles(self) -> List[Type]:
|
|
24
|
+
return [FileNode]
|
|
25
|
+
|
|
26
|
+
def execute(self, node: FileNode, exec_context) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Execute file operation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
node: FileNode with file operation configuration
|
|
32
|
+
exec_context: Execution context
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
File operation result dict
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
context = exec_context.get_all_variables()
|
|
39
|
+
|
|
40
|
+
if node.action == 'upload':
|
|
41
|
+
return self._execute_upload(node, context, exec_context)
|
|
42
|
+
elif node.action == 'delete':
|
|
43
|
+
return self._execute_delete(node, context, exec_context)
|
|
44
|
+
else:
|
|
45
|
+
raise ExecutorError(f"Unsupported file action: {node.action}")
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise ExecutorError(f"File execution error: {e}")
|
|
49
|
+
|
|
50
|
+
def _execute_upload(self, node: FileNode, context: Dict[str, Any], exec_context) -> Dict:
|
|
51
|
+
"""Execute file upload"""
|
|
52
|
+
# Resolve file variable
|
|
53
|
+
file_var = node.file.strip('{}')
|
|
54
|
+
file_obj = context.get(file_var)
|
|
55
|
+
|
|
56
|
+
if not file_obj:
|
|
57
|
+
raise ExecutorError(f"File variable '{file_var}' not found")
|
|
58
|
+
|
|
59
|
+
# Resolve destination
|
|
60
|
+
destination = self.apply_databinding(node.destination, context)
|
|
61
|
+
|
|
62
|
+
# Upload file
|
|
63
|
+
result = self.services.file_upload.upload_file(
|
|
64
|
+
file=file_obj,
|
|
65
|
+
destination=destination,
|
|
66
|
+
name_conflict=node.name_conflict
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Store results
|
|
70
|
+
if node.result:
|
|
71
|
+
exec_context.set_variable(node.result, result, scope="component")
|
|
72
|
+
|
|
73
|
+
exec_context.set_variable(f"{file_var}_upload", result, scope="component")
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def _execute_delete(self, node: FileNode, context: Dict[str, Any], exec_context) -> Dict:
|
|
78
|
+
"""Execute file deletion"""
|
|
79
|
+
# Resolve filepath
|
|
80
|
+
filepath = self.apply_databinding(node.file, context)
|
|
81
|
+
|
|
82
|
+
# Delete file
|
|
83
|
+
result = self.services.file_upload.delete_file(filepath)
|
|
84
|
+
|
|
85
|
+
# Store result
|
|
86
|
+
if node.result:
|
|
87
|
+
exec_context.set_variable(node.result, result, scope="component")
|
|
88
|
+
|
|
89
|
+
return result
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log Executor - Execute q:log statements
|
|
3
|
+
|
|
4
|
+
Handles structured logging with levels and context.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
import json
|
|
9
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
10
|
+
from quantum.core.features.logging.src import LogNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LogExecutor(BaseExecutor):
|
|
14
|
+
"""
|
|
15
|
+
Executor for q:log statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Multiple log levels (debug, info, warn, error)
|
|
19
|
+
- Conditional logging (when attribute)
|
|
20
|
+
- Context data attachment
|
|
21
|
+
- Correlation IDs for tracing
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def handles(self) -> List[Type]:
|
|
26
|
+
return [LogNode]
|
|
27
|
+
|
|
28
|
+
def execute(self, node: LogNode, exec_context) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Execute logging statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
node: LogNode with logging configuration
|
|
34
|
+
exec_context: Execution context
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
None (logging is side-effect only)
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
context = exec_context.get_all_variables()
|
|
41
|
+
|
|
42
|
+
# Check conditional execution
|
|
43
|
+
if node.when:
|
|
44
|
+
condition_result = self.apply_databinding(node.when, context)
|
|
45
|
+
if not self.services.logging.should_log(condition_result):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Resolve message
|
|
49
|
+
message = self.apply_databinding(node.message, context)
|
|
50
|
+
|
|
51
|
+
# Resolve context data
|
|
52
|
+
context_data = None
|
|
53
|
+
if node.context:
|
|
54
|
+
context_expr = self.apply_databinding(node.context, context)
|
|
55
|
+
if isinstance(context_expr, dict):
|
|
56
|
+
context_data = context_expr
|
|
57
|
+
elif isinstance(context_expr, str):
|
|
58
|
+
try:
|
|
59
|
+
context_data = json.loads(context_expr)
|
|
60
|
+
except:
|
|
61
|
+
context_data = {'value': context_expr}
|
|
62
|
+
|
|
63
|
+
# Resolve correlation_id
|
|
64
|
+
correlation_id = None
|
|
65
|
+
if node.correlation_id:
|
|
66
|
+
correlation_id = self.apply_databinding(node.correlation_id, context)
|
|
67
|
+
|
|
68
|
+
# Execute logging
|
|
69
|
+
self.services.logging.log(
|
|
70
|
+
level=node.level,
|
|
71
|
+
message=str(message),
|
|
72
|
+
context=context_data,
|
|
73
|
+
correlation_id=correlation_id
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise ExecutorError(f"Logging error: {e}")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mail Executor - Execute q:mail statements
|
|
3
|
+
|
|
4
|
+
Handles email sending with HTML/text support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
9
|
+
from quantum.core.ast_nodes import MailNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MailExecutor(BaseExecutor):
|
|
13
|
+
"""
|
|
14
|
+
Executor for q:mail statements.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- HTML and plain text emails
|
|
18
|
+
- CC, BCC, Reply-To
|
|
19
|
+
- Attachments
|
|
20
|
+
- Databinding in all fields
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def handles(self) -> List[Type]:
|
|
25
|
+
return [MailNode]
|
|
26
|
+
|
|
27
|
+
def execute(self, node: MailNode, exec_context) -> Any:
|
|
28
|
+
"""
|
|
29
|
+
Execute email sending.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
node: MailNode with email configuration
|
|
33
|
+
exec_context: Execution context
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Email send result dict
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
context = exec_context.get_all_variables()
|
|
40
|
+
|
|
41
|
+
# Resolve all email properties
|
|
42
|
+
to = self.apply_databinding(node.to, context)
|
|
43
|
+
subject = self.apply_databinding(node.subject, context)
|
|
44
|
+
body = self.apply_databinding(node.body, context)
|
|
45
|
+
|
|
46
|
+
from_addr = None
|
|
47
|
+
if node.from_addr:
|
|
48
|
+
from_addr = self.apply_databinding(node.from_addr, context)
|
|
49
|
+
|
|
50
|
+
cc = None
|
|
51
|
+
if node.cc:
|
|
52
|
+
cc = self.apply_databinding(node.cc, context)
|
|
53
|
+
|
|
54
|
+
bcc = None
|
|
55
|
+
if node.bcc:
|
|
56
|
+
bcc = self.apply_databinding(node.bcc, context)
|
|
57
|
+
|
|
58
|
+
reply_to = None
|
|
59
|
+
if node.reply_to:
|
|
60
|
+
reply_to = self.apply_databinding(node.reply_to, context)
|
|
61
|
+
|
|
62
|
+
# Send email
|
|
63
|
+
result = self.services.email.send_email(
|
|
64
|
+
to=to,
|
|
65
|
+
subject=subject,
|
|
66
|
+
body=body,
|
|
67
|
+
from_addr=from_addr,
|
|
68
|
+
cc=cc,
|
|
69
|
+
bcc=bcc,
|
|
70
|
+
reply_to=reply_to,
|
|
71
|
+
email_type=node.type,
|
|
72
|
+
attachments=node.attachments
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Store result
|
|
76
|
+
exec_context.set_variable('_mail_result', result, scope="component")
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise ExecutorError(f"Mail execution error: {e}")
|