Pydantic Mastery — Backend, AI, ML & LLM | Foysal Ahmed Pranto
Bilingual Tech Book · English + বাংলা

Pydantic Mastery

Data validation made simple — for Backend, AI, ML & LLM engineers.
সহজ ভাষায়, প্রচুর উদাহরণসহ — একদম শূন্য থেকে প্রফেশনাল পর্যন্ত।

BaseModel FastAPI ML Serving LLM Structured Output
FP
Author · লেখক
Foysal Ahmed Pranto
📞 01313-990906
1

What is Pydantic?

Pydantic কী? — একদম সহজ ভাষায়
EN Think of Pydantic as a security guard for your data. You tell the guard: "only let in a name (text) and an age (number)". If someone sends wrong data — the guard stops it at the gate and tells you exactly what was wrong.
বাং Pydantic-কে ভাবুন আপনার ডেটার দারোয়ান হিসেবে। আপনি দারোয়ানকে বলে দিলেন: "শুধু নাম (text) আর বয়স (number) ঢুকতে দেবে।" কেউ ভুল ডেটা পাঠালে — দারোয়ান গেটেই আটকে দেবে, আর বলে দেবে ঠিক কী ভুল ছিল।

The problem — without Pydantic / সমস্যাটা — Pydantic ছাড়া

# Data comes from outside — you can't trust it
# ডেটা আসে বাইরে থেকে — বিশ্বাস করা যায় না
def create_user(data: dict):
    age = data["age"]      # might be "25" (a string!) / string হতে পারে!
    return age + 1        # 💥 CRASH — but much later, somewhere confusing

The solution — with Pydantic / সমাধান — Pydantic সহ

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

u = User(name="Rahim", age="25")   # "25" → 25 ✅ auto-converted / নিজেই convert
u = User(name="Rahim", age="abc")  # ❌ instant clear error / সাথে সাথে পরিষ্কার error

Where will you use it? / কোথায় কোথায় লাগবে?

AreaEnglishবাংলা
Backend APICheck every request & response (FastAPI is built on it)প্রতিটা request/response চেক (FastAPI এর ওপরেই তৈরি)
MLMake sure features are complete & in rangeFeature সম্পূর্ণ ও সীমার মধ্যে কিনা
LLMCheck prompts, parse AI's JSON replyPrompt চেক, AI-এর JSON উত্তর parse
ConfigLoad .env safely.env নিরাপদে load
💡
One line to remember / এক লাইনে মনে রাখুন: Pydantic changes "I hope the data is right" into "the data is guaranteed right." — "আশা করি ডেটা ঠিক আছে" বদলে হয়ে যায় "ডেটা নিশ্চিত ঠিক আছে।"
2

Your First Model

প্রথম Model — BaseModel দিয়ে শুরু
EN Rule is simple: make a class, inherit BaseModel, write each field with a type. No default = required. Has default = optional. That's 80% of Pydantic.
বাং নিয়ম খুব সহজ: একটা class বানান, BaseModel থেকে inherit করুন, প্রতিটা field-এর type লিখুন। Default নেই = দিতেই হবে। Default আছে = না দিলেও চলবে। এটুকুই Pydantic-এর ৮০%।
from pydantic import BaseModel

class Student(BaseModel):
    id: int                    # required / আবশ্যক
    name: str                  # required / আবশ্যক
    is_active: bool = True     # optional (default True)
    bio: str | None = None     # optional, null চলবে

3 ways to create / তৈরির ৩টা উপায়

# 1) Directly / সরাসরি
s = Student(id=1, name="Karim")

# 2) From a dict (like parsed JSON) / dict থেকে
s = Student.model_validate({"id": 2, "name": "Fatima"})

# 3) From a raw JSON string / সরাসরি JSON string থেকে
s = Student.model_validate_json('{"id": 3, "name": "Ayan"}')

print(s.name)        # normal access / সাধারণভাবে ব্যবহার
print(s.is_active)   # True — default মান

Errors are your friend / Error আপনার বন্ধু

from pydantic import ValidationError

try:
    Student(id="abc", name=123)
except ValidationError as e:
    print(e)
# id   → Input should be a valid integer  [input_value='abc']
# ↑ WHICH field, WHAT problem, WHAT bad value — all at once!
# ↑ কোন field, কী সমস্যা, ভুল মান কী — সব একসাথে!
✅
EN: Pydantic reports all errors together, not just the first — fix everything in one go.
বাং: Pydantic সব error একসাথে দেখায়, শুধু প্রথমটা না — এক দফায় সব ঠিক করে ফেলুন।
3

