From 35b42c397b69714d01a88b730d7d894a9971e7ac Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 31 Mar 2026 22:37:16 -0700 Subject: [PATCH] [Pattern.append] don't dirty pattern if append() fails --- masque/pattern.py | 17 ++++++++++------- masque/test/test_pattern.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/masque/pattern.py b/masque/pattern.py index ab5f55a..287ca92 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -349,6 +349,16 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): Returns: self """ + annotation_conflicts: set[str] = set() + if other_pattern.annotations is not None and self.annotations is not None: + annotation_conflicts = set(self.annotations.keys()) & set(other_pattern.annotations.keys()) + if annotation_conflicts: + raise PatternError(f'Annotation keys overlap: {annotation_conflicts}') + + port_conflicts = set(self.ports.keys()) & set(other_pattern.ports.keys()) + if port_conflicts: + raise PatternError(f'Port names overlap: {port_conflicts}') + for target, rseq in other_pattern.refs.items(): self.refs[target].extend(rseq) for layer, sseq in other_pattern.shapes.items(): @@ -359,14 +369,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): if other_pattern.annotations is not None: if self.annotations is None: self.annotations = {} - annotation_conflicts = set(self.annotations.keys()) & set(other_pattern.annotations.keys()) - if annotation_conflicts: - raise PatternError(f'Annotation keys overlap: {annotation_conflicts}') self.annotations.update(other_pattern.annotations) - - port_conflicts = set(self.ports.keys()) & set(other_pattern.ports.keys()) - if port_conflicts: - raise PatternError(f'Port names overlap: {port_conflicts}') self.ports.update(other_pattern.ports) return self diff --git a/masque/test/test_pattern.py b/masque/test/test_pattern.py index e875641..59cc225 100644 --- a/masque/test/test_pattern.py +++ b/masque/test/test_pattern.py @@ -151,6 +151,33 @@ def test_pattern_interface() -> None: assert iface.ports["out_A"].ptype == "test" +def test_pattern_append_port_conflict_is_atomic() -> None: + pat1 = Pattern() + pat1.ports["A"] = Port((0, 0), 0) + + pat2 = Pattern() + pat2.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]]) + pat2.ports["A"] = Port((1, 0), 0) + + with pytest.raises(PatternError, match="Port names overlap"): + pat1.append(pat2) + + assert not pat1.shapes + assert set(pat1.ports) == {"A"} + + +def test_pattern_append_annotation_conflict_is_atomic() -> None: + pat1 = Pattern(annotations={"k": [1]}) + pat2 = Pattern(annotations={"k": [2]}) + pat2.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]]) + + with pytest.raises(PatternError, match="Annotation keys overlap"): + pat1.append(pat2) + + assert not pat1.shapes + assert pat1.annotations == {"k": [1]} + + def test_pattern_deepcopy_does_not_share_shape_repetitions() -> None: pat = Pattern() pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]], repetition=Grid(a_vector=(10, 0), a_count=2))