Ek Modeller¶
🌐 Translation by AI and humans
This translation was made by AI guided by humans. 🤝
It could have mistakes of misunderstanding the original meaning, or looking unnatural, etc. 🤖
You can improve this translation by helping us guide the AI LLM better.
Önceki örnekten devam edersek, birbiriyle ilişkili birden fazla modelin olması oldukça yaygındır.
Bu durum özellikle kullanıcı modellerinde sık görülür, çünkü:
- input modeli bir
passwordiçerebilmelidir. - output modeli
passwordiçermemelidir. - database modeli büyük ihtimalle hash'lenmiş bir
passwordtutmalıdır.
Danger
Kullanıcının düz metin (plaintext) password'ünü asla saklamayın. Her zaman sonradan doğrulayabileceğiniz "güvenli bir hash" saklayın.
Eğer bilmiyorsanız, "password hash" nedir konusunu güvenlik bölümlerinde öğreneceksiniz.
Birden Çok Model¶
password alanlarıyla birlikte modellerin genel olarak nasıl görünebileceğine ve nerelerde kullanılacaklarına dair bir fikir:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: str | None = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_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")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: Union[str, None] = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_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")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
**user_in.model_dump() Hakkında¶
Pydantic'in .model_dump() Metodu¶
user_in, UserIn sınıfına ait bir Pydantic modelidir.
Pydantic modellerinde, model verilerini içeren bir dict döndüren .model_dump() metodu bulunur.
Yani, şöyle bir Pydantic nesnesi user_in oluşturursak:
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
ve sonra şunu çağırırsak:
user_dict = user_in.model_dump()
artık user_dict değişkeninde modelin verilerini içeren bir dict vardır (Pydantic model nesnesi yerine bir dict elde etmiş oluruz).
Ve eğer şunu çağırırsak:
print(user_dict)
şöyle bir Python dict elde ederiz:
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
Bir dict'i Unpack Etmek¶
user_dict gibi bir dict alıp bunu bir fonksiyona (ya da sınıfa) **user_dict ile gönderirsek, Python bunu "unpack" eder. Yani user_dict içindeki key ve value'ları doğrudan key-value argümanları olarak geçirir.
Dolayısıyla, yukarıdaki user_dict ile devam edersek, şunu yazmak:
UserInDB(**user_dict)
şuna eşdeğer bir sonuç üretir:
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
Ya da daha net şekilde, user_dict'i doğrudan kullanarak, gelecekte içeriği ne olursa olsun:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
Bir Pydantic Modelinden Diğerinin İçeriğiyle Pydantic Model Oluşturmak¶
Yukarıdaki örnekte user_dict'i user_in.model_dump() ile elde ettiğimiz için, şu kod:
user_dict = user_in.model_dump()
UserInDB(**user_dict)
şuna eşdeğerdir:
UserInDB(**user_in.model_dump())
...çünkü user_in.model_dump() bir dict döndürür ve biz de bunu UserInDB'ye ** önekiyle vererek Python'ın "unpack" etmesini sağlarız.
Böylece, bir Pydantic modelindeki verilerden başka bir Pydantic model üretmiş oluruz.
Bir dict'i Unpack Etmek ve Ek Keyword'ler¶
Sonrasında, aşağıdaki gibi ek keyword argümanı hashed_password=hashed_password eklemek:
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
...şuna benzer bir sonuca dönüşür:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
Warning
Ek destek fonksiyonları olan fake_password_hasher ve fake_save_user sadece verinin olası bir akışını göstermek içindir; elbette gerçek bir güvenlik sağlamazlar.
Tekrarı Azaltma¶
Kod tekrarını azaltmak, FastAPI'nin temel fikirlerinden biridir.
Kod tekrarı; bug, güvenlik problemi, kodun senkron dışına çıkması (bir yeri güncelleyip diğerlerini güncellememek) gibi sorunların olasılığını artırır.
Bu modellerin hepsi verinin büyük bir kısmını paylaşıyor ve attribute adlarını ve type'larını tekrar ediyor.
Daha iyisini yapabiliriz.
Diğer modellerimiz için temel olacak bir UserBase modeli tanımlayabiliriz. Sonra da bu modelden türeyen (subclass) modeller oluşturup onun attribute'larını (type deklarasyonları, doğrulama vb.) miras aldırabiliriz.
Tüm veri dönüştürme, doğrulama, dokümantasyon vb. her zamanki gibi çalışmaya devam eder.
Bu sayede modeller arasındaki farkları (plaintext password olan, hashed_password olan ve password olmayan) sadece o farklılıklar olarak tanımlayabiliriz:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_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")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_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")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
Union veya anyOf¶
Bir response'u iki ya da daha fazla type'ın Union'ı olarak tanımlayabilirsiniz; bu, response'un bunlardan herhangi biri olabileceği anlamına gelir.
OpenAPI'de bu anyOf ile tanımlanır.
Bunu yapmak için standart Python type hint'i olan typing.Union'ı kullanın:
Note
Bir Union tanımlarken en spesifik type'ı önce, daha az spesifik olanı sonra ekleyin. Aşağıdaki örnekte daha spesifik olan PlaneItem, Union[PlaneItem, CarItem] içinde CarItem'dan önce gelir.
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type: str = "car"
class PlaneItem(BaseItem):
type: str = "plane"
size: int
items = {
"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=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type: str = "car"
class PlaneItem(BaseItem):
type: str = "plane"
size: int
items = {
"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=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
Python 3.10'da Union¶
Bu örnekte Union[PlaneItem, CarItem] değerini response_model argümanına veriyoruz.
Bunu bir type annotation içine koymak yerine bir argümana değer olarak geçtiğimiz için, Python 3.10'da bile Union kullanmamız gerekiyor.
Eğer bu bir type annotation içinde olsaydı, dikey çizgiyi kullanabilirdik:
some_variable: PlaneItem | CarItem
Ancak bunu response_model=PlaneItem | CarItem atamasına koyarsak hata alırız; çünkü Python bunu bir type annotation olarak yorumlamak yerine PlaneItem ile CarItem arasında geçersiz bir işlem yapmaya çalışır.
Model Listesi¶
Aynı şekilde, nesne listesi döndüren response'ları da tanımlayabilirsiniz.
Bunun için standart Python typing.List'i (ya da Python 3.9 ve üzeri için sadece list) kullanın:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=list[Item])
async def read_items():
return items
Rastgele dict ile Response¶
Bir Pydantic modeli kullanmadan, sadece key ve value type'larını belirterek düz, rastgele bir dict ile de response tanımlayabilirsiniz.
Bu, geçerli field/attribute adlarını (Pydantic modeli için gerekli olurdu) önceden bilmiyorsanız kullanışlıdır.
Bu durumda typing.Dict (ya da Python 3.9 ve üzeri için sadece dict) kullanabilirsiniz:
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
Özet¶
Her duruma göre birden fazla Pydantic modeli kullanın ve gerekirse özgürce inheritance uygulayın.
Bir entity'nin farklı "state"lere sahip olması gerekiyorsa, o entity için tek bir veri modeli kullanmak zorunda değilsiniz. Örneğin password içeren, password_hash içeren ve password içermeyen state'lere sahip kullanıcı "entity"si gibi.