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,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Set Executor - Execute q:set statements
|
|
3
|
+
|
|
4
|
+
Handles variable assignment and all operations:
|
|
5
|
+
- assign, increment, decrement
|
|
6
|
+
- add, multiply (arithmetic)
|
|
7
|
+
- append, prepend, remove, clear, sort, etc. (array)
|
|
8
|
+
- merge, setProperty, deleteProperty, clone (object)
|
|
9
|
+
- uppercase, lowercase, trim, format (string)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, List, Dict, Type
|
|
13
|
+
import json
|
|
14
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
15
|
+
from quantum.core.features.state_management.src.ast_node import SetNode
|
|
16
|
+
from quantum.core.expressions import coerce_number
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SetExecutor(BaseExecutor):
|
|
20
|
+
"""
|
|
21
|
+
Executor for q:set statements.
|
|
22
|
+
|
|
23
|
+
Supports:
|
|
24
|
+
- Basic assignment with type conversion
|
|
25
|
+
- Increment/decrement operations
|
|
26
|
+
- Array operations (append, remove, sort, etc.)
|
|
27
|
+
- Object operations (merge, setProperty, etc.)
|
|
28
|
+
- String transformations (uppercase, trim, etc.)
|
|
29
|
+
- Validation rules
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def handles(self) -> List[Type]:
|
|
34
|
+
return [SetNode]
|
|
35
|
+
|
|
36
|
+
def execute(self, node: SetNode, exec_context) -> Any:
|
|
37
|
+
"""
|
|
38
|
+
Execute q:set statement.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
node: SetNode to execute
|
|
42
|
+
exec_context: Execution context
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
None (set doesn't return a value)
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
context = exec_context.get_all_variables()
|
|
49
|
+
|
|
50
|
+
# Handle different operations
|
|
51
|
+
if node.operation == "assign":
|
|
52
|
+
value = self._execute_assign(node, context)
|
|
53
|
+
elif node.operation == "increment":
|
|
54
|
+
value = self._execute_increment(node, exec_context, node.step)
|
|
55
|
+
elif node.operation == "decrement":
|
|
56
|
+
value = self._execute_decrement(node, exec_context, node.step)
|
|
57
|
+
elif node.operation in ["add", "multiply"]:
|
|
58
|
+
value = self._execute_arithmetic(node, exec_context, context)
|
|
59
|
+
elif node.operation in ["append", "prepend", "remove", "removeAt", "clear", "sort", "reverse", "unique"]:
|
|
60
|
+
value = self._execute_array_operation(node, exec_context)
|
|
61
|
+
elif node.operation in ["merge", "setProperty", "deleteProperty", "clone"]:
|
|
62
|
+
value = self._execute_object_operation(node, exec_context, context)
|
|
63
|
+
elif node.operation in ["uppercase", "lowercase", "trim", "format"]:
|
|
64
|
+
value = self._execute_transformation(node, exec_context)
|
|
65
|
+
else:
|
|
66
|
+
raise ExecutorError(f"Unsupported operation: {node.operation}")
|
|
67
|
+
|
|
68
|
+
# Validate the value
|
|
69
|
+
self._validate_value(node, value)
|
|
70
|
+
|
|
71
|
+
# Set the variable in the appropriate scope
|
|
72
|
+
self._set_variable(node, value, exec_context)
|
|
73
|
+
|
|
74
|
+
return None # q:set doesn't return a value
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise ExecutorError(f"Set execution error for '{node.name}': {e}")
|
|
78
|
+
|
|
79
|
+
def _execute_assign(self, node: SetNode, context: Dict[str, Any]) -> Any:
|
|
80
|
+
"""Execute assign operation"""
|
|
81
|
+
value_expr = node.value if node.value is not None else node.default
|
|
82
|
+
|
|
83
|
+
if value_expr is None:
|
|
84
|
+
if not node.nullable:
|
|
85
|
+
raise ExecutorError(f"Variable '{node.name}' cannot be null")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
# Process databinding
|
|
89
|
+
processed_value = self.apply_databinding(value_expr, context)
|
|
90
|
+
|
|
91
|
+
# Convert to appropriate type
|
|
92
|
+
return self._convert_to_type(processed_value, node.type)
|
|
93
|
+
|
|
94
|
+
def _numeric_or_fail(self, value: Any, op: str, where: str) -> Any:
|
|
95
|
+
"""Coerce a numeric-looking value, or fail loudly.
|
|
96
|
+
|
|
97
|
+
A .q attribute is a string by nature — `<q:set name="estoque"
|
|
98
|
+
value="7" />` stores "7" unless the author remembered type="number".
|
|
99
|
+
Refusing to add to that made increment/decrement unusable in the
|
|
100
|
+
ordinary case, so it is coerced, the same way {a + b} coerces.
|
|
101
|
+
"""
|
|
102
|
+
coerced = coerce_number(value)
|
|
103
|
+
if isinstance(coerced, bool) or not isinstance(coerced, (int, float)):
|
|
104
|
+
raise ExecutorError(
|
|
105
|
+
f"Cannot {op} non-numeric value {value!r} ({where}). "
|
|
106
|
+
f"Set it with type=\"number\", or give q:set a numeric value."
|
|
107
|
+
)
|
|
108
|
+
return coerced
|
|
109
|
+
|
|
110
|
+
def _current_numeric(self, node: SetNode, exec_context, op: str):
|
|
111
|
+
"""(value, exists) for the variable being incremented/decremented.
|
|
112
|
+
|
|
113
|
+
The previous version wrapped BOTH the lookup and the type check in one
|
|
114
|
+
`try: ... except Exception: return step`, so the ExecutorError it
|
|
115
|
+
raised for a non-numeric value was caught by its own handler one line
|
|
116
|
+
later and turned into the step itself. A stock of "7" decremented by 5
|
|
117
|
+
became -5 — the detection was correct and then discarded. Missing and
|
|
118
|
+
non-numeric are separated here so only the first one falls back.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
current = exec_context.get_variable(node.name)
|
|
122
|
+
except Exception:
|
|
123
|
+
return None, False
|
|
124
|
+
if current is None or current == '':
|
|
125
|
+
return None, False
|
|
126
|
+
return self._numeric_or_fail(current, op, f"current value of {node.name!r}"), True
|
|
127
|
+
|
|
128
|
+
def _execute_increment(self, node: SetNode, exec_context, step: int) -> Any:
|
|
129
|
+
"""Execute increment operation"""
|
|
130
|
+
if node.value:
|
|
131
|
+
context = exec_context.get_all_variables()
|
|
132
|
+
base_value = self.apply_databinding(node.value, context)
|
|
133
|
+
return self._numeric_or_fail(base_value, 'increment', 'value=') + step
|
|
134
|
+
|
|
135
|
+
current, exists = self._current_numeric(node, exec_context, 'increment')
|
|
136
|
+
return (current + step) if exists else step
|
|
137
|
+
|
|
138
|
+
def _execute_decrement(self, node: SetNode, exec_context, step: int) -> Any:
|
|
139
|
+
"""Execute decrement operation"""
|
|
140
|
+
if node.value:
|
|
141
|
+
context = exec_context.get_all_variables()
|
|
142
|
+
base_value = self.apply_databinding(node.value, context)
|
|
143
|
+
return self._numeric_or_fail(base_value, 'decrement', 'value=') - step
|
|
144
|
+
|
|
145
|
+
current, exists = self._current_numeric(node, exec_context, 'decrement')
|
|
146
|
+
return (current - step) if exists else -step
|
|
147
|
+
|
|
148
|
+
def _execute_arithmetic(self, node: SetNode, exec_context, context: Dict[str, Any]) -> Any:
|
|
149
|
+
"""Execute arithmetic operations (add, multiply)"""
|
|
150
|
+
try:
|
|
151
|
+
current_value = exec_context.get_variable(node.name)
|
|
152
|
+
except Exception:
|
|
153
|
+
current_value = 0
|
|
154
|
+
if current_value is None or current_value == '':
|
|
155
|
+
current_value = 0
|
|
156
|
+
|
|
157
|
+
# Same coercion as increment/decrement: a .q attribute is a string by
|
|
158
|
+
# nature, so add/multiply on a variable set without type="number" used
|
|
159
|
+
# to fail even though the value was plainly numeric.
|
|
160
|
+
current_value = self._numeric_or_fail(
|
|
161
|
+
current_value, node.operation, f"current value of {node.name!r}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
operand_expr = node.value
|
|
165
|
+
if not operand_expr:
|
|
166
|
+
raise ExecutorError("Arithmetic operation requires a value")
|
|
167
|
+
|
|
168
|
+
fresh_context = exec_context.get_all_variables()
|
|
169
|
+
processed = self.apply_databinding(operand_expr, fresh_context)
|
|
170
|
+
operand = self._convert_to_type(processed, "number")
|
|
171
|
+
|
|
172
|
+
if node.operation == "add":
|
|
173
|
+
return current_value + operand
|
|
174
|
+
elif node.operation == "multiply":
|
|
175
|
+
return current_value * operand
|
|
176
|
+
|
|
177
|
+
return current_value
|
|
178
|
+
|
|
179
|
+
def _execute_array_operation(self, node: SetNode, exec_context) -> Any:
|
|
180
|
+
"""Execute array operations"""
|
|
181
|
+
try:
|
|
182
|
+
current_value = exec_context.get_variable(node.name)
|
|
183
|
+
except Exception:
|
|
184
|
+
current_value = []
|
|
185
|
+
|
|
186
|
+
if not isinstance(current_value, list):
|
|
187
|
+
raise ExecutorError(f"Cannot perform array operation on non-array: {type(current_value)}")
|
|
188
|
+
|
|
189
|
+
result = current_value.copy()
|
|
190
|
+
|
|
191
|
+
# Resolve databinding in value
|
|
192
|
+
resolved_value = node.value
|
|
193
|
+
if resolved_value and node.operation in ("append", "prepend", "remove"):
|
|
194
|
+
context = exec_context.get_all_variables()
|
|
195
|
+
resolved = self.apply_databinding(resolved_value, context)
|
|
196
|
+
if resolved is not None:
|
|
197
|
+
resolved_value = resolved
|
|
198
|
+
|
|
199
|
+
if node.operation == "append":
|
|
200
|
+
if resolved_value:
|
|
201
|
+
result.append(resolved_value)
|
|
202
|
+
elif node.operation == "prepend":
|
|
203
|
+
if resolved_value:
|
|
204
|
+
result.insert(0, resolved_value)
|
|
205
|
+
elif node.operation == "remove":
|
|
206
|
+
if resolved_value and resolved_value in result:
|
|
207
|
+
result.remove(resolved_value)
|
|
208
|
+
elif node.operation == "removeAt":
|
|
209
|
+
if node.index is not None:
|
|
210
|
+
idx = int(node.index)
|
|
211
|
+
if 0 <= idx < len(result):
|
|
212
|
+
result.pop(idx)
|
|
213
|
+
elif node.operation == "clear":
|
|
214
|
+
result = []
|
|
215
|
+
elif node.operation == "sort":
|
|
216
|
+
result.sort()
|
|
217
|
+
elif node.operation == "reverse":
|
|
218
|
+
result.reverse()
|
|
219
|
+
elif node.operation == "unique":
|
|
220
|
+
result = list(dict.fromkeys(result))
|
|
221
|
+
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
def _execute_object_operation(self, node: SetNode, exec_context, context: Dict[str, Any]) -> Any:
|
|
225
|
+
"""Execute object operations"""
|
|
226
|
+
try:
|
|
227
|
+
current_value = exec_context.get_variable(node.name)
|
|
228
|
+
except Exception:
|
|
229
|
+
current_value = {}
|
|
230
|
+
|
|
231
|
+
if not isinstance(current_value, dict):
|
|
232
|
+
raise ExecutorError(f"Cannot perform object operation on non-object: {type(current_value)}")
|
|
233
|
+
|
|
234
|
+
result = current_value.copy()
|
|
235
|
+
|
|
236
|
+
if node.operation == "merge":
|
|
237
|
+
if node.value:
|
|
238
|
+
try:
|
|
239
|
+
merge_data = json.loads(node.value)
|
|
240
|
+
result.update(merge_data)
|
|
241
|
+
except json.JSONDecodeError:
|
|
242
|
+
raise ExecutorError(f"Invalid JSON for merge: {node.value}")
|
|
243
|
+
elif node.operation == "setProperty":
|
|
244
|
+
if node.key and node.value:
|
|
245
|
+
result[node.key] = node.value
|
|
246
|
+
elif node.operation == "deleteProperty":
|
|
247
|
+
if node.key and node.key in result:
|
|
248
|
+
del result[node.key]
|
|
249
|
+
elif node.operation == "clone":
|
|
250
|
+
if node.source:
|
|
251
|
+
try:
|
|
252
|
+
source_obj = exec_context.get_variable(node.source)
|
|
253
|
+
if isinstance(source_obj, dict):
|
|
254
|
+
result = source_obj.copy()
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
def _execute_transformation(self, node: SetNode, exec_context) -> Any:
|
|
261
|
+
"""Execute string transformation operations"""
|
|
262
|
+
if node.value:
|
|
263
|
+
context = exec_context.get_all_variables()
|
|
264
|
+
processed_value = self.apply_databinding(node.value, context)
|
|
265
|
+
value_str = str(processed_value)
|
|
266
|
+
else:
|
|
267
|
+
try:
|
|
268
|
+
current_value = exec_context.get_variable(node.name)
|
|
269
|
+
value_str = str(current_value)
|
|
270
|
+
except Exception:
|
|
271
|
+
value_str = ""
|
|
272
|
+
|
|
273
|
+
if node.operation == "uppercase":
|
|
274
|
+
return value_str.upper()
|
|
275
|
+
elif node.operation == "lowercase":
|
|
276
|
+
return value_str.lower()
|
|
277
|
+
elif node.operation == "trim":
|
|
278
|
+
return value_str.strip()
|
|
279
|
+
elif node.operation == "format":
|
|
280
|
+
return value_str
|
|
281
|
+
|
|
282
|
+
return value_str
|
|
283
|
+
|
|
284
|
+
def _convert_to_type(self, value: Any, target_type: str) -> Any:
|
|
285
|
+
"""Convert value to target type"""
|
|
286
|
+
if value is None:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
if target_type == "string":
|
|
291
|
+
return str(value)
|
|
292
|
+
elif target_type in ("integer", "number"):
|
|
293
|
+
try:
|
|
294
|
+
return int(value)
|
|
295
|
+
except (ValueError, TypeError):
|
|
296
|
+
return float(value)
|
|
297
|
+
elif target_type == "decimal":
|
|
298
|
+
return float(value)
|
|
299
|
+
elif target_type == "boolean":
|
|
300
|
+
if isinstance(value, bool):
|
|
301
|
+
return value
|
|
302
|
+
if isinstance(value, str):
|
|
303
|
+
return value.lower() in ['true', '1', 'yes']
|
|
304
|
+
return bool(value)
|
|
305
|
+
elif target_type == "array":
|
|
306
|
+
if isinstance(value, list):
|
|
307
|
+
return value
|
|
308
|
+
if isinstance(value, str):
|
|
309
|
+
return json.loads(value)
|
|
310
|
+
return [value]
|
|
311
|
+
elif target_type == "object":
|
|
312
|
+
if isinstance(value, dict):
|
|
313
|
+
return value
|
|
314
|
+
if isinstance(value, str):
|
|
315
|
+
return json.loads(value)
|
|
316
|
+
return {}
|
|
317
|
+
elif target_type == "json":
|
|
318
|
+
if isinstance(value, (dict, list)):
|
|
319
|
+
return value
|
|
320
|
+
if isinstance(value, str):
|
|
321
|
+
return json.loads(value)
|
|
322
|
+
return value
|
|
323
|
+
else:
|
|
324
|
+
return value
|
|
325
|
+
except Exception as e:
|
|
326
|
+
raise ExecutorError(f"Type conversion error to '{target_type}': {e}")
|
|
327
|
+
|
|
328
|
+
def _validate_value(self, node: SetNode, value: Any):
|
|
329
|
+
"""Validate value against set_node rules"""
|
|
330
|
+
# Import validators lazily to avoid circular imports
|
|
331
|
+
from quantum.runtime.validators import QuantumValidators, ValidationError
|
|
332
|
+
|
|
333
|
+
# Check required
|
|
334
|
+
if node.required:
|
|
335
|
+
is_valid, error = QuantumValidators.validate_required(value)
|
|
336
|
+
if not is_valid:
|
|
337
|
+
raise ExecutorError(error)
|
|
338
|
+
|
|
339
|
+
# Check nullable
|
|
340
|
+
if not node.nullable and value is None:
|
|
341
|
+
raise ExecutorError(f"Variable '{node.name}' cannot be null")
|
|
342
|
+
|
|
343
|
+
# Skip other validations if null and nullable
|
|
344
|
+
if value is None and node.nullable:
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
# Check validate_rule
|
|
348
|
+
if node.validate_rule:
|
|
349
|
+
if node.validate_rule == 'cpf':
|
|
350
|
+
is_valid, error = QuantumValidators.validate_cpf(str(value))
|
|
351
|
+
elif node.validate_rule == 'cnpj':
|
|
352
|
+
is_valid, error = QuantumValidators.validate_cnpj(str(value))
|
|
353
|
+
else:
|
|
354
|
+
is_valid, error = QuantumValidators.validate(value, node.validate_rule)
|
|
355
|
+
|
|
356
|
+
if not is_valid:
|
|
357
|
+
raise ExecutorError(error)
|
|
358
|
+
|
|
359
|
+
# Check pattern
|
|
360
|
+
if node.pattern and not node.validate_rule:
|
|
361
|
+
is_valid, error = QuantumValidators.validate(value, node.pattern)
|
|
362
|
+
if not is_valid:
|
|
363
|
+
raise ExecutorError(error)
|
|
364
|
+
|
|
365
|
+
# Check range
|
|
366
|
+
if node.range:
|
|
367
|
+
is_valid, error = QuantumValidators.validate_range(value, node.range)
|
|
368
|
+
if not is_valid:
|
|
369
|
+
raise ExecutorError(error)
|
|
370
|
+
|
|
371
|
+
# Check enum
|
|
372
|
+
if node.enum:
|
|
373
|
+
is_valid, error = QuantumValidators.validate_enum(value, node.enum)
|
|
374
|
+
if not is_valid:
|
|
375
|
+
raise ExecutorError(error)
|
|
376
|
+
|
|
377
|
+
# Check min/max
|
|
378
|
+
if node.min or node.max:
|
|
379
|
+
is_valid, error = QuantumValidators.validate_min_max(
|
|
380
|
+
value, min_val=node.min, max_val=node.max
|
|
381
|
+
)
|
|
382
|
+
if not is_valid:
|
|
383
|
+
raise ExecutorError(error)
|
|
384
|
+
|
|
385
|
+
# Check minlength/maxlength
|
|
386
|
+
if node.type == "string" and (node.minlength or node.maxlength):
|
|
387
|
+
minlen = int(node.minlength) if node.minlength else None
|
|
388
|
+
maxlen = int(node.maxlength) if node.maxlength else None
|
|
389
|
+
|
|
390
|
+
is_valid, error = QuantumValidators.validate_length(
|
|
391
|
+
value, minlength=minlen, maxlength=maxlen
|
|
392
|
+
)
|
|
393
|
+
if not is_valid:
|
|
394
|
+
raise ExecutorError(error)
|
|
395
|
+
|
|
396
|
+
def _set_variable(self, node: SetNode, value: Any, exec_context):
|
|
397
|
+
"""Set variable in the appropriate scope"""
|
|
398
|
+
is_update_operation = node.operation in [
|
|
399
|
+
"increment", "decrement", "add", "multiply",
|
|
400
|
+
"append", "prepend", "remove", "removeAt",
|
|
401
|
+
"clear", "sort", "reverse", "unique",
|
|
402
|
+
"merge", "setProperty", "deleteProperty",
|
|
403
|
+
"uppercase", "lowercase", "trim", "format"
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
if is_update_operation or (exec_context.has_variable(node.name) and node.scope == "local"):
|
|
407
|
+
exec_context.update_variable(node.name, value)
|
|
408
|
+
else:
|
|
409
|
+
actual_scope = node.scope
|
|
410
|
+
if node.scope == "local" and exec_context.parent is not None:
|
|
411
|
+
actual_scope = "function"
|
|
412
|
+
exec_context.set_variable(node.name, value, scope=actual_scope)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Executors
|
|
3
|
+
|
|
4
|
+
Executors for data operations: query, invoke, data, transaction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .query_executor import QueryExecutor
|
|
8
|
+
from .invoke_executor import InvokeExecutor
|
|
9
|
+
from .data_executor import DataExecutor
|
|
10
|
+
from .transaction_executor import TransactionExecutor
|
|
11
|
+
|
|
12
|
+
__all__ = ['QueryExecutor', 'InvokeExecutor', 'DataExecutor', 'TransactionExecutor']
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Executor - Execute q:data statements
|
|
3
|
+
|
|
4
|
+
Handles data import from CSV, JSON, XML sources with transformations.
|
|
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 DataNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataExecutor(BaseExecutor):
|
|
13
|
+
"""
|
|
14
|
+
Executor for q:data statements.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- CSV import with column definitions
|
|
18
|
+
- JSON import from files or URLs
|
|
19
|
+
- XML import with XPath
|
|
20
|
+
- Data transformations (filter, sort, limit, compute)
|
|
21
|
+
- Response caching with TTL
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def handles(self) -> List[Type]:
|
|
26
|
+
return [DataNode]
|
|
27
|
+
|
|
28
|
+
def execute(self, node: DataNode, exec_context) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Execute data import.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
node: DataNode with import configuration
|
|
34
|
+
exec_context: Execution context
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
None (stores result in context)
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
context = exec_context.get_all_variables()
|
|
41
|
+
|
|
42
|
+
# Resolve source
|
|
43
|
+
source = self.apply_databinding(node.source, context)
|
|
44
|
+
|
|
45
|
+
# Build parameters
|
|
46
|
+
params = self._build_params(node, context)
|
|
47
|
+
|
|
48
|
+
# Execute data import
|
|
49
|
+
result = self.services.data_import.import_data(
|
|
50
|
+
node.data_type,
|
|
51
|
+
source,
|
|
52
|
+
params,
|
|
53
|
+
context=exec_context
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Store result
|
|
57
|
+
self._store_result(node, result, exec_context)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise ExecutorError(f"Data import error in '{node.name}': {e}")
|
|
61
|
+
|
|
62
|
+
def _build_params(self, node: DataNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
63
|
+
"""Build parameters for data import"""
|
|
64
|
+
params = {
|
|
65
|
+
'cache': node.cache,
|
|
66
|
+
'ttl': node.ttl,
|
|
67
|
+
'delimiter': node.delimiter,
|
|
68
|
+
'quote': node.quote,
|
|
69
|
+
'header': node.header,
|
|
70
|
+
'encoding': node.encoding,
|
|
71
|
+
'skip_rows': node.skip_rows,
|
|
72
|
+
'xpath': node.xpath,
|
|
73
|
+
'namespace': node.namespace,
|
|
74
|
+
'columns': [],
|
|
75
|
+
'fields': [],
|
|
76
|
+
'transforms': [],
|
|
77
|
+
'headers': []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Add column definitions (for CSV)
|
|
81
|
+
for col in node.columns:
|
|
82
|
+
params['columns'].append({
|
|
83
|
+
'name': col.name,
|
|
84
|
+
'type': col.col_type,
|
|
85
|
+
'required': col.required,
|
|
86
|
+
'default': col.default
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# Add field definitions (for XML)
|
|
90
|
+
for field in node.fields:
|
|
91
|
+
params['fields'].append({
|
|
92
|
+
'name': field.name,
|
|
93
|
+
'xpath': field.xpath,
|
|
94
|
+
'type': field.field_type
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
# Add HTTP headers
|
|
98
|
+
for header in node.headers:
|
|
99
|
+
resolved_value = self.apply_databinding(header.value, context)
|
|
100
|
+
params['headers'].append({
|
|
101
|
+
'name': header.name,
|
|
102
|
+
'value': resolved_value
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
# Add transformations
|
|
106
|
+
for transform in node.transforms:
|
|
107
|
+
for operation in transform.operations:
|
|
108
|
+
op_dict = {
|
|
109
|
+
'type': operation.__class__.__name__.replace('Node', '').lower()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if hasattr(operation, 'condition'):
|
|
113
|
+
op_dict['condition'] = operation.condition
|
|
114
|
+
elif hasattr(operation, 'by'):
|
|
115
|
+
op_dict['by'] = operation.by
|
|
116
|
+
op_dict['order'] = operation.order
|
|
117
|
+
elif hasattr(operation, 'value'):
|
|
118
|
+
op_dict['value'] = operation.value
|
|
119
|
+
elif hasattr(operation, 'field'):
|
|
120
|
+
op_dict['field'] = operation.field
|
|
121
|
+
op_dict['expression'] = operation.expression
|
|
122
|
+
op_dict['type'] = operation.comp_type
|
|
123
|
+
|
|
124
|
+
params['transforms'].append(op_dict)
|
|
125
|
+
|
|
126
|
+
return params
|
|
127
|
+
|
|
128
|
+
def _store_result(self, node: DataNode, result, exec_context):
|
|
129
|
+
"""Store data import result in context"""
|
|
130
|
+
exec_context.set_variable(node.name, result.data, scope="component")
|
|
131
|
+
|
|
132
|
+
result_dict = {
|
|
133
|
+
'success': result.success,
|
|
134
|
+
'data': result.data,
|
|
135
|
+
'error': result.error,
|
|
136
|
+
'recordCount': result.recordCount,
|
|
137
|
+
'loadTime': result.loadTime,
|
|
138
|
+
'cached': result.cached,
|
|
139
|
+
'source': result.source
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
exec_context.set_variable(f"{node.name}_result", result_dict, scope="component")
|
|
143
|
+
|
|
144
|
+
if node.result:
|
|
145
|
+
exec_context.set_variable(node.result, result_dict, scope="component")
|