Merge pull request #10901 from bluetech/exceptioninfo-from-exception
code: add `ExceptionInfo.from_exception`
This commit is contained in:
		
						commit
						5d1385320f
					
				| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
 | 
				
			||||||
 | 
					This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
 | 
				
			||||||
| 
						 | 
					@ -469,18 +469,41 @@ class ExceptionInfo(Generic[E]):
 | 
				
			||||||
        self._traceback = traceback
 | 
					        self._traceback = traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def from_exc_info(
 | 
					    def from_exception(
 | 
				
			||||||
        cls,
 | 
					        cls,
 | 
				
			||||||
        exc_info: Tuple[Type[E], E, TracebackType],
 | 
					        # Ignoring error: "Cannot use a covariant type variable as a parameter".
 | 
				
			||||||
 | 
					        # This is OK to ignore because this class is (conceptually) readonly.
 | 
				
			||||||
 | 
					        # See https://github.com/python/mypy/issues/7049.
 | 
				
			||||||
 | 
					        exception: E,  # type: ignore[misc]
 | 
				
			||||||
        exprinfo: Optional[str] = None,
 | 
					        exprinfo: Optional[str] = None,
 | 
				
			||||||
    ) -> "ExceptionInfo[E]":
 | 
					    ) -> "ExceptionInfo[E]":
 | 
				
			||||||
        """Return an ExceptionInfo for an existing exc_info tuple.
 | 
					        """Return an ExceptionInfo for an existing exception.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The exception must have a non-``None`` ``__traceback__`` attribute,
 | 
				
			||||||
 | 
					        otherwise this function fails with an assertion error. This means that
 | 
				
			||||||
 | 
					        the exception must have been raised, or added a traceback with the
 | 
				
			||||||
 | 
					        :py:meth:`~BaseException.with_traceback()` method.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param exprinfo:
 | 
					        :param exprinfo:
 | 
				
			||||||
            A text string helping to determine if we should strip
 | 
					            A text string helping to determine if we should strip
 | 
				
			||||||
            ``AssertionError`` from the output. Defaults to the exception
 | 
					            ``AssertionError`` from the output. Defaults to the exception
 | 
				
			||||||
            message/``__str__()``.
 | 
					            message/``__str__()``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .. versionadded:: 7.4
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            exception.__traceback__
 | 
				
			||||||
 | 
					        ), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__."
 | 
				
			||||||
 | 
					        exc_info = (type(exception), exception, exception.__traceback__)
 | 
				
			||||||
 | 
					        return cls.from_exc_info(exc_info, exprinfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def from_exc_info(
 | 
				
			||||||
 | 
					        cls,
 | 
				
			||||||
 | 
					        exc_info: Tuple[Type[E], E, TracebackType],
 | 
				
			||||||
 | 
					        exprinfo: Optional[str] = None,
 | 
				
			||||||
 | 
					    ) -> "ExceptionInfo[E]":
 | 
				
			||||||
 | 
					        """Like :func:`from_exception`, but using old-style exc_info tuple."""
 | 
				
			||||||
        _striptext = ""
 | 
					        _striptext = ""
 | 
				
			||||||
        if exprinfo is None and isinstance(exc_info[1], AssertionError):
 | 
					        if exprinfo is None and isinstance(exc_info[1], AssertionError):
 | 
				
			||||||
            exprinfo = getattr(exc_info[1], "msg", None)
 | 
					            exprinfo = getattr(exc_info[1], "msg", None)
 | 
				
			||||||
| 
						 | 
					@ -954,21 +977,13 @@ class FormattedExcinfo:
 | 
				
			||||||
            repr_chain += [(reprtraceback, reprcrash, descr)]
 | 
					            repr_chain += [(reprtraceback, reprcrash, descr)]
 | 
				
			||||||
            if e.__cause__ is not None and self.chain:
 | 
					            if e.__cause__ is not None and self.chain:
 | 
				
			||||||
                e = e.__cause__
 | 
					                e = e.__cause__
 | 
				
			||||||
                excinfo_ = (
 | 
					                excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
 | 
				
			||||||
                    ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
 | 
					 | 
				
			||||||
                    if e.__traceback__
 | 
					 | 
				
			||||||
                    else None
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                descr = "The above exception was the direct cause of the following exception:"
 | 
					                descr = "The above exception was the direct cause of the following exception:"
 | 
				
			||||||
            elif (
 | 
					            elif (
 | 
				
			||||||
                e.__context__ is not None and not e.__suppress_context__ and self.chain
 | 
					                e.__context__ is not None and not e.__suppress_context__ and self.chain
 | 
				
			||||||
            ):
 | 
					            ):
 | 
				
			||||||
                e = e.__context__
 | 
					                e = e.__context__
 | 
				
			||||||
                excinfo_ = (
 | 
					                excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
 | 
				
			||||||
                    ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
 | 
					 | 
				
			||||||
                    if e.__traceback__
 | 
					 | 
				
			||||||
                    else None
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                descr = "During handling of the above exception, another exception occurred:"
 | 
					                descr = "During handling of the above exception, another exception occurred:"
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                e = None
 | 
					                e = None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -950,11 +950,7 @@ def raises(  # noqa: F811
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            func(*args[1:], **kwargs)
 | 
					            func(*args[1:], **kwargs)
 | 
				
			||||||
        except expected_exception as e:
 | 
					        except expected_exception as e:
 | 
				
			||||||
            # We just caught the exception - there is a traceback.
 | 
					            return _pytest._code.ExceptionInfo.from_exception(e)
 | 
				
			||||||
            assert e.__traceback__ is not None
 | 
					 | 
				
			||||||
            return _pytest._code.ExceptionInfo.from_exc_info(
 | 
					 | 
				
			||||||
                (type(e), e, e.__traceback__)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    fail(message)
 | 
					    fail(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,20 @@ def test_excinfo_from_exc_info_simple() -> None:
 | 
				
			||||||
    assert info.type == ValueError
 | 
					    assert info.type == ValueError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_excinfo_from_exception_simple() -> None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        raise ValueError
 | 
				
			||||||
 | 
					    except ValueError as e:
 | 
				
			||||||
 | 
					        assert e.__traceback__ is not None
 | 
				
			||||||
 | 
					        info = _pytest._code.ExceptionInfo.from_exception(e)
 | 
				
			||||||
 | 
					    assert info.type == ValueError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_excinfo_from_exception_missing_traceback_assertion() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(AssertionError, match=r"must have.*__traceback__"):
 | 
				
			||||||
 | 
					        _pytest._code.ExceptionInfo.from_exception(ValueError())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_excinfo_getstatement():
 | 
					def test_excinfo_getstatement():
 | 
				
			||||||
    def g():
 | 
					    def g():
 | 
				
			||||||
        raise ValueError
 | 
					        raise ValueError
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue