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,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Executor - Abstract base class for all node executors
|
|
3
|
+
|
|
4
|
+
All executors inherit from BaseExecutor and implement the execute() method.
|
|
5
|
+
The ExecutorRegistry uses the 'handles' property to dispatch nodes to the correct executor.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any, List, Type, Dict, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from quantum.runtime.execution_context import ExecutionContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExecutorError(Exception):
|
|
16
|
+
"""Base exception for executor errors"""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseExecutor(ABC):
|
|
21
|
+
"""
|
|
22
|
+
Abstract base class for all node executors.
|
|
23
|
+
|
|
24
|
+
Each executor handles one or more AST node types and implements
|
|
25
|
+
the execute() method to process them.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
class LoopExecutor(BaseExecutor):
|
|
29
|
+
@property
|
|
30
|
+
def handles(self) -> List[Type]:
|
|
31
|
+
return [LoopNode]
|
|
32
|
+
|
|
33
|
+
def execute(self, node: LoopNode, context: ExecutionContext) -> Any:
|
|
34
|
+
# Execute loop logic
|
|
35
|
+
pass
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, runtime: 'ComponentRuntime'):
|
|
39
|
+
"""
|
|
40
|
+
Initialize executor with reference to runtime.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
runtime: The ComponentRuntime instance (provides services and context)
|
|
44
|
+
"""
|
|
45
|
+
self._runtime = runtime
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def runtime(self) -> 'ComponentRuntime':
|
|
49
|
+
"""Access to the component runtime"""
|
|
50
|
+
return self._runtime
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def context(self) -> 'ExecutionContext':
|
|
54
|
+
"""Shortcut to execution context"""
|
|
55
|
+
return self._runtime.execution_context
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def services(self) -> 'ServiceContainer':
|
|
59
|
+
"""Shortcut to service container"""
|
|
60
|
+
return self._runtime.services
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def handles(self) -> List[Type]:
|
|
65
|
+
"""
|
|
66
|
+
List of AST node types this executor handles.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of node classes (e.g., [LoopNode, ForNode])
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def execute(self, node: Any, exec_context: 'ExecutionContext') -> Any:
|
|
75
|
+
"""
|
|
76
|
+
Execute the node and return result.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
node: The AST node to execute
|
|
80
|
+
exec_context: The execution context with variables and scopes
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The result of execution (type depends on node type)
|
|
84
|
+
"""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# ==========================================================================
|
|
88
|
+
# Helper methods - Common utilities available to all executors
|
|
89
|
+
# ==========================================================================
|
|
90
|
+
|
|
91
|
+
def apply_databinding(self, value: str, context: Dict[str, Any] = None) -> Any:
|
|
92
|
+
"""
|
|
93
|
+
Apply databinding to resolve {variable} expressions.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
value: String potentially containing {expressions}
|
|
97
|
+
context: Variable context (uses exec_context if not provided)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Resolved value
|
|
101
|
+
"""
|
|
102
|
+
if context is None:
|
|
103
|
+
context = self.get_all_variables()
|
|
104
|
+
return self._runtime._apply_databinding(value, context)
|
|
105
|
+
|
|
106
|
+
def evaluate_condition(self, condition: str, context: Dict[str, Any] = None) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Evaluate a condition string.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
condition: Condition expression (e.g., "{x} > 5")
|
|
112
|
+
context: Variable context
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Boolean result
|
|
116
|
+
"""
|
|
117
|
+
if context is None:
|
|
118
|
+
context = self.get_all_variables()
|
|
119
|
+
return self._runtime._evaluate_condition(condition, context)
|
|
120
|
+
|
|
121
|
+
def get_all_variables(self) -> Dict[str, Any]:
|
|
122
|
+
"""
|
|
123
|
+
Get all variables from execution context.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dict of all variables across all scopes
|
|
127
|
+
"""
|
|
128
|
+
return self.context.get_all_variables()
|
|
129
|
+
|
|
130
|
+
def set_variable(self, name: str, value: Any, scope: str = "local"):
|
|
131
|
+
"""
|
|
132
|
+
Set a variable in the execution context.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
name: Variable name
|
|
136
|
+
value: Variable value
|
|
137
|
+
scope: Scope (local, component, session, application)
|
|
138
|
+
"""
|
|
139
|
+
self.context.set_variable(name, value, scope=scope)
|
|
140
|
+
|
|
141
|
+
def get_variable(self, name: str, default: Any = None) -> Any:
|
|
142
|
+
"""
|
|
143
|
+
Get a variable from the execution context.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
name: Variable name
|
|
147
|
+
default: Default value if not found
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Variable value or default
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
return self.context.get_variable(name)
|
|
154
|
+
except Exception:
|
|
155
|
+
return default
|
|
156
|
+
|
|
157
|
+
def execute_child(self, node: Any, exec_context: 'ExecutionContext' = None) -> Any:
|
|
158
|
+
"""
|
|
159
|
+
Execute a child node using the registry.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
node: Child AST node
|
|
163
|
+
exec_context: Execution context (uses self.context if not provided)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Result of child execution
|
|
167
|
+
"""
|
|
168
|
+
if exec_context is None:
|
|
169
|
+
exec_context = self.context
|
|
170
|
+
return self._runtime.executor_registry.execute(node, exec_context)
|
|
171
|
+
|
|
172
|
+
def execute_children(self, nodes: List[Any], exec_context: 'ExecutionContext' = None) -> List[Any]:
|
|
173
|
+
"""
|
|
174
|
+
Execute multiple child nodes.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
nodes: List of AST nodes
|
|
178
|
+
exec_context: Execution context
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
List of results
|
|
182
|
+
"""
|
|
183
|
+
results = []
|
|
184
|
+
for node in nodes:
|
|
185
|
+
result = self.execute_child(node, exec_context)
|
|
186
|
+
if result is not None:
|
|
187
|
+
results.append(result)
|
|
188
|
+
return results
|
|
189
|
+
|
|
190
|
+
def require_python_scripting(self, tag: str):
|
|
191
|
+
"""Refuse to run in-process Python when policy disables it.
|
|
192
|
+
|
|
193
|
+
q:python/q:pyclass/q:pyimport execute arbitrary Python in the server
|
|
194
|
+
process — no sandbox, full access to the filesystem, network and os.
|
|
195
|
+
That is a deliberate escape hatch for author-written code, but it
|
|
196
|
+
must be switchable off before anything the author did not write
|
|
197
|
+
(LLM-generated templates, user-submitted components) can reach it.
|
|
198
|
+
"""
|
|
199
|
+
if not self.services.python_scripting_enabled:
|
|
200
|
+
# A mensagem tem de dizer COMO ligar. O default virou desligado,
|
|
201
|
+
# entao esta e a primeira coisa que quem usa q:python encontra —
|
|
202
|
+
# e um erro que so diz "desabilitado" manda a pessoa procurar no
|
|
203
|
+
# codigo qual e a chave.
|
|
204
|
+
raise ExecutorError(
|
|
205
|
+
f"{tag} is off: security.python_scripting is not enabled. It "
|
|
206
|
+
f"executes arbitrary Python in the server process with no "
|
|
207
|
+
f"sandbox, so it is off by default — turn it on only for "
|
|
208
|
+
f"code you wrote yourself.\n"
|
|
209
|
+
f" In quantum.config.yaml:\n"
|
|
210
|
+
f" security:\n"
|
|
211
|
+
f" python_scripting: true\n"
|
|
212
|
+
f" Or in the environment: QUANTUM_PYTHON_SCRIPTING=1"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def run_body_in_child_context(self, body: List[Any], variables: Dict[str, Any] = None) -> Any:
|
|
216
|
+
"""
|
|
217
|
+
Execute a list of AST statements in a fresh child context.
|
|
218
|
+
|
|
219
|
+
Used by nodes whose body runs detached from the surrounding flow
|
|
220
|
+
(q:schedule callbacks, q:thread callbacks, q:agent tool bodies).
|
|
221
|
+
Render-only nodes are skipped — they belong to HTMLRenderer, not the
|
|
222
|
+
executor registry. Returns the first q:return value found, if any.
|
|
223
|
+
"""
|
|
224
|
+
from quantum.core.ast_nodes import (
|
|
225
|
+
QuantumReturn, HTMLNode, TextNode, DocTypeNode, CommentNode,
|
|
226
|
+
)
|
|
227
|
+
render_only = (HTMLNode, TextNode, DocTypeNode, CommentNode)
|
|
228
|
+
|
|
229
|
+
child_context = self.context.create_child_context()
|
|
230
|
+
for key, value in (variables or {}).items():
|
|
231
|
+
child_context.set_variable(key, value, scope="local")
|
|
232
|
+
|
|
233
|
+
for statement in body or []:
|
|
234
|
+
if isinstance(statement, QuantumReturn):
|
|
235
|
+
return self.resolve_value(
|
|
236
|
+
statement.value, child_context.get_all_variables()
|
|
237
|
+
)
|
|
238
|
+
if isinstance(statement, render_only):
|
|
239
|
+
continue
|
|
240
|
+
self._runtime.executor_registry.execute(statement, child_context)
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
def resolve_value(self, value: str, context: Dict[str, Any] = None) -> Any:
|
|
245
|
+
"""
|
|
246
|
+
Resolve a value string to its actual value.
|
|
247
|
+
|
|
248
|
+
Handles:
|
|
249
|
+
- Databinding: {variable}
|
|
250
|
+
- Literals: "string", 123, true/false
|
|
251
|
+
- JSON: {"key": "value"}
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
value: Value string to resolve
|
|
255
|
+
context: Variable context
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Resolved value
|
|
259
|
+
"""
|
|
260
|
+
if context is None:
|
|
261
|
+
context = self.get_all_variables()
|
|
262
|
+
return self._runtime._process_return_value(value, context)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Control Flow Executors
|
|
3
|
+
|
|
4
|
+
Executors for control flow statements: if, loop, set.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .if_executor import IfExecutor
|
|
8
|
+
from .loop_executor import LoopExecutor
|
|
9
|
+
from .set_executor import SetExecutor
|
|
10
|
+
|
|
11
|
+
__all__ = ['IfExecutor', 'LoopExecutor', 'SetExecutor']
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
If Executor - Execute q:if statements
|
|
3
|
+
|
|
4
|
+
Handles conditional execution with elseif and else blocks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
9
|
+
from quantum.runtime.executors.control_flow.loop_executor import produced_return
|
|
10
|
+
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
11
|
+
from quantum.core.ast_nodes import (
|
|
12
|
+
QuantumReturn,
|
|
13
|
+
HTMLNode, TextNode, DocTypeNode, CommentNode,
|
|
14
|
+
ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Same render-only skip list as component.py's top-level _execute_statement
|
|
18
|
+
# and loop_executor.py — text/HTML inside a q:if body is handled by
|
|
19
|
+
# HTMLRenderer, not by the executor registry.
|
|
20
|
+
_RENDER_ONLY_NODE_TYPES = (
|
|
21
|
+
HTMLNode, TextNode, DocTypeNode, CommentNode,
|
|
22
|
+
ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IfExecutor(BaseExecutor):
|
|
27
|
+
"""
|
|
28
|
+
Executor for q:if statements.
|
|
29
|
+
|
|
30
|
+
Supports:
|
|
31
|
+
- Simple if conditions
|
|
32
|
+
- Multiple elseif blocks
|
|
33
|
+
- Optional else block
|
|
34
|
+
- Nested conditionals
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def handles(self) -> List[Type]:
|
|
39
|
+
return [IfNode]
|
|
40
|
+
|
|
41
|
+
def execute(self, node: IfNode, exec_context) -> Any:
|
|
42
|
+
"""
|
|
43
|
+
Execute q:if statement with elseif and else.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
node: IfNode to execute
|
|
47
|
+
exec_context: Execution context
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Result of executed branch, or None
|
|
51
|
+
"""
|
|
52
|
+
context = self.get_all_variables()
|
|
53
|
+
|
|
54
|
+
# Evaluate main if condition
|
|
55
|
+
if self.evaluate_condition(node.condition, context):
|
|
56
|
+
return self._execute_body(node.if_body, context)
|
|
57
|
+
|
|
58
|
+
# Check elseif conditions
|
|
59
|
+
for elseif_block in node.elseif_blocks:
|
|
60
|
+
if self.evaluate_condition(elseif_block["condition"], context):
|
|
61
|
+
return self._execute_body(elseif_block["body"], context)
|
|
62
|
+
|
|
63
|
+
# Execute else block if present
|
|
64
|
+
if node.else_body:
|
|
65
|
+
return self._execute_body(node.else_body, context)
|
|
66
|
+
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def _execute_body(self, statements: List, context: Dict[str, Any]) -> Any:
|
|
70
|
+
"""
|
|
71
|
+
Execute a list of statements in a branch body.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
statements: List of AST nodes to execute
|
|
75
|
+
context: Variable context
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Return value if any statement returns, None otherwise
|
|
79
|
+
"""
|
|
80
|
+
for statement in statements:
|
|
81
|
+
if isinstance(statement, QuantumReturn):
|
|
82
|
+
return self.resolve_value(statement.value, context)
|
|
83
|
+
elif isinstance(statement, _RENDER_ONLY_NODE_TYPES):
|
|
84
|
+
continue
|
|
85
|
+
else:
|
|
86
|
+
# Use registry to execute child statements
|
|
87
|
+
result = self.execute_child(statement)
|
|
88
|
+
# A nested q:if that returned, or a q:loop that collected
|
|
89
|
+
# returns, ends this branch with that value.
|
|
90
|
+
if produced_return(statement, result):
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
return None
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loop Executor - Execute q:loop statements
|
|
3
|
+
|
|
4
|
+
Handles all loop types: range, array, list, query.
|
|
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.loops.src.ast_node import LoopNode
|
|
11
|
+
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
12
|
+
from quantum.core.features.state_management.src.ast_node import SetNode
|
|
13
|
+
from quantum.core.features.logging.src import LogNode
|
|
14
|
+
from quantum.core.features.dump.src import DumpNode
|
|
15
|
+
from quantum.core.ast_nodes import (
|
|
16
|
+
QuantumReturn,
|
|
17
|
+
HTMLNode, TextNode, DocTypeNode, CommentNode,
|
|
18
|
+
ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Render-only / special-purpose nodes that never go through the executor
|
|
22
|
+
# registry (see component.py's top-level _execute_statement, which skips the
|
|
23
|
+
# same types). A loop body with plain text content used to raise
|
|
24
|
+
# "No executor registered for TextNode" — reachable only once the loop-type
|
|
25
|
+
# inference bug in loop_parser.py was fixed, since before that such bodies
|
|
26
|
+
# never parsed as array/list/query loops in the first place.
|
|
27
|
+
_RENDER_ONLY_NODE_TYPES = (
|
|
28
|
+
HTMLNode, TextNode, DocTypeNode, CommentNode,
|
|
29
|
+
ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LoopReturns(list):
|
|
34
|
+
"""Os valores dos q:return executados dentro de um q:loop, em ordem.
|
|
35
|
+
|
|
36
|
+
Um tipo proprio, e nao `list`, porque o resultado de um loop antes
|
|
37
|
+
misturava qualquer coisa nao-None que o corpo produzisse — linhas de um
|
|
38
|
+
q:query, por exemplo — e por isso quem chamava nao tinha como tratar o
|
|
39
|
+
loop como return e simplesmente jogava o resultado fora. Era assim que
|
|
40
|
+
`<q:loop ...><q:return value="{x}"/></q:loop>` num componente devolvia
|
|
41
|
+
None, contra o que getting-started.md documenta.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def produced_return(statement, result) -> bool:
|
|
46
|
+
"""O statement executado deve encerrar o corpo que o contem?
|
|
47
|
+
|
|
48
|
+
Vale para um q:if cujo ramo executou um q:return, e para um q:loop que
|
|
49
|
+
coletou pelo menos um. Um loop que nao coletou nada — so q:set, ou um
|
|
50
|
+
filtro que nao casou — deixa a execucao seguir, como um q:if falso.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(result, LoopReturns):
|
|
53
|
+
return len(result) > 0
|
|
54
|
+
return result is not None and isinstance(statement, IfNode)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def as_return_value(result):
|
|
58
|
+
"""Entrega um LoopReturns como lista comum para quem esta fora do runtime."""
|
|
59
|
+
return list(result) if isinstance(result, LoopReturns) else result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LoopExecutor(BaseExecutor):
|
|
63
|
+
"""
|
|
64
|
+
Executor for q:loop statements.
|
|
65
|
+
|
|
66
|
+
Supports:
|
|
67
|
+
- range: numeric iteration (from/to/step)
|
|
68
|
+
- array: iterate over array items
|
|
69
|
+
- list: iterate over delimited string
|
|
70
|
+
- query: iterate over query result rows
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def handles(self) -> List[Type]:
|
|
75
|
+
return [LoopNode]
|
|
76
|
+
|
|
77
|
+
def execute(self, node: LoopNode, exec_context) -> Any:
|
|
78
|
+
"""
|
|
79
|
+
Execute q:loop statement based on type.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
node: LoopNode to execute
|
|
83
|
+
exec_context: Execution context
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of results from loop iterations
|
|
87
|
+
"""
|
|
88
|
+
if node.loop_type == 'range':
|
|
89
|
+
return self._execute_range(node, exec_context)
|
|
90
|
+
elif node.loop_type == 'array':
|
|
91
|
+
return self._execute_array(node, exec_context)
|
|
92
|
+
elif node.loop_type == 'list':
|
|
93
|
+
return self._execute_list(node, exec_context)
|
|
94
|
+
elif node.loop_type == 'query':
|
|
95
|
+
return self._execute_query(node, exec_context)
|
|
96
|
+
else:
|
|
97
|
+
raise ExecutorError(f"Unsupported loop type: {node.loop_type}")
|
|
98
|
+
|
|
99
|
+
def _execute_range(self, node: LoopNode, exec_context) -> List:
|
|
100
|
+
"""Execute range loop (from/to/step)"""
|
|
101
|
+
results = LoopReturns()
|
|
102
|
+
context = self.get_all_variables()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
start = int(self._evaluate_simple_expr(node.from_value, context))
|
|
106
|
+
end = int(self._evaluate_simple_expr(node.to_value, context))
|
|
107
|
+
step = node.step_value
|
|
108
|
+
|
|
109
|
+
for i in range(start, end + 1, step):
|
|
110
|
+
# Update loop variable
|
|
111
|
+
loop_context = context.copy()
|
|
112
|
+
loop_context[node.var_name] = i
|
|
113
|
+
exec_context.set_variable(node.var_name, i, scope="local")
|
|
114
|
+
|
|
115
|
+
# Execute body
|
|
116
|
+
for statement in node.body:
|
|
117
|
+
result = self._execute_body_statement(statement, loop_context, exec_context)
|
|
118
|
+
self._collect(results, statement, result)
|
|
119
|
+
|
|
120
|
+
return results
|
|
121
|
+
|
|
122
|
+
except (ValueError, TypeError) as e:
|
|
123
|
+
raise ExecutorError(f"Range loop error: {e}")
|
|
124
|
+
|
|
125
|
+
def _execute_array(self, node: LoopNode, exec_context) -> List:
|
|
126
|
+
"""Execute array loop"""
|
|
127
|
+
results = LoopReturns()
|
|
128
|
+
context = self.get_all_variables()
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
array_data = self._parse_array_items(node.items, context)
|
|
132
|
+
|
|
133
|
+
for index, item in enumerate(array_data):
|
|
134
|
+
exec_context.set_variable(node.var_name, item, scope="local")
|
|
135
|
+
|
|
136
|
+
if node.index_name:
|
|
137
|
+
exec_context.set_variable(node.index_name, index, scope="local")
|
|
138
|
+
|
|
139
|
+
loop_context = exec_context.get_all_variables()
|
|
140
|
+
|
|
141
|
+
for statement in node.body:
|
|
142
|
+
result = self._execute_body_statement(statement, loop_context, exec_context)
|
|
143
|
+
self._collect(results, statement, result)
|
|
144
|
+
loop_context = exec_context.get_all_variables()
|
|
145
|
+
|
|
146
|
+
return results
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
raise ExecutorError(f"Array loop error: {e}")
|
|
150
|
+
|
|
151
|
+
def _execute_list(self, node: LoopNode, exec_context) -> List:
|
|
152
|
+
"""Execute list loop (delimited string)"""
|
|
153
|
+
results = LoopReturns()
|
|
154
|
+
context = self.get_all_variables()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
list_data = self._parse_list_items(node.items, node.delimiter, context)
|
|
158
|
+
|
|
159
|
+
for index, item in enumerate(list_data):
|
|
160
|
+
loop_context = context.copy()
|
|
161
|
+
loop_context[node.var_name] = item.strip()
|
|
162
|
+
exec_context.set_variable(node.var_name, item.strip(), scope="local")
|
|
163
|
+
|
|
164
|
+
if node.index_name:
|
|
165
|
+
loop_context[node.index_name] = index
|
|
166
|
+
exec_context.set_variable(node.index_name, index, scope="local")
|
|
167
|
+
|
|
168
|
+
for statement in node.body:
|
|
169
|
+
result = self._execute_body_statement(statement, loop_context, exec_context)
|
|
170
|
+
self._collect(results, statement, result)
|
|
171
|
+
|
|
172
|
+
return results
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
raise ExecutorError(f"List loop error: {e}")
|
|
176
|
+
|
|
177
|
+
def _execute_query(self, node: LoopNode, exec_context) -> List:
|
|
178
|
+
"""Execute query loop - iterate over query result rows"""
|
|
179
|
+
results = LoopReturns()
|
|
180
|
+
context = self.get_all_variables()
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
query_name = node.query_name if hasattr(node, 'query_name') else node.var_name
|
|
184
|
+
query_data = context.get(query_name)
|
|
185
|
+
|
|
186
|
+
if query_data is None:
|
|
187
|
+
query_data = exec_context.get_variable(query_name)
|
|
188
|
+
|
|
189
|
+
if not query_data:
|
|
190
|
+
raise ExecutorError(f"Query '{query_name}' not found in context")
|
|
191
|
+
|
|
192
|
+
if not isinstance(query_data, list):
|
|
193
|
+
raise ExecutorError(f"Query '{query_name}' is not iterable")
|
|
194
|
+
|
|
195
|
+
for index, row in enumerate(query_data):
|
|
196
|
+
loop_context = context.copy()
|
|
197
|
+
|
|
198
|
+
# Make row fields accessible
|
|
199
|
+
if isinstance(row, dict):
|
|
200
|
+
for field_name, field_value in row.items():
|
|
201
|
+
dotted_key = f"{query_name}.{field_name}"
|
|
202
|
+
loop_context[dotted_key] = field_value
|
|
203
|
+
exec_context.set_variable(dotted_key, field_value, scope="local")
|
|
204
|
+
loop_context[field_name] = field_value
|
|
205
|
+
exec_context.set_variable(field_name, field_value, scope="local")
|
|
206
|
+
|
|
207
|
+
loop_context['currentRow'] = row
|
|
208
|
+
exec_context.set_variable('currentRow', row, scope="local")
|
|
209
|
+
|
|
210
|
+
if node.index_name:
|
|
211
|
+
loop_context[node.index_name] = index
|
|
212
|
+
exec_context.set_variable(node.index_name, index, scope="local")
|
|
213
|
+
|
|
214
|
+
for statement in node.body:
|
|
215
|
+
result = self._execute_body_statement(statement, loop_context, exec_context)
|
|
216
|
+
self._collect(results, statement, result)
|
|
217
|
+
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
raise ExecutorError(f"Query loop error: {e}")
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def _collect(results: LoopReturns, statement, result) -> None:
|
|
225
|
+
"""Guarda em `results` o que o statement retornou, e so isso.
|
|
226
|
+
|
|
227
|
+
Cada q:return executado vira um item — inclusive os de um loop
|
|
228
|
+
aninhado, que entram soltos, na ordem (docs/guide/loops.md, "Nested
|
|
229
|
+
Loops"). Um q:if cujo ramo retornou vira um item. O resto (linhas de
|
|
230
|
+
q:query, valores de q:set etc.) nao e return e nao entra.
|
|
231
|
+
|
|
232
|
+
So LoopReturns e achatado: `<q:return value="{lista}"/>` continua
|
|
233
|
+
sendo UM item, mesmo sendo uma lista.
|
|
234
|
+
"""
|
|
235
|
+
if isinstance(statement, QuantumReturn):
|
|
236
|
+
if result is not None:
|
|
237
|
+
results.append(result)
|
|
238
|
+
elif isinstance(result, LoopReturns):
|
|
239
|
+
results.extend(result)
|
|
240
|
+
elif produced_return(statement, result):
|
|
241
|
+
results.append(result)
|
|
242
|
+
|
|
243
|
+
def _execute_body_statement(self, statement, context: Dict[str, Any], exec_context) -> Any:
|
|
244
|
+
"""Execute a statement inside a loop body"""
|
|
245
|
+
if isinstance(statement, QuantumReturn):
|
|
246
|
+
return self.resolve_value(statement.value, context)
|
|
247
|
+
elif isinstance(statement, _RENDER_ONLY_NODE_TYPES):
|
|
248
|
+
# Text/HTML content is handled by HTMLRenderer._render_loop(),
|
|
249
|
+
# which independently re-iterates and renders the body — it is
|
|
250
|
+
# not meant to be executed here.
|
|
251
|
+
return None
|
|
252
|
+
else:
|
|
253
|
+
# Use registry for all other statements
|
|
254
|
+
return self.execute_child(statement, exec_context)
|
|
255
|
+
|
|
256
|
+
def _evaluate_simple_expr(self, expr: str, context: Dict[str, Any]) -> Any:
|
|
257
|
+
"""Evaluate simple numeric expression"""
|
|
258
|
+
if not expr:
|
|
259
|
+
return 0
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
return int(expr)
|
|
263
|
+
except ValueError:
|
|
264
|
+
try:
|
|
265
|
+
return float(expr)
|
|
266
|
+
except ValueError:
|
|
267
|
+
return context.get(expr, expr)
|
|
268
|
+
|
|
269
|
+
def _parse_array_items(self, items_expr: str, context: Dict[str, Any]) -> list:
|
|
270
|
+
"""Parse array items from expression"""
|
|
271
|
+
if not items_expr:
|
|
272
|
+
return []
|
|
273
|
+
|
|
274
|
+
# Apply databinding
|
|
275
|
+
if '{' in items_expr and '}' in items_expr:
|
|
276
|
+
resolved = self.apply_databinding(items_expr, context)
|
|
277
|
+
if isinstance(resolved, list):
|
|
278
|
+
return resolved
|
|
279
|
+
items_expr = str(resolved) if not isinstance(resolved, str) else resolved
|
|
280
|
+
|
|
281
|
+
# Handle JSON array notation
|
|
282
|
+
if items_expr.startswith('[') and items_expr.endswith(']'):
|
|
283
|
+
try:
|
|
284
|
+
return json.loads(items_expr)
|
|
285
|
+
except json.JSONDecodeError:
|
|
286
|
+
items_str = items_expr[1:-1]
|
|
287
|
+
return [item.strip().strip('"\'') for item in items_str.split(',') if item.strip()]
|
|
288
|
+
|
|
289
|
+
# Variable reference
|
|
290
|
+
if items_expr in context:
|
|
291
|
+
return context[items_expr]
|
|
292
|
+
|
|
293
|
+
return [items_expr]
|
|
294
|
+
|
|
295
|
+
def _parse_list_items(self, items_expr: str, delimiter: str, context: Dict[str, Any]) -> list:
|
|
296
|
+
"""Parse list items from delimited string"""
|
|
297
|
+
if not items_expr:
|
|
298
|
+
return []
|
|
299
|
+
|
|
300
|
+
if items_expr in context:
|
|
301
|
+
items_value = context[items_expr]
|
|
302
|
+
if isinstance(items_value, list):
|
|
303
|
+
return items_value
|
|
304
|
+
elif isinstance(items_value, str):
|
|
305
|
+
return items_value.split(delimiter)
|
|
306
|
+
|
|
307
|
+
return items_expr.split(delimiter)
|