Skip to content

Using Generic Exceptions (ExceptionGroups in particular) with raises in a type-safe manner is not ideal #13115

Open
@tapetersen

Description

As I understand the arguments for not extending pytest.raises to automatically unwrap ExceptionGroupsas 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.

Metadata

Assignees

No one assigned

    Labels

    topic: typingtype-annotation issuetype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions