Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
22 changes: 17 additions & 5 deletions alternative.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions test_alternative.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,68 @@ 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.<locals>.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: "<unknown module>.<unknown> (<unknown location>)",
)

@alternative.reference
def f():
return 1

assert f.reference.label == "<unknown module>.<unknown> (<unknown location>)"


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.<locals>.alt)"
)
Loading