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

Unable to trace full error location when calling super().__init__() from custom model #7498

Closed
1 task done
alice-luc opened this issue Sep 19, 2023 · 16 comments
Closed
1 task done
Assignees
Labels

Comments

@alice-luc
Copy link

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

Im trying to reassign annotation of the field "g" to locate the exact field that causes ValidationError. But when i spot the final field, im only able to trace error back up to last init call.

Errors i get:

{
'input': '654f',
'loc': ('g', 0, 'e', 'a'), < -------- no 'h' in location
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'type': 'int_parsing',
'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'
},
{
'input': 'tr',
'loc': ('g', 0, 'e', 'c'),
'msg': 'Extra inputs are not permitted',
'type': 'extra_forbidden',
'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'
},
{
'input': {},
'loc': ('g', 0, 'e', 'd'),
'msg': 'Extra inputs are not permitted',
'type': 'extra_forbidden',
'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'
}

Expected errors

{
'input': '654f',
'loc': ('h', 'g', 0, 'e', 'a'),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'type': 'int_parsing',
'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'
},
{
'input': 'tr',
'loc': ('h', 'g', 0, 'e', 'c'),
'msg': 'Extra inputs are not permitted',
'type': 'extra_forbidden',
'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'
},
{
'input': {},
'loc': ('h', 'g', 0, 'e', 'd'),
'msg': 'Extra inputs are not permitted',
'type': 'extra_forbidden',
'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'
}

Example Code

from pprint import pprint

from pydantic import BaseModel as BM, ConfigDict


class BaseModel(BM):
    model_config = ConfigDict(extra='forbid')


class A(BaseModel):
    a: int = 1
    b: str = "12345"


class B(BaseModel):
    c: int = 2
    d: str = "qwe"


class C(BaseModel):
    e: A


class D(BaseModel):
    f: C


class F(BaseModel):
    g: list[A | C]

    def __init__(self, *args, **kwargs):
        try:
            super().__init__(*args, **kwargs)
        except ValidationError:

            class _F(BaseModel):
                g: list[C]

            _F(*args, **kwargs)


class G(BaseModel):
    h: F


try:
    f = G(**{"h": {"g": [{"e": {"a": "654f", "c": "tr", "d": {}}}]}})
except Exception as e:
    pprint(e.errors())

Python, Pydantic & OS Version

pydantic version: 2.3.0
pydantic-core version: 2.6.3
pydantic-core build: profile=release pgo=false
install path: /[location]/lib/python3.11/site-packages/pydantic
python version: 3.11.4 (v3.11.4:d2340ef257, Jun  6 2023, 19:15:51) [Clang 13.0.0 (clang-1300.0.29.30)]
platform: macOS-13.5.2-arm64-arm-64bit
optional deps. installed: ['typing-extensions']
@alice-luc alice-luc added bug V2 Bug related to Pydantic V2 unconfirmed Bug not yet confirmed as valid/applicable labels Sep 19, 2023
@sydney-runkle sydney-runkle self-assigned this Sep 19, 2023
@sydney-runkle sydney-runkle added question and removed unconfirmed Bug not yet confirmed as valid/applicable bug V2 Bug related to Pydantic V2 labels Sep 19, 2023
@sydney-runkle
Copy link
Member

Hi @alice-luc,

I'm guessing that the issue you're having is that the BaseModel.__init__() method makes this call which runs rust code, which you can't trace in a Python debugger very easily.

@alice-luc
Copy link
Author

Thank you for your reply @sydney-runkle,
What is the proper way to get error location in this case? i can not use discriminator either, because its value appears in location tuple
#7500

@sydney-runkle
Copy link
Member

HI @alice-luc,

Sure thing. Hopefully this helps:

I think the print of e.errors() is giving you the info you need - when I run your code, I get the following:

