[set_dead] improve handling of dead ports
This commit is contained in:
parent
429e687666
commit
84106dc355
5 changed files with 350 additions and 88 deletions
185
masque/ports.py
185
masque/ports.py
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue