Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tapetersen opened this issue Jan 7, 2025 · 1 comment
Labels
topic: typing type-annotation issue type: bug problem that needs to be addressed

Comments

@tapetersen
Copy link

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.

@tapetersen tapetersen changed the title Using Generic Exceptions (ExceptionGroups in particular) with raises in a type-safe manner is not ideal. Using Generic Exceptions (ExceptionGroups in particular) with raises in a type-safe manner is not ideal Jan 7, 2025
@Zac-HD Zac-HD added type: bug problem that needs to be addressed topic: typing type-annotation issue labels Jan 9, 2025
@Zac-HD
Copy link
Member

Zac-HD commented Jan 9, 2025

Yep, I think calling get_origin() on generics to 'unwrap' the bare type as part of this code would be a great fix. Would you be interested in opening a PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: typing type-annotation issue type: bug problem that needs to be addressed
Projects
None yet
Development

No branches or pull requests

2 participants