Fields & Rules

Field ও নিয়ম — সীমা বেঁধে দিন
EN A type says what kind of value ("a number"). Field() says which values are okay ("a number between 1 and 5"). Example: a rating must be 1–5, a price must be above 0.
বাং Type বলে মানটা কী ধরনের ("একটা সংখ্যা")। আর Field() বলে কোন মানগুলো ঠিক আছে ("১ থেকে ৫-এর মধ্যে সংখ্যা")। যেমন: rating হতে হবে ১–৫, দাম হতে হবে ০-এর বেশি।
from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0)                  # gt = greater than / এর বেশি
    rating: int = Field(default=5, ge=1, le=5) # 1 থেকে 5
    sku: str = Field(pattern=r"^[A-Z]{3}-\d{4}$") # e.g. ABC-1234
RuleMeaningঅর্থFor
gt / gegreater / greater-or-equalবেশি / সমান বা বেশিnumbers
lt / leless / less-or-equalকম / সমান বা কমnumbers
min_length / max_lengthlength limitsদৈর্ঘ্যের সীমাstr, list
patternmust match regexregex-এর সাথে মিলতে হবেstr
default_factoryfresh default each timeপ্রতিবার নতুন defaultlist, dict

Free ready-made types / ফ্রি তৈরি-করা type

from pydantic import BaseModel, EmailStr, HttpUrl, SecretStr

class Account(BaseModel):
    email: EmailStr      # checks REAL email format / আসল email কিনা যাচাই
    website: HttpUrl     # must be a valid URL / বৈধ URL হতে হবে
    api_key: SecretStr   # hidden when printed! / print করলে লুকানো!

acc = Account(email="dev@x.com", website="https://x.com", api_key="sk-123")
print(acc.api_key)   # SecretStr('**********') 🔒 — safe in logs / log-এ নিরাপদ
🔒
EN: Always use SecretStr for API keys & passwords — they will never leak into your logs.
বাং: API key ও password-এ সবসময় SecretStr — ভুলেও log-এ ফাঁস হবে না।
4

Nested Models

Model-এর ভেতরে Model
EN Real JSON is rarely flat — an order has an address inside it, and a list of items. Just use one model as the type of another field. Pydantic checks the whole tree, every level.
বাং বাস্তবের JSON খুব কমই সরল হয় — একটা order-এর ভেতরে address থাকে, item-এর list থাকে। শুধু একটা model-কে আরেক field-এর type বানিয়ে দিন। Pydantic পুরো গাছটাই চেক করবে, প্রতিটা স্তর।
class Address(BaseModel):
    city: str
    postal_code: str = Field(pattern=r"^\d{4}$")   # e.g. 1207

class Item(BaseModel):
    product: str
    quantity: int = Field(ge=1)

class Order(BaseModel):
    customer: str
    address: Address          # ← one model inside / ভেতরে একটা model
    items: list[Item]         # ← a LIST of models / model-এর list

order = Order.model_validate({
    "customer": "Nadia",
    "address": {"city": "Dhaka", "postal_code": "1207"},
    "items": [{"product": "Mouse", "quantity": 2}],
})
print(order.address.city)      # Dhaka
print(order.items[0].quantity) # 2
🎯
EN: Error paths point into the tree: items.1.quantity = "2nd item's quantity is wrong". In a 500-row payload this saves hours.
বাং: Error-এর ঠিকানা গাছের ভেতর পর্যন্ত যায়: items.1.quantity মানে "২য় item-এর quantity ভুল"। ৫০০ row-এর ডেটায় এটা ঘণ্টার পর ঘণ্টা বাঁচায়।
5

Custom Validators

নিজের Validator — নিজের নিয়ম নিজে লিখুন
EN Built-in rules cover ranges and lengths. But what about "username can't have spaces" or "check-out date must be after check-in"? For those, write your own rule — a small function attached to the model.
বাং Built-in নিয়মে সীমা আর দৈর্ঘ্য হয়ে যায়। কিন্তু "username-এ স্পেস চলবে না" বা "check-out অবশ্যই check-in-এর পরে" — এগুলোর জন্য নিজের নিয়ম লিখুন — model-এর সাথে জোড়া ছোট্ট একটা ফাংশন।

