pydpm_xl 0.2.4__py3-none-any.whl → 0.2.5rc1__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 CHANGED
@@ -41,7 +41,7 @@ Available packages:
41
41
  - pydpm.api: Main APIs for migration, syntax, and semantic analysis
42
42
  """
43
43
 
44
- __version__ = "0.2.4"
44
+ __version__ = "0.2.5rc1"
45
45
  __author__ = "MeaningfulData S.L."
46
46
  __email__ = "info@meaningfuldata.eu"
47
47
  __license__ = "GPL-3.0-or-later"
@@ -369,6 +369,7 @@ class ASTGeneratorAPI:
369
369
  precondition: Optional[str] = None,
370
370
  release_id: Optional[int] = None,
371
371
  output_path: Optional[Union[str, Path]] = None,
372
+ primary_module_vid: Optional[int] = None,
372
373
  ) -> Dict[str, Any]:
373
374
  """
374
375
  Generate enriched, engine-ready AST with framework structure (Level 3).
@@ -381,13 +382,14 @@ class ASTGeneratorAPI:
381
382
  - Everything from generate_complete_ast() PLUS:
382
383
  - Framework structure: operations, variables, tables, preconditions
383
384
  - Module metadata: version, release info, dates
384
- - Dependency information
385
+ - Dependency information (including cross-module dependencies)
385
386
  - Coordinates (x/y/z) added to data entries
386
387
 
387
388
  **Typical use case:**
388
389
  - Feeding AST to business rule execution engines
389
390
  - Validation framework integration
390
391
  - Production rule processing
392
+ - Module exports with cross-module dependency tracking
391
393
 
392
394
  Args:
393
395
  expression: DPM-XL expression string
@@ -399,6 +401,11 @@ class ASTGeneratorAPI:
399
401
  If None, uses all available data (release-agnostic).
400
402
  output_path: Optional path (string or Path) to save the enriched_ast as JSON file.
401
403
  If provided, the enriched_ast will be automatically saved to this location.
404
+ primary_module_vid: Optional module version ID of the module being exported.
405
+ When provided, enables detection of cross-module dependencies - tables from
406
+ other modules will be identified and added to dependency_modules and
407
+ cross_instance_dependencies fields. If None, cross-module detection uses
408
+ the first table's module as the primary module.
402
409
 
403
410
  Returns:
404
411
  dict: {
@@ -416,14 +423,17 @@ class ASTGeneratorAPI:
416
423
  ... )
417
424
  >>> # result['enriched_ast'] contains framework structure ready for engines
418
425
  >>>
419
- >>> # Or save directly to a file:
426
+ >>> # For module exports with cross-module dependency tracking:
420
427
  >>> result = generator.generate_enriched_ast(
421
- ... "{tF_01.00, r0010, c0010}",
428
+ ... "{tC_26.00, r030, c010} * {tC_01.00, r0015, c0010}",
422
429
  ... dpm_version="4.2",
423
- ... operation_code="my_validation",
424
- ... output_path="./output/enriched_ast.json"
430
+ ... operation_code="v2814_m",
431
+ ... primary_module_vid=123, # Module being exported
432
+ ... release_id=42
425
433
  ... )
426
- >>> # The enriched_ast is automatically saved to the specified path
434
+ >>> # result['enriched_ast']['dependency_modules'] contains external module info
435
+ >>> # result['enriched_ast']['dependency_information']['cross_instance_dependencies']
436
+ >>> # contains list of external module dependencies
427
437
  """
428
438
  try:
429
439
  # Generate complete AST first
@@ -447,6 +457,8 @@ class ASTGeneratorAPI:
447
457
  dpm_version=dpm_version,
448
458
  operation_code=operation_code,
449
459
  precondition=precondition,
460
+ release_id=release_id,
461
+ primary_module_vid=primary_module_vid,
450
462
  )
451
463
 
452
464
  # Save to file if output_path is provided
@@ -723,11 +735,23 @@ class ASTGeneratorAPI:
723
735
  dpm_version: Optional[str] = None,
724
736
  operation_code: Optional[str] = None,
725
737
  precondition: Optional[str] = None,
738
+ release_id: Optional[int] = None,
739
+ primary_module_vid: Optional[int] = None,
726
740
  ) -> Dict[str, Any]:
727
741
  """
728
742
  Add framework structure (operations, variables, tables, preconditions) to complete AST.
729
743
 
730
744
  This creates the engine-ready format with all metadata sections.
