pytest-dev/pytest · error · AttributeError

{self!r} has no valid result

Error message

{self!r} has no valid result

What it means

Raised by the CallInfo.result property when a plugin or hook accesses .result on a CallInfo whose underlying call actually raised an exception (excinfo is not None). CallInfo is pytest's internal wrapper for each test phase (setup/call/teardown) created via CallInfo.from_call; .result is only meaningful when the call succeeded. Accessing it after a failure is a contract violation on the plugin author's side.

Source

Thrown at src/_pytest/runner.py:334

        *,
        _ispytest: bool = False,
    ) -> None:
        check_ispytest(_ispytest)
        self._result = result
        self.excinfo = excinfo
        self.start = start
        self.stop = stop
        self.duration = duration
        self.when = when

    @property
    def result(self) -> TResult:
        """The return value of the call, if it didn't raise.

        Can only be accessed if excinfo is None.
        """
        if self.excinfo is not None:
            raise AttributeError(f"{self!r} has no valid result")
        # The cast is safe because an exception wasn't raised, hence
        # _result has the expected function return type (which may be
        #  None, that's why a cast and not an assert).
        return cast(TResult, self._result)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Guard the access: read call.result only inside `if call.excinfo is None:` blocks.
  2. If you only need the exception, use call.excinfo directly instead of call.result.
  3. When wrapping via from_call, pass the reraise= argument so expected exceptions propagate instead of being stored as excinfo.
  4. Update to a newer version of the offending plugin; this is almost always a plugin bug, not a test bug.

Example fix

// before
from _pytest.runner import CallInfo
def my_hook(call: CallInfo):
    value = call.result  # raises AttributeError on failure

// after
from _pytest.runner import CallInfo
def my_hook(call: CallInfo):
    if call.excinfo is not None:
        return  # or handle call.excinfo
    value = call.result
Defensive patterns

Strategy: type-guard

Validate before calling

from _pytest.runner import CallInfo

def safe_result(call: CallInfo):
    if call.excinfo is not None:
        raise call.excinfo.value
    return call.result

Type guard

from typing import TypeGuard
from _pytest.runner import CallInfo

def call_succeeded(call: CallInfo) -> TypeGuard[CallInfo]:
    return call.excinfo is None

Try / catch

try:
    value = call.result
except AttributeError:
    # call raised; inspect call.excinfo instead
    assert call.excinfo is not None
    handle(call.excinfo.value)

Prevention

When it happens

Trigger: A pytest hook or plugin calls CallInfo.from_call(...) and then unconditionally reads call.result without first checking call.excinfo. Reproduces whenever the wrapped func raises during the from_call invocation, because excinfo gets set and the property then refuses to hand back a (nonexistent) return value.

Common situations: Third-party plugins that wrap runtest protocol hooks and forget the excinfo check; test frameworks building on top of pytest that assume the call always succeeds; migrating plugins across pytest versions where the API became stricter about the result/excinfo invariant.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/cadbd03a070c65c9.json. Report an issue: GitHub.