informatica-python 1.1.0__tar.gz → 1.2.0__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 (28) hide show
  1. {informatica_python-1.1.0 → informatica_python-1.2.0}/PKG-INFO +1 -1
  2. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/converter.py +30 -5
  3. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/error_log_gen.py +45 -19
  4. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/helper_gen.py +396 -0
  5. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/mapping_gen.py +137 -22
  6. informatica_python-1.2.0/informatica_python/utils/expression_converter.py +259 -0
  7. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/PKG-INFO +1 -1
  8. {informatica_python-1.1.0 → informatica_python-1.2.0}/pyproject.toml +1 -1
  9. informatica_python-1.2.0/tests/test_converter.py +551 -0
  10. informatica_python-1.1.0/informatica_python/utils/expression_converter.py +0 -128
  11. informatica_python-1.1.0/tests/test_converter.py +0 -260
  12. {informatica_python-1.1.0 → informatica_python-1.2.0}/README.md +0 -0
  13. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/__init__.py +0 -0
  14. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/cli.py +0 -0
  15. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/__init__.py +0 -0
  16. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/config_gen.py +0 -0
  17. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/sql_gen.py +0 -0
  18. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/generators/workflow_gen.py +0 -0
  19. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/models.py +0 -0
  20. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/parser.py +0 -0
  21. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/utils/__init__.py +0 -0
  22. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python/utils/datatype_map.py +0 -0
  23. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/SOURCES.txt +0 -0
  24. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/dependency_links.txt +0 -0
  25. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/entry_points.txt +0 -0
  26. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/requires.txt +0 -0
  27. {informatica_python-1.1.0 → informatica_python-1.2.0}/informatica_python.egg-info/top_level.txt +0 -0
  28. {informatica_python-1.1.0 → informatica_python-1.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: informatica-python
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Convert Informatica PowerCenter workflow XML to Python/PySpark code
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.8
@@ -235,7 +235,9 @@ class InformaticaConverter:
235
235
  if tgt.xmlinfo:
236
236
  d["xmlinfo"] = {"xml_type": tgt.xmlinfo.xml_type, "root_element": tgt.xmlinfo.root_element}
237
237
  if tgt.groups:
238
- d["groups"] = [{"name": g.name, "type": g.type} for g in tgt.groups]
238
+ d["groups"] = [{"name": g.name, "type": g.type, "fields": [self._field_to_dict(f) for f in g.fields]} for g in tgt.groups]
239
+ if tgt.keywords:
240
+ d["keywords"] = [{"name": k.name, "value": k.value} for k in tgt.keywords]
239
241
  return d
240
242
 
241
243
  def _transformation_to_dict(self, tx):
@@ -257,7 +259,9 @@ class InformaticaConverter:
257
259
  if tx.erp_info:
258
260
  d["erp_info"] = {"name": tx.erp_info.name, "erp_type": tx.erp_info.erp_type}
259
261
  if tx.groups:
260
- d["groups"] = [{"name": g.name, "type": g.type} for g in tx.groups]
262
+ d["groups"] = [{"name": g.name, "type": g.type, "fields": [self._field_to_dict(f) for f in g.fields]} for g in tx.groups]
263
+ if tx.metadata_extensions:
264
+ d["metadata_extensions"] = [self._meta_ext_to_dict(me) for me in tx.metadata_extensions]
261
265
  if tx.sap_functions:
262
266
  d["sap_functions"] = [self._sap_function_to_dict(sf) for sf in tx.sap_functions]
263
267
  return d
@@ -322,7 +326,13 @@ class InformaticaConverter:
322
326
  "transformations": [self._transformation_to_dict(tx) for tx in mapplet.transformations],
323
327
  "connectors": [{"from_field": c.from_field, "from_instance": c.from_instance,
324
328
  "to_field": c.to_field, "to_instance": c.to_instance} for c in mapplet.connectors],
325
- "instances": [{"name": i.name, "type": i.type, "transformation_name": i.transformation_name} for i in mapplet.instances],
329
+ "instances": [{"name": i.name, "type": i.type, "transformation_name": i.transformation_name,
330
+ "associated_source_instances": [{"name": a.name, "source_instance": a.source_instance} for a in i.associated_source_instances]}
331
+ for i in mapplet.instances],
332
+ "metadata_extensions": [self._meta_ext_to_dict(me) for me in mapplet.metadata_extensions],
333
+ "map_dependencies": [{"name": md.name, "from_mapping": md.from_mapping, "to_mapping": md.to_mapping} for md in mapplet.map_dependencies],
334
+ "field_dependencies": [{"name": fd.name, "from_field": fd.from_field, "from_instance": fd.from_instance,
335
+ "to_field": fd.to_field, "to_instance": fd.to_instance, "expression": fd.expression} for fd in mapplet.field_dependencies],
326
336
  }
