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,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persist Parser - Parse q:persist statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import PersistNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PersistParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:persist statements for explicit persistence configuration.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:persist scope="local" prefix="myapp_">
|
|
17
|
+
<q:var name="theme" />
|
|
18
|
+
<q:var name="locale" />
|
|
19
|
+
</q:persist>
|
|
20
|
+
|
|
21
|
+
<q:persist scope="sync" key="user_prefs" encrypt="true">
|
|
22
|
+
<q:var name="darkMode" />
|
|
23
|
+
<q:var name="fontSize" />
|
|
24
|
+
</q:persist>
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def tag_names(self) -> List[str]:
|
|
29
|
+
return ['persist']
|
|
30
|
+
|
|
31
|
+
def parse(self, element: ET.Element) -> PersistNode:
|
|
32
|
+
"""Parse q:persist element"""
|
|
33
|
+
scope = element.get('scope', 'local')
|
|
34
|
+
prefix = element.get('prefix')
|
|
35
|
+
key = element.get('key')
|
|
36
|
+
encrypt = element.get('encrypt', 'false').lower() == 'true'
|
|
37
|
+
storage = element.get('storage')
|
|
38
|
+
|
|
39
|
+
ttl = None
|
|
40
|
+
ttl_attr = element.get('ttl')
|
|
41
|
+
if ttl_attr:
|
|
42
|
+
try:
|
|
43
|
+
ttl = int(ttl_attr)
|
|
44
|
+
except ValueError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
persist_node = PersistNode(
|
|
48
|
+
scope=scope,
|
|
49
|
+
prefix=prefix,
|
|
50
|
+
key=key,
|
|
51
|
+
encrypt=encrypt,
|
|
52
|
+
ttl=ttl,
|
|
53
|
+
storage=storage
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Parse q:var children
|
|
57
|
+
for child in element:
|
|
58
|
+
child_type = self._get_element_name(child)
|
|
59
|
+
if child_type == 'var':
|
|
60
|
+
var_name = child.get('name')
|
|
61
|
+
if var_name:
|
|
62
|
+
persist_node.add_variable(var_name)
|
|
63
|
+
|
|
64
|
+
return persist_node
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Route Parser - Parse q:route statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import QuantumRoute
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RouteParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:route statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:route path="/users" method="GET" />
|
|
17
|
+
<q:route path="/api/products/{id}" method="POST">
|
|
18
|
+
<q:return name="product" type="object" />
|
|
19
|
+
</q:route>
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def tag_names(self) -> List[str]:
|
|
24
|
+
return ['route']
|
|
25
|
+
|
|
26
|
+
def parse(self, element: ET.Element) -> QuantumRoute:
|
|
27
|
+
"""Parse q:route element"""
|
|
28
|
+
path = element.get('path', '/')
|
|
29
|
+
method = element.get('method', 'GET').upper()
|
|
30
|
+
|
|
31
|
+
route = QuantumRoute(path, method)
|
|
32
|
+
|
|
33
|
+
# Parse q:return inside the route
|
|
34
|
+
for child in element:
|
|
35
|
+
child_type = self._get_element_name(child)
|
|
36
|
+
if child_type == 'return':
|
|
37
|
+
return_node = self.parse_child(child)
|
|
38
|
+
if return_node:
|
|
39
|
+
route.returns.append(return_node)
|
|
40
|
+
|
|
41
|
+
return route
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scripting Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for Python scripting: python, pyimport, pyclass, pydecorator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .python_parser import PythonParser
|
|
8
|
+
from .pyimport_parser import PyImportParser
|
|
9
|
+
from .pyclass_parser import PyClassParser
|
|
10
|
+
from .pydecorator_parser import PyDecoratorParser
|
|
11
|
+
|
|
12
|
+
__all__ = ['PythonParser', 'PyImportParser', 'PyClassParser', 'PyDecoratorParser']
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyClass Parser - Parse q:class statements
|
|
3
|
+
|
|
4
|
+
Handles inline Python class definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import PyClassNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PyClassParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:class statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Class definition
|
|
19
|
+
- Base class inheritance
|
|
20
|
+
- Decorator application
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['class', 'pyclass']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> PyClassNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:class statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:class
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
PyClassNode AST node
|
|
36
|
+
"""
|
|
37
|
+
name = self.get_attr(element, 'name')
|
|
38
|
+
code = self.get_text(element)
|
|
39
|
+
|
|
40
|
+
if not name:
|
|
41
|
+
raise ParserError("class requires 'name' attribute")
|
|
42
|
+
if not code or not code.strip():
|
|
43
|
+
raise ParserError("class body cannot be empty")
|
|
44
|
+
|
|
45
|
+
# Parse bases
|
|
46
|
+
bases_attr = self.get_attr(element, 'bases')
|
|
47
|
+
bases = []
|
|
48
|
+
if bases_attr:
|
|
49
|
+
bases = [b.strip() for b in bases_attr.split(',')]
|
|
50
|
+
|
|
51
|
+
# Parse decorators
|
|
52
|
+
decorators_attr = self.get_attr(element, 'decorators')
|
|
53
|
+
decorators = []
|
|
54
|
+
if decorators_attr:
|
|
55
|
+
decorators = [d.strip() for d in decorators_attr.split(',')]
|
|
56
|
+
|
|
57
|
+
return PyClassNode(
|
|
58
|
+
name=name,
|
|
59
|
+
code=code,
|
|
60
|
+
bases=bases,
|
|
61
|
+
decorators=decorators
|
|
62
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyDecorator Parser - Parse q:decorator statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
from typing import List
|
|
7
|
+
from xml.etree import ElementTree as ET
|
|
8
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
9
|
+
from quantum.core.ast_nodes import PyDecoratorNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PyDecoratorParser(BaseTagParser):
|
|
13
|
+
"""
|
|
14
|
+
Parser for q:decorator statements for Python decorator definitions.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
<!-- Caching decorator -->
|
|
18
|
+
<q:decorator name="cached">
|
|
19
|
+
from functools import wraps
|
|
20
|
+
def decorator(func):
|
|
21
|
+
cache = {}
|
|
22
|
+
@wraps(func)
|
|
23
|
+
def wrapper(*args):
|
|
24
|
+
if args in cache:
|
|
25
|
+
return cache[args]
|
|
26
|
+
result = func(*args)
|
|
27
|
+
cache[args] = result
|
|
28
|
+
return result
|
|
29
|
+
return wrapper
|
|
30
|
+
return decorator
|
|
31
|
+
</q:decorator>
|
|
32
|
+
|
|
33
|
+
<!-- Decorator with parameters -->
|
|
34
|
+
<q:decorator name="retry" params="attempts, delay">
|
|
35
|
+
import time
|
|
36
|
+
def decorator(func):
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
for i in range(attempts):
|
|
39
|
+
try:
|
|
40
|
+
return func(*args, **kwargs)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
if i == attempts - 1:
|
|
43
|
+
raise
|
|
44
|
+
time.sleep(delay)
|
|
45
|
+
return wrapper
|
|
46
|
+
return decorator
|
|
47
|
+
</q:decorator>
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def tag_names(self) -> List[str]:
|
|
52
|
+
return ['decorator', 'pydecorator']
|
|
53
|
+
|
|
54
|
+
def parse(self, element: ET.Element) -> PyDecoratorNode:
|
|
55
|
+
"""Parse q:decorator element"""
|
|
56
|
+
name = element.get('name', '')
|
|
57
|
+
code = element.text or ""
|
|
58
|
+
code = textwrap.dedent(code).strip()
|
|
59
|
+
|
|
60
|
+
# Parse params (comma-separated)
|
|
61
|
+
params_str = element.get('params', '')
|
|
62
|
+
params = [p.strip() for p in params_str.split(',') if p.strip()]
|
|
63
|
+
|
|
64
|
+
return PyDecoratorNode(
|
|
65
|
+
name=name,
|
|
66
|
+
code=code,
|
|
67
|
+
params=params
|
|
68
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyImport Parser - Parse q:pyimport statements
|
|
3
|
+
|
|
4
|
+
Handles Python module imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import PyImportNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PyImportParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:pyimport statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Module import with alias
|
|
19
|
+
- Specific name imports
|
|
20
|
+
- Star imports
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['pyimport']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> PyImportNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:pyimport statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:pyimport
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
PyImportNode AST node
|
|
36
|
+
"""
|
|
37
|
+
module = self.get_attr(element, 'module')
|
|
38
|
+
|
|
39
|
+
if not module:
|
|
40
|
+
raise ParserError("pyimport requires 'module' attribute")
|
|
41
|
+
|
|
42
|
+
# Parse names if present
|
|
43
|
+
names_attr = self.get_attr(element, 'names')
|
|
44
|
+
names = []
|
|
45
|
+
if names_attr:
|
|
46
|
+
names = [n.strip() for n in names_attr.split(',')]
|
|
47
|
+
|
|
48
|
+
return PyImportNode(
|
|
49
|
+
module=module,
|
|
50
|
+
alias=self.get_attr(element, 'as'),
|
|
51
|
+
names=names
|
|
52
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python Parser - Parse q:python statements
|
|
3
|
+
|
|
4
|
+
Handles embedded Python code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import PythonNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PythonParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:python statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Inline Python code
|
|
19
|
+
- Scope management
|
|
20
|
+
- Async execution
|
|
21
|
+
- Result capture
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['python']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> PythonNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:python statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:python
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
PythonNode AST node
|
|
37
|
+
"""
|
|
38
|
+
code = self.get_text(element)
|
|
39
|
+
|
|
40
|
+
if not code or not code.strip():
|
|
41
|
+
raise ParserError("Python block cannot be empty")
|
|
42
|
+
|
|
43
|
+
return PythonNode(
|
|
44
|
+
code=code,
|
|
45
|
+
scope=self.get_attr(element, 'scope', 'component'),
|
|
46
|
+
async_mode=self.get_bool_attr(element, 'async', False),
|
|
47
|
+
timeout=self.get_attr(element, 'timeout'),
|
|
48
|
+
result=self.get_attr(element, 'result')
|
|
49
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Services Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for service operations: log, dump, file, mail.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .log_parser import LogParser
|
|
8
|
+
from .dump_parser import DumpParser
|
|
9
|
+
from .file_parser import FileParser
|
|
10
|
+
from .mail_parser import MailParser
|
|
11
|
+
|
|
12
|
+
__all__ = ['LogParser', 'DumpParser', 'FileParser', 'MailParser']
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dump Parser - Parse q:dump statements
|
|
3
|
+
|
|
4
|
+
Handles variable inspection and debugging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
10
|
+
from quantum.core.features.dump.src import DumpNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DumpParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:dump statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Variable inspection
|
|
19
|
+
- Multiple output formats
|
|
20
|
+
- Depth control
|
|
21
|
+
- Conditional dumping
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['dump']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> DumpNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:dump statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:dump
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
DumpNode AST node
|
|
37
|
+
"""
|
|
38
|
+
var = self.get_attr(element, 'var', '')
|
|
39
|
+
label = self.get_attr(element, 'label', var)
|
|
40
|
+
format_type = self.get_attr(element, 'format', 'text')
|
|
41
|
+
depth = self.get_int_attr(element, 'depth', 3)
|
|
42
|
+
when = self.get_attr(element, 'when')
|
|
43
|
+
|
|
44
|
+
dump_node = DumpNode(
|
|
45
|
+
var=var,
|
|
46
|
+
label=label,
|
|
47
|
+
format=format_type,
|
|
48
|
+
depth=depth,
|
|
49
|
+
when=when
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return dump_node
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Parser - Parse q:file statements
|
|
3
|
+
|
|
4
|
+
Handles file upload and deletion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import FileNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FileParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:file statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- File upload with conflict handling
|
|
19
|
+
- File deletion
|
|
20
|
+
- Result storage
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['file']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> FileNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:file statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:file
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
FileNode AST node
|
|
36
|
+
"""
|
|
37
|
+
action = self.get_attr(element, 'action', 'upload')
|
|
38
|
+
file_attr = self.get_attr(element, 'file')
|
|
39
|
+
|
|
40
|
+
if not file_attr:
|
|
41
|
+
raise ParserError("File requires 'file' attribute")
|
|
42
|
+
|
|
43
|
+
file_node = FileNode(action, file_attr)
|
|
44
|
+
file_node.destination = self.get_attr(element, 'destination')
|
|
45
|
+
file_node.name_conflict = self.get_attr(element, 'nameConflict', 'makeUnique')
|
|
46
|
+
file_node.result = self.get_attr(element, 'result')
|
|
47
|
+
|
|
48
|
+
return file_node
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log Parser - Parse q:log statements
|
|
3
|
+
|
|
4
|
+
Handles structured logging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
10
|
+
from quantum.core.features.logging.src import LogNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LogParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:log statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Log levels (debug, info, warn, error)
|
|
19
|
+
- Conditional logging
|
|
20
|
+
- Context data
|
|
21
|
+
- Correlation IDs
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['log']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> LogNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:log statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:log
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
LogNode AST node
|
|
37
|
+
"""
|
|
38
|
+
level = self.get_attr(element, 'level', 'info')
|
|
39
|
+
message = self.get_attr(element, 'message') or self.get_text(element)
|
|
40
|
+
|
|
41
|
+
log_node = LogNode(level=level, message=message)
|
|
42
|
+
log_node.when = self.get_attr(element, 'when')
|
|
43
|
+
log_node.context = self.get_attr(element, 'context')
|
|
44
|
+
log_node.correlation_id = self.get_attr(element, 'correlationId')
|
|
45
|
+
|
|
46
|
+
return log_node
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mail Parser - Parse q:mail statements
|
|
3
|
+
|
|
4
|
+
Handles email sending.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import MailNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MailParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:mail statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- HTML and plain text emails
|
|
19
|
+
- CC, BCC, Reply-To
|
|
20
|
+
- Attachments
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['mail']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> MailNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:mail statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:mail
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
MailNode AST node
|
|
36
|
+
"""
|
|
37
|
+
to = self.get_attr(element, 'to')
|
|
38
|
+
subject = self.get_attr(element, 'subject')
|
|
39
|
+
|
|
40
|
+
if not to:
|
|
41
|
+
raise ParserError("Mail requires 'to' attribute")
|
|
42
|
+
if not subject:
|
|
43
|
+
raise ParserError("Mail requires 'subject' attribute")
|
|
44
|
+
|
|
45
|
+
mail_node = MailNode(to, subject)
|
|
46
|
+
|
|
47
|
+
# Get body from content or attribute
|
|
48
|
+
mail_node.body = self.get_text(element) or self.get_attr(element, 'body', '')
|
|
49
|
+
|
|
50
|
+
# Optional attributes
|
|
51
|
+
mail_node.from_addr = self.get_attr(element, 'from')
|
|
52
|
+
mail_node.cc = self.get_attr(element, 'cc')
|
|
53
|
+
mail_node.bcc = self.get_attr(element, 'bcc')
|
|
54
|
+
mail_node.reply_to = self.get_attr(element, 'replyTo')
|
|
55
|
+
mail_node.type = self.get_attr(element, 'type', 'html')
|
|
56
|
+
|
|
57
|
+
# Parse attachments
|
|
58
|
+
for child in element:
|
|
59
|
+
child_type = self.get_element_name(child)
|
|
60
|
+
if child_type == 'attachment':
|
|
61
|
+
attachment = self.get_attr(child, 'file')
|
|
62
|
+
if attachment:
|
|
63
|
+
mail_node.attachments.append(attachment)
|
|
64
|
+
|
|
65
|
+
return mail_node
|
quantum/core/tiers.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Support tiers for Quantum tags.
|
|
3
|
+
|
|
4
|
+
PRODUCTION_READINESS.md Fase 0. "Production-ready" is meaningful only relative
|
|
5
|
+
to a defined surface, so the surface is defined here, in code, and a tag
|
|
6
|
+
outside the supported set announces itself the first time it is used instead
|
|
7
|
+
of pretending to be first-class.
|
|
8
|
+
|
|
9
|
+
The lists mirror SUPPORT_TIERS.md. When they disagree, this file is the one the
|
|
10
|
+
engine actually enforces, so keep it in step.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("quantum.tiers")
|
|
16
|
+
|
|
17
|
+
# Documented, tested end-to-end, stable. A break here is a critical bug.
|
|
18
|
+
CORE = frozenset({
|
|
19
|
+
"component", "param", "return", "set", "if", "loop", "function",
|
|
20
|
+
"query", "action", "redirect", "flash", "invoke", "data",
|
|
21
|
+
"import", "slot",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
# The reason the project exists. Same contract as Core is the goal; some of it
|
|
25
|
+
# is still being validated end-to-end (PRODUCTION_READINESS.md Fase 4).
|
|
26
|
+
DIFERENCIAL = frozenset({"llm", "knowledge", "agent", "team"})
|
|
27
|
+
|
|
28
|
+
# Kept and registered, but no stability promise and not in the README. Using
|
|
29
|
+
# one is fine for experiments; relying on one in production is not supported.
|
|
30
|
+
EXPERIMENTAL = frozenset({
|
|
31
|
+
"job", "schedule", "thread",
|
|
32
|
+
"message", "queue", "subscribe", "messageAck", "messageNack",
|
|
33
|
+
"websocket", "websocket-send", "websocket-close",
|
|
34
|
+
"mail", "file", "log", "dump", "persist",
|
|
35
|
+
"python", "pyclass", "pyimport", "class", "decorator", "pydecorator",
|
|
36
|
+
"dispatchEvent", "transaction",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
SUPPORTED = CORE | DIFERENCIAL
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def tier_of(tag_name: str) -> str:
|
|
43
|
+
"""'core' | 'diferencial' | 'experimental' | 'unknown'."""
|
|
44
|
+
if tag_name in CORE:
|
|
45
|
+
return "core"
|
|
46
|
+
if tag_name in DIFERENCIAL:
|
|
47
|
+
return "diferencial"
|
|
48
|
+
if tag_name in EXPERIMENTAL:
|
|
49
|
+
return "experimental"
|
|
50
|
+
return "unknown"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Warn once per tag per process — a q:loop over 1,000 rows must not emit 1,000
|
|
54
|
+
# lines, the same discipline as expression_diagnostics.
|
|
55
|
+
_warned: set = set()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def warn_if_unsupported(tag_name: str) -> None:
|
|
59
|
+
"""Emit a one-time warning when an experimental tag is used.
|
|
60
|
+
|
|
61
|
+
Not an error: experimental tags still run. The point is that an operator
|
|
62
|
+
reading the log sees, once, that their app leans on something the project
|
|
63
|
+
does not promise to keep working — the audit found apps built entirely on
|
|
64
|
+
tags that parse and then explode, with nothing warning anyone.
|
|
65
|
+
"""
|
|
66
|
+
tier = tier_of(tag_name)
|
|
67
|
+
if tier in ("core", "diferencial", "unknown"):
|
|
68
|
+
# 'unknown' is handled by the parser's own error path, not here.
|
|
69
|
+
return
|
|
70
|
+
if tag_name in _warned:
|
|
71
|
+
return
|
|
72
|
+
_warned.add(tag_name)
|
|
73
|
+
logger.warning(
|
|
74
|
+
"q:%s is EXPERIMENTAL — not covered by the production support surface "
|
|
75
|
+
"(SUPPORT_TIERS.md). It may change or break without notice; do not rely "
|
|
76
|
+
"on it in production.", tag_name
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def reset_warnings() -> None:
|
|
81
|
+
"""For tests, and for a reloading server."""
|
|
82
|
+
_warned.clear()
|