विषय पर बढ़ें

Extra Models

🌐 एआई और मनुष्यों द्वारा किया गया अनुवाद

यह अनुवाद मनुष्यों के मार्गदर्शन में एआई द्वारा किया गया है। 🤝

इसमें मूल अर्थ को गलत समझने या अप्राकृतिक लगने आदि जैसी गलतियाँ हो सकती हैं। 🤖

आप हमें एआई LLM को बेहतर मार्गदर्शन करने में मदद करके इस अनुवाद को बेहतर बना सकते हैं।

अंग्रेज़ी संस्करण

पिछले उदाहरण को आगे बढ़ाते हुए, एक से अधिक संबंधित model होना आम बात होगी।

यह खासकर user models के मामले में होता है, क्योंकि:

  • input model में password हो सकने की क्षमता चाहिए।
  • output model में password नहीं होना चाहिए।
  • database model में शायद hashed password होना चाहिए।

खतरा

user के plaintext passwords कभी store न करें। हमेशा एक "secure hash" store करें जिसे आप बाद में verify कर सकें।

अगर आप नहीं जानते, तो आप security chapters में सीखेंगे कि "password hash" क्या होता है।

कई models

यह एक सामान्य idea है कि models अपने password fields और जहाँ वे इस्तेमाल होते हैं, वहाँ कैसे दिख सकते हैं:

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

**user_in.model_dump() के बारे में

Pydantic का .model_dump()

user_in, class UserIn का एक Pydantic model है।

Pydantic models में एक .model_dump() method होता है जो model के data के साथ एक dict return करता है।

तो, अगर हम इस तरह एक Pydantic object user_in बनाते हैं:

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

और फिर हम call करते हैं:

user_dict = user_in.model_dump()

तो अब हमारे पास variable user_dict में data के साथ एक dict है (यह Pydantic model object के बजाय एक dict है)।

और अगर हम call करते हैं:

print(user_dict)

तो हमें यह Python dict मिलेगा:

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

एक dict को unpack करना

अगर हम user_dict जैसा एक dict लेते हैं और उसे किसी function (या class) को **user_dict के साथ pass करते हैं, तो Python उसे "unpack" करेगा। यह user_dict की keys और values को सीधे key-value arguments के रूप में pass करेगा।

तो, ऊपर वाले user_dict को जारी रखते हुए, यह लिखना:

UserInDB(**user_dict)

कुछ इस equivalent result देगा:

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

या अधिक सही रूप में, user_dict को सीधे इस्तेमाल करते हुए, उसमें भविष्य में जो भी contents हों:

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

दूसरे model के contents से एक Pydantic model

जैसा कि ऊपर के उदाहरण में हमें user_in.model_dump() से user_dict मिला, यह code:

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

इसके equivalent होगा:

UserInDB(**user_in.model_dump())

...क्योंकि user_in.model_dump() एक dict है, और फिर हम उसे ** prefix के साथ UserInDB को pass करके Python से उसे "unpack" करवाते हैं।

तो, हमें एक Pydantic model के data से दूसरा Pydantic model मिलता है।

एक dict को unpack करना और extra keywords

और फिर extra keyword argument hashed_password=hashed_password जोड़ना, जैसे:

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

...अंत में ऐसा बन जाता है:

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

चेतावनी

supporting additional functions fake_password_hasher और fake_save_user सिर्फ data के एक possible flow को demo करने के लिए हैं, लेकिन वे निश्चित रूप से कोई real security नहीं दे रहे हैं।

Duplication कम करें

Code duplication कम करना FastAPI के core ideas में से एक है।

क्योंकि code duplication बढ़ने से bugs, security issues, code desynchronization issues (जब आप एक जगह update करते हैं लेकिन बाकी जगह नहीं), आदि की संभावना बढ़ जाती है।

और ये models बहुत सारा data share कर रहे हैं और attribute names और types को duplicate कर रहे हैं।

हम इससे बेहतर कर सकते हैं।

हम एक UserBase model declare कर सकते हैं जो हमारे दूसरे models के लिए base की तरह काम करता है। और फिर हम उस model की subclasses बना सकते हैं जो उसके attributes (type declarations, validation, आदि) inherit करती हैं।

सारा data conversion, validation, documentation, आदि सामान्य रूप से काम करता रहेगा।

इस तरह, हम केवल models के बीच के अंतर declare कर सकते हैं (plaintext password के साथ, hashed_password के साथ और password के बिना):

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

Union या anyOf

आप response को दो या अधिक types के Union के रूप में declare कर सकते हैं, जिसका मतलब है कि response उनमें से कोई भी हो सकता है।

इसे OpenAPI में anyOf के साथ define किया जाएगा।

ऐसा करने के लिए, standard Python type hint typing.Union का इस्तेमाल करें:

नोट

Union define करते समय, सबसे specific type को पहले include करें, उसके बाद कम specific type को। नीचे दिए गए उदाहरण में, अधिक specific PlaneItem, Union[PlaneItem, CarItem] में CarItem से पहले आता है।

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=PlaneItem | CarItem)
async def read_item(item_id: str):
    return items[item_id]

Python 3.10 में Union

इस उदाहरण में हम argument response_model की value के रूप में Union[PlaneItem, CarItem] pass करते हैं।

क्योंकि हम इसे type annotation में रखने के बजाय argument को value के रूप में pass कर रहे हैं, इसलिए हमें Python 3.10 में भी Union इस्तेमाल करना होगा।

अगर यह type annotation में होता तो हम vertical bar इस्तेमाल कर सकते थे, जैसे:

some_variable: PlaneItem | CarItem

लेकिन अगर हम इसे assignment response_model=PlaneItem | CarItem में डालते, तो हमें error मिलता, क्योंकि Python इसे type annotation के रूप में interpret करने के बजाय PlaneItem और CarItem के बीच एक invalid operation perform करने की कोशिश करता।

Models की list

इसी तरह, आप objects की lists के responses declare कर सकते हैं।

इसके लिए, standard Python list इस्तेमाल करें:

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

Arbitrary dict के साथ response

आप plain arbitrary dict का इस्तेमाल करके भी response declare कर सकते हैं, जिसमें Pydantic model का इस्तेमाल किए बिना केवल keys और values का type declare किया जाता है।

यह तब उपयोगी है जब आपको valid field/attribute names (जो Pydantic model के लिए चाहिए होंगे) पहले से नहीं पता हों।

इस मामले में, आप dict इस्तेमाल कर सकते हैं:

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}

Recap

हर case के लिए कई Pydantic models इस्तेमाल करें और freely inherit करें।

अगर किसी entity में अलग-अलग "states" हो सकने चाहिए, तो आपको प्रति entity एक ही data model रखने की जरूरत नहीं है। user "entity" एक उदाहरण है, जिसमें states में password, password_hash, या कोई password नहीं होना शामिल है।