Skip to content

New rule: raising ExceptionGroup child exception loses context/cause #298

@jakkdl

Description

@jakkdl
def raise_error():
    try:
        1/0
    finally:
        raise ValueError

def wrap_error():
    try:
        raise_error()
    except Exception as e:
        raise ExceptionGroup("aoeu", [e])

def unwrap_error():
    try:
        wrap_error()
    except ExceptionGroup as exc_group:
        if ...:
            # Oops: we now lost the DivisionByZeroError as cause/context
            raise exc_group.exceptions[0] from exc_group
        if ...:
            # could catch this with type tracking
            e = exc_group.exceptions[0]
            raise e from None
        if ...:
            # this is fine
            raise NewError from exc_group

I realized I've done this in several PRs (at least the one in trio-websocket), and nobody has commented on the fact. It could also be a better fit for flake8-bugbear (although they don't have any type-tracking infrastructure).
I haven't yet figured out the best way of avoiding it though, options include:

def unwrap_error2():
    my_exc = None
    try:
        wrap_error()
    except ExceptionGroup as exc_group:
        if ...:
            my_exc = exc_group.exceptions[0]
    # child exception cause/context is fully preserved, but you lose all info about the
    # group. This might be good or bad depending on the situation
    if my_exc is not None:
        raise my_exc

def unwrap_error_3():
    try:
        wrap_error()
    except ExceptionGroup as exc_group:
        # this seems sketchy??
        import copy
        my_exc = copy.copy(exc_group.exceptions[0])
        raise my_exc from exc_group

also see HypothesisWorks/hypothesis#4115 for context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions