Skip to content

ValueError and ExtraField exceptions don't specify where they occurred in nested classes #236

@vedantpuri

Description

@vedantpuri
  • cattrs version: 22.1.0.dev0
  • Python version: 3.7.5
  • Operating System: macOS Monterey (12.2.1)

Description

This is referring to the _exceptions attribute on some of the exceptions raised in cattrs when structuring

I was trying to use cattrs inbuilt exceptions for structuring nested attrs classes. I noticed that in this case, KeyError and TypeError exceptions are nested to exactly where the issue occurred, which is great!

But I was expecting the same type of formatting to show up for Value Errors, which is not the case, they don't specify where the issue was caused. Similar issue happens for when there are extra Fields (Also, I think there should be a separate Exception class for that, but I'll make a new issue for that — #237 )

Also, should _exceptions be made public ?

What I Did

POC of issue:

import cattrs
from cattrs.errors import ClassValidationError
from attrs import define, field
from attrs.validators import lt

@define
class _InnerInnerClass:
    f_inner_inner: int


@define
class _InnerClass:
    f_inner_1: int
    f_inner_2: int = field(validator=lt(2))
    inner_inner_props: _InnerInnerClass


@define
class ComposedClass:
    inner_props: _InnerClass
    f: str


def make_my_class(payload, payload_kind):
    print(f"PAYLOAD_TYPE={payload_kind}")
    c = cattrs.GenConverter(forbid_extra_keys=True)
    try:
        c.structure(payload, ComposedClass)
    except ClassValidationError as ex:
        print(ex.__dict__, end="\n\n")
        return
    print("Success", end="\n\n")


if __name__ == "__main__":
    success_payload = {
        "f": "outer_most_string",
        "inner_props": {
            "f_inner_1": 10,
            "f_inner_2": 1,
            "inner_inner_props": {"f_inner_inner": 99},
        },
    }
    make_my_class(success_payload, "SUCCESS")

    value_error_payload = {
        "f": "outer_most_string",
        "inner_props": {
            "f_inner_1": 10,
            "f_inner_2": 12,  # Bad value
            "inner_inner_props": {"f_inner_inner": 99},
        },
    }
    make_my_class(value_error_payload, "BAD_VALUE")


    type_error_payload = {
        "f": "outer_most_string",
        "inner_props": {
            "f_inner_1": 10,
            "f_inner_2": None,  # Bad type
            "inner_inner_props": {"f_inner_inner": 99},
        },
    }
    make_my_class(type_error_payload, "BAD_TYPE")

    # Missing/Bad Key
    key_error_payload = {
        "f": "outer_most_string",
        "inner_props": {
            "f_inner_1": 10,
            "f_inner_2_bad_key": 12,
            "inner_inner_props": {"f_inner_inner": 99},
        },
    }
    make_my_class(key_error_payload, "BAD_KEY")

    extra_fields_payload = {
        "f": "outer_most_string",
        "inner_props": {
            "f_inner_1": 10,
            "f_inner_2": 1,
            "f_extra": 900,
            "inner_inner_props": {"f_inner_inner": 99},
        },
    }
    make_my_class(extra_fields_payload, "EXTRA_FIELDS")

Output:

$ python src/playground.py 
PAYLOAD_TYPE=SUCCESS
Success

PAYLOAD_TYPE=BAD_VALUE
{'cl': <class '__main__.ComposedClass'>, '_message': 'While structuring ComposedClass', '_exceptions': [ValueError("'f_inner_2' must be < 2: 12")], '__note__': None}

PAYLOAD_TYPE=BAD_TYPE
{'cl': <class '__main__.ComposedClass'>, '_message': 'While structuring ComposedClass', '_exceptions': [ClassValidationError('While structuring _InnerClass', [TypeError("int() argument must be a string, a bytes-like object or a number, not 'NoneType'")])], '__note__': None}

PAYLOAD_TYPE=BAD_KEY
{'cl': <class '__main__.ComposedClass'>, '_message': 'While structuring ComposedClass', '_exceptions': [ClassValidationError('While structuring _InnerClass', [KeyError('f_inner_2')])], '__note__': None}

PAYLOAD_TYPE=EXTRA_FIELDS
{'cl': <class '__main__.ComposedClass'>, '_message': 'While structuring ComposedClass', '_exceptions': [Exception('Extra fields in constructor for _InnerClass: f_extra')], '__note__': None}

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