{"id":"cadbd03a070c65c9","repo":"pytest-dev/pytest","slug":"self-r-has-no-valid-result","errorCode":null,"errorMessage":"{self!r} has no valid result","messagePattern":"(.+?) has no valid result","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/runner.py","lineNumber":334,"sourceCode":"        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)\n        self._result = result\n        self.excinfo = excinfo\n        self.start = start\n        self.stop = stop\n        self.duration = duration\n        self.when = when\n\n    @property\n    def result(self) -> TResult:\n        \"\"\"The return value of the call, if it didn't raise.\n\n        Can only be accessed if excinfo is None.\n        \"\"\"\n        if self.excinfo is not None:\n            raise AttributeError(f\"{self!r} has no valid result\")\n        # The cast is safe because an exception wasn't raised, hence\n        # _result has the expected function return type (which may be\n        #  None, that's why a cast and not an assert).\n        return cast(TResult, self._result)\n\n    @classmethod\n    def from_call(\n        cls,\n        func: Callable[[], TResult],\n        when: Literal[\"collect\", \"setup\", \"call\", \"teardown\"],\n        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,\n    ) -> CallInfo[TResult]:\n        \"\"\"Call func, wrapping the result in a CallInfo.\n\n        :param func:\n            The function to call. Called without arguments.\n        :type func: Callable[[], _pytest.runner.TResult]\n        :param when:","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/runner.py#L316-L352","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Guard the access: read call.result only inside `if call.excinfo is None:` blocks.","If you only need the exception, use call.excinfo directly instead of call.result.","When wrapping via from_call, pass the reraise= argument so expected exceptions propagate instead of being stored as excinfo.","Update to a newer version of the offending plugin; this is almost always a plugin bug, not a test bug."],"exampleFix":"// before\nfrom _pytest.runner import CallInfo\ndef my_hook(call: CallInfo):\n    value = call.result  # raises AttributeError on failure\n\n// after\nfrom _pytest.runner import CallInfo\ndef my_hook(call: CallInfo):\n    if call.excinfo is not None:\n        return  # or handle call.excinfo\n    value = call.result","handlingStrategy":"type-guard","validationCode":"from _pytest.runner import CallInfo\n\ndef safe_result(call: CallInfo):\n    if call.excinfo is not None:\n        raise call.excinfo.value\n    return call.result","typeGuard":"from typing import TypeGuard\nfrom _pytest.runner import CallInfo\n\ndef call_succeeded(call: CallInfo) -> TypeGuard[CallInfo]:\n    return call.excinfo is None","tryCatchPattern":"try:\n    value = call.result\nexcept AttributeError:\n    # call raised; inspect call.excinfo instead\n    assert call.excinfo is not None\n    handle(call.excinfo.value)","preventionTips":["Always branch on call.excinfo before touching call.result.","Prefer the reraise= argument to from_call so expected exceptions propagate rather than becoming excinfo.","Treat AttributeError from .result as a programming error in your hook, not a runtime condition."],"tags":["plugin-api","callinfo","internal-api","pytest-runner"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}