From 9d5b91908e414f39edb07e7536a5198e6060de0d Mon Sep 17 00:00:00 2001 From: Oliver Bristow Date: Sat, 9 May 2026 10:34:36 +0100 Subject: [PATCH 1/2] Tighten label assertions and document debug labels --- README.md | 2 ++ alternative.py | 22 +++++++++++++++++----- test_alternative.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc63bdb..d52c68a 100644 --- a/README.md +++ b/README.md @@ -38,4 +38,6 @@ Running with `ALTERNATIVE_DEBUG=1` as an environment variable will result in the responsible for critical state changes like the point that a default implementation was selected or the implementations were inspected. These are then used in error messages which can be used to resolve stateful issues. +When debug mode is enabled, each `Implementation` also captures a label containing its registration call-site. This label is surfaced in `repr(...)` and selected debug errors to help disambiguate which implementation instance was involved. + For how this can tie in with equivalence checks and benchmarking in pytest, see the examples directory. \ No newline at end of file diff --git a/alternative.py b/alternative.py index 98f1601..20b749d 100644 --- a/alternative.py +++ b/alternative.py @@ -101,7 +101,7 @@ class Mutability(enum.IntEnum): class Alternatives[**P, R]: def __init__(self, implementation: Callable[P, R], *, default: bool = False): - imp = Implementation(self, implementation) + imp = Implementation(self, implementation, label=maybe_get_caller_path()) self.reference = imp # tracks the active implementation self._default: Implementation[P, R] | None = None @@ -155,16 +155,17 @@ def wrapper( return cast(ImplementationWrapper[P, R], wrapper) + label = maybe_get_caller_path() if not isinstance(implementation, Implementation): - imp = Implementation(self, implementation) + imp = Implementation(self, implementation, label=label) else: - imp = Implementation(self, implementation.implementation) + imp = Implementation(self, implementation.implementation, label=label) if default: if self._default is not None: # only allow explicitly setting the default implementation once if DEBUG: - msg = f"first default was specified at {self._debug_default}" + msg = f"first default was specified at {self._debug_default}; existing default={self._default!r}" else: msg = None raise MultipleDefaults(msg) @@ -375,8 +376,19 @@ def _select_parametrize_pairs( class Implementation[**P, R]: alternatives: Alternatives[P, R] implementation: Callable[P, R] + label: str | None = None - # TODO: add something like "label" which can be printed, e.g. "examples/test_measure.py:36" which would be a hyperlink in PyCharm + def __post_init__(self): + if self.label is None: + self.label = maybe_get_caller_path() + + def __repr__(self) -> str: + implementation_name = getattr( + self.implementation, "__qualname__", repr(self.implementation) + ) + if self.label: + return f"Implementation({implementation_name}, label={self.label!r})" + return f"Implementation({implementation_name})" def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: self.__call__ = self.implementation diff --git a/test_alternative.py b/test_alternative.py index 4229ce1..82df9a4 100644 --- a/test_alternative.py +++ b/test_alternative.py @@ -247,3 +247,49 @@ def f2(): # when duplicating an implementation, a new Implementation object is returned assert f1.add(alt1) is not alt1 + + +def test_implementation_label_populated_in_debug(monkeypatch): + """Implementation labels include caller metadata in debug mode.""" + monkeypatch.setattr(alternative, "DEBUG", True) + + @alternative.reference + def f(): + return 1 + + @f.add + def alt(): + return 2 + + expected_prefix = ( + f"test_alternative.test_implementation_label_populated_in_debug ({__file__}" + ) + + f_reference_prefix, _, f_reference_line = f.reference.label.rpartition(":") + assert f_reference_prefix == expected_prefix + assert f_reference_line[:-1].isdigit() + + alt_prefix, _, alt_line = alt.label.rpartition(":") + assert alt_prefix == expected_prefix + assert alt_line[:-1].isdigit() + + assert ( + repr(alt) + == f"Implementation(test_implementation_label_populated_in_debug..alt, label={alt.label!r})" + ) + + +def test_implementation_label_safe_when_caller_unavailable(monkeypatch): + """Implementation label uses fallback when caller information cannot be resolved.""" + monkeypatch.setattr(alternative, "DEBUG", True) + monkeypatch.setattr( + alternative, + "get_caller_path", + lambda: ". ()", + ) + + @alternative.reference + def f(): + return 1 + + assert f.reference.label == ". ()" From c1092eb4ffa9a57b16487304db98bbec8ce44696 Mon Sep 17 00:00:00 2001 From: Oliver Bristow Date: Sat, 9 May 2026 10:39:48 +0100 Subject: [PATCH 2/2] Add coverage for Implementation repr without label --- test_alternative.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test_alternative.py b/test_alternative.py index 82df9a4..64c2690 100644 --- a/test_alternative.py +++ b/test_alternative.py @@ -293,3 +293,22 @@ def f(): return 1 assert f.reference.label == ". ()" + + +def test_implementation_repr_without_label(monkeypatch): + """Implementation repr omits label when no label metadata is available.""" + monkeypatch.setattr(alternative, "DEBUG", False) + + @alternative.reference + def f(): + return 1 + + @f.add + def alt(): + return 2 + + assert alt.label is None + assert ( + repr(alt) + == "Implementation(test_implementation_repr_without_label..alt)" + )