pytest-dev/pytest · error · ValueError

Only `ExceptionGroup[Exception]` or…

Error message

Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` are accepted as generic types but got `{exc}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`.

What it means

Raised by RaisesExc._parse_exc when a parameterized exception group is passed to pytest.raises (or RaisesExc) with a generic argument that is not exactly Exception, BaseException, or Any. pytest intentionally rejects constrained generics like ExceptionGroup[ValueError] because pytest.raises catches the whole group regardless of the type argument, so the nested type would be silently ignored and produce misleading tests. Use RaisesGroup to assert on nested exception types.

Solutions

  1. Replace pytest.raises(ExceptionGroup[ValueError]) with pytest.raises(ExceptionGroup) wrapped in or followed by a RaisesGroup(ValueError) assertion to inspect nested exceptions.
  2. Use RaisesGroup(ValueError) directly as the raises argument to match a group whose leaf is ValueError.
  3. If you truly want to catch any ExceptionGroup, pass ExceptionGroup (bare) or ExceptionGroup[Any].

Example fix

// before
with pytest.raises(ExceptionGroup[ValueError]):
    ...
// after
with pytest.raises(RaisesGroup(ValueError)):
    ...
// or for the outer group catch plus inner check:
with pytest.raises(ExceptionGroup) as eg:
    ...
assert RaisesGroup(ValueError).matches(eg.value)
Defensive patterns

Strategy: validation

Validate before calling

import typing
origin = typing.get_origin(exc)
if origin is not None and issubclass(origin, BaseExceptionGroup):
    arg = typing.get_args(exc)[0]
    assert arg in (Exception, BaseException, typing.Any), (
        f'pytest.raises cannot constrain {origin.__name__}[{arg.__name__}]; '
        'use RaisesGroup for nested-type matching'
    )

Type guard

def is_valid_raises_group_generic(exc) -> bool:
    import typing
    origin = typing.get_origin(exc)
    if origin is None or not issubclass(origin, BaseExceptionGroup):
        return True
    arg = typing.get_args(exc)[0]
    return arg in (Exception, BaseException, typing.Any)

Prevention

When it happens

Trigger: Calling pytest.raises(ExceptionGroup[ValueError]), pytest.raises(BaseExceptionGroup[KeyboardInterrupt]), or RaisesExc(ExceptionGroup[KeyError]) — any BaseExceptionGroup subclass parameterized with a concrete non-Any, non-base exception type. The branch fires after get_origin(exc) returns a BaseExceptionGroup subclass and get_args(exc)[0] fails the Exception/BaseException/Any membership check.

Common situations: Developers new to Python 3.11 exception groups who try to assert 'a group containing ValueError' with the natural-looking raises(ExceptionGroup[ValueError]) syntax. Migrating from raises(ValueError) to group-based assertions and assuming the type parameter expresses the inner expectation.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/be424c7d64acc5bb. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/raises.py:441

            if not issubclass(exc, Exception):
                self.is_baseexception = True
            return exc
        # because RaisesGroup does not support variable number of exceptions there's
        # still a use for RaisesExc(ExceptionGroup[Exception]).
        origin_exc: type[BaseException] | None = get_origin(exc)
        if origin_exc and issubclass(origin_exc, BaseExceptionGroup):
            exc_type = get_args(exc)[0]
            if (
                issubclass(origin_exc, ExceptionGroup) and exc_type in (Exception, Any)
            ) or (
                issubclass(origin_exc, BaseExceptionGroup)
                and exc_type in (BaseException, Any)
            ):
                if not issubclass(origin_exc, ExceptionGroup):
                    self.is_baseexception = True
                return cast(type[BaseExcT_1], origin_exc)
            else:
                raise ValueError(
                    f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` "
                    f"are accepted as generic types but got `{exc}`. "
                    f"As `raises` will catch all instances of the specified group regardless of the "
                    f"generic argument specific nested exceptions has to be checked "
                    f"with `RaisesGroup`."
                )
        # unclear if the Type/ValueError distinction is even helpful here
        msg = f"Expected {expected}, but got "
        if isinstance(exc, type):  # type: ignore[unreachable]
            raise ValueError(msg + f"{exc.__name__!r}")
        if isinstance(exc, BaseException):  # type: ignore[unreachable]
            raise TypeError(msg + f"an exception instance: {type(exc).__name__}")
        raise TypeError(msg + repr(type(exc).__name__))

    @property
    def fail_reason(self) -> str | None:
        """Set after a call to :meth:`matches` to give a human-readable reason for why the match failed.
        When used as a context manager the string will be printed as the reason for the

View on GitHub (pinned to 0d6fbdeffa)