One field → @field_validator / এক field-এর জন্য

from pydantic import BaseModel, field_validator

class SignUp(BaseModel):
    username: str
    password: str

    @field_validator("username")
    @classmethod
    def no_spaces(cls, v):
        if " " in v:
            raise ValueError("No spaces allowed / স্পেস চলবে না")
        return v.lower()   # can also TRANSFORM! / বদলেও দিতে পারে!

u = SignUp(username="RAHIM", password="secret123")
print(u.username)   # "rahim" — lowercase হয়ে গেছে ✨

Multiple fields together → @model_validator / একাধিক field একসাথে

from datetime import date
from pydantic import BaseModel, model_validator

class Booking(BaseModel):
    check_in: date
    check_out: date

    @model_validator(mode="after")
    def dates_ok(self):
        if self.check_out <= self.check_in:
            raise ValueError("check_out must be after check_in / পরে হতে হবে")
        return self

Clean messy input first → mode="before" / আগে ময়লা পরিষ্কার

class Post(BaseModel):
    tags: list[str]

    @field_validator("tags", mode="before")
    @classmethod
    def split_string(cls, v):
        # Accept "a,b,c" OR ["a","b","c"] — both! / দুটোই নিন!
        if isinstance(v, str):
            return [t.strip() for t in v.split(",")]
        return v

Post(tags="ml, ai, backend").tags   # ['ml', 'ai', 'backend'] ✅
🤖
LLM tip / LLM টিপ: AI models often send "5" instead of 5, or a comma-string instead of a list. mode="before" cleans it before validation. — AI প্রায়ই 5-এর বদলে "5" পাঠায়। mode="before" সেটা validation-এর আগেই ঠিক করে দেয়।
6

Model → JSON

Serialization — ডেটা বাইরে পাঠানো
EN Validation = data coming in. Serialization = data going out (API response, database, logs). Two methods do it all: model_dump() → dict, model_dump_json() → JSON string.
বাং Validation = ডেটা ভেতরে আসা। Serialization = ডেটা বাইরে যাওয়া (API response, database, log)। দুটো method-ই সব করে: model_dump() → dict, model_dump_json() → JSON string।
from datetime import datetime

class Event(BaseModel):
    name: str
    when: datetime

e = Event(name="Deploy", when="2026-07-03T10:30:00")

e.model_dump()        # {'name': 'Deploy', 'when': datetime(...)}  ← dict
e.model_dump_json()   # '{"name":"Deploy","when":"2026-07-03T10:30:00"}'
                      # datetime → ISO string automatically! / নিজে নিজেই!

# Control the output / output নিয়ন্ত্রণ:
e.model_dump(include={"name"})       # only these / শুধু এগুলো
e.model_dump(exclude_none=True)      # drop None fields / None বাদ
e.model_dump(exclude_unset=True)     # only fields user actually sent ⭐

The PATCH trick / PATCH-এর কৌশল

class UserUpdate(BaseModel):
    name: str | None = None
    email: str | None = None

@app.patch("/users/{user_id}")
def update_user(user_id: int, patch: UserUpdate):
    changes = patch.model_dump(exclude_unset=True)
    # User sent only {"name": "New"} → changes has ONLY name!
    # email untouched / email অক্ষত থাকল — এটাই আসল PATCH ✅
    db.update(user_id, **changes)
⭐
EN: exclude_unset=True = "give me only what the client actually sent." The secret of every clean PATCH endpoint.
বাং: exclude_unset=True মানে "client যা পাঠিয়েছে শুধু তাই দাও।" প্রতিটা পরিষ্কার PATCH endpoint-এর গোপন রহস্য।
7

Settings & Config

Backend Config — .env থেকে নিরাপদে
EN Every backend needs config: database URL, API keys, model paths. Hardcoding = dangerous. Scattered os.getenv() = messy. pydantic-settings reads your .env into one validated class — and checks everything at startup.
বাং প্রতিটা backend-এ config লাগে: database URL, API key, model path। Hardcode করা = বিপজ্জনক। জায়গায় জায়গায় os.getenv() = অগোছালো। pydantic-settings আপনার .env পড়ে একটা validated class বানায় — আর startup-এই সব চেক করে ফেলে।
pip install pydantic-settings
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env")

    app_name: str = "my-api"
    debug: bool = False
    database_url: str                          # required — no default!
    anthropic_api_key: SecretStr               # hidden in logs 🔒
    max_batch_size: int = Field(default=32, ge=1, le=256)

