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.
Files changed (123) hide show
  1. py_dpm/__init__.py +1 -1
  2. py_dpm/api/__init__.py +58 -189
  3. py_dpm/api/dpm/__init__.py +20 -0
  4. py_dpm/api/{data_dictionary.py → dpm/data_dictionary.py} +903 -984
  5. py_dpm/api/dpm/explorer.py +236 -0
  6. py_dpm/api/dpm/hierarchical_queries.py +142 -0
  7. py_dpm/api/{migration.py → dpm/migration.py} +16 -19
  8. py_dpm/api/{operation_scopes.py → dpm/operation_scopes.py} +319 -267
  9. py_dpm/api/dpm_xl/__init__.py +25 -0
  10. py_dpm/api/{ast_generator.py → dpm_xl/ast_generator.py} +3 -3
  11. py_dpm/api/{complete_ast.py → dpm_xl/complete_ast.py} +186 -284
  12. py_dpm/api/dpm_xl/semantic.py +358 -0
  13. py_dpm/api/{syntax.py → dpm_xl/syntax.py} +6 -5
  14. py_dpm/api/explorer.py +4 -0
  15. py_dpm/api/semantic.py +30 -306
  16. py_dpm/cli/__init__.py +9 -0
  17. py_dpm/{client.py → cli/main.py} +12 -10
  18. py_dpm/dpm/__init__.py +11 -0
  19. py_dpm/{models.py → dpm/models.py} +112 -88
  20. py_dpm/dpm/queries/base.py +100 -0
  21. py_dpm/dpm/queries/basic_objects.py +33 -0
  22. py_dpm/dpm/queries/explorer_queries.py +352 -0
  23. py_dpm/dpm/queries/filters.py +139 -0
  24. py_dpm/dpm/queries/glossary.py +45 -0
  25. py_dpm/dpm/queries/hierarchical_queries.py +838 -0
  26. py_dpm/dpm/queries/tables.py +133 -0
  27. py_dpm/dpm/utils.py +356 -0
  28. py_dpm/dpm_xl/__init__.py +8 -0
  29. py_dpm/dpm_xl/ast/__init__.py +14 -0
  30. py_dpm/{AST/ASTConstructor.py → dpm_xl/ast/constructor.py} +6 -6
  31. py_dpm/{AST/MLGeneration.py → dpm_xl/ast/ml_generation.py} +137 -87
  32. py_dpm/{AST/ModuleAnalyzer.py → dpm_xl/ast/module_analyzer.py} +7 -7
  33. py_dpm/{AST/ModuleDependencies.py → dpm_xl/ast/module_dependencies.py} +56 -41
  34. py_dpm/{AST/ASTObjects.py → dpm_xl/ast/nodes.py} +1 -1
  35. py_dpm/{AST/check_operands.py → dpm_xl/ast/operands.py} +16 -13
  36. py_dpm/{AST/ASTTemplate.py → dpm_xl/ast/template.py} +2 -2
  37. py_dpm/{AST/WhereClauseChecker.py → dpm_xl/ast/where_clause.py} +2 -2
  38. py_dpm/dpm_xl/grammar/__init__.py +18 -0
  39. py_dpm/dpm_xl/operators/__init__.py +19 -0
  40. py_dpm/{Operators/AggregateOperators.py → dpm_xl/operators/aggregate.py} +7 -7
  41. py_dpm/{Operators/NumericOperators.py → dpm_xl/operators/arithmetic.py} +6 -6
  42. py_dpm/{Operators/Operator.py → dpm_xl/operators/base.py} +5 -5
  43. py_dpm/{Operators/BooleanOperators.py → dpm_xl/operators/boolean.py} +5 -5
  44. py_dpm/{Operators/ClauseOperators.py → dpm_xl/operators/clause.py} +8 -8
  45. py_dpm/{Operators/ComparisonOperators.py → dpm_xl/operators/comparison.py} +5 -5
  46. py_dpm/{Operators/ConditionalOperators.py → dpm_xl/operators/conditional.py} +7 -7
  47. py_dpm/{Operators/StringOperators.py → dpm_xl/operators/string.py} +5 -5
  48. py_dpm/{Operators/TimeOperators.py → dpm_xl/operators/time.py} +6 -6
  49. py_dpm/{semantics/SemanticAnalyzer.py → dpm_xl/semantic_analyzer.py} +168 -68
  50. py_dpm/{semantics/Symbols.py → dpm_xl/symbols.py} +3 -3
  51. py_dpm/dpm_xl/types/__init__.py +13 -0
  52. py_dpm/{DataTypes/TypePromotion.py → dpm_xl/types/promotion.py} +2 -2
  53. py_dpm/{DataTypes/ScalarTypes.py → dpm_xl/types/scalar.py} +2 -2
  54. py_dpm/dpm_xl/utils/__init__.py +14 -0
  55. py_dpm/{data_handlers.py → dpm_xl/utils/data_handlers.py} +2 -2
  56. py_dpm/{Utils → dpm_xl/utils}/operands_mapping.py +1 -1
  57. py_dpm/{Utils → dpm_xl/utils}/operator_mapping.py +8 -8
  58. py_dpm/{OperationScopes/OperationScopeService.py → dpm_xl/utils/scopes_calculator.py} +148 -58
  59. py_dpm/{Utils/ast_serialization.py → dpm_xl/utils/serialization.py} +3 -4
  60. py_dpm/dpm_xl/validation/__init__.py +12 -0
  61. py_dpm/{Utils/ValidationsGenerationUtils.py → dpm_xl/validation/generation_utils.py} +2 -3
  62. py_dpm/{ValidationsGeneration/PropertiesConstraintsProcessor.py → dpm_xl/validation/property_constraints.py} +56 -21
  63. py_dpm/{ValidationsGeneration/auxiliary_functions.py → dpm_xl/validation/utils.py} +2 -2
  64. py_dpm/{ValidationsGeneration/VariantsProcessor.py → dpm_xl/validation/variants.py} +149 -55
  65. py_dpm/exceptions/__init__.py +23 -0
  66. py_dpm/{Exceptions → exceptions}/exceptions.py +7 -2
  67. pydpm_xl-0.2.1.dist-info/METADATA +278 -0
  68. pydpm_xl-0.2.1.dist-info/RECORD +88 -0
  69. pydpm_xl-0.2.1.dist-info/entry_points.txt +2 -0
  70. py_dpm/Exceptions/__init__.py +0 -0
  71. py_dpm/OperationScopes/__init__.py +0 -0
  72. py_dpm/Operators/__init__.py +0 -0
  73. py_dpm/Utils/__init__.py +0 -0
  74. py_dpm/Utils/utils.py +0 -2
  75. py_dpm/ValidationsGeneration/Utils.py +0 -364
  76. py_dpm/ValidationsGeneration/__init__.py +0 -0
  77. py_dpm/api/data_dictionary_validation.py +0 -614
  78. py_dpm/db_utils.py +0 -221
  79. py_dpm/grammar/__init__.py +0 -0
  80. py_dpm/grammar/dist/__init__.py +0 -0
  81. py_dpm/grammar/dpm_xlLexer.g4 +0 -437
  82. py_dpm/grammar/dpm_xlParser.g4 +0 -263
  83. py_dpm/semantics/DAG/DAGAnalyzer.py +0 -158
  84. py_dpm/semantics/DAG/__init__.py +0 -0
  85. py_dpm/semantics/__init__.py +0 -0
  86. py_dpm/views/data_types.sql +0 -12
  87. py_dpm/views/datapoints.sql +0 -65
  88. py_dpm/views/hierarchy_operand_reference.sql +0 -11
  89. py_dpm/views/hierarchy_preconditions.sql +0 -13
  90. py_dpm/views/hierarchy_variables.sql +0 -26
  91. py_dpm/views/hierarchy_variables_context.sql +0 -14
  92. py_dpm/views/key_components.sql +0 -18
  93. py_dpm/views/module_from_table.sql +0 -11
  94. py_dpm/views/open_keys.sql +0 -13
  95. py_dpm/views/operation_info.sql +0 -27
  96. py_dpm/views/operation_list.sql +0 -18
  97. py_dpm/views/operations_versions_from_module_version.sql +0 -30
  98. py_dpm/views/precondition_info.sql +0 -17
  99. py_dpm/views/report_type_operand_reference_info.sql +0 -18
  100. py_dpm/views/subcategory_info.sql +0 -17
  101. py_dpm/views/table_info.sql +0 -19
  102. pydpm_xl-0.1.39rc32.dist-info/METADATA +0 -53
  103. pydpm_xl-0.1.39rc32.dist-info/RECORD +0 -96
  104. pydpm_xl-0.1.39rc32.dist-info/entry_points.txt +0 -2
  105. /py_dpm/{AST → cli/commands}/__init__.py +0 -0
  106. /py_dpm/{migration.py → dpm/migration.py} +0 -0
  107. /py_dpm/{AST/ASTVisitor.py → dpm_xl/ast/visitor.py} +0 -0
  108. /py_dpm/{DataTypes → dpm_xl/grammar/generated}/__init__.py +0 -0
  109. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.interp +0 -0
  110. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.py +0 -0
  111. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.tokens +0 -0
  112. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.interp +0 -0
  113. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.py +0 -0
  114. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.tokens +0 -0
  115. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserListener.py +0 -0
  116. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserVisitor.py +0 -0
  117. /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/listeners.py +0 -0
  118. /py_dpm/{DataTypes/TimeClasses.py → dpm_xl/types/time.py} +0 -0
  119. /py_dpm/{Utils → dpm_xl/utils}/tokens.py +0 -0
  120. /py_dpm/{Exceptions → exceptions}/messages.py +0 -0
  121. {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/WHEEL +0 -0
  122. {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/licenses/LICENSE +0 -0
  123. {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.AST.ASTObjects import VarID, WithExpression
7
- from py_dpm.AST.ASTTemplate import ASTTemplate
8
- from py_dpm.Exceptions import exceptions
9
- from py_dpm.models import ModuleVersionComposition, TableGroup, TableGroupComposition, TableVersion, ViewDatapoints
10
- from py_dpm.Utils.tokens import EXPRESSION, STATUS, STATUS_CORRECT, STATUS_INCOMPLETE, STATUS_INCORRECT, VALIDATION_CODE
11
- from py_dpm.data_handlers import filter_all_data
12
-
13
- cell_components = ['table', 'rows', 'cols', 'sheets']
14
- TABLE_ID = 'TableID'
15
- MODULE_VID = 'ModuleVID'
16
- TABLE_CODE = 'Code'
17
- GROUP = 'group'
18
- MODULE_VERSION_ID = 'module_version_id'
19
- MODULE_CODE = 'module_code'
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(session=self.session, table=table_code, release_id=self.release_id)
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(data=datapoints, table_code=table_code, rows=rows, cols=cols, sheets=sheets)
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(session=self.session,
93
- table_id=table_module_id,
94
- is_abstract=is_abstract,
95
- release_id=self.release_id)
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(table_version.Code, node.rows, node.cols, node.sheets)
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) + '[^.$]' + ")", self.expression)
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(re.escape(f"g{group_code}") + '[^.$]', f"t{table_code}" + group[-1], self.expression)
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(session=self.session, table_ids=table_ids,
139
- release_id=self.release_id)
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(lambda x: self.generate_child_expression(x[GROUP], x[TABLE_CODE]), axis=1)
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
- {EXPRESSION: self.expression, MODULE_VERSION_ID: module, MODULE_CODE: module_code})
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
- {EXPRESSION: self.expression, MODULE_VERSION_ID: module, MODULE_CODE: module_code})
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
- {EXPRESSION: self.expression, MODULE_VERSION_ID: module, MODULE_CODE: module_code})
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 = self.generate_child_validation_code() if status == STATUS_CORRECT else None
174
-
175
- validation = {VALIDATION_CODE: validation_code,
176
- EXPRESSION: expression_info[EXPRESSION],
177
- STATUS: status,
178
- MODULE_VERSION_ID: expression_info[MODULE_VERSION_ID],
179
- MODULE_CODE: expression_info[MODULE_CODE]}
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 = pd.DataFrame.from_records(correct_expressions)['expression'].unique().tolist() if correct_expressions else []
194
- unique_incomplete_expressions = pd.DataFrame.from_records(incomplete_expressions)['expression'].unique().tolist() if incomplete_expressions else []
195
- unique_incorrect_expressions = pd.DataFrame.from_records(incorrect_expressions)['expression'].unique().tolist() if incorrect_expressions else []
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(correct_expressions, unique_correct_expressions, STATUS_CORRECT)
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(incomplete_expressions, unique_incomplete_expressions, STATUS_INCOMPLETE)
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(incorrect_expressions, unique_incorrect_expressions, STATUS_INCORRECT)
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(self, expressions, unique_expressions, status):
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['scopes'] = []
293
+ aux["scopes"] = []
220
294
  for elto in expressions:
221
295
  if elto[EXPRESSION] == aux[EXPRESSION]:
222
- aux['scopes'].append(
223
- {"module_versions_ids": [elto[MODULE_VERSION_ID]],
224
- "module_code": elto[MODULE_CODE]
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(self.partial_selection, attribute):
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(session=self.session, group_code=node.table)
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 = TableGroupComposition.get_from_parent_table_code(code=node.table, session=self.session)
257
- self.table_groups_compositions[node.table] = table_groups_compositions
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[node.table]
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(node, table_id, is_abstract)
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
- {GROUP: node.table, TABLE_CODE: table_version.Code, TABLE_ID: table_version.TableID})
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.Exceptions.messages import centralised_messages
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]