informatica-python 1.9.0__tar.gz → 1.9.2__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 (31) hide show
  1. {informatica_python-1.9.0 → informatica_python-1.9.2}/PKG-INFO +7 -1
  2. {informatica_python-1.9.0 → informatica_python-1.9.2}/README.md +6 -0
  3. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/__init__.py +1 -1
  4. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/helper_gen.py +11 -0
  5. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/mapping_gen.py +36 -24
  6. informatica_python-1.9.2/informatica_python/utils/expression_converter.py +946 -0
  7. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/PKG-INFO +7 -1
  8. {informatica_python-1.9.0 → informatica_python-1.9.2}/pyproject.toml +1 -1
  9. {informatica_python-1.9.0 → informatica_python-1.9.2}/tests/test_integration.py +75 -0
  10. informatica_python-1.9.0/informatica_python/utils/expression_converter.py +0 -481
  11. {informatica_python-1.9.0 → informatica_python-1.9.2}/LICENSE +0 -0
  12. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/cli.py +0 -0
  13. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/converter.py +0 -0
  14. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/__init__.py +0 -0
  15. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/config_gen.py +0 -0
  16. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/error_log_gen.py +0 -0
  17. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/sql_gen.py +0 -0
  18. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/generators/workflow_gen.py +0 -0
  19. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/models.py +0 -0
  20. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/parser.py +0 -0
  21. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/utils/__init__.py +0 -0
  22. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/utils/datatype_map.py +0 -0
  23. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/utils/lib_adapters.py +0 -0
  24. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python/utils/sql_dialect.py +0 -0
  25. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/SOURCES.txt +0 -0
  26. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/dependency_links.txt +0 -0
  27. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/entry_points.txt +0 -0
  28. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/requires.txt +0 -0
  29. {informatica_python-1.9.0 → informatica_python-1.9.2}/informatica_python.egg-info/top_level.txt +0 -0
  30. {informatica_python-1.9.0 → informatica_python-1.9.2}/setup.cfg +0 -0
  31. {informatica_python-1.9.0 → informatica_python-1.9.2}/tests/test_converter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: informatica-python
3
- Version: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Convert Informatica PowerCenter workflow XML to Python/PySpark code
5
5
  Author: Nick
6
6
  License: MIT
@@ -307,6 +307,12 @@ Converts Informatica expressions to Python equivalents:
307
307
  ### v1.9.x (Phase 8)
308
308
  - Mapping output files now use real mapping names (e.g., `mapping_m_customer_load.py`) instead of generic numeric indices (`mapping_1.py`)
309
309
  - Workflow imports automatically match the named mapping files
310
+ - **Expression converter rewrite**: Recursive parenthesis-aware parser replacing simple regex; fixes nested IIF/INSTR/LTRIM/RTRIM/REPLACECHR/REPLACESTR/SUBSTR/TO_CHAR/CHR/MAKE_DATE_TIME
311
+ - **`:LKP.` references** now properly converted to `lookup_func()` calls in vectorized mode
312
+ - **String literal safety**: `||` concatenation no longer applies `.astype(str)` to string literals
313
+ - **NULL/TRUE/FALSE**: Correctly resolved as `None`/`True`/`False` before field-name substitution
314
+ - **`import pandas as pd`** and `from datetime import datetime` now included in generated mapping files
315
+ - **MSSQL connection fallbacks**: `pymssql` and `sqlalchemy` tried when `pyodbc` unavailable
310
316
 
311
317
  ### v1.8.x (Phase 7)
312
318
  - Row-count logging at every pipeline step (source reads, transforms, target writes)
@@ -280,6 +280,12 @@ Converts Informatica expressions to Python equivalents:
280
280
  ### v1.9.x (Phase 8)
281
281
  - Mapping output files now use real mapping names (e.g., `mapping_m_customer_load.py`) instead of generic numeric indices (`mapping_1.py`)
282
282
  - Workflow imports automatically match the named mapping files