settings = Settings()   # reads env + .env, validates ALL / সব validate
# .env file / .env ফাইল
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
ANTHROPIC_API_KEY=sk-ant-xxxx
DEBUG=true
MAX_BATCH_SIZE=64
🌙
EN: If DATABASE_URL is missing, the app fails at startup with a clear message — not at 3 AM when the first user hits a broken connection.
বাং: DATABASE_URL না থাকলে অ্যাপ startup-এই fail করবে স্পষ্ট বার্তা দিয়ে — রাত ৩টায় প্রথম user এসে ভাঙা connection-এ পড়ার সময় নয়।
8

Pydantic + FastAPI

দুজনে মিলে API তৈরি
EN FastAPI is built on top of Pydantic. Every request body, every 422 error, every schema in /docs — that's Pydantic working. Here Pydantic does 3 jobs: validate input, filter output, generate docs.
বাং FastAPI তৈরিই হয়েছে Pydantic-এর উপরে। প্রতিটা request body, প্রতিটা 422 error, /docs-এর প্রতিটা schema — সবই Pydantic-এর কাজ। এখানে Pydantic ৩টা কাজ করে: input যাচাই, output ছাঁকা, docs তৈরি।
from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr

app = FastAPI()

# INPUT model — has password / password আছে
class UserCreate(BaseModel):
    name: str = Field(min_length=1)
    email: EmailStr
    password: str = Field(min_length=8)

# OUTPUT model — NO password! / password নেই!
class UserOut(BaseModel):
    id: int
    name: str
    email: EmailStr

db = {}

@app.post("/users/", response_model=UserOut, status_code=201)
def create_user(user: UserCreate):
    new_id = len(db) + 1
    db[new_id] = {"id": new_id, **user.model_dump()}
    return db[new_id]
    # ↑ this dict HAS the password...
    # ...but response_model FILTERS it out! 🛡️
    # ↑ dict-এ password আছে... কিন্তু response_model ছেঁকে ফেলে দেয়!
#Jobকাজ
1Validates input — bad JSON → automatic 422Input যাচাই — খারাপ JSON → স্বয়ংক্রিয় 422
2Filters output — response_model drops extra fields (like password)Output ছাঁকে — বাড়তি field (যেমন password) বাদ
3Makes docs — open /docs, everything is documentedDocs বানায় — /docs খুলুন, সব লেখা আছে

Path + Query + Body together / তিনটা একসাথে

class ReviewIn(BaseModel):
    rating: int = Field(ge=1, le=5)
    comment: str = Field(max_length=500)

@app.post("/products/{product_id}/reviews")
def add_review(
    product_id: int,        # in the URL path → path param
    notify: bool = False,   # simple + not in path → query param
    review: ReviewIn = ..., # Pydantic model → request body
):
    return {"ok": True}
🧠
How FastAPI decides / FastAPI যেভাবে ঠিক করে: in the path → path param · Pydantic model → body · everything else → query param. — path-এ আছে → path param · Pydantic model → body · বাকি সব → query param। একটাই নিয়ম, শূন্য config।
9

Pydantic for ML

ML Model Serve করা
EN An ML model is the pickiest data user you'll ever have. Wrong order, a missing value, or a number outside the training range — and it quietly gives garbage predictions. Pydantic is the guard at your /predict door.
বাং ML model হলো ডেটার সবচেয়ে খুঁতখুঁতে ব্যবহারকারী। ক্রম ভুল, মান missing, বা training range-এর বাইরের সংখ্যা — model চুপচাপ আবর্জনা prediction দেবে। Pydantic হলো আপনার /predict দরজার প্রহরী।
from enum import Enum
from pydantic import BaseModel, Field
import joblib

model = joblib.load("iris_model.pkl")

# INPUT — exactly what the model NEEDS / model-এর যা লাগবেই
class IrisFeatures(BaseModel):
    sepal_length: float = Field(gt=0, le=10)
    sepal_width:  float = Field(gt=0, le=10)
    petal_length: float = Field(gt=0, le=10)
    petal_width:  float = Field(gt=0, le=10)

    def to_vector(self):
        # GUARANTEED correct order / নিশ্চিত সঠিক ক্রম
        return [self.sepal_length, self.sepal_width,
                self.petal_length, self.petal_width]

