From 3fac2b8ad1498cca61fbb948e7f5caeef5774886 Mon Sep 17 00:00:00 2001 From: Oliver Bristow Date: Sat, 9 May 2026 12:12:38 +0100 Subject: [PATCH 1/2] Tighten cross-owner error test expectations --- alternative.py | 10 ++++++++++ test_alternative.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/alternative.py b/alternative.py index 20b749d..bf6969e 100644 --- a/alternative.py +++ b/alternative.py @@ -40,6 +40,10 @@ class MultipleDefaults(AlternativeError): """Cannot set the default implementation more than once.""" +class CrossAlternativesImplementation(AlternativeError): + """Cannot add an Implementation object that belongs to a different Alternatives set.""" + + def get_caller_path() -> str | None: """ Return 'module.QualName (file.py:line)' pointing to the line @@ -158,6 +162,12 @@ def wrapper( label = maybe_get_caller_path() if not isinstance(implementation, Implementation): imp = Implementation(self, implementation, label=label) + elif implementation.alternatives is not self: + raise CrossAlternativesImplementation( + f"Cannot add {implementation!r} to {self.reference!r}; " + "it belongs to a different Alternatives set. " + "Pass implementation.implementation to clone explicitly." + ) else: imp = Implementation(self, implementation.implementation, label=label) diff --git a/test_alternative.py b/test_alternative.py index 64c2690..8b6a735 100644 --- a/test_alternative.py +++ b/test_alternative.py @@ -243,12 +243,39 @@ def f2(): # alt1 comes from a different set of alternatives of f2 assert isinstance(alt1, alternative.Implementation) - assert f2.add(alt1).alternatives is f2 + with pytest.raises(alternative.CrossAlternativesImplementation): + f2.add(alt1) - # when duplicating an implementation, a new Implementation object is returned + # adding an implementation to its own alternatives clones the wrapper assert f1.add(alt1) is not alt1 +def test_cross_owner_add_error(): + """Adding a cross-owner implementation raises a dedicated explicit error.""" + + @alternative.reference + def source(): + return 1 + + @source.add + def source_alt(): + return 2 + + @alternative.reference + def target(): + return 3 + + expected = ( + r"^Cannot add " + r"Implementation\(test_cross_owner_add_error\.\.source_alt(?:, label='[^']+')?\) " + r"to Implementation\(test_cross_owner_add_error\.\.target(?:, label='[^']+')?\); " + r"it belongs to a different Alternatives set\. " + r"Pass implementation\.implementation to clone explicitly\.$" + ) + with pytest.raises(alternative.CrossAlternativesImplementation, match=expected): + target.add(source_alt) + + def test_implementation_label_populated_in_debug(monkeypatch): """Implementation labels include caller metadata in debug mode.""" monkeypatch.setattr(alternative, "DEBUG", True) From 620b06b52d631daa646f994cf3152ae594684d47 Mon Sep 17 00:00:00 2001 From: Oliver Bristow Date: Sat, 9 May 2026 12:16:40 +0100 Subject: [PATCH 2/2] Remove stale FIXME for cross-owner guard --- alternative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alternative.py b/alternative.py index bf6969e..0897254 100644 --- a/alternative.py +++ b/alternative.py @@ -151,7 +151,7 @@ def add( raise AddTooLate(msg) if isinstance(implementation, _UNDEFINED): - # FIXME: handle when implementation is for a different set of alternatives + def wrapper( implementation: ImplementationSig[P, R], ) -> Implementation[P, R]: