Ana içeriğe geç

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.

English version

Ö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 password içerebilmelidir.
  • output modeli password içermemelidir.
  • database modeli büyük ihtimalle hash'lenmiş bir password tutmalı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.