pydpm_xl 0.1.10__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/AST/ASTConstructor.py +503 -0
- py_dpm/AST/ASTObjects.py +827 -0
- py_dpm/AST/ASTTemplate.py +101 -0
- py_dpm/AST/ASTVisitor.py +13 -0
- py_dpm/AST/MLGeneration.py +588 -0
- py_dpm/AST/ModuleAnalyzer.py +79 -0
- py_dpm/AST/ModuleDependencies.py +203 -0
- py_dpm/AST/WhereClauseChecker.py +12 -0
- py_dpm/AST/__init__.py +0 -0
- py_dpm/AST/check_operands.py +302 -0
- py_dpm/DataTypes/ScalarTypes.py +324 -0
- py_dpm/DataTypes/TimeClasses.py +370 -0
- py_dpm/DataTypes/TypePromotion.py +195 -0
- py_dpm/DataTypes/__init__.py +0 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/Exceptions/exceptions.py +84 -0
- py_dpm/Exceptions/messages.py +114 -0
- py_dpm/OperationScopes/OperationScopeService.py +247 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/AggregateOperators.py +138 -0
- py_dpm/Operators/BooleanOperators.py +30 -0
- py_dpm/Operators/ClauseOperators.py +159 -0
- py_dpm/Operators/ComparisonOperators.py +69 -0
- py_dpm/Operators/ConditionalOperators.py +362 -0
- py_dpm/Operators/NumericOperators.py +101 -0
- py_dpm/Operators/Operator.py +388 -0
- py_dpm/Operators/StringOperators.py +27 -0
- py_dpm/Operators/TimeOperators.py +53 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/ValidationsGenerationUtils.py +429 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/operands_mapping.py +73 -0
- py_dpm/Utils/operator_mapping.py +89 -0
- py_dpm/Utils/tokens.py +172 -0
- py_dpm/Utils/utils.py +2 -0
- py_dpm/ValidationsGeneration/PropertiesConstraintsProcessor.py +190 -0
- py_dpm/ValidationsGeneration/Utils.py +364 -0
- py_dpm/ValidationsGeneration/VariantsProcessor.py +265 -0
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/ValidationsGeneration/auxiliary_functions.py +98 -0
- py_dpm/__init__.py +61 -0
- py_dpm/api/__init__.py +140 -0
- py_dpm/api/ast_generator.py +438 -0
- py_dpm/api/complete_ast.py +241 -0
- py_dpm/api/data_dictionary_validation.py +577 -0
- py_dpm/api/migration.py +77 -0
- py_dpm/api/semantic.py +224 -0
- py_dpm/api/syntax.py +182 -0
- py_dpm/client.py +106 -0
- py_dpm/data_handlers.py +99 -0
- py_dpm/db_utils.py +117 -0
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dist/dpm_xlLexer.interp +428 -0
- py_dpm/grammar/dist/dpm_xlLexer.py +804 -0
- py_dpm/grammar/dist/dpm_xlLexer.tokens +106 -0
- py_dpm/grammar/dist/dpm_xlParser.interp +249 -0
- py_dpm/grammar/dist/dpm_xlParser.py +5224 -0
- py_dpm/grammar/dist/dpm_xlParser.tokens +106 -0
- py_dpm/grammar/dist/dpm_xlParserListener.py +742 -0
- py_dpm/grammar/dist/dpm_xlParserVisitor.py +419 -0
- py_dpm/grammar/dist/listeners.py +10 -0
- py_dpm/grammar/dpm_xlLexer.g4 +435 -0
- py_dpm/grammar/dpm_xlParser.g4 +260 -0
- py_dpm/migration.py +282 -0
- py_dpm/models.py +2139 -0
- py_dpm/semantics/DAG/DAGAnalyzer.py +158 -0
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/SemanticAnalyzer.py +320 -0
- py_dpm/semantics/Symbols.py +223 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/utils/__init__.py +0 -0
- py_dpm/utils/ast_serialization.py +481 -0
- py_dpm/views/data_types.sql +12 -0
- py_dpm/views/datapoints.sql +65 -0
- py_dpm/views/hierarchy_operand_reference.sql +11 -0
- py_dpm/views/hierarchy_preconditions.sql +13 -0
- py_dpm/views/hierarchy_variables.sql +26 -0
- py_dpm/views/hierarchy_variables_context.sql +14 -0
- py_dpm/views/key_components.sql +18 -0
- py_dpm/views/module_from_table.sql +11 -0
- py_dpm/views/open_keys.sql +13 -0
- py_dpm/views/operation_info.sql +27 -0
- py_dpm/views/operation_list.sql +18 -0
- py_dpm/views/operations_versions_from_module_version.sql +30 -0
- py_dpm/views/precondition_info.sql +17 -0
- py_dpm/views/report_type_operand_reference_info.sql +18 -0
- py_dpm/views/subcategory_info.sql +17 -0
- py_dpm/views/table_info.sql +19 -0
- pydpm_xl-0.1.10.dist-info/LICENSE +674 -0
- pydpm_xl-0.1.10.dist-info/METADATA +50 -0
- pydpm_xl-0.1.10.dist-info/RECORD +94 -0
- pydpm_xl-0.1.10.dist-info/WHEEL +4 -0
- pydpm_xl-0.1.10.dist-info/entry_points.txt +3 -0
py_dpm/migration.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from sqlalchemy import create_engine, text
|
|
4
|
+
from sqlalchemy.orm import sessionmaker
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from .models import Base, ViewDatapoints
|
|
9
|
+
|
|
10
|
+
def extract_access_tables(access_file):
|
|
11
|
+
"""Extract tables from Access database using multiple methods"""
|
|
12
|
+
|
|
13
|
+
# Method 1: Try mdbtools first
|
|
14
|
+
try:
|
|
15
|
+
# Get list of tables
|
|
16
|
+
tables = subprocess.check_output(["mdb-tables", access_file]).decode().split()
|
|
17
|
+
print("✓ Using mdb-tools for Access database extraction")
|
|
18
|
+
return _extract_with_mdbtools(access_file, tables)
|
|
19
|
+
except FileNotFoundError:
|
|
20
|
+
print("⚠ mdb-tables command not found, trying alternative methods...", file=sys.stderr)
|
|
21
|
+
except subprocess.CalledProcessError as e:
|
|
22
|
+
print(f"⚠ mdb-tools failed: {e}, trying alternative methods...", file=sys.stderr)
|
|
23
|
+
|
|
24
|
+
# Method 2: Try pyodbc with different drivers
|
|
25
|
+
try:
|
|
26
|
+
print("Trying pyodbc with Access drivers...")
|
|
27
|
+
return _extract_with_pyodbc(access_file)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"⚠ pyodbc method failed: {e}", file=sys.stderr)
|
|
30
|
+
|
|
31
|
+
# If all methods fail
|
|
32
|
+
raise Exception(
|
|
33
|
+
"Unable to extract Access database. Please install one of:\n"
|
|
34
|
+
" - mdb-tools: sudo apt-get install mdb-tools\n"
|
|
35
|
+
" - Microsoft Access ODBC Driver\n"
|
|
36
|
+
" - Or convert your .accdb file to SQLite manually"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def _extract_with_mdbtools(access_file, tables):
|
|
40
|
+
"""Extract using mdb-tools (original method)"""
|
|
41
|
+
data = {}
|
|
42
|
+
for table in tables:
|
|
43
|
+
# Export each table to CSV
|
|
44
|
+
print(table)
|
|
45
|
+
# Use platform-independent temporary file
|
|
46
|
+
temp_dir = tempfile.gettempdir()
|
|
47
|
+
csv_file = os.path.join(temp_dir, f"{table}.csv")
|
|
48
|
+
try:
|
|
49
|
+
with open(csv_file, "w") as f:
|
|
50
|
+
subprocess.run(["mdb-export", access_file, table], stdout=f, check=True)
|
|
51
|
+
except subprocess.CalledProcessError as e:
|
|
52
|
+
print(f"Error exporting table {table} to CSV: {e}", file=sys.stderr)
|
|
53
|
+
continue
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f"An unexpected error occurred during export of table {table}: {e}", file=sys.stderr)
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Read CSV into pandas DataFrame with specific dtype settings
|
|
59
|
+
STRING_COLUMNS = ["row", "column", "sheet"]
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
df = pd.read_csv(csv_file, dtype=str)
|
|
63
|
+
|
|
64
|
+
numeric_columns = []
|
|
65
|
+
for column in df.columns:
|
|
66
|
+
if column in STRING_COLUMNS:
|
|
67
|
+
continue
|
|
68
|
+
# Check if the column contains only numeric values
|
|
69
|
+
try:
|
|
70
|
+
# Convert to numeric and check if any values start with '0' (except '0' itself)
|
|
71
|
+
numeric_series = pd.to_numeric(df[column], errors='coerce')
|
|
72
|
+
has_leading_zeros = df[column].str.match(r'^0\d+').any()
|
|
73
|
+
|
|
74
|
+
if not has_leading_zeros and not numeric_series.isna().all():
|
|
75
|
+
numeric_columns.append(column)
|
|
76
|
+
except Exception:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Convert only the identified numeric columns
|
|
80
|
+
for col in numeric_columns:
|
|
81
|
+
try:
|
|
82
|
+
df[col] = pd.to_numeric(df[col])
|
|
83
|
+
except (ValueError, TypeError):
|
|
84
|
+
# Keep as string if conversion fails
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
data[table] = df
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error processing table {table}: {str(e)}")
|
|
91
|
+
continue
|
|
92
|
+
finally:
|
|
93
|
+
# Clean up
|
|
94
|
+
if os.path.exists(csv_file):
|
|
95
|
+
os.remove(csv_file)
|
|
96
|
+
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
def _extract_with_pyodbc(access_file):
|
|
100
|
+
"""Extract using pyodbc with different Access drivers"""
|
|
101
|
+
try:
|
|
102
|
+
import pyodbc
|
|
103
|
+
except ImportError:
|
|
104
|
+
raise Exception("pyodbc not available")
|
|
105
|
+
|
|
106
|
+
# Try different Access drivers
|
|
107
|
+
drivers_to_try = [
|
|
108
|
+
r'DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};',
|
|
109
|
+
r'DRIVER={Microsoft Access Driver (*.mdb)};',
|
|
110
|
+
r'DRIVER={MDBTools};'
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
conn = None
|
|
114
|
+
for driver in drivers_to_try:
|
|
115
|
+
try:
|
|
116
|
+
conn_str = driver + f'DBQ={access_file};'
|
|
117
|
+
conn = pyodbc.connect(conn_str)
|
|
118
|
+
print(f"✓ Connected using: {driver}")
|
|
119
|
+
break
|
|
120
|
+
except pyodbc.Error:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
if not conn:
|
|
124
|
+
raise Exception("No suitable ODBC driver found for Access database")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Get all table names
|
|
128
|
+
cursor = conn.cursor()
|
|
129
|
+
tables = []
|
|
130
|
+
for table_info in cursor.tables(tableType='TABLE'):
|
|
131
|
+
table_name = table_info.table_name
|
|
132
|
+
if not table_name.startswith('MSys'): # Skip system tables
|
|
133
|
+
tables.append(table_name)
|
|
134
|
+
|
|
135
|
+
data = {}
|
|
136
|
+
STRING_COLUMNS = ["row", "column", "sheet"]
|
|
137
|
+
|
|
138
|
+
# Extract each table
|
|
139
|
+
for table_name in tables:
|
|
140
|
+
print(table_name)
|
|
141
|
+
try:
|
|
142
|
+
cursor.execute(f"SELECT * FROM [{table_name}]")
|
|
143
|
+
columns = [column[0] for column in cursor.description]
|
|
144
|
+
rows = cursor.fetchall()
|
|
145
|
+
|
|
146
|
+
if rows:
|
|
147
|
+
# Convert to DataFrame
|
|
148
|
+
df = pd.DataFrame([list(row) for row in rows], columns=columns)
|
|
149
|
+
|
|
150
|
+
# Apply same dtype conversion logic as mdb-tools method
|
|
151
|
+
# Start with all strings, but preserve None as actual None (not string 'None')
|
|
152
|
+
for col in df.columns:
|
|
153
|
+
df[col] = df[col].astype(object)
|
|
154
|
+
mask = df[col].notna()
|
|
155
|
+
df.loc[mask, col] = df.loc[mask, col].astype(str)
|
|
156
|
+
|
|
157
|
+
numeric_columns = []
|
|
158
|
+
for column in df.columns:
|
|
159
|
+
if column in STRING_COLUMNS:
|
|
160
|
+
continue
|
|
161
|
+
try:
|
|
162
|
+
# Convert to numeric and check if any values start with '0' (except '0' itself)
|
|
163
|
+
# Only check string values for leading zeros
|
|
164
|
+
string_mask = df[column].astype(str).str.match(r'^0\d+', na=False)
|
|
165
|
+
has_leading_zeros = string_mask.any()
|
|
166
|
+
|
|
167
|
+
# Test numeric conversion
|
|
168
|
+
numeric_series = pd.to_numeric(df[column], errors='coerce')
|
|
169
|
+
|
|
170
|
+
if not has_leading_zeros and not numeric_series.isna().all():
|
|
171
|
+
numeric_columns.append(column)
|
|
172
|
+
except Exception:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# Convert only the identified numeric columns
|
|
176
|
+
for col in numeric_columns:
|
|
177
|
+
try:
|
|
178
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
179
|
+
except (ValueError, TypeError):
|
|
180
|
+
# Keep as string if conversion fails
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
data[table_name] = df
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"Error processing table {table_name}: {e}", file=sys.stderr)
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
return data
|
|
190
|
+
|
|
191
|
+
finally:
|
|
192
|
+
conn.close()
|
|
193
|
+
|
|
194
|
+
def migrate_to_sqlite(data, sqlite_db_path):
|
|
195
|
+
"""Migrate data to SQLite"""
|
|
196
|
+
engine = create_engine(f"sqlite:///{sqlite_db_path}")
|
|
197
|
+
|
|
198
|
+
# Create all tables defined in the models
|
|
199
|
+
Base.metadata.create_all(engine)
|
|
200
|
+
|
|
201
|
+
for table_name, df in data.items():
|
|
202
|
+
df.to_sql(
|
|
203
|
+
table_name.replace(" ", "_"), # Sanitize table names
|
|
204
|
+
engine,
|
|
205
|
+
if_exists="replace",
|
|
206
|
+
index=False
|
|
207
|
+
)
|
|
208
|
+
return engine
|
|
209
|
+
|
|
210
|
+
def create_datapoints_view(engine):
|
|
211
|
+
"""Create the datapoints view in the database"""
|
|
212
|
+
# Create a session
|
|
213
|
+
Session = sessionmaker(bind=engine)
|
|
214
|
+
session = Session()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Get the view query
|
|
218
|
+
view_query = ViewDatapoints.create_view_query(session)
|
|
219
|
+
|
|
220
|
+
# Convert the SQLAlchemy query to SQL
|
|
221
|
+
compiled_query = view_query.statement.compile(
|
|
222
|
+
dialect=engine.dialect,
|
|
223
|
+
compile_kwargs={"literal_binds": True}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Create the view in the database
|
|
227
|
+
create_view_sql = f"CREATE VIEW IF NOT EXISTS datapoints AS {compiled_query}"
|
|
228
|
+
|
|
229
|
+
with engine.connect() as conn:
|
|
230
|
+
conn.execute(text(create_view_sql))
|
|
231
|
+
conn.commit()
|
|
232
|
+
|
|
233
|
+
print("Datapoints view created successfully")
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
print(f"Error creating datapoints view: {e}")
|
|
237
|
+
raise
|
|
238
|
+
finally:
|
|
239
|
+
session.close()
|
|
240
|
+
|
|
241
|
+
def run_migration(file_name, sqlite_db_path):
|
|
242
|
+
try:
|
|
243
|
+
# Extract data from Access
|
|
244
|
+
print("Extracting data from Access database...")
|
|
245
|
+
data = extract_access_tables(file_name)
|
|
246
|
+
|
|
247
|
+
# Migrate to SQLite
|
|
248
|
+
print("Migrating data to SQLite...")
|
|
249
|
+
engine = migrate_to_sqlite(data, sqlite_db_path)
|
|
250
|
+
|
|
251
|
+
# Create the datapoints view
|
|
252
|
+
print("Creating datapoints view...")
|
|
253
|
+
create_datapoints_view(engine)
|
|
254
|
+
|
|
255
|
+
print("Migration complete")
|
|
256
|
+
return engine
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
print(f"An error occurred during migration: {e}")
|
|
260
|
+
raise
|
|
261
|
+
|
|
262
|
+
# CLI functionality for standalone CSV export
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
import argparse
|
|
265
|
+
|
|
266
|
+
parser = argparse.ArgumentParser(
|
|
267
|
+
description="Migrate Access database or dump datapoints view to CSV"
|
|
268
|
+
)
|
|
269
|
+
parser.add_argument(
|
|
270
|
+
"database",
|
|
271
|
+
help="Path to the SQLite database file (or Access file for migration)"
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"-o", "--output",
|
|
275
|
+
default="datapoints.csv",
|
|
276
|
+
help="Output CSV file path (default: datapoints.csv)"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
args = parser.parse_args()
|
|
280
|
+
|
|
281
|
+
sqlite_path = args.database.replace('.mdb', '.db')
|
|
282
|
+
run_migration(args.database, sqlite_path)
|