informatica-python 1.6.1__tar.gz → 1.7.1__tar.gz
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.
- {informatica_python-1.6.1 → informatica_python-1.7.1}/PKG-INFO +1 -1
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/__init__.py +1 -1
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/helper_gen.py +235 -4
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/mapping_gen.py +91 -7
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/workflow_gen.py +23 -2
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/PKG-INFO +1 -1
- {informatica_python-1.6.1 → informatica_python-1.7.1}/pyproject.toml +1 -1
- {informatica_python-1.6.1 → informatica_python-1.7.1}/tests/test_integration.py +311 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/LICENSE +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/README.md +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/cli.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/converter.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/__init__.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/config_gen.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/error_log_gen.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/sql_gen.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/models.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/parser.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/__init__.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/datatype_map.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/expression_converter.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/lib_adapters.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/sql_dialect.py +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/SOURCES.txt +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/dependency_links.txt +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/entry_points.txt +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/requires.txt +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/top_level.txt +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/setup.cfg +0 -0
- {informatica_python-1.6.1 → informatica_python-1.7.1}/tests/test_converter.py +0 -0
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/helper_gen.py
RENAMED
|
@@ -1063,10 +1063,15 @@ def _add_expression_helpers(lines):
|
|
|
1063
1063
|
lines.append("")
|
|
1064
1064
|
lines.append("")
|
|
1065
1065
|
lines.append("def percentile_val(value, pct):")
|
|
1066
|
-
lines.append(' """Informatica PERCENTILE equivalent."""')
|
|
1066
|
+
lines.append(' """Informatica PERCENTILE equivalent (row-level fallback)."""')
|
|
1067
1067
|
lines.append(" return value")
|
|
1068
1068
|
lines.append("")
|
|
1069
1069
|
lines.append("")
|
|
1070
|
+
lines.append("def percentile_df(df, col, pct=0.5):")
|
|
1071
|
+
lines.append(' """Informatica PERCENTILE equivalent (DataFrame-level)."""')
|
|
1072
|
+
lines.append(" return df[col].quantile(pct)")
|
|
1073
|
+
lines.append("")
|
|
1074
|
+
lines.append("")
|
|
1070
1075
|
lines.append("def first_val(*args):")
|
|
1071
1076
|
lines.append(' """Informatica FIRST equivalent."""')
|
|
1072
1077
|
lines.append(" for a in args:")
|
|
@@ -1085,20 +1090,38 @@ def _add_expression_helpers(lines):
|
|
|
1085
1090
|
lines.append("")
|
|
1086
1091
|
lines.append("")
|
|
1087
1092
|
lines.append("def moving_avg(value, window=3):")
|
|
1088
|
-
lines.append(' """Informatica MOVINGAVG equivalent."""')
|
|
1093
|
+
lines.append(' """Informatica MOVINGAVG equivalent (row-level fallback)."""')
|
|
1089
1094
|
lines.append(" return value")
|
|
1090
1095
|
lines.append("")
|
|
1091
1096
|
lines.append("")
|
|
1097
|
+
lines.append("def moving_avg_df(df, col, window=3):")
|
|
1098
|
+
lines.append(' """Informatica MOVINGAVG equivalent (DataFrame-level).')
|
|
1099
|
+
lines.append(' Returns a rolling mean over the specified window size."""')
|
|
1100
|
+
lines.append(" return df[col].rolling(window=window, min_periods=1).mean()")
|
|
1101
|
+
lines.append("")
|
|
1102
|
+
lines.append("")
|
|
1092
1103
|
lines.append("def moving_sum(value, window=3):")
|
|
1093
|
-
lines.append(' """Informatica MOVINGSUM equivalent."""')
|
|
1104
|
+
lines.append(' """Informatica MOVINGSUM equivalent (row-level fallback)."""')
|
|
1094
1105
|
lines.append(" return value")
|
|
1095
1106
|
lines.append("")
|
|
1096
1107
|
lines.append("")
|
|
1108
|
+
lines.append("def moving_sum_df(df, col, window=3):")
|
|
1109
|
+
lines.append(' """Informatica MOVINGSUM equivalent (DataFrame-level).')
|
|
1110
|
+
lines.append(' Returns a rolling sum over the specified window size."""')
|
|
1111
|
+
lines.append(" return df[col].rolling(window=window, min_periods=1).sum()")
|
|
1112
|
+
lines.append("")
|
|
1113
|
+
lines.append("")
|
|
1097
1114
|
lines.append("def cume(value):")
|
|
1098
|
-
lines.append(' """Informatica CUME equivalent."""')
|
|
1115
|
+
lines.append(' """Informatica CUME equivalent (row-level fallback)."""')
|
|
1099
1116
|
lines.append(" return value")
|
|
1100
1117
|
lines.append("")
|
|
1101
1118
|
lines.append("")
|
|
1119
|
+
lines.append("def cume_df(df, col):")
|
|
1120
|
+
lines.append(' """Informatica CUME equivalent (DataFrame-level).')
|
|
1121
|
+
lines.append(' Returns the cumulative (expanding) sum of the column."""')
|
|
1122
|
+
lines.append(" return df[col].expanding(min_periods=1).sum()")
|
|
1123
|
+
lines.append("")
|
|
1124
|
+
lines.append("")
|
|
1102
1125
|
lines.append("def set_count_variable(var_name, value=1):")
|
|
1103
1126
|
lines.append(' """Informatica SETCOUNTVARIABLE equivalent."""')
|
|
1104
1127
|
lines.append(" return set_variable(var_name, value)")
|
|
@@ -1150,6 +1173,214 @@ def _add_expression_helpers(lines):
|
|
|
1150
1173
|
lines.append("")
|
|
1151
1174
|
lines.append("")
|
|
1152
1175
|
|
|
1176
|
+
_add_stored_procedure_functions(lines)
|
|
1177
|
+
_add_update_strategy_functions(lines)
|
|
1178
|
+
_add_state_persistence_functions(lines)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def _add_stored_procedure_functions(lines):
|
|
1182
|
+
lines.append("# ============================================================")
|
|
1183
|
+
lines.append("# Stored Procedure Execution")
|
|
1184
|
+
lines.append("# ============================================================")
|
|
1185
|
+
lines.append("")
|
|
1186
|
+
lines.append("")
|
|
1187
|
+
lines.append("def call_stored_procedure(config, proc_name, params=None, connection_name='default', output_params=None):")
|
|
1188
|
+
lines.append(' """')
|
|
1189
|
+
lines.append(" Execute a stored procedure and return results.")
|
|
1190
|
+
lines.append(" params: dict of {param_name: value} for input parameters")
|
|
1191
|
+
lines.append(" output_params: list of output parameter names to capture")
|
|
1192
|
+
lines.append(" Returns: (result_df, output_values) where output_values is a dict")
|
|
1193
|
+
lines.append(' """')
|
|
1194
|
+
lines.append(" if params is None:")
|
|
1195
|
+
lines.append(" params = {}")
|
|
1196
|
+
lines.append(" if output_params is None:")
|
|
1197
|
+
lines.append(" output_params = []")
|
|
1198
|
+
lines.append("")
|
|
1199
|
+
lines.append(" conn = get_db_connection(config, connection_name)")
|
|
1200
|
+
lines.append(" conn_config = config.get('connections', {}).get(connection_name, {})")
|
|
1201
|
+
lines.append(" db_type = conn_config.get('type', 'mssql')")
|
|
1202
|
+
lines.append("")
|
|
1203
|
+
lines.append(" try:")
|
|
1204
|
+
lines.append(" cursor = conn.cursor()")
|
|
1205
|
+
lines.append(" logger.info(f'Calling stored procedure: {proc_name} with params: {params}')")
|
|
1206
|
+
lines.append("")
|
|
1207
|
+
lines.append(" if db_type == 'oracle':")
|
|
1208
|
+
lines.append(" param_list = list(params.values())")
|
|
1209
|
+
lines.append(" for out_p in output_params:")
|
|
1210
|
+
lines.append(" import cx_Oracle")
|
|
1211
|
+
lines.append(" param_list.append(cursor.var(cx_Oracle.STRING))")
|
|
1212
|
+
lines.append(" result = cursor.callproc(proc_name, param_list)")
|
|
1213
|
+
lines.append(" out_values = {}")
|
|
1214
|
+
lines.append(" for i, out_p in enumerate(output_params):")
|
|
1215
|
+
lines.append(" out_values[out_p] = result[len(params) + i].getvalue()")
|
|
1216
|
+
lines.append(" elif db_type == 'mssql':")
|
|
1217
|
+
lines.append(" param_placeholders = ', '.join(['?'] * len(params))")
|
|
1218
|
+
lines.append(" if output_params:")
|
|
1219
|
+
lines.append(" out_decl = '; '.join(f'@{p} OUTPUT' for p in output_params)")
|
|
1220
|
+
lines.append(" sql = f'EXEC {proc_name} {param_placeholders}'")
|
|
1221
|
+
lines.append(" if out_decl:")
|
|
1222
|
+
lines.append(" sql += f', {out_decl}'")
|
|
1223
|
+
lines.append(" else:")
|
|
1224
|
+
lines.append(" sql = f'EXEC {proc_name} {param_placeholders}'")
|
|
1225
|
+
lines.append(" cursor.execute(sql, list(params.values()))")
|
|
1226
|
+
lines.append(" out_values = {}")
|
|
1227
|
+
lines.append(" else:")
|
|
1228
|
+
lines.append(" param_placeholders = ', '.join(['%s'] * len(params))")
|
|
1229
|
+
lines.append(" cursor.execute(f'CALL {proc_name}({param_placeholders})', list(params.values()))")
|
|
1230
|
+
lines.append(" out_values = {}")
|
|
1231
|
+
lines.append("")
|
|
1232
|
+
lines.append(" try:")
|
|
1233
|
+
lines.append(" columns = [desc[0] for desc in cursor.description] if cursor.description else []")
|
|
1234
|
+
lines.append(" rows = cursor.fetchall()")
|
|
1235
|
+
lines.append(" result_df = pd.DataFrame(rows, columns=columns) if columns else pd.DataFrame()")
|
|
1236
|
+
lines.append(" except Exception:")
|
|
1237
|
+
lines.append(" result_df = pd.DataFrame()")
|
|
1238
|
+
lines.append("")
|
|
1239
|
+
lines.append(" conn.commit()")
|
|
1240
|
+
lines.append(" logger.info(f'Stored procedure {proc_name} returned {len(result_df)} rows')")
|
|
1241
|
+
lines.append(" return result_df, out_values")
|
|
1242
|
+
lines.append("")
|
|
1243
|
+
lines.append(" except Exception as e:")
|
|
1244
|
+
lines.append(" logger.error(f'Stored procedure {proc_name} failed: {e}')")
|
|
1245
|
+
lines.append(" conn.rollback()")
|
|
1246
|
+
lines.append(" raise")
|
|
1247
|
+
lines.append(" finally:")
|
|
1248
|
+
lines.append(" conn.close()")
|
|
1249
|
+
lines.append("")
|
|
1250
|
+
lines.append("")
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
def _add_update_strategy_functions(lines):
|
|
1254
|
+
lines.append("# ============================================================")
|
|
1255
|
+
lines.append("# Update Strategy Target Operations")
|
|
1256
|
+
lines.append("# ============================================================")
|
|
1257
|
+
lines.append("")
|
|
1258
|
+
lines.append("")
|
|
1259
|
+
lines.append("def write_with_update_strategy(config, df, table_name, connection_name='default', key_columns=None, schema=None):")
|
|
1260
|
+
lines.append(' """')
|
|
1261
|
+
lines.append(" Write DataFrame to database using row-level update strategy.")
|
|
1262
|
+
lines.append(" Expects a '_update_strategy' column with values: INSERT, UPDATE, DELETE, REJECT.")
|
|
1263
|
+
lines.append(" key_columns: list of primary key columns for UPDATE/DELETE operations.")
|
|
1264
|
+
lines.append(' """')
|
|
1265
|
+
lines.append(" if '_update_strategy' not in df.columns:")
|
|
1266
|
+
lines.append(" logger.warning('No _update_strategy column found — falling back to bulk INSERT')")
|
|
1267
|
+
lines.append(" write_to_db(config, df, table_name, connection_name, schema=schema)")
|
|
1268
|
+
lines.append(" return")
|
|
1269
|
+
lines.append("")
|
|
1270
|
+
lines.append(" conn = get_db_connection(config, connection_name)")
|
|
1271
|
+
lines.append(" conn_config = config.get('connections', {}).get(connection_name, {})")
|
|
1272
|
+
lines.append(" if schema is None:")
|
|
1273
|
+
lines.append(" schema = conn_config.get('schema', 'dbo')")
|
|
1274
|
+
lines.append(" qualified_table = f'{schema}.{table_name}' if schema else table_name")
|
|
1275
|
+
lines.append("")
|
|
1276
|
+
lines.append(" data_cols = [c for c in df.columns if c != '_update_strategy']")
|
|
1277
|
+
lines.append(" if key_columns is None:")
|
|
1278
|
+
lines.append(" key_columns = data_cols[:1]")
|
|
1279
|
+
lines.append(" logger.warning(f'No key columns specified for update strategy — using first column: {key_columns}')")
|
|
1280
|
+
lines.append("")
|
|
1281
|
+
lines.append(" db_type = conn_config.get('type', 'mssql')")
|
|
1282
|
+
lines.append(" ph = '?' if db_type == 'mssql' else '%s'")
|
|
1283
|
+
lines.append("")
|
|
1284
|
+
lines.append(" try:")
|
|
1285
|
+
lines.append(" cursor = conn.cursor()")
|
|
1286
|
+
lines.append("")
|
|
1287
|
+
lines.append(" df_insert = df[df['_update_strategy'] == 'INSERT']")
|
|
1288
|
+
lines.append(" df_update = df[df['_update_strategy'] == 'UPDATE']")
|
|
1289
|
+
lines.append(" df_delete = df[df['_update_strategy'] == 'DELETE']")
|
|
1290
|
+
lines.append(" df_reject = df[df['_update_strategy'] == 'REJECT']")
|
|
1291
|
+
lines.append("")
|
|
1292
|
+
lines.append(" if len(df_reject) > 0:")
|
|
1293
|
+
lines.append(" logger.warning(f'Rejecting {len(df_reject)} rows for target {table_name}')")
|
|
1294
|
+
lines.append("")
|
|
1295
|
+
lines.append(" if len(df_insert) > 0:")
|
|
1296
|
+
lines.append(" logger.info(f'Inserting {len(df_insert)} rows into {qualified_table}')")
|
|
1297
|
+
lines.append(" df_insert[data_cols].to_sql(table_name, conn, schema=schema, if_exists='append', index=False)")
|
|
1298
|
+
lines.append("")
|
|
1299
|
+
lines.append(" if len(df_update) > 0:")
|
|
1300
|
+
lines.append(" non_key_cols = [c for c in data_cols if c not in key_columns]")
|
|
1301
|
+
lines.append(" set_clause = ', '.join(f'{c} = {ph}' for c in non_key_cols)")
|
|
1302
|
+
lines.append(" where_clause = ' AND '.join(f'{c} = {ph}' for c in key_columns)")
|
|
1303
|
+
lines.append(" update_sql = f'UPDATE {qualified_table} SET {set_clause} WHERE {where_clause}'")
|
|
1304
|
+
lines.append(" logger.info(f'Updating {len(df_update)} rows in {qualified_table}')")
|
|
1305
|
+
lines.append(" for _, row in df_update.iterrows():")
|
|
1306
|
+
lines.append(" values = [row[c] for c in non_key_cols] + [row[c] for c in key_columns]")
|
|
1307
|
+
lines.append(" cursor.execute(update_sql, values)")
|
|
1308
|
+
lines.append("")
|
|
1309
|
+
lines.append(" if len(df_delete) > 0:")
|
|
1310
|
+
lines.append(" where_clause = ' AND '.join(f'{c} = {ph}' for c in key_columns)")
|
|
1311
|
+
lines.append(" delete_sql = f'DELETE FROM {qualified_table} WHERE {where_clause}'")
|
|
1312
|
+
lines.append(" logger.info(f'Deleting {len(df_delete)} rows from {qualified_table}')")
|
|
1313
|
+
lines.append(" for _, row in df_delete.iterrows():")
|
|
1314
|
+
lines.append(" values = [row[c] for c in key_columns]")
|
|
1315
|
+
lines.append(" cursor.execute(delete_sql, values)")
|
|
1316
|
+
lines.append("")
|
|
1317
|
+
lines.append(" conn.commit()")
|
|
1318
|
+
lines.append(" total = len(df_insert) + len(df_update) + len(df_delete)")
|
|
1319
|
+
lines.append(" logger.info(f'Update strategy complete for {qualified_table}: {len(df_insert)} inserts, {len(df_update)} updates, {len(df_delete)} deletes, {len(df_reject)} rejects')")
|
|
1320
|
+
lines.append("")
|
|
1321
|
+
lines.append(" except Exception as e:")
|
|
1322
|
+
lines.append(" logger.error(f'Update strategy write error for {qualified_table}: {e}')")
|
|
1323
|
+
lines.append(" conn.rollback()")
|
|
1324
|
+
lines.append(" raise")
|
|
1325
|
+
lines.append(" finally:")
|
|
1326
|
+
lines.append(" conn.close()")
|
|
1327
|
+
lines.append("")
|
|
1328
|
+
lines.append("")
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
def _add_state_persistence_functions(lines):
|
|
1332
|
+
lines.append("# ============================================================")
|
|
1333
|
+
lines.append("# State Persistence (Persistent Variables)")
|
|
1334
|
+
lines.append("# ============================================================")
|
|
1335
|
+
lines.append("")
|
|
1336
|
+
lines.append("")
|
|
1337
|
+
lines.append("_persistent_state = {}")
|
|
1338
|
+
lines.append("")
|
|
1339
|
+
lines.append("")
|
|
1340
|
+
lines.append("def load_persistent_state(state_file='persistent_state.json'):")
|
|
1341
|
+
lines.append(' """Load persistent variable state from JSON file."""')
|
|
1342
|
+
lines.append(" import json")
|
|
1343
|
+
lines.append(" global _persistent_state")
|
|
1344
|
+
lines.append(" if os.path.exists(state_file):")
|
|
1345
|
+
lines.append(" try:")
|
|
1346
|
+
lines.append(" with open(state_file, 'r') as f:")
|
|
1347
|
+
lines.append(" _persistent_state = json.load(f)")
|
|
1348
|
+
lines.append(" logger.info(f'Loaded persistent state from {state_file} ({len(_persistent_state)} scopes)')")
|
|
1349
|
+
lines.append(" except Exception as e:")
|
|
1350
|
+
lines.append(" logger.warning(f'Could not load persistent state from {state_file}: {e}')")
|
|
1351
|
+
lines.append(" _persistent_state = {}")
|
|
1352
|
+
lines.append(" else:")
|
|
1353
|
+
lines.append(" logger.info(f'No persistent state file found at {state_file} — starting fresh')")
|
|
1354
|
+
lines.append(" _persistent_state = {}")
|
|
1355
|
+
lines.append(" return _persistent_state")
|
|
1356
|
+
lines.append("")
|
|
1357
|
+
lines.append("")
|
|
1358
|
+
lines.append("def save_persistent_state(state_file='persistent_state.json'):")
|
|
1359
|
+
lines.append(' """Save persistent variable state to JSON file."""')
|
|
1360
|
+
lines.append(" import json")
|
|
1361
|
+
lines.append(" try:")
|
|
1362
|
+
lines.append(" with open(state_file, 'w') as f:")
|
|
1363
|
+
lines.append(" json.dump(_persistent_state, f, indent=2, default=str)")
|
|
1364
|
+
lines.append(" logger.info(f'Saved persistent state to {state_file}')")
|
|
1365
|
+
lines.append(" except Exception as e:")
|
|
1366
|
+
lines.append(" logger.warning(f'Could not save persistent state to {state_file}: {e}')")
|
|
1367
|
+
lines.append("")
|
|
1368
|
+
lines.append("")
|
|
1369
|
+
lines.append("def get_persistent_variable(scope, var_name, default=None):")
|
|
1370
|
+
lines.append(' """Get a persistent variable value from state."""')
|
|
1371
|
+
lines.append(" scope_state = _persistent_state.get(scope, {})")
|
|
1372
|
+
lines.append(" return scope_state.get(var_name, default)")
|
|
1373
|
+
lines.append("")
|
|
1374
|
+
lines.append("")
|
|
1375
|
+
lines.append("def set_persistent_variable(scope, var_name, value):")
|
|
1376
|
+
lines.append(' """Set a persistent variable value in state."""')
|
|
1377
|
+
lines.append(" if scope not in _persistent_state:")
|
|
1378
|
+
lines.append(" _persistent_state[scope] = {}")
|
|
1379
|
+
lines.append(" _persistent_state[scope][var_name] = value")
|
|
1380
|
+
lines.append(" return value")
|
|
1381
|
+
lines.append("")
|
|
1382
|
+
lines.append("")
|
|
1383
|
+
|
|
1153
1384
|
|
|
1154
1385
|
def _add_utility_functions(lines):
|
|
1155
1386
|
lines.append("# ============================================================")
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/mapping_gen.py
RENAMED
|
@@ -253,6 +253,15 @@ def generate_mapping_code(mapping: MappingDef, folder: FolderDef,
|
|
|
253
253
|
lines.append(f" start_time = log_mapping_start('{mapping.name}')")
|
|
254
254
|
lines.append("")
|
|
255
255
|
|
|
256
|
+
has_persistent_vars = any(
|
|
257
|
+
getattr(v, 'is_persistent', 'NO').upper() == 'YES'
|
|
258
|
+
for v in (mapping.variables or [])
|
|
259
|
+
)
|
|
260
|
+
if has_persistent_vars:
|
|
261
|
+
lines.append(" # Load persistent state for mapping variables")
|
|
262
|
+
lines.append(" load_persistent_state()")
|
|
263
|
+
lines.append("")
|
|
264
|
+
|
|
256
265
|
if mapping.variables:
|
|
257
266
|
lines.append(" # Mapping Variables")
|
|
258
267
|
for var in mapping.variables:
|
|
@@ -260,7 +269,11 @@ def generate_mapping_code(mapping: MappingDef, folder: FolderDef,
|
|
|
260
269
|
default = var.default_value or "''"
|
|
261
270
|
if var.datatype.lower() in ("integer", "bigint", "int"):
|
|
262
271
|
default = var.default_value or "0"
|
|
263
|
-
|
|
272
|
+
if getattr(var, 'is_persistent', 'NO').upper() == 'YES':
|
|
273
|
+
has_persistent_vars = True
|
|
274
|
+
lines.append(f" {safe_var} = get_persistent_variable('{mapping.name}', '{safe_var}', {default})")
|
|
275
|
+
else:
|
|
276
|
+
lines.append(f" {safe_var} = {default}")
|
|
264
277
|
lines.append("")
|
|
265
278
|
|
|
266
279
|
if session_overrides:
|
|
@@ -313,6 +326,14 @@ def generate_mapping_code(mapping: MappingDef, folder: FolderDef,
|
|
|
313
326
|
for tgt_name, tgt_def in target_map.items():
|
|
314
327
|
_generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs, transform_map, instance_map, session_overrides, validate_casts=validate_casts)
|
|
315
328
|
|
|
329
|
+
if has_persistent_vars:
|
|
330
|
+
lines.append(" # Save persistent mapping variables")
|
|
331
|
+
for var in mapping.variables:
|
|
332
|
+
if getattr(var, 'is_persistent', 'NO').upper() == 'YES':
|
|
333
|
+
safe_var = _safe_name(var.name.replace("$$", ""))
|
|
334
|
+
lines.append(f" set_persistent_variable('{mapping.name}', '{safe_var}', {safe_var})")
|
|
335
|
+
lines.append(" save_persistent_state()")
|
|
336
|
+
|
|
316
337
|
lines.append("")
|
|
317
338
|
lines.append(f" log_mapping_end('{mapping.name}', start_time)")
|
|
318
339
|
lines.append(f" logger.info('Mapping {mapping.name} completed successfully')")
|
|
@@ -978,10 +999,30 @@ def _gen_update_strategy(lines, tx, tx_safe, input_df, source_dfs):
|
|
|
978
999
|
if attr.name == "Update Strategy Expression":
|
|
979
1000
|
strategy_expr = attr.value
|
|
980
1001
|
strategy_map = {"0": "INSERT", "1": "UPDATE", "2": "DELETE", "3": "REJECT"}
|
|
981
|
-
|
|
982
|
-
lines.append(f" # Update Strategy: {
|
|
1002
|
+
|
|
1003
|
+
lines.append(f" # Update Strategy: {tx.name}")
|
|
983
1004
|
lines.append(f" df_{tx_safe} = {input_df}.copy()")
|
|
984
|
-
|
|
1005
|
+
|
|
1006
|
+
if strategy_expr in strategy_map:
|
|
1007
|
+
strategy_name = strategy_map[strategy_expr]
|
|
1008
|
+
lines.append(f" df_{tx_safe}['_update_strategy'] = '{strategy_name}'")
|
|
1009
|
+
else:
|
|
1010
|
+
dd_map = {
|
|
1011
|
+
"DD_INSERT": "INSERT", "DD_UPDATE": "UPDATE",
|
|
1012
|
+
"DD_DELETE": "DELETE", "DD_REJECT": "REJECT",
|
|
1013
|
+
}
|
|
1014
|
+
expr = strategy_expr
|
|
1015
|
+
for dd_const, label in dd_map.items():
|
|
1016
|
+
expr = expr.replace(dd_const, f"'{label}'")
|
|
1017
|
+
try:
|
|
1018
|
+
converted = convert_expression(expr)
|
|
1019
|
+
lines.append(f" # Original expression: {strategy_expr}")
|
|
1020
|
+
lines.append(f" def _resolve_strategy(row):")
|
|
1021
|
+
lines.append(f" return {converted}")
|
|
1022
|
+
lines.append(f" df_{tx_safe}['_update_strategy'] = df_{tx_safe}.apply(_resolve_strategy, axis=1)")
|
|
1023
|
+
except Exception:
|
|
1024
|
+
lines.append(f" # Could not parse strategy expression: {strategy_expr}")
|
|
1025
|
+
lines.append(f" df_{tx_safe}['_update_strategy'] = 'INSERT'")
|
|
985
1026
|
source_dfs[tx.name] = f"df_{tx_safe}"
|
|
986
1027
|
|
|
987
1028
|
|
|
@@ -1186,13 +1227,47 @@ def _gen_custom_transform(lines, tx, tx_safe, input_df, input_sources, source_df
|
|
|
1186
1227
|
|
|
1187
1228
|
def _gen_stored_proc(lines, tx, tx_safe, input_df, source_dfs):
|
|
1188
1229
|
proc_name = ""
|
|
1230
|
+
conn_name = "default"
|
|
1189
1231
|
for attr in tx.attributes:
|
|
1190
1232
|
if attr.name in ("Stored Procedure Name", "sp name"):
|
|
1191
1233
|
proc_name = attr.value
|
|
1234
|
+
elif attr.name in ("Connection Name", "connection_name"):
|
|
1235
|
+
conn_name = attr.value or "default"
|
|
1236
|
+
|
|
1237
|
+
input_params = []
|
|
1238
|
+
output_params = []
|
|
1239
|
+
for fld in tx.fields:
|
|
1240
|
+
pt = (fld.porttype or "").upper()
|
|
1241
|
+
if "INPUT" in pt and "OUTPUT" not in pt:
|
|
1242
|
+
input_params.append(fld.name)
|
|
1243
|
+
elif "OUTPUT" in pt:
|
|
1244
|
+
output_params.append(fld.name)
|
|
1192
1245
|
|
|
1193
1246
|
lines.append(f" # Stored Procedure: {proc_name or tx.name}")
|
|
1194
|
-
|
|
1195
|
-
|
|
1247
|
+
|
|
1248
|
+
if input_params:
|
|
1249
|
+
param_dict_items = ", ".join(f"'{p}': {input_df}['{p}'].iloc[0] if '{p}' in {input_df}.columns and len({input_df}) > 0 else None" for p in input_params)
|
|
1250
|
+
lines.append(f" _sp_params_{tx_safe} = {{{param_dict_items}}}")
|
|
1251
|
+
else:
|
|
1252
|
+
lines.append(f" _sp_params_{tx_safe} = {{}}")
|
|
1253
|
+
|
|
1254
|
+
if output_params:
|
|
1255
|
+
out_list = repr(output_params)
|
|
1256
|
+
lines.append(f" _sp_out_names_{tx_safe} = {out_list}")
|
|
1257
|
+
else:
|
|
1258
|
+
lines.append(f" _sp_out_names_{tx_safe} = []")
|
|
1259
|
+
|
|
1260
|
+
lines.append(f" df_{tx_safe}, _sp_out_vals_{tx_safe} = call_stored_procedure(")
|
|
1261
|
+
lines.append(f" config, '{proc_name or tx.name}', params=_sp_params_{tx_safe},")
|
|
1262
|
+
lines.append(f" connection_name='{conn_name}', output_params=_sp_out_names_{tx_safe})")
|
|
1263
|
+
|
|
1264
|
+
if output_params:
|
|
1265
|
+
lines.append(f" if df_{tx_safe}.empty and _sp_out_vals_{tx_safe}:")
|
|
1266
|
+
lines.append(f" df_{tx_safe} = {input_df}.copy()")
|
|
1267
|
+
for op in output_params:
|
|
1268
|
+
lines.append(f" if '{op}' in _sp_out_vals_{tx_safe}:")
|
|
1269
|
+
lines.append(f" df_{tx_safe}['{op}'] = _sp_out_vals_{tx_safe}['{op}']")
|
|
1270
|
+
|
|
1196
1271
|
source_dfs[tx.name] = f"df_{tx_safe}"
|
|
1197
1272
|
|
|
1198
1273
|
|
|
@@ -1245,6 +1320,8 @@ def _generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs
|
|
|
1245
1320
|
target_cols = [f.name for f in tgt_def.fields] if tgt_def.fields else None
|
|
1246
1321
|
if target_cols:
|
|
1247
1322
|
lines.append(f" available_cols = [c for c in {target_cols} if c in df_target_{tgt_safe}.columns]")
|
|
1323
|
+
lines.append(f" if '_update_strategy' in df_target_{tgt_safe}.columns and '_update_strategy' not in available_cols:")
|
|
1324
|
+
lines.append(f" available_cols.append('_update_strategy')")
|
|
1248
1325
|
lines.append(f" df_target_{tgt_safe} = df_target_{tgt_safe}[available_cols]")
|
|
1249
1326
|
else:
|
|
1250
1327
|
lines.append(f" df_target_{tgt_safe} = {input_df}")
|
|
@@ -1265,7 +1342,14 @@ def _generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs
|
|
|
1265
1342
|
lines.append(f" write_file(df_target_{tgt_safe}, _tgt_path_{tgt_safe}, config.get('targets', {{}}).get('{tgt_def.name}', {{}}))")
|
|
1266
1343
|
elif tgt_def.database_type and tgt_def.database_type != "Flat File":
|
|
1267
1344
|
conn_label = tgt_conn or "target"
|
|
1268
|
-
lines.append(f"
|
|
1345
|
+
lines.append(f" if '_update_strategy' in df_target_{tgt_safe}.columns:")
|
|
1346
|
+
key_cols = [f.name for f in tgt_def.fields if getattr(f, 'keytype', 'NOT A KEY') == 'PRIMARY KEY'] or None
|
|
1347
|
+
if key_cols:
|
|
1348
|
+
lines.append(f" write_with_update_strategy(config, df_target_{tgt_safe}, '{tgt_def.name}', '{conn_label}', key_columns={key_cols})")
|
|
1349
|
+
else:
|
|
1350
|
+
lines.append(f" write_with_update_strategy(config, df_target_{tgt_safe}, '{tgt_def.name}', '{conn_label}')")
|
|
1351
|
+
lines.append(f" else:")
|
|
1352
|
+
lines.append(f" write_to_db(config, df_target_{tgt_safe}, '{tgt_def.name}', '{conn_label}')")
|
|
1269
1353
|
elif tgt_def.flatfile:
|
|
1270
1354
|
_emit_flatfile_write(lines, tgt_safe, tgt_def)
|
|
1271
1355
|
else:
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/workflow_gen.py
RENAMED
|
@@ -22,7 +22,7 @@ def generate_workflow_code(folder: FolderDef) -> str:
|
|
|
22
22
|
lines.append("import sys")
|
|
23
23
|
lines.append("import logging")
|
|
24
24
|
lines.append("from datetime import datetime")
|
|
25
|
-
lines.append("from helper_functions import load_config, logger")
|
|
25
|
+
lines.append("from helper_functions import load_config, logger, load_persistent_state, save_persistent_state, get_persistent_variable, set_persistent_variable")
|
|
26
26
|
lines.append("")
|
|
27
27
|
|
|
28
28
|
for i, mapping in enumerate(folder.mappings, 1):
|
|
@@ -124,12 +124,24 @@ def _generate_workflow_function(lines, wf: WorkflowDef, folder: FolderDef, workl
|
|
|
124
124
|
lines.append(f" failed_tasks = []")
|
|
125
125
|
lines.append("")
|
|
126
126
|
|
|
127
|
+
has_persistent = any(
|
|
128
|
+
getattr(v, 'is_persistent', 'NO').upper() == 'YES'
|
|
129
|
+
for v in (wf.variables or [])
|
|
130
|
+
)
|
|
131
|
+
if has_persistent:
|
|
132
|
+
lines.append(" # Load persistent variable state from previous runs")
|
|
133
|
+
lines.append(" load_persistent_state()")
|
|
134
|
+
lines.append("")
|
|
135
|
+
|
|
127
136
|
if wf.variables:
|
|
128
137
|
lines.append(" # Workflow Variables")
|
|
129
138
|
for var in wf.variables:
|
|
130
139
|
var_name = _safe_name(var.name.replace("$$", ""))
|
|
131
140
|
default = var.default_value or "''"
|
|
132
|
-
|
|
141
|
+
if getattr(var, 'is_persistent', 'NO').upper() == 'YES':
|
|
142
|
+
lines.append(f" {var_name} = get_persistent_variable('{wf.name}', '{var_name}', {default})")
|
|
143
|
+
else:
|
|
144
|
+
lines.append(f" {var_name} = {default}")
|
|
133
145
|
lines.append("")
|
|
134
146
|
|
|
135
147
|
execution_order = _get_task_execution_order(wf)
|
|
@@ -146,6 +158,15 @@ def _generate_workflow_function(lines, wf: WorkflowDef, folder: FolderDef, workl
|
|
|
146
158
|
for task in execution_order:
|
|
147
159
|
_emit_task_code(lines, task, mapping_name_map, session_to_mapping, wf, worklets or [])
|
|
148
160
|
|
|
161
|
+
if has_persistent:
|
|
162
|
+
lines.append(" # Save persistent variable state for next run")
|
|
163
|
+
for var in wf.variables:
|
|
164
|
+
if getattr(var, 'is_persistent', 'NO').upper() == 'YES':
|
|
165
|
+
var_name = _safe_name(var.name.replace("$$", ""))
|
|
166
|
+
lines.append(f" set_persistent_variable('{wf.name}', '{var_name}', {var_name})")
|
|
167
|
+
lines.append(" save_persistent_state()")
|
|
168
|
+
lines.append("")
|
|
169
|
+
|
|
149
170
|
lines.append(f" elapsed = (datetime.now() - wf_start).total_seconds()")
|
|
150
171
|
lines.append(f" if success:")
|
|
151
172
|
lines.append(f" logger.info(f'=== Workflow {wf.name} completed successfully in {{elapsed:.2f}}s ===')")
|
|
@@ -827,3 +827,314 @@ class TestDataQualityValidation:
|
|
|
827
827
|
pass
|
|
828
828
|
help_text = f.getvalue()
|
|
829
829
|
assert "--validate-casts" in help_text or "validate_casts" in help_text
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
class TestWindowAnalyticFunctions:
|
|
833
|
+
def test_moving_avg_df_generated(self):
|
|
834
|
+
from informatica_python.models import FolderDef
|
|
835
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
836
|
+
folder = FolderDef(name="TestFolder")
|
|
837
|
+
code = generate_helper_functions(folder)
|
|
838
|
+
assert "def moving_avg_df(df, col, window=3):" in code
|
|
839
|
+
assert ".rolling(window=window, min_periods=1).mean()" in code
|
|
840
|
+
|
|
841
|
+
def test_moving_sum_df_generated(self):
|
|
842
|
+
from informatica_python.models import FolderDef
|
|
843
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
844
|
+
folder = FolderDef(name="TestFolder")
|
|
845
|
+
code = generate_helper_functions(folder)
|
|
846
|
+
assert "def moving_sum_df(df, col, window=3):" in code
|
|
847
|
+
assert ".rolling(window=window, min_periods=1).sum()" in code
|
|
848
|
+
|
|
849
|
+
def test_cume_df_generated(self):
|
|
850
|
+
from informatica_python.models import FolderDef
|
|
851
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
852
|
+
folder = FolderDef(name="TestFolder")
|
|
853
|
+
code = generate_helper_functions(folder)
|
|
854
|
+
assert "def cume_df(df, col):" in code
|
|
855
|
+
assert ".expanding(min_periods=1).sum()" in code
|
|
856
|
+
|
|
857
|
+
def test_percentile_df_generated(self):
|
|
858
|
+
from informatica_python.models import FolderDef
|
|
859
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
860
|
+
folder = FolderDef(name="TestFolder")
|
|
861
|
+
code = generate_helper_functions(folder)
|
|
862
|
+
assert "def percentile_df(df, col, pct=0.5):" in code
|
|
863
|
+
assert ".quantile(pct)" in code
|
|
864
|
+
|
|
865
|
+
def test_row_level_fallbacks_still_exist(self):
|
|
866
|
+
from informatica_python.models import FolderDef
|
|
867
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
868
|
+
folder = FolderDef(name="TestFolder")
|
|
869
|
+
code = generate_helper_functions(folder)
|
|
870
|
+
assert "def moving_avg(value, window=3):" in code
|
|
871
|
+
assert "def moving_sum(value, window=3):" in code
|
|
872
|
+
assert "def cume(value):" in code
|
|
873
|
+
assert "def percentile_val(value, pct):" in code
|
|
874
|
+
|
|
875
|
+
def test_window_functions_execute(self):
|
|
876
|
+
import pandas as pd
|
|
877
|
+
df = pd.DataFrame({"val": [10, 20, 30, 40, 50]})
|
|
878
|
+
rolling_mean = df["val"].rolling(window=3, min_periods=1).mean()
|
|
879
|
+
assert rolling_mean.iloc[0] == 10.0
|
|
880
|
+
assert rolling_mean.iloc[2] == 20.0
|
|
881
|
+
rolling_sum = df["val"].rolling(window=3, min_periods=1).sum()
|
|
882
|
+
assert rolling_sum.iloc[2] == 60.0
|
|
883
|
+
cume_sum = df["val"].expanding(min_periods=1).sum()
|
|
884
|
+
assert cume_sum.iloc[4] == 150.0
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
class TestUpdateStrategy:
|
|
888
|
+
def test_static_insert_strategy(self):
|
|
889
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
890
|
+
from informatica_python.generators.mapping_gen import _gen_update_strategy
|
|
891
|
+
tx = TransformationDef(name="UPD_INSERT", type="Update Strategy",
|
|
892
|
+
attributes=[TableAttribute(name="Update Strategy Expression", value="0")])
|
|
893
|
+
lines = []
|
|
894
|
+
source_dfs = {}
|
|
895
|
+
_gen_update_strategy(lines, tx, "upd_insert", "df_input", source_dfs)
|
|
896
|
+
code = "\n".join(lines)
|
|
897
|
+
assert "'_update_strategy'] = 'INSERT'" in code
|
|
898
|
+
assert "upd_insert" in source_dfs.get("UPD_INSERT", "")
|
|
899
|
+
|
|
900
|
+
def test_static_update_strategy(self):
|
|
901
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
902
|
+
from informatica_python.generators.mapping_gen import _gen_update_strategy
|
|
903
|
+
tx = TransformationDef(name="UPD_UPDATE", type="Update Strategy",
|
|
904
|
+
attributes=[TableAttribute(name="Update Strategy Expression", value="1")])
|
|
905
|
+
lines = []
|
|
906
|
+
source_dfs = {}
|
|
907
|
+
_gen_update_strategy(lines, tx, "upd_update", "df_input", source_dfs)
|
|
908
|
+
code = "\n".join(lines)
|
|
909
|
+
assert "'_update_strategy'] = 'UPDATE'" in code
|
|
910
|
+
|
|
911
|
+
def test_static_delete_strategy(self):
|
|
912
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
913
|
+
from informatica_python.generators.mapping_gen import _gen_update_strategy
|
|
914
|
+
tx = TransformationDef(name="UPD_DEL", type="Update Strategy",
|
|
915
|
+
attributes=[TableAttribute(name="Update Strategy Expression", value="2")])
|
|
916
|
+
lines = []
|
|
917
|
+
source_dfs = {}
|
|
918
|
+
_gen_update_strategy(lines, tx, "upd_del", "df_input", source_dfs)
|
|
919
|
+
code = "\n".join(lines)
|
|
920
|
+
assert "'_update_strategy'] = 'DELETE'" in code
|
|
921
|
+
|
|
922
|
+
def test_dd_constant_expression(self):
|
|
923
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
924
|
+
from informatica_python.generators.mapping_gen import _gen_update_strategy
|
|
925
|
+
tx = TransformationDef(name="UPD_EXPR", type="Update Strategy",
|
|
926
|
+
attributes=[TableAttribute(name="Update Strategy Expression", value="DD_UPDATE")])
|
|
927
|
+
lines = []
|
|
928
|
+
source_dfs = {}
|
|
929
|
+
_gen_update_strategy(lines, tx, "upd_expr", "df_input", source_dfs)
|
|
930
|
+
code = "\n".join(lines)
|
|
931
|
+
assert "_update_strategy" in code
|
|
932
|
+
assert "UPD_EXPR" in source_dfs
|
|
933
|
+
|
|
934
|
+
def test_target_write_routes_strategy(self):
|
|
935
|
+
from informatica_python.models import TargetDef, FieldDef
|
|
936
|
+
from informatica_python.generators.mapping_gen import _generate_target_write
|
|
937
|
+
tgt = TargetDef(name="TGT_DB", database_type="Oracle",
|
|
938
|
+
fields=[FieldDef(name="ID", datatype="integer", keytype="PRIMARY KEY"),
|
|
939
|
+
FieldDef(name="VAL", datatype="string")])
|
|
940
|
+
lines = []
|
|
941
|
+
source_dfs = {"SRC": "df_src"}
|
|
942
|
+
connector_graph = {"to": {"TGT_DB": []}, "from": {}}
|
|
943
|
+
_generate_target_write(lines, "TGT_DB", tgt, connector_graph, source_dfs, {}, {})
|
|
944
|
+
code = "\n".join(lines)
|
|
945
|
+
assert "write_with_update_strategy" in code
|
|
946
|
+
assert "write_to_db" in code
|
|
947
|
+
assert "_update_strategy" in code
|
|
948
|
+
|
|
949
|
+
def test_update_strategy_preserved_through_projection(self):
|
|
950
|
+
from informatica_python.models import TargetDef, FieldDef
|
|
951
|
+
from informatica_python.generators.mapping_gen import _generate_target_write
|
|
952
|
+
tgt = TargetDef(name="TGT_DB", database_type="Oracle",
|
|
953
|
+
fields=[FieldDef(name="ID", datatype="integer"),
|
|
954
|
+
FieldDef(name="VAL", datatype="string")])
|
|
955
|
+
lines = []
|
|
956
|
+
source_dfs = {"SRC": "df_src"}
|
|
957
|
+
from informatica_python.models import ConnectorDef
|
|
958
|
+
conns = [ConnectorDef(from_instance="SRC", to_instance="TGT_DB",
|
|
959
|
+
from_instance_type="", to_instance_type="",
|
|
960
|
+
from_field="ID", to_field="ID")]
|
|
961
|
+
connector_graph = {"to": {"TGT_DB": conns}, "from": {"SRC": conns}}
|
|
962
|
+
_generate_target_write(lines, "TGT_DB", tgt, connector_graph, source_dfs, {}, {})
|
|
963
|
+
code = "\n".join(lines)
|
|
964
|
+
assert "_update_strategy" in code
|
|
965
|
+
assert "available_cols.append('_update_strategy')" in code
|
|
966
|
+
|
|
967
|
+
def test_update_strategy_dialect_aware_placeholders(self):
|
|
968
|
+
from informatica_python.models import FolderDef
|
|
969
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
970
|
+
folder = FolderDef(name="TestFolder")
|
|
971
|
+
code = generate_helper_functions(folder)
|
|
972
|
+
assert "ph = '?' if db_type == 'mssql' else '%s'" in code
|
|
973
|
+
|
|
974
|
+
def test_update_strategy_helper_generated(self):
|
|
975
|
+
from informatica_python.models import FolderDef
|
|
976
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
977
|
+
folder = FolderDef(name="TestFolder")
|
|
978
|
+
code = generate_helper_functions(folder)
|
|
979
|
+
assert "def write_with_update_strategy(" in code
|
|
980
|
+
assert "df_insert" in code
|
|
981
|
+
assert "df_update" in code
|
|
982
|
+
assert "df_delete" in code
|
|
983
|
+
assert "df_reject" in code
|
|
984
|
+
assert "INSERT" in code
|
|
985
|
+
assert "UPDATE" in code
|
|
986
|
+
assert "DELETE" in code
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
class TestStoredProcedure:
|
|
990
|
+
def test_stored_proc_basic(self):
|
|
991
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
992
|
+
from informatica_python.generators.mapping_gen import _gen_stored_proc
|
|
993
|
+
tx = TransformationDef(name="SP_GET_DATA", type="Stored Procedure",
|
|
994
|
+
attributes=[TableAttribute(name="Stored Procedure Name", value="usp_get_data")])
|
|
995
|
+
lines = []
|
|
996
|
+
source_dfs = {}
|
|
997
|
+
_gen_stored_proc(lines, tx, "sp_get_data", "df_input", source_dfs)
|
|
998
|
+
code = "\n".join(lines)
|
|
999
|
+
assert "call_stored_procedure" in code
|
|
1000
|
+
assert "usp_get_data" in code
|
|
1001
|
+
assert "SP_GET_DATA" in source_dfs
|
|
1002
|
+
|
|
1003
|
+
def test_stored_proc_with_input_params(self):
|
|
1004
|
+
from informatica_python.models import TransformationDef, TableAttribute, FieldDef
|
|
1005
|
+
from informatica_python.generators.mapping_gen import _gen_stored_proc
|
|
1006
|
+
tx = TransformationDef(name="SP_LOOKUP", type="Stored Procedure",
|
|
1007
|
+
attributes=[TableAttribute(name="Stored Procedure Name", value="usp_lookup")],
|
|
1008
|
+
fields=[FieldDef(name="CUSTOMER_ID", datatype="integer", porttype="INPUT"),
|
|
1009
|
+
FieldDef(name="RESULT", datatype="string", porttype="OUTPUT")])
|
|
1010
|
+
lines = []
|
|
1011
|
+
source_dfs = {}
|
|
1012
|
+
_gen_stored_proc(lines, tx, "sp_lookup", "df_input", source_dfs)
|
|
1013
|
+
code = "\n".join(lines)
|
|
1014
|
+
assert "CUSTOMER_ID" in code
|
|
1015
|
+
assert "RESULT" in code
|
|
1016
|
+
assert "_sp_out_names_" in code
|
|
1017
|
+
assert "call_stored_procedure" in code
|
|
1018
|
+
|
|
1019
|
+
def test_stored_proc_with_output_params(self):
|
|
1020
|
+
from informatica_python.models import TransformationDef, TableAttribute, FieldDef
|
|
1021
|
+
from informatica_python.generators.mapping_gen import _gen_stored_proc
|
|
1022
|
+
tx = TransformationDef(name="SP_OUT", type="Stored Procedure",
|
|
1023
|
+
attributes=[TableAttribute(name="Stored Procedure Name", value="usp_out")],
|
|
1024
|
+
fields=[FieldDef(name="RET_VAL", datatype="string", porttype="OUTPUT")])
|
|
1025
|
+
lines = []
|
|
1026
|
+
source_dfs = {}
|
|
1027
|
+
_gen_stored_proc(lines, tx, "sp_out", "df_input", source_dfs)
|
|
1028
|
+
code = "\n".join(lines)
|
|
1029
|
+
assert "['RET_VAL']" in code
|
|
1030
|
+
assert "_sp_out_vals_" in code
|
|
1031
|
+
assert "if df_sp_out.empty" in code
|
|
1032
|
+
|
|
1033
|
+
def test_stored_proc_helper_generated(self):
|
|
1034
|
+
from informatica_python.models import FolderDef
|
|
1035
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
1036
|
+
folder = FolderDef(name="TestFolder")
|
|
1037
|
+
code = generate_helper_functions(folder)
|
|
1038
|
+
assert "def call_stored_procedure(" in code
|
|
1039
|
+
assert "cursor.callproc" in code
|
|
1040
|
+
assert "EXEC" in code
|
|
1041
|
+
assert "CALL" in code
|
|
1042
|
+
|
|
1043
|
+
def test_stored_proc_connection_override(self):
|
|
1044
|
+
from informatica_python.models import TransformationDef, TableAttribute
|
|
1045
|
+
from informatica_python.generators.mapping_gen import _gen_stored_proc
|
|
1046
|
+
tx = TransformationDef(name="SP_CONN", type="Stored Procedure",
|
|
1047
|
+
attributes=[
|
|
1048
|
+
TableAttribute(name="Stored Procedure Name", value="usp_custom"),
|
|
1049
|
+
TableAttribute(name="Connection Name", value="oracle_prod"),
|
|
1050
|
+
])
|
|
1051
|
+
lines = []
|
|
1052
|
+
source_dfs = {}
|
|
1053
|
+
_gen_stored_proc(lines, tx, "sp_conn", "df_input", source_dfs)
|
|
1054
|
+
code = "\n".join(lines)
|
|
1055
|
+
assert "oracle_prod" in code
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
class TestStatePersistence:
|
|
1059
|
+
def test_persistent_state_helpers_generated(self):
|
|
1060
|
+
from informatica_python.models import FolderDef
|
|
1061
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
1062
|
+
folder = FolderDef(name="TestFolder")
|
|
1063
|
+
code = generate_helper_functions(folder)
|
|
1064
|
+
assert "def load_persistent_state(" in code
|
|
1065
|
+
assert "def save_persistent_state(" in code
|
|
1066
|
+
assert "def get_persistent_variable(" in code
|
|
1067
|
+
assert "def set_persistent_variable(" in code
|
|
1068
|
+
assert "_persistent_state" in code
|
|
1069
|
+
assert "persistent_state.json" in code
|
|
1070
|
+
|
|
1071
|
+
def test_workflow_loads_persistent_state(self):
|
|
1072
|
+
from informatica_python.models import FolderDef, WorkflowDef, WorkflowVariable
|
|
1073
|
+
from informatica_python.generators.workflow_gen import generate_workflow_code
|
|
1074
|
+
wf = WorkflowDef(name="wf_test",
|
|
1075
|
+
variables=[WorkflowVariable(name="$$RUN_COUNT", datatype="integer",
|
|
1076
|
+
default_value="0", is_persistent="YES")])
|
|
1077
|
+
folder = FolderDef(name="TestFolder", workflows=[wf])
|
|
1078
|
+
code = generate_workflow_code(folder)
|
|
1079
|
+
assert "load_persistent_state()" in code
|
|
1080
|
+
assert "get_persistent_variable" in code
|
|
1081
|
+
assert "save_persistent_state()" in code
|
|
1082
|
+
assert "set_persistent_variable" in code
|
|
1083
|
+
|
|
1084
|
+
def test_workflow_no_persist_when_not_needed(self):
|
|
1085
|
+
from informatica_python.models import FolderDef, WorkflowDef, WorkflowVariable
|
|
1086
|
+
from informatica_python.generators.workflow_gen import generate_workflow_code
|
|
1087
|
+
wf = WorkflowDef(name="wf_test",
|
|
1088
|
+
variables=[WorkflowVariable(name="$$TEMP_VAR", datatype="string",
|
|
1089
|
+
default_value="''", is_persistent="NO")])
|
|
1090
|
+
folder = FolderDef(name="TestFolder", workflows=[wf])
|
|
1091
|
+
code = generate_workflow_code(folder)
|
|
1092
|
+
assert "load_persistent_state()" not in code
|
|
1093
|
+
assert "save_persistent_state()" not in code
|
|
1094
|
+
|
|
1095
|
+
def test_mapping_persistent_variables(self):
|
|
1096
|
+
from informatica_python.models import (
|
|
1097
|
+
FolderDef, MappingDef, MappingVariable, SourceDef, FieldDef,
|
|
1098
|
+
TransformationDef, ConnectorDef, InstanceDef
|
|
1099
|
+
)
|
|
1100
|
+
from informatica_python.generators.mapping_gen import generate_mapping_code
|
|
1101
|
+
mapping = MappingDef(
|
|
1102
|
+
name="m_persist_test",
|
|
1103
|
+
variables=[
|
|
1104
|
+
MappingVariable(name="$$LAST_ID", datatype="integer",
|
|
1105
|
+
default_value="0", is_persistent="YES"),
|
|
1106
|
+
MappingVariable(name="$$TEMP", datatype="string",
|
|
1107
|
+
default_value="''", is_persistent="NO"),
|
|
1108
|
+
],
|
|
1109
|
+
sources=[],
|
|
1110
|
+
targets=[],
|
|
1111
|
+
transformations=[],
|
|
1112
|
+
connectors=[],
|
|
1113
|
+
instances=[],
|
|
1114
|
+
)
|
|
1115
|
+
folder = FolderDef(name="TestFolder", mappings=[mapping])
|
|
1116
|
+
code = generate_mapping_code(mapping, folder, "pandas", 1)
|
|
1117
|
+
assert "get_persistent_variable('m_persist_test', 'last_id'" in code
|
|
1118
|
+
assert "temp = ''" in code
|
|
1119
|
+
assert "load_persistent_state()" in code
|
|
1120
|
+
assert "set_persistent_variable('m_persist_test', 'last_id'" in code
|
|
1121
|
+
assert "save_persistent_state()" in code
|
|
1122
|
+
|
|
1123
|
+
def test_workflow_persistent_imports(self):
|
|
1124
|
+
from informatica_python.models import FolderDef, WorkflowDef, WorkflowVariable
|
|
1125
|
+
from informatica_python.generators.workflow_gen import generate_workflow_code
|
|
1126
|
+
wf = WorkflowDef(name="wf_test",
|
|
1127
|
+
variables=[WorkflowVariable(name="$$COUNT", is_persistent="YES")])
|
|
1128
|
+
folder = FolderDef(name="TestFolder", workflows=[wf])
|
|
1129
|
+
code = generate_workflow_code(folder)
|
|
1130
|
+
assert "load_persistent_state" in code
|
|
1131
|
+
assert "save_persistent_state" in code
|
|
1132
|
+
|
|
1133
|
+
def test_state_file_json_format(self):
|
|
1134
|
+
from informatica_python.models import FolderDef
|
|
1135
|
+
from informatica_python.generators.helper_gen import generate_helper_functions
|
|
1136
|
+
folder = FolderDef(name="TestFolder")
|
|
1137
|
+
code = generate_helper_functions(folder)
|
|
1138
|
+
assert "json.load" in code
|
|
1139
|
+
assert "json.dump" in code
|
|
1140
|
+
assert "persistent_state.json" in code
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/__init__.py
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/config_gen.py
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/error_log_gen.py
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/generators/sql_gen.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/datatype_map.py
RENAMED
|
File without changes
|
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/lib_adapters.py
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python/utils/sql_dialect.py
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{informatica_python-1.6.1 → informatica_python-1.7.1}/informatica_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|