pydpm_xl 0.1.10__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.
- py_dpm/AST/ASTConstructor.py +503 -0
- py_dpm/AST/ASTObjects.py +827 -0
- py_dpm/AST/ASTTemplate.py +101 -0
- py_dpm/AST/ASTVisitor.py +13 -0
- py_dpm/AST/MLGeneration.py +588 -0
- py_dpm/AST/ModuleAnalyzer.py +79 -0
- py_dpm/AST/ModuleDependencies.py +203 -0
- py_dpm/AST/WhereClauseChecker.py +12 -0
- py_dpm/AST/__init__.py +0 -0
- py_dpm/AST/check_operands.py +302 -0
- py_dpm/DataTypes/ScalarTypes.py +324 -0
- py_dpm/DataTypes/TimeClasses.py +370 -0
- py_dpm/DataTypes/TypePromotion.py +195 -0
- py_dpm/DataTypes/__init__.py +0 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/Exceptions/exceptions.py +84 -0
- py_dpm/Exceptions/messages.py +114 -0
- py_dpm/OperationScopes/OperationScopeService.py +247 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/AggregateOperators.py +138 -0
- py_dpm/Operators/BooleanOperators.py +30 -0
- py_dpm/Operators/ClauseOperators.py +159 -0
- py_dpm/Operators/ComparisonOperators.py +69 -0
- py_dpm/Operators/ConditionalOperators.py +362 -0
- py_dpm/Operators/NumericOperators.py +101 -0
- py_dpm/Operators/Operator.py +388 -0
- py_dpm/Operators/StringOperators.py +27 -0
- py_dpm/Operators/TimeOperators.py +53 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/ValidationsGenerationUtils.py +429 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/operands_mapping.py +73 -0
- py_dpm/Utils/operator_mapping.py +89 -0
- py_dpm/Utils/tokens.py +172 -0
- py_dpm/Utils/utils.py +2 -0
- py_dpm/ValidationsGeneration/PropertiesConstraintsProcessor.py +190 -0
- py_dpm/ValidationsGeneration/Utils.py +364 -0
- py_dpm/ValidationsGeneration/VariantsProcessor.py +265 -0
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/ValidationsGeneration/auxiliary_functions.py +98 -0
- py_dpm/__init__.py +61 -0
- py_dpm/api/__init__.py +140 -0
- py_dpm/api/ast_generator.py +438 -0
- py_dpm/api/complete_ast.py +241 -0
- py_dpm/api/data_dictionary_validation.py +577 -0
- py_dpm/api/migration.py +77 -0
- py_dpm/api/semantic.py +224 -0
- py_dpm/api/syntax.py +182 -0
- py_dpm/client.py +106 -0
- py_dpm/data_handlers.py +99 -0
- py_dpm/db_utils.py +117 -0
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dist/dpm_xlLexer.interp +428 -0
- py_dpm/grammar/dist/dpm_xlLexer.py +804 -0
- py_dpm/grammar/dist/dpm_xlLexer.tokens +106 -0
- py_dpm/grammar/dist/dpm_xlParser.interp +249 -0
- py_dpm/grammar/dist/dpm_xlParser.py +5224 -0
- py_dpm/grammar/dist/dpm_xlParser.tokens +106 -0
- py_dpm/grammar/dist/dpm_xlParserListener.py +742 -0
- py_dpm/grammar/dist/dpm_xlParserVisitor.py +419 -0
- py_dpm/grammar/dist/listeners.py +10 -0
- py_dpm/grammar/dpm_xlLexer.g4 +435 -0
- py_dpm/grammar/dpm_xlParser.g4 +260 -0
- py_dpm/migration.py +282 -0
- py_dpm/models.py +2139 -0
- py_dpm/semantics/DAG/DAGAnalyzer.py +158 -0
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/SemanticAnalyzer.py +320 -0
- py_dpm/semantics/Symbols.py +223 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/utils/__init__.py +0 -0
- py_dpm/utils/ast_serialization.py +481 -0
- py_dpm/views/data_types.sql +12 -0
- py_dpm/views/datapoints.sql +65 -0
- py_dpm/views/hierarchy_operand_reference.sql +11 -0
- py_dpm/views/hierarchy_preconditions.sql +13 -0
- py_dpm/views/hierarchy_variables.sql +26 -0
- py_dpm/views/hierarchy_variables_context.sql +14 -0
- py_dpm/views/key_components.sql +18 -0
- py_dpm/views/module_from_table.sql +11 -0
- py_dpm/views/open_keys.sql +13 -0
- py_dpm/views/operation_info.sql +27 -0
- py_dpm/views/operation_list.sql +18 -0
- py_dpm/views/operations_versions_from_module_version.sql +30 -0
- py_dpm/views/precondition_info.sql +17 -0
- py_dpm/views/report_type_operand_reference_info.sql +18 -0
- py_dpm/views/subcategory_info.sql +17 -0
- py_dpm/views/table_info.sql +19 -0
- pydpm_xl-0.1.10.dist-info/LICENSE +674 -0
- pydpm_xl-0.1.10.dist-info/METADATA +50 -0
- pydpm_xl-0.1.10.dist-info/RECORD +94 -0
- pydpm_xl-0.1.10.dist-info/WHEEL +4 -0
- pydpm_xl-0.1.10.dist-info/entry_points.txt +3 -0
py_dpm/api/semantic.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
# Import directly to avoid circular imports
|
|
5
|
+
from antlr4 import CommonTokenStream, InputStream
|
|
6
|
+
from py_dpm.grammar.dist.dpm_xlLexer import dpm_xlLexer
|
|
7
|
+
from py_dpm.grammar.dist.dpm_xlParser import dpm_xlParser
|
|
8
|
+
from py_dpm.grammar.dist.listeners import DPMErrorListener
|
|
9
|
+
from py_dpm.AST.ASTConstructor import ASTVisitor
|
|
10
|
+
from py_dpm.AST.check_operands import OperandsChecking
|
|
11
|
+
from py_dpm.semantics import SemanticAnalyzer
|
|
12
|
+
from py_dpm.db_utils import get_session, get_engine
|
|
13
|
+
from py_dpm.Exceptions.exceptions import SemanticError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SemanticValidationResult:
|
|
18
|
+
"""
|
|
19
|
+
Result of semantic validation.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
is_valid (bool): Whether the semantic validation passed
|
|
23
|
+
error_message (Optional[str]): Error message if validation failed
|
|
24
|
+
error_code (Optional[str]): Error code if validation failed
|
|
25
|
+
expression (str): The original expression that was validated
|
|
26
|
+
validation_type (str): Type of validation performed
|
|
27
|
+
results (Optional[Any]): Additional results from semantic analysis
|
|
28
|
+
"""
|
|
29
|
+
is_valid: bool
|
|
30
|
+
error_message: Optional[str]
|
|
31
|
+
error_code: Optional[str]
|
|
32
|
+
expression: str
|
|
33
|
+
validation_type: str
|
|
34
|
+
results: Optional[Any] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SemanticAPI:
|
|
38
|
+
"""
|
|
39
|
+
API for DPM-XL semantic validation and analysis.
|
|
40
|
+
|
|
41
|
+
This class provides methods to perform semantic analysis on DPM-XL expressions,
|
|
42
|
+
including operand checking, data type validation, and structure validation.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
"""Initialize the Semantic API."""
|
|
47
|
+
get_engine()
|
|
48
|
+
self.session = get_session()
|
|
49
|
+
self.error_listener = DPMErrorListener()
|
|
50
|
+
self.visitor = ASTVisitor()
|
|
51
|
+
|
|
52
|
+
def validate_expression(self, expression: str) -> SemanticValidationResult:
|
|
53
|
+
"""
|
|
54
|
+
Perform semantic validation on a DPM-XL expression.
|
|
55
|
+
|
|
56
|
+
This includes syntax validation, operands checking, data type validation,
|
|
57
|
+
and structure validation.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
expression (str): The DPM-XL expression to validate
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
SemanticValidationResult: Result containing validation status and details
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> from pydpm.api import SemanticAPI
|
|
67
|
+
>>> semantic = SemanticAPI()
|
|
68
|
+
>>> result = semantic.validate_expression("{tC_01.00, r0100, c0010} + {tC_01.00, r0200, c0010}")
|
|
69
|
+
>>> print(result.is_valid)
|
|
70
|
+
True
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
# Parse expression to AST
|
|
74
|
+
input_stream = InputStream(expression)
|
|
75
|
+
lexer = dpm_xlLexer(input_stream)
|
|
76
|
+
lexer._listeners = [self.error_listener]
|
|
77
|
+
token_stream = CommonTokenStream(lexer)
|
|
78
|
+
|
|
79
|
+
parser = dpm_xlParser(token_stream)
|
|
80
|
+
parser._listeners = [self.error_listener]
|
|
81
|
+
parse_tree = parser.start()
|
|
82
|
+
|
|
83
|
+
if parser._syntaxErrors > 0:
|
|
84
|
+
return SemanticValidationResult(
|
|
85
|
+
is_valid=False,
|
|
86
|
+
error_message="Syntax errors detected",
|
|
87
|
+
error_code="SYNTAX_ERROR",
|
|
88
|
+
expression=expression,
|
|
89
|
+
validation_type="SEMANTIC"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Generate AST
|
|
93
|
+
ast = self.visitor.visit(parse_tree)
|
|
94
|
+
|
|
95
|
+
# Perform semantic analysis
|
|
96
|
+
oc = OperandsChecking(session=self.session, expression=expression, ast=ast, release_id=None)
|
|
97
|
+
semanticAnalysis = SemanticAnalyzer.InputAnalyzer(expression)
|
|
98
|
+
|
|
99
|
+
semanticAnalysis.data = oc.data
|
|
100
|
+
semanticAnalysis.key_components = oc.key_components
|
|
101
|
+
semanticAnalysis.open_keys = oc.open_keys
|
|
102
|
+
semanticAnalysis.preconditions = oc.preconditions
|
|
103
|
+
|
|
104
|
+
results = semanticAnalysis.visit(ast)
|
|
105
|
+
|
|
106
|
+
return SemanticValidationResult(
|
|
107
|
+
is_valid=True,
|
|
108
|
+
error_message=None,
|
|
109
|
+
error_code=None,
|
|
110
|
+
expression=expression,
|
|
111
|
+
validation_type="SEMANTIC",
|
|
112
|
+
results=results
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
except SemanticError as e:
|
|
116
|
+
return SemanticValidationResult(
|
|
117
|
+
is_valid=False,
|
|
118
|
+
error_message=str(e),
|
|
119
|
+
error_code=getattr(e, 'code', None),
|
|
120
|
+
expression=expression,
|
|
121
|
+
validation_type="SEMANTIC"
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return SemanticValidationResult(
|
|
125
|
+
is_valid=False,
|
|
126
|
+
error_message=str(e),
|
|
127
|
+
error_code="UNKNOWN",
|
|
128
|
+
expression=expression,
|
|
129
|
+
validation_type="SEMANTIC"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def analyze_expression(self, expression: str) -> Dict[str, Any]:
|
|
133
|
+
"""
|
|
134
|
+
Perform detailed semantic analysis on a DPM-XL expression.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
expression (str): The DPM-XL expression to analyze
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dict[str, Any]: Detailed analysis results
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
Exception: If analysis fails
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> from pydpm.api import SemanticAPI
|
|
147
|
+
>>> semantic = SemanticAPI()
|
|
148
|
+
>>> analysis = semantic.analyze_expression("{tC_01.00, r0100, c0010}")
|
|
149
|
+
"""
|
|
150
|
+
result = self.validate_expression(expression)
|
|
151
|
+
|
|
152
|
+
if not result.is_valid:
|
|
153
|
+
raise Exception(f"Semantic analysis failed: {result.error_message}")
|
|
154
|
+
|
|
155
|
+
# Extract additional analysis information
|
|
156
|
+
analysis = {
|
|
157
|
+
'expression': expression,
|
|
158
|
+
'is_valid': True,
|
|
159
|
+
'results': result.results,
|
|
160
|
+
'data_types': getattr(result.results, 'type', None) if result.results else None,
|
|
161
|
+
'components': getattr(result.results, 'components', None) if result.results else None
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return analysis
|
|
165
|
+
|
|
166
|
+
def is_valid_semantics(self, expression: str) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Quick check if expression has valid semantics.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
expression (str): The DPM-XL expression to check
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
bool: True if semantics are valid, False otherwise
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> from pydpm.api import SemanticAPI
|
|
178
|
+
>>> semantic = SemanticAPI()
|
|
179
|
+
>>> is_valid = semantic.is_valid_semantics("{tC_01.00, r0100, c0010}")
|
|
180
|
+
"""
|
|
181
|
+
result = self.validate_expression(expression)
|
|
182
|
+
return result.is_valid
|
|
183
|
+
|
|
184
|
+
def __del__(self):
|
|
185
|
+
"""Clean up resources."""
|
|
186
|
+
if hasattr(self, 'session'):
|
|
187
|
+
self.session.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Convenience functions for direct usage
|
|
191
|
+
def validate_expression(expression: str) -> SemanticValidationResult:
|
|
192
|
+
"""
|
|
193
|
+
Convenience function to validate DPM-XL expression semantics.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
expression (str): The DPM-XL expression to validate
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
SemanticValidationResult: Result containing validation status and details
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
>>> from pydpm.api.semantic import validate_expression
|
|
203
|
+
>>> result = validate_expression("{tC_01.00, r0100, c0010}")
|
|
204
|
+
"""
|
|
205
|
+
api = SemanticAPI()
|
|
206
|
+
return api.validate_expression(expression)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def is_valid_semantics(expression: str) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Convenience function to check if expression has valid semantics.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
expression (str): The DPM-XL expression to check
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
bool: True if semantics are valid, False otherwise
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> from pydpm.api.semantic import is_valid_semantics
|
|
221
|
+
>>> is_valid = is_valid_semantics("{tC_01.00, r0100, c0010}")
|
|
222
|
+
"""
|
|
223
|
+
api = SemanticAPI()
|
|
224
|
+
return api.is_valid_semantics(expression)
|
py_dpm/api/syntax.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
# Import directly to avoid circular imports
|
|
5
|
+
from antlr4 import CommonTokenStream, InputStream
|
|
6
|
+
from py_dpm.grammar.dist.dpm_xlLexer import dpm_xlLexer
|
|
7
|
+
from py_dpm.grammar.dist.dpm_xlParser import dpm_xlParser
|
|
8
|
+
from py_dpm.grammar.dist.listeners import DPMErrorListener
|
|
9
|
+
from py_dpm.AST.ASTConstructor import ASTVisitor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SyntaxValidationResult:
|
|
14
|
+
"""
|
|
15
|
+
Result of syntax validation.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
is_valid (bool): Whether the syntax is valid
|
|
19
|
+
error_message (Optional[str]): Error message if validation failed
|
|
20
|
+
expression (str): The original expression that was validated
|
|
21
|
+
"""
|
|
22
|
+
is_valid: bool
|
|
23
|
+
error_message: Optional[str]
|
|
24
|
+
expression: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SyntaxAPI:
|
|
28
|
+
"""
|
|
29
|
+
API for DPM-XL syntax validation and analysis.
|
|
30
|
+
|
|
31
|
+
This class provides methods to validate DPM-XL expression syntax.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
"""Initialize the Syntax API."""
|
|
36
|
+
self.error_listener = DPMErrorListener()
|
|
37
|
+
self.visitor = ASTVisitor()
|
|
38
|
+
|
|
39
|
+
def validate_expression(self, expression: str) -> SyntaxValidationResult:
|
|
40
|
+
"""
|
|
41
|
+
Validate the syntax of a DPM-XL expression.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
expression (str): The DPM-XL expression to validate
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
SyntaxValidationResult: Result containing validation status and details
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> from pydpm.api import SyntaxAPI
|
|
51
|
+
>>> syntax = SyntaxAPI()
|
|
52
|
+
>>> result = syntax.validate_expression("{tC_01.00, r0100, c0010}")
|
|
53
|
+
>>> print(result.is_valid)
|
|
54
|
+
True
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
# Use direct ANTLR validation
|
|
58
|
+
input_stream = InputStream(expression)
|
|
59
|
+
lexer = dpm_xlLexer(input_stream)
|
|
60
|
+
lexer._listeners = [self.error_listener]
|
|
61
|
+
token_stream = CommonTokenStream(lexer)
|
|
62
|
+
|
|
63
|
+
parser = dpm_xlParser(token_stream)
|
|
64
|
+
parser._listeners = [self.error_listener]
|
|
65
|
+
parse_tree = parser.start()
|
|
66
|
+
|
|
67
|
+
if parser._syntaxErrors == 0:
|
|
68
|
+
return SyntaxValidationResult(
|
|
69
|
+
is_valid=True,
|
|
70
|
+
error_message=None,
|
|
71
|
+
expression=expression
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
return SyntaxValidationResult(
|
|
75
|
+
is_valid=False,
|
|
76
|
+
error_message="Syntax errors detected",
|
|
77
|
+
expression=expression
|
|
78
|
+
)
|
|
79
|
+
except SyntaxError as e:
|
|
80
|
+
return SyntaxValidationResult(
|
|
81
|
+
is_valid=False,
|
|
82
|
+
error_message=str(e),
|
|
83
|
+
expression=expression
|
|
84
|
+
)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return SyntaxValidationResult(
|
|
87
|
+
is_valid=False,
|
|
88
|
+
error_message=f"Unexpected error: {str(e)}",
|
|
89
|
+
expression=expression
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def parse_expression(self, expression: str):
|
|
93
|
+
"""
|
|
94
|
+
Parse a DPM-XL expression and return the AST.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
expression (str): The DPM-XL expression to parse
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
AST: The Abstract Syntax Tree for the expression
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
Exception: If parsing fails
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> from pydpm.api import SyntaxAPI
|
|
107
|
+
>>> syntax = SyntaxAPI()
|
|
108
|
+
>>> ast = syntax.parse_expression("{tC_01.00, r0100, c0010}")
|
|
109
|
+
"""
|
|
110
|
+
# Parse directly using ANTLR and AST visitor
|
|
111
|
+
try:
|
|
112
|
+
input_stream = InputStream(expression)
|
|
113
|
+
lexer = dpm_xlLexer(input_stream)
|
|
114
|
+
token_stream = CommonTokenStream(lexer)
|
|
115
|
+
parser = dpm_xlParser(token_stream)
|
|
116
|
+
parse_tree = parser.start()
|
|
117
|
+
|
|
118
|
+
# Create AST visitor and visit the parse tree
|
|
119
|
+
ast = self.visitor.visit(parse_tree)
|
|
120
|
+
return ast
|
|
121
|
+
except Exception as e:
|
|
122
|
+
raise Exception(f"Failed to parse DPM-XL expression '{expression}': {str(e)}")
|
|
123
|
+
|
|
124
|
+
def is_valid_syntax(self, expression: str) -> bool:
|
|
125
|
+
"""
|
|
126
|
+
Quick check if expression has valid syntax.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
expression (str): The DPM-XL expression to check
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
bool: True if syntax is valid, False otherwise
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> from pydpm.api import SyntaxAPI
|
|
136
|
+
>>> syntax = SyntaxAPI()
|
|
137
|
+
>>> is_valid = syntax.is_valid_syntax("{tC_01.00, r0100, c0010}")
|
|
138
|
+
"""
|
|
139
|
+
# Use existing validate_expression method
|
|
140
|
+
result = self.validate_expression(expression)
|
|
141
|
+
return result.is_valid
|
|
142
|
+
|
|
143
|
+
def __del__(self):
|
|
144
|
+
"""Clean up resources."""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Convenience functions for direct usage
|
|
149
|
+
def validate_expression(expression: str) -> SyntaxValidationResult:
|
|
150
|
+
"""
|
|
151
|
+
Convenience function to validate DPM-XL expression syntax.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
expression (str): The DPM-XL expression to validate
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
SyntaxValidationResult: Result containing validation status and details
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
>>> from pydpm.api.syntax import validate_expression
|
|
161
|
+
>>> result = validate_expression("{tC_01.00, r0100, c0010}")
|
|
162
|
+
"""
|
|
163
|
+
api = SyntaxAPI()
|
|
164
|
+
return api.validate_expression(expression)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_valid_syntax(expression: str) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
Convenience function to check if expression has valid syntax.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
expression (str): The DPM-XL expression to check
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
bool: True if syntax is valid, False otherwise
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
>>> from pydpm.api.syntax import is_valid_syntax
|
|
179
|
+
>>> is_valid = is_valid_syntax("{tC_01.00, r0100, c0010}")
|
|
180
|
+
"""
|
|
181
|
+
api = SyntaxAPI()
|
|
182
|
+
return api.is_valid_syntax(expression)
|
py_dpm/client.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from py_dpm.api import API
|
|
9
|
+
from py_dpm.migration import run_migration
|
|
10
|
+
from py_dpm.Utils.tokens import CODE, ERROR, ERROR_CODE, EXPRESSION, OP_VERSION_ID, STATUS, \
|
|
11
|
+
STATUS_CORRECT, STATUS_UNKNOWN, VALIDATIONS, \
|
|
12
|
+
VALIDATION_TYPE, \
|
|
13
|
+
VARIABLES
|
|
14
|
+
from py_dpm.Exceptions.exceptions import SemanticError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
@click.version_option()
|
|
21
|
+
def main():
|
|
22
|
+
"""pyDPM CLI - A command line interface for pyDPM"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@main.command()
|
|
26
|
+
@click.argument('access_file', type=click.Path(exists=True))
|
|
27
|
+
def migrate_access(access_file: str):
|
|
28
|
+
"""
|
|
29
|
+
Migrates data from an Access database to a SQLite database.
|
|
30
|
+
|
|
31
|
+
ACCESS_FILE: Path to the Access database file (.mdb or .accdb).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
sqlite_db = os.getenv("SQLITE_DB_PATH", "database.db")
|
|
35
|
+
console.print(f"Starting migration from '{access_file}' to '{sqlite_db}'...")
|
|
36
|
+
try:
|
|
37
|
+
run_migration(access_file, sqlite_db)
|
|
38
|
+
console.print("Migration completed successfully.", style="bold green")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
console.print(f"An error occurred during migration: {e}", style="bold red")
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@main.command()
|
|
45
|
+
@click.argument('expression', type=str)
|
|
46
|
+
def semantic(expression: str):
|
|
47
|
+
"""
|
|
48
|
+
Semantically analyses the input expression by applying the syntax validation, the operands checking, the data type
|
|
49
|
+
validation and the structure validation
|
|
50
|
+
:param expression: Expression to be analysed
|
|
51
|
+
:param release_id: ID of the release used. If None, gathers the live release
|
|
52
|
+
Used only in DPM-ML generation
|
|
53
|
+
:return if Return_data is False, any Symbol, else data extracted from DB based on operands cell references
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
error_code = ""
|
|
57
|
+
validation_type = STATUS_UNKNOWN
|
|
58
|
+
|
|
59
|
+
api = API()
|
|
60
|
+
try:
|
|
61
|
+
validation_type = "OTHER"
|
|
62
|
+
api.semantic_validation(expression)
|
|
63
|
+
status = 200
|
|
64
|
+
message_error = ''
|
|
65
|
+
except Exception as error:
|
|
66
|
+
status = 500
|
|
67
|
+
message_error = str(error)
|
|
68
|
+
error_code = 1
|
|
69
|
+
message_response = {
|
|
70
|
+
ERROR: message_error,
|
|
71
|
+
ERROR_CODE: error_code,
|
|
72
|
+
VALIDATION_TYPE: validation_type,
|
|
73
|
+
}
|
|
74
|
+
api.session.close()
|
|
75
|
+
if error_code and status == 500:
|
|
76
|
+
console.print(f"Semantic validation failed for expression: {expression}.", style="bold red")
|
|
77
|
+
else:
|
|
78
|
+
console.log(f"Semantic validation completed for expression: {expression}.")
|
|
79
|
+
console.print(f"Status: {status}", style="bold green")
|
|
80
|
+
return status
|
|
81
|
+
|
|
82
|
+
@main.command()
|
|
83
|
+
@click.argument('expression', type=str)
|
|
84
|
+
def syntax(expression: str):
|
|
85
|
+
"""Perform syntactic analysis on a DPM expression."""
|
|
86
|
+
|
|
87
|
+
status = 0
|
|
88
|
+
api = API()
|
|
89
|
+
try:
|
|
90
|
+
api.syntax_validation(expression)
|
|
91
|
+
message_formatted = Text("Syntax OK", style="bold green")
|
|
92
|
+
except SyntaxError as e:
|
|
93
|
+
message = str(e)
|
|
94
|
+
message_formatted = Text(f"Syntax Error: {message}", style="bold red")
|
|
95
|
+
status = 0
|
|
96
|
+
except Exception as e:
|
|
97
|
+
message = str(e)
|
|
98
|
+
message_formatted = Text(f"Unexpected Error: {message}", style="bold red")
|
|
99
|
+
status = 1
|
|
100
|
+
|
|
101
|
+
console.print(message_formatted)
|
|
102
|
+
|
|
103
|
+
return status
|
|
104
|
+
|
|
105
|
+
if __name__ == '__main__':
|
|
106
|
+
main()
|
py_dpm/data_handlers.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from py_dpm.Exceptions.exceptions import SemanticError
|
|
4
|
+
from py_dpm.Utils.tokens import *
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def filter_data_by_cell_element(series, cell_elements, element_name, table_code):
|
|
8
|
+
"""
|
|
9
|
+
Filter data by cell elements
|
|
10
|
+
:param series: data to be filtered
|
|
11
|
+
:param cell_elements: rows, columns or sheets using to filter data
|
|
12
|
+
:param element_name: name of cell elements using to filter data
|
|
13
|
+
:return: filtered data
|
|
14
|
+
"""
|
|
15
|
+
if len(cell_elements) == 1 and '-' not in cell_elements[0]:
|
|
16
|
+
series = series[series[element_name] == cell_elements[0]]
|
|
17
|
+
elif len(cell_elements) == 1 and '-' in cell_elements[0]:
|
|
18
|
+
limits = cell_elements[0].split('-')
|
|
19
|
+
series = series[series[element_name].between(limits[0], limits[1])]
|
|
20
|
+
else:
|
|
21
|
+
range_control = any(['-' in x for x in cell_elements])
|
|
22
|
+
if range_control: # Range in cell elements, we must separate them
|
|
23
|
+
data_range = []
|
|
24
|
+
data_single = []
|
|
25
|
+
for x in cell_elements:
|
|
26
|
+
if '-' in x:
|
|
27
|
+
limits = x.split('-')
|
|
28
|
+
data_range += list(series[series[element_name].between(limits[0], limits[1])][element_name].unique())
|
|
29
|
+
else:
|
|
30
|
+
data_single.append(x)
|
|
31
|
+
cell_elements = sorted(list(set(data_range + data_single)))
|
|
32
|
+
series = series[series[element_name].isin(cell_elements)]
|
|
33
|
+
cells_not_found = [x for x in cell_elements if x not in list(series[element_name].unique())]
|
|
34
|
+
|
|
35
|
+
if cells_not_found:
|
|
36
|
+
header = "rows" if element_name == ROW_CODE else "columns" if element_name == COLUMN_CODE else "sheets"
|
|
37
|
+
cell_elements = ", ".join([f"{header[0]}{x}" for x in cells_not_found]) if cells_not_found else None
|
|
38
|
+
op_pos = [table_code, cell_elements]
|
|
39
|
+
cell_exp = ", ".join(x for x in op_pos if x is not None)
|
|
40
|
+
raise SemanticError("1-2", cell_expression=cell_exp)
|
|
41
|
+
return series
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def filter_all_data(data, table_code, rows, cols, sheets):
|
|
45
|
+
df = data[data["table_code"] == table_code].reset_index(drop=True)
|
|
46
|
+
if rows and rows[0] != '*':
|
|
47
|
+
df = filter_data_by_cell_element(df, rows, ROW_CODE, table_code)
|
|
48
|
+
if cols and cols[0] != '*':
|
|
49
|
+
df = filter_data_by_cell_element(df, cols, COLUMN_CODE, table_code)
|
|
50
|
+
if sheets and sheets[0] != '*':
|
|
51
|
+
df = filter_data_by_cell_element(df, sheets, SHEET_CODE, table_code)
|
|
52
|
+
df = df.reset_index(drop=True)
|
|
53
|
+
return df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def generate_xyz(data: pd.DataFrame):
|
|
57
|
+
|
|
58
|
+
for letter in [INDEX_X, INDEX_Y, INDEX_Z]:
|
|
59
|
+
data[letter] = None
|
|
60
|
+
|
|
61
|
+
number_of_rows = len(list(data[ROW_CODE].unique()))
|
|
62
|
+
number_of_columns = len(list(data[COLUMN_CODE].unique()))
|
|
63
|
+
number_of_sheets = len(list(data[SHEET_CODE].unique()))
|
|
64
|
+
group = []
|
|
65
|
+
|
|
66
|
+
if number_of_rows > 1:
|
|
67
|
+
data.sort_values(by=[ROW_CODE], inplace=True)
|
|
68
|
+
data[INDEX_X] = data[ROW_CODE].rank(method='dense').astype(int)
|
|
69
|
+
group.append(ROW_CODE)
|
|
70
|
+
|
|
71
|
+
if number_of_columns > 1:
|
|
72
|
+
data.sort_values(by=[COLUMN_CODE], inplace=True)
|
|
73
|
+
if data[INDEX_X].isnull().all():
|
|
74
|
+
data[INDEX_Y] = data[COLUMN_CODE].rank(method='dense').astype(int)
|
|
75
|
+
else:
|
|
76
|
+
data_groups = list(data.groupby(ROW_CODE))
|
|
77
|
+
for _, group_data in data_groups:
|
|
78
|
+
group_data.sort_values(by=[COLUMN_CODE], inplace=True)
|
|
79
|
+
group_data[INDEX_Y] = group_data[COLUMN_CODE].rank(method='dense').astype(int)
|
|
80
|
+
# Add to data[INDEX_Y] the values of group_data[INDEX_Y]
|
|
81
|
+
data.loc[group_data.index, INDEX_Y] = group_data[INDEX_Y]
|
|
82
|
+
group.append(COLUMN_CODE)
|
|
83
|
+
|
|
84
|
+
if number_of_sheets > 1:
|
|
85
|
+
data.sort_values(by=[SHEET_CODE], inplace=True)
|
|
86
|
+
if len(group) == 0:
|
|
87
|
+
data[INDEX_Z] = data[SHEET_CODE].rank(method='dense').astype(int)
|
|
88
|
+
else:
|
|
89
|
+
data_groups = list(data.groupby(group))
|
|
90
|
+
for _, group_data in data_groups:
|
|
91
|
+
group_data.sort_values(by=[SHEET_CODE], inplace=True)
|
|
92
|
+
group_data[INDEX_Z] = group_data[SHEET_CODE].rank(method='dense').astype(int)
|
|
93
|
+
data.loc[group_data.index, INDEX_Z] = group_data[INDEX_Z]
|
|
94
|
+
group.append(SHEET_CODE)
|
|
95
|
+
if len(group) > 0:
|
|
96
|
+
data.sort_values(by=group, inplace=True)
|
|
97
|
+
data.drop_duplicates(keep="first", inplace=True)
|
|
98
|
+
list_xyz = data.to_dict(orient='records')
|
|
99
|
+
return list_xyz
|