327
337
 
328
338
  def _session_to_dict(self, session):
@@ -350,6 +360,7 @@ class InformaticaConverter:
350
360
  ],
351
361
  "config_references": session.config_references,
352
362
  "components": session.components,
363
+ "metadata_extensions": [self._meta_ext_to_dict(me) for me in session.metadata_extensions],
353
364
  }
354
365
 
355
366
  def _task_to_dict(self, task):
@@ -360,13 +371,17 @@ class InformaticaConverter:
360
371
  }
361
372
  if task.timer:
362
373
  d["timer"] = {"name": task.timer.name, "start_type": task.timer.start_type,
363
- "start_date": task.timer.start_date, "start_time": task.timer.start_time}
374
+ "start_date": task.timer.start_date, "start_time": task.timer.start_time,
375
+ "end_type": task.timer.end_type, "end_date": task.timer.end_date, "end_time": task.timer.end_time}
376
+ if task.metadata_extensions:
377
+ d["metadata_extensions"] = [self._meta_ext_to_dict(me) for me in task.metadata_extensions]
364
378
  return d
365
379
 
366
380
  def _config_to_dict(self, cfg):
367
381
  return {
368
382
  "name": cfg.name, "description": cfg.description, "is_valid": cfg.is_valid,
369
383
  "attributes": [{"name": a.name, "value": a.value} for a in cfg.attributes],
384
+ "metadata_extensions": [self._meta_ext_to_dict(me) for me in cfg.metadata_extensions],
370
385
  }
371
386
 
372
387
  def _scheduler_to_dict(self, sched):
@@ -380,10 +395,18 @@ class InformaticaConverter:
380
395
  d["start_options"] = sched.start_options.attributes
381
396
  if sched.end_options:
382
397
  d["end_options"] = sched.end_options.attributes
398
+ if sched.schedule_options:
399
+ d["schedule_options"] = sched.schedule_options.attributes
383
400
  if sched.recurring:
384
401
  d["recurring"] = sched.recurring.attributes
402
+ if sched.custom:
403
+ d["custom"] = sched.custom.attributes
385
404
  if sched.daily_frequency:
386
405
  d["daily_frequency"] = sched.daily_frequency.attributes
406
+ if sched.repeat:
407
+ d["repeat"] = sched.repeat.attributes
408
+ if sched.scheduler_filter:
409
+ d["filter"] = sched.scheduler_filter.attributes
387
410
  return d
388
411
 
389
412
  def _shortcut_to_dict(self, sc):
@@ -417,9 +440,11 @@ class InformaticaConverter:
417
440
  for v in wf.variables
418
441
  ],
419
442
  "events": [
420
- {"name": e.name, "event_type": e.event_type, "description": e.description}
443
+ {"name": e.name, "event_type": e.event_type, "description": e.description,
444
+ "attributes": [{"name": a.name, "value": a.value} for a in e.attributes]}
421
445
  for e in wf.events
422
446
  ],
423
447
  "attributes": [{"name": a.name, "value": a.value} for a in wf.attributes],
424
448
  "metadata": wf.metadata,
449
+ "metadata_extensions": [self._meta_ext_to_dict(me) for me in wf.metadata_extensions],
425
450
  }