# OUTPUT — typed prediction / typed prediction
class Prediction(BaseModel):
    species: str
    confidence: float = Field(ge=0, le=1)

@app.post("/predict", response_model=Prediction)
def predict(features: IrisFeatures):
    # Here, data is COMPLETE, TYPED, IN-RANGE — guaranteed ✅
    # এই লাইনে ডেটা সম্পূর্ণ, typed, সীমার মধ্যে — নিশ্চিত
    proba = model.predict_proba([features.to_vector()])[0]
    idx = proba.argmax()
    return Prediction(species=str(idx), confidence=round(float(proba[idx]), 4))

Batch prediction — protect your server / সার্ভার বাঁচান

class BatchRequest(BaseModel):
    rows: list[IrisFeatures] = Field(min_length=1, max_length=256)
    # max_length stops a 1-million-row request from killing your CPU!
    # max_length ১০ লাখ row-এর request থেকে CPU বাঁচায়!
📉
Catch data drift at the door / দরজাতেই drift ধরুন: Set le=/ge= to your training data's range. If callers send values far outside it, they get a clear 422 — and the failed requests become a signal that reality has drifted. — le=/ge= দিন training range অনুযায়ী। বাইরের মান এলে পরিষ্কার 422 — আর fail হওয়া request-ই সংকেত দেবে যে বাস্তব বদলে গেছে।
🎯
The golden pattern / সোনালি প্যাটার্ন: Features (in) + Prediction (out) + to_vector() = a complete contract for any sklearn / XGBoost / PyTorch model. — যেকোনো ML model-এর জন্য এটাই সম্পূর্ণ চুক্তি।
10

Pydantic for LLM

Chat ও RAG অ্যাপে
EN LLM apps have two risky doors: (1) user input going to the model, (2) the model's reply coming back. Pydantic guards both. Start with the request going in.
বাং LLM অ্যাপে দুটো ঝুঁকির দরজা: (১) user input যা model-এর দিকে যায়, (২) model-এর উত্তর যা ফিরে আসে। Pydantic দুটোই পাহারা দেয়। শুরু করি ভেতরে যাওয়া request দিয়ে।
from typing import Literal
from pydantic import BaseModel, Field

class Message(BaseModel):
    role: Literal["system", "user", "assistant"]  # ONLY these 3 / শুধু ৩টা
    content: str = Field(min_length=1, max_length=32_000)

class ChatRequest(BaseModel):
    messages: list[Message] = Field(min_length=1, max_length=100)
    temperature: float = Field(default=0.7, ge=0, le=2)
    max_tokens: int = Field(default=1024, ge=1, le=8192)
RuleWhat it buys youকী দেয়
Literal[...] on rolea typo like "users" fails instantly"users"-এর মতো typo সাথে সাথে আটকায়
max_length on contentprotects your token budget & wallettoken budget ও পকেট বাঁচায়
max_length on messagescaps chat history sizehistory-র আকার সীমিত রাখে
ge/le on temperatureenforces valid range before the APIAPI-র আগেই বৈধ সীমা প্রয়োগ

RAG query model / RAG-এর request model

class RAGQuery(BaseModel):
    question: str = Field(min_length=3, max_length=2000)
    top_k: int = Field(default=5, ge=1, le=20)      # how many chunks / কতগুলো
    collection: str = Field(pattern=r"^[a-z0-9_-]+$") # safe names only
🛡️
EN: The pattern on collection is quiet security — it blocks path-traversal names like ../secrets from ever reaching your vector DB.
বাং: collection-এর pattern নীরব নিরাপত্তা — ../secrets-এর মতো নাম vector DB পর্যন্ত পৌঁছাতেই দেয় না।
11

LLM Structured Output

সবচেয়ে দামি প্যাটার্ন ⭐
EN This is the #1 most valuable pattern in modern AI work. LLMs give you text; your program needs data. The recipe in 4 steps: 1) define a model, 2) send its JSON schema to the LLM, 3) validate the reply, 4) if it fails, send the error back and let the LLM fix it.
বাং এটাই আধুনিক AI-এর সবচেয়ে দামি প্যাটার্ন। LLM দেয় text; আপনার দরকার data। ৪ ধাপের রেসিপি: ১) model লিখুন, ২) তার JSON schema LLM-কে দিন, ৩) উত্তর validate করুন, ৪) fail করলে error ফেরত পাঠিয়ে LLM-কে ঠিক করতে বলুন।
import json, anthropic
from pydantic import BaseModel, Field, ValidationError

