[library / gdsii] more cleanup

This commit is contained in:
Jan Petykiewicz 2026-07-13 21:40:10 -07:00
commit e7f7cae8b9
4 changed files with 40 additions and 1 deletions

View file

@ -47,5 +47,9 @@ class IBorrowing(ABC):
The source may use a different name, which is returned alongside it. The source may use a different name, which is returned alongside it.
Port metadata may differ because ports are not layout-file content. Port metadata may differ because ports are not layout-file content.
The result is not recursively resolved: consumers must follow further
borrowing views themselves and enforce format-specific constraints such
as whether the visible and source names must match. `None` means the
source cannot be reused safely or no source provenance is available.
""" """
return None return None

View file

@ -70,6 +70,7 @@ class _SubtreeLibraryView(ILibraryView, IMaterializable, IBorrowing):
name: set(child_graph.get(name, set())) name: set(child_graph.get(name, set()))
for name in self._order for name in self._order
} }
def __getitem__(self, key: str) -> Pattern: def __getitem__(self, key: str) -> Pattern:
if key not in self._names: if key not in self._names:
raise KeyError(key) raise KeyError(key)
@ -136,6 +137,7 @@ class _SubtreeLibraryView(ILibraryView, IMaterializable, IBorrowing):
refs = self._source.find_refs_local(name, parent_graph=parent_graph, dangling=dangling) refs = self._source.find_refs_local(name, parent_graph=parent_graph, dangling=dangling)
return {parent: transforms for parent, transforms in refs.items() if parent in self._names} return {parent: transforms for parent, transforms in refs.items() if parent in self._names}
class Library(ILibrary): class Library(ILibrary):
""" """
Default implementation for a writeable library. Default implementation for a writeable library.

View file

@ -79,6 +79,7 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
self._replace = replace self._replace = replace
self._cache: dict[str, Pattern] = {} self._cache: dict[str, Pattern] = {}
self._lookups_in_progress: list[str] = [] self._lookups_in_progress: list[str] = []
def __getitem__(self, key: str) -> Pattern: def __getitem__(self, key: str) -> Pattern:
return self.materialize(key, persist=True) return self.materialize(key, persist=True)
@ -160,6 +161,7 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
return cast('dict[str, list[NDArray[numpy.float64]]]', finder(name, parent_graph=parent_graph, dangling=dangling)) return cast('dict[str, list[NDArray[numpy.float64]]]', finder(name, parent_graph=parent_graph, dangling=dangling))
return super().find_refs_local(name, parent_graph=parent_graph, dangling=dangling) return super().find_refs_local(name, parent_graph=parent_graph, dangling=dangling)
class OverlayLibrary(ILibrary, IMaterializable, IBorrowing): class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
""" """
Mutable overlay over one or more source libraries. Mutable overlay over one or more source libraries.

View file

@ -7,10 +7,11 @@ from numpy.testing import assert_allclose
from ..file import gdsii from ..file import gdsii
from ..file.gdsii import lazy as gdsii_lazy from ..file.gdsii import lazy as gdsii_lazy
from ..file.gdsii import lazy_write as gdsii_lazy_write
from ..error import LibraryError from ..error import LibraryError
from ..pattern import Pattern from ..pattern import Pattern
from ..ports import Port from ..ports import Port
from ..library import IBorrowing, IMaterializable, LazyLibrary, Library, OverlayLibrary, PortsLibraryView from ..library import IBorrowing, IMaterializable, LazyLibrary, Library, LibraryView, OverlayLibrary, PortsLibraryView
def _make_lazy_port_library() -> Library: def _make_lazy_port_library() -> Library:
@ -76,6 +77,19 @@ def test_gdsii_lazy_write_materializes_transiently() -> None:
assert info['name'] == 'transient' assert info['name'] == 'transient'
def test_gdsii_raw_copy_provenance_cycle_falls_back() -> None:
class SelfBorrowingView(LibraryView, IBorrowing):
def borrowed_sources(self) -> tuple['SelfBorrowingView', ...]:
return (self,)
def source_cell(self, name: str) -> tuple['SelfBorrowingView', str] | None:
return self, name
view = SelfBorrowingView({'top': Pattern()})
assert gdsii_lazy_write._resolve_raw_struct(view, 'top') is None
def test_gdsii_lazy_source_exposes_order_and_graph_without_materializing(tmp_path: Path) -> None: def test_gdsii_lazy_source_exposes_order_and_graph_without_materializing(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_source.gds' gds_file = tmp_path / 'lazy_source.gds'
src = _make_lazy_port_library() src = _make_lazy_port_library()
@ -147,6 +161,23 @@ def test_gdsii_lazy_subtree_stays_borrowed_and_preserves_write_metadata(tmp_path
assert set(roundtrip) == {'leaf', 'child', 'top'} assert set(roundtrip) == {'leaf', 'child', 'top'}
def test_gdsii_lazy_borrowed_sources_require_matching_units(tmp_path: Path) -> None:
gds_a = tmp_path / 'units_a.gds'
gds_b = tmp_path / 'units_b.gds'
source = Library({'top': Pattern()})
gdsii.writefile(source, gds_a, meters_per_unit=1e-9, library_name='units-a')
gdsii.writefile(source, gds_b, meters_per_unit=2e-9, library_name='units-b')
lazy_a, _ = gdsii_lazy.readfile(gds_a)
lazy_b, _ = gdsii_lazy.readfile(gds_b)
overlay = OverlayLibrary()
overlay.add_source(lazy_a)
overlay.add_source(lazy_b, rename_theirs=lambda lib, name: lib.get_name(name))
with pytest.raises(LibraryError, match='identical units'):
gdsii_lazy.write(overlay, io.BytesIO())
def test_gdsii_lazy_overlay_subtree_preserves_source_laziness(tmp_path: Path) -> None: def test_gdsii_lazy_overlay_subtree_preserves_source_laziness(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_overlay_subtree_source.gds' gds_file = tmp_path / 'lazy_overlay_subtree_source.gds'
src = _make_lazy_port_library() src = _make_lazy_port_library()