pydpm_xl 0.1.39rc29__py3-none-any.whl → 0.1.39rc31__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.
@@ -360,13 +360,26 @@ class ASTToJSONVisitor(NodeVisitor):
360
360
  'class_name': 'AggregationOp',
361
361
  'op': node.op,
362
362
  'operand': self.visit(node.operand),
363
- 'grouping_clause': None
364
363
  }
365
- # Add grouping_clause if present
364
+ # Add grouping_clause if present and has actual components
366
365
  if hasattr(node, 'grouping_clause') and node.grouping_clause is not None:
367
- result['grouping_clause'] = self.visit(node.grouping_clause)
366
+ gc = self.visit(node.grouping_clause)
367
+ # Only include grouping_clause if it has components
368
+ if gc and gc.get('components'):
369
+ result['grouping_clause'] = gc
368
370
  return result
369
371
 
372
+ def visit_GroupingClause(self, node):
373
+ """Visit GroupingClause nodes."""
374
+ components = getattr(node, 'components', None)
375
+ if components:
376
+ return {
377
+ 'class_name': 'GroupingClause',
378
+ 'components': components
379
+ }
380
+ # Return None for empty grouping clauses (will be filtered out)
381
+ return None
382
+
370
383
  def visit_ComplexNumericOp(self, node):
371
384
  """Visit ComplexNumericOp nodes (max, min)."""
