pydpm_xl 0.1.39rc32__py3-none-any.whl → 0.2.0__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} +191 -167
- py_dpm/api/dpm_xl/semantic.py +354 -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} +8 -8
- 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} +2 -2
- 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.0.dist-info/METADATA +278 -0
- pydpm_xl-0.2.0.dist-info/RECORD +88 -0
- pydpm_xl-0.2.0.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.0.dist-info}/WHEEL +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
from typing import List, Optional, Any
|
|
1
|
+
from typing import List, Optional, Dict, Any
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from datetime import date
|
|
4
4
|
|
|
5
|
-
from sqlalchemy import and_, or_
|
|
6
5
|
from antlr4 import CommonTokenStream, InputStream
|
|
7
|
-
from py_dpm.grammar.
|
|
8
|
-
from py_dpm.grammar.
|
|
9
|
-
from py_dpm.grammar.
|
|
10
|
-
from py_dpm.
|
|
11
|
-
from py_dpm.
|
|
12
|
-
from py_dpm.
|
|
13
|
-
from py_dpm.
|
|
14
|
-
from py_dpm.models import
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
from py_dpm.dpm_xl.grammar.generated.dpm_xlLexer import dpm_xlLexer
|
|
7
|
+
from py_dpm.dpm_xl.grammar.generated.dpm_xlParser import dpm_xlParser
|
|
8
|
+
from py_dpm.dpm_xl.grammar.generated.listeners import DPMErrorListener
|
|
9
|
+
from py_dpm.dpm_xl.ast.constructor import ASTVisitor
|
|
10
|
+
from py_dpm.dpm_xl.ast.operands import OperandsChecking
|
|
11
|
+
from py_dpm.dpm_xl.ast.nodes import PreconditionItem
|
|
12
|
+
from py_dpm.dpm_xl.utils.scopes_calculator import OperationScopeService
|
|
13
|
+
from py_dpm.dpm.models import (
|
|
14
|
+
ModuleVersion,
|
|
15
|
+
OperationScope,
|
|
16
|
+
TableVersion,
|
|
17
|
+
HeaderVersion,
|
|
18
|
+
TableVersionHeader,
|
|
19
|
+
Framework,
|
|
20
|
+
Module,
|
|
21
|
+
)
|
|
22
|
+
from py_dpm.dpm.utils import get_session, get_engine
|
|
23
|
+
from py_dpm.exceptions.exceptions import SemanticError
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
@dataclass
|
|
@@ -32,6 +39,7 @@ class OperationScopeResult:
|
|
|
32
39
|
release_id (Optional[int]): Release ID used for filtering
|
|
33
40
|
expression (Optional[str]): Original expression if calculated from expression
|
|
34
41
|
"""
|
|
42
|
+
|
|
35
43
|
existing_scopes: List[OperationScope] = field(default_factory=list)
|
|
36
44
|
new_scopes: List[OperationScope] = field(default_factory=list)
|
|
37
45
|
total_scopes: int = 0
|
|
@@ -43,77 +51,6 @@ class OperationScopeResult:
|
|
|
43
51
|
expression: Optional[str] = None
|
|
44
52
|
|
|
45
53
|
|
|
46
|
-
@dataclass
|
|
47
|
-
class ModuleVersionInfo:
|
|
48
|
-
"""
|
|
49
|
-
Module version information with metadata.
|
|
50
|
-
|
|
51
|
-
Attributes:
|
|
52
|
-
module_vid (int): Module version ID
|
|
53
|
-
code (str): Module code
|
|
54
|
-
name (str): Module name
|
|
55
|
-
description (str): Module description
|
|
56
|
-
version_number (str): Version number
|
|
57
|
-
from_reference_date (Optional[date]): Start date of validity
|
|
58
|
-
to_reference_date (Optional[date]): End date of validity
|
|
59
|
-
"""
|
|
60
|
-
module_vid: int
|
|
61
|
-
code: str
|
|
62
|
-
name: str
|
|
63
|
-
description: str
|
|
64
|
-
version_number: str
|
|
65
|
-
from_reference_date: Optional[date] = None
|
|
66
|
-
to_reference_date: Optional[date] = None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@dataclass
|
|
70
|
-
class TableVersionInfo:
|
|
71
|
-
"""
|
|
72
|
-
Table version information with metadata.
|
|
73
|
-
|
|
74
|
-
Attributes:
|
|
75
|
-
table_vid (int): Table version ID
|
|
76
|
-
code (str): Table code
|
|
77
|
-
name (str): Table name
|
|
78
|
-
description (str): Table description
|
|
79
|
-
module_vid (Optional[int]): Module version ID
|
|
80
|
-
module_code (Optional[str]): Module code
|
|
81
|
-
module_name (Optional[str]): Module name
|
|
82
|
-
module_version (Optional[str]): Module version number
|
|
83
|
-
"""
|
|
84
|
-
table_vid: int
|
|
85
|
-
code: str
|
|
86
|
-
name: str
|
|
87
|
-
description: str
|
|
88
|
-
module_vid: Optional[int] = None
|
|
89
|
-
module_code: Optional[str] = None
|
|
90
|
-
module_name: Optional[str] = None
|
|
91
|
-
module_version: Optional[str] = None
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@dataclass
|
|
95
|
-
class HeaderVersionInfo:
|
|
96
|
-
"""
|
|
97
|
-
Header version information with metadata.
|
|
98
|
-
|
|
99
|
-
Attributes:
|
|
100
|
-
header_vid (int): Header version ID
|
|
101
|
-
code (str): Header code
|
|
102
|
-
label (str): Header label/name
|
|
103
|
-
header_type (str): Type of header (row/column/sheet)
|
|
104
|
-
table_vid (Optional[int]): Associated table version ID (if queried with table context)
|
|
105
|
-
table_code (Optional[str]): Associated table code
|
|
106
|
-
table_name (Optional[str]): Associated table name
|
|
107
|
-
"""
|
|
108
|
-
header_vid: int
|
|
109
|
-
code: str
|
|
110
|
-
label: str
|
|
111
|
-
header_type: str
|
|
112
|
-
table_vid: Optional[int] = None
|
|
113
|
-
table_code: Optional[str] = None
|
|
114
|
-
table_name: Optional[str] = None
|
|
115
|
-
|
|
116
|
-
|
|
117
54
|
@dataclass
|
|
118
55
|
class FrameworkInfo:
|
|
119
56
|
"""
|
|
@@ -125,6 +62,7 @@ class FrameworkInfo:
|
|
|
125
62
|
name (str): Framework name
|
|
126
63
|
description (str): Framework description
|
|
127
64
|
"""
|
|
65
|
+
|
|
128
66
|
framework_id: int
|
|
129
67
|
code: str
|
|
130
68
|
name: str
|
|
@@ -144,12 +82,13 @@ class OperationScopeDetailedInfo:
|
|
|
144
82
|
from_submission_date (Optional[date]): Start date for submission
|
|
145
83
|
module_versions (List[ModuleVersionInfo]): List of modules with metadata
|
|
146
84
|
"""
|
|
85
|
+
|
|
147
86
|
operation_scope_id: int
|
|
148
87
|
operation_vid: int
|
|
149
88
|
is_active: int
|
|
150
89
|
severity: str
|
|
151
90
|
from_submission_date: Optional[date]
|
|
152
|
-
module_versions: List[
|
|
91
|
+
module_versions: List[Dict[str, Any]] = field(default_factory=list)
|
|
153
92
|
|
|
154
93
|
|
|
155
94
|
class OperationScopesAPI:
|
|
@@ -160,7 +99,9 @@ class OperationScopesAPI:
|
|
|
160
99
|
in a DPM-XL operation based on table references and precondition items.
|
|
161
100
|
"""
|
|
162
101
|
|
|
163
|
-
def __init__(
|
|
102
|
+
def __init__(
|
|
103
|
+
self, database_path: Optional[str] = None, connection_url: Optional[str] = None
|
|
104
|
+
):
|
|
164
105
|
"""
|
|
165
106
|
Initialize the Operation Scopes API.
|
|
166
107
|
|
|
@@ -175,7 +116,7 @@ class OperationScopesAPI:
|
|
|
175
116
|
if connection_url:
|
|
176
117
|
# Create isolated engine and session for the provided connection URL
|
|
177
118
|
from sqlalchemy.orm import sessionmaker
|
|
178
|
-
from py_dpm.
|
|
119
|
+
from py_dpm.dpm.utils import create_engine_from_url
|
|
179
120
|
|
|
180
121
|
# Create engine for the connection URL (supports SQLite, PostgreSQL, MySQL, etc.)
|
|
181
122
|
self.engine = create_engine_from_url(connection_url)
|
|
@@ -212,7 +153,7 @@ class OperationScopesAPI:
|
|
|
212
153
|
expression: str,
|
|
213
154
|
operation_version_id: Optional[int] = None,
|
|
214
155
|
release_id: Optional[int] = None,
|
|
215
|
-
read_only: bool = False
|
|
156
|
+
read_only: bool = False,
|
|
216
157
|
) -> OperationScopeResult:
|
|
217
158
|
"""
|
|
218
159
|
Calculate operation scopes from a DPM-XL expression.
|
|
@@ -259,19 +200,26 @@ class OperationScopesAPI:
|
|
|
259
200
|
has_error=True,
|
|
260
201
|
error_message="Syntax errors detected in expression",
|
|
261
202
|
expression=expression,
|
|
262
|
-
release_id=release_id
|
|
203
|
+
release_id=release_id,
|
|
263
204
|
)
|
|
264
205
|
|
|
265
206
|
# Generate AST
|
|
266
207
|
ast = self.visitor.visit(parse_tree)
|
|
267
208
|
|
|
268
209
|
# Perform operands checking to get data
|
|
269
|
-
oc = OperandsChecking(
|
|
210
|
+
oc = OperandsChecking(
|
|
211
|
+
session=self.session,
|
|
212
|
+
expression=expression,
|
|
213
|
+
ast=ast,
|
|
214
|
+
release_id=release_id,
|
|
215
|
+
)
|
|
270
216
|
|
|
271
217
|
# Extract table VIDs, precondition items, and table codes from AST
|
|
272
218
|
# Always extract table codes for cross-version scope calculation
|
|
273
219
|
# (release_id will be determined later if None)
|
|
274
|
-
table_vids, precondition_items, table_codes = self._extract_vids_from_ast(
|
|
220
|
+
table_vids, precondition_items, table_codes = self._extract_vids_from_ast(
|
|
221
|
+
ast, oc.data, extract_codes=True
|
|
222
|
+
)
|
|
275
223
|
|
|
276
224
|
# Calculate scopes using the low-level API
|
|
277
225
|
return self.calculate_scopes(
|
|
@@ -281,7 +229,7 @@ class OperationScopesAPI:
|
|
|
281
229
|
release_id=release_id,
|
|
282
230
|
expression=expression,
|
|
283
231
|
table_codes=table_codes,
|
|
284
|
-
read_only=read_only
|
|
232
|
+
read_only=read_only,
|
|
285
233
|
)
|
|
286
234
|
|
|
287
235
|
except SemanticError as e:
|
|
@@ -289,17 +237,19 @@ class OperationScopesAPI:
|
|
|
289
237
|
has_error=True,
|
|
290
238
|
error_message=str(e),
|
|
291
239
|
expression=expression,
|
|
292
|
-
release_id=release_id
|
|
240
|
+
release_id=release_id,
|
|
293
241
|
)
|
|
294
242
|
except Exception as e:
|
|
295
243
|
return OperationScopeResult(
|
|
296
244
|
has_error=True,
|
|
297
245
|
error_message=f"Unexpected error: {str(e)}",
|
|
298
246
|
expression=expression,
|
|
299
|
-
release_id=release_id
|
|
247
|
+
release_id=release_id,
|
|
300
248
|
)
|
|
301
249
|
|
|
302
|
-
def _extract_vids_from_ast(
|
|
250
|
+
def _extract_vids_from_ast(
|
|
251
|
+
self, ast, data, extract_codes=False
|
|
252
|
+
) -> tuple[List[int], List[str], List[str]]:
|
|
303
253
|
"""
|
|
304
254
|
Extract table VIDs, table codes, and precondition items from OperandsChecking data.
|
|
305
255
|
|
|
@@ -323,12 +273,12 @@ class OperationScopesAPI:
|
|
|
323
273
|
precondition_items = []
|
|
324
274
|
|
|
325
275
|
# Extract unique table VIDs from the data DataFrame
|
|
326
|
-
if
|
|
327
|
-
table_vids = data[
|
|
276
|
+
if "table_vid" in data.columns:
|
|
277
|
+
table_vids = data["table_vid"].dropna().unique().astype(int).tolist()
|
|
328
278
|
|
|
329
279
|
# If requested, also extract table codes for cross-version scope calculation
|
|
330
280
|
if extract_codes and table_vids:
|
|
331
|
-
from py_dpm.models import TableVersion
|
|
281
|
+
from py_dpm.dpm.models import TableVersion
|
|
332
282
|
|
|
333
283
|
# Get table codes for the VIDs
|
|
334
284
|
table_codes_query = (
|
|
@@ -350,15 +300,19 @@ class OperationScopesAPI:
|
|
|
350
300
|
precondition_items.append(precondition_code)
|
|
351
301
|
|
|
352
302
|
# Recursively process child nodes
|
|
353
|
-
if hasattr(node,
|
|
303
|
+
if hasattr(node, "__dict__"):
|
|
354
304
|
for attr_value in vars(node).values():
|
|
355
|
-
if hasattr(attr_value,
|
|
356
|
-
|
|
305
|
+
if hasattr(attr_value, "__class__") and hasattr(
|
|
306
|
+
attr_value.__class__, "__module__"
|
|
307
|
+
):
|
|
308
|
+
if "ASTObjects" in attr_value.__class__.__module__:
|
|
357
309
|
walk_ast(attr_value)
|
|
358
310
|
elif isinstance(attr_value, list):
|
|
359
311
|
for item in attr_value:
|
|
360
|
-
if hasattr(item,
|
|
361
|
-
|
|
312
|
+
if hasattr(item, "__class__") and hasattr(
|
|
313
|
+
item.__class__, "__module__"
|
|
314
|
+
):
|
|
315
|
+
if "ASTObjects" in item.__class__.__module__:
|
|
362
316
|
walk_ast(item)
|
|
363
317
|
|
|
364
318
|
walk_ast(ast)
|
|
@@ -372,7 +326,7 @@ class OperationScopesAPI:
|
|
|
372
326
|
release_id: Optional[int] = None,
|
|
373
327
|
expression: Optional[str] = None,
|
|
374
328
|
table_codes: Optional[List[str]] = None,
|
|
375
|
-
read_only: bool = False
|
|
329
|
+
read_only: bool = False,
|
|
376
330
|
) -> OperationScopeResult:
|
|
377
331
|
"""
|
|
378
332
|
Calculate operation scopes from table VIDs and precondition items.
|
|
@@ -412,8 +366,7 @@ class OperationScopesAPI:
|
|
|
412
366
|
|
|
413
367
|
# Create service and calculate scopes
|
|
414
368
|
service = OperationScopeService(
|
|
415
|
-
operation_version_id=temp_operation_version_id,
|
|
416
|
-
session=self.session
|
|
369
|
+
operation_version_id=temp_operation_version_id, session=self.session
|
|
417
370
|
)
|
|
418
371
|
|
|
419
372
|
# Use no_autoflush when not persisting to avoid premature flush attempts
|
|
@@ -422,14 +375,13 @@ class OperationScopesAPI:
|
|
|
422
375
|
tables_vids=tables_vids,
|
|
423
376
|
precondition_items=precondition_items,
|
|
424
377
|
release_id=release_id,
|
|
425
|
-
table_codes=table_codes
|
|
378
|
+
table_codes=table_codes,
|
|
426
379
|
)
|
|
427
380
|
|
|
428
381
|
# Analyze results
|
|
429
382
|
all_scopes = existing_scopes + new_scopes
|
|
430
383
|
is_cross_module = any(
|
|
431
|
-
len(scope.operation_scope_compositions) > 1
|
|
432
|
-
for scope in all_scopes
|
|
384
|
+
len(scope.operation_scope_compositions) > 1 for scope in all_scopes
|
|
433
385
|
)
|
|
434
386
|
|
|
435
387
|
# Collect unique module versions
|
|
@@ -454,7 +406,7 @@ class OperationScopesAPI:
|
|
|
454
406
|
has_error=False,
|
|
455
407
|
error_message=None,
|
|
456
408
|
release_id=release_id,
|
|
457
|
-
expression=expression
|
|
409
|
+
expression=expression,
|
|
458
410
|
)
|
|
459
411
|
|
|
460
412
|
except SemanticError as e:
|
|
@@ -463,7 +415,7 @@ class OperationScopesAPI:
|
|
|
463
415
|
has_error=True,
|
|
464
416
|
error_message=str(e),
|
|
465
417
|
release_id=release_id,
|
|
466
|
-
expression=expression
|
|
418
|
+
expression=expression,
|
|
467
419
|
)
|
|
468
420
|
except Exception as e:
|
|
469
421
|
self.session.rollback()
|
|
@@ -471,7 +423,7 @@ class OperationScopesAPI:
|
|
|
471
423
|
has_error=True,
|
|
472
424
|
error_message=f"Unexpected error: {str(e)}",
|
|
473
425
|
release_id=release_id,
|
|
474
|
-
expression=expression
|
|
426
|
+
expression=expression,
|
|
475
427
|
)
|
|
476
428
|
|
|
477
429
|
def get_existing_scopes(self, operation_version_id: int) -> List[OperationScope]:
|
|
@@ -539,7 +491,9 @@ class OperationScopesAPI:
|
|
|
539
491
|
except Exception:
|
|
540
492
|
return False
|
|
541
493
|
|
|
542
|
-
def get_scopes_with_metadata(
|
|
494
|
+
def get_scopes_with_metadata(
|
|
495
|
+
self, operation_version_id: int
|
|
496
|
+
) -> List[OperationScopeDetailedInfo]:
|
|
543
497
|
"""
|
|
544
498
|
Get operation scopes with detailed module metadata.
|
|
545
499
|
|
|
@@ -571,31 +525,33 @@ class OperationScopesAPI:
|
|
|
571
525
|
.first()
|
|
572
526
|
)
|
|
573
527
|
if module:
|
|
574
|
-
module_infos.append(
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
528
|
+
module_infos.append(
|
|
529
|
+
{
|
|
530
|
+
"module_vid": module.modulevid,
|
|
531
|
+
"code": module.code or "",
|
|
532
|
+
"name": module.name or "",
|
|
533
|
+
"description": module.description or "",
|
|
534
|
+
"version_number": module.versionnumber or "",
|
|
535
|
+
"from_reference_date": module.fromreferencedate,
|
|
536
|
+
"to_reference_date": module.toreferencedate,
|
|
537
|
+
}
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
result.append(
|
|
541
|
+
OperationScopeDetailedInfo(
|
|
542
|
+
operation_scope_id=scope.operationscopeid,
|
|
543
|
+
operation_vid=scope.operationvid,
|
|
544
|
+
is_active=scope.isactive,
|
|
545
|
+
severity=scope.severity or "",
|
|
546
|
+
from_submission_date=scope.fromsubmissiondate,
|
|
547
|
+
module_versions=module_infos,
|
|
548
|
+
)
|
|
549
|
+
)
|
|
592
550
|
|
|
593
551
|
return result
|
|
594
552
|
|
|
595
553
|
def get_scopes_with_metadata_from_expression(
|
|
596
|
-
self,
|
|
597
|
-
expression: str,
|
|
598
|
-
release_id: Optional[int] = None
|
|
554
|
+
self, expression: str, release_id: Optional[int] = None
|
|
599
555
|
) -> List[OperationScopeDetailedInfo]:
|
|
600
556
|
"""
|
|
601
557
|
Calculate operation scopes from expression and return with detailed metadata.
|
|
@@ -620,9 +576,7 @@ class OperationScopesAPI:
|
|
|
620
576
|
"""
|
|
621
577
|
# Calculate scopes in read-only mode
|
|
622
578
|
scope_result = self.calculate_scopes_from_expression(
|
|
623
|
-
expression=expression,
|
|
624
|
-
release_id=release_id,
|
|
625
|
-
read_only=True
|
|
579
|
+
expression=expression, release_id=release_id, read_only=True
|
|
626
580
|
)
|
|
627
581
|
|
|
628
582
|
if scope_result.has_error:
|
|
@@ -641,28 +595,34 @@ class OperationScopesAPI:
|
|
|
641
595
|
.first()
|
|
642
596
|
)
|
|
643
597
|
if module:
|
|
644
|
-
module_infos.append(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
598
|
+
module_infos.append(
|
|
599
|
+
{
|
|
600
|
+
"module_vid": module.modulevid,
|
|
601
|
+
"code": module.code or "",
|
|
602
|
+
"name": module.name or "",
|
|
603
|
+
"description": module.description or "",
|
|
604
|
+
"version_number": module.versionnumber or "",
|
|
605
|
+
"from_reference_date": module.fromreferencedate,
|
|
606
|
+
"to_reference_date": module.toreferencedate,
|
|
607
|
+
}
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
result.append(
|
|
611
|
+
OperationScopeDetailedInfo(
|
|
612
|
+
operation_scope_id=scope.operationscopeid,
|
|
613
|
+
operation_vid=scope.operationvid,
|
|
614
|
+
is_active=scope.isactive,
|
|
615
|
+
severity=scope.severity or "",
|
|
616
|
+
from_submission_date=scope.fromsubmissiondate,
|
|
617
|
+
module_versions=module_infos,
|
|
618
|
+
)
|
|
619
|
+
)
|
|
662
620
|
|
|
663
621
|
return result
|
|
664
622
|
|
|
665
|
-
def get_tables_with_metadata(
|
|
623
|
+
def get_tables_with_metadata(
|
|
624
|
+
self, operation_version_id: int
|
|
625
|
+
) -> List[Dict[str, Any]]:
|
|
666
626
|
"""
|
|
667
627
|
Get all tables involved in operation scopes with metadata.
|
|
668
628
|
|
|
@@ -670,14 +630,14 @@ class OperationScopesAPI:
|
|
|
670
630
|
operation_version_id (int): Operation version ID
|
|
671
631
|
|
|
672
632
|
Returns:
|
|
673
|
-
List[
|
|
633
|
+
List[Dict[str, Any]]: List of unique tables with metadata
|
|
674
634
|
|
|
675
635
|
Example:
|
|
676
636
|
>>> from py_dpm.api import OperationScopesAPI
|
|
677
637
|
>>> api = OperationScopesAPI()
|
|
678
638
|
>>> tables = api.get_tables_with_metadata(operation_version_id=1)
|
|
679
639
|
>>> for table in tables:
|
|
680
|
-
... print(f"{table
|
|
640
|
+
... print(f"{table['code']}: {table['name']}")
|
|
681
641
|
"""
|
|
682
642
|
scopes = self.get_existing_scopes(operation_version_id)
|
|
683
643
|
|
|
@@ -691,7 +651,7 @@ class OperationScopesAPI:
|
|
|
691
651
|
return []
|
|
692
652
|
|
|
693
653
|
# Query tables from these modules
|
|
694
|
-
from py_dpm.models import ModuleVersionComposition
|
|
654
|
+
from py_dpm.dpm.models import ModuleVersionComposition
|
|
695
655
|
|
|
696
656
|
tables_query = (
|
|
697
657
|
self.session.query(
|
|
@@ -699,35 +659,47 @@ class OperationScopesAPI:
|
|
|
699
659
|
ModuleVersionComposition.modulevid,
|
|
700
660
|
ModuleVersion.code,
|
|
701
661
|
ModuleVersion.name,
|
|
702
|
-
ModuleVersion.versionnumber
|
|
662
|
+
ModuleVersion.versionnumber,
|
|
663
|
+
)
|
|
664
|
+
.join(
|
|
665
|
+
ModuleVersionComposition,
|
|
666
|
+
ModuleVersionComposition.tablevid == TableVersion.tablevid,
|
|
667
|
+
)
|
|
668
|
+
.join(
|
|
669
|
+
ModuleVersion,
|
|
670
|
+
ModuleVersion.modulevid == ModuleVersionComposition.modulevid,
|
|
703
671
|
)
|
|
704
|
-
.join(ModuleVersionComposition, ModuleVersionComposition.tablevid == TableVersion.tablevid)
|
|
705
|
-
.join(ModuleVersion, ModuleVersion.modulevid == ModuleVersionComposition.modulevid)
|
|
706
672
|
.filter(ModuleVersionComposition.modulevid.in_(module_vids))
|
|
707
673
|
.distinct()
|
|
708
674
|
.order_by(TableVersion.code)
|
|
709
675
|
)
|
|
710
676
|
|
|
711
677
|
result = []
|
|
712
|
-
for
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
678
|
+
for (
|
|
679
|
+
table,
|
|
680
|
+
module_vid,
|
|
681
|
+
module_code,
|
|
682
|
+
module_name,
|
|
683
|
+
module_version,
|
|
684
|
+
) in tables_query.all():
|
|
685
|
+
result.append(
|
|
686
|
+
{
|
|
687
|
+
"table_vid": table.tablevid,
|
|
688
|
+
"code": table.code or "",
|
|
689
|
+
"name": table.name or "",
|
|
690
|
+
"description": table.description or "",
|
|
691
|
+
"module_vid": module_vid,
|
|
692
|
+
"module_code": module_code or "",
|
|
693
|
+
"module_name": module_name or "",
|
|
694
|
+
"module_version": module_version or "",
|
|
695
|
+
}
|
|
696
|
+
)
|
|
723
697
|
|
|
724
698
|
return result
|
|
725
699
|
|
|
726
700
|
def get_tables_with_metadata_from_expression(
|
|
727
|
-
self,
|
|
728
|
-
|
|
729
|
-
release_id: Optional[int] = None
|
|
730
|
-
) -> List[TableVersionInfo]:
|
|
701
|
+
self, expression: str, release_id: Optional[int] = None
|
|
702
|
+
) -> List[Dict[str, Any]]:
|
|
731
703
|
"""
|
|
732
704
|
Get tables from expression with metadata.
|
|
733
705
|
|
|
@@ -739,7 +711,7 @@ class OperationScopesAPI:
|
|
|
739
711
|
release_id (Optional[int]): Specific release ID to filter modules
|
|
740
712
|
|
|
741
713
|
Returns:
|
|
742
|
-
List[
|
|
714
|
+
List[Dict[str, Any]]: List of tables referenced in the expression with metadata
|
|
743
715
|
|
|
744
716
|
Example:
|
|
745
717
|
>>> from py_dpm.api import OperationScopesAPI
|
|
@@ -767,16 +739,23 @@ class OperationScopesAPI:
|
|
|
767
739
|
ast = self.visitor.visit(parse_tree)
|
|
768
740
|
|
|
769
741
|
# Perform operands checking to get data
|
|
770
|
-
oc = OperandsChecking(
|
|
742
|
+
oc = OperandsChecking(
|
|
743
|
+
session=self.session,
|
|
744
|
+
expression=expression,
|
|
745
|
+
ast=ast,
|
|
746
|
+
release_id=release_id,
|
|
747
|
+
)
|
|
771
748
|
|
|
772
749
|
# Extract table VIDs referenced in the expression
|
|
773
|
-
table_vids, _, _ = self._extract_vids_from_ast(
|
|
750
|
+
table_vids, _, _ = self._extract_vids_from_ast(
|
|
751
|
+
ast, oc.data, extract_codes=False
|
|
752
|
+
)
|
|
774
753
|
|
|
775
754
|
if not table_vids:
|
|
776
755
|
return []
|
|
777
756
|
|
|
778
757
|
# Query only the specific tables referenced in the expression
|
|
779
|
-
from py_dpm.models import ModuleVersionComposition
|
|
758
|
+
from py_dpm.dpm.models import ModuleVersionComposition
|
|
780
759
|
|
|
781
760
|
tables_query = (
|
|
782
761
|
self.session.query(
|
|
@@ -784,27 +763,41 @@ class OperationScopesAPI:
|
|
|
784
763
|
ModuleVersionComposition.modulevid,
|
|
785
764
|
ModuleVersion.code,
|
|
786
765
|
ModuleVersion.name,
|
|
787
|
-
ModuleVersion.versionnumber
|
|
766
|
+
ModuleVersion.versionnumber,
|
|
767
|
+
)
|
|
768
|
+
.join(
|
|
769
|
+
ModuleVersionComposition,
|
|
770
|
+
ModuleVersionComposition.tablevid == TableVersion.tablevid,
|
|
771
|
+
)
|
|
772
|
+
.join(
|
|
773
|
+
ModuleVersion,
|
|
774
|
+
ModuleVersion.modulevid == ModuleVersionComposition.modulevid,
|
|
788
775
|
)
|
|
789
|
-
.join(ModuleVersionComposition, ModuleVersionComposition.tablevid == TableVersion.tablevid)
|
|
790
|
-
.join(ModuleVersion, ModuleVersion.modulevid == ModuleVersionComposition.modulevid)
|
|
791
776
|
.filter(TableVersion.tablevid.in_(table_vids))
|
|
792
777
|
.distinct()
|
|
793
778
|
.order_by(TableVersion.code)
|
|
794
779
|
)
|
|
795
780
|
|
|
796
781
|
result = []
|
|
797
|
-
for
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
782
|
+
for (
|
|
783
|
+
table,
|
|
784
|
+
module_vid,
|
|
785
|
+
module_code,
|
|
786
|
+
module_name,
|
|
787
|
+
module_version,
|
|
788
|
+
) in tables_query.all():
|
|
789
|
+
result.append(
|
|
790
|
+
{
|
|
791
|
+
"table_vid": table.tablevid,
|
|
792
|
+
"code": table.code or "",
|
|
793
|
+
"name": table.name or "",
|
|
794
|
+
"description": table.description or "",
|
|
795
|
+
"module_vid": module_vid,
|
|
796
|
+
"module_code": module_code or "",
|
|
797
|
+
"module_name": module_name or "",
|
|
798
|
+
"module_version": module_version or "",
|
|
799
|
+
}
|
|
800
|
+
)
|
|
808
801
|
|
|
809
802
|
return result
|
|
810
803
|
|
|
@@ -814,10 +807,8 @@ class OperationScopesAPI:
|
|
|
814
807
|
return []
|
|
815
808
|
|
|
816
809
|
def get_headers_with_metadata(
|
|
817
|
-
self,
|
|
818
|
-
|
|
819
|
-
table_vid: Optional[int] = None
|
|
820
|
-
) -> List[HeaderVersionInfo]:
|
|
810
|
+
self, operation_version_id: int, table_vid: Optional[int] = None
|
|
811
|
+
) -> List[Dict[str, Any]]:
|
|
821
812
|
"""
|
|
822
813
|
Get headers from tables in operation scopes with metadata.
|
|
823
814
|
|
|
@@ -826,7 +817,7 @@ class OperationScopesAPI:
|
|
|
826
817
|
table_vid (Optional[int]): Filter by specific table VID. If None, returns all headers.
|
|
827
818
|
|
|
828
819
|
Returns:
|
|
829
|
-
List[
|
|
820
|
+
List[Dict[str, Any]]: List of headers with metadata
|
|
830
821
|
|
|
831
822
|
Example:
|
|
832
823
|
>>> from py_dpm.api import OperationScopesAPI
|
|
@@ -848,7 +839,7 @@ class OperationScopesAPI:
|
|
|
848
839
|
return []
|
|
849
840
|
|
|
850
841
|
# Get table VIDs from modules
|
|
851
|
-
from py_dpm.models import ModuleVersionComposition, Header
|
|
842
|
+
from py_dpm.dpm.models import ModuleVersionComposition, Header
|
|
852
843
|
|
|
853
844
|
table_vids_query = (
|
|
854
845
|
self.session.query(ModuleVersionComposition.tablevid)
|
|
@@ -858,7 +849,9 @@ class OperationScopesAPI:
|
|
|
858
849
|
|
|
859
850
|
if table_vid is not None:
|
|
860
851
|
# Filter by specific table
|
|
861
|
-
table_vids_query = table_vids_query.filter(
|
|
852
|
+
table_vids_query = table_vids_query.filter(
|
|
853
|
+
ModuleVersionComposition.tablevid == table_vid
|
|
854
|
+
)
|
|
862
855
|
|
|
863
856
|
table_vids = [row[0] for row in table_vids_query.all()]
|
|
864
857
|
|
|
@@ -872,9 +865,12 @@ class OperationScopesAPI:
|
|
|
872
865
|
TableVersionHeader.tablevid,
|
|
873
866
|
Header.direction,
|
|
874
867
|
TableVersion.code,
|
|
875
|
-
TableVersion.name
|
|
868
|
+
TableVersion.name,
|
|
869
|
+
)
|
|
870
|
+
.join(
|
|
871
|
+
TableVersionHeader,
|
|
872
|
+
TableVersionHeader.headervid == HeaderVersion.headervid,
|
|
876
873
|
)
|
|
877
|
-
.join(TableVersionHeader, TableVersionHeader.headervid == HeaderVersion.headervid)
|
|
878
874
|
.join(Header, Header.headerid == TableVersionHeader.headerid)
|
|
879
875
|
.join(TableVersion, TableVersion.tablevid == TableVersionHeader.tablevid)
|
|
880
876
|
.filter(TableVersionHeader.tablevid.in_(table_vids))
|
|
@@ -883,20 +879,30 @@ class OperationScopesAPI:
|
|
|
883
879
|
)
|
|
884
880
|
|
|
885
881
|
result = []
|
|
886
|
-
for
|
|
882
|
+
for (
|
|
883
|
+
header_version,
|
|
884
|
+
table_vid_val,
|
|
885
|
+
direction,
|
|
886
|
+
table_code,
|
|
887
|
+
table_name,
|
|
888
|
+
) in headers_query.all():
|
|
887
889
|
# Map direction to readable type (DPM uses X=Row, Y=Column, Z=Sheet)
|
|
888
|
-
header_type_map = {
|
|
890
|
+
header_type_map = {"X": "Row", "Y": "Column", "Z": "Sheet"}
|
|
889
891
|
header_type = header_type_map.get(direction, direction or "Unknown")
|
|
890
892
|
|
|
891
|
-
result.append(
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
893
|
+
result.append(
|
|
894
|
+
result.append(
|
|
895
|
+
{
|
|
896
|
+
"header_vid": header_version.headervid,
|
|
897
|
+
"code": header_version.code or "",
|
|
898
|
+
"label": header_version.label or "",
|
|
899
|
+
"header_type": header_type,
|
|
900
|
+
"table_vid": table_vid_val,
|
|
901
|
+
"table_code": table_code or "",
|
|
902
|
+
"table_name": table_name or "",
|
|
903
|
+
}
|
|
904
|
+
)
|
|
905
|
+
)
|
|
900
906
|
|
|
901
907
|
return result
|
|
902
908
|
|
|
@@ -904,8 +910,8 @@ class OperationScopesAPI:
|
|
|
904
910
|
self,
|
|
905
911
|
expression: str,
|
|
906
912
|
table_vid: Optional[int] = None,
|
|
907
|
-
release_id: Optional[int] = None
|
|
908
|
-
) -> List[
|
|
913
|
+
release_id: Optional[int] = None,
|
|
914
|
+
) -> List[Dict[str, Any]]:
|
|
909
915
|
"""
|
|
910
916
|
Get headers from expression with metadata.
|
|
911
917
|
|
|
@@ -919,7 +925,7 @@ class OperationScopesAPI:
|
|
|
919
925
|
release_id (Optional[int]): Specific release ID to filter modules
|
|
920
926
|
|
|
921
927
|
Returns:
|
|
922
|
-
List[
|
|
928
|
+
List[Dict[str, Any]]: List of headers referenced in the expression with metadata
|
|
923
929
|
|
|
924
930
|
Example:
|
|
925
931
|
>>> from py_dpm.api import OperationScopesAPI
|
|
@@ -948,10 +954,17 @@ class OperationScopesAPI:
|
|
|
948
954
|
ast = self.visitor.visit(parse_tree)
|
|
949
955
|
|
|
950
956
|
# Perform operands checking to get data
|
|
951
|
-
oc = OperandsChecking(
|
|
957
|
+
oc = OperandsChecking(
|
|
958
|
+
session=self.session,
|
|
959
|
+
expression=expression,
|
|
960
|
+
ast=ast,
|
|
961
|
+
release_id=release_id,
|
|
962
|
+
)
|
|
952
963
|
|
|
953
964
|
# Extract table VIDs referenced in the expression
|
|
954
|
-
table_vids, _, _ = self._extract_vids_from_ast(
|
|
965
|
+
table_vids, _, _ = self._extract_vids_from_ast(
|
|
966
|
+
ast, oc.data, extract_codes=False
|
|
967
|
+
)
|
|
955
968
|
|
|
956
969
|
if not table_vids:
|
|
957
970
|
return []
|
|
@@ -968,9 +981,9 @@ class OperationScopesAPI:
|
|
|
968
981
|
# are USED in the expression syntax (r=Row, c=Column, s=Sheet), which may differ
|
|
969
982
|
# from the Header.direction field in the database (some tables may be transposed).
|
|
970
983
|
# We return headers based on their USAGE in the expression, not their catalog definition.
|
|
971
|
-
row_codes = set(oc.data[
|
|
972
|
-
column_codes = set(oc.data[
|
|
973
|
-
sheet_codes = set(oc.data[
|
|
984
|
+
row_codes = set(oc.data["row_code"].dropna().unique().tolist())
|
|
985
|
+
column_codes = set(oc.data["column_code"].dropna().unique().tolist())
|
|
986
|
+
sheet_codes = set(oc.data["sheet_code"].dropna().unique().tolist())
|
|
974
987
|
|
|
975
988
|
# Create mapping: code -> usage dimension(s) in the expression
|
|
976
989
|
# The same code might be used in multiple dimensions
|
|
@@ -978,24 +991,26 @@ class OperationScopesAPI:
|
|
|
978
991
|
for code in row_codes:
|
|
979
992
|
if code not in code_usage:
|
|
980
993
|
code_usage[code] = set()
|
|
981
|
-
code_usage[code].add(
|
|
994
|
+
code_usage[code].add("Row")
|
|
982
995
|
for code in column_codes:
|
|
983
996
|
if code not in code_usage:
|
|
984
997
|
code_usage[code] = set()
|
|
985
|
-
code_usage[code].add(
|
|
998
|
+
code_usage[code].add("Column")
|
|
986
999
|
for code in sheet_codes:
|
|
987
1000
|
if code not in code_usage:
|
|
988
1001
|
code_usage[code] = set()
|
|
989
|
-
code_usage[code].add(
|
|
1002
|
+
code_usage[code].add("Sheet")
|
|
990
1003
|
|
|
991
1004
|
if not code_usage:
|
|
992
1005
|
return []
|
|
993
1006
|
|
|
994
1007
|
all_header_codes = set(code_usage.keys())
|
|
995
1008
|
|
|
1009
|
+
# Query headers - get all headers with matching codes
|
|
996
1010
|
# Query headers - get all headers with matching codes
|
|
997
1011
|
# Note: We don't filter by Header.direction because tables may be transposed
|
|
998
|
-
from py_dpm.models import Header
|
|
1012
|
+
from py_dpm.dpm.models import Header
|
|
1013
|
+
from sqlalchemy import and_
|
|
999
1014
|
|
|
1000
1015
|
headers_query = (
|
|
1001
1016
|
self.session.query(
|
|
@@ -1003,15 +1018,20 @@ class OperationScopesAPI:
|
|
|
1003
1018
|
TableVersionHeader.tablevid,
|
|
1004
1019
|
Header.direction,
|
|
1005
1020
|
TableVersion.code,
|
|
1006
|
-
TableVersion.name
|
|
1021
|
+
TableVersion.name,
|
|
1022
|
+
)
|
|
1023
|
+
.join(
|
|
1024
|
+
TableVersionHeader,
|
|
1025
|
+
TableVersionHeader.headervid == HeaderVersion.headervid,
|
|
1007
1026
|
)
|
|
1008
|
-
.join(TableVersionHeader, TableVersionHeader.headervid == HeaderVersion.headervid)
|
|
1009
1027
|
.join(Header, Header.headerid == HeaderVersion.headerid)
|
|
1010
|
-
.join(
|
|
1028
|
+
.join(
|
|
1029
|
+
TableVersion, TableVersion.tablevid == TableVersionHeader.tablevid
|
|
1030
|
+
)
|
|
1011
1031
|
.filter(
|
|
1012
1032
|
and_(
|
|
1013
1033
|
TableVersionHeader.tablevid.in_(table_vids),
|
|
1014
|
-
HeaderVersion.code.in_(all_header_codes)
|
|
1034
|
+
HeaderVersion.code.in_(all_header_codes),
|
|
1015
1035
|
)
|
|
1016
1036
|
)
|
|
1017
1037
|
.distinct()
|
|
@@ -1020,7 +1040,13 @@ class OperationScopesAPI:
|
|
|
1020
1040
|
result = []
|
|
1021
1041
|
seen = set() # Track (code, usage_type, table_vid) to avoid duplicates
|
|
1022
1042
|
|
|
1023
|
-
for
|
|
1043
|
+
for (
|
|
1044
|
+
header_version,
|
|
1045
|
+
table_vid_val,
|
|
1046
|
+
direction,
|
|
1047
|
+
table_code,
|
|
1048
|
+
table_name,
|
|
1049
|
+
) in headers_query.all():
|
|
1024
1050
|
code = header_version.code or ""
|
|
1025
1051
|
|
|
1026
1052
|
# For each usage dimension of this code in the expression
|
|
@@ -1033,15 +1059,18 @@ class OperationScopesAPI:
|
|
|
1033
1059
|
seen.add(key)
|
|
1034
1060
|
|
|
1035
1061
|
# Return header with usage type from expression, not catalog direction
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1062
|
+
# Return header with usage type from expression, not catalog direction
|
|
1063
|
+
result.append(
|
|
1064
|
+
{
|
|
1065
|
+
"header_vid": header_version.headervid,
|
|
1066
|
+
"code": code,
|
|
1067
|
+
"label": header_version.label or "",
|
|
1068
|
+
"header_type": usage_type, # Usage in expression: Row, Column, or Sheet
|
|
1069
|
+
"table_vid": table_vid_val,
|
|
1070
|
+
"table_code": table_code or "",
|
|
1071
|
+
"table_name": table_name or "",
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1045
1074
|
break # Only add each header once per code (we'll get multiple if used in multiple dims)
|
|
1046
1075
|
|
|
1047
1076
|
return result
|
|
@@ -1052,8 +1081,7 @@ class OperationScopesAPI:
|
|
|
1052
1081
|
return []
|
|
1053
1082
|
|
|
1054
1083
|
def get_frameworks_with_metadata(
|
|
1055
|
-
self,
|
|
1056
|
-
operation_version_id: int
|
|
1084
|
+
self, operation_version_id: int
|
|
1057
1085
|
) -> List[FrameworkInfo]:
|
|
1058
1086
|
"""
|
|
1059
1087
|
Get frameworks from operation scopes with metadata.
|
|
@@ -1094,19 +1122,19 @@ class OperationScopesAPI:
|
|
|
1094
1122
|
|
|
1095
1123
|
result = []
|
|
1096
1124
|
for framework in frameworks_query.all():
|
|
1097
|
-
result.append(
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1125
|
+
result.append(
|
|
1126
|
+
FrameworkInfo(
|
|
1127
|
+
framework_id=framework.frameworkid,
|
|
1128
|
+
code=framework.code or "",
|
|
1129
|
+
name=framework.name or "",
|
|
1130
|
+
description=framework.description or "",
|
|
1131
|
+
)
|
|
1132
|
+
)
|
|
1103
1133
|
|
|
1104
1134
|
return result
|
|
1105
1135
|
|
|
1106
1136
|
def get_frameworks_with_metadata_from_expression(
|
|
1107
|
-
self,
|
|
1108
|
-
expression: str,
|
|
1109
|
-
release_id: Optional[int] = None
|
|
1137
|
+
self, expression: str, release_id: Optional[int] = None
|
|
1110
1138
|
) -> List[FrameworkInfo]:
|
|
1111
1139
|
"""
|
|
1112
1140
|
Get frameworks from expression with metadata.
|
|
@@ -1127,9 +1155,7 @@ class OperationScopesAPI:
|
|
|
1127
1155
|
"""
|
|
1128
1156
|
# Calculate scopes in read-only mode
|
|
1129
1157
|
scope_result = self.calculate_scopes_from_expression(
|
|
1130
|
-
expression=expression,
|
|
1131
|
-
release_id=release_id,
|
|
1132
|
-
read_only=True
|
|
1158
|
+
expression=expression, release_id=release_id, read_only=True
|
|
1133
1159
|
)
|
|
1134
1160
|
|
|
1135
1161
|
if scope_result.has_error:
|
|
@@ -1153,22 +1179,46 @@ class OperationScopesAPI:
|
|
|
1153
1179
|
|
|
1154
1180
|
result = []
|
|
1155
1181
|
for framework in frameworks_query.all():
|
|
1156
|
-
result.append(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1182
|
+
result.append(
|
|
1183
|
+
FrameworkInfo(
|
|
1184
|
+
framework_id=framework.frameworkid,
|
|
1185
|
+
code=framework.code or "",
|
|
1186
|
+
name=framework.name or "",
|
|
1187
|
+
description=framework.description or "",
|
|
1188
|
+
)
|
|
1189
|
+
)
|
|
1162
1190
|
|
|
1163
1191
|
return result
|
|
1164
1192
|
|
|
1165
1193
|
def __del__(self):
|
|
1166
1194
|
"""Clean up resources."""
|
|
1167
|
-
if hasattr(self,
|
|
1195
|
+
if hasattr(self, "session"):
|
|
1168
1196
|
self.session.close()
|
|
1169
|
-
if hasattr(self,
|
|
1197
|
+
if hasattr(self, "engine") and self.engine is not None:
|
|
1170
1198
|
self.engine.dispose()
|
|
1171
1199
|
|
|
1200
|
+
def close(self):
|
|
1201
|
+
"""
|
|
1202
|
+
Explicitly close the underlying SQLAlchemy session and dispose any private engine.
|
|
1203
|
+
"""
|
|
1204
|
+
try:
|
|
1205
|
+
if hasattr(self, "session") and self.session:
|
|
1206
|
+
self.session.close()
|
|
1207
|
+
except Exception:
|
|
1208
|
+
pass
|
|
1209
|
+
|
|
1210
|
+
try:
|
|
1211
|
+
if hasattr(self, "engine") and self.engine is not None:
|
|
1212
|
+
self.engine.dispose()
|
|
1213
|
+
except Exception:
|
|
1214
|
+
pass
|
|
1215
|
+
|
|
1216
|
+
def __enter__(self):
|
|
1217
|
+
return self
|
|
1218
|
+
|
|
1219
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
1220
|
+
self.close()
|
|
1221
|
+
|
|
1172
1222
|
|
|
1173
1223
|
# Convenience functions for direct usage
|
|
1174
1224
|
def calculate_scopes_from_expression(
|
|
@@ -1177,7 +1227,7 @@ def calculate_scopes_from_expression(
|
|
|
1177
1227
|
release_id: Optional[int] = None,
|
|
1178
1228
|
database_path: Optional[str] = None,
|
|
1179
1229
|
connection_url: Optional[str] = None,
|
|
1180
|
-
read_only: bool = True
|
|
1230
|
+
read_only: bool = True,
|
|
1181
1231
|
) -> OperationScopeResult:
|
|
1182
1232
|
"""
|
|
1183
1233
|
Convenience function to calculate operation scopes from expression.
|
|
@@ -1194,7 +1244,7 @@ def calculate_scopes_from_expression(
|
|
|
1194
1244
|
OperationScopeResult: Result containing existing and new scopes
|
|
1195
1245
|
|
|
1196
1246
|
Example:
|
|
1197
|
-
>>> from py_dpm.api.operation_scopes import calculate_scopes_from_expression
|
|
1247
|
+
>>> from py_dpm.api.dpm.operation_scopes import calculate_scopes_from_expression
|
|
1198
1248
|
>>> result = calculate_scopes_from_expression(
|
|
1199
1249
|
... "{tC_01.00, r0100, c0010}",
|
|
1200
1250
|
... release_id=4,
|
|
@@ -1203,13 +1253,15 @@ def calculate_scopes_from_expression(
|
|
|
1203
1253
|
>>> print(f"Total scopes: {result.total_scopes}")
|
|
1204
1254
|
"""
|
|
1205
1255
|
api = OperationScopesAPI(database_path=database_path, connection_url=connection_url)
|
|
1206
|
-
return api.calculate_scopes_from_expression(
|
|
1256
|
+
return api.calculate_scopes_from_expression(
|
|
1257
|
+
expression, operation_version_id, release_id, read_only=read_only
|
|
1258
|
+
)
|
|
1207
1259
|
|
|
1208
1260
|
|
|
1209
1261
|
def get_existing_scopes(
|
|
1210
1262
|
operation_version_id: int,
|
|
1211
1263
|
database_path: Optional[str] = None,
|
|
1212
|
-
connection_url: Optional[str] = None
|
|
1264
|
+
connection_url: Optional[str] = None,
|
|
1213
1265
|
) -> List[OperationScope]:
|
|
1214
1266
|
"""
|
|
1215
1267
|
Convenience function to get existing scopes for an operation.
|
|
@@ -1223,7 +1275,7 @@ def get_existing_scopes(
|
|
|
1223
1275
|
List[OperationScope]: List of existing scopes
|
|
1224
1276
|
|
|
1225
1277
|
Example:
|
|
1226
|
-
>>> from py_dpm.api.operation_scopes import get_existing_scopes
|
|
1278
|
+
>>> from py_dpm.api.dpm.operation_scopes import get_existing_scopes
|
|
1227
1279
|
>>> scopes = get_existing_scopes(operation_version_id=1, database_path="./database.db")
|
|
1228
1280
|
"""
|
|
1229
1281
|
api = OperationScopesAPI(database_path=database_path, connection_url=connection_url)
|