pydpm_xl 0.1.39rc32__py3-none-any.whl → 0.2.1__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/__init__.py +1 -1
- py_dpm/api/__init__.py +58 -189
- py_dpm/api/dpm/__init__.py +20 -0
- py_dpm/api/{data_dictionary.py → dpm/data_dictionary.py} +903 -984
- py_dpm/api/dpm/explorer.py +236 -0
- py_dpm/api/dpm/hierarchical_queries.py +142 -0
- py_dpm/api/{migration.py → dpm/migration.py} +16 -19
- py_dpm/api/{operation_scopes.py → dpm/operation_scopes.py} +319 -267
- py_dpm/api/dpm_xl/__init__.py +25 -0
- py_dpm/api/{ast_generator.py → dpm_xl/ast_generator.py} +3 -3
- py_dpm/api/{complete_ast.py → dpm_xl/complete_ast.py} +186 -284
- py_dpm/api/dpm_xl/semantic.py +358 -0
- py_dpm/api/{syntax.py → dpm_xl/syntax.py} +6 -5
- py_dpm/api/explorer.py +4 -0
- py_dpm/api/semantic.py +30 -306
- py_dpm/cli/__init__.py +9 -0
- py_dpm/{client.py → cli/main.py} +12 -10
- py_dpm/dpm/__init__.py +11 -0
- py_dpm/{models.py → dpm/models.py} +112 -88
- py_dpm/dpm/queries/base.py +100 -0
- py_dpm/dpm/queries/basic_objects.py +33 -0
- py_dpm/dpm/queries/explorer_queries.py +352 -0
- py_dpm/dpm/queries/filters.py +139 -0
- py_dpm/dpm/queries/glossary.py +45 -0
- py_dpm/dpm/queries/hierarchical_queries.py +838 -0
- py_dpm/dpm/queries/tables.py +133 -0
- py_dpm/dpm/utils.py +356 -0
- py_dpm/dpm_xl/__init__.py +8 -0
- py_dpm/dpm_xl/ast/__init__.py +14 -0
- py_dpm/{AST/ASTConstructor.py → dpm_xl/ast/constructor.py} +6 -6
- py_dpm/{AST/MLGeneration.py → dpm_xl/ast/ml_generation.py} +137 -87
- py_dpm/{AST/ModuleAnalyzer.py → dpm_xl/ast/module_analyzer.py} +7 -7
- py_dpm/{AST/ModuleDependencies.py → dpm_xl/ast/module_dependencies.py} +56 -41
- py_dpm/{AST/ASTObjects.py → dpm_xl/ast/nodes.py} +1 -1
- py_dpm/{AST/check_operands.py → dpm_xl/ast/operands.py} +16 -13
- py_dpm/{AST/ASTTemplate.py → dpm_xl/ast/template.py} +2 -2
- py_dpm/{AST/WhereClauseChecker.py → dpm_xl/ast/where_clause.py} +2 -2
- py_dpm/dpm_xl/grammar/__init__.py +18 -0
- py_dpm/dpm_xl/operators/__init__.py +19 -0
- py_dpm/{Operators/AggregateOperators.py → dpm_xl/operators/aggregate.py} +7 -7
- py_dpm/{Operators/NumericOperators.py → dpm_xl/operators/arithmetic.py} +6 -6
- py_dpm/{Operators/Operator.py → dpm_xl/operators/base.py} +5 -5
- py_dpm/{Operators/BooleanOperators.py → dpm_xl/operators/boolean.py} +5 -5
- py_dpm/{Operators/ClauseOperators.py → dpm_xl/operators/clause.py} +8 -8
- py_dpm/{Operators/ComparisonOperators.py → dpm_xl/operators/comparison.py} +5 -5
- py_dpm/{Operators/ConditionalOperators.py → dpm_xl/operators/conditional.py} +7 -7
- py_dpm/{Operators/StringOperators.py → dpm_xl/operators/string.py} +5 -5
- py_dpm/{Operators/TimeOperators.py → dpm_xl/operators/time.py} +6 -6
- py_dpm/{semantics/SemanticAnalyzer.py → dpm_xl/semantic_analyzer.py} +168 -68
- py_dpm/{semantics/Symbols.py → dpm_xl/symbols.py} +3 -3
- py_dpm/dpm_xl/types/__init__.py +13 -0
- py_dpm/{DataTypes/TypePromotion.py → dpm_xl/types/promotion.py} +2 -2
- py_dpm/{DataTypes/ScalarTypes.py → dpm_xl/types/scalar.py} +2 -2
- py_dpm/dpm_xl/utils/__init__.py +14 -0
- py_dpm/{data_handlers.py → dpm_xl/utils/data_handlers.py} +2 -2
- py_dpm/{Utils → dpm_xl/utils}/operands_mapping.py +1 -1
- py_dpm/{Utils → dpm_xl/utils}/operator_mapping.py +8 -8
- py_dpm/{OperationScopes/OperationScopeService.py → dpm_xl/utils/scopes_calculator.py} +148 -58
- py_dpm/{Utils/ast_serialization.py → dpm_xl/utils/serialization.py} +3 -4
- py_dpm/dpm_xl/validation/__init__.py +12 -0
- py_dpm/{Utils/ValidationsGenerationUtils.py → dpm_xl/validation/generation_utils.py} +2 -3
- py_dpm/{ValidationsGeneration/PropertiesConstraintsProcessor.py → dpm_xl/validation/property_constraints.py} +56 -21
- py_dpm/{ValidationsGeneration/auxiliary_functions.py → dpm_xl/validation/utils.py} +2 -2
- py_dpm/{ValidationsGeneration/VariantsProcessor.py → dpm_xl/validation/variants.py} +149 -55
- py_dpm/exceptions/__init__.py +23 -0
- py_dpm/{Exceptions → exceptions}/exceptions.py +7 -2
- pydpm_xl-0.2.1.dist-info/METADATA +278 -0
- pydpm_xl-0.2.1.dist-info/RECORD +88 -0
- pydpm_xl-0.2.1.dist-info/entry_points.txt +2 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/utils.py +0 -2
- py_dpm/ValidationsGeneration/Utils.py +0 -364
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/api/data_dictionary_validation.py +0 -614
- py_dpm/db_utils.py +0 -221
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dpm_xlLexer.g4 +0 -437
- py_dpm/grammar/dpm_xlParser.g4 +0 -263
- py_dpm/semantics/DAG/DAGAnalyzer.py +0 -158
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/views/data_types.sql +0 -12
- py_dpm/views/datapoints.sql +0 -65
- py_dpm/views/hierarchy_operand_reference.sql +0 -11
- py_dpm/views/hierarchy_preconditions.sql +0 -13
- py_dpm/views/hierarchy_variables.sql +0 -26
- py_dpm/views/hierarchy_variables_context.sql +0 -14
- py_dpm/views/key_components.sql +0 -18
- py_dpm/views/module_from_table.sql +0 -11
- py_dpm/views/open_keys.sql +0 -13
- py_dpm/views/operation_info.sql +0 -27
- py_dpm/views/operation_list.sql +0 -18
- py_dpm/views/operations_versions_from_module_version.sql +0 -30
- py_dpm/views/precondition_info.sql +0 -17
- py_dpm/views/report_type_operand_reference_info.sql +0 -18
- py_dpm/views/subcategory_info.sql +0 -17
- py_dpm/views/table_info.sql +0 -19
- pydpm_xl-0.1.39rc32.dist-info/METADATA +0 -53
- pydpm_xl-0.1.39rc32.dist-info/RECORD +0 -96
- pydpm_xl-0.1.39rc32.dist-info/entry_points.txt +0 -2
- /py_dpm/{AST → cli/commands}/__init__.py +0 -0
- /py_dpm/{migration.py → dpm/migration.py} +0 -0
- /py_dpm/{AST/ASTVisitor.py → dpm_xl/ast/visitor.py} +0 -0
- /py_dpm/{DataTypes → dpm_xl/grammar/generated}/__init__.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserListener.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserVisitor.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/listeners.py +0 -0
- /py_dpm/{DataTypes/TimeClasses.py → dpm_xl/types/time.py} +0 -0
- /py_dpm/{Utils → dpm_xl/utils}/tokens.py +0 -0
- /py_dpm/{Exceptions → exceptions}/messages.py +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/WHEEL +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -3,20 +3,33 @@ import re
|
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
from py_dpm.
|
|
7
|
-
from py_dpm.
|
|
8
|
-
from py_dpm.
|
|
9
|
-
from py_dpm.models import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
from py_dpm.dpm_xl.ast.nodes import VarID, WithExpression
|
|
7
|
+
from py_dpm.dpm_xl.ast.template import ASTTemplate
|
|
8
|
+
from py_dpm.exceptions import exceptions
|
|
9
|
+
from py_dpm.dpm.models import (
|
|
10
|
+
ModuleVersionComposition,
|
|
11
|
+
TableGroup,
|
|
12
|
+
TableGroupComposition,
|
|
13
|
+
TableVersion,
|
|
14
|
+
ViewDatapoints,
|
|
15
|
+
)
|
|
16
|
+
from py_dpm.dpm_xl.utils.tokens import (
|
|
17
|
+
EXPRESSION,
|
|
18
|
+
STATUS,
|
|
19
|
+
STATUS_CORRECT,
|
|
20
|
+
STATUS_INCOMPLETE,
|
|
21
|
+
STATUS_INCORRECT,
|
|
22
|
+
VALIDATION_CODE,
|
|
23
|
+
)
|
|
24
|
+
from py_dpm.dpm_xl.utils.data_handlers import filter_all_data
|
|
25
|
+
|
|
26
|
+
cell_components = ["table", "rows", "cols", "sheets"]
|
|
27
|
+
TABLE_ID = "TableID"
|
|
28
|
+
MODULE_VID = "ModuleVID"
|
|
29
|
+
TABLE_CODE = "Code"
|
|
30
|
+
GROUP = "group"
|
|
31
|
+
MODULE_VERSION_ID = "module_version_id"
|
|
32
|
+
MODULE_CODE = "module_code"
|
|
20
33
|
|
|
21
34
|
|
|
22
35
|
class VariantsProcessorChecker(ASTTemplate):
|
|
@@ -73,11 +86,15 @@ class VariantsProcessor(ASTTemplate):
|
|
|
73
86
|
:return: True if table has cells, False otherwise.
|
|
74
87
|
"""
|
|
75
88
|
if table_code not in self.table_data:
|
|
76
|
-
datapoints = ViewDatapoints.get_table_data(
|
|
89
|
+
datapoints = ViewDatapoints.get_table_data(
|
|
90
|
+
session=self.session, table=table_code, release_id=self.release_id
|
|
91
|
+
)
|
|
77
92
|
self.table_data[table_code] = datapoints
|
|
78
93
|
else:
|
|
79
94
|
datapoints = self.table_data[table_code]
|
|
80
|
-
data = filter_all_data(
|
|
95
|
+
data = filter_all_data(
|
|
96
|
+
data=datapoints, table_code=table_code, rows=rows, cols=cols, sheets=sheets
|
|
97
|
+
)
|
|
81
98
|
return not data.empty
|
|
82
99
|
|
|
83
100
|
def check_table_from_module(self, node, table_module_id, is_abstract):
|
|
@@ -89,13 +106,17 @@ class VariantsProcessor(ASTTemplate):
|
|
|
89
106
|
:return: table version if table version has cells, None otherwise.
|
|
90
107
|
"""
|
|
91
108
|
|
|
92
|
-
table_versions = TableVersion.get_tables_versions_of_table_group_compositions(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
table_versions = TableVersion.get_tables_versions_of_table_group_compositions(
|
|
110
|
+
session=self.session,
|
|
111
|
+
table_id=table_module_id,
|
|
112
|
+
is_abstract=is_abstract,
|
|
113
|
+
release_id=self.release_id,
|
|
114
|
+
)
|
|
96
115
|
|
|
97
116
|
for table_version in table_versions:
|
|
98
|
-
table_with_cells = self.check_if_table_has_cells(
|
|
117
|
+
table_with_cells = self.check_if_table_has_cells(
|
|
118
|
+
table_version.Code, node.rows, node.cols, node.sheets
|
|
119
|
+
)
|
|
99
120
|
if table_with_cells:
|
|
100
121
|
return table_version
|
|
101
122
|
else:
|
|
@@ -109,10 +130,10 @@ class VariantsProcessor(ASTTemplate):
|
|
|
109
130
|
:param group_code: Group code.
|
|
110
131
|
:param table_code: Table code.
|
|
111
132
|
"""
|
|
112
|
-
groups = re.search("(" + re.escape(group_code) +
|
|
133
|
+
groups = re.search("(" + re.escape(group_code) + "[^.$]" + ")", self.expression)
|
|
113
134
|
if f"g{group_code}" in self.expression and groups:
|
|
114
135
|
group = groups.group(0)
|
|
115
|
-
suffix = table_code[-(len(table_code)-len(group_code)):]
|
|
136
|
+
suffix = table_code[-(len(table_code) - len(group_code)) :]
|
|
116
137
|
if group:
|
|
117
138
|
if not self.children_suffix:
|
|
118
139
|
self.children_suffix = suffix
|
|
@@ -120,7 +141,11 @@ class VariantsProcessor(ASTTemplate):
|
|
|
120
141
|
if self.children_suffix != suffix:
|
|
121
142
|
self.equal_children_suffix = False
|
|
122
143
|
return
|
|
123
|
-
self.expression = re.sub(
|
|
144
|
+
self.expression = re.sub(
|
|
145
|
+
re.escape(f"g{group_code}") + "[^.$]",
|
|
146
|
+
f"t{table_code}" + group[-1],
|
|
147
|
+
self.expression,
|
|
148
|
+
)
|
|
124
149
|
|
|
125
150
|
def generate_child_expressions(self):
|
|
126
151
|
"""
|
|
@@ -135,25 +160,44 @@ class VariantsProcessor(ASTTemplate):
|
|
|
135
160
|
df_tables.drop_duplicates(inplace=True)
|
|
136
161
|
|
|
137
162
|
table_ids = df_tables[TABLE_ID].tolist()
|
|
138
|
-
modules_df = ModuleVersionComposition.get_modules_from_table_ids(
|
|
139
|
-
|
|
163
|
+
modules_df = ModuleVersionComposition.get_modules_from_table_ids(
|
|
164
|
+
session=self.session, table_ids=table_ids, release_id=self.release_id
|
|
165
|
+
)
|
|
140
166
|
|
|
141
167
|
data = pd.merge(df_tables, modules_df, on=[TABLE_ID])
|
|
142
168
|
|
|
143
169
|
expression = copy.deepcopy(self.expression)
|
|
144
170
|
for module, modules_tables in data.groupby(MODULE_VID):
|
|
145
|
-
modules_tables.apply(
|
|
171
|
+
modules_tables.apply(
|
|
172
|
+
lambda x: self.generate_child_expression(x[GROUP], x[TABLE_CODE]),
|
|
173
|
+
axis=1,
|
|
174
|
+
)
|
|
146
175
|
module_code = modules_tables[MODULE_CODE].unique().tolist()[0]
|
|
147
176
|
if len(modules_tables) < len(self.table_groups_compositions):
|
|
148
177
|
final_expressions_with_errors.append(
|
|
149
|
-
{
|
|
178
|
+
{
|
|
179
|
+
EXPRESSION: self.expression,
|
|
180
|
+
MODULE_VERSION_ID: module,
|
|
181
|
+
MODULE_CODE: module_code,
|
|
182
|
+
}
|
|
183
|
+
)
|
|
150
184
|
else:
|
|
151
185
|
if self.equal_children_suffix:
|
|
152
186
|
final_expressions.append(
|
|
153
|
-
{
|
|
187
|
+
{
|
|
188
|
+
EXPRESSION: self.expression,
|
|
189
|
+
MODULE_VERSION_ID: module,
|
|
190
|
+
MODULE_CODE: module_code,
|
|
191
|
+
}
|
|
192
|
+
)
|
|
154
193
|
else:
|
|
155
194
|
final_expressions_with_errors.append(
|
|
156
|
-
{
|
|
195
|
+
{
|
|
196
|
+
EXPRESSION: self.expression,
|
|
197
|
+
MODULE_VERSION_ID: module,
|
|
198
|
+
MODULE_CODE: module_code,
|
|
199
|
+
}
|
|
200
|
+
)
|
|
157
201
|
self.equal_children_suffix = True
|
|
158
202
|
self.expression = copy.deepcopy(expression)
|
|
159
203
|
self.children_suffix = None
|
|
@@ -170,13 +214,17 @@ class VariantsProcessor(ASTTemplate):
|
|
|
170
214
|
:return: Validation with code, expression and status
|
|
171
215
|
"""
|
|
172
216
|
|
|
173
|
-
validation_code =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
217
|
+
validation_code = (
|
|
218
|
+
self.generate_child_validation_code() if status == STATUS_CORRECT else None
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
validation = {
|
|
222
|
+
VALIDATION_CODE: validation_code,
|
|
223
|
+
EXPRESSION: expression_info[EXPRESSION],
|
|
224
|
+
STATUS: status,
|
|
225
|
+
MODULE_VERSION_ID: expression_info[MODULE_VERSION_ID],
|
|
226
|
+
MODULE_CODE: expression_info[MODULE_CODE],
|
|
227
|
+
}
|
|
180
228
|
return validation
|
|
181
229
|
|
|
182
230
|
def create_validation_new_format(self, expressions_dict):
|
|
@@ -190,23 +238,49 @@ class VariantsProcessor(ASTTemplate):
|
|
|
190
238
|
correct_expressions = expressions_dict[STATUS_CORRECT]
|
|
191
239
|
incomplete_expressions = expressions_dict[STATUS_INCOMPLETE]
|
|
192
240
|
incorrect_expressions = expressions_dict[STATUS_INCORRECT]
|
|
193
|
-
unique_correct_expressions =
|
|
194
|
-
|
|
195
|
-
|
|
241
|
+
unique_correct_expressions = (
|
|
242
|
+
pd.DataFrame.from_records(correct_expressions)["expression"]
|
|
243
|
+
.unique()
|
|
244
|
+
.tolist()
|
|
245
|
+
if correct_expressions
|
|
246
|
+
else []
|
|
247
|
+
)
|
|
248
|
+
unique_incomplete_expressions = (
|
|
249
|
+
pd.DataFrame.from_records(incomplete_expressions)["expression"]
|
|
250
|
+
.unique()
|
|
251
|
+
.tolist()
|
|
252
|
+
if incomplete_expressions
|
|
253
|
+
else []
|
|
254
|
+
)
|
|
255
|
+
unique_incorrect_expressions = (
|
|
256
|
+
pd.DataFrame.from_records(incorrect_expressions)["expression"]
|
|
257
|
+
.unique()
|
|
258
|
+
.tolist()
|
|
259
|
+
if incorrect_expressions
|
|
260
|
+
else []
|
|
261
|
+
)
|
|
196
262
|
validations = []
|
|
197
263
|
if correct_expressions:
|
|
198
|
-
v = self._aux_create_validation_new_format(
|
|
264
|
+
v = self._aux_create_validation_new_format(
|
|
265
|
+
correct_expressions, unique_correct_expressions, STATUS_CORRECT
|
|
266
|
+
)
|
|
199
267
|
validations.extend(v)
|
|
200
268
|
if incomplete_expressions:
|
|
201
|
-
v = self._aux_create_validation_new_format(
|
|
269
|
+
v = self._aux_create_validation_new_format(
|
|
270
|
+
incomplete_expressions, unique_incomplete_expressions, STATUS_INCOMPLETE
|
|
271
|
+
)
|
|
202
272
|
validations.extend(v)
|
|
203
273
|
if incorrect_expressions:
|
|
204
|
-
v = self._aux_create_validation_new_format(
|
|
274
|
+
v = self._aux_create_validation_new_format(
|
|
275
|
+
incorrect_expressions, unique_incorrect_expressions, STATUS_INCORRECT
|
|
276
|
+
)
|
|
205
277
|
validations.extend(v)
|
|
206
278
|
|
|
207
279
|
return validations
|
|
208
280
|
|
|
209
|
-
def _aux_create_validation_new_format(
|
|
281
|
+
def _aux_create_validation_new_format(
|
|
282
|
+
self, expressions, unique_expressions, status
|
|
283
|
+
):
|
|
210
284
|
validations = []
|
|
211
285
|
for expr in unique_expressions:
|
|
212
286
|
aux = {}
|
|
@@ -216,12 +290,13 @@ class VariantsProcessor(ASTTemplate):
|
|
|
216
290
|
aux[VALIDATION_CODE] = self.generate_child_validation_code()
|
|
217
291
|
else:
|
|
218
292
|
aux[VALIDATION_CODE] = None
|
|
219
|
-
aux[
|
|
293
|
+
aux["scopes"] = []
|
|
220
294
|
for elto in expressions:
|
|
221
295
|
if elto[EXPRESSION] == aux[EXPRESSION]:
|
|
222
|
-
aux[
|
|
223
|
-
{
|
|
224
|
-
|
|
296
|
+
aux["scopes"].append(
|
|
297
|
+
{
|
|
298
|
+
"module_versions_ids": [elto[MODULE_VERSION_ID]],
|
|
299
|
+
"module_code": elto[MODULE_CODE],
|
|
225
300
|
}
|
|
226
301
|
)
|
|
227
302
|
validations.append(aux)
|
|
@@ -244,22 +319,41 @@ class VariantsProcessor(ASTTemplate):
|
|
|
244
319
|
def visit_VarID(self, node: VarID):
|
|
245
320
|
if self.partial_selection:
|
|
246
321
|
for attribute in cell_components:
|
|
247
|
-
if not getattr(node, attribute, False) and hasattr(
|
|
322
|
+
if not getattr(node, attribute, False) and hasattr(
|
|
323
|
+
self.partial_selection, attribute
|
|
324
|
+
):
|
|
248
325
|
setattr(node, attribute, getattr(self.partial_selection, attribute))
|
|
249
326
|
|
|
250
327
|
if node.is_table_group or self.partial_selection.is_table_group:
|
|
251
328
|
if node.table:
|
|
252
329
|
if node.table not in self.table_groups_compositions:
|
|
253
|
-
group: TableGroup = TableGroup.get_group_from_code(
|
|
330
|
+
group: TableGroup = TableGroup.get_group_from_code(
|
|
331
|
+
session=self.session, group_code=node.table
|
|
332
|
+
)
|
|
254
333
|
if not group:
|
|
255
334
|
raise exceptions.SemanticError("1-6", table_group=node.table)
|
|
256
|
-
table_groups_compositions =
|
|
257
|
-
|
|
335
|
+
table_groups_compositions = (
|
|
336
|
+
TableGroupComposition.get_from_parent_table_code(
|
|
337
|
+
code=node.table, session=self.session
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
self.table_groups_compositions[node.table] = (
|
|
341
|
+
table_groups_compositions
|
|
342
|
+
)
|
|
258
343
|
else:
|
|
259
|
-
table_groups_compositions = self.table_groups_compositions[
|
|
344
|
+
table_groups_compositions = self.table_groups_compositions[
|
|
345
|
+
node.table
|
|
346
|
+
]
|
|
260
347
|
|
|
261
348
|
for table_id, is_abstract in table_groups_compositions:
|
|
262
|
-
table_version = self.check_table_from_module(
|
|
349
|
+
table_version = self.check_table_from_module(
|
|
350
|
+
node, table_id, is_abstract
|
|
351
|
+
)
|
|
263
352
|
if table_version:
|
|
264
353
|
self.group_tables.append(
|
|
265
|
-
{
|
|
354
|
+
{
|
|
355
|
+
GROUP: node.table,
|
|
356
|
+
TABLE_CODE: table_version.Code,
|
|
357
|
+
TABLE_ID: table_version.TableID,
|
|
358
|
+
}
|
|
359
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyDPM Exceptions Module
|
|
3
|
+
|
|
4
|
+
Custom exceptions for DPM processing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from py_dpm.exceptions.exceptions import (
|
|
8
|
+
DrrException,
|
|
9
|
+
SyntaxError,
|
|
10
|
+
SemanticError,
|
|
11
|
+
DataTypeError,
|
|
12
|
+
ScriptingError,
|
|
13
|
+
)
|
|
14
|
+
from py_dpm.exceptions.messages import centralised_messages
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"DrrException",
|
|
18
|
+
"SyntaxError",
|
|
19
|
+
"SemanticError",
|
|
20
|
+
"DataTypeError",
|
|
21
|
+
"ScriptingError",
|
|
22
|
+
"centralised_messages",
|
|
23
|
+
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from py_dpm.
|
|
2
|
-
from py_dpm.Utils.operands_mapping import LabelHandler, get_type_from_label
|
|
1
|
+
from py_dpm.exceptions.messages import centralised_messages
|
|
3
2
|
|
|
4
3
|
"""
|
|
5
4
|
Exceptions management.
|
|
@@ -34,6 +33,9 @@ class SyntaxError(DrrException):
|
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
def gather_expression(operand):
|
|
36
|
+
# Lazy import to avoid circular dependency
|
|
37
|
+
from py_dpm.dpm_xl.utils.operands_mapping import LabelHandler
|
|
38
|
+
|
|
37
39
|
operands_labels = LabelHandler().operands_labels
|
|
38
40
|
|
|
39
41
|
expression = operand
|
|
@@ -52,6 +54,9 @@ class SemanticError(DrrException):
|
|
|
52
54
|
"""
|
|
53
55
|
|
|
54
56
|
def __init__(self, code, **kwargs):
|
|
57
|
+
# Lazy import to avoid circular dependency
|
|
58
|
+
from py_dpm.dpm_xl.utils.operands_mapping import LabelHandler, get_type_from_label
|
|
59
|
+
|
|
55
60
|
operands_labels = LabelHandler().operands_labels
|
|
56
61
|
message = centralised_messages[code].format(**kwargs)
|
|
57
62
|
for operand in reversed(operands_labels):
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pydpm_xl
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python library for DPM-XL data processing and analysis
|
|
5
|
+
Author-email: "MeaningfulData S.L." <info@meaningfuldata.eu>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Keywords: dpm,dpm-xl,data-processing,migration,analysis
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: rich<13.8.0,>=13.7.1
|
|
22
|
+
Requires-Dist: sqlalchemy<1.5.0,>=1.4.50
|
|
23
|
+
Requires-Dist: pandas>=2.1.4
|
|
24
|
+
Requires-Dist: antlr4-python3-runtime<4.9.3,>=4.9.2
|
|
25
|
+
Requires-Dist: pyodbc<5.2.0,>=5.1.0
|
|
26
|
+
Requires-Dist: click<8.2.0,>=8.1.6
|
|
27
|
+
Requires-Dist: python-dotenv<1.1.0,>=1.0.1
|
|
28
|
+
Requires-Dist: psycopg2-binary<3.0.0,>=2.9.0
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest<8.0.0,>=7.4.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov<5.0.0,>=4.1.0; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# pyDPM
|
|
35
|
+
|
|
36
|
+
A Python library for processing DPM (Data Point Model) expressions and working with regulatory reporting data.
|
|
37
|
+
|
|
38
|
+
## Overview
|
|
39
|
+
|
|
40
|
+
pyDPM provides two main areas of functionality:
|
|
41
|
+
|
|
42
|
+
- **DPM-XL Processing**: Parse, validate, and generate ASTs for DPM-XL expressions
|
|
43
|
+
- **DPM Utilities**: Work with DPM databases, explore data dictionaries, and manage operation scopes
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Using Poetry (Recommended)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
poetry install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Using pip
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
py_dpm/
|
|
63
|
+
├── api/ # Public APIs
|
|
64
|
+
│ ├── dpm_xl/ # DPM-XL expression APIs
|
|
65
|
+
│ └── dpm/ # General DPM APIs
|
|
66
|
+
├── dpm_xl/ # DPM-XL Processing Engine
|
|
67
|
+
│ ├── grammar/ # ANTLR grammar
|
|
68
|
+
│ ├── ast/ # AST generation
|
|
69
|
+
│ ├── operators/ # Expression operators
|
|
70
|
+
│ ├── types/ # Type system
|
|
71
|
+
│ ├── validation/ # Validation logic
|
|
72
|
+
│ └── utils/ # DPM-XL utilities
|
|
73
|
+
├── dpm/ # General DPM Core
|
|
74
|
+
│ ├── db/ # Database models & views
|
|
75
|
+
│ ├── scopes/ # Operation scopes
|
|
76
|
+
│ └── explorer/ # Data dictionary explorer
|
|
77
|
+
├── cli/ # Command-line interface
|
|
78
|
+
├── exceptions/ # Custom exceptions
|
|
79
|
+
└── utils/ # Shared utilities
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Database Configuration
|
|
83
|
+
|
|
84
|
+
pyDPM supports multiple database backends. It selects the connection method based on the following hierarchy of preference:
|
|
85
|
+
|
|
86
|
+
1. **Explicit Argument**: Passing a connection URL or path directly in Python code overrides all configuration.
|
|
87
|
+
2. **Unified RDBMS Configuration**: If `PYDPM_RDBMS` and the `PYDPM_DB_*` variables are set, it connects to the configured server database.
|
|
88
|
+
3. **Legacy PostgreSQL**: If `USE_POSTGRES=true` in `.env`, it connects to the configured Postgres server.
|
|
89
|
+
4. **SQLite**: If `USE_SQLITE=true` (default), it connects to a local SQLite file.
|
|
90
|
+
5. **SQL Server**: Legacy fallback if no other option is selected.
|
|
91
|
+
|
|
92
|
+
### Environment Variables (.env)
|
|
93
|
+
|
|
94
|
+
Configure your database connection in the `.env` file:
|
|
95
|
+
|
|
96
|
+
```ini
|
|
97
|
+
# --- Option 1: SQLite (Default) ---
|
|
98
|
+
USE_SQLITE=true
|
|
99
|
+
SQLITE_DB_PATH=database.db
|
|
100
|
+
|
|
101
|
+
# --- Option 2: Unified server database (recommended) ---
|
|
102
|
+
# PYDPM_RDBMS=postgres # or "sqlserver"
|
|
103
|
+
# PYDPM_DB_HOST=localhost
|
|
104
|
+
# PYDPM_DB_PORT=5432 # defaults: 5432 for postgres, 1433 for sqlserver
|
|
105
|
+
# PYDPM_DB_NAME=dpm_db
|
|
106
|
+
# PYDPM_DB_USER=myuser
|
|
107
|
+
# PYDPM_DB_PASSWORD=mypassword
|
|
108
|
+
|
|
109
|
+
# --- Option 3: Legacy PostgreSQL (backward compatible) ---
|
|
110
|
+
# USE_POSTGRES=true
|
|
111
|
+
# POSTGRES_HOST=localhost
|
|
112
|
+
# POSTGRES_PORT=5432
|
|
113
|
+
# POSTGRES_DB=dpm_db
|
|
114
|
+
# POSTGRES_USER=myuser
|
|
115
|
+
# POSTGRES_PASS=mypassword
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
### Command Line Interface
|
|
121
|
+
|
|
122
|
+
#### Migrate Access Database
|
|
123
|
+
```bash
|
|
124
|
+
poetry run pydpm migrate-access ./path-to-release.accdb
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Syntax Validation
|
|
128
|
+
```bash
|
|
129
|
+
poetry run pydpm syntax "{tT_01.00, r0010, c0010}"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Semantic Validation
|
|
133
|
+
```bash
|
|
134
|
+
poetry run pydpm semantic "{tT_01.00, r0010, c0010}"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Python API
|
|
138
|
+
|
|
139
|
+
#### DPM-XL Expression Processing
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from py_dpm.api import SyntaxAPI, SemanticAPI, ASTGenerator
|
|
143
|
+
|
|
144
|
+
# Syntax validation
|
|
145
|
+
syntax_api = SyntaxAPI()
|
|
146
|
+
is_valid = syntax_api.is_valid_syntax("{tT_01.00, r0010, c0010}")
|
|
147
|
+
print(f"Valid syntax: {is_valid}")
|
|
148
|
+
|
|
149
|
+
# Get detailed syntax errors
|
|
150
|
+
errors = syntax_api.validate_syntax("invalid expression")
|
|
151
|
+
for error in errors:
|
|
152
|
+
print(f"Line {error.line}, Col {error.column}: {error.message}")
|
|
153
|
+
|
|
154
|
+
# Generate AST
|
|
155
|
+
ast_gen = ASTGenerator()
|
|
156
|
+
ast = ast_gen.generate("{tT_01.00, r0010, c0010}")
|
|
157
|
+
print(f"AST: {ast}")
|
|
158
|
+
|
|
159
|
+
# Semantic validation (requires database)
|
|
160
|
+
semantic_api = SemanticAPI()
|
|
161
|
+
result = semantic_api.validate("{tT_01.00, r0010, c0010}", release_id=123)
|
|
162
|
+
if result.is_valid:
|
|
163
|
+
print("Semantically valid!")
|
|
164
|
+
else:
|
|
165
|
+
for error in result.errors:
|
|
166
|
+
print(f"Error: {error.message}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Working with DPM Database
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from py_dpm.api import DataDictionaryAPI, DPMExplorer
|
|
173
|
+
|
|
174
|
+
# Query data dictionary
|
|
175
|
+
dd_api = DataDictionaryAPI()
|
|
176
|
+
|
|
177
|
+
# Get all tables
|
|
178
|
+
tables = dd_api.get_all_tables(release_id=123)
|
|
179
|
+
for table in tables:
|
|
180
|
+
print(f"Table: {table.code} - {table.name}")
|
|
181
|
+
|
|
182
|
+
# Get table details
|
|
183
|
+
table = dd_api.get_table_by_code("T_01.00", release_id=123)
|
|
184
|
+
print(f"Table headers: {len(table.headers)}")
|
|
185
|
+
|
|
186
|
+
# Explore database structure
|
|
187
|
+
explorer = DPMExplorer()
|
|
188
|
+
modules = explorer.get_modules(release_id=123)
|
|
189
|
+
for module in modules:
|
|
190
|
+
print(f"Module: {module.code}")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Operation Scopes
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from py_dpm.api import OperationScopesAPI, calculate_scopes_from_expression
|
|
197
|
+
|
|
198
|
+
# Calculate scopes for an expression
|
|
199
|
+
scopes_api = OperationScopesAPI()
|
|
200
|
+
result = calculate_scopes_from_expression(
|
|
201
|
+
expression="{tT_01.00, r0010, c0010}",
|
|
202
|
+
release_id=123
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if result.success:
|
|
206
|
+
for scope in result.scopes:
|
|
207
|
+
print(f"Table: {scope.table_code}")
|
|
208
|
+
print(f"Headers: {[h.header_code for h in scope.headers]}")
|
|
209
|
+
else:
|
|
210
|
+
print(f"Error: {result.error}")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Complete AST Generation
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from py_dpm.api import generate_complete_ast, generate_enriched_ast
|
|
217
|
+
|
|
218
|
+
# Generate complete AST with data fields
|
|
219
|
+
result = generate_complete_ast("{tT_01.00, r0010, c0010}", release_id=123)
|
|
220
|
+
if result.success:
|
|
221
|
+
print(f"AST: {result.ast}")
|
|
222
|
+
print(f"Data fields: {result.data_fields}")
|
|
223
|
+
else:
|
|
224
|
+
print(f"Errors: {result.errors}")
|
|
225
|
+
|
|
226
|
+
# Generate enriched AST (ready for execution engine)
|
|
227
|
+
enriched = generate_enriched_ast("{tT_01.00, r0010, c0010}", release_id=123)
|
|
228
|
+
print(f"Enriched AST: {enriched}")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### Migration
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from py_dpm.api import MigrationAPI
|
|
235
|
+
|
|
236
|
+
# Migrate Access database to SQLAlchemy
|
|
237
|
+
migration_api = MigrationAPI()
|
|
238
|
+
migration_api.migrate_from_access(
|
|
239
|
+
access_db_path="./release.accdb",
|
|
240
|
+
release_id=123
|
|
241
|
+
)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Development
|
|
245
|
+
|
|
246
|
+
### Running Tests
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
poetry run pytest
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Code Structure
|
|
253
|
+
|
|
254
|
+
- See [API_DOCUMENTATION.md](API_DOCUMENTATION.md) for detailed API reference
|
|
255
|
+
- See [REORGANIZATION_SUMMARY.md](REORGANIZATION_SUMMARY.md) for reorganization details
|
|
256
|
+
|
|
257
|
+
## Important Notes
|
|
258
|
+
|
|
259
|
+
### ANTLR Version
|
|
260
|
+
This project uses **ANTLR 4.9.2**. Always run Python scripts using Poetry to ensure the correct runtime version:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
poetry run python your_script.py # ✅ Correct
|
|
264
|
+
python your_script.py # ❌ May use wrong ANTLR version
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Database Sessions
|
|
268
|
+
When using database APIs without explicit connection configuration, pyDPM uses global session management. For concurrent usage or testing, pass explicit database paths or connection URLs:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
api = DataDictionaryAPI(database_path="./test.db")
|
|
272
|
+
# or
|
|
273
|
+
api = DataDictionaryAPI(connection_url="postgresql://user:pass@localhost/db")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
[Add your license information here]
|