informatica-python 1.9.9__py3-none-any.whl → 1.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,8 @@ def _expand_mapplet_recursive(mapplet, mapplet_map, prefix, depth=0, max_depth=1
54
54
  to_instance=new_to,
55
55
  to_field=conn.to_field,
56
56
  to_instance_type=conn.to_instance_type,
57
+ from_instance_group=conn.from_instance_group,
58
+ to_instance_group=conn.to_instance_group,
57
59
  ))
58
60
 
59
61
  for inst in getattr(mapplet, 'instances', []):
@@ -138,6 +140,8 @@ def _inline_mapplets(mapping, folder):
138
140
  to_instance=first_tx,
139
141
  to_field=conn.to_field,
140
142
  to_instance_type=conn.to_instance_type,
143
+ from_instance_group=conn.from_instance_group,
144
+ to_instance_group=conn.to_instance_group,
141
145
  ))
142
146
  else:
143
147
  rewired_connectors.append(conn)
@@ -167,6 +171,8 @@ def _inline_mapplets(mapping, folder):
167
171
  to_instance=conn.to_instance,
168
172
  to_field=conn.to_field,
169
173
  to_instance_type=conn.to_instance_type,
174
+ from_instance_group=conn.from_instance_group,
175
+ to_instance_group=conn.to_instance_group,
170
176
  ))
171
177
  else:
172
178
  rewired_connectors.append(conn)
