Search code examples
pythonvalidationsubclassfastapipydantic

Changing pydantic model Field() arguments with class variables for Fastapi


I'm a little new to tinkering with class inheritance in python, particularly when it comes down to using class attributes. In this case I am using a class attribute to change an argument in pydantic's Field() function. This wouldn't be too hard to do if my class contained it's own constructor, however, my class User1 is inheriting this from pydantic's BaseModel. The idea is that I would like to be able to change the class attribute prior to creating the instance.

Please see some example code below:

from pydantic import Basemodel, Field   

class User1(BaseModel):
    _set_ge = None # create class attribute
    item: float = Field(..., ge=_set_ge)

    # avoid overriding BaseModel's __init__
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
User1._set_ge = 0 # setting the class attribute to a new value
instance = User1(item=-1)
print(instance) # item=-1.0

When creating the instance using instance = User1(item=-1) I would expect a validation error to be thrown, but it instead passes validation and simply returns the item value.

If I had my own constructor there would be little issue in changing the _set_ge, but as User1 inheriting this constructor from BaseModel, things are a little more complicated.

The eventual aim is to add this class to a fastapi endpoint as follows:

from fastapi import Fastapi
from schemas import User1    

class NewUser1(User1):
      pass

NewUser1._set_ge = 0    

@app.post("/")
def endpoint(request: NewUser1):
    return User1.item

To reduce code duplication, I aimed to use this method to easily change Field() arguments. If there is a better way, I'd be glad to consider that too.

This question is quite closely related to this unanswered one.


Solution

  • In the end, the @validator proposal by @hernán-alarcón is probably the best way to do this. For example:

    from pydantic import Basemodel, Field, NumberNotGeError
    from typing import ClassVar   
    
    class User(BaseModel):
        _set_ge = ClassVar[float] # added the ClassVar typing to make clearer, but the underscore should be sufficient
        item: float = Field(...)
    
        @validator('item')
        def limits(cls, v):
            limit_number = cls._set_ge
            if v >= limit_number:
                return v
            else:
                raise NumberNotGeError(limit_value=limit_number)
            
        
    class User1(User)
         _set_ge = 0 # setting the class attribute to a new value
    
    instance = User1(item=-1) # raises the error