Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

Extra Models

Continuing with the previous example, it will be common to have more than one related model.

This is especially the case for user models, because:

  • Theinput model needs to be able to have a password.
  • Theoutput model should not have a password.
  • Thedatabase model would probably need to have a hashed password.

Danger

Never store user's plaintext passwords. Always store a "secure hash" that you can then verify.

If you don't know, you will learn what a "password hash" is in thesecurity chapters.

Multiple models

Here's a general idea of how the models could look like with their password fields and the places where they are used:

fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserIn(BaseModel):username:strpassword:stremail:EmailStrfull_name:str|None=NoneclassUserOut(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserInDB(BaseModel):username:strhashed_password:stremail:EmailStrfull_name:str|None=Nonedeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_saved

About**user_in.model_dump()

Pydantic's.model_dump()

user_in is a Pydantic model of classUserIn.

Pydantic models have a.model_dump() method that returns adict with the model's data.

So, if we create a Pydantic objectuser_in like:

user_in=UserIn(username="john",password="secret",email="john.doe@example.com")

and then we call:

user_dict=user_in.model_dump()

we now have adict with the data in the variableuser_dict (it's adict instead of a Pydantic model object).

And if we call:

print(user_dict)

we would get a Pythondict with:

{'username':'john','password':'secret','email':'john.doe@example.com','full_name':None,}

Unpacking adict

If we take adict likeuser_dict and pass it to a function (or class) with**user_dict, Python will "unpack" it. It will pass the keys and values of theuser_dict directly as key-value arguments.

So, continuing with theuser_dict from above, writing:

UserInDB(**user_dict)

would result in something equivalent to:

UserInDB(username="john",password="secret",email="john.doe@example.com",full_name=None,)

Or more exactly, usinguser_dict directly, with whatever contents it might have in the future:

UserInDB(username=user_dict["username"],password=user_dict["password"],email=user_dict["email"],full_name=user_dict["full_name"],)

A Pydantic model from the contents of another

As in the example above we gotuser_dict fromuser_in.model_dump(), this code:

user_dict=user_in.model_dump()UserInDB(**user_dict)

would be equivalent to:

UserInDB(**user_in.model_dump())

...becauseuser_in.model_dump() is adict, and then we make Python "unpack" it by passing it toUserInDB prefixed with**.

So, we get a Pydantic model from the data in another Pydantic model.

Unpacking adict and extra keywords

And then adding the extra keyword argumenthashed_password=hashed_password, like in:

UserInDB(**user_in.model_dump(),hashed_password=hashed_password)

...ends up being like:

UserInDB(username=user_dict["username"],password=user_dict["password"],email=user_dict["email"],full_name=user_dict["full_name"],hashed_password=hashed_password,)

Warning

The supporting additional functionsfake_password_hasher andfake_save_user are just to demo a possible flow of the data, but they of course are not providing any real security.

Reduce duplication

Reducing code duplication is one of the core ideas inFastAPI.

As code duplication increments the chances of bugs, security issues, code desynchronization issues (when you update in one place but not in the others), etc.

And these models are all sharing a lot of the data and duplicating attribute names and types.

We could do better.

We can declare aUserBase model that serves as a base for our other models. And then we can make subclasses of that model that inherit its attributes (type declarations, validation, etc).

All the data conversion, validation, documentation, etc. will still work as normally.

That way, we can declare just the differences between the models (with plaintextpassword, withhashed_password and without password):

fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserBase(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserIn(UserBase):password:strclassUserOut(UserBase):passclassUserInDB(UserBase):hashed_password:strdeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_saved

Union oranyOf

You can declare a response to be theUnion of two or more types, that means, that the response would be any of them.

It will be defined in OpenAPI withanyOf.

To do that, use the standard Python type hinttyping.Union:

Note

When defining aUnion, include the most specific type first, followed by the less specific type. In the example below, the more specificPlaneItem comes beforeCarItem inUnion[PlaneItem, CarItem].

fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classBaseItem(BaseModel):description:strtype:strclassCarItem(BaseItem):type:str="car"classPlaneItem(BaseItem):type:str="plane"size:intitems={"item1":{"description":"All my friends drive a low rider","type":"car"},"item2":{"description":"Music is my aeroplane, it's my aeroplane","type":"plane","size":5,},}@app.get("/items/{item_id}",response_model=PlaneItem|CarItem)asyncdefread_item(item_id:str):returnitems[item_id]

Union in Python 3.10

In this example we passUnion[PlaneItem, CarItem] as the value of the argumentresponse_model.

Because we are passing it as avalue to an argument instead of putting it in atype annotation, we have to useUnion even in Python 3.10.

If it was in a type annotation we could have used the vertical bar, as:

some_variable:PlaneItem|CarItem

But if we put that in the assignmentresponse_model=PlaneItem | CarItem we would get an error, because Python would try to perform aninvalid operation betweenPlaneItem andCarItem instead of interpreting that as a type annotation.

List of models

The same way, you can declare responses of lists of objects.

For that, use the standard Pythonlist:

fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classItem(BaseModel):name:strdescription:stritems=[{"name":"Foo","description":"There comes my hero"},{"name":"Red","description":"It's my aeroplane"},]@app.get("/items/",response_model=list[Item])asyncdefread_items():returnitems

Response with arbitrarydict

You can also declare a response using a plain arbitrarydict, declaring just the type of the keys and values, without using a Pydantic model.

This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.

In this case, you can usedict:

fromfastapiimportFastAPIapp=FastAPI()@app.get("/keyword-weights/",response_model=dict[str,float])asyncdefread_keyword_weights():return{"foo":2.3,"bar":3.4}

Recap

Use multiple Pydantic models and inherit freely for each case.

You don't need to have a single data model per entity if that entity must be able to have different "states". As the case with the user "entity" with a state includingpassword,password_hash and no password.


[8]ページ先頭

©2009-2026 Movatter.jp