informatica-python 1.7.0__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.
Files changed (30) hide show
  1. {informatica_python-1.7.0 → informatica_python-1.7.1}/PKG-INFO +1 -1
  2. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/__init__.py +1 -1
  3. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/helper_gen.py +6 -3
  4. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/mapping_gen.py +20 -2
  5. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/PKG-INFO +1 -1
  6. {informatica_python-1.7.0 → informatica_python-1.7.1}/pyproject.toml +1 -1
  7. {informatica_python-1.7.0 → informatica_python-1.7.1}/tests/test_integration.py +30 -2
  8. {informatica_python-1.7.0 → informatica_python-1.7.1}/LICENSE +0 -0
  9. {informatica_python-1.7.0 → informatica_python-1.7.1}/README.md +0 -0
  10. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/cli.py +0 -0
  11. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/converter.py +0 -0
  12. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/__init__.py +0 -0
  13. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/config_gen.py +0 -0
  14. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/error_log_gen.py +0 -0
  15. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/sql_gen.py +0 -0
  16. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/generators/workflow_gen.py +0 -0
  17. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/models.py +0 -0
  18. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/parser.py +0 -0
  19. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/utils/__init__.py +0 -0
  20. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/utils/datatype_map.py +0 -0
  21. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/utils/expression_converter.py +0 -0
  22. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/utils/lib_adapters.py +0 -0
  23. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python/utils/sql_dialect.py +0 -0
  24. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/SOURCES.txt +0 -0
  25. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/dependency_links.txt +0 -0
  26. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/entry_points.txt +0 -0
  27. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/requires.txt +0 -0
  28. {informatica_python-1.7.0 → informatica_python-1.7.1}/informatica_python.egg-info/top_level.txt +0 -0
  29. {informatica_python-1.7.0 → informatica_python-1.7.1}/setup.cfg +0 -0
  30. {informatica_python-1.7.0 → informatica_python-1.7.1}/tests/test_converter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: informatica-python
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: Convert Informatica PowerCenter workflow XML to Python/PySpark code
5
5
  Author: Nick
6
6
  License: MIT
@@ -7,7 +7,7 @@ Licensed under the MIT License.
7
7
 
8
8
  from informatica_python.converter import InformaticaConverter
9
9
 
10
- __version__ = "1.7.0"
10
+ __version__ = "1.7.1"
11
11
  __author__ = "Nick"
12
12
  __license__ = "MIT"
13
13
  __all__ = ["InformaticaConverter"]
@@ -1278,6 +1278,9 @@ def _add_update_strategy_functions(lines):
1278
1278
  lines.append(" key_columns = data_cols[:1]")
1279
1279
  lines.append(" logger.warning(f'No key columns specified for update strategy — using first column: {key_columns}')")
1280
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("")
1281
1284
  lines.append(" try:")
1282
1285
  lines.append(" cursor = conn.cursor()")
1283
1286
  lines.append("")
@@ -1295,8 +1298,8 @@ def _add_update_strategy_functions(lines):
1295
1298
  lines.append("")
1296
1299
  lines.append(" if len(df_update) > 0:")
1297
1300
  lines.append(" non_key_cols = [c for c in data_cols if c not in key_columns]")
1298
- lines.append(" set_clause = ', '.join(f'{c} = ?' for c in non_key_cols)")
1299
- lines.append(" where_clause = ' AND '.join(f'{c} = ?' for c 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)")
1300
1303
  lines.append(" update_sql = f'UPDATE {qualified_table} SET {set_clause} WHERE {where_clause}'")
1301
1304
  lines.append(" logger.info(f'Updating {len(df_update)} rows in {qualified_table}')")
1302
1305
  lines.append(" for _, row in df_update.iterrows():")
@@ -1304,7 +1307,7 @@ def _add_update_strategy_functions(lines):
1304
1307
  lines.append(" cursor.execute(update_sql, values)")
1305
1308
  lines.append("")
1306
1309
  lines.append(" if len(df_delete) > 0:")