283
+ - **Expression converter rewrite**: Recursive parenthesis-aware parser replacing simple regex; fixes nested IIF/INSTR/LTRIM/RTRIM/REPLACECHR/REPLACESTR/SUBSTR/TO_CHAR/CHR/MAKE_DATE_TIME
284
+ - **`:LKP.` references** now properly converted to `lookup_func()` calls in vectorized mode
285
+ - **String literal safety**: `||` concatenation no longer applies `.astype(str)` to string literals
286
+ - **NULL/TRUE/FALSE**: Correctly resolved as `None`/`True`/`False` before field-name substitution
287
+ - **`import pandas as pd`** and `from datetime import datetime` now included in generated mapping files
288
+ - **MSSQL connection fallbacks**: `pymssql` and `sqlalchemy` tried when `pyodbc` unavailable
283
289
 
284
290
  ### v1.8.x (Phase 7)
285
291
  - Row-count logging at every pipeline step (source reads, transforms, target writes)
@@ -7,7 +7,7 @@ Licensed under the MIT License.
7
7
 
8
8
  from informatica_python.converter import InformaticaConverter
9
9
 
10
- __version__ = "1.9.0"
10
+ __version__ = "1.9.2"
11
11
  __author__ = "Nick"
12
12
  __license__ = "MIT"
13
13
  __all__ = ["InformaticaConverter"]
@@ -151,6 +151,17 @@ def _add_db_functions(lines, data_lib):
151
151
  lines.append(" return pyodbc.connect(conn_str)")
152
152
  lines.append(" except ImportError:")
153
153
  lines.append(" pass")
154
+ lines.append(" try:")
155
+ lines.append(" import pymssql")
156
+ lines.append(" return pymssql.connect(server=host, port=int(port), database=database, user=username, password=password)")
157
+ lines.append(" except ImportError:")
158
+ lines.append(" pass")
159
+ lines.append(" try:")
160
+ lines.append(" from sqlalchemy import create_engine")
161
+ lines.append(" engine = create_engine(f'mssql+pymssql://{username}:{password}@{host}:{port}/{database}')")
162
+ lines.append(" return engine.connect()")
163
+ lines.append(" except ImportError:")
164
+ lines.append(" pass")
154
165
  lines.append("")
155
166
  lines.append(" if db_type == 'postgresql':")
156
167
  lines.append(" try:")
