[OverlayLibrary] enable renaming all cells during add_source with rename_when='always'

This commit is contained in:
Jan Petykiewicz 2026-06-19 21:34:16 -07:00
commit 08bbb10827
2 changed files with 58 additions and 4 deletions

View file

@ -17,7 +17,7 @@ Both the classic and Arrow-backed lazy GDS readers rely on these helpers.
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import IO, Any, cast from typing import IO, Any, Literal, cast
from collections import defaultdict from collections import defaultdict
from collections.abc import Callable, Iterator, Mapping, Sequence from collections.abc import Callable, Iterator, Mapping, Sequence
import copy import copy
@ -303,7 +303,23 @@ class OverlayLibrary(ILibrary):
source: Mapping[str, Pattern] | ILibraryView, source: Mapping[str, Pattern] | ILibraryView,
*, *,
rename_theirs: Callable[[ILibraryView, str], str] | None = None, rename_theirs: Callable[[ILibraryView, str], str] | None = None,
rename_when: Literal['conflict', 'always'] = 'conflict',
) -> dict[str, str]: ) -> dict[str, str]:
"""
Add a source-backed library layer.
Args:
rename_theirs: Function used to choose visible names for imported
source cells.
rename_when: If `'conflict'`, only conflicting names are renamed.
If `'always'`, every imported source name is passed through
`rename_theirs`.
"""
if rename_when not in ('conflict', 'always'):
raise ValueError(f'Unknown source rename mode: {rename_when!r}')
if rename_when == 'always' and rename_theirs is None:
raise TypeError('rename_theirs is required when rename_when="always"')
view = _coerce_library_view(source) view = _coerce_library_view(source)
source_order = list(view.source_order()) source_order = list(view.source_order())
child_graph = view.child_graph(dangling='include') child_graph = view.child_graph(dangling='include')
@ -314,12 +330,20 @@ class OverlayLibrary(ILibrary):
for name in source_order: for name in source_order:
visible = name visible = name
if visible in self._entries or visible in visible_to_source: renamed = False
if rename_when == 'always':
visible = cast('Callable[[ILibraryView, str], str]', rename_theirs)(self, name)
renamed = True
elif visible in self._entries or visible in visible_to_source:
if rename_theirs is None: if rename_theirs is None:
raise LibraryError(f'Conflicting name while adding source: {name!r}') raise LibraryError(f'Conflicting name while adding source: {name!r}')
visible = rename_theirs(self, name) visible = rename_theirs(self, name)
renamed = True
if visible in self._entries or visible in visible_to_source: if visible in self._entries or visible in visible_to_source:
if not renamed:
raise LibraryError(f'Conflicting name while adding source: {name!r}')
raise LibraryError(f'Unresolved duplicate key encountered while adding source: {name!r} -> {visible!r}') raise LibraryError(f'Unresolved duplicate key encountered while adding source: {name!r} -> {visible!r}')
if visible != name:
rename_map[name] = visible rename_map[name] = visible
source_to_visible[name] = visible source_to_visible[name] = visible
visible_to_source[visible] = name visible_to_source[visible] = name

View file

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
import numpy import numpy
import pytest
from numpy.testing import assert_allclose from numpy.testing import assert_allclose
from ..file import gdsii, gdsii_lazy from ..file import gdsii, gdsii_lazy
@ -82,6 +83,35 @@ def test_gdsii_lazy_overlay_add_source_stays_lazy_for_processed_view(tmp_path: P
assert set(abstract.ports) == {'A'} assert set(abstract.ports) == {'A'}
def test_gdsii_lazy_overlay_add_source_can_rename_every_source_cell() -> None:
src = _make_lazy_port_library()
overlay = gdsii_lazy.OverlayLibrary()
rename_map = overlay.add_source(
src,
rename_theirs=lambda _lib, name: f'mapped_{name}',
rename_when='always',
)
assert rename_map == {
'leaf': 'mapped_leaf',
'child': 'mapped_child',
'top': 'mapped_top',
}
assert tuple(overlay.keys()) == ('mapped_leaf', 'mapped_child', 'mapped_top')
assert 'mapped_leaf' in overlay['mapped_child'].refs
def test_gdsii_lazy_overlay_add_source_rename_when_validation() -> None:
src = _make_lazy_port_library()
with pytest.raises(TypeError, match='rename_theirs'):
gdsii_lazy.OverlayLibrary().add_source(src, rename_when='always')
with pytest.raises(ValueError, match='rename mode'):
gdsii_lazy.OverlayLibrary().add_source(src, rename_when='sometimes') # type: ignore[arg-type]
def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path: Path) -> None: def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path: Path) -> None:
gds_file = tmp_path / 'lazy_roundtrip.gds' gds_file = tmp_path / 'lazy_roundtrip.gds'
src = _make_lazy_port_library() src = _make_lazy_port_library()