skip to Main Content

I have a model defined like this;

class SomeModel(BaseModel):
    name: str

class SomeOtherModel(BaseModel):
    name: int

class MyModel(BaseModel):
    items: List[Union[SomeModel, SomeOtherModel]]

    @validator("items", always=True)
    def validate(cls, value):
        by_type = list(filter(lambda v: isinstance(v, SomeModel), value))
        if len(by_type) < 1:
            raise ValueError("we need at least one SomeModel")

The submodels are unimportant, essentially I need a list of different sub-models, and the list must contain at least one of the first type. All well and good.

Elsewhere in my code I am referring to this model (context: for the purposes of saving user settings, should be irrelevant for this question).

class ComposerModels(BaseModel):
    user: List[MyModel] = []
    system: List[MyModel] = []

class ComposerSettings(BaseModel):
    models: ComposerModels

class UserSettings(BaseModel):
    composer: ComposerSettings

My program needs to be able to save new models into the UserSettings model, something like this;

my_model = MyModel(items=[SomeModel(name="a"), SomeOtherModel(name=1)])

user_settings = UserSettings(
    composer=ComposerSettings(
        models=ComposerModels(
            user=[my_model]
        )
    )
)

However, this throws an error;

Traceback (most recent call last):
  File ".../pydantic/shenanigans.py", line 38, in <module>
    user=[my_model]
  File "pydantic/main.py", line 342, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ComposerModels
user -> 0
  we need at least one SomeModel (type=value_error)

This is where it gets weird.

For some reason, when trying to instantiate UserSettings, it goes through validation of the items field inside MyModel, but instead of having been passed a list of sub-models, as expected, somehow it’s getting a list of MyModel instances instead. This obviously fails validation, and raises the error above. However, if I comment out the validation code, it works. No error, and the user settings model contains the MyModel we’d expect.

I can’t just disable that validation, I need it elsewhere in my program… Any ideas on what’s going on here? I’m stumped…

I’m running python 3.7 with pydantic 1.10 on centos 7. I can’t easily upgrade any versions, because my company doesn’t believe in devops, so if this is a known bug with pydantic at that version I’ll have to think of something else.

2

Answers


  1. It must be some kind of bug with pydantic v1.10 . No wonder they deprecated validator in the v2.

    I have done a slight modification, so that it works. I think there will be no problem for you, in initializing the UserSettings class with empty user and system.

    my_model = MyModel(items=[SomeModel(name="a"), SomeOtherModel(name=1)])
    
    user_settings = UserSettings(
        composer=ComposerSettings(
            models=ComposerModels(
            )
        )
    )
    
    user_settings.composer.models.user = [my_model]
    print(user_settings.__dict__)
    

    Hope this is the output you have been expecting:

    Output

    Login or Signup to reply.
  2. Use a different function name for the validator. The validate method is an existing method of the BaseModel and you’re overriding it. Whenever a new object is created, pydantic validates the fields with their corresponding types using validate method of the model.
    Since you have overriden the validate method, it will be used to validate the model.

    The flow of the code will be as follows:

    my_model = MyModel(items=[SomeModel(name="a"), SomeOtherModel(name=1)])
    
    user_settings = UserSettings(
        composer=ComposerSettings(
            models=ComposerModels(
                user=[my_model]  # Validate user with List[MyModel]
            )
        )
    )
    

    Now, to validate user it will first validate my_model with MyModel
    It will call something similar to MyModel.validate(my_model) internally (you can check the exact source here https://github.com/pydantic/pydantic/blob/547925887071a8cedf4642c4eb16ed749bf802cc/pydantic/v1/main.py#L1074) and since you’ve overriden the validate method the isinstance(v, SomeModel) check will not be satisfied for my_model and it will fail and raise exception..

    So the solution is simply to use different function name.

    class MyModel(BaseModel):
        items: List[Union[SomeModel, SomeOtherModel]]
    
        @validator("items", always=True)
        def validate_items(cls, value):  # Changed to validate_items
            by_type = list(filter(lambda v: isinstance(v, SomeModel), value))
            if len(by_type) < 1:
                raise ValueError("we need at least one SomeModel")
    

    This will work as expected.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search