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,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTML Parser - Parse HTML elements
|
|
3
|
+
|
|
4
|
+
Handles parsing of standard HTML elements within Quantum components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
10
|
+
from quantum.core.ast_nodes import HTMLNode, TextNode, HTML_VOID_ELEMENTS
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HTMLParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for HTML elements.
|
|
16
|
+
|
|
17
|
+
Handles standard HTML tags like div, span, p, a, etc.
|
|
18
|
+
This parser is used as a fallback for elements that aren't
|
|
19
|
+
Quantum-specific tags.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Common HTML tags (not exhaustive - uses detection logic)
|
|
23
|
+
HTML_TAGS = {
|
|
24
|
+
'div', 'span', 'p', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
25
|
+
'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'thead', 'tbody',
|
|
26
|
+
'form', 'input', 'button', 'select', 'option', 'textarea', 'label',
|
|
27
|
+
'img', 'video', 'audio', 'source', 'canvas', 'svg',
|
|
28
|
+
'header', 'footer', 'nav', 'main', 'section', 'article', 'aside',
|
|
29
|
+
'br', 'hr', 'pre', 'code', 'blockquote', 'em', 'strong', 'b', 'i',
|
|
30
|
+
'script', 'style', 'link', 'meta', 'head', 'body', 'html',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def tag_names(self) -> List[str]:
|
|
35
|
+
"""HTML parser handles all HTML tags."""
|
|
36
|
+
return list(self.HTML_TAGS)
|
|
37
|
+
|
|
38
|
+
def parse(self, element: ET.Element) -> HTMLNode:
|
|
39
|
+
"""
|
|
40
|
+
Parse HTML element.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
element: XML element representing HTML tag
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
HTMLNode AST node
|
|
47
|
+
"""
|
|
48
|
+
tag_name = self._get_local_name(element)
|
|
49
|
+
|
|
50
|
+
# Get attributes
|
|
51
|
+
attributes = dict(element.attrib)
|
|
52
|
+
|
|
53
|
+
# Check if void element
|
|
54
|
+
is_void = tag_name.lower() in HTML_VOID_ELEMENTS
|
|
55
|
+
|
|
56
|
+
# Create HTML node
|
|
57
|
+
html_node = HTMLNode(tag_name, attributes)
|
|
58
|
+
|
|
59
|
+
# Parse children if not void element
|
|
60
|
+
if not is_void:
|
|
61
|
+
html_node.children = self._parse_children(element)
|
|
62
|
+
|
|
63
|
+
return html_node
|
|
64
|
+
|
|
65
|
+
def _get_local_name(self, element: ET.Element) -> str:
|
|
66
|
+
"""Extract local name from element tag."""
|
|
67
|
+
tag = element.tag
|
|
68
|
+
if '}' in tag:
|
|
69
|
+
return tag.split('}')[1]
|
|
70
|
+
if ':' in tag:
|
|
71
|
+
return tag.split(':')[1]
|
|
72
|
+
return tag
|
|
73
|
+
|
|
74
|
+
def _parse_children(self, element: ET.Element) -> List:
|
|
75
|
+
"""Parse child elements and text."""
|
|
76
|
+
children = []
|
|
77
|
+
|
|
78
|
+
# Add leading text if present
|
|
79
|
+
if element.text and element.text.strip():
|
|
80
|
+
children.append(TextNode(element.text))
|
|
81
|
+
|
|
82
|
+
# Parse child elements
|
|
83
|
+
for child in element:
|
|
84
|
+
# Use main parser to handle child
|
|
85
|
+
child_node = self.parse_statement(child)
|
|
86
|
+
if child_node:
|
|
87
|
+
children.append(child_node)
|
|
88
|
+
|
|
89
|
+
# Add tail text after child
|
|
90
|
+
if child.tail and child.tail.strip():
|
|
91
|
+
children.append(TextNode(child.tail))
|
|
92
|
+
|
|
93
|
+
return children
|
|
94
|
+
|
|
95
|
+
def can_parse(self, tag_name: str) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Check if this parser can handle the tag.
|
|
98
|
+
|
|
99
|
+
Uses heuristics to detect HTML elements:
|
|
100
|
+
1. Known HTML tags
|
|
101
|
+
2. Lowercase tags (not Quantum q: or component PascalCase)
|
|
102
|
+
"""
|
|
103
|
+
if not tag_name:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# Lowercase and in known tags
|
|
107
|
+
lower_tag = tag_name.lower()
|
|
108
|
+
if lower_tag in self.HTML_TAGS:
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
# Lowercase first letter (not a component)
|
|
112
|
+
if tag_name[0].islower():
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
return False
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Jobs Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for job operations: schedule, thread, job.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .schedule_parser import ScheduleParser
|
|
8
|
+
from .thread_parser import ThreadParser
|
|
9
|
+
from .job_parser import JobParser
|
|
10
|
+
|
|
11
|
+
__all__ = ['ScheduleParser', 'ThreadParser', 'JobParser']
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Job Parser - Parse q:job statements
|
|
3
|
+
|
|
4
|
+
Handles job queue configuration.
|
|
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 JobNode, QuantumParam
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JobParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:job statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Job definition
|
|
19
|
+
- Job dispatch
|
|
20
|
+
- Batch dispatch
|
|
21
|
+
- Retry configuration
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['job']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> JobNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:job statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:job
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
JobNode AST node
|
|
37
|
+
"""
|
|
38
|
+
name = self.get_attr(element, 'name')
|
|
39
|
+
|
|
40
|
+
if not name:
|
|
41
|
+
raise ParserError("Job requires 'name' attribute")
|
|
42
|
+
|
|
43
|
+
queue = self.get_attr(element, 'queue', 'default')
|
|
44
|
+
action = self.get_attr(element, 'action', 'define')
|
|
45
|
+
|
|
46
|
+
job_node = JobNode(name, queue, action)
|
|
47
|
+
job_node.delay = self.get_attr(element, 'delay')
|
|
48
|
+
job_node.priority = self.get_int_attr(element, 'priority', 0)
|
|
49
|
+
job_node.attempts = self.get_int_attr(element, 'attempts', 3)
|
|
50
|
+
job_node.backoff = self.get_attr(element, 'backoff', '30s')
|
|
51
|
+
job_node.timeout = self.get_attr(element, 'timeout')
|
|
52
|
+
|
|
53
|
+
# Parse children
|
|
54
|
+
for child in element:
|
|
55
|
+
child_type = self.get_element_name(child)
|
|
56
|
+
|
|
57
|
+
if child_type == 'param':
|
|
58
|
+
param = QuantumParam(
|
|
59
|
+
self.get_attr(child, 'name', ''),
|
|
60
|
+
self.get_attr(child, 'type', 'string')
|
|
61
|
+
)
|
|
62
|
+
param.value = self.get_attr(child, 'value')
|
|
63
|
+
param.required = self.get_bool_attr(child, 'required', False)
|
|
64
|
+
job_node.add_param(param)
|
|
65
|
+
else:
|
|
66
|
+
# Job body statement
|
|
67
|
+
statement = self.parse_statement(child)
|
|
68
|
+
if statement:
|
|
69
|
+
job_node.add_statement(statement)
|
|
70
|
+
|
|
71
|
+
return job_node
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schedule Parser - Parse q:schedule statements
|
|
3
|
+
|
|
4
|
+
Handles scheduled task configuration.
|
|
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 ScheduleNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ScheduleParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:schedule statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Interval scheduling
|
|
19
|
+
- Cron expressions
|
|
20
|
+
- One-time execution
|
|
21
|
+
- Timezone support
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['schedule']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> ScheduleNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:schedule statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:schedule
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
ScheduleNode AST node
|
|
37
|
+
"""
|
|
38
|
+
name = self.get_attr(element, 'name')
|
|
39
|
+
|
|
40
|
+
if not name:
|
|
41
|
+
raise ParserError("Schedule requires 'name' attribute")
|
|
42
|
+
|
|
43
|
+
schedule_node = ScheduleNode(
|
|
44
|
+
name=name,
|
|
45
|
+
action=self.get_attr(element, 'action', 'run'),
|
|
46
|
+
interval=self.get_attr(element, 'interval'),
|
|
47
|
+
cron=self.get_attr(element, 'cron'),
|
|
48
|
+
at=self.get_attr(element, 'at'),
|
|
49
|
+
timezone=self.get_attr(element, 'timezone', 'UTC'),
|
|
50
|
+
retry=self.get_int_attr(element, 'retry', 3),
|
|
51
|
+
timeout=self.get_attr(element, 'timeout'),
|
|
52
|
+
overlap=self.get_bool_attr(element, 'overlap', False),
|
|
53
|
+
enabled=self.get_bool_attr(element, 'enabled', True)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Parse body statements
|
|
57
|
+
for child in element:
|
|
58
|
+
statement = self.parse_statement(child)
|
|
59
|
+
if statement:
|
|
60
|
+
schedule_node.add_statement(statement)
|
|
61
|
+
|
|
62
|
+
return schedule_node
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thread Parser - Parse q:thread statements
|
|
3
|
+
|
|
4
|
+
Handles async thread configuration.
|
|
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 ThreadNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ThreadParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:thread statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Thread execution
|
|
19
|
+
- Join/terminate actions
|
|
20
|
+
- Priority and callbacks
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['thread']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> ThreadNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:thread statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:thread
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
ThreadNode AST node
|
|
36
|
+
"""
|
|
37
|
+
name = self.get_attr(element, 'name')
|
|
38
|
+
|
|
39
|
+
if not name:
|
|
40
|
+
raise ParserError("Thread requires 'name' attribute")
|
|
41
|
+
|
|
42
|
+
thread_node = ThreadNode(
|
|
43
|
+
name=name,
|
|
44
|
+
action=self.get_attr(element, 'action', 'run'),
|
|
45
|
+
priority=self.get_attr(element, 'priority', 'normal'),
|
|
46
|
+
timeout=self.get_attr(element, 'timeout'),
|
|
47
|
+
on_complete=self.get_attr(element, 'onComplete'),
|
|
48
|
+
on_error=self.get_attr(element, 'onError')
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Parse body statements
|
|
52
|
+
for child in element:
|
|
53
|
+
statement = self.parse_statement(child)
|
|
54
|
+
if statement:
|
|
55
|
+
thread_node.add_statement(statement)
|
|
56
|
+
|
|
57
|
+
return thread_node
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Messaging Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for messaging operations: websocket, message, subscribe, queue, messageAck, messageNack.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .websocket_parser import WebSocketParser, WebSocketSendParser, WebSocketCloseParser
|
|
8
|
+
from .message_parser import MessageParser, SubscribeParser
|
|
9
|
+
from .queue_parser import QueueParser
|
|
10
|
+
from .message_ack_parser import MessageAckParser
|
|
11
|
+
from .message_nack_parser import MessageNackParser
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'WebSocketParser', 'WebSocketSendParser', 'WebSocketCloseParser',
|
|
15
|
+
'MessageParser', 'SubscribeParser', 'QueueParser',
|
|
16
|
+
'MessageAckParser', 'MessageNackParser'
|
|
17
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MessageAck Parser - Parse q:messageAck 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 MessageAckNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MessageAckParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:messageAck statements for acknowledging messages.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
<q:subscribe name="worker" queue="tasks" ack="manual">
|
|
17
|
+
<q:onMessage>
|
|
18
|
+
<q:set name="processed" value="true" />
|
|
19
|
+
<q:messageAck />
|
|
20
|
+
</q:onMessage>
|
|
21
|
+
</q:subscribe>
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['messageAck']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> MessageAckNode:
|
|
29
|
+
"""Parse q:messageAck element"""
|
|
30
|
+
return MessageAckNode()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MessageNack Parser - Parse q:messageNack 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 MessageNackNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MessageNackParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:messageNack statements for negative acknowledgment.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<!-- Reject and requeue -->
|
|
17
|
+
<q:messageNack requeue="true" />
|
|
18
|
+
|
|
19
|
+
<!-- Reject without requeue (send to DLQ) -->
|
|
20
|
+
<q:messageNack requeue="false" />
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['messageNack']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> MessageNackNode:
|
|
28
|
+
"""Parse q:messageNack element"""
|
|
29
|
+
requeue = element.get('requeue', 'true').lower() == 'true'
|
|
30
|
+
return MessageNackNode(requeue=requeue)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Message Parser - Parse q:message and q:subscribe statements
|
|
3
|
+
|
|
4
|
+
Handles message queue operations.
|
|
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 MessageNode, SubscribeNode, MessageHeaderNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:message statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Publish (topic)
|
|
19
|
+
- Send (queue)
|
|
20
|
+
- Request/reply
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['message']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> MessageNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:message statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:message
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
MessageNode AST node
|
|
36
|
+
"""
|
|
37
|
+
msg_node = MessageNode(
|
|
38
|
+
name=self.get_attr(element, 'name'),
|
|
39
|
+
topic=self.get_attr(element, 'topic'),
|
|
40
|
+
queue=self.get_attr(element, 'queue'),
|
|
41
|
+
type=self.get_attr(element, 'type', 'publish'),
|
|
42
|
+
timeout=self.get_attr(element, 'timeout')
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Parse children
|
|
46
|
+
for child in element:
|
|
47
|
+
child_type = self.get_element_name(child)
|
|
48
|
+
|
|
49
|
+
if child_type == 'header':
|
|
50
|
+
header = MessageHeaderNode(
|
|
51
|
+
name=self.get_attr(child, 'name', ''),
|
|
52
|
+
value=self.get_attr(child, 'value', '')
|
|
53
|
+
)
|
|
54
|
+
msg_node.add_header(header)
|
|
55
|
+
elif child_type == 'body':
|
|
56
|
+
msg_node.body = self.get_text(child)
|
|
57
|
+
|
|
58
|
+
return msg_node
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SubscribeParser(BaseTagParser):
|
|
62
|
+
"""
|
|
63
|
+
Parser for q:subscribe statements.
|
|
64
|
+
|
|
65
|
+
Supports:
|
|
66
|
+
- Topic subscription
|
|
67
|
+
- Queue consumption
|
|
68
|
+
- Message handlers
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def tag_names(self) -> List[str]:
|
|
73
|
+
return ['subscribe']
|
|
74
|
+
|
|
75
|
+
def parse(self, element: ET.Element) -> SubscribeNode:
|
|
76
|
+
"""
|
|
77
|
+
Parse q:subscribe statement.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
element: XML element for q:subscribe
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
SubscribeNode AST node
|
|
84
|
+
"""
|
|
85
|
+
name = self.get_attr(element, 'name')
|
|
86
|
+
|
|
87
|
+
if not name:
|
|
88
|
+
raise ParserError("Subscribe requires 'name' attribute")
|
|
89
|
+
|
|
90
|
+
sub_node = SubscribeNode(
|
|
91
|
+
name=name,
|
|
92
|
+
topic=self.get_attr(element, 'topic'),
|
|
93
|
+
topics=self.get_attr(element, 'topics'),
|
|
94
|
+
queue=self.get_attr(element, 'queue'),
|
|
95
|
+
ack=self.get_attr(element, 'ack', 'auto'),
|
|
96
|
+
prefetch=self.get_int_attr(element, 'prefetch', 1)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Parse handlers
|
|
100
|
+
for child in element:
|
|
101
|
+
child_type = self.get_element_name(child)
|
|
102
|
+
|
|
103
|
+
if child_type == 'onMessage':
|
|
104
|
+
for handler_child in child:
|
|
105
|
+
statement = self.parse_statement(handler_child)
|
|
106
|
+
if statement:
|
|
107
|
+
sub_node.add_on_message_statement(statement)
|
|
108
|
+
elif child_type == 'onError':
|
|
109
|
+
for handler_child in child:
|
|
110
|
+
statement = self.parse_statement(handler_child)
|
|
111
|
+
if statement:
|
|
112
|
+
sub_node.add_on_error_statement(statement)
|
|
113
|
+
|
|
114
|
+
return sub_node
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Queue Parser - Parse q:queue statements
|
|
3
|
+
|
|
4
|
+
Handles queue management operations.
|
|
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 QueueNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QueueParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:queue statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Queue declaration
|
|
19
|
+
- Queue purge/delete
|
|
20
|
+
- Queue info
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['queue']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> QueueNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:queue statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:queue
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
QueueNode AST node
|
|
36
|
+
"""
|
|
37
|
+
name = self.get_attr(element, 'name')
|
|
38
|
+
action = self.get_attr(element, 'action', 'declare')
|
|
39
|
+
|
|
40
|
+
if not name:
|
|
41
|
+
raise ParserError("Queue requires 'name' attribute")
|
|
42
|
+
|
|
43
|
+
# Parse attributes matching legacy method
|
|
44
|
+
durable = self.get_bool_attr(element, 'durable', True)
|
|
45
|
+
exclusive = self.get_bool_attr(element, 'exclusive', False)
|
|
46
|
+
auto_delete = self.get_bool_attr(element, 'autoDelete', False)
|
|
47
|
+
dead_letter_queue = self.get_attr(element, 'deadLetterQueue')
|
|
48
|
+
ttl_str = self.get_attr(element, 'ttl')
|
|
49
|
+
ttl = int(ttl_str) if ttl_str else None
|
|
50
|
+
result = self.get_attr(element, 'result')
|
|
51
|
+
|
|
52
|
+
return QueueNode(
|
|
53
|
+
name=name,
|
|
54
|
+
action=action,
|
|
55
|
+
durable=durable,
|
|
56
|
+
exclusive=exclusive,
|
|
57
|
+
auto_delete=auto_delete,
|
|
58
|
+
dead_letter_queue=dead_letter_queue,
|
|
59
|
+
ttl=ttl,
|
|
60
|
+
result=result
|
|
61
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket Parser - Parse q:websocket statements
|
|
3
|
+
|
|
4
|
+
Handles WebSocket connection and messaging configuration.
|
|
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.features.websocket.src.ast_node import (
|
|
11
|
+
WebSocketNode, WebSocketHandlerNode,
|
|
12
|
+
WebSocketSendNode, WebSocketCloseNode
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebSocketParser(BaseTagParser):
|
|
17
|
+
"""
|
|
18
|
+
Parser for q:websocket statements.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- Connection configuration
|
|
22
|
+
- Event handlers
|
|
23
|
+
- Reconnection settings
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def tag_names(self) -> List[str]:
|
|
28
|
+
return ['websocket']
|
|
29
|
+
|
|
30
|
+
def parse(self, element: ET.Element) -> WebSocketNode:
|
|
31
|
+
"""
|
|
32
|
+
Parse q:websocket statement.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
element: XML element for q:websocket
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
WebSocketNode AST node
|
|
39
|
+
"""
|
|
40
|
+
name = self.get_attr(element, 'name')
|
|
41
|
+
url = self.get_attr(element, 'url')
|
|
42
|
+
|
|
43
|
+
if not name:
|
|
44
|
+
raise ParserError("WebSocket requires 'name' attribute")
|
|
45
|
+
if not url:
|
|
46
|
+
raise ParserError("WebSocket requires 'url' attribute")
|
|
47
|
+
|
|
48
|
+
ws_node = WebSocketNode(
|
|
49
|
+
name=name,
|
|
50
|
+
url=url,
|
|
51
|
+
auto_connect=self.get_bool_attr(element, 'autoConnect', True),
|
|
52
|
+
reconnect=self.get_bool_attr(element, 'reconnect', True),
|
|
53
|
+
reconnect_delay=self.get_int_attr(element, 'reconnectDelay', 1000),
|
|
54
|
+
max_reconnects=self.get_int_attr(element, 'maxReconnects', 10),
|
|
55
|
+
heartbeat=self.get_int_attr(element, 'heartbeat', 30000),
|
|
56
|
+
protocols=self.get_attr(element, 'protocols', '')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Parse event handlers
|
|
60
|
+
for child in element:
|
|
61
|
+
child_type = self.get_element_name(child)
|
|
62
|
+
|
|
63
|
+
if child_type.startswith('on-'):
|
|
64
|
+
event = child_type[3:] # Remove 'on-' prefix
|
|
65
|
+
handler = self._parse_handler(child, event)
|
|
66
|
+
ws_node.handlers.append(handler)
|
|
67
|
+
|
|
68
|
+
return ws_node
|
|
69
|
+
|
|
70
|
+
def _parse_handler(self, element: ET.Element, event: str) -> WebSocketHandlerNode:
|
|
71
|
+
"""Parse event handler."""
|
|
72
|
+
handler = WebSocketHandlerNode(event=event)
|
|
73
|
+
|
|
74
|
+
for child in element:
|
|
75
|
+
statement = self.parse_statement(child)
|
|
76
|
+
if statement:
|
|
77
|
+
handler.body.append(statement)
|
|
78
|
+
|
|
79
|
+
return handler
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class WebSocketSendParser(BaseTagParser):
|
|
83
|
+
"""Parser for q:websocket-send statements."""
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def tag_names(self) -> List[str]:
|
|
87
|
+
return ['websocket-send']
|
|
88
|
+
|
|
89
|
+
def parse(self, element: ET.Element) -> WebSocketSendNode:
|
|
90
|
+
connection = self.get_attr(element, 'connection')
|
|
91
|
+
message = self.get_attr(element, 'message')
|
|
92
|
+
msg_type = self.get_attr(element, 'type', 'text')
|
|
93
|
+
|
|
94
|
+
if not connection:
|
|
95
|
+
raise ParserError("websocket-send requires 'connection' attribute")
|
|
96
|
+
|
|
97
|
+
return WebSocketSendNode(
|
|
98
|
+
connection=connection,
|
|
99
|
+
message=message or '',
|
|
100
|
+
type=msg_type
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class WebSocketCloseParser(BaseTagParser):
|
|
105
|
+
"""Parser for q:websocket-close statements."""
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def tag_names(self) -> List[str]:
|
|
109
|
+
return ['websocket-close']
|
|
110
|
+
|
|
111
|
+
def parse(self, element: ET.Element) -> WebSocketCloseNode:
|
|
112
|
+
connection = self.get_attr(element, 'connection')
|
|
113
|
+
|
|
114
|
+
if not connection:
|
|
115
|
+
raise ParserError("websocket-close requires 'connection' attribute")
|
|
116
|
+
|
|
117
|
+
return WebSocketCloseNode(
|
|
118
|
+
connection=connection,
|
|
119
|
+
code=self.get_int_attr(element, 'code', 1000),
|
|
120
|
+
reason=self.get_attr(element, 'reason', '')
|
|
121
|
+
)
|