12

I am using Pydantic to validate data inputs in a server. Pydantic models:

  • User: for common fields
  • UserIn: user input data to create new account
  • UserInDB: to hash password and include extra fields like created_at
  • UserOut: to output created/updated user data

Now how can I utilize the model UserIn for user account partial updates? so that None fields are excluded. Or do I have to rewrite it again and make every field optional?

class User(BaseModel):

    name_first :          str = Field(...)
    name_last :           Optional[str] = None
    mobile :              int = Field(..., description="It has to be 8 digits and starts with either 9 or 7", example="99009900")
    
    email :               Optional[EmailStr] = None
    
    gender:               Literal['m', 'f'] = Field(..., example="m")
    birth_date:           datetime.date
    preferred_language :  Literal['ar', 'en'] = Field(..., example="ar")
    newsletter :          bool = Field(...)


    @validator('mobile')
    def number_has_to_start_with_9_or_7(cls, v):
        if str(v)[:1] !="9" and str(v)[:1] !="7":
            raise ValueError('Mobile number must start with 9 or 7')
        if len(str(v)) !=8:
            raise ValueError('Mobile number must be 8 digits')
        return v


class UserIn(User):
    password: str = Field(...)

class UserInDB(UserIn):
    mobile_otp:           int = Field(...)
    bitrole:              int = Field(...)
    created_at:           datetime.datetime
    updated_at:           datetime.datetime

class UserOut(User):
    id:                   int
    mobile_otp:           int
    bitrole:              int
    mobile_confirmed :    bool
    email_confirmed :     bool
    disabled :            bool
    created_at:           datetime.datetime
    updated_at:           datetime.datetime

    class Config:
        orm_mode = True
funnydman
  • 9,083
  • 4
  • 40
  • 55
Sami Al-Subhi
  • 4,406
  • 9
  • 39
  • 66

1 Answers1

11

Unfortunately this is a problem that has been already faced by quite a lot of people (me included).

There is no simple way of handling it out of the box and as far as I know.

Here are some solutions proposed to bypass the problem, although not completely, on github

https://github.com/samuelcolvin/pydantic/issues/990#issuecomment-645961530

MY SOLUTION

I use pydantic with fastapi and partial updates of resources are quite common. Due to the limitation of pydantic I ask the user to resubmit all the values, filling correctly the pydantic model and then update all the fields that can be updated.

It's not ideal, I know, but it seems to work

UPDATE

I've found another possible solution. Basically, retrieve the old data, update it, and write the updated data (much better than my solution).

See fastapi docs https://fastapi.tiangolo.com/tutorial/body-updates/#partial-updates-with-patch

Warning: Partial updates with nested object are currently not implemented in pydantic. See https://github.com/pydantic/pydantic/issues/4177

lsabi
  • 3,641
  • 1
  • 14
  • 26
  • 2
    The FastAPI approach is kind of acceptable to me. If you notice, all the fields in the Pydantic update model/schema are optional. The first point here is that you can not use a Pydantic create model for partial updates. There has to be a second model with all the fields optional. 2nd point is that you do not need to retrieve stored data as in that example. You can simply partially update a db record using query update. – Sami Al-Subhi Sep 26 '20 at 13:25
  • I know, but my approach is from way before the PATCH solution come out. I haven't checked the docs in a while, which I did after (and hence the update). Fastapi uses pydantic for data validation, so it's out of pydantic's scope to provide updates for db records. – lsabi Sep 26 '20 at 16:49