client = anthropic.Anthropic()

# STEP 1 — the shape you want / যে আকৃতি চান
class ResumeInfo(BaseModel):
    name: str
    years_experience: int = Field(ge=0, le=60)
    skills: list[str] = Field(min_length=1)
    seniority: str = Field(pattern=r"^(junior|mid|senior|lead)$")

def extract(text, retries=2):
    # STEP 2 — hand the schema to the LLM / LLM-কে schema দিন
    schema = json.dumps(ResumeInfo.model_json_schema())
    prompt = f"Extract as JSON matching this schema. JSON only.\n{schema}\n\n{text}"

    for _ in range(retries + 1):
        resp = client.messages.create(model="claude-sonnet-4-6",
                                       max_tokens=1000,
                                       messages=[{"role":"user","content":prompt}])
        raw = resp.content[0].text.strip()
        raw = raw.removeprefix("\`\`\`json").removesuffix("\`\`\`")
        try:
            # STEP 3 — validate / validate করুন
            return ResumeInfo.model_validate_json(raw)
        except ValidationError as e:
            # STEP 4 — retry WITH the error as the new prompt!
            # error-টাকেই নতুন prompt বানিয়ে আবার চেষ্টা!
            prompt = f"Your JSON had errors:\n{e}\nFix & return JSON only:\n{raw}"
    raise RuntimeError("LLM could not produce valid JSON / পারল না")
🔁
Why the retry loop works / retry কেন কাজ করে: Pydantic's errors are so precise ("years_experience: should be a valid integer") that the LLM almost always self-corrects in one try. You're using validation errors as prompts. — Pydantic-এর error এতই নিখুঁত যে LLM প্রায় সবসময় এক চেষ্টাতেই শুধরে নেয়। আপনি error-কেই prompt বানাচ্ছেন।

Tool / function calling / Tool calling

class WeatherQuery(BaseModel):
    """Get current weather for a city."""
    city: str = Field(description="City name, e.g. 'Dhaka'")
    unit: str = Field(default="celsius", pattern=r"^(celsius|fahrenheit)$")

# ONE model = docs + schema-for-LLM + validator-for-reply
# একটাই model = docs + LLM-এর schema + উত্তরের validator
tool = {
    "name": "get_weather",
    "description": WeatherQuery.__doc__,
    "input_schema": WeatherQuery.model_json_schema(),   # one line! ⚡
}

# When the LLM calls the tool, validate ITS arguments too:
def handle(args: dict):
    q = WeatherQuery.model_validate(args)   # LLM's args, checked! ✅
    return fetch_weather(q.city, q.unit)
📦
EN: Libraries like Instructor, LangChain, and PydanticAI just package this exact pattern — now you know what's under the hood.
বাং: Instructor, LangChain, PydanticAI ঠিক এই প্যাটার্নটাই প্যাকেজ করে — এখন আপনি জানেন ভেতরে কী আছে।
12

Advanced Patterns

এডভান্সড — কাজে লাগবে এমন কৌশল

1 · Enum & Literal — fixed choices / নির্দিষ্ট option

EN When only a few values are allowed (status = pending/running/done), use Enum or Literal. Anything else → clear error listing the valid options.
বাং যখন শুধু কয়েকটা মান চলবে (status = pending/running/done), Enum বা Literal ব্যবহার করুন। অন্য কিছু দিলে বৈধ option দেখিয়ে পরিষ্কার error।
from enum import Enum

class Status(str, Enum):    # str + Enum → JSON-friendly
    pending = "pending"
    running = "running"
    done    = "done"

class Task(BaseModel):
    status: Status = Status.pending

Task(status="running")   # ✅
Task(status="paused")    # ❌ lists the 3 valid values / ৩টা বৈধ মান দেখায়

2 · Immutable (frozen) models / অপরিবর্তনীয় model

from pydantic import BaseModel, ConfigDict

class Config(BaseModel):
    model_config = ConfigDict(frozen=True)   # can't change after creation
    temperature: float

c = Config(temperature=0.7)
c.temperature = 1.5   # ❌ frozen! "who changed my settings?" bugs vanish
                    # "কে আমার settings বদলাল?" bug উধাও

3 · computed_field — auto-calculated output / হিসাব-করা মান