1307
- lines.append(" where_clause = ' AND '.join(f'{c} = ?' for c in key_columns)")
1310
+ lines.append(" where_clause = ' AND '.join(f'{c} = {ph}' for c in key_columns)")
1308
1311
  lines.append(" delete_sql = f'DELETE FROM {qualified_table} WHERE {where_clause}'")
1309
1312
  lines.append(" logger.info(f'Deleting {len(df_delete)} rows from {qualified_table}')")
1310
1313
  lines.append(" for _, row in df_delete.iterrows():")
@@ -253,7 +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 = False
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
+
257
265
  if mapping.variables:
258
266
  lines.append(" # Mapping Variables")
259
267
  for var in mapping.variables:
@@ -318,6 +326,14 @@ def generate_mapping_code(mapping: MappingDef, folder: FolderDef,
318
326
  for tgt_name, tgt_def in target_map.items():
319
327
  _generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs, transform_map, instance_map, session_overrides, validate_casts=validate_casts)
320
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
+
321
337
  lines.append("")
322
338
  lines.append(f" log_mapping_end('{mapping.name}', start_time)")
323
339
  lines.append(f" logger.info('Mapping {mapping.name} completed successfully')")
@@ -1230,7 +1246,7 @@ def _gen_stored_proc(lines, tx, tx_safe, input_df, source_dfs):
1230
1246
  lines.append(f" # Stored Procedure: {proc_name or tx.name}")
1231
1247
 
1232
1248
  if input_params:
1233
- param_dict_items = ", ".join(f"'{p}': {input_df}['{p}'].iloc[0] if '{p}' in {input_df}.columns else None" for p in 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)
1234
1250
  lines.append(f" _sp_params_{tx_safe} = {{{param_dict_items}}}")
1235
1251
  else:
1236
1252
  lines.append(f" _sp_params_{tx_safe} = {{}}")
@@ -1304,6 +1320,8 @@ def _generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs
1304
1320
  target_cols = [f.name for f in tgt_def.fields] if tgt_def.fields else None
1305
1321
  if target_cols:
1306
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')")
1307
1325
  lines.append(f" df_target_{tgt_safe} = df_target_{tgt_safe}[available_cols]")
1308
1326
  else:
1309
1327
  lines.append(f" df_target_{tgt_safe} = {input_df}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: informatica-python
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: Convert Informatica PowerCenter workflow XML to Python/PySpark code
5
5
  Author: Nick
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "informatica-python"
7
- version = "1.7.0"
7
+ version = "1.7.1"
8
8
  description = "Convert Informatica PowerCenter workflow XML to Python/PySpark code"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -946,6 +946,31 @@ class TestUpdateStrategy:
946
946
  assert "write_to_db" in code
947
947
  assert "_update_strategy" in code
948
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
+
949
974
  def test_update_strategy_helper_generated(self):
950
975
  from informatica_python.models import FolderDef
951
976
  from informatica_python.generators.helper_gen import generate_helper_functions
@@ -1064,8 +1089,8 @@ class TestStatePersistence:
1064
1089
  default_value="''", is_persistent="NO")])
1065
1090
  folder = FolderDef(name="TestFolder", workflows=[wf])
1066
1091
  code = generate_workflow_code(folder)
1067
- assert "load_persistent_state" not in code or "import" in code
1068
- assert "save_persistent_state" not in code or "import" in code
1092
+ assert "load_persistent_state()" not in code
1093
+ assert "save_persistent_state()" not in code
1069
1094
 
1070
1095
  def test_mapping_persistent_variables(self):
1071
1096
  from informatica_python.models import (
@@ -1091,6 +1116,9 @@ class TestStatePersistence:
1091
1116
  code = generate_mapping_code(mapping, folder, "pandas", 1)
1092
1117
  assert "get_persistent_variable('m_persist_test', 'last_id'" in code
1093
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
1094
1122
 
1095
1123
  def test_workflow_persistent_imports(self):
1096
1124
  from informatica_python.models import FolderDef, WorkflowDef, WorkflowVariable