Using Generic Exceptions (ExceptionGroups in particular) with raises in a type-safe manner is not ideal #13115
Description
As I understand the arguments for not extending pytest.raises
to automatically unwrap ExceptionGroups
as discussed in #11538 I've tried to use the ExceptionInfo.group_contains()
method which works well enough for the runtime parts.
However since the return value of raises()
( ExceptionInfo
) is generic in the exception type, the stricter type-checkers will complain about the unknown type-argument of the ExceptionGroup
.
# pyright: strict
import pytest
import sys
from typing import cast
if sys.version_info < (3, 11):
from exceptiongroup import ExceptionGroup
class FooError(Exception):
pass
# Pyright will report an error for the code below as:
# Type of "exc_group_info" is partially unknown
# Type of "exc_group_info" is "ExceptionInfo[ExceptionGroup]"PylancereportUnknownVariableType
with pytest.raises(ExceptionGroup) as exc_group_info:
raise ExceptionGroup("Some error occured", [FooError("Error")])
assert exc_group_info.group_contains(FooError)
If trying to rectify this by supplying the type-argument it fails various checks in raises
that checks that the argument is an instance of type
which GenericAlias
(that you get when indexing a generic class) is not.
# If the type is added pyright is ok but the runtime is not:
# Traceback (most recent call last):
# File ".../test.py", line 21, in <module>
# with pytest.raises(ExceptionGroup[ExceptionGroup[FooError]]) as exc_group_info:
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# File ".../.venv/lib/python3.12/site-packages/_pytest/python_api.py", line 959, in raises
# raise TypeError(msg.format(not_a))
# TypeError: expected exception must be a BaseException type, not GenericAlias
with pytest.raises(ExceptionGroup[ExceptionGroup[FooError]]) as exc_group_info:
raise ExceptionGroup("Some error occured", [FooError("Error")])
assert exc_group_info.group_contains(FooError)
The correct casting that is required to get this correct would have to be something like below.
# With the cast we can get it to work but that is quite verbose and doesn't
# catch errors of BaseExceptionGroup vs ExceptionGroup in the cast for example.
with pytest.raises(cast(type[ExceptionGroup[FooError]], ExceptionGroup)) as exc_group_info:
raise ExceptionGroup("Some error occured", [FooError("Error")])
assert exc_group_info.group_contains(FooError)
This could probably be fixed by handling generic Exceptions (or at least ExceptionGroups) explicitly in raises using https://docs.python.org/3/library/typing.html#typing.get_origin.