372
385
  return {
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.1.39rc29"
44
+ __version__ = "0.1.39rc31"
45
45
  __author__ = "MeaningfulData S.L."
46
46
  __email__ = "info@meaningfuldata.eu"
47
47
  __license__ = "GPL-3.0-or-later"
@@ -14,7 +14,12 @@ from typing import Dict, Any, Optional
14
14
  from py_dpm.Utils.ast_serialization import ASTToJSONVisitor
15
15
 
16
16
 
17
- def generate_complete_ast(expression: str, database_path: str = None, connection_url: str = None):
17
+ def generate_complete_ast(
18
+ expression: str,
19
+ database_path: str = None,
20
+ connection_url: str = None,
21
+ release_id: Optional[int] = None
22
+ ):
18
23
  """
19
24
  Generate complete AST with all data fields, exactly like json_scripts examples.
20
25
 
@@ -25,6 +30,8 @@ def generate_complete_ast(expression: str, database_path: str = None, connection
25
30
  expression: DPM-XL expression string
26
31
  database_path: Path to SQLite database file (e.g., "./database.db")
27
32
  connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
33
+ release_id: Optional release ID to filter database lookups by specific release.
34
+ If None, uses all available data (release-agnostic).
28
35
 
29
36
  Returns:
30
37
  dict: {
@@ -87,7 +94,7 @@ def generate_complete_ast(expression: str, database_path: str = None, connection
87
94
  session=session,
88
95
  expression=expression,
89
96
  ast=inner_ast,
90
- release_id=None
97
+ release_id=release_id
91
98
  )
92
99
 
93
100
  # Apply the data from operand checker to VarID nodes
@@ -246,7 +253,12 @@ def _check_data_fields_populated(ast_dict):
246
253
  return False
247
254
 
248
255
 
249
- def generate_complete_batch(expressions: list, database_path: str = None, connection_url: str = None):
256
+ def generate_complete_batch(
257
+ expressions: list,
258
+ database_path: str = None,
259
+ connection_url: str = None,
260
+ release_id: Optional[int] = None
261
+ ):
250
262
  """
251
263
  Generate complete ASTs for multiple expressions.
252
264
 
@@ -254,20 +266,29 @@ def generate_complete_batch(expressions: list, database_path: str = None, connec
254
266
  expressions: List of DPM-XL expression strings
255
267
  database_path: Path to SQLite database file
256
268
  connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
269
+ release_id: Optional release ID to filter database lookups by specific release.
270
+ If None, uses all available data (release-agnostic).
257
271
 
258
272
  Returns:
259
273
  list: List of result dictionaries
260
274
  """
261
275
  results = []
262
276
  for i, expr in enumerate(expressions):
263
- result = generate_complete_ast(expr, database_path, connection_url)
277
+ result = generate_complete_ast(
278
+ expr, database_path, connection_url, release_id=release_id
279
+ )
264
280
  result['batch_index'] = i
265
281
  results.append(result)
266
282
  return results
267
283
 
268
284
 
269
285
  # Convenience function with cleaner interface
270
- def parse_with_data_fields(expression: str, database_path: str = None, connection_url: str = None):
286
+ def parse_with_data_fields(
287
+ expression: str,
288
+ database_path: str = None,
289
+ connection_url: str = None,
290
+ release_id: Optional[int] = None
291
+ ):
271
292
  """
272
293
  Simple function to parse expression and get AST with data fields.
273
294
 
@@ -275,11 +296,15 @@ def parse_with_data_fields(expression: str, database_path: str = None, connectio
275
296
  expression: DPM-XL expression string
276
297
  database_path: Path to SQLite database file
277
298
  connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
299
+ release_id: Optional release ID to filter database lookups by specific release.
300
+ If None, uses all available data (release-agnostic).
278
301
 
279
302
  Returns:
280
303
  dict: AST dictionary with data fields, or None if failed
281
304
  """
282
- result = generate_complete_ast(expression, database_path, connection_url)
305
+ result = generate_complete_ast(
306
+ expression, database_path, connection_url, release_id=release_id
307
+ )
283
308
  return result['ast'] if result['success'] else None
284
309
 
285
310
 
@@ -296,6 +321,7 @@ def generate_enriched_ast(
296
321
  operation_code: Optional[str] = None,
297
322
  table_context: Optional[Dict[str, Any]] = None,
298
323
  precondition: Optional[str] = None,
324
+ release_id: Optional[int] = None,
299
325
  ) -> Dict[str, Any]:
300
326
  """
301
327
  Generate enriched, engine-ready AST from DPM-XL expression.
@@ -311,6 +337,8 @@ def generate_enriched_ast(
311
337
  operation_code: Optional operation code (defaults to "default_code")
312
338
  table_context: Optional table context dict with keys: 'table', 'columns', 'rows', 'sheets', 'default', 'interval'
313
339
  precondition: Optional precondition variable reference (e.g., {v_F_44_04})
340
+ release_id: Optional release ID to filter database lookups by specific release.
341
+ If None, uses all available data (release-agnostic).
314
342
 
315
343
  Returns:
316
344
  dict: {
@@ -321,7 +349,9 @@ def generate_enriched_ast(
321
349
  """
322
350
  try:
323
351
  # Generate complete AST first
324
- complete_result = generate_complete_ast(expression, database_path, connection_url)
352
+ complete_result = generate_complete_ast(
353
+ expression, database_path, connection_url, release_id=release_id
354
+ )
325
355
 
326
356
  if not complete_result['success']:
327
357
  return {
py_dpm/client.py CHANGED
@@ -6,26 +6,38 @@ import os
6
6
  import sys
7
7
  import pandas as pd
8
8
 
9
- from py_dpm.api import API
9
+ from py_dpm.api import API, SemanticAPI
10
+ from py_dpm.api.semantic import SemanticValidationResult
10
11
  from py_dpm.api.operation_scopes import OperationScopesAPI
11
12
  from py_dpm.migration import run_migration
12
- from py_dpm.Utils.tokens import CODE, ERROR, ERROR_CODE, EXPRESSION, OP_VERSION_ID, STATUS, \
13
- STATUS_CORRECT, STATUS_UNKNOWN, VALIDATIONS, \
14
- VALIDATION_TYPE, \
15
- VARIABLES
13
+ from py_dpm.Utils.tokens import (
14
+ CODE,
15
+ ERROR,
16
+ ERROR_CODE,
17
+ EXPRESSION,
18
+ OP_VERSION_ID,
19
+ STATUS,
20
+ STATUS_CORRECT,
21
+ STATUS_UNKNOWN,
22
+ VALIDATIONS,
23
+ VALIDATION_TYPE,
24
+ VARIABLES,
25
+ )
16
26
  from py_dpm.Exceptions.exceptions import SemanticError
17
27
 
18
28
 
19
29
  console = Console()
20
30
 
31
+
21
32
  @click.group()
22
33
  @click.version_option()
23
34
  def main():
24
35
  """pyDPM CLI - A command line interface for pyDPM"""
25
36
  pass
26
37
 
38
+
27
39
  @main.command()
28
- @click.argument('access_file', type=click.Path(exists=True))
40
+ @click.argument("access_file", type=click.Path(exists=True))
29
41
  def migrate_access(access_file: str):
30
42
  """
31
43
  Migrates data from an Access database to a SQLite database.
@@ -44,45 +56,93 @@ def migrate_access(access_file: str):
44
56
 
45
57
 
46
58
  @main.command()
47
- @click.argument('expression', type=str)
48
- def semantic(expression: str):
59
+ @click.argument("expression", type=str)
60
+ @click.option(
61
+ "--release-id", type=int, help="Release ID to use for validation", default=None
62
+ )
63
+ @click.option(
64
+ "--dpm-version",
65
+ type=str,
66
+ help="DPM Version (e.g. 4.2) to use for validation",
67
+ default=None,
68
+ )
69
+ def semantic(expression: str, release_id: int, dpm_version: str):
49
70
  """
50
71
  Semantically analyses the input expression by applying the syntax validation, the operands checking, the data type
51
72
  validation and the structure validation
52
73
  :param expression: Expression to be analysed
53
74
  :param release_id: ID of the release used. If None, gathers the live release
75
+ :param dpm_version: Version code of the release used.
54
76
  Used only in DPM-ML generation
55
77
  :return if Return_data is False, any Symbol, else data extracted from DB based on operands cell references
56
78
  """
57
79
 
80
+ if release_id is not None and dpm_version is not None:
81
+ raise click.UsageError("Cannot provide both --release-id and --dpm-version")
82
+
58
83
  error_code = ""
59
84
  validation_type = STATUS_UNKNOWN
60
85
 
61
- api = API()
86
+ semantic_api = SemanticAPI()
87
+
88
+ if dpm_version:
89
+ from py_dpm.models import Release
90
+
91
+ release_id = (
92
+ semantic_api.session.query(Release.releaseid)
93
+ .filter(Release.code == dpm_version)
94
+ .scalar()
95
+ )
96
+ if release_id is None:
97
+ console.print(
98
+ f"Error: DPM version '{dpm_version}' not found.", style="bold red"
99
+ )
100
+ sys.exit(1)
101
+
62
102
  try:
63
103
  validation_type = "OTHER"
64
- api.semantic_validation(expression)
65
- status = 200
66
- message_error = ''
104
+ # Validate using the semantic API with release_id support
105
+ result: SemanticValidationResult = semantic_api.validate_expression(
106
+ expression, release_id=release_id
107
+ )
108
+
109
+ if result.is_valid:
110
+ status = 200
111
+ message_error = ""
112
+ else:
113
+ status = 500
114
+ message_error = result.error_message
115
+ error_code = result.error_code or 1
116
+
67
117
  except Exception as error:
68
118
  status = 500
69
119
  message_error = str(error)
70
120
  error_code = 1
121
+
122
+ # Clean up resources
123
+ # semantic_api destructor handles this, but we can be explicit if needed
124
+
71
125
  message_response = {
72
126
  ERROR: message_error,
73
127
  ERROR_CODE: error_code,
74
128
  VALIDATION_TYPE: validation_type,
75
129
  }
76
- api.session.close()
130
+
77
131
  if error_code and status == 500:
78
- console.print(f"Semantic validation failed for expression: {expression}.", style="bold red")
132
+ console.print(
133
+ f"Semantic validation failed for expression: {expression}.",
134
+ style="bold red",
135
+ )
136
+ if message_error:
137
+ console.print(f"Error: {message_error}", style="red")
79
138
  else:
80
139
  console.log(f"Semantic validation completed for expression: {expression}.")
81
140
  console.print(f"Status: {status}", style="bold green")
82
141
  return status
83
142
 
143
+
84
144
  @main.command()
85
- @click.argument('expression', type=str)
145
+ @click.argument("expression", type=str)
86
146
  def syntax(expression: str):
87
147
  """Perform syntactic analysis on a DPM expression."""
88
148
 
@@ -106,11 +166,21 @@ def syntax(expression: str):
106
166
 
107
167
 
108
168
  @main.command()
109
- @click.argument('expression', type=str, required=False)
110
- @click.option('--operation-vid', type=int, help='Operation version ID to associate scopes with')
111
- @click.option('--tables', type=str, help='Comma-separated table VIDs (for low-level mode)')
112
- @click.option('--preconditions', type=str, help='Comma-separated precondition item codes')
113
- @click.option('--release-id', type=int, help='Release ID to filter modules (defaults to last release)')
169
+ @click.argument("expression", type=str, required=False)
170
+ @click.option(
171
+ "--operation-vid", type=int, help="Operation version ID to associate scopes with"
172
+ )
173
+ @click.option(
174
+ "--tables", type=str, help="Comma-separated table VIDs (for low-level mode)"
175
+ )
176
+ @click.option(
177
+ "--preconditions", type=str, help="Comma-separated precondition item codes"
178
+ )
179
+ @click.option(
180
+ "--release-id",
181
+ type=int,
182
+ help="Release ID to filter modules (defaults to last release)",
183
+ )
114
184
  def calculate_scopes(expression, operation_vid, tables, preconditions, release_id):
115
185
  """
116
186
  Calculate operation scopes from a DPM-XL expression or table VIDs.
@@ -133,12 +203,14 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
133
203
  expression=expression,
134
204
  operation_version_id=operation_vid,
135
205
  release_id=release_id,
136
- read_only=True
206
+ read_only=True,
137
207
  )
138
208
  elif tables:
139
209
  # Low-level mode with table VIDs
140
- table_vids = [int(t.strip()) for t in tables.split(',')]
141
- precondition_items = [p.strip() for p in preconditions.split(',')] if preconditions else []
210
+ table_vids = [int(t.strip()) for t in tables.split(",")]
211
+ precondition_items = (
212
+ [p.strip() for p in preconditions.split(",")] if preconditions else []
213
+ )
142
214
 
143
215
  # Always use read_only=True for CLI to prevent accidental database modifications
144
216
  result = api.calculate_scopes(
@@ -146,15 +218,20 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
146
218
  tables_vids=table_vids,
147
219
  precondition_items=precondition_items,
148
220
  release_id=release_id,
149
- read_only=True
221
+ read_only=True,
150
222
  )
151
223
  else:
152
- console.print("Error: Either EXPRESSION or --tables must be provided", style="bold red")
224
+ console.print(
225
+ "Error: Either EXPRESSION or --tables must be provided",
226
+ style="bold red",
227
+ )
153
228
  sys.exit(1)
154
229
 
155
230
  # Check for errors
156
231
  if result.has_error:
157
- console.print(f"Error calculating scopes: {result.error_message}", style="bold red")
232
+ console.print(
233
+ f"Error calculating scopes: {result.error_message}", style="bold red"
234
+ )
158
235
  sys.exit(1)
159
236
 
160
237
  # Display summary
@@ -168,9 +245,16 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
168
245
  summary_table.add_row("Total Scopes:", str(result.total_scopes))
169
246
  summary_table.add_row("Existing Scopes:", str(len(result.existing_scopes)))
170
247
  summary_table.add_row("New Scopes:", str(len(result.new_scopes)))
171
- summary_table.add_row("Cross-Module:", "Yes" if result.is_cross_module else "No")
172
- summary_table.add_row("Module Versions:", ', '.join(map(str, result.module_versions)))
173
- summary_table.add_row("Release ID:", str(result.release_id) if result.release_id else "Default (Last)")
248
+ summary_table.add_row(
249
+ "Cross-Module:", "Yes" if result.is_cross_module else "No"
250
+ )
251
+ summary_table.add_row(
252
+ "Module Versions:", ", ".join(map(str, result.module_versions))
253
+ )
254
+ summary_table.add_row(
255
+ "Release ID:",
256
+ str(result.release_id) if result.release_id else "Default (Last)",
257
+ )
174
258
 
175
259
  if result.expression:
176
260
  summary_table.add_row("Expression:", result.expression)
@@ -189,29 +273,37 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
189
273
  scopes_table.add_column("From Date", justify="center")
190
274
 
191
275
  for scope in result.existing_scopes:
192
- module_vids = [str(comp.modulevid) for comp in scope.operation_scope_compositions]
276
+ module_vids = [
277
+ str(comp.modulevid) for comp in scope.operation_scope_compositions
278
+ ]
193
279
  scope_type = "Cross-Module" if len(module_vids) > 1 else "Intra-Module"
194
- from_date = str(scope.fromsubmissiondate) if scope.fromsubmissiondate else "N/A"
280
+ from_date = (
281
+ str(scope.fromsubmissiondate) if scope.fromsubmissiondate else "N/A"
282
+ )
195
283
 
196
284
  scopes_table.add_row(
197
285
  str(scope.operationscopeid),
198
286
  "[yellow]Existing[/yellow]",
199
287
  ", ".join(module_vids),
200
288
  scope_type,
201
- from_date
289
+ from_date,
202
290
  )
203
291
 
204
292
  for scope in result.new_scopes:
205
- module_vids = [str(comp.modulevid) for comp in scope.operation_scope_compositions]
293
+ module_vids = [
294
+ str(comp.modulevid) for comp in scope.operation_scope_compositions
295
+ ]
206
296
  scope_type = "Cross-Module" if len(module_vids) > 1 else "Intra-Module"
207
- from_date = str(scope.fromsubmissiondate) if scope.fromsubmissiondate else "N/A"
297
+ from_date = (
298
+ str(scope.fromsubmissiondate) if scope.fromsubmissiondate else "N/A"
299
+ )
208
300
 
209
301
  scopes_table.add_row(
210
302
  "[dim]New (not committed)[/dim]",
211
303
  "[green]New[/green]",
212
304
  ", ".join(module_vids),
213
305
  scope_type,
214
- from_date
306
+ from_date,
215
307
  )
216
308
 
217
309
  console.print(scopes_table)
@@ -230,20 +322,28 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
230
322
  from py_dpm.models import ModuleVersion
231
323
 
232
324
  for module_vid in result.module_versions:
233
- module_df = ModuleVersion.get_module_version_by_vid(api.session, module_vid)
325
+ module_df = ModuleVersion.get_module_version_by_vid(
326
+ api.session, module_vid
327
+ )
234
328
  if not module_df.empty:
235
329
  module = module_df.iloc[0]
236
330
  modules_table.add_row(
237
- str(module['ModuleVID']),
238
- str(module['Code']),
239
- str(module['Name']),
240
- str(module['FromReferenceDate']),
241
- str(module['ToReferenceDate']) if module['ToReferenceDate'] is not pd.NaT else "Open"
331
+ str(module["ModuleVID"]),
332
+ str(module["Code"]),
333
+ str(module["Name"]),
334
+ str(module["FromReferenceDate"]),
335
+ (
336
+ str(module["ToReferenceDate"])
337
+ if module["ToReferenceDate"] is not pd.NaT
338
+ else "Open"
339
+ ),
242
340
  )
243
341
 
244
342
  console.print(modules_table)
245
343
 
246
- console.print("\n[bold green]✓ Scope calculation completed successfully[/bold green]\n")
344
+ console.print(
345
+ "\n[bold green]✓ Scope calculation completed successfully[/bold green]\n"
346
+ )
247
347
 
248
348
  except SemanticError as e:
249
349
  console.print(f"Semantic error: {str(e)}", style="bold red")
@@ -256,9 +356,13 @@ def calculate_scopes(expression, operation_vid, tables, preconditions, release_i
256
356
 
257
357
 
258
358
  @main.command()
259
- @click.argument('expression', type=str, required=False)
260
- @click.option('--operation-vid', type=int, help='Operation version ID')
261
- @click.option('--release-id', type=int, help='Release ID to filter modules (defaults to last release)')
359
+ @click.argument("expression", type=str, required=False)
360
+ @click.option("--operation-vid", type=int, help="Operation version ID")
361
+ @click.option(
362
+ "--release-id",
363
+ type=int,
364
+ help="Release ID to filter modules (defaults to last release)",
365
+ )
262
366
  def get_scopes_metadata(expression, operation_vid, release_id):
263
367
  """
264
368
  Get operation scopes with detailed module metadata.
@@ -276,15 +380,17 @@ def get_scopes_metadata(expression, operation_vid, release_id):
276
380
  # Determine mode: expression-based or operation-vid-based
277
381
  if expression:
278
382
  scopes = api.get_scopes_with_metadata_from_expression(
279
- expression=expression,
280
- release_id=release_id
383
+ expression=expression, release_id=release_id
281
384
  )
282
385
  mode = "expression"
283
386
  elif operation_vid:
284
387
  scopes = api.get_scopes_with_metadata(operation_version_id=operation_vid)
285
388
  mode = "operation_vid"
286
389
  else:
287
- console.print("Error: Either EXPRESSION or --operation-vid must be provided", style="bold red")
390
+ console.print(
391
+ "Error: Either EXPRESSION or --operation-vid must be provided",
392
+ style="bold red",
393
+ )
288
394
  sys.exit(1)
289
395
 
290
396
  # Display results
@@ -292,7 +398,9 @@ def get_scopes_metadata(expression, operation_vid, release_id):
292
398
  console.print("\n[yellow]No scopes found[/yellow]\n")
293
399
  return
294
400
 
295
- console.print(f"\n[bold cyan]Operation Scopes with Metadata[/bold cyan] (Total: {len(scopes)})")
401
+ console.print(
402
+ f"\n[bold cyan]Operation Scopes with Metadata[/bold cyan] (Total: {len(scopes)})"
403
+ )
296
404
  console.print("=" * 80)
297
405
 
298
406
  for idx, scope in enumerate(scopes, 1):
@@ -314,7 +422,9 @@ def get_scopes_metadata(expression, operation_vid, release_id):
314
422
 
315
423
  # Module versions
316
424
  if scope.module_versions:
317
- console.print(f"\n [bold]Modules ({len(scope.module_versions)}):[/bold]")
425
+ console.print(
426
+ f"\n [bold]Modules ({len(scope.module_versions)}):[/bold]"
427
+ )
318
428
 
319
429
  modules_table = Table(show_header=True, header_style="bold magenta")
320
430
  modules_table.add_column("Module VID", justify="right")
@@ -328,15 +438,29 @@ def get_scopes_metadata(expression, operation_vid, release_id):
328
438
  modules_table.add_row(
329
439
  str(module.module_vid),
330
440
  module.code,
331
- module.name[:40] + "..." if len(module.name) > 40 else module.name,
441
+ (
442
+ module.name[:40] + "..."
443
+ if len(module.name) > 40
444
+ else module.name
445
+ ),
332
446
  module.version_number,
333
- str(module.from_reference_date) if module.from_reference_date else "N/A",
334
- str(module.to_reference_date) if module.to_reference_date else "Open"
447
+ (
448
+ str(module.from_reference_date)
449
+ if module.from_reference_date
450
+ else "N/A"
451
+ ),
452
+ (
453
+ str(module.to_reference_date)
454
+ if module.to_reference_date
455
+ else "Open"
456
+ ),
335
457
  )
336
458
 
337
459
  console.print(modules_table)
338
460
 
339
- console.print("\n[bold green]✓ Scopes metadata retrieved successfully[/bold green]\n")
461
+ console.print(
462
+ "\n[bold green]✓ Scopes metadata retrieved successfully[/bold green]\n"
463
+ )
340
464
 
341
465
  except Exception as e:
342
466
  console.print(f"Error: {str(e)}", style="bold red")
@@ -346,9 +470,13 @@ def get_scopes_metadata(expression, operation_vid, release_id):
346
470
 
347
471
 
348
472
  @main.command()
349
- @click.argument('expression', type=str, required=False)
350
- @click.option('--operation-vid', type=int, help='Operation version ID')
351
- @click.option('--release-id', type=int, help='Release ID to filter modules (defaults to last release)')
473
+ @click.argument("expression", type=str, required=False)
474
+ @click.option("--operation-vid", type=int, help="Operation version ID")
475
+ @click.option(
476
+ "--release-id",
477
+ type=int,
478
+ help="Release ID to filter modules (defaults to last release)",
479
+ )
352
480
  def get_tables_metadata(expression, operation_vid, release_id):
353
481
  """
354
482
  Get tables from operation scopes with metadata.
@@ -366,13 +494,15 @@ def get_tables_metadata(expression, operation_vid, release_id):
366
494
  # Determine mode: expression-based or operation-vid-based
367
495
  if expression:
368
496
  tables = api.get_tables_with_metadata_from_expression(
369
- expression=expression,
370
- release_id=release_id
497
+ expression=expression, release_id=release_id
371
498
  )
372
499
  elif operation_vid:
373
500
  tables = api.get_tables_with_metadata(operation_version_id=operation_vid)
374
501
  else:
375
- console.print("Error: Either EXPRESSION or --operation-vid must be provided", style="bold red")
502
+ console.print(
503
+ "Error: Either EXPRESSION or --operation-vid must be provided",
504
+ style="bold red",
505
+ )
376
506
  sys.exit(1)
377
507
 
378
508
  # Display results
@@ -380,7 +510,9 @@ def get_tables_metadata(expression, operation_vid, release_id):
380
510
  console.print("\n[yellow]No tables found[/yellow]\n")
381
511
  return
382
512
 
383
- console.print(f"\n[bold cyan]Tables with Metadata[/bold cyan] (Total: {len(tables)})")
513
+ console.print(
514
+ f"\n[bold cyan]Tables with Metadata[/bold cyan] (Total: {len(tables)})"
515
+ )
384
516
  console.print("=" * 80)
385
517
 
386
518
  tables_table = Table(show_header=True, header_style="bold magenta")
@@ -391,17 +523,23 @@ def get_tables_metadata(expression, operation_vid, release_id):
391
523
 
392
524
  for table in tables:
393
525
  # Truncate long descriptions
394
- description = table.description[:60] + "..." if len(table.description) > 60 else table.description
526
+ description = (
527
+ table.description[:60] + "..."
528
+ if len(table.description) > 60
529
+ else table.description
530
+ )
395
531
 
396
532
  tables_table.add_row(
397
533
  str(table.table_vid),
398
534
  table.code,
399
535
  table.name[:40] + "..." if len(table.name) > 40 else table.name,
400
- description
536
+ description,
401
537
  )
402
538
 
403
539
  console.print(tables_table)
404
- console.print("\n[bold green]✓ Tables metadata retrieved successfully[/bold green]\n")
540
+ console.print(
541
+ "\n[bold green]✓ Tables metadata retrieved successfully[/bold green]\n"
542
+ )
405
543
 
406
544
  except Exception as e:
407
545
  console.print(f"Error: {str(e)}", style="bold red")
@@ -411,10 +549,14 @@ def get_tables_metadata(expression, operation_vid, release_id):
411
549
 
412
550
 
413
551
  @main.command()
414
- @click.argument('expression', type=str, required=False)
415
- @click.option('--operation-vid', type=int, help='Operation version ID')
416
- @click.option('--table-vid', type=int, help='Filter by specific table VID')
417
- @click.option('--release-id', type=int, help='Release ID to filter modules (defaults to last release)')
552
+ @click.argument("expression", type=str, required=False)
553
+ @click.option("--operation-vid", type=int, help="Operation version ID")
554
+ @click.option("--table-vid", type=int, help="Filter by specific table VID")
555
+ @click.option(
556
+ "--release-id",
557
+ type=int,
558
+ help="Release ID to filter modules (defaults to last release)",
559
+ )
418
560
  def get_headers_metadata(expression, operation_vid, table_vid, release_id):
419
561
  """
420
562
  Get headers from operation scopes with metadata.
@@ -433,17 +575,17 @@ def get_headers_metadata(expression, operation_vid, table_vid, release_id):
433
575
  # Determine mode: expression-based or operation-vid-based
434
576
  if expression:
435
577
  headers = api.get_headers_with_metadata_from_expression(
436
- expression=expression,
437
- table_vid=table_vid,
438
- release_id=release_id
578
+ expression=expression, table_vid=table_vid, release_id=release_id
439
579
  )
440
580
  elif operation_vid:
441
581
  headers = api.get_headers_with_metadata(
442
- operation_version_id=operation_vid,
443
- table_vid=table_vid
582
+ operation_version_id=operation_vid, table_vid=table_vid
444
583
  )
445
584
  else:
446
- console.print("Error: Either EXPRESSION or --operation-vid must be provided", style="bold red")
585
+ console.print(
586
+ "Error: Either EXPRESSION or --operation-vid must be provided",
587
+ style="bold red",
588
+ )
447
589
  sys.exit(1)
448
590
 
449
591
  # Display results
@@ -451,7 +593,9 @@ def get_headers_metadata(expression, operation_vid, table_vid, release_id):
451
593
  console.print("\n[yellow]No headers found[/yellow]\n")
452
594
  return
453
595
 
454
- console.print(f"\n[bold cyan]Headers with Metadata[/bold cyan] (Total: {len(headers)})")
596
+ console.print(
597
+ f"\n[bold cyan]Headers with Metadata[/bold cyan] (Total: {len(headers)})"
598
+ )
455
599
  if table_vid:
456
600
  console.print(f"[dim]Filtered by Table VID: {table_vid}[/dim]")
457
601
  console.print("=" * 80)
@@ -469,11 +613,13 @@ def get_headers_metadata(expression, operation_vid, table_vid, release_id):
469
613
  str(header.table_vid) if header.table_vid else "N/A",
470
614
  header.code,
471
615
  header.label[:50] + "..." if len(header.label) > 50 else header.label,
472
- header.header_type
616
+ header.header_type,
473
617
  )
474
618
 
475
619
  console.print(headers_table)
476
- console.print("\n[bold green]✓ Headers metadata retrieved successfully[/bold green]\n")
620
+ console.print(
621
+ "\n[bold green]✓ Headers metadata retrieved successfully[/bold green]\n"
622
+ )
477
623
 
478
624
  except Exception as e:
479
625
  console.print(f"Error: {str(e)}", style="bold red")
@@ -482,5 +628,5 @@ def get_headers_metadata(expression, operation_vid, table_vid, release_id):
482
628
  api.session.close()
483
629
 
484
630
 
485
- if __name__ == '__main__':
486
- main()
631
+ if __name__ == "__main__":
632
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydpm_xl
3
- Version: 0.1.39rc29
3
+ Version: 0.1.39rc31
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
@@ -25,7 +25,7 @@ Requires-Dist: antlr4-python3-runtime<4.9.3,>=4.9.2
25
25
  Requires-Dist: pyodbc<5.2.0,>=5.1.0
26
26
  Requires-Dist: click<8.2.0,>=8.1.6
27
27
  Requires-Dist: python-dotenv<1.1.0,>=1.0.1
28
- Requires-Dist: psycopg2<3.0.0,>=2.9.0
28
+ Requires-Dist: psycopg2-binary<3.0.0,>=2.9.0
29
29
  Provides-Extra: test
30
30
  Requires-Dist: pytest<8.0.0,>=7.4.0; extra == "test"
31
31
  Requires-Dist: pytest-cov<5.0.0,>=4.1.0; extra == "test"
@@ -1,5 +1,5 @@
1
- py_dpm/__init__.py,sha256=mHCg5ux9mRv69Wp72Tulb994Kb5PQhpr_TS_pl8WMwU,1863
2
- py_dpm/client.py,sha256=DspAS6lVLh62nLlyOy4UEBy00GLR0bb9Wbd6hOZ6bHY,19491
1
+ py_dpm/__init__.py,sha256=Ka4t1a-16Iht1Iqn-XfNPTJuUt9FbyFX_3SCDdabdZQ,1863
2
+ py_dpm/client.py,sha256=f-D3-5-tNpBM2fr5H8ImdwZM5hyuqfCXJRJd66euHU8,22279
3
3
  py_dpm/data_handlers.py,sha256=_aJqkyflJr1oNsOQ24nB9yzfrpxu28NPrpoil2aAm58,4395
4
4
  py_dpm/db_utils.py,sha256=f0da6FT7HusGpLfGen4fceSvcojW4bzrp5sucgt6XVQ,7822
5
5
  py_dpm/migration.py,sha256=ivO_ObvKzVomTns6qfo-o5FuciWxkXbMd_gJ4_tu7Xc,14110
@@ -35,7 +35,7 @@ py_dpm/Operators/TimeOperators.py,sha256=klcFIYFonGVRNhr66Ipu1lwOr9CxzDGIbA3QQLm
35
35
  py_dpm/Operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  py_dpm/Utils/ValidationsGenerationUtils.py,sha256=1yINJ_ANqr9BbIDdPm4WxRG2EQu0yaRICikYG1nIhzs,22652
37
37
  py_dpm/Utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- py_dpm/Utils/ast_serialization.py,sha256=ldCTenu-BMIccOzhSYtKupmEVzjWdXSAr-XUWfwNT9A,31961
38
+ py_dpm/Utils/ast_serialization.py,sha256=c23qwwGtjDJgOxoOo41g4p7CYdbkVPz3q_Q6KssMSIU,32462
39
39
  py_dpm/Utils/operands_mapping.py,sha256=CDWvoKxh-nBcEKMxrUmMnaCveE8dExjeuFUa-lgK8Rg,2239
40
40
  py_dpm/Utils/operator_mapping.py,sha256=SK8utWaUaQJybSmErkF_2blzUIgiVOUNGFozIk7dBg8,1926
41
41
  py_dpm/Utils/tokens.py,sha256=VRIrPDi5ttwgH-on5Qt4-l4ho4bLA755-nfTalponcA,3496
@@ -47,7 +47,7 @@ py_dpm/ValidationsGeneration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
47
47
  py_dpm/ValidationsGeneration/auxiliary_functions.py,sha256=4msDLINbwNb4gTc05x_351eb_VIra1H0lWE1hPijlVI,4187
48
48
  py_dpm/api/__init__.py,sha256=41ILMv8oQ9mmw_zkIhRHVBnfYi1OQYEAFWqyqc3O8_U,6986
49
49
  py_dpm/api/ast_generator.py,sha256=HjosdS1CkguPJDjp2JUqzO7WaRCS2GjXKuFgQXQkkPg,16143
50
- py_dpm/api/complete_ast.py,sha256=_PMwIGwUqSzw8kPo0mgHQPrl5V9oTMIJlTY6Nq2jkx8,28953
50
+ py_dpm/api/complete_ast.py,sha256=8hMPoVyVOHXIR5Qm8TGUuva8rONntN-TJoFQHU0ElW8,29892
51
51
  py_dpm/api/data_dictionary.py,sha256=0k-tz0DNP1gZXlCA4EX9BoWHq2RB7nDnlsxRfbxNwow,33083
52
52
  py_dpm/api/data_dictionary_validation.py,sha256=OtvErzLtDMryoxdO5ktACjvGWeDEqG6SXuvuJyzKdys,23495
53
53
  py_dpm/api/migration.py,sha256=LW8V-sq9iBAuXUHWPGqWl-tvxz2vAGUvIYyZLtgfjKA,2512
@@ -88,9 +88,9 @@ py_dpm/views/precondition_info.sql,sha256=l24U8c6NtwFcUDFwb2FqH9h0xgpoi9WbvsKUgJ
88
88
  py_dpm/views/report_type_operand_reference_info.sql,sha256=_lBK9HQILk9yLD7cW9Ieud-vfTc-KQiU3WGAnN6LuNY,977
89
89
  py_dpm/views/subcategory_info.sql,sha256=V8UZiGjicmOWfBEHX2v6ktSMR4r_f1YiGD_LeN9Gn6o,812
90
90
  py_dpm/views/table_info.sql,sha256=htRXIVJya44CZAqjO5sVaJJwiQBQ30oiLFFu5DiBpR4,793
91
- pydpm_xl-0.1.39rc29.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
92
- pydpm_xl-0.1.39rc29.dist-info/METADATA,sha256=_KHZGTtkz50qVANC29LNEe63THXegD9lnUkfCWYkZWE,1592
93
- pydpm_xl-0.1.39rc29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
- pydpm_xl-0.1.39rc29.dist-info/entry_points.txt,sha256=W9j5xCzgvdfzOKSfEpkk2jSRhEcpmXZUVMwHrsEFAbY,45
95
- pydpm_xl-0.1.39rc29.dist-info/top_level.txt,sha256=495PvWZRoKl2NvbQU25W7dqWIBHqY-mFMPt83uxPpcM,7
96
- pydpm_xl-0.1.39rc29.dist-info/RECORD,,
91
+ pydpm_xl-0.1.39rc31.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
92
+ pydpm_xl-0.1.39rc31.dist-info/METADATA,sha256=TLfn-srr0mIT5UNqwsL_46ewXN9GsuO4M_RgaiueiF0,1599
93
+ pydpm_xl-0.1.39rc31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
+ pydpm_xl-0.1.39rc31.dist-info/entry_points.txt,sha256=W9j5xCzgvdfzOKSfEpkk2jSRhEcpmXZUVMwHrsEFAbY,45
95
+ pydpm_xl-0.1.39rc31.dist-info/top_level.txt,sha256=495PvWZRoKl2NvbQU25W7dqWIBHqY-mFMPt83uxPpcM,7
96
+ pydpm_xl-0.1.39rc31.dist-info/RECORD,,