[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

@ -599,17 +599,23 @@ old Nx4 form.
`BuildReport` mapping fields are now defensively copied and read-only. Copy a
field to a new `dict` before adding or removing report entries.
`PortsLibraryView`, `OverlayLibrary`, and source-backed outputs returned by
`LibraryBuilder.build()` borrow their sources. They do not close source resources, and
`PortsLibraryView` is not a context manager. Keep each lazy source open and
unchanged until every borrowing view or overlay is finished, then close the
owning source explicitly. An eager `build(output='library')` result is detached
and does not need its sources afterward.
`PortLoadView`, `LayerMappedView`, `OverlayLibrary`, and source-backed outputs
returned by `LibraryBuilder.build()` borrow their sources. They do not close
source resources, and the views are not context managers. Keep each lazy
source open and unchanged until every borrowing view or overlay is finished,
then close the owning source explicitly. An eager `build(output='library')`
result is detached and does not need its sources afterward.
Lazy GDS sources no longer provide `with_ports_from_data()` or
`with_port_overrides()` convenience methods. Construct the generic view
directly instead: `PortsLibraryView(source, layers=...)` imports port data, and
`PortsLibraryView(source, ports=..., replace=...)` applies explicit overrides.
directly instead: `PortLoadView(source, layers=...)` imports port data, and
`PortLoadView(source, ports=..., replace=...)` applies explicit overrides.
`LayerMappedView(source, map_layer)` lazily remaps shape and label layers while
leaving the source untouched. Its default `copy_through=False` forces mapped
serialization of every cell. Set `copy_through=True` only when source-aware
writers may copy untouched cells unchanged and intentionally skip their layer
mapping; persistently accessed cells are mapped and no longer copied through.
Generic borrowing views no longer expose GDS-specific `raw_struct_bytes()`,
`can_copy_raw_struct()`, or forwarded `library_info` attributes. Keep the
@ -697,11 +703,12 @@ through `source_cell()`; format writers decide whether that provenance permits
raw copy-through. Raw GDS structure access remains confined to GDS sources and
writers.
`LibraryBuilder`, `OverlayLibrary`, and `PortsLibraryView` are new additive
library implementations. `LibraryBuilder` supports declarative `@cell` recipes
and dependency-aware builds; `OverlayLibrary` composes source libraries without
eagerly copying all patterns; `PortsLibraryView` overlays port metadata on a
read-only source.
`LibraryBuilder`, `OverlayLibrary`, `PortLoadView`, and `LayerMappedView` are new
additive library implementations. `LibraryBuilder` supports declarative `@cell`
recipes and dependency-aware builds; `OverlayLibrary` composes source libraries
without eagerly copying all patterns; `PortLoadView` loads port metadata from
labels and/or explicit mappings; `LayerMappedView` remaps shape and label layers
on detached materialization.
Flattening with `flatten_ports=True` now rejects repeated refs whose target has
ports, because expanding them would create duplicate port names. Resolve the
@ -797,7 +804,7 @@ scikit-image implementation. The `arrow` and `boolean` extras are also new.
These are additive, but available now from `masque` and `masque.builder`:
- from `masque`: `RectCollection`, `boolean`, `OverlayLibrary`,
`PortsLibraryView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
`PortLoadView`, `LayerMappedView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
`BuildReport`, `CellProvenance`, and `cell`
- from `masque.builder`: `CostCallable`, `RenderStepKind`, the concrete
primitive-offer classes, structured route error/status types,

View file

@ -11,7 +11,7 @@ from typing import Any
from pprint import pformat
from masque import ILibrary, LibraryBuilder, Pather, Pattern, PortsLibraryView, cell
from masque import ILibrary, LibraryBuilder, Pather, Pattern, PortLoadView, cell
from masque.file.gdsii import writefile
from masque.file.gdsii.lazy import readfile
@ -53,7 +53,7 @@ def main() -> None:
# imported on first materialization, but the raw source remains untouched
# until we build the final library.
gds_lib, _properties = readfile('circuit.gds')
builder.add_source(PortsLibraryView(gds_lib, layers=[(3, 0)], max_depth=1))
builder.add_source(PortLoadView(gds_lib, layers=[(3, 0)], max_depth=1))
print('Registered imported cells:\n' + pformat(list(gds_lib.keys())))

View file

@ -67,7 +67,8 @@ from .library import (
LibraryView as LibraryView,
Library as Library,
OverlayLibrary as OverlayLibrary,
PortsLibraryView as PortsLibraryView,
PortLoadView as PortLoadView,
LayerMappedView as LayerMappedView,
LibraryBuilder as LibraryBuilder,
BuildReport as BuildReport,
CellProvenance as CellProvenance,

View file

@ -7,7 +7,7 @@ This module provides the non-Arrow half of Masque's lazy GDS architecture:
struct order, and child edges without materializing every cell.
- cells are materialized on demand through the classic `gdsii` decoder
whenever a caller indexes the lazy view
- the source can be wrapped in `PortsLibraryView` or merged through
- the source can be wrapped in `PortLoadView` or merged through
`OverlayLibrary`
The public surface intentionally parallels `gdsii.lazy_arrow` closely so that

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.

View file

@ -11,7 +11,10 @@ from ..file.gdsii import lazy_write as gdsii_lazy_write
from ..error import LibraryError
from ..pattern import Pattern
from ..ports import Port
from ..library import IBorrowing, IMaterializable, LazyLibrary, Library, LibraryView, OverlayLibrary, PortsLibraryView
from ..library import (
IBorrowing, IMaterializable, LayerMappedView, LazyLibrary, Library, LibraryView,
OverlayLibrary, PortLoadView,
)
def _make_lazy_port_library() -> Library:
@ -201,13 +204,13 @@ def test_gdsii_lazy_overlay_subtree_preserves_source_laziness(tmp_path: Path) ->
assert set(roundtrip) == {'leaf', 'child', 'top'}
def test_gdsii_lazy_ports_view_keeps_raw_source_unmodified(tmp_path: Path) -> None:
def test_gdsii_lazy_port_load_view_keeps_raw_source_unmodified(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_ports.gds'
src = _make_lazy_port_library()
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-ports')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
assert not hasattr(processed, 'library_info')
top = processed['top']
@ -219,14 +222,14 @@ def test_gdsii_lazy_ports_view_keeps_raw_source_unmodified(tmp_path: Path) -> No
assert not raw_top.ports
def test_gdsii_lazy_ports_view_detaches_previously_cached_source(tmp_path: Path) -> None:
def test_gdsii_lazy_port_load_view_detaches_previously_cached_source(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_cached_ports.gds'
src = _make_lazy_port_library()
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-cached-ports')
raw, _ = gdsii_lazy.readfile(gds_file)
raw_top = raw['top']
processed = PortsLibraryView(raw, ports={
processed = PortLoadView(raw, ports={
'top': {
'P': Port((1, 2), rotation=0, ptype='wire'),
},
@ -247,7 +250,7 @@ def test_gdsii_lazy_port_overrides_without_data_stay_lazy(tmp_path: Path) -> Non
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overrides')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(raw, ports={
processed = PortLoadView(raw, ports={
'top': {
'P': Port((1, 2), rotation=0, ptype='wire'),
},
@ -270,7 +273,7 @@ def test_gdsii_lazy_port_overrides_apply_after_extraction(tmp_path: Path) -> Non
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-override-extracted')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(
processed = PortLoadView(
raw,
layers=[(10, 0)],
max_depth=2,
@ -299,7 +302,7 @@ def test_gdsii_lazy_port_overrides_replace_extracted_ports(tmp_path: Path) -> No
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-replace-ports')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(
processed = PortLoadView(
raw,
layers=[(10, 0)],
max_depth=2,
@ -323,7 +326,7 @@ def test_gdsii_lazy_overlay_add_source_stays_lazy_for_processed_view(tmp_path: P
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
overlay = OverlayLibrary()
overlay.add_source(processed)
@ -341,7 +344,7 @@ def test_gdsii_lazy_overlay_add_source_sees_port_overrides(tmp_path: Path) -> No
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay-override')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(raw, ports={
processed = PortLoadView(raw, ports={
'top': {
'P': Port((1, 2), rotation=0, ptype='wire'),
},
@ -393,7 +396,7 @@ def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path:
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-roundtrip')
raw, _ = gdsii_lazy.readfile(gds_file)
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
out_file = tmp_path / 'lazy_roundtrip_out.gds'
gdsii_lazy.writefile(processed, out_file)
@ -401,6 +404,23 @@ def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path:
assert out_file.read_bytes() == gds_file.read_bytes()
def test_gdsii_lazy_layer_mapped_write_materializes_without_source_cache(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_layer_source.gds'
src = _make_lazy_port_library()
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-layers')
raw, _ = gdsii_lazy.readfile(gds_file)
mapped = LayerMappedView(raw, lambda layer: (20, 0) if layer == (10, 0) else layer)
out_file = tmp_path / 'lazy_layer_mapped.gds'
gdsii_lazy.writefile(mapped, out_file)
assert not raw._cache
roundtrip, info = gdsii.readfile(out_file)
assert info['name'] == 'classic-layers'
assert set(roundtrip['leaf'].labels) == {(20, 0)}
assert (10, 0) not in roundtrip['leaf'].labels
def test_gdsii_removed_closure_based_lazy_loader() -> None:
assert not hasattr(gdsii, 'load_library')
assert not hasattr(gdsii, 'load_libraryfile')

View file

@ -10,7 +10,7 @@ import pytest
pytest.importorskip('pyarrow')
from .. import PatternError
from ..library import IBorrowing, IMaterializable, Library, OverlayLibrary, PortsLibraryView
from ..library import IBorrowing, IMaterializable, LayerMappedView, Library, OverlayLibrary, PortLoadView
from ..pattern import Pattern
from ..repetition import Grid
from ..file import gdsii
@ -240,7 +240,7 @@ def test_gdsii_raw_copy_resolves_generic_borrowing_views(tmp_path: Path, monkeyp
gdsii.writefile(_make_small_library(), gds_file, meters_per_unit=1e-9, library_name='provenance')
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
ports = PortsLibraryView(raw)
ports = PortLoadView(raw)
subtree = ports.subtree('top')
overlay = OverlayLibrary()
overlay.add_source(subtree)
@ -270,6 +270,55 @@ def test_gdsii_raw_copy_resolves_generic_borrowing_views(tmp_path: Path, monkeyp
assert gdsii_lazy_write._resolve_raw_struct(remapped, 'mid') is None
def test_gdsii_layer_mapped_view_controls_raw_copy_through(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
gds_file = tmp_path / 'layer_mapped_source.gds'
gdsii.writefile(_make_small_library(), gds_file, meters_per_unit=1e-9, library_name='layer-mapped')
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
copied: list[str] = []
raw_reader = raw.raw_struct_bytes
def record_raw_read(name: str) -> bytes:
copied.append(name)
return raw_reader(name)
def map_layer(layer): # noqa: ANN001,ANN202
return (20, 0) if layer == (1, 0) else layer
monkeypatch.setattr(raw, 'raw_struct_bytes', record_raw_read)
mapped = LayerMappedView(raw, map_layer)
mapped_file = tmp_path / 'layer_mapped_all.gds'
gdsii_lazy_arrow.writefile(mapped, mapped_file)
assert copied == []
assert not raw._cache
roundtrip, info = gdsii.readfile(mapped_file)
assert info['name'] == 'layer-mapped'
assert set(roundtrip['leaf'].shapes) == {(20, 0)}
passthrough = LayerMappedView(raw, map_layer, copy_through=True)
copied_file = tmp_path / 'layer_mapped_copied.gds'
gdsii_lazy_arrow.writefile(passthrough, copied_file)
assert copied == ['leaf', 'mid', 'top']
assert copied_file.read_bytes() == gds_file.read_bytes()
assert not raw._cache
copied.clear()
assert set(passthrough['leaf'].shapes) == {(20, 0)}
promoted_file = tmp_path / 'layer_mapped_promoted.gds'
gdsii_lazy_arrow.writefile(passthrough, promoted_file)
assert copied == ['mid', 'top']
assert not raw._cache
roundtrip, _ = gdsii.readfile(promoted_file)
assert set(roundtrip['leaf'].shapes) == {(20, 0)}
def test_gdsii_lazy_arrow_processed_cell_edit_disables_raw_copy(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@ -279,7 +328,7 @@ def test_gdsii_lazy_arrow_processed_cell_edit_disables_raw_copy(
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='processed-edit')
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
processed = PortsLibraryView(raw)
processed = PortLoadView(raw)
processed['top'].polygon((7, 0), vertices=[[0, 0], [4, 0], [0, 4]])
copied: list[str] = []

View file

@ -2,7 +2,10 @@ import pytest
from collections.abc import Mapping, MutableMapping
from typing import cast, TYPE_CHECKING
from numpy.testing import assert_allclose
from ..library import IBorrowing, INameView, IMaterializable, Library, LibraryView, LazyLibrary, OverlayLibrary, PortsLibraryView
from ..library import (
IBorrowing, INameView, IMaterializable, LayerMappedView, Library, LibraryView,
LazyLibrary, OverlayLibrary, PortLoadView,
)
from ..pattern import Pattern
from ..error import LibraryError, PatternError
from ..ports import Port
@ -540,11 +543,11 @@ def test_overlay_add_source_callback_sees_earlier_name_reservations() -> None:
assert set(rename_map.values()) <= set(overlay)
def test_ports_view_detaches_already_materialized_overlay_pattern() -> None:
def test_port_load_view_detaches_already_materialized_overlay_pattern() -> None:
overlay = OverlayLibrary()
overlay.add_source(Library({"top": Pattern()}))
raw = overlay["top"]
processed = PortsLibraryView(
processed = PortLoadView(
overlay,
ports={"top": {"P": Port((1, 2), 0)}},
)
@ -557,6 +560,77 @@ def test_ports_view_detaches_already_materialized_overlay_pattern() -> None:
assert not hasattr(processed, "close")
def test_layer_mapped_view_detaches_and_maps_shapes_and_labels() -> None:
import masque
child = Pattern()
source_pattern = Pattern(ports={'P': Port((1, 2), 0, ptype='wire')})
source_pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
source_pattern.polygon('B', vertices=[[2, 0], [3, 0], [2, 1]])
source_pattern.label('TEXT', string='port', offset=(1, 2))
source_pattern.ref('child')
source_pattern.annotations['note'] = ['unchanged']
source = Library({'child': child, 'top': source_pattern})
mapped = LayerMappedView(
source,
lambda layer: {'A': 'DRAW', 'B': 'DRAW', 'TEXT': 'LABEL'}.get(layer, layer),
)
mapped_top = mapped['top']
assert mapped_top is not source_pattern
assert set(mapped_top.shapes) == {'DRAW'}
assert len(mapped_top.shapes['DRAW']) == 2
assert set(mapped_top.labels) == {'LABEL'}
assert set(mapped_top.ports) == {'P'}
assert set(mapped_top.refs) == {'child'}
assert mapped_top.annotations == {'note': ['unchanged']}
assert set(source_pattern.shapes) == {'A', 'B'}
assert set(source_pattern.labels) == {'TEXT'}
assert mapped.child_graph() == source.child_graph()
assert mapped.source_order() == source.source_order()
assert masque.LayerMappedView is LayerMappedView
assert masque.PortLoadView is PortLoadView
assert not hasattr(masque, 'PortsLibraryView')
assert not hasattr(masque.library, 'PortsLibraryView')
def test_layer_mapped_view_materialization_and_copy_through() -> None:
pattern = Pattern()
pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
source = Library({'top': pattern})
mapped = LayerMappedView(source, lambda _layer: 'B')
passthrough = LayerMappedView(source, lambda _layer: 'B', copy_through=True)
assert mapped.source_cell('top') is None
assert passthrough.source_cell('top') == (source, 'top')
transient = passthrough.materialize('top', persist=False)
assert set(transient.shapes) == {'B'}
assert passthrough.source_cell('top') == (source, 'top')
persistent = passthrough['top']
assert passthrough['top'] is persistent
assert passthrough.source_cell('top') is None
assert set(persistent.shapes) == {'B'}
def test_layer_mapped_view_composes_with_ports_and_overlay() -> None:
pattern = Pattern()
pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
source = Library({'top': pattern})
ports = PortLoadView(source, ports={'top': {'P': Port((1, 2), 0)}})
mapped = LayerMappedView(ports, lambda _layer: 'B')
overlay = OverlayLibrary()
overlay.add_source(mapped)
top = overlay['top']
assert set(top.shapes) == {'B'}
assert set(top.ports) == {'P'}
assert mapped.borrowed_sources() == (ports,)
assert not pattern.ports
def test_library_subtree() -> None:
lib = Library()
lib["a"] = Pattern()
@ -655,7 +729,7 @@ def test_library_materialization_and_borrowing_capabilities() -> None:
overlay = OverlayLibrary()
overlay.add_source(lazy)
ports = PortsLibraryView(overlay)
ports = PortLoadView(overlay)
subtree = ports.subtree("top")
assert isinstance(overlay, IMaterializable)
@ -676,7 +750,7 @@ def test_library_materialization_and_borrowing_capabilities() -> None:
def test_borrowed_source_cell_tracks_persistent_materialization() -> None:
source = Library({"top": Pattern()})
source.library_info = {"name": "not-forwarded"} # type: ignore[attr-defined]
processed = PortsLibraryView(source)
processed = PortLoadView(source)
assert processed.source_cell("top") == (source, "top")
assert not hasattr(processed, "library_info")