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.
Files changed (94) hide show
  1. py_dpm/AST/ASTConstructor.py +503 -0
  2. py_dpm/AST/ASTObjects.py +827 -0
  3. py_dpm/AST/ASTTemplate.py +101 -0
  4. py_dpm/AST/ASTVisitor.py +13 -0
  5. py_dpm/AST/MLGeneration.py +588 -0
  6. py_dpm/AST/ModuleAnalyzer.py +79 -0
  7. py_dpm/AST/ModuleDependencies.py +203 -0
  8. py_dpm/AST/WhereClauseChecker.py +12 -0
  9. py_dpm/AST/__init__.py +0 -0
  10. py_dpm/AST/check_operands.py +302 -0
  11. py_dpm/DataTypes/ScalarTypes.py +324 -0
  12. py_dpm/DataTypes/TimeClasses.py +370 -0
  13. py_dpm/DataTypes/TypePromotion.py +195 -0
  14. py_dpm/DataTypes/__init__.py +0 -0
  15. py_dpm/Exceptions/__init__.py +0 -0
  16. py_dpm/Exceptions/exceptions.py +84 -0
  17. py_dpm/Exceptions/messages.py +114 -0
  18. py_dpm/OperationScopes/OperationScopeService.py +247 -0
  19. py_dpm/OperationScopes/__init__.py +0 -0
  20. py_dpm/Operators/AggregateOperators.py +138 -0
  21. py_dpm/Operators/BooleanOperators.py +30 -0
  22. py_dpm/Operators/ClauseOperators.py +159 -0
  23. py_dpm/Operators/ComparisonOperators.py +69 -0
  24. py_dpm/Operators/ConditionalOperators.py +362 -0
  25. py_dpm/Operators/NumericOperators.py +101 -0
  26. py_dpm/Operators/Operator.py +388 -0
  27. py_dpm/Operators/StringOperators.py +27 -0
  28. py_dpm/Operators/TimeOperators.py +53 -0
  29. py_dpm/Operators/__init__.py +0 -0
  30. py_dpm/Utils/ValidationsGenerationUtils.py +429 -0
  31. py_dpm/Utils/__init__.py +0 -0
  32. py_dpm/Utils/operands_mapping.py +73 -0
  33. py_dpm/Utils/operator_mapping.py +89 -0
  34. py_dpm/Utils/tokens.py +172 -0
  35. py_dpm/Utils/utils.py +2 -0
  36. py_dpm/ValidationsGeneration/PropertiesConstraintsProcessor.py +190 -0
  37. py_dpm/ValidationsGeneration/Utils.py +364 -0
  38. py_dpm/ValidationsGeneration/VariantsProcessor.py +265 -0
  39. py_dpm/ValidationsGeneration/__init__.py +0 -0
  40. py_dpm/ValidationsGeneration/auxiliary_functions.py +98 -0
  41. py_dpm/__init__.py +61 -0
  42. py_dpm/api/__init__.py +140 -0
  43. py_dpm/api/ast_generator.py +438 -0
  44. py_dpm/api/complete_ast.py +241 -0
  45. py_dpm/api/data_dictionary_validation.py +577 -0
  46. py_dpm/api/migration.py +77 -0
  47. py_dpm/api/semantic.py +224 -0
  48. py_dpm/api/syntax.py +182 -0
  49. py_dpm/client.py +106 -0
  50. py_dpm/data_handlers.py +99 -0
  51. py_dpm/db_utils.py +117 -0
  52. py_dpm/grammar/__init__.py +0 -0
  53. py_dpm/grammar/dist/__init__.py +0 -0
  54. py_dpm/grammar/dist/dpm_xlLexer.interp +428 -0
  55. py_dpm/grammar/dist/dpm_xlLexer.py +804 -0
  56. py_dpm/grammar/dist/dpm_xlLexer.tokens +106 -0
  57. py_dpm/grammar/dist/dpm_xlParser.interp +249 -0
  58. py_dpm/grammar/dist/dpm_xlParser.py +5224 -0
  59. py_dpm/grammar/dist/dpm_xlParser.tokens +106 -0
  60. py_dpm/grammar/dist/dpm_xlParserListener.py +742 -0
  61. py_dpm/grammar/dist/dpm_xlParserVisitor.py +419 -0
  62. py_dpm/grammar/dist/listeners.py +10 -0
  63. py_dpm/grammar/dpm_xlLexer.g4 +435 -0
  64. py_dpm/grammar/dpm_xlParser.g4 +260 -0
  65. py_dpm/migration.py +282 -0
  66. py_dpm/models.py +2139 -0
  67. py_dpm/semantics/DAG/DAGAnalyzer.py +158 -0
  68. py_dpm/semantics/DAG/__init__.py +0 -0
  69. py_dpm/semantics/SemanticAnalyzer.py +320 -0
  70. py_dpm/semantics/Symbols.py +223 -0
  71. py_dpm/semantics/__init__.py +0 -0
  72. py_dpm/utils/__init__.py +0 -0
  73. py_dpm/utils/ast_serialization.py +481 -0
  74. py_dpm/views/data_types.sql +12 -0
  75. py_dpm/views/datapoints.sql +65 -0
  76. py_dpm/views/hierarchy_operand_reference.sql +11 -0
  77. py_dpm/views/hierarchy_preconditions.sql +13 -0
  78. py_dpm/views/hierarchy_variables.sql +26 -0
  79. py_dpm/views/hierarchy_variables_context.sql +14 -0
  80. py_dpm/views/key_components.sql +18 -0
  81. py_dpm/views/module_from_table.sql +11 -0
  82. py_dpm/views/open_keys.sql +13 -0
  83. py_dpm/views/operation_info.sql +27 -0
  84. py_dpm/views/operation_list.sql +18 -0
  85. py_dpm/views/operations_versions_from_module_version.sql +30 -0
  86. py_dpm/views/precondition_info.sql +17 -0
  87. py_dpm/views/report_type_operand_reference_info.sql +18 -0
  88. py_dpm/views/subcategory_info.sql +17 -0
  89. py_dpm/views/table_info.sql +19 -0
  90. pydpm_xl-0.1.10.dist-info/LICENSE +674 -0
  91. pydpm_xl-0.1.10.dist-info/METADATA +50 -0
  92. pydpm_xl-0.1.10.dist-info/RECORD +94 -0
  93. pydpm_xl-0.1.10.dist-info/WHEEL +4 -0
  94. 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)