pydpm_xl 0.1.39rc32__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_dpm/__init__.py +1 -1
- py_dpm/api/__init__.py +58 -189
- py_dpm/api/dpm/__init__.py +20 -0
- py_dpm/api/{data_dictionary.py → dpm/data_dictionary.py} +903 -984
- py_dpm/api/dpm/explorer.py +236 -0
- py_dpm/api/dpm/hierarchical_queries.py +142 -0
- py_dpm/api/{migration.py → dpm/migration.py} +16 -19
- py_dpm/api/{operation_scopes.py → dpm/operation_scopes.py} +319 -267
- py_dpm/api/dpm_xl/__init__.py +25 -0
- py_dpm/api/{ast_generator.py → dpm_xl/ast_generator.py} +3 -3
- py_dpm/api/{complete_ast.py → dpm_xl/complete_ast.py} +186 -284
- py_dpm/api/dpm_xl/semantic.py +358 -0
- py_dpm/api/{syntax.py → dpm_xl/syntax.py} +6 -5
- py_dpm/api/explorer.py +4 -0
- py_dpm/api/semantic.py +30 -306
- py_dpm/cli/__init__.py +9 -0
- py_dpm/{client.py → cli/main.py} +12 -10
- py_dpm/dpm/__init__.py +11 -0
- py_dpm/{models.py → dpm/models.py} +112 -88
- py_dpm/dpm/queries/base.py +100 -0
- py_dpm/dpm/queries/basic_objects.py +33 -0
- py_dpm/dpm/queries/explorer_queries.py +352 -0
- py_dpm/dpm/queries/filters.py +139 -0
- py_dpm/dpm/queries/glossary.py +45 -0
- py_dpm/dpm/queries/hierarchical_queries.py +838 -0
- py_dpm/dpm/queries/tables.py +133 -0
- py_dpm/dpm/utils.py +356 -0
- py_dpm/dpm_xl/__init__.py +8 -0
- py_dpm/dpm_xl/ast/__init__.py +14 -0
- py_dpm/{AST/ASTConstructor.py → dpm_xl/ast/constructor.py} +6 -6
- py_dpm/{AST/MLGeneration.py → dpm_xl/ast/ml_generation.py} +137 -87
- py_dpm/{AST/ModuleAnalyzer.py → dpm_xl/ast/module_analyzer.py} +7 -7
- py_dpm/{AST/ModuleDependencies.py → dpm_xl/ast/module_dependencies.py} +56 -41
- py_dpm/{AST/ASTObjects.py → dpm_xl/ast/nodes.py} +1 -1
- py_dpm/{AST/check_operands.py → dpm_xl/ast/operands.py} +16 -13
- py_dpm/{AST/ASTTemplate.py → dpm_xl/ast/template.py} +2 -2
- py_dpm/{AST/WhereClauseChecker.py → dpm_xl/ast/where_clause.py} +2 -2
- py_dpm/dpm_xl/grammar/__init__.py +18 -0
- py_dpm/dpm_xl/operators/__init__.py +19 -0
- py_dpm/{Operators/AggregateOperators.py → dpm_xl/operators/aggregate.py} +7 -7
- py_dpm/{Operators/NumericOperators.py → dpm_xl/operators/arithmetic.py} +6 -6
- py_dpm/{Operators/Operator.py → dpm_xl/operators/base.py} +5 -5
- py_dpm/{Operators/BooleanOperators.py → dpm_xl/operators/boolean.py} +5 -5
- py_dpm/{Operators/ClauseOperators.py → dpm_xl/operators/clause.py} +8 -8
- py_dpm/{Operators/ComparisonOperators.py → dpm_xl/operators/comparison.py} +5 -5
- py_dpm/{Operators/ConditionalOperators.py → dpm_xl/operators/conditional.py} +7 -7
- py_dpm/{Operators/StringOperators.py → dpm_xl/operators/string.py} +5 -5
- py_dpm/{Operators/TimeOperators.py → dpm_xl/operators/time.py} +6 -6
- py_dpm/{semantics/SemanticAnalyzer.py → dpm_xl/semantic_analyzer.py} +168 -68
- py_dpm/{semantics/Symbols.py → dpm_xl/symbols.py} +3 -3
- py_dpm/dpm_xl/types/__init__.py +13 -0
- py_dpm/{DataTypes/TypePromotion.py → dpm_xl/types/promotion.py} +2 -2
- py_dpm/{DataTypes/ScalarTypes.py → dpm_xl/types/scalar.py} +2 -2
- py_dpm/dpm_xl/utils/__init__.py +14 -0
- py_dpm/{data_handlers.py → dpm_xl/utils/data_handlers.py} +2 -2
- py_dpm/{Utils → dpm_xl/utils}/operands_mapping.py +1 -1
- py_dpm/{Utils → dpm_xl/utils}/operator_mapping.py +8 -8
- py_dpm/{OperationScopes/OperationScopeService.py → dpm_xl/utils/scopes_calculator.py} +148 -58
- py_dpm/{Utils/ast_serialization.py → dpm_xl/utils/serialization.py} +3 -4
- py_dpm/dpm_xl/validation/__init__.py +12 -0
- py_dpm/{Utils/ValidationsGenerationUtils.py → dpm_xl/validation/generation_utils.py} +2 -3
- py_dpm/{ValidationsGeneration/PropertiesConstraintsProcessor.py → dpm_xl/validation/property_constraints.py} +56 -21
- py_dpm/{ValidationsGeneration/auxiliary_functions.py → dpm_xl/validation/utils.py} +2 -2
- py_dpm/{ValidationsGeneration/VariantsProcessor.py → dpm_xl/validation/variants.py} +149 -55
- py_dpm/exceptions/__init__.py +23 -0
- py_dpm/{Exceptions → exceptions}/exceptions.py +7 -2
- pydpm_xl-0.2.1.dist-info/METADATA +278 -0
- pydpm_xl-0.2.1.dist-info/RECORD +88 -0
- pydpm_xl-0.2.1.dist-info/entry_points.txt +2 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/utils.py +0 -2
- py_dpm/ValidationsGeneration/Utils.py +0 -364
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/api/data_dictionary_validation.py +0 -614
- py_dpm/db_utils.py +0 -221
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dpm_xlLexer.g4 +0 -437
- py_dpm/grammar/dpm_xlParser.g4 +0 -263
- py_dpm/semantics/DAG/DAGAnalyzer.py +0 -158
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/views/data_types.sql +0 -12
- py_dpm/views/datapoints.sql +0 -65
- py_dpm/views/hierarchy_operand_reference.sql +0 -11
- py_dpm/views/hierarchy_preconditions.sql +0 -13
- py_dpm/views/hierarchy_variables.sql +0 -26
- py_dpm/views/hierarchy_variables_context.sql +0 -14
- py_dpm/views/key_components.sql +0 -18
- py_dpm/views/module_from_table.sql +0 -11
- py_dpm/views/open_keys.sql +0 -13
- py_dpm/views/operation_info.sql +0 -27
- py_dpm/views/operation_list.sql +0 -18
- py_dpm/views/operations_versions_from_module_version.sql +0 -30
- py_dpm/views/precondition_info.sql +0 -17
- py_dpm/views/report_type_operand_reference_info.sql +0 -18
- py_dpm/views/subcategory_info.sql +0 -17
- py_dpm/views/table_info.sql +0 -19
- pydpm_xl-0.1.39rc32.dist-info/METADATA +0 -53
- pydpm_xl-0.1.39rc32.dist-info/RECORD +0 -96
- pydpm_xl-0.1.39rc32.dist-info/entry_points.txt +0 -2
- /py_dpm/{AST → cli/commands}/__init__.py +0 -0
- /py_dpm/{migration.py → dpm/migration.py} +0 -0
- /py_dpm/{AST/ASTVisitor.py → dpm_xl/ast/visitor.py} +0 -0
- /py_dpm/{DataTypes → dpm_xl/grammar/generated}/__init__.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserListener.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserVisitor.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/listeners.py +0 -0
- /py_dpm/{DataTypes/TimeClasses.py → dpm_xl/types/time.py} +0 -0
- /py_dpm/{Utils → dpm_xl/utils}/tokens.py +0 -0
- /py_dpm/{Exceptions → exceptions}/messages.py +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/WHEEL +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {pydpm_xl-0.1.39rc32.dist-info → pydpm_xl-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from typing import List, Optional, Dict, Any
|
|
2
|
+
|
|
3
|
+
from py_dpm.api.dpm.data_dictionary import DataDictionaryAPI
|
|
4
|
+
from py_dpm.dpm.queries.explorer_queries import ExplorerQuery
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExplorerQueryAPI:
|
|
8
|
+
"""
|
|
9
|
+
Explorer API for introspection and "inverse" queries of the DPM structure.
|
|
10
|
+
Methods here answer "Where is X used?" or "What relates to Y?".
|
|
11
|
+
|
|
12
|
+
This class composes DataDictionaryAPI for basic queries but adds higher-order logic.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, data_dict_api: Optional[DataDictionaryAPI] = None):
|
|
16
|
+
self.api = data_dict_api or DataDictionaryAPI()
|
|
17
|
+
|
|
18
|
+
def close(self):
|
|
19
|
+
"""
|
|
20
|
+
Explicitly close the underlying DataDictionaryAPI (and its session).
|
|
21
|
+
"""
|
|
22
|
+
if hasattr(self, "api") and hasattr(self.api, "close"):
|
|
23
|
+
try:
|
|
24
|
+
self.api.close()
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def __enter__(self):
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
32
|
+
self.close()
|
|
33
|
+
|
|
34
|
+
# ==================== Existing Explorer Methods ====================
|
|
35
|
+
|
|
36
|
+
def get_properties_using_item(
|
|
37
|
+
self, item_code: str, release_id: Optional[int] = None
|
|
38
|
+
) -> List[str]:
|
|
39
|
+
"""
|
|
40
|
+
Find all property codes that use the given item code as a valid value.
|
|
41
|
+
(Inverse of getting valid items for a property).
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
item_code: The item code to search for (e.g. 'EUR')
|
|
45
|
+
release_id: Optional release ID
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of property codes (e.g. ['sCRNCY', 'sTRNS_CRNCY'])
|
|
49
|
+
"""
|
|
50
|
+
from py_dpm.dpm.models import ItemCategory, PropertyCategory, Category
|
|
51
|
+
from sqlalchemy.orm import aliased
|
|
52
|
+
|
|
53
|
+
session = self.api.session
|
|
54
|
+
|
|
55
|
+
# Aliases for clarity
|
|
56
|
+
ic_child = aliased(ItemCategory, name="ic_child") # The item (value)
|
|
57
|
+
ic_parent = aliased(ItemCategory, name="ic_parent") # The property
|
|
58
|
+
|
|
59
|
+
query = (
|
|
60
|
+
session.query(ic_parent.code)
|
|
61
|
+
.select_from(ic_child)
|
|
62
|
+
.join(Category, ic_child.categoryid == Category.categoryid)
|
|
63
|
+
.join(PropertyCategory, Category.categoryid == PropertyCategory.categoryid)
|
|
64
|
+
.join(ic_parent, PropertyCategory.propertyid == ic_parent.itemid)
|
|
65
|
+
.filter(ic_child.code == item_code)
|
|
66
|
+
.distinct()
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if release_id is not None:
|
|
70
|
+
query = query.filter(
|
|
71
|
+
(ic_child.endreleaseid.is_(None))
|
|
72
|
+
| (ic_child.endreleaseid > release_id),
|
|
73
|
+
ic_child.startreleaseid <= release_id,
|
|
74
|
+
(ic_parent.endreleaseid.is_(None))
|
|
75
|
+
| (ic_parent.endreleaseid > release_id),
|
|
76
|
+
ic_parent.startreleaseid <= release_id,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
results = query.all()
|
|
80
|
+
return [r.code for r in results]
|
|
81
|
+
|
|
82
|
+
def search_table(
|
|
83
|
+
self, query: str, release_id: Optional[int] = None
|
|
84
|
+
) -> List[Dict[str, Any]]:
|
|
85
|
+
"""
|
|
86
|
+
Search for tables by code or name substring.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
query: Substring to search for
|
|
90
|
+
release_id: Optional release ID
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of matching dictionaries with table info
|
|
94
|
+
"""
|
|
95
|
+
from py_dpm.dpm.models import TableVersion
|
|
96
|
+
from sqlalchemy import or_
|
|
97
|
+
|
|
98
|
+
session = self.api.session
|
|
99
|
+
search_pattern = f"%{query}%"
|
|
100
|
+
|
|
101
|
+
db_query = session.query(
|
|
102
|
+
TableVersion.tablevid,
|
|
103
|
+
TableVersion.code,
|
|
104
|
+
TableVersion.name,
|
|
105
|
+
TableVersion.description,
|
|
106
|
+
).filter(
|
|
107
|
+
or_(
|
|
108
|
+
TableVersion.code.like(search_pattern),
|
|
109
|
+
TableVersion.name.like(search_pattern),
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if release_id is not None:
|
|
114
|
+
db_query = db_query.filter(
|
|
115
|
+
or_(
|
|
116
|
+
TableVersion.endreleaseid.is_(None),
|
|
117
|
+
TableVersion.endreleaseid > release_id,
|
|
118
|
+
),
|
|
119
|
+
TableVersion.startreleaseid <= release_id,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
results = db_query.all()
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
"table_vid": r.tablevid,
|
|
126
|
+
"code": r.code,
|
|
127
|
+
"name": r.name,
|
|
128
|
+
"description": r.description,
|
|
129
|
+
}
|
|
130
|
+
for r in results
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
def audit_table(self, table_code: str, release_id: Optional[int] = None) -> dict:
|
|
134
|
+
"""
|
|
135
|
+
Provide a comprehensive audit of a table structure: dimensions, open keys, and basic stats.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
table_code: Table code
|
|
139
|
+
release_id: Optional release ID
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dict summarizing table metadata
|
|
143
|
+
"""
|
|
144
|
+
table_info = self.api.get_table_version(table_code, release_id)
|
|
145
|
+
if not table_info:
|
|
146
|
+
return {"error": f"Table {table_code} not found"}
|
|
147
|
+
|
|
148
|
+
open_keys = self.api.get_open_keys_for_table(table_code, release_id)
|
|
149
|
+
|
|
150
|
+
# Dimensions (Header Rows/Cols) could be fetched if we had a method.
|
|
151
|
+
# For now we return what we can easily aggregate.
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"info": table_info,
|
|
155
|
+
"open_keys": open_keys,
|
|
156
|
+
"open_keys_count": len(open_keys),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ==================== New Explorer Methods Backed by Query Layer ====================
|
|
160
|
+
|
|
161
|
+
def get_variable_usage(
|
|
162
|
+
self,
|
|
163
|
+
variable_id: Optional[int] = None,
|
|
164
|
+
variable_vid: Optional[int] = None,
|
|
165
|
+
release_id: Optional[int] = None,
|
|
166
|
+
date: Optional[str] = None,
|
|
167
|
+
release_code: Optional[str] = None,
|
|
168
|
+
) -> List[Dict[str, Any]]:
|
|
169
|
+
"""
|
|
170
|
+
Expose ExplorerQuery.get_variable_usage through the Explorer API.
|
|
171
|
+
|
|
172
|
+
Exactly one of variable_id or variable_vid must be provided.
|
|
173
|
+
Release arguments follow the same semantics as hierarchical queries:
|
|
174
|
+
at most one of release_id, date or release_code may be specified.
|
|
175
|
+
"""
|
|
176
|
+
return ExplorerQuery.get_variable_usage(
|
|
177
|
+
self.api.session,
|
|
178
|
+
variable_id=variable_id,
|
|
179
|
+
variable_vid=variable_vid,
|
|
180
|
+
release_id=release_id,
|
|
181
|
+
date=date,
|
|
182
|
+
release_code=release_code,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def get_module_url(
|
|
186
|
+
self,
|
|
187
|
+
module_code: str,
|
|
188
|
+
date: Optional[str] = None,
|
|
189
|
+
release_id: Optional[int] = None,
|
|
190
|
+
release_code: Optional[str] = None,
|
|
191
|
+
) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Get the EBA taxonomy URL for a module.
|
|
194
|
+
|
|
195
|
+
The URL has the form:
|
|
196
|
+
http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/{framework_code}/{release_code}/mod/{module_code}.json
|
|
197
|
+
|
|
198
|
+
Exactly one of date, release_id or release_code may be specified.
|
|
199
|
+
If none are provided, the URL is built for the currently active
|
|
200
|
+
module version.
|
|
201
|
+
"""
|
|
202
|
+
return ExplorerQuery.get_module_url(
|
|
203
|
+
self.api.session,
|
|
204
|
+
module_code=module_code,
|
|
205
|
+
date=date,
|
|
206
|
+
release_id=release_id,
|
|
207
|
+
release_code=release_code,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def get_variable_from_cell_address(
|
|
211
|
+
self,
|
|
212
|
+
table_code: str,
|
|
213
|
+
row_code: Optional[str] = None,
|
|
214
|
+
column_code: Optional[str] = None,
|
|
215
|
+
sheet_code: Optional[str] = None,
|
|
216
|
+
release_id: Optional[int] = None,
|
|
217
|
+
release_code: Optional[str] = None,
|
|
218
|
+
date: Optional[str] = None,
|
|
219
|
+
) -> List[Dict[str, Any]]:
|
|
220
|
+
"""
|
|
221
|
+
Resolve variable information from a cell address (table/row/column/sheet).
|
|
222
|
+
|
|
223
|
+
Row, column and sheet codes are optional and are only used when
|
|
224
|
+
provided. Release parameters follow the standard semantics; if none
|
|
225
|
+
are given, only active module versions are considered.
|
|
226
|
+
"""
|
|
227
|
+
return ExplorerQuery.get_variable_from_cell_address(
|
|
228
|
+
self.api.session,
|
|
229
|
+
table_code=table_code,
|
|
230
|
+
row_code=row_code,
|
|
231
|
+
column_code=column_code,
|
|
232
|
+
sheet_code=sheet_code,
|
|
233
|
+
release_id=release_id,
|
|
234
|
+
release_code=release_code,
|
|
235
|
+
date=date,
|
|
236
|
+
)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hierarchical Queries API
|
|
3
|
+
|
|
4
|
+
This module provides ORM-based query methods for accessing t he data dictionary.
|
|
5
|
+
All methods use SQLAlchemy ORM instead of raw SQL for PostgreSQL compatibility.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
|
|
10
|
+
from py_dpm.dpm.utils import get_session, get_engine
|
|
11
|
+
from py_dpm.dpm.queries.hierarchical_queries import HierarchicalQuery
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HierarchicalQueryAPI:
|
|
15
|
+
"""
|
|
16
|
+
Main API for querying the data dictionary using ORM.
|
|
17
|
+
|
|
18
|
+
This class provides methods for:
|
|
19
|
+
- Table/row/column reference lookups
|
|
20
|
+
- Wildcard resolution
|
|
21
|
+
- Item and sheet validation
|
|
22
|
+
- Open key queries
|
|
23
|
+
- Metadata retrieval
|
|
24
|
+
|
|
25
|
+
All methods use SQLAlchemy ORM for database-agnostic queries.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
database_path: Optional[str] = None,
|
|
31
|
+
connection_url: Optional[str] = None,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the Data Dictionary API.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
database_path: Path to SQLite database (optional)
|
|
38
|
+
connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
|
|
39
|
+
"""
|
|
40
|
+
# engine is created but not stored since it's used globally/per-session
|
|
41
|
+
get_engine(database_path=database_path, connection_url=connection_url)
|
|
42
|
+
self.session = get_session()
|
|
43
|
+
|
|
44
|
+
def close(self):
|
|
45
|
+
"""
|
|
46
|
+
Explicitly close the underlying SQLAlchemy session.
|
|
47
|
+
"""
|
|
48
|
+
if hasattr(self, "session") and self.session:
|
|
49
|
+
try:
|
|
50
|
+
self.session.close()
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
58
|
+
self.close()
|
|
59
|
+
|
|
60
|
+
def get_module_version(
|
|
61
|
+
self,
|
|
62
|
+
module_code: str,
|
|
63
|
+
release_id: Optional[int] = None,
|
|
64
|
+
date: Optional[str] = None,
|
|
65
|
+
release_code: Optional[str] = None,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Fetch list of available releases from database.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of dictionaries containing release info
|
|
72
|
+
"""
|
|
73
|
+
# Use ReleaseQuery
|
|
74
|
+
return HierarchicalQuery.get_module_version(
|
|
75
|
+
self.session,
|
|
76
|
+
module_code=module_code,
|
|
77
|
+
release_id=release_id,
|
|
78
|
+
date=date,
|
|
79
|
+
release_code=release_code,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def get_all_frameworks(
|
|
83
|
+
self,
|
|
84
|
+
release_id: Optional[int] = None,
|
|
85
|
+
date: Optional[str] = None,
|
|
86
|
+
release_code: Optional[str] = None,
|
|
87
|
+
) -> Dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Fetch list of available releases from database.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
List of dictionaries containing release info
|
|
93
|
+
"""
|
|
94
|
+
# Use ReleaseQuery
|
|
95
|
+
return HierarchicalQuery.get_all_frameworks(
|
|
96
|
+
self.session,
|
|
97
|
+
release_id=release_id,
|
|
98
|
+
date=date,
|
|
99
|
+
release_code=release_code,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def get_table_details(
|
|
103
|
+
self,
|
|
104
|
+
table_code: str,
|
|
105
|
+
release_id: Optional[int] = None,
|
|
106
|
+
date: Optional[str] = None,
|
|
107
|
+
release_code: Optional[str] = None,
|
|
108
|
+
) -> Dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
Fetch table structure and cell data from database.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dictionary with DPM JSON structure
|
|
114
|
+
"""
|
|
115
|
+
return HierarchicalQuery.get_table_details(
|
|
116
|
+
self.session,
|
|
117
|
+
table_code=table_code,
|
|
118
|
+
release_id=release_id,
|
|
119
|
+
date=date,
|
|
120
|
+
release_code=release_code,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def get_table_modelling(
|
|
124
|
+
self,
|
|
125
|
+
table_code: str,
|
|
126
|
+
release_id: Optional[int] = None,
|
|
127
|
+
date: Optional[str] = None,
|
|
128
|
+
release_code: Optional[str] = None,
|
|
129
|
+
) -> Dict[str, Any]:
|
|
130
|
+
"""
|
|
131
|
+
Placeholder API for table modelling metadata.
|
|
132
|
+
|
|
133
|
+
Mirrors the signature of get_table_details; the underlying query and
|
|
134
|
+
output format will be provided later.
|
|
135
|
+
"""
|
|
136
|
+
return HierarchicalQuery.get_table_modelling(
|
|
137
|
+
self.session,
|
|
138
|
+
table_code=table_code,
|
|
139
|
+
release_id=release_id,
|
|
140
|
+
date=date,
|
|
141
|
+
release_code=release_code,
|
|
142
|
+
)
|
|
@@ -2,40 +2,38 @@ import os
|
|
|
2
2
|
from typing import Optional
|
|
3
3
|
from sqlalchemy.engine import Engine
|
|
4
4
|
|
|
5
|
-
from py_dpm.migration import run_migration as _run_migration
|
|
5
|
+
from py_dpm.dpm.migration import run_migration as _run_migration
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class MigrationAPI:
|
|
9
9
|
"""
|
|
10
10
|
API for database migration operations.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
This class provides methods to migrate data from Access databases to SQLite.
|
|
13
13
|
"""
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
def __init__(self):
|
|
16
16
|
"""Initialize the Migration API."""
|
|
17
17
|
pass
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def migrate_access_to_sqlite(
|
|
20
|
-
self,
|
|
21
|
-
access_file_path: str,
|
|
22
|
-
sqlite_db_path: Optional[str] = None
|
|
20
|
+
self, access_file_path: str, sqlite_db_path: Optional[str] = None
|
|
23
21
|
) -> Engine:
|
|
24
22
|
"""
|
|
25
23
|
Migrate data from an Access database to SQLite.
|
|
26
|
-
|
|
24
|
+
|
|
27
25
|
Args:
|
|
28
26
|
access_file_path (str): Path to the Access database file (.mdb or .accdb)
|
|
29
|
-
sqlite_db_path (Optional[str]): Path for the SQLite database.
|
|
27
|
+
sqlite_db_path (Optional[str]): Path for the SQLite database.
|
|
30
28
|
If None, defaults to "database.db"
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
Returns:
|
|
33
31
|
Engine: SQLAlchemy engine for the created SQLite database
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
Raises:
|
|
36
34
|
FileNotFoundError: If the Access file doesn't exist
|
|
37
35
|
Exception: If migration fails
|
|
38
|
-
|
|
36
|
+
|
|
39
37
|
Example:
|
|
40
38
|
>>> from pydpm.api import MigrationAPI
|
|
41
39
|
>>> migration = MigrationAPI()
|
|
@@ -43,10 +41,10 @@ class MigrationAPI:
|
|
|
43
41
|
"""
|
|
44
42
|
if not os.path.exists(access_file_path):
|
|
45
43
|
raise FileNotFoundError(f"Access file not found: {access_file_path}")
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
if sqlite_db_path is None:
|
|
48
46
|
sqlite_db_path = os.getenv("SQLITE_DB_PATH", "database.db")
|
|
49
|
-
|
|
47
|
+
|
|
50
48
|
try:
|
|
51
49
|
engine = _run_migration(access_file_path, sqlite_db_path)
|
|
52
50
|
return engine
|
|
@@ -56,19 +54,18 @@ class MigrationAPI:
|
|
|
56
54
|
|
|
57
55
|
# Convenience function for direct usage
|
|
58
56
|
def migrate_access_to_sqlite(
|
|
59
|
-
access_file_path: str,
|
|
60
|
-
sqlite_db_path: Optional[str] = None
|
|
57
|
+
access_file_path: str, sqlite_db_path: Optional[str] = None
|
|
61
58
|
) -> Engine:
|
|
62
59
|
"""
|
|
63
60
|
Convenience function to migrate Access database to SQLite.
|
|
64
|
-
|
|
61
|
+
|
|
65
62
|
Args:
|
|
66
63
|
access_file_path (str): Path to the Access database file
|
|
67
64
|
sqlite_db_path (Optional[str]): Path for the SQLite database
|
|
68
|
-
|
|
65
|
+
|
|
69
66
|
Returns:
|
|
70
67
|
Engine: SQLAlchemy engine for the created SQLite database
|
|
71
|
-
|
|
68
|
+
|
|
72
69
|
Example:
|
|
73
70
|
>>> from pydpm.api.migration import migrate_access_to_sqlite
|
|
74
71
|
>>> engine = migrate_access_to_sqlite("data.mdb", "output.db")
|