from pydantic import BaseModel, computed_field

class Usage(BaseModel):
    input_tokens: int
    output_tokens: int

    @computed_field
    @property
    def total(self) -> int:
        return self.input_tokens + self.output_tokens

Usage(input_tokens=1200, output_tokens=400).model_dump()
# {'input_tokens': 1200, 'output_tokens': 400, 'total': 1600}
# 'total' auto-appears in the JSON! / নিজে থেকেই JSON-এ যোগ হয়!

4 · TypeAdapter — validate without a class / class ছাড়াই যাচাই

from pydantic import TypeAdapter

# "Just check this is a list of floats" — no model needed
# "শুধু দেখো এটা float-এর list কিনা" — model লাগবে না
adapter = TypeAdapter(list[float])
embedding = adapter.validate_python([0.12, -0.5, 0.98])  # ✅ great for embeddings
13

Cheat Sheet

শেষ কথা ও চিটশিট

7 golden rules / ৭টি সোনালি নিয়ম

#Rule (EN)নিয়ম (বাংলা)
1Validate at every boundary — inside, trust your typesপ্রতিটা সীমান্তে যাচাই — ভেতরে type-কে বিশ্বাস
2Separate In / Out models (password in one, never the other)In / Out আলাদা রাখুন (password একটাতে, অন্যটাতে কখনো না)
3Constrain to reality (trained on 0–10 → le=10)বাস্তবতা অনুযায়ী সীমা (0–10 হলে le=10)
4Name by direction: ...Request / ...Predictionদিক অনুযায়ী নাম: ...Request / ...Prediction
5Cap every list with max_length (saves compute & bill)প্রতি list-এ max_length (compute ও বিল বাঁচায়)
6SecretStr for anything secretগোপন সবকিছুতে SecretStr
7Fail at startup, not at 3 AM (validate config with BaseSettings)রাত ৩টায় নয়, startup-এ fail করুন

Method cheat sheet / Method চিটশিট

MethodDoes whatকী করে
Model(**kwargs)create + validateতৈরি + যাচাই
.model_validate(dict)validate a dictdict যাচাই
.model_validate_json(str)validate raw JSONকাঁচা JSON যাচাই
.model_dump()model → dictmodel → dict
.model_dump_json()model → JSON stringmodel → JSON string
.model_json_schema()model → JSON Schema (for LLMs!)model → JSON Schema (LLM-এর জন্য!)

When to use what / কখন কোনটা

Situation / পরিস্থিতিUse / ব্যবহার
Simple type + range / সাধারণ type + সীমাField(gt=..., le=...)
One field custom logic / এক field নিজস্ব logic@field_validator
Rule across fields / একাধিক field জুড়ে@model_validator(mode="after")
Clean messy input / ময়লা input পরিষ্কারmode="before"
Fixed choices / নির্দিষ্ট optionEnum / Literal
App config / অ্যাপ configBaseSettings
Never changes / কখনো বদলাবে নাfrozen=True
🏁
The whole book in one line / পুরো বই এক লাইনে: Pydantic turns your type hints into enforceable contracts — guarding every boundary of a modern backend, ML, or LLM system. Learn one library, protect the whole system. — Pydantic আপনার type hint-কে বলবৎযোগ্য চুক্তিতে বদলায় — backend, ML, LLM-এর প্রতিটা সীমান্ত পাহারা দেয়। একটা লাইব্রেরি শিখুন, পুরো সিস্টেম রক্ষা করুন।

🎯 Practice projects / অনুশীলন প্রজেক্ট

  • User API — build UserCreate/UserOut; make sure password never appears in any response. — password যেন কোনো response-এ না থাকে।
  • ML endpoint — wrap any sklearn model with Features + Prediction, batch capped at 100. — batch ১০০-তে সীমিত।
  • LLM extractor — use the schema→validate→retry loop to pull structured data from messy text. — অগোছালো text থেকে structured ডেটা বের করুন।
  • Config refactor — move every hardcoded value into a Settings class with a .env. — সব hardcoded মান .env-সহ Settings-এ সরান।
Foysal Ahmed Pranto
Author · লেখক
📞 01313-990906
Pydantic Mastery — a bilingual guide for backend, AI, ML & LLM engineers.
Backend, AI, ML ও LLM ইঞ্জিনিয়ারদের জন্য দ্বিভাষিক গাইড। শুভ validation! ✅