[{'input': '654f',
  'loc': ('g', 0, 'e', 'a'),
  'msg': 'Input should be a valid integer, unable to parse string as an '
         'integer',
  'type': 'int_parsing',
  'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'},
 {'input': 'tr',
  'loc': ('g', 0, 'e', 'c'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'},
 {'input': {},
  'loc': ('g', 0, 'e', 'd'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'}]

Which indicates that the value of the innermost a field as well as the extra c and d fields seem to be causing the issue.

@alice-luc
Copy link
Author

But what if i need it to point to root 'h' field in model G?

HI @alice-luc,

Sure thing. Hopefully this helps:

I think the print of e.errors() is giving you the info you need - when I run your code, I get the following:

[{'input': '654f',
  'loc': ('g', 0, 'e', 'a'),
  'msg': 'Input should be a valid integer, unable to parse string as an '
         'integer',
  'type': 'int_parsing',
  'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'},
 {'input': 'tr',
  'loc': ('g', 0, 'e', 'c'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'},
 {'input': {},
  'loc': ('g', 0, 'e', 'd'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'}]

Which indicates that the value of the innermost a field as well as the extra c and d fields seem to be causing the issue.

@adriangb
Copy link
Member

@alice-luc would you mind explaining what you are actually trying to do? We generally discourage trying to override BaseModel.__init__, it can lead to a lot of problems.

@sydney-runkle
Copy link
Member

Following up - if you don't override the __init__ function on a BaseModel, you get the errors you'd expect:

[{'input': {'a': '654f', 'c': 'tr', 'd': {}},
  'loc': ('h', 'g', 0, 'A', 'e'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'},
 {'input': '654f',
  'loc': ('h', 'g', 0, 'C', 'e', 'a'),
  'msg': 'Input should be a valid integer, unable to parse string as an '
         'integer',
  'type': 'int_parsing',
  'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'},
 {'input': 'tr',
  'loc': ('h', 'g', 0, 'C', 'e', 'c'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'},
 {'input': {},
  'loc': ('h', 'g', 0, 'C', 'e', 'd'),
  'msg': 'Extra inputs are not permitted',
  'type': 'extra_forbidden',
  'url': 'https://errors.pydantic.dev/2.3/v/extra_forbidden'}]

@alice-luc
Copy link
Author

@sydney-runkle
Yeah, it might work in this bare minimum code example, but not in more complex models

@sydney-runkle
Copy link
Member

@alice-luc is there any particular reason you need to override the __init__?

@alice-luc
Copy link
Author

alice-luc commented Sep 19, 2023

Let's say i have a house model that has the following structure:
floors: list[FloorModel],

FloorModel has
apartments: list[ApartmentModel | DormitoryModel ]

ApartmentModel has
rooms: list[KitchenModel | LivingRoomModel | BedRoomModel]

When im trying to validate large object with an error in KitchenModel.wall, pydantic does not specify which exact child model and field ruining validation, it only traces the path to 'apartments'.
If i specify discriminator in Union lists, it appears in loc tuple, so its not an option

@sydney-runkle
Copy link
Member

sydney-runkle commented Sep 19, 2023

@alice-luc, could you please send a code sample so that we can reproduce your problem? As long as I don't override BaseModel's __init__ with a custom version, I don't have any issues validating a large object + viewing the exact field that's causing a problem.

@adriangb
Copy link
Member

Just checking, have you tried using @pydantic.model_validator as an alternative to overriding __init__? It can accomplish the same in most cases.

@alice-luc
Copy link
Author

alice-luc commented Sep 20, 2023

Sure @sydney-runkle
If i run this piece of code with 1 potential ValidationError, i receive 3. Two of them are caused by List[Union] usage on a field, which pydantic is not able to identify as one of LivingRoom or BedRoom.
So here, using the init method at the Apartment model i would be able to do it manually, and point at the exact Model the object is supposed to be validated through to trace the error correctly


from pydantic import BaseModel
from typing import Dict, List, Optional, Literal, Union
from pprint import pprint


class BedRoom(BaseModel):
    color: Literal["red"]
    bedroom_items: List[Dict]


class LivingRoom(BaseModel):
    area: Optional[float]
    lr_items: List[Dict]


class Apartment(BaseModel):
    rooms: List[Union[BedRoom, LivingRoom]]


class Floor(BaseModel):
    apartments: List[Optional[Apartment]]


class House(BaseModel):
    floors: List[Optional[Floor]]



test_data = {
    "floors": [
        {
            "apartments": [
                {
                    "rooms": [
                        {
                            "area": 123.123,
                            "lr_items": [{}, {}]
                        },
                        {
                            "color": "red",
                            "bedroom_items": 123
                        }
                    ]
                }
            ]
        }
    ]
}

try:
    test = House(**test_data)
except Exception as e:
    pprint(e.errors())

@alice-luc
Copy link
Author

alice-luc commented Sep 20, 2023

i sure have @adriangb
i might be doing it incorrectly, if you could suggest me the right way to solve the issue i described above via model_validator either field_validator, it be awesome))
The only way that worked for me, is to use discriminator, and then use crutches to exclude it from exception loc tuple

@sydney-runkle
Copy link
Member

Hi @alice-luc,

Thanks for the example. The exhaustive errors that you're reporting aren't a unique function of the many levels of nested models you have - that happens with every Union type as Pydantic exhaustively checks against all possibilities (when a discriminator isn't provided).

See below:

from pprint import pprint
from typing import List, Union

from pydantic import BaseModel

class ModelA(BaseModel):
    a: str


class ModelB(BaseModel):
    b: int


class MyModel(BaseModel):
    value: List[Union[ModelA, ModelB]]

try:
    my_model = MyModel(**{"value": [{'a': "a value"}, {"b": "not an int"}]})
except Exception as e:
    pprint(e.errors())

    """
    [{'input': {'b': 'not an int'},
      'loc': ('value', 1, 'ModelA', 'a'),
      'msg': 'Field required',
      'type': 'missing',
      'url': 'https://errors.pydantic.dev/2.3/v/missing'},
     {'input': 'not an int',
      'loc': ('value', 1, 'ModelB', 'b'),
      'msg': 'Input should be a valid integer, unable to parse string as an integer',
      'type': 'int_parsing',
      'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'}]
    """

We currently have a PR that's a work in progress for supporting functional discriminators, which might help with your use case (see #6915). We also have an open issue (see #7462) calling for increased support for these. In the meantime, you could do something like this with a PydanticCustomError to simplify the errors resulting from exhaustive Union checking. You could consider filtering the errors based on the type of the input.

Let me know if that makes sense / if there's anything else we can do to help! 😄

@alice-luc
Copy link
Author

Thank you for reply @sydney-runkle!
I will look closer into related issues, hopefully functional discriminators will be the key

@sydney-runkle
Copy link
Member

@alice-luc sure thing! I'm going to close this issue in favor of continuing discussion on the PR + issue mentioned above, but we'll be sure to keep in mind the desire to simplify errors coming from validation against Union types when we look to implement functional discriminators.

Thanks for your questions / prompt responses! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants