Accepting PEP 654 (Exception Groups and except*)

Yes, I’d be glad to.

Imagine you have a class that looks like this:

from dataclasses import dataclass

@dataclass
class Test:
    a: int
    b: float

You want to receive it via JSON (an extremely common use case). The JSON libs all produce a dictionary, you use cattrs to structure it into an instance of Test. But what if the payload is malformed (the values for ‘a’ and ‘b’ cannot be converted to int and float, respectively)?

Right now, I have cattrs set up so that the first exception encountered flies out (this would be a ValueError: invalid literal for int() with base 10: 'a_string'). That works, but what if:

  • you want more context during debugging
  • you actually want a better error message for your caller

You need to know which field caused the error, and ideally you’d get all the errors at once instead of one by one. Hence, ExceptionGroups.

So in this case, I suppose, I’d subclass ExceptionGroup:

class ClassStructuringError(ExceptionGroup):
    cls: type  # Useful to have, right?

And the code in cattrs would essentially do this (ignoring a bunch of edge cases now):

def structure_Test(o):
    errors = []
    try:
        a = int(o['a'])
    except Exception as e:
        e.__note__ = "Attribute a error"
        errors.append(e)
    try:
        b = int(o['b'])
    except Exception as e:
        e.__note__ = "Attribute b error"
        errors.append(e)
    if errors:
        raise ClassStructuringError("Structuring Test", errors, cls=Test)
    return Test(a, b)

And for the purposes of displaying a nice traceback what would be adequate.

But what if I want to catch this exceptiongroup, walk its tree and return this via my HTTP API:

{
  "errors": {
    "data": {
      "a": "invalid literal for int() with base 10: 'a_string'",
      "b": "could not convert string to float: 'a_string'"
    }
  }
}

My issue is that with notes being strings, I essentially need to parse my own note. Which is not the end of the world but I feel like I’m doing a formatting/parsing dance for no reason. Maybe it’s OK.