@@ -227,6 +227,8 @@ def generate_mapping_code(mapping: MappingDef, folder: FolderDef,
227
227
  lines.append("")
228
228
  lines.append("import logging")
229
229
  lines.append("import numpy as np")
230
+ lines.append("import pandas as pd")
231
+ lines.append("from datetime import datetime")
230
232
  lines.append("from helper_functions import *")
231
233
  lines.append("")
232
234
  lines.append("logger = logging.getLogger(__name__)")
@@ -601,8 +603,19 @@ def _generate_source_qualifier(lines, sq, source_map, source_dfs, connector_grap
601
603
  lines.append(f" execute_sql(config, '''{pre_sql}''')")
602
604
  lines.append("")
603
605
 
604
- if sql_override:
605
- src_name = next(iter(connected_sources)) if connected_sources else "source"
606
+ if not connected_sources:
607
+ sq_src_name = sq.name[3:] if sq.name.upper().startswith("SQ_") else sq.name
608
+ if sql_override:
609
+ lines.append(f" sql_{sq_safe} = '''")
610
+ for sql_line in sql_override.strip().split("\n"):
611
+ lines.append(f" {sql_line}")
612
+ lines.append(f" '''")
613
+ lines.append(f" df_{sq_safe} = read_from_db(config, sql_{sq_safe}, 'default')")
614
+ else:
615
+ lines.append(f" df_{sq_safe} = read_file(config.get('sources', {{}}).get('{sq_src_name}', {{}}).get('file_path', '{sq_src_name}'),")
616
+ lines.append(f" config.get('sources', {{}}).get('{sq_src_name}', {{}}))")
617
+ elif sql_override:
618
+ src_name = next(iter(connected_sources))
606
619
  src_def = source_map.get(src_name, SourceDef(name=src_name))
607
620
  sq_override = (session_overrides or {}).get(sq.name, {}) or (session_overrides or {}).get(src_name, {})
608
621
  conn_name = sq_override.get("connection_name") or (_safe_name(src_def.db_name) if src_def.db_name else "default")
@@ -612,36 +625,35 @@ def _generate_source_qualifier(lines, sq, source_map, source_dfs, connector_grap
612
625
  lines.append(f" {sql_line}")
613
626
  lines.append(f" '''")
614
627
  lines.append(f" df_{sq_safe} = read_from_db(config, sql_{sq_safe}, '{conn_name}')")
628
+ elif len(connected_sources) == 1:
629
+ src_name = next(iter(connected_sources))
630
+ src_def = source_map.get(src_name, SourceDef(name=src_name))
631
+ safe_src = _safe_name(src_name)
632
+ src_override = (session_overrides or {}).get(sq.name, {}) or (session_overrides or {}).get(src_name, {})
633
+ if src_def.database_type and src_def.database_type != "Flat File":
634
+ conn_name = src_override.get("connection_name") or (_safe_name(src_def.db_name) if src_def.db_name else "default")
635
+ schema = src_def.owner_name or "dbo"
636
+ cols = ", ".join(f.name for f in src_def.fields) if src_def.fields else "*"
637
+ lines.append(f" df_{sq_safe} = read_from_db(config, 'SELECT {cols} FROM {schema}.{src_def.name}', '{conn_name}')")
638
+ elif src_def.flatfile:
639
+ _emit_flatfile_read(lines, sq_safe, src_def)
640
+ else:
641
+ lines.append(f" df_{sq_safe} = read_file(config.get('sources', {{}}).get('{src_def.name}', {{}}).get('file_path', '{src_def.name}'),")
642
+ lines.append(f" config.get('sources', {{}}).get('{src_def.name}', {{}}))")
615
643
  else:
616
- if len(connected_sources) == 1:
617
- src_name = next(iter(connected_sources))
644
+ for src_name in connected_sources:
618
645
  src_def = source_map.get(src_name, SourceDef(name=src_name))
619
646
  safe_src = _safe_name(src_name)
620
- src_override = (session_overrides or {}).get(sq.name, {}) or (session_overrides or {}).get(src_name, {})
621
647
  if src_def.database_type and src_def.database_type != "Flat File":
622
- conn_name = src_override.get("connection_name") or (_safe_name(src_def.db_name) if src_def.db_name else "default")
648
+ conn_name = _safe_name(src_def.db_name) if src_def.db_name else "default"
623
649
  schema = src_def.owner_name or "dbo"
624
- cols = ", ".join(f.name for f in src_def.fields) if src_def.fields else "*"
625
- lines.append(f" df_{sq_safe} = read_from_db(config, 'SELECT {cols} FROM {schema}.{src_def.name}', '{conn_name}')")
650
+ lines.append(f" df_{safe_src} = read_from_db(config, 'SELECT * FROM {schema}.{src_def.name}', '{conn_name}')")
626
651
  elif src_def.flatfile:
627
- _emit_flatfile_read(lines, sq_safe, src_def)
652
+ _emit_flatfile_read(lines, safe_src, src_def)
628
653
  else:
629
- lines.append(f" df_{sq_safe} = read_file(config.get('sources', {{}}).get('{src_def.name}', {{}}).get('file_path', '{src_def.name}'),")
654
+ lines.append(f" df_{safe_src} = read_file(config.get('sources', {{}}).get('{src_def.name}', {{}}).get('file_path', '{src_def.name}'),")
630
655
  lines.append(f" config.get('sources', {{}}).get('{src_def.name}', {{}}))")
631
- else:
632
- for src_name in connected_sources:
633
- src_def = source_map.get(src_name, SourceDef(name=src_name))
634
- safe_src = _safe_name(src_name)
635
- if src_def.database_type and src_def.database_type != "Flat File":
636
- conn_name = _safe_name(src_def.db_name) if src_def.db_name else "default"
637
- schema = src_def.owner_name or "dbo"
638
- lines.append(f" df_{safe_src} = read_from_db(config, 'SELECT * FROM {schema}.{src_def.name}', '{conn_name}')")
639
- elif src_def.flatfile:
640
- _emit_flatfile_read(lines, safe_src, src_def)
641
- else:
642
- lines.append(f" df_{safe_src} = read_file(config.get('sources', {{}}).get('{src_def.name}', {{}}).get('file_path', '{src_def.name}'),")
643
- lines.append(f" config.get('sources', {{}}).get('{src_def.name}', {{}}))")
644
- lines.append(f" df_{sq_safe} = df_{_safe_name(next(iter(connected_sources)))}")
656
+ lines.append(f" df_{sq_safe} = df_{_safe_name(next(iter(connected_sources)))}")
645
657
 
646
658
  source_dfs[sq.name] = f"df_{sq_safe}"
647
659
  lines.append(f" try:")