[set_dead] improve handling of dead ports

This commit is contained in:
Jan Petykiewicz 2026-04-08 17:41:50 -07:00
commit 84106dc355
5 changed files with 350 additions and 88 deletions

View file

@ -324,6 +324,105 @@ class PortList(metaclass=ABCMeta):
Returns:
self
"""
self._rename_ports_impl(mapping, overwrite=overwrite)
return self
@staticmethod
def _normalize_target_mapping(
ordered_targets: Iterable[tuple[str, str | None]],
explicit_map: Mapping[str, str | None] | None = None,
) -> dict[str, str | None]:
ordered_targets = list(ordered_targets)
normalized = {} if explicit_map is None else copy.deepcopy(dict(explicit_map))
winners = {
target: source
for source, target in ordered_targets
if target is not None
}
for source, target in ordered_targets:
if target is not None and winners[target] != source:
normalized[source] = None
return normalized
def _resolve_insert_mapping(
self,
other_names: Iterable[str],
map_in: Mapping[str, str] | None = None,
map_out: Mapping[str, str | None] | None = None,
*,
allow_conflicts: bool = False,
) -> tuple[dict[str, str | None], set[str]]:
if map_in is None:
map_in = {}
normalized_map_out = {} if map_out is None else copy.deepcopy(dict(map_out))
other_names = list(other_names)
other = set(other_names)
missing_inkeys = set(map_in.keys()) - set(self.ports.keys())
if missing_inkeys:
raise PortError(f'`map_in` keys not present in device: {missing_inkeys}')
missing_invals = set(map_in.values()) - other
if missing_invals:
raise PortError(f'`map_in` values not present in other device: {missing_invals}')
map_in_counts = Counter(map_in.values())
conflicts_in = {kk for kk, vv in map_in_counts.items() if vv > 1}
if conflicts_in:
raise PortError(f'Duplicate values in `map_in`: {conflicts_in}')
missing_outkeys = set(normalized_map_out.keys()) - other
if missing_outkeys:
raise PortError(f'`map_out` keys not present in other device: {missing_outkeys}')
connected_outkeys = set(normalized_map_out.keys()) & set(map_in.values())
if connected_outkeys:
raise PortError(f'`map_out` keys conflict with connected ports: {connected_outkeys}')
orig_remaining = set(self.ports.keys()) - set(map_in.keys())
connected = set(map_in.values())
if allow_conflicts:
ordered_targets = [
(name, normalized_map_out.get(name, name))
for name in other_names
if name not in connected
]
normalized_map_out = self._normalize_target_mapping(ordered_targets, normalized_map_out)
final_targets = {
normalized_map_out.get(name, name)
for name in other_names
if name not in connected and normalized_map_out.get(name, name) is not None
}
overwrite_targets = {target for target in final_targets if target in orig_remaining}
return normalized_map_out, overwrite_targets
other_remaining = other - set(normalized_map_out.keys()) - connected
mapped_vals = set(normalized_map_out.values())
mapped_vals.discard(None)
conflicts_final = orig_remaining & (other_remaining | mapped_vals)
if conflicts_final:
raise PortError(f'Device ports conflict with existing ports: {conflicts_final}')
conflicts_partial = other_remaining & mapped_vals
if conflicts_partial:
raise PortError(f'`map_out` targets conflict with non-mapped outputs: {conflicts_partial}')
map_out_counts = Counter(normalized_map_out.values())
map_out_counts[None] = 0
conflicts_out = {kk for kk, vv in map_out_counts.items() if vv > 1}
if conflicts_out:
raise PortError(f'Duplicate targets in `map_out`: {conflicts_out}')
return normalized_map_out, set()
def _rename_ports_impl(
self,
mapping: Mapping[str, str | None],
*,
overwrite: bool = False,
allow_collisions: bool = False,
) -> dict[str, str]:
if not overwrite:
duplicates = (set(self.ports.keys()) - set(mapping.keys())) & set(mapping.values())
if duplicates:
@ -332,25 +431,40 @@ class PortList(metaclass=ABCMeta):
if missing:
raise PortError(f'Ports to rename were not found: {missing}')
renamed_targets = [vv for vv in mapping.values() if vv is not None]
duplicate_targets = {vv for vv in renamed_targets if renamed_targets.count(vv) > 1}
if duplicate_targets:
raise PortError(f'Renamed ports would collide: {duplicate_targets}')
if not allow_collisions:
duplicate_targets = {vv for vv in renamed_targets if renamed_targets.count(vv) > 1}
if duplicate_targets:
raise PortError(f'Renamed ports would collide: {duplicate_targets}')
winners = {
target: source
for source, target in mapping.items()
if target is not None
}
overwritten = {
target
for target, source in winners.items()
if target in self.ports and target not in mapping and target != source
}
for kk, vv in mapping.items():
if vv is None or vv != kk:
self._log_port_removal(kk)
renamed = {vv: self.ports.pop(kk) for kk, vv in mapping.items()}
if None in renamed:
del renamed[None]
source_ports = {kk: self.ports.pop(kk) for kk in mapping}
for target in overwritten:
self.ports.pop(target, None)
renamed = {
vv: source_ports[kk]
for kk, vv in mapping.items()
if vv is not None and winners[vv] == kk
}
self.ports.update(renamed) # type: ignore
for vv in mapping.values():
if vv is not None:
self._log_port_update(vv)
return self
for vv in winners:
self._log_port_update(vv)
return winners
def add_port_pair(
self,
@ -494,54 +608,7 @@ class PortList(metaclass=ABCMeta):
`PortError` if there are any duplicate names after `map_in` and `map_out`
are applied.
"""
if map_in is None:
map_in = {}
if map_out is None:
map_out = {}
other = set(other_names)
missing_inkeys = set(map_in.keys()) - set(self.ports.keys())
if missing_inkeys:
raise PortError(f'`map_in` keys not present in device: {missing_inkeys}')
missing_invals = set(map_in.values()) - other
if missing_invals:
raise PortError(f'`map_in` values not present in other device: {missing_invals}')
map_in_counts = Counter(map_in.values())
conflicts_in = {kk for kk, vv in map_in_counts.items() if vv > 1}
if conflicts_in:
raise PortError(f'Duplicate values in `map_in`: {conflicts_in}')
missing_outkeys = set(map_out.keys()) - other
if missing_outkeys:
raise PortError(f'`map_out` keys not present in other device: {missing_outkeys}')
connected_outkeys = set(map_out.keys()) & set(map_in.values())
if connected_outkeys:
raise PortError(f'`map_out` keys conflict with connected ports: {connected_outkeys}')
orig_remaining = set(self.ports.keys()) - set(map_in.keys())
other_remaining = other - set(map_out.keys()) - set(map_in.values())
mapped_vals = set(map_out.values())
mapped_vals.discard(None)
conflicts_final = orig_remaining & (other_remaining | mapped_vals)
if conflicts_final:
raise PortError(f'Device ports conflict with existing ports: {conflicts_final}')
conflicts_partial = other_remaining & mapped_vals
if conflicts_partial:
raise PortError(f'`map_out` targets conflict with non-mapped outputs: {conflicts_partial}')
map_out_counts = Counter(map_out.values())
map_out_counts[None] = 0
conflicts_out = {kk for kk, vv in map_out_counts.items() if vv > 1}
if conflicts_out:
raise PortError(f'Duplicate targets in `map_out`: {conflicts_out}')
self._resolve_insert_mapping(other_names, map_in, map_out)
return self
def find_transform(