745
+
746
+ Args:
747
+ ast_dict: Complete AST dictionary
748
+ expression: Original DPM-XL expression
749
+ context: Context dict with table, rows, columns, sheets, default, interval
750
+ dpm_version: DPM version code (e.g., "4.2")
751
+ operation_code: Operation code (defaults to "default_code")
752
+ precondition: Precondition variable reference (e.g., {v_F_44_04})
753
+ release_id: Optional release ID to filter database lookups
754
+ primary_module_vid: Module VID being exported (to identify external dependencies)
731
755
  """
732
756
  from py_dpm.dpm.utils import get_engine
733
757
  import copy
@@ -795,15 +819,21 @@ class ASTGeneratorAPI:
795
819
  engine=engine,
796
820
  )
797
821
 
822
+ # Detect cross-module dependencies
823
+ dependency_modules, cross_instance_dependencies = self._detect_cross_module_dependencies(
824
+ expression=expression,
825
+ variables_by_table=variables_by_table,
826
+ primary_module_vid=primary_module_vid,
827
+ operation_code=operation_code,
828
+ release_id=release_id,
829
+ )
830
+
798
831
  # Build dependency information
799
832
  dependency_info = {
800
833
  "intra_instance_validations": [operation_code],
801
- "cross_instance_dependencies": [],
834
+ "cross_instance_dependencies": cross_instance_dependencies,
802
835
  }
803
836
 
804
- # Build dependency modules
805
- dependency_modules = {}
806
-
807
837
  # Build complete structure
808
838
  namespace = "default_module"
809
839
 
@@ -1009,6 +1039,154 @@ class ASTGeneratorAPI:
1009
1039
  extract_from_node(ast_dict)
1010
1040
  return all_variables, variables_by_table
1011
1041
 
1042
+ def _detect_cross_module_dependencies(
1043
+ self,
1044
+ expression: str,
1045
+ variables_by_table: Dict[str, Dict[str, str]],
1046
+ primary_module_vid: Optional[int],
1047
+ operation_code: str,
1048
+ release_id: Optional[int] = None,
1049
+ ) -> tuple:
1050
+ """
1051
+ Detect cross-module dependencies for a single expression.
1052
+
1053
+ Uses existing OperationScopesAPI and ExplorerQuery to detect external module
1054
+ references in cross-module expressions.
1055
+
1056
+ Args:
1057
+ expression: DPM-XL expression
1058
+ variables_by_table: Variables by table code (from _extract_variables_from_ast)
1059
+ primary_module_vid: The module being exported (if known)
1060
+ operation_code: Current operation code
1061
+ release_id: Optional release ID for filtering
1062
+
1063
+ Returns:
1064
+ Tuple of (dependency_modules, cross_instance_dependencies)
1065
+ - dependency_modules: {uri: {tables: {...}, variables: {...}}}
1066
+ - cross_instance_dependencies: [{modules: [...], affected_operations: [...], ...}]
1067
+ """
1068
+ from py_dpm.api.dpm_xl.operation_scopes import OperationScopesAPI
1069
+ from py_dpm.dpm.queries.explorer_queries import ExplorerQuery
1070
+ import logging
1071
+
1072
+ scopes_api = OperationScopesAPI(
1073
+ database_path=self.database_path,
1074
+ connection_url=self.connection_url
1075
+ )
1076
+
1077
+ try:
1078
+ # Get tables with module info
1079
+ tables_with_modules = scopes_api.get_tables_with_metadata_from_expression(
1080
+ expression=expression,
1081
+ release_id=release_id
1082
+ )
1083
+
1084
+ # Check if cross-module
1085
+ scope_result = scopes_api.calculate_scopes_from_expression(
1086
+ expression=expression,
1087
+ release_id=release_id,
1088
+ read_only=True
1089
+ )
1090
+
1091
+ if scope_result.has_error or not scope_result.is_cross_module:
1092
+ return {}, []
1093
+
1094
+ # Determine primary module from first table if not provided
1095
+ if primary_module_vid is None and tables_with_modules:
1096
+ primary_module_vid = tables_with_modules[0].get("module_vid")
1097
+
1098
+ # Group external tables by module
1099
+ external_modules = {}
1100
+ for table_info in tables_with_modules:
1101
+ module_vid = table_info.get("module_vid")
1102
+ if module_vid == primary_module_vid:
1103
+ continue # Skip primary module
1104
+
1105
+ module_code = table_info.get("module_code")
1106
+ if not module_code:
1107
+ continue
1108
+
1109
+ # Get module URI using existing ExplorerQuery
1110
+ try:
1111
+ module_uri = ExplorerQuery.get_module_url(
1112
+ scopes_api.session,
1113
+ module_code=module_code,
1114
+ release_id=release_id,
1115
+ )
1116
+ # Remove .json suffix if present (for consistency with expected format)
1117
+ if module_uri.endswith(".json"):
1118
+ module_uri = module_uri[:-5]
1119
+ except Exception:
1120
+ continue
1121
+
1122
+ if module_uri not in external_modules:
1123
+ external_modules[module_uri] = {
1124
+ "module_vid": module_vid,
1125
+ "tables": {},
1126
+ "variables": {},
1127
+ "from_date": None,
1128
+ "to_date": None
1129
+ }
1130
+
1131
+ # Add table - get variables from variables_by_table
1132
+ table_code = table_info.get("code")
1133
+ if table_code:
1134
+ # Look up variables in variables_by_table (handles t prefix)
1135
+ table_variables = variables_by_table.get(table_code, {})
1136
+ if not table_variables:
1137
+ # Try with t prefix
1138
+ table_variables = variables_by_table.get(f"t{table_code}", {})
1139
+ external_modules[module_uri]["tables"][table_code] = {
1140
+ "variables": table_variables,
1141
+ "open_keys": {}
1142
+ }
1143
+ external_modules[module_uri]["variables"].update(table_variables)
1144
+
1145
+ # Get date info from scopes metadata
1146
+ scopes_metadata = scopes_api.get_scopes_with_metadata_from_expression(
1147
+ expression=expression,
1148
+ release_id=release_id
1149
+ )
1150
+ for scope_info in scopes_metadata:
1151
+ for module in scope_info.module_versions:
1152
+ mvid = module.get("module_vid")
1153
+ for uri, data in external_modules.items():
1154
+ if data["module_vid"] == mvid:
1155
+ data["from_date"] = module.get("from_reference_date")
1156
+ data["to_date"] = module.get("to_reference_date")
1157
+
1158
+ # Build output structures
1159
+ dependency_modules = {}
1160
+ cross_instance_dependencies = []
1161
+
1162
+ for uri, data in external_modules.items():
1163
+ # dependency_modules entry
1164
+ dependency_modules[uri] = {
1165
+ "tables": data["tables"],
1166
+ "variables": data["variables"]
1167
+ }
1168
+
1169
+ # cross_instance_dependencies entry (one per external module)
1170
+ from_date = data["from_date"]
1171
+ to_date = data["to_date"]
1172
+ cross_instance_dependencies.append({
1173
+ "modules": [{
1174
+ "URI": uri,
1175
+ "ref_period": "T"
1176
+ }],
1177
+ "affected_operations": [operation_code],
1178
+ "from_reference_date": str(from_date) if from_date else "",
1179
+ "to_reference_date": str(to_date) if to_date else ""
1180
+ })
1181
+
1182
+ return dependency_modules, cross_instance_dependencies
1183
+
1184
+ except Exception as e:
1185
+ logging.warning(f"Failed to detect cross-module dependencies: {e}")
1186
+ return {}, []
1187
+ finally:
1188
+ scopes_api.close()
1189
+
1012
1190
  def _add_coordinates_to_ast(
1013
1191
  self, ast_dict: Dict[str, Any], context: Optional[Dict[str, Any]]
1014
1192
  ) -> Dict[str, Any]:
@@ -117,6 +117,7 @@ def generate_enriched_ast(
117
117
  table_context: Optional[Dict[str, Any]] = None,
118
118
  precondition: Optional[str] = None,
119
119
  release_id: Optional[int] = None,
120
+ primary_module_vid: Optional[int] = None,
120
121
  ) -> Dict[str, Any]:
121
122
  """
