diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index 668a8bbf..253f1dde 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -668,7 +668,10 @@ def __init__( self.trylast: Final = hook_impl_opts["trylast"] def __repr__(self) -> str: - return f"" + return ( + f"" + ) @final diff --git a/src/pluggy/_tracing.py b/src/pluggy/_tracing.py index 7be55493..41d9b939 100644 --- a/src/pluggy/_tracing.py +++ b/src/pluggy/_tracing.py @@ -3,11 +3,11 @@ """ from __future__ import annotations +import reprlib from typing import Any from typing import Callable from typing import Sequence from typing import Tuple -import reprlib _Writer = Callable[[str], object] @@ -60,6 +60,7 @@ def setprocessor(self, tags: str | tuple[str, ...], processor: _Processor) -> No assert isinstance(tags, tuple) self._tags2proc[tags] = processor + def _try_repr_or_str(obj: object) -> str: try: return repr(obj) @@ -68,6 +69,7 @@ def _try_repr_or_str(obj: object) -> str: except BaseException: return f'{type(obj).__name__}("{obj}")' + def _format_repr_exception(exc: BaseException, obj: object) -> str: try: exc_info = _try_repr_or_str(exc) @@ -79,20 +81,24 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str: exc_info, type(obj).__name__, id(obj) ) + def _ellipsize(s: str, maxsize: int) -> str: if len(s) > maxsize: i = max(0, (maxsize - 3) // 2) j = max(0, maxsize - 3 - i) - return s[:i] + "..." + s[len(s) - j :] + + x = len(s) - j + return s[:i] + "..." + s[x:] return s + class SafeRepr(reprlib.Repr): """ repr.Repr that limits the resulting size of repr() and includes information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -136,8 +142,10 @@ def repr_instance(self, x: object, level: int) -> str: # Maximum size of overall repr of objects to display during assertion errors. DEFAULT_REPR_MAX_SIZE = 240 + + def saferepr( - obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False ) -> str: """Return a size-limited safe repr-string for the given object. @@ -151,6 +159,7 @@ def saferepr( return SafeRepr(maxsize, use_ascii).repr(obj) + class TagTracerSub: def __init__(self, root: TagTracer, tags: tuple[str, ...]) -> None: self.root = root diff --git a/testing/benchmark.py b/testing/benchmark.py index 2860dfd4..ea13bfb2 100644 --- a/testing/benchmark.py +++ b/testing/benchmark.py @@ -3,14 +3,13 @@ """ import pytest +from ._tracing import saferepr from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager from pluggy._callers import _multicall from pluggy._hooks import HookImpl -from ._tracing import saferepr - hookspec = HookspecMarker("example") hookimpl = HookimplMarker("example") diff --git a/testing/test_hookcaller.py b/testing/test_hookcaller.py index 8a7d1aeb..b21902e6 100644 --- a/testing/test_hookcaller.py +++ b/testing/test_hookcaller.py @@ -450,20 +450,15 @@ def conflict(self) -> None: ".Api1'>" ) -def test_hookcaller_repr_with_saferepr_failure(hc: HookCaller, addmeth: AddMeth) -> None: - @addmeth() - def he_method1() -> None: - pass +def test_hookcaller_repr_with_saferepr_failure( + hc: HookCaller, addmeth: AddMeth +) -> None: @addmeth() def he_method2() -> None: # Intentional error to make the repr fail raise ValueError("Intentional error in he_method2") - @addmeth() - def he_method3() -> None: - pass - # Verify that HookCaller.repr with saferepr still works despite the error expected_repr = f"" - assert repr(hc) == expected_repr \ No newline at end of file + assert repr(hc) == expected_repr diff --git a/testing/test_tracer.py b/testing/test_tracer.py index b63637c6..46658486 100644 --- a/testing/test_tracer.py +++ b/testing/test_tracer.py @@ -2,7 +2,8 @@ import pytest -from pluggy._tracing import saferepr, DEFAULT_REPR_MAX_SIZE +from pluggy._tracing import DEFAULT_REPR_MAX_SIZE +from pluggy._tracing import saferepr from pluggy._tracing import TagTracer @@ -79,6 +80,7 @@ def test_setprocessor(rootlogger: TagTracer) -> None: tags, args = l2[0] assert args == ("seen",) + def test_saferepr_simple_repr(): assert saferepr(1) == "1" assert saferepr(None) == "None" @@ -110,10 +112,10 @@ def __repr__(self): def test_saferepr_exceptions() -> None: class BrokenRepr: - def __init__(self, ex): + def __init__(self, ex) -> None: self.ex = ex - def __repr__(self): + def __repr__(self) -> str: raise self.ex class BrokenReprException(Exception): @@ -143,10 +145,10 @@ def test_saferepr_baseexception(): """Test saferepr() with BaseExceptions, which includes pytest outcomes.""" class RaisingOnStrRepr(BaseException): - def __init__(self, exc_types): + def __init__(self, exc_types) -> None: self.exc_types = exc_types - def raise_exc(self, *args): + def raise_exc(self, *args) -> None: try: self.exc_type = self.exc_types.pop(0) except IndexError: @@ -155,17 +157,19 @@ def raise_exc(self, *args): raise self.exc_type(*args) raise self.exc_type - def __str__(self): + def __str__(self) -> str: self.raise_exc("__str__") + return "" - def __repr__(self): + def __repr__(self) -> str: self.raise_exc("__repr__") + return "" class BrokenObj: - def __init__(self, exc): + def __init__(self, exc) -> None: self.exc = exc - def __repr__(self): + def __repr__(self) -> str: raise self.exc __str__ = __repr__ @@ -247,4 +251,4 @@ def __repr__(self): assert saferepr(SomeClass()).startswith( "<[RuntimeError() raised in repr()] SomeClass object at 0x" - ) \ No newline at end of file + )