[PortLoadView / LayerMappedView] rename/add new views

This commit is contained in:
Jan Petykiewicz 2026-07-14 11:18:20 -07:00
commit cbf75319ad
9 changed files with 287 additions and 87 deletions

View file

@ -23,7 +23,8 @@ from .mapping import (
)
from .overlay import (
OverlayLibrary as OverlayLibrary,
PortsLibraryView as PortsLibraryView,
PortLoadView as PortLoadView,
LayerMappedView as LayerMappedView,
)
from .build import (
LibraryBuilder as LibraryBuilder,

View file

@ -1,4 +1,4 @@
"""Overlay and ports-importing library views."""
"""Overlay and lazily processed library views."""
from __future__ import annotations
from collections import defaultdict
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Literal, Self, cast
import copy
from ..error import LibraryError
from ..pattern import Pattern, map_targets
from ..pattern import Pattern, map_layers, map_targets
from .base import ILibrary, ILibraryView
from .capabilities import IBorrowing, IMaterializable
from .utils import INameView, dangling_mode_t, _plan_source_names, _source_rename_map, _validate_dangling_mode
@ -44,39 +44,17 @@ def _materialize_detached_pattern(view: ILibraryView, name: str) -> Pattern:
return view[name].deepcopy()
class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
"""
Read-only view which imports or applies ports on first materialization.
The wrapped source remains untouched; this view owns a separate processed
cache so direct-copy workflows can continue to use the raw source view.
The view borrows its source: callers must keep the source open for the
lifetime of the view and close the source themselves.
Graph queries and source ordering are delegated to the wrapped source,
while `source_cell()` exposes unchanged layout provenance and `__getitem__`
and `materialize_many()` return port-imported patterns.
"""
class _ProcessedLibraryView(ILibraryView, IMaterializable, IBorrowing):
"""Shared detached-materialization behavior for read-only processing views."""
def __init__(
self,
source: ILibraryView,
*,
layers: Sequence[layer_t] = (),
max_depth: int = 0,
skip_subcells: bool = True,
ports: Mapping[str, Mapping[str, Port]] | None = None,
replace: bool = False,
copy_through: bool,
) -> None:
self._source = source
self._layers = tuple(layers)
self._max_depth = max_depth
self._skip_subcells = skip_subcells
self._ports = {
name: copy.deepcopy(dict(cell_ports))
for name, cell_ports in (ports or {}).items()
}
self._replace = replace
self._copy_through = copy_through
self._cache: dict[str, Pattern] = {}
self._lookups_in_progress: list[str] = []
@ -92,9 +70,11 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
def __contains__(self, key: object) -> bool:
return key in self._source
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
from ..utils.ports2data import data_to_ports # noqa: PLC0415
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
"""Apply this view's processing to one detached source pattern."""
raise NotImplementedError
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
if name in self._cache:
return self._cache[name]
@ -108,28 +88,14 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
self._lookups_in_progress.append(name)
try:
pat = _materialize_detached_pattern(self._source, name)
if self._layers:
pat = data_to_ports(
layers=self._layers,
library=self,
pattern=pat,
name=name,
max_depth=self._max_depth,
skip_subcells=self._skip_subcells,
)
if name in self._ports:
ports = copy.deepcopy(self._ports[name])
if self._replace:
pat.ports = ports
else:
pat.ports.update(ports)
pattern = _materialize_detached_pattern(self._source, name)
pattern = self._process_pattern(name, pattern)
finally:
self._lookups_in_progress.pop()
if persist:
self._cache[name] = pat
return pat
self._cache[name] = pattern
return pattern
def source_order(self) -> tuple[str, ...]:
return self._source.source_order()
@ -138,7 +104,7 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
return (self._source,)
def source_cell(self, name: str) -> tuple[ILibraryView, str] | None:
if name not in self._source or name in self._cache:
if not self._copy_through or name not in self._source or name in self._cache:
return None
return self._source, name
@ -162,6 +128,88 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
return super().find_refs_local(name, parent_graph=parent_graph, dangling=dangling)
class PortLoadView(_ProcessedLibraryView):
"""
Read-only view which loads or applies ports on first materialization.
The wrapped source remains untouched; this view owns a separate processed
cache so direct-copy workflows can continue to use the raw source view.
The view borrows its source: callers must keep the source open for the
lifetime of the view and close the source themselves.
Graph queries and source ordering are delegated to the wrapped source,
while `source_cell()` exposes unchanged layout provenance and `__getitem__`
and `materialize_many()` return port-imported patterns.
"""
def __init__(
self,
source: ILibraryView,
*,
layers: Sequence[layer_t] = (),
max_depth: int = 0,
skip_subcells: bool = True,
ports: Mapping[str, Mapping[str, Port]] | None = None,
replace: bool = False,
) -> None:
super().__init__(source, copy_through=True)
self._layers = tuple(layers)
self._max_depth = max_depth
self._skip_subcells = skip_subcells
self._ports = {
name: copy.deepcopy(dict(cell_ports))
for name, cell_ports in (ports or {}).items()
}
self._replace = replace
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
from ..utils.ports2data import data_to_ports # noqa: PLC0415
if self._layers:
pattern = data_to_ports(
layers=self._layers,
library=self,
pattern=pattern,
name=name,
max_depth=self._max_depth,
skip_subcells=self._skip_subcells,
)
if name in self._ports:
ports = copy.deepcopy(self._ports[name])
if self._replace:
pattern.ports = ports
else:
pattern.ports.update(ports)
return pattern
class LayerMappedView(_ProcessedLibraryView):
"""
Read-only view which remaps shape and label layers on materialization.
The wrapped source remains untouched. By default, source-aware writers
must materialize and serialize every mapped cell. With `copy_through=True`,
unmaterialized cells may instead be copied unchanged from their source;
persistent access maps and caches a cell, disabling copy-through for it.
"""
def __init__(
self,
source: ILibraryView,
map_layer: Callable[[layer_t], layer_t],
*,
copy_through: bool = False,
) -> None:
super().__init__(source, copy_through=copy_through)
self._map_layer = map_layer
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
_ = name
pattern.shapes = map_layers(pattern.shapes, self._map_layer)
pattern.labels = map_layers(pattern.labels, self._map_layer)
return pattern
class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
"""
Mutable overlay over one or more source libraries.