122
123
  Generate enriched, engine-ready AST from DPM-XL expression.
@@ -133,6 +134,8 @@ def generate_enriched_ast(
133
134
  precondition: Optional precondition variable reference (e.g., {v_F_44_04})
134
135
  release_id: Optional release ID to filter database lookups by specific release.
135
136
  If None, uses all available data (release-agnostic).
137
+ primary_module_vid: Optional module version ID of the module being exported.
138
+ When provided, enables detection of cross-module dependencies.
136
139
 
137
140
  Returns:
138
141
  dict: {
@@ -153,6 +156,7 @@ def generate_enriched_ast(
153
156
  table_context=table_context,
154
157
  precondition=precondition,
155
158
  release_id=release_id,
159
+ primary_module_vid=primary_module_vid,
156
160
  )
157
161
 
158
162
 
@@ -165,6 +169,8 @@ def enrich_ast_with_metadata(
165
169
  dpm_version: Optional[str] = None,
166
170
  operation_code: Optional[str] = None,
167
171
  precondition: Optional[str] = None,
172
+ release_id: Optional[int] = None,
173
+ primary_module_vid: Optional[int] = None,
168
174
  ) -> Dict[str, Any]:
169
175
  """
170
176
  Add framework structure (operations, variables, tables, preconditions) to complete AST.
@@ -180,6 +186,8 @@ def enrich_ast_with_metadata(
180
186
  dpm_version: DPM version code (e.g., "4.2")
181
187
  operation_code: Operation code (defaults to "default_code")
182
188
  precondition: Precondition variable reference (e.g., {v_F_44_04})
189
+ release_id: Optional release ID to filter database lookups
190
+ primary_module_vid: Optional module VID of the module being exported
183
191
 
184
192
  Returns:
185
193
  dict: Engine-ready AST with framework structure
@@ -196,4 +204,6 @@ def enrich_ast_with_metadata(
196
204
  dpm_version=dpm_version,
197
205
  operation_code=operation_code,
198
206
  precondition=precondition,
207
+ release_id=release_id,
208
+ primary_module_vid=primary_module_vid,
199
209
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydpm_xl
3
- Version: 0.2.4
3
+ Version: 0.2.5rc1
4
4
  Summary: Python library for DPM-XL data processing and analysis
5
5
  Author-email: "MeaningfulData S.L." <info@meaningfuldata.eu>
6
6
  License: GPL-3.0-or-later
@@ -241,6 +241,59 @@ migration_api.migrate_from_access(
241
241
  )
242
242
  ```
243
243
 
244
+ #### XBRL-CSV Instance Generation
245
+
246
+ ```python
247
+ from py_dpm.api import InstanceAPI
248
+
249
+ api = InstanceAPI()
250
+
251
+ # Build package from dictionary
252
+ data = {
253
+ "module_code": "F_01.01",
254
+ "parameters": {"refPeriod": "2024-12-31"},
255
+ "facts": [
256
+ {"table_code": "t001", "row_code": "r010", "column_code": "c010", "value": 1000000}
257
+ ]
258
+ }
259
+ output_path = api.build_package_from_dict(data, "/tmp/output")
260
+
261
+ # Build package from JSON file
262
+ output_path = api.build_package_from_json("instance_data.json", "/tmp/output")
263
+ ```
264
+
265
+ #### DPM Explorer - Introspection Queries
266
+
267
+ ```python
268
+ from py_dpm.api import ExplorerQueryAPI
269
+
270
+ with ExplorerQueryAPI() as api:
271
+ # Find all properties using a specific item
272
+ properties = api.get_properties_using_item("EUR")
273
+
274
+ # Get module URL for documentation
275
+ url = api.get_module_url(module_code="F_01.01")
276
+
277
+ # Explore variable usage
278
+ tables = api.get_tables_using_variable(variable_code="mi123")
279
+ ```
280
+
281
+ #### Hierarchical Queries
282
+
283
+ ```python
284
+ from py_dpm.api import HierarchicalQueryAPI
285
+
286
+ with HierarchicalQueryAPI() as api:
287
+ # Get hierarchy for a domain
288
+ hierarchy = api.get_hierarchy(domain_code="DOM_001")
289
+
290
+ # Navigate parent-child relationships
291
+ children = api.get_children(item_code="PARENT_001")
292
+
293
+ # Get all ancestors
294
+ ancestors = api.get_ancestors(item_code="LEAF_001")
295
+ ```
296
+
244
297
  ## Development
245
298
 
246
299
  ### Running Tests
@@ -1,4 +1,4 @@
1
- py_dpm/__init__.py,sha256=LkRi-JRS2aWrSr5G5J8eTVRvZt1J1WfIrS2dXyTemmw,1858
1
+ py_dpm/__init__.py,sha256=GmaGEIVu3IEt3i3UkrQJSpoLu_ENSpiSt_4rBceOjpc,1861
2
2
  py_dpm/api/__init__.py,sha256=n79vAD7qatlYaXaI2N5IAD9m_8Fgb00EOdapVXZYTpI,1081
3
3
  py_dpm/api/dpm/__init__.py,sha256=HQflgiRbs1eDi3KTadNhxS1NoaG6PGQDVMvFnuIEfXo,506
4
4
  py_dpm/api/dpm/data_dictionary.py,sha256=g0h6Yfschz7rboYly9LTbP-2SS5UxltU3AXu0v0tqrU,29457
@@ -7,8 +7,8 @@ py_dpm/api/dpm/hierarchical_queries.py,sha256=X4AbpsWy3iItOTVIdVbtaTmRgOHPf0Y64I
7
7
  py_dpm/api/dpm/instance.py,sha256=v3DWzdaM5gPCecLjwjZ49FGfqZzUR3dPC0U8zGwdttk,3795
8
8
  py_dpm/api/dpm/migration.py,sha256=9FT7zzz4QdUIRR6MD01gMODBtfq9HH_RF4hRgZqMcZc,2404
9
9
  py_dpm/api/dpm_xl/__init__.py,sha256=aRjaMAf_i2a33UAGTg-TF1BfO6miOOrbCydTUqAVvRU,910
10
- py_dpm/api/dpm_xl/ast_generator.py,sha256=maVFlxBnBBZ6UYgVxbvewPFTbrTUqXfdKpmjB0ue0qc,42979
11
- py_dpm/api/dpm_xl/complete_ast.py,sha256=sOaN9uSQo-Nns8twElVQHBg7MnaDSo_8EuZPSIqGdU0,7125
10
+ py_dpm/api/dpm_xl/ast_generator.py,sha256=_ir-LP3hgT8diUFt9sEcXsh3cQhA_YiSSFe4a03zjuA,51050
11
+ py_dpm/api/dpm_xl/complete_ast.py,sha256=VkmcBatrydu97Inwp3pHjz93F38q2JRo-4Lohdu30RY,7684
12
12
  py_dpm/api/dpm_xl/operation_scopes.py,sha256=7AyOFAn9h012JPF9H5EtZ3sPzv6DOxkoinpj5ArzVOc,48492
13
13
  py_dpm/api/dpm_xl/semantic.py,sha256=Buo_t-sEv65r6RmYDy1xkCWGlU2pB2WQsDM-X-FX4cc,13629
14
14
  py_dpm/api/dpm_xl/syntax.py,sha256=Ke_kKd9ModoJ6siL3GPT9j9QClmopryCRcdDAT3M5-E,5954
@@ -76,9 +76,9 @@ py_dpm/exceptions/exceptions.py,sha256=6S3p-_i5O1oStvSMixt_JQG0xwTeSfBcdzrwL8yBy
76
76
  py_dpm/exceptions/messages.py,sha256=UwY6QIK8c-POcDCc9HYbZFGArCIYAanUGNh2LNKPx3U,7534
77
77
  py_dpm/instance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
78
  py_dpm/instance/instance.py,sha256=OPSEPgSYAxhgqhKuxbMpMPTfBnaFNzURTrUUT4kvGKc,10820
79
- pydpm_xl-0.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
80
- pydpm_xl-0.2.4.dist-info/METADATA,sha256=4tYu82PigByb4NHsUhtYMN3YsXhy_2FKjlmn9r_XrH8,7974
81
- pydpm_xl-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
- pydpm_xl-0.2.4.dist-info/entry_points.txt,sha256=6DDmBfw-AjtgvMHgq_I730i_LAAs_7-N3C95HD_bRr4,47
83
- pydpm_xl-0.2.4.dist-info/top_level.txt,sha256=495PvWZRoKl2NvbQU25W7dqWIBHqY-mFMPt83uxPpcM,7
84
- pydpm_xl-0.2.4.dist-info/RECORD,,
79
+ pydpm_xl-0.2.5rc1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
80
+ pydpm_xl-0.2.5rc1.dist-info/METADATA,sha256=X7pPwj8tY9-p6ApsGXKW533zyekcZHHqRf9w1Ld9Ar4,9305
81
+ pydpm_xl-0.2.5rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
+ pydpm_xl-0.2.5rc1.dist-info/entry_points.txt,sha256=6DDmBfw-AjtgvMHgq_I730i_LAAs_7-N3C95HD_bRr4,47
83
+ pydpm_xl-0.2.5rc1.dist-info/top_level.txt,sha256=495PvWZRoKl2NvbQU25W7dqWIBHqY-mFMPt83uxPpcM,7
84
+ pydpm_xl-0.2.5rc1.dist-info/RECORD,,