@@ -730,7 +736,11 @@ def _generate_transformation(lines, tx, connector_graph, source_dfs, transform_m
730
736
  input_conns = connector_graph.get("to", {}).get(tx.name, [])
731
737
  input_sources = set()
732
738
  for c in input_conns:
733
- input_sources.add(c.from_instance)
739
+ if c.from_instance_group:
740
+ group_key = f"{c.from_instance}:{c.from_instance_group}"
741
+ input_sources.add(group_key)
742
+ else:
743
+ input_sources.add(c.from_instance)
734
744
 
735
745
  input_df = None
736
746
  for src in input_sources:
@@ -739,7 +749,14 @@ def _generate_transformation(lines, tx, connector_graph, source_dfs, transform_m
739
749
  break
740
750
  if not input_df:
741
751
  for src in input_sources:
742
- input_df = f"df_{_safe_name(src)}"
752
+ base = src.split(":")[0] if ":" in src else src
753
+ if base in source_dfs:
754
+ input_df = source_dfs[base]
755
+ break
756
+ if not input_df:
757
+ for src in input_sources:
758
+ base = src.split(":")[0] if ":" in src else src
759
+ input_df = f"df_{_safe_name(base)}"
743
760
  break
744
761
  if not input_df:
745
762
  input_df = "df_input"
@@ -946,10 +963,11 @@ def _gen_joiner_transform(lines, tx, tx_safe, input_df, input_sources, source_df
946
963
  to_field = conn.to_field
947
964
  port_to_col[to_field] = conn.from_field
948
965
  port_to_col[to_field.lower()] = conn.from_field
966
+ src_key = f"{conn.from_instance}:{conn.from_instance_group}" if conn.from_instance_group else conn.from_instance
949
967
  if to_field in master_fields or to_field.lower() in master_fields_lower:
950
- master_src = conn.from_instance
968
+ master_src = src_key
951
969
  elif to_field in detail_fields or to_field.lower() in detail_fields_lower:
952
- detail_src = conn.from_instance
970
+ detail_src = src_key
953
971
 
954
972
  if left_keys and right_keys and port_to_col:
955
973
  left_keys = [port_to_col.get(k, port_to_col.get(k.lower(), k)) for k in left_keys]
@@ -967,9 +985,17 @@ def _gen_joiner_transform(lines, tx, tx_safe, input_df, input_sources, source_df
967
985
  detail_src = s
968
986
  break
969
987
 
988
+ def _resolve_src(key):
989
+ if key in source_dfs:
990
+ return source_dfs[key]
991
+ base = key.split(":")[0] if ":" in key else key
992
+ if base in source_dfs:
993
+ return source_dfs[base]
994
+ return f"df_{_safe_name(base)}"
995
+
970
996
  if master_src and detail_src:
971
- df_master = source_dfs.get(master_src, f"df_{_safe_name(master_src)}")
972
- df_detail = source_dfs.get(detail_src, f"df_{_safe_name(detail_src)}")
997
+ df_master = _resolve_src(master_src)
998
+ df_detail = _resolve_src(detail_src)
973
999
 
974
1000
  lines.append(f" # Join ({join_type}): {join_condition or 'auto'}")
975
1001
  if left_keys and right_keys:
@@ -1095,9 +1121,24 @@ def _gen_lookup_transform(lines, tx, tx_safe, input_df, source_dfs, connector_gr
1095
1121
  def _gen_router_transform(lines, tx, tx_safe, input_df, source_dfs):
1096
1122
  lines.append(f" # Router groups:")
1097
1123
  group_conditions = {}
1098
- for attr in tx.attributes:
1099
- if "Group Filter Condition" in attr.name:
1100
- group_conditions[attr.name] = attr.value
1124
+
1125
+ output_groups = [
1126
+ g for g in tx.groups
1127
+ if g.type.upper() not in ("INPUT", "") and "DEFAULT" not in g.type.upper()
1128
+ ]
1129
+ output_groups.sort(key=lambda g: g.order)
1130
+
1131
+ if output_groups:
1132
+ for g in output_groups:
1133
+ if g.expression and g.expression.strip():
1134
+ group_conditions[g.name] = g.expression
1135
+ else:
1136
+ group_conditions[g.name] = ""
1137
+
1138
+ if not group_conditions:
1139
+ for attr in tx.attributes:
1140
+ if "Group Filter Condition" in attr.name:
1141
+ group_conditions[attr.name] = attr.value
1101
1142
 
1102
1143
  remaining_mask_parts = []
1103
1144
  if group_conditions:
@@ -1108,15 +1149,21 @@ def _gen_router_transform(lines, tx, tx_safe, input_df, source_dfs):
1108
1149
  expr_py = f"pd.Series(True, index={input_df}.index)"
1109
1150
  mask_var = f"_router_mask_{tx_safe}_{i}"
1110
1151
  lines.append(f" {mask_var} = {expr_py} # {gname}")
1111
- lines.append(f" df_{tx_safe}_group{i} = {input_df}[{mask_var}].copy()")
1112
- source_dfs[f"{tx.name}_group{i}"] = f"df_{tx_safe}_group{i}"
1152
+ group_df_name = f"df_{tx_safe}_{_safe_name(gname)}"
1153
+ lines.append(f" {group_df_name} = {input_df}[{mask_var}].copy()")
1154
+ source_dfs[f"{tx.name}:{gname}"] = group_df_name
1113
1155
  remaining_mask_parts.append(f"~{mask_var}")
1156
+
1157
+ default_groups = [g for g in tx.groups if "DEFAULT" in g.type.upper()]
1158
+ default_name = default_groups[0].name if default_groups else "DEFAULT"
1159
+
1114
1160
  if remaining_mask_parts:
1115
1161
  lines.append(f" _router_default_mask = {' & '.join(remaining_mask_parts)}")
1116
- lines.append(f" df_{tx_safe} = {input_df}[_router_default_mask].copy() # Default group")
1162
+ lines.append(f" df_{tx_safe} = {input_df}[_router_default_mask].copy() # Default group ({default_name})")
1117
1163
  else:
1118
- lines.append(f" df_{tx_safe} = {input_df}.copy() # Default group")
1164
+ lines.append(f" df_{tx_safe} = {input_df}.copy() # Default group ({default_name})")
1119
1165
  source_dfs[tx.name] = f"df_{tx_safe}"
1166
+ source_dfs[f"{tx.name}:{default_name}"] = f"df_{tx_safe}"
1120
1167
 
1121
1168
 
1122
1169
  def _gen_union_transform(lines, tx, tx_safe, input_sources, source_dfs, data_lib="pandas"):
@@ -1448,6 +1495,11 @@ def _generate_target_write(lines, tgt_name, tgt_def, connector_graph, source_dfs
1448
1495
  to_conns = connector_graph.get("to", {}).get(tgt_name, [])
1449
1496
  input_df = None
1450
1497
  for c in to_conns:
1498
+ if c.from_instance_group:
1499
+ group_key = f"{c.from_instance}:{c.from_instance_group}"
1500
+ if group_key in source_dfs:
1501
+ input_df = source_dfs[group_key]
1502
+ break
1451
1503
  if c.from_instance in source_dfs:
1452
1504
  input_df = source_dfs[c.from_instance]
1453
1505
  break
@@ -80,6 +80,7 @@ class GroupDef:
80
80
  type: str = ""
81
81
  description: str = ""
82
82
  order: int = 0
83
+ expression: str = ""
83
84
  fields: List[FieldDef] = field(default_factory=list)
84
85
 
85
86
 
@@ -274,6 +275,8 @@ class ConnectorDef:
274
275
  to_field: str
275
276
  to_instance: str
276
277
  to_instance_type: str
278
+ from_instance_group: str = ""
279
+ to_instance_group: str = ""
277
280
 
278
281
 
279
282
  @dataclass
@@ -240,6 +240,7 @@ class InformaticaParser:
240
240
  type=self._attr(elem, "TYPE"),
241
241
  description=self._attr(elem, "DESCRIPTION"),
242
242
  order=self._int_attr(elem, "ORDER"),
243
+ expression=self._attr(elem, "EXPRESSION"),
243
244
  )
244
245
  for fld in elem.findall("SOURCEFIELD"):
245
246
  grp.fields.append(self._parse_source_field(fld))
@@ -580,6 +581,8 @@ class InformaticaParser:
580
581
  to_field=self._attr(elem, "TOFIELD"),
581
582
  to_instance=self._attr(elem, "TOINSTANCE"),
582
583
  to_instance_type=self._attr(elem, "TOINSTANCETYPE"),
584
+ from_instance_group=self._attr(elem, "FROMINSTANCEGROUP"),
585
+ to_instance_group=self._attr(elem, "TOINSTANCEGROUP"),
583
586
  )
584
587
 
585
588
  def _parse_instance(self, elem) -> InstanceDef:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: informatica-python
3
- Version: 1.9.9
3
+ Version: 1.10.1
4
4
  Summary: Convert Informatica PowerCenter workflow XML to Python/PySpark code
5
5
  Author: Nick
6
6
  License: MIT
@@ -414,7 +414,15 @@ The generated `helper_functions.py` provides a complete runtime library:
414
414
 
415
415
  ## Changelog
416
416
 
417
- ### v1.9.8 (Current)
417
+ ### v1.10.1 (Current)
418
+ - **Router multi-group output support**: Router transformations now properly handle `<GROUP>` elements with `EXPRESSION` attributes — generates separate filtered DataFrames for each named output group (e.g., `df_rtr_rest_type_per`, `df_rtr_rest_value_per`), not just the DEFAULT group
419
+ - **Connector group routing**: `FROMINSTANCEGROUP` / `TOINSTANCEGROUP` attributes on `CONNECTOR` elements are now parsed and used to wire downstream transforms/targets to the correct Router output group
420
+ - **Joiner group-aware routing**: Joiner transformations now correctly identify master/detail sources through Router group connectors — two different Router output groups feeding a Joiner are properly resolved to their distinct DataFrames
421
+ - **GroupDef expression field**: `GroupDef` model now stores the `EXPRESSION` attribute from `<GROUP>` XML elements
422
+ - **Backward-compatible Router fallback**: Existing `TABLEATTRIBUTE`-based Router group conditions (older XML format) continue to work — the code checks `<GROUP>` elements first, then falls back to `TABLEATTRIBUTE` entries
423
+ - **223 tests** passing
424
+
425
+ ### v1.9.8
418
426
  - **NOT(expr) function-call form**: `NOT(ISNULL(x))` now correctly converts to `~(df["x"].isna())` — handles both `NOT ` (with space) and `NOT(` (without space) forms
419
427
  - **AND/OR/NOT as field names fix**: Logical operators no longer mangled into `df["AND"]` / `df["OR"]` — conversion moved before field substitution in both `_vec_recursive` fallback and `_vectorize_simple`
420
428
  - **Condition tokenizer word-boundary fix**: `_split_condition_tokens` no longer splits on `OR` inside field names like `DeletedIndicator` — verifies preceding character is a real word boundary
@@ -1,13 +1,13 @@
1
1
  informatica_python/__init__.py,sha256=JFO8fVMClSWe0SR-CBseX4RaPyyC3rZBdxxjy47ZT5E,337
2
2
  informatica_python/cli.py,sha256=gFwg0O99vKM-OLO0HoHA4emd-6qrgjMNqa9T59e4e_s,2905
3
3
  informatica_python/converter.py,sha256=xCuWrYzDji0yN72D3QqOgZCVVM2j3k2_CvlGplCWxLU,22779
4
- informatica_python/models.py,sha256=sZvVzYrEIRAfzV_HduN-qCeOAt5KZ_z7jzNTmPP3Oxs,17371
5
- informatica_python/parser.py,sha256=RVxoT1j6QTer2RyeG-PCEyKaoZAQhFepRcrRdsEm6OM,45410
4
+ informatica_python/models.py,sha256=lBlxJBVSyW050KAGj8OyUd11Wyfk-MA2XWBT3qEQSK4,17462
5
+ informatica_python/parser.py,sha256=LpX6Cg5jlqqZyLMr_VRUVvNaLdqls4aB-PdGCLbehZo,45603
6
6
  informatica_python/generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  informatica_python/generators/config_gen.py,sha256=4tqcNKTB06kyGZIiM4yl0q97q_i3zeCHXTjuE1dNFKY,5726
8
8
  informatica_python/generators/error_log_gen.py,sha256=2cc0rEcblydHkb9VAMXlrH7WdSQ-CNqAXcwVk3FYZeM,21319
9
9
  informatica_python/generators/helper_gen.py,sha256=lC30hyZn6RIkbo4e_6sbqdrCfmZHWaXdr-p0tmtfILc,82376
10
- informatica_python/generators/mapping_gen.py,sha256=5wPS9t3OLpbo89gYsHMbVqCg9Jgfzmt13IqK4diOS2g,72781
10
+ informatica_python/generators/mapping_gen.py,sha256=BA703-7byzqvRy_23w-1mpWNs7v8dEaXh2c1NUwtRIk,74841
11
11
  informatica_python/generators/sql_gen.py,sha256=O8Y-aJz9EyFJ0DXeuISRt5yKwC3wlp2K3B0BHrmxrXw,4872
12
12
  informatica_python/generators/workflow_gen.py,sha256=_uSlBg31ZRMhMlCYk4hWDRBPaBROrepD8_v3QGEWJxE,18089
13
13
  informatica_python/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -15,9 +15,9 @@ informatica_python/utils/datatype_map.py,sha256=iLOYg-iBKT4rMecGbrFkTpJj4yqs5S9H
15
15
  informatica_python/utils/expression_converter.py,sha256=ynprsvZGvavML3Y8C485GyjaoqQ-k67OESXHShafeTo,48244
16
16
  informatica_python/utils/lib_adapters.py,sha256=1ZtuMbgDg9Ukf-OF_EG1L_BeeR-6JQk8Kx3WwMfvNRU,6516
17
17
  informatica_python/utils/sql_dialect.py,sha256=_IHJbfu8a3mT_OvHpybgSfZKqz6mwVy5ItTKDRChqnU,5461
18
- informatica_python-1.9.9.dist-info/licenses/LICENSE,sha256=77RaRDdXgey1D90YZAjXqEQdBxWfvUQqLQX3pC1qjUE,1061
19
- informatica_python-1.9.9.dist-info/METADATA,sha256=2NqepCsEQA7-10Pa-XAmaAnaGgD4eGkWCfw095zJ7GE,29558
20
- informatica_python-1.9.9.dist-info/WHEEL,sha256=PovZm1ExVWmrRefZoXCfejlbKLnQI5SVIf1SWRV4QQI,97
21
- informatica_python-1.9.9.dist-info/entry_points.txt,sha256=030jjTrx-1oRRQ16HZz52rdcKS8R8_llnymsTUtn_Xc,67
22
- informatica_python-1.9.9.dist-info/top_level.txt,sha256=Dngg-WNteYi22XAJU2XKAQS8aZ52yM2LYC0tzxrlbVQ,19
23
- informatica_python-1.9.9.dist-info/RECORD,,
18
+ informatica_python-1.10.1.dist-info/licenses/LICENSE,sha256=77RaRDdXgey1D90YZAjXqEQdBxWfvUQqLQX3pC1qjUE,1061
19
+ informatica_python-1.10.1.dist-info/METADATA,sha256=FfUt2F7VzYojRHWjCMtYD-rVqJZnQR1RS3uyWC6nWXc,30668
20
+ informatica_python-1.10.1.dist-info/WHEEL,sha256=PovZm1ExVWmrRefZoXCfejlbKLnQI5SVIf1SWRV4QQI,97
21
+ informatica_python-1.10.1.dist-info/entry_points.txt,sha256=030jjTrx-1oRRQ16HZz52rdcKS8R8_llnymsTUtn_Xc,67
22
+ informatica_python-1.10.1.dist-info/top_level.txt,sha256=Dngg-WNteYi22XAJU2XKAQS8aZ52yM2LYC0tzxrlbVQ,19
23
+ informatica_python-1.10.1.dist-info/RECORD,,