@@ -228,10 +228,36 @@ def generate_error_log(folder: FolderDef, parser_errors=None, parser_warnings=No
228
228
  lines.append("-" * 70)
229
229
  lines.append("")
230
230
 
231
+ all_transformations = list(folder.transformations)
232
+ for m in folder.mappings:
233
+ all_transformations.extend(m.transformations)
234
+ for mpl in folder.mapplets:
235
+ all_transformations.extend(mpl.transformations)
236
+
237
+ all_instances = []
238
+ for m in folder.mappings:
239
+ all_instances.extend(m.instances)
240
+ for mpl in folder.mapplets:
241
+ all_instances.extend(mpl.instances)
242
+
243
+ me_count = len(folder.metadata_extensions)
244
+ me_count += sum(len(s.metadata_extensions) for s in folder.sources)
245
+ me_count += sum(len(t.metadata_extensions) for t in folder.targets)
246
+ me_count += sum(len(m.metadata_extensions) for m in folder.mappings)
247
+ me_count += sum(len(mpl.metadata_extensions) for mpl in folder.mapplets)
248
+ me_count += sum(len(s.metadata_extensions) for s in folder.sessions)
249
+ me_count += sum(len(w.metadata_extensions) for w in folder.workflows)
250
+ me_count += sum(len(t.metadata_extensions) for t in folder.tasks)
251
+ me_count += sum(len(c.metadata_extensions) for c in folder.configs)
252
+ me_count += sum(len(tx.metadata_extensions) for tx in all_transformations)
253
+
254
+ group_count = (
255
+ sum(len(s.groups) for s in folder.sources)
256
+ + sum(len(t.groups) for t in folder.targets)
257
+ + sum(len(tx.groups) for tx in all_transformations)
258
+ )
259
+
231
260
  tag_counts = {
232
- "POWERMART": 1,
233
- "REPOSITORY": sum(1 for _ in [1]),
234
- "FOLDER": 1,
235
261
  "FOLDERVERSION": len(folder.folder_versions),
236
262
  "SOURCE": len(folder.sources),
237
263
  "SOURCEFIELD": sum(len(s.fields) for s in folder.sources),
@@ -241,24 +267,24 @@ def generate_error_log(folder: FolderDef, parser_errors=None, parser_warnings=No
241
267
  "TARGETINDEXFIELD": sum(len(idx.fields) for t in folder.targets for idx in t.indexes),
242
268
  "FLATFILE": sum(1 for s in folder.sources if s.flatfile) + sum(1 for t in folder.targets if t.flatfile),
243
269
  "XMLINFO": sum(1 for s in folder.sources if s.xmlinfo) + sum(1 for t in folder.targets if t.xmlinfo),
244
- "GROUP": sum(len(s.groups) for s in folder.sources) + sum(len(t.groups) for t in folder.targets),
245
- "KEYWORD": sum(len(s.keywords) for s in folder.sources),
270
+ "GROUP": group_count,
271
+ "KEYWORD": sum(len(s.keywords) for s in folder.sources) + sum(len(t.keywords) for t in folder.targets),
246
272
  "ERPSRCINFO": sum(1 for s in folder.sources if s.erp_src_info),
247
273
  "MAPPING": len(folder.mappings),
248
274
  "MAPPLET": len(folder.mapplets),
249
- "TRANSFORMATION": sum(len(m.transformations) for m in folder.mappings) + len(folder.transformations),
250
- "TRANSFORMFIELD": sum(len(tx.fields) for m in folder.mappings for tx in m.transformations),
251
- "TRANSFORMFIELDATTR": sum(len(tx.field_attrs) for m in folder.mappings for tx in m.transformations),
252
- "TRANSFORMFIELDATTRDEF": sum(len(tx.field_attr_defs) for m in folder.mappings for tx in m.transformations),
253
- "INITPROP": sum(len(tx.init_props) for m in folder.mappings for tx in m.transformations),
254
- "ERPINFO": sum(1 for m in folder.mappings for tx in m.transformations if tx.erp_info),
255
- "INSTANCE": sum(len(m.instances) for m in folder.mappings),
256
- "ASSOCIATED_SOURCE_INSTANCE": sum(len(i.associated_source_instances) for m in folder.mappings for i in m.instances),
257
- "CONNECTOR": sum(len(m.connectors) for m in folder.mappings),
275
+ "TRANSFORMATION": len(all_transformations),
276
+ "TRANSFORMFIELD": sum(len(tx.fields) for tx in all_transformations),
277
+ "TRANSFORMFIELDATTR": sum(len(tx.field_attrs) for tx in all_transformations),
278
+ "TRANSFORMFIELDATTRDEF": sum(len(tx.field_attr_defs) for tx in all_transformations),
279
+ "INITPROP": sum(len(tx.init_props) for tx in all_transformations),
280
+ "ERPINFO": sum(1 for tx in all_transformations if tx.erp_info),
281
+ "INSTANCE": len(all_instances),
282
+ "ASSOCIATED_SOURCE_INSTANCE": sum(len(i.associated_source_instances) for i in all_instances),
283
+ "CONNECTOR": sum(len(m.connectors) for m in folder.mappings) + sum(len(mpl.connectors) for mpl in folder.mapplets),
258
284
  "TARGETLOADORDER": sum(len(m.target_load_orders) for m in folder.mappings),
259
285
  "MAPPINGVARIABLE": sum(len(m.variables) for m in folder.mappings),
260
- "MAPDEPENDENCY": sum(len(m.map_dependencies) for m in folder.mappings),
261
- "FIELDDEPENDENCY": sum(len(m.field_dependencies) for m in folder.mappings),
286
+ "MAPDEPENDENCY": sum(len(m.map_dependencies) for m in folder.mappings) + sum(len(mpl.map_dependencies) for mpl in folder.mapplets),
287
+ "FIELDDEPENDENCY": sum(len(m.field_dependencies) for m in folder.mappings) + sum(len(mpl.field_dependencies) for mpl in folder.mapplets),
262
288
  "SESSION": len(folder.sessions),
263
289
  "SESSTRANSFORMATIONINST": sum(len(s.transform_instances) for s in folder.sessions),
264
290
  "SESSTRANSFORMATIONGROUP": sum(len(s.transform_groups) for s in folder.sessions),
@@ -273,15 +299,15 @@ def generate_error_log(folder: FolderDef, parser_errors=None, parser_warnings=No
273
299
  "TIMER": sum(1 for t in folder.tasks if t.timer),
274
300
  "CONFIG": len(folder.configs),
275
301
  "SCHEDULER": len(folder.schedulers),
276
- "WORKFLOW": len(folder.workflows),
302
+ "WORKFLOW": sum(1 for w in folder.workflows if w.metadata.get("is_worklet") != "YES"),
277
303
  "WORKLET": sum(1 for w in folder.workflows if w.metadata.get("is_worklet") == "YES"),
278
304
  "TASKINSTANCE": sum(len(w.task_instances) for w in folder.workflows),
279
305
  "WORKFLOWLINK": sum(len(w.links) for w in folder.workflows),
280
306
  "WORKFLOWVARIABLE": sum(len(w.variables) for w in folder.workflows),
281
307
  "WORKFLOWEVENT": sum(len(w.events) for w in folder.workflows),
282
308
  "SHORTCUT": len(folder.shortcuts),
283
- "METADATAEXTENSION": len(folder.metadata_extensions),
284
- "SAPFUNCTION": sum(len(tx.sap_functions) for m in folder.mappings for tx in m.transformations),
309
+ "METADATAEXTENSION": me_count,
310
+ "SAPFUNCTION": sum(len(tx.sap_functions) for tx in all_transformations),
285
311
  }
286
312
 
287
313
  for tag, count in sorted(tag_counts.items()):
@@ -631,6 +631,402 @@ def _add_expression_helpers(lines):
631
631
  lines.append(" return date_val")
632
632
  lines.append("")
633
633
  lines.append("")
634
+ lines.append("def initcap(value):")
635
+ lines.append(' """Informatica INITCAP equivalent."""')
636
+ lines.append(" return str(value).title() if value is not None else None")
637
+ lines.append("")
638
+ lines.append("")
639
+ lines.append("def reverse_str(value):")
640
+ lines.append(' """Informatica REVERSE equivalent."""')
641
+ lines.append(" return str(value)[::-1] if value is not None else None")
642
+ lines.append("")
643
+ lines.append("")
644
+ lines.append("def chr_func(code):")
645
+ lines.append(' """Informatica CHR equivalent."""')
646
+ lines.append(" return chr(int(code)) if code is not None else None")
647
+ lines.append("")
648
+ lines.append("")
649
+ lines.append("def ascii_func(value):")
650
+ lines.append(' """Informatica ASCII equivalent."""')
651
+ lines.append(" if value is None or str(value) == '':")
652
+ lines.append(" return None")
653
+ lines.append(" return ord(str(value)[0])")
654
+ lines.append("")
655
+ lines.append("")
656
+ lines.append("def left_str(value, n):")
657
+ lines.append(' """Return leftmost n characters."""')
658
+ lines.append(" return str(value)[:int(n)] if value is not None else None")
659
+ lines.append("")
660
+ lines.append("")
661
+ lines.append("def right_str(value, n):")
662
+ lines.append(' """Return rightmost n characters."""')
663
+ lines.append(" return str(value)[-int(n):] if value is not None else None")
664
+ lines.append("")
665
+ lines.append("")
666
+ lines.append("def trim_func(value):")
667
+ lines.append(' """Informatica TRIM equivalent."""')
668
+ lines.append(" return str(value).strip() if value is not None else None")
669
+ lines.append("")
670
+ lines.append("")
671
+ lines.append("def indexof(value, search, start=1):")
672
+ lines.append(' """Informatica INDEXOF equivalent (1-based)."""')
673
+ lines.append(" if value is None or search is None:")
674
+ lines.append(" return 0")
675
+ lines.append(" idx = str(value).find(str(search), max(start - 1, 0))")
676
+ lines.append(" return idx + 1 if idx >= 0 else 0")
677
+ lines.append("")
678
+ lines.append("")
679
+ lines.append("def metaphone_func(value):")
680
+ lines.append(' """Informatica METAPHONE equivalent (simplified)."""')
681
+ lines.append(" if value is None:")
682
+ lines.append(" return None")
683
+ lines.append(" try:")
684
+ lines.append(" import jellyfish")
685
+ lines.append(" return jellyfish.metaphone(str(value))")
686
+ lines.append(" except ImportError:")
687
+ lines.append(" return str(value).upper()[:4]")
688
+ lines.append("")
689
+ lines.append("")
690
+ lines.append("def soundex_func(value):")
691
+ lines.append(' """Informatica SOUNDEX equivalent (simplified)."""')
692
+ lines.append(" if value is None:")
693
+ lines.append(" return None")
694
+ lines.append(" try:")
695
+ lines.append(" import jellyfish")
696
+ lines.append(" return jellyfish.soundex(str(value))")
697
+ lines.append(" except ImportError:")
698
+ lines.append(" s = str(value).upper()")
699
+ lines.append(" if not s:")
700
+ lines.append(" return '0000'")
701
+ lines.append(" codes = {'B':'1','F':'1','P':'1','V':'1','C':'2','G':'2','J':'2','K':'2','Q':'2','S':'2','X':'2','Z':'2','D':'3','T':'3','L':'4','M':'5','N':'5','R':'6'}")
702
+ lines.append(" result = s[0]")
703
+ lines.append(" for ch in s[1:]:")
704
+ lines.append(" code = codes.get(ch, '0')")
705
+ lines.append(" if code != '0' and code != result[-1]:")
706
+ lines.append(" result += code")
707
+ lines.append(" return (result + '0000')[:4]")
708
+ lines.append("")
709
+ lines.append("")
710
+ lines.append("def compress_func(value):")
711
+ lines.append(' """Informatica COMPRESS equivalent - removes spaces."""')
712
+ lines.append(" return str(value).replace(' ', '') if value is not None else None")
713
+ lines.append("")
714
+ lines.append("")
715
+ lines.append("def decompress_func(value):")
716
+ lines.append(' """Informatica DECOMPRESS equivalent."""')
717
+ lines.append(" return value")
718
+ lines.append("")
719
+ lines.append("")
720
+ lines.append("def to_timestamp_func(value, fmt=None):")
721
+ lines.append(' """Informatica TO_TIMESTAMP equivalent."""')
722
+ lines.append(" return to_date(value, fmt)")
723
+ lines.append("")
724
+ lines.append("")
725
+ lines.append("def cast_func(value, datatype):")
726
+ lines.append(' """Informatica CAST equivalent."""')
727
+ lines.append(" if value is None:")
728
+ lines.append(" return None")
729
+ lines.append(" dt = str(datatype).upper()")
730
+ lines.append(" if 'INT' in dt:")
731
+ lines.append(" return int(float(str(value)))")
732
+ lines.append(" elif 'FLOAT' in dt or 'DOUBLE' in dt or 'DECIMAL' in dt or 'NUMBER' in dt:")
733
+ lines.append(" return float(str(value))")
734
+ lines.append(" elif 'CHAR' in dt or 'STRING' in dt or 'VARCHAR' in dt:")
735
+ lines.append(" return str(value)")
736
+ lines.append(" elif 'DATE' in dt or 'TIMESTAMP' in dt:")
737
+ lines.append(" return to_date(value)")
738
+ lines.append(" return value")
739
+ lines.append("")
740
+ lines.append("")
741
+ lines.append("def set_date_part(part, date_val, value):")
742
+ lines.append(' """Informatica SET_DATE_PART equivalent."""')
743
+ lines.append(" if date_val is None:")
744
+ lines.append(" return None")
745
+ lines.append(" if isinstance(date_val, str):")
746
+ lines.append(" date_val = datetime.fromisoformat(date_val)")
747
+ lines.append(" p = part.upper()")
748
+ lines.append(" if p in ('YYYY', 'YY', 'YEAR'):")
749
+ lines.append(" return date_val.replace(year=int(value))")
750
+ lines.append(" elif p in ('MM', 'MON', 'MONTH'):")
751
+ lines.append(" return date_val.replace(month=int(value))")
752
+ lines.append(" elif p in ('DD', 'DAY'):")
753
+ lines.append(" return date_val.replace(day=int(value))")
754
+ lines.append(" elif p in ('HH', 'HH24', 'HOUR'):")
755
+ lines.append(" return date_val.replace(hour=int(value))")
756
+ lines.append(" elif p in ('MI', 'MINUTE'):")
757
+ lines.append(" return date_val.replace(minute=int(value))")
758
+ lines.append(" elif p in ('SS', 'SECOND'):")
759
+ lines.append(" return date_val.replace(second=int(value))")
760
+ lines.append(" return date_val")
761
+ lines.append("")
762
+ lines.append("")
763
+ lines.append("def date_diff(date1, date2, part='DD'):")
764
+ lines.append(' """Informatica DATE_DIFF equivalent."""')
765
+ lines.append(" if date1 is None or date2 is None:")
766
+ lines.append(" return None")
767
+ lines.append(" if isinstance(date1, str):")
768
+ lines.append(" date1 = datetime.fromisoformat(date1)")
769
+ lines.append(" if isinstance(date2, str):")
770
+ lines.append(" date2 = datetime.fromisoformat(date2)")
771
+ lines.append(" delta = date1 - date2")
772
+ lines.append(" p = part.upper()")
773
+ lines.append(" if p in ('DD', 'DAY', 'D'):")
774
+ lines.append(" return delta.days")
775
+ lines.append(" elif p in ('HH', 'HOUR'):")
776
+ lines.append(" return int(delta.total_seconds() / 3600)")
777
+ lines.append(" elif p in ('MI', 'MINUTE'):")
778
+ lines.append(" return int(delta.total_seconds() / 60)")
779
+ lines.append(" elif p in ('SS', 'SECOND'):")
780
+ lines.append(" return int(delta.total_seconds())")
781
+ lines.append(" elif p in ('MM', 'MONTH'):")
782
+ lines.append(" return (date1.year - date2.year) * 12 + (date1.month - date2.month)")
783
+ lines.append(" elif p in ('YYYY', 'YEAR'):")
784
+ lines.append(" return date1.year - date2.year")
785
+ lines.append(" return delta.days")
786
+ lines.append("")
787
+ lines.append("")
788
+ lines.append("def date_compare(date1, date2):")
789
+ lines.append(' """Informatica DATE_COMPARE equivalent. Returns -1, 0, or 1."""')
790
+ lines.append(" if date1 is None and date2 is None:")
791
+ lines.append(" return 0")
792
+ lines.append(" if date1 is None:")
793
+ lines.append(" return -1")
794
+ lines.append(" if date2 is None:")
795
+ lines.append(" return 1")
796
+ lines.append(" if isinstance(date1, str):")
797
+ lines.append(" date1 = datetime.fromisoformat(date1)")
798
+ lines.append(" if isinstance(date2, str):")
799
+ lines.append(" date2 = datetime.fromisoformat(date2)")
800
+ lines.append(" if date1 < date2:")
801
+ lines.append(" return -1")
802
+ lines.append(" elif date1 > date2:")
803
+ lines.append(" return 1")
804
+ lines.append(" return 0")
805
+ lines.append("")
806
+ lines.append("")
807
+ lines.append("def last_day(date_val):")
808
+ lines.append(' """Informatica LAST_DAY equivalent."""')
809
+ lines.append(" if date_val is None:")
810
+ lines.append(" return None")
811
+ lines.append(" if isinstance(date_val, str):")
812
+ lines.append(" date_val = datetime.fromisoformat(date_val)")
813
+ lines.append(" import calendar")
814
+ lines.append(" last = calendar.monthrange(date_val.year, date_val.month)[1]")
815
+ lines.append(" return date_val.replace(day=last)")
816
+ lines.append("")
817
+ lines.append("")
818
+ lines.append("def make_date_time(year, month, day, hour=0, minute=0, second=0):")
819
+ lines.append(' """Informatica MAKE_DATE_TIME equivalent."""')
820
+ lines.append(" return datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))")
821
+ lines.append("")
822
+ lines.append("")
823
+ lines.append("def trunc(value, precision=0):")
824
+ lines.append(' """Informatica TRUNC equivalent (numeric or date)."""')
825
+ lines.append(" if value is None:")
826
+ lines.append(" return None")
827
+ lines.append(" if hasattr(value, 'replace') and hasattr(value, 'year'):")
828
+ lines.append(" return value.replace(hour=0, minute=0, second=0, microsecond=0)")
829
+ lines.append(" import math")
830
+ lines.append(" factor = 10 ** int(precision)")
831
+ lines.append(" return math.trunc(float(value) * factor) / factor")
832
+ lines.append("")
833
+ lines.append("")
834
+ lines.append("def round_val(value, precision=0):")
835
+ lines.append(' """Informatica ROUND equivalent."""')
836
+ lines.append(" if value is None:")
837
+ lines.append(" return None")
838
+ lines.append(" return round(float(value), int(precision))")
839
+ lines.append("")
840
+ lines.append("")
841
+ lines.append("def abs_val(value):")
842
+ lines.append(' """Informatica ABS equivalent."""')
843
+ lines.append(" return abs(float(value)) if value is not None else None")
844
+ lines.append("")
845
+ lines.append("")
846
+ lines.append("def ceil_val(value):")
847
+ lines.append(' """Informatica CEIL equivalent."""')
848
+ lines.append(" import math")
849
+ lines.append(" return math.ceil(float(value)) if value is not None else None")
850
+ lines.append("")
851
+ lines.append("")
852
+ lines.append("def floor_val(value):")
853
+ lines.append(' """Informatica FLOOR equivalent."""')
854
+ lines.append(" import math")
855
+ lines.append(" return math.floor(float(value)) if value is not None else None")
856
+ lines.append("")
857
+ lines.append("")
858
+ lines.append("def mod_val(a, b):")
859
+ lines.append(' """Informatica MOD equivalent."""')
860
+ lines.append(" if a is None or b is None or float(b) == 0:")
861
+ lines.append(" return None")
862
+ lines.append(" return float(a) % float(b)")
863
+ lines.append("")
864
+ lines.append("")
865
+ lines.append("def power_val(base, exp):")
866
+ lines.append(' """Informatica POWER equivalent."""')
867
+ lines.append(" if base is None or exp is None:")
868
+ lines.append(" return None")
869
+ lines.append(" return float(base) ** float(exp)")
870
+ lines.append("")
871
+ lines.append("")
872
+ lines.append("def sqrt_val(value):")
873
+ lines.append(' """Informatica SQRT equivalent."""')
874
+ lines.append(" import math")
875
+ lines.append(" return math.sqrt(float(value)) if value is not None else None")
876
+ lines.append("")
877
+ lines.append("")
878
+ lines.append("def log_val(base, value):")
879
+ lines.append(' """Informatica LOG equivalent."""')
880
+ lines.append(" import math")
881
+ lines.append(" if value is None or base is None:")
882
+ lines.append(" return None")
883
+ lines.append(" return math.log(float(value), float(base))")
884
+ lines.append("")
885
+ lines.append("")
886
+ lines.append("def ln_val(value):")
887
+ lines.append(' """Informatica LN (natural log) equivalent."""')
888
+ lines.append(" import math")
889
+ lines.append(" return math.log(float(value)) if value is not None else None")
890
+ lines.append("")
891
+ lines.append("")
892
+ lines.append("def exp_val(value):")
893
+ lines.append(' """Informatica EXP equivalent."""')
894
+ lines.append(" import math")
895
+ lines.append(" return math.exp(float(value)) if value is not None else None")
896
+ lines.append("")
897
+ lines.append("")
898
+ lines.append("def sign_val(value):")
899
+ lines.append(' """Informatica SIGN equivalent."""')
900
+ lines.append(" if value is None:")
901
+ lines.append(" return None")
902
+ lines.append(" v = float(value)")
903
+ lines.append(" return 1 if v > 0 else (-1 if v < 0 else 0)")
904
+ lines.append("")
905
+ lines.append("")
906
+ lines.append("def rand_val(seed=None):")
907
+ lines.append(' """Informatica RAND equivalent."""')
908
+ lines.append(" import random")
909
+ lines.append(" if seed is not None:")
910
+ lines.append(" random.seed(seed)")
911
+ lines.append(" return random.random()")
912
+ lines.append("")
913
+ lines.append("")
914
+ lines.append("def greatest_val(*args):")
915
+ lines.append(' """Informatica GREATEST equivalent."""')
916
+ lines.append(" filtered = [a for a in args if a is not None]")
917
+ lines.append(" return max(filtered) if filtered else None")
918
+ lines.append("")
919
+ lines.append("")
920
+ lines.append("def least_val(*args):")
921
+ lines.append(' """Informatica LEAST equivalent."""')
922
+ lines.append(" filtered = [a for a in args if a is not None]")
923
+ lines.append(" return min(filtered) if filtered else None")
924
+ lines.append("")
925
+ lines.append("")
926
+ lines.append("def choose_expr(index, *values):")
927
+ lines.append(' """Informatica CHOOSE equivalent."""')
928
+ lines.append(" idx = int(index)")
929
+ lines.append(" if 1 <= idx <= len(values):")
930
+ lines.append(" return values[idx - 1]")
931
+ lines.append(" return None")
932
+ lines.append("")
933
+ lines.append("")
934
+ lines.append("def in_expr(value, *candidates):")
935
+ lines.append(' """Informatica IN equivalent."""')
936
+ lines.append(" return value in candidates")
937
+ lines.append("")
938
+ lines.append("")
939
+ lines.append("def max_val(*args):")
940
+ lines.append(' """Informatica MAX equivalent (row-level)."""')
941
+ lines.append(" filtered = [a for a in args if a is not None]")
942
+ lines.append(" return max(filtered) if filtered else None")
943
+ lines.append("")
944
+ lines.append("")
945
+ lines.append("def min_val(*args):")
946
+ lines.append(' """Informatica MIN equivalent (row-level)."""')
947
+ lines.append(" filtered = [a for a in args if a is not None]")
948
+ lines.append(" return min(filtered) if filtered else None")
949
+ lines.append("")
950
+ lines.append("")
951
+ lines.append("def sum_val(*args):")
952
+ lines.append(' """Informatica SUM equivalent (row-level)."""')
953
+ lines.append(" return sum(float(a) for a in args if a is not None)")
954
+ lines.append("")
955
+ lines.append("")
956
+ lines.append("def count_val(*args):")
957
+ lines.append(' """Informatica COUNT equivalent (row-level)."""')
958
+ lines.append(" return sum(1 for a in args if a is not None)")
959
+ lines.append("")
960
+ lines.append("")
961
+ lines.append("def avg_val(*args):")
962
+ lines.append(' """Informatica AVG equivalent (row-level)."""')
963
+ lines.append(" filtered = [float(a) for a in args if a is not None]")
964
+ lines.append(" return sum(filtered) / len(filtered) if filtered else None")
965
+ lines.append("")
966
+ lines.append("")
967
+ lines.append("def median_val(*args):")
968
+ lines.append(' """Informatica MEDIAN equivalent (row-level)."""')
969
+ lines.append(" import statistics")
970
+ lines.append(" filtered = [float(a) for a in args if a is not None]")
971
+ lines.append(" return statistics.median(filtered) if filtered else None")
972
+ lines.append("")
973
+ lines.append("")
974
+ lines.append("def stddev_val(*args):")
975
+ lines.append(' """Informatica STDDEV equivalent (row-level)."""')
976
+ lines.append(" import statistics")
977
+ lines.append(" filtered = [float(a) for a in args if a is not None]")
978
+ lines.append(" return statistics.stdev(filtered) if len(filtered) > 1 else 0")
979
+ lines.append("")
980
+ lines.append("")
981
+ lines.append("def variance_val(*args):")
982
+ lines.append(' """Informatica VARIANCE equivalent (row-level)."""')
983
+ lines.append(" import statistics")
984
+ lines.append(" filtered = [float(a) for a in args if a is not None]")
985
+ lines.append(" return statistics.variance(filtered) if len(filtered) > 1 else 0")
986
+ lines.append("")
987
+ lines.append("")
988
+ lines.append("def percentile_val(value, pct):")
989
+ lines.append(' """Informatica PERCENTILE equivalent."""')
990
+ lines.append(" return value")
991
+ lines.append("")
992
+ lines.append("")
993
+ lines.append("def first_val(*args):")
994
+ lines.append(' """Informatica FIRST equivalent."""')
995
+ lines.append(" for a in args:")
996
+ lines.append(" if a is not None:")
997
+ lines.append(" return a")
998
+ lines.append(" return None")
999
+ lines.append("")
1000
+ lines.append("")
1001
+ lines.append("def last_val(*args):")
1002
+ lines.append(' """Informatica LAST equivalent."""')
1003
+ lines.append(" result = None")
1004
+ lines.append(" for a in args:")
1005
+ lines.append(" if a is not None:")
1006
+ lines.append(" result = a")
1007
+ lines.append(" return result")
1008
+ lines.append("")
1009
+ lines.append("")
1010
+ lines.append("def moving_avg(value, window=3):")
1011
+ lines.append(' """Informatica MOVINGAVG equivalent."""')
1012
+ lines.append(" return value")
1013
+ lines.append("")
1014
+ lines.append("")
1015
+ lines.append("def moving_sum(value, window=3):")
1016
+ lines.append(' """Informatica MOVINGSUM equivalent."""')
1017
+ lines.append(" return value")
1018
+ lines.append("")
1019
+ lines.append("")
1020
+ lines.append("def cume(value):")
1021
+ lines.append(' """Informatica CUME equivalent."""')
1022
+ lines.append(" return value")
1023
+ lines.append("")
1024
+ lines.append("")
1025
+ lines.append("def set_count_variable(var_name, value=1):")
1026
+ lines.append(' """Informatica SETCOUNTVARIABLE equivalent."""')
1027
+ lines.append(" return set_variable(var_name, value)")
1028
+ lines.append("")
1029
+ lines.append("")
634
1030
  lines.append("def raise_error(message):")
635
1031
  lines.append(' """Informatica ERROR function equivalent."""')
636
1032
  lines.append(" logger.error(f'INFORMATICA ERROR: {message}')")