IlmHamroh
Python kursi/Turlar tizimi8/9-dars21 daqiqa
Mundarija (21)

13.8-dars: Runtime validatsiya (pydantic bilan tanishuv)

13-QISM — TURLAR TIZIMI · 8-dars


1. Kirish va motivatsiya

13.7-darsda mypy sizning kodingizni tekshirdi. Lekin tashqaridan keladigan ma'lumot — JSON so'rov, forma, konfiguratsiya fayli, boshqa servisning javobi — kod emas: mypy uni ko'rmaydi.

python
def buyurtma_yarat(xom: dict) -> Buyurtma:
    return Buyurtma(id=xom["id"], summa=Decimal(xom["summa"]))

Tashqi tomon "summa": "ikki yuz ming" yuborsa, mypy jim; xato ish vaqtida, Decimal ichida chiqadi — chalkash xabar bilan va foydalanuvchiga tushunarsiz shaklda.

Real vaziyat. To'lov API si amount maydonini "250000.00" satr sifatida kutardi. Mijoz ilovasi yangilanishdan keyin 250000.0 (son) yubora boshladi — Python kodi Decimal(float) orqali 250000.00000000000582... hosil qildi va hisob-kitoblar tiyin darajasida buzildi. Xato uch hafta sezilmadi.

Yechim — chegarada ish vaqtida validatsiya: tashqi ma'lumotni ishonchli turlarga aylantirish va noto'g'ri bo'lsa aniq xato qaytarish. pydantic shu ish uchun standart vosita bo'lib qoldi: u annotatsiyalarni shartnoma sifatida ishlatadi.

Bu darsda:

  • BaseModel: annotatsiyalardan validatsiya
  • Turlarni keltirish (coercion) va qat'iy (strict) rejim
  • Field cheklovlari va Annotated
  • Validatorlar: field_validator, model_validator
  • ValidationError tuzilishi va foydalanuvchiga xabar
  • Serializatsiya: model_dump, model_dump_json, alias, JSON Schema
  • Narxi va chegarasi: qayerda pydantic, qayerda dataclass
  • Amaliy: API chegarasidan domen modeliga

2. Nazariya — chuqur tushuntirish

2.1. BaseModel

python
from pydantic import BaseModel


class Manzil(BaseModel):
    shahar: str
    kocha: str | None = None


class Buyurtma(BaseModel):
    id: int
    mijoz: str
    summa: Decimal
    manzil: Manzil | None = None
  • Annotatsiyalar — ish vaqtida majburlanadigan shartnoma
  • Ichma-ich modellar avtomatik validatsiya qilinadi
  • Noto'g'ri ma'lumot — ValidationError (hamma xatolar ro'yxati bilan)
  • pydantic v2 yadrosi Rust'da yozilgan — validatsiya tez

2.2. Turlarni keltirish va qat'iy rejim

Sukut bo'yicha pydantic mos keladigan qiymatlarni o'giradi:

Kiruvchi int maydon Decimal maydon
5 5 Decimal("5")
"5" 5 Decimal("5")
5.0 5 Decimal("5.0")
"besh" int_parsing decimal_parsing

float dan Decimal ga o'girish aniqlikni yo'qotishi mumkin — pul uchun maydonni Decimal deb e'lon qiling va satr yuborishni talab qiling (strict=True yoki Field(strict=True)).

python
class Buyurtma(BaseModel):
    model_config = ConfigDict(strict=True, extra="forbid")
Sozlama Ma'nosi
strict=True Turlarni o'girmaydi — aniq tur talab qiladi
extra="forbid" Ortiqcha maydonlar — xato (sukut: "ignore")
frozen=True O'zgarmas model
populate_by_name=True Alias bilan birga asl nom ham qabul qilinadi

2.3. Field va Annotated

python
class Foydalanuvchi(BaseModel):
    ism: str = Field(min_length=2, max_length=50)
    yosh: int = Field(ge=0, le=150)
    email: Annotated[str, Field(pattern=r"^[^@]+@[^@]+\.[a-z]+$")]
    teglar: list[str] = Field(default_factory=list)
Cheklov Nima uchun
gt, ge, lt, le Sonlar chegarasi
min_length, max_length Satr va ro'yxat uzunligi
pattern Regulyar ifoda
default_factory O'zgaruvchan sukut qiymat (11.6-dars tuzog'idan himoya)
alias Tashqi nom (order_id → id)
description JSON Schema va hujjat uchun

2.4. Validatorlar

python
class Buyurtma(BaseModel):
    mijoz: str
    holat: Literal["yangi", "yakunlangan"] = "yangi"
    manzil: Manzil | None = None

    @field_validator("mijoz")
    @classmethod
    def ismni_tozala(cls, qiymat: str) -> str:
        return qiymat.strip().title()

    @model_validator(mode="after")
    def manzil_talab(self) -> "Buyurtma":
        if self.holat == "yakunlangan" and self.manzil is None:
            raise ValueError("yakunlangan buyurtmada manzil bo'lishi kerak")
        return self
Validator Qachon Nima uchun
field_validator(..., mode="before") Turga o'girishdan oldin Xom ma'lumotni tozalash (" 12 000 " → 12000)
field_validator(...) (after) O'girishdan keyin Qiymatni normallashtirish, qo'shimcha shart
model_validator(mode="after") Butun model tayyor bo'lgach Maydonlararo qoidalar

2.5. ValidationError

python
try:
    Buyurtma.model_validate(xom)
except ValidationError as xato:
    xato.error_count()      # nechta muammo
    xato.errors()           # [{'loc': ('summa',), 'type': 'greater_than', 'msg': ..., 'input': ...}]
  • Hamma xato birdan qaytadi — foydalanuvchiga to'liq ro'yxat ko'rsatish mumkin
  • loc — maydon yo'li (ichma-ich modellarda ('manzil', 'shahar'))
  • type — mashina o'qiydigan kod (int_parsing, string_too_short, extra_forbidden)
  • HTTP API da odatda 422 javobiga aylantiriladi (20-qism)

2.6. Serializatsiya va sxema

python
b.model_dump()                     # dict (Python obyektlari bilan)
b.model_dump(mode="json")          # JSON ga mos turlar
b.model_dump_json()                # JSON satri
b.model_dump(exclude={"manzil"}, by_alias=True)
Buyurtma.model_validate(xom)       # dict dan
Buyurtma.model_validate_json(matn) # JSON satridan (tezroq)
Buyurtma.model_json_schema()       # JSON Schema (hujjat, OpenAPI)
b.model_copy(update={"holat": "yakunlangan"})

TypeAdapter — model bo'lmagan turlar uchun: TypeAdapter(list[Buyurtma]).validate_python(xom).

2.7. Narxi va chegarasi

dataclass pydantic.BaseModel
Yaratish tezligi Juda tez Sekinroq (validatsiya)
Xotira Kamroq (slots=True) Ko'proq
Validatsiya Yo'q To'liq
JSON Qo'lda O'rnatilgan
Qachon Ichki domen modeli Chegara: API, konfiguratsiya, fayl

Qoida: chegarada pydantic, ichkarida dataclass — 13.6-darsdagi naqshning davomi. Har ichki funksiya chaqiruvida qayta validatsiya qilish — behuda narx.


3. Tez ma'lumotnoma

python
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator, model_validator

class Model(BaseModel):
    model_config = ConfigDict(extra="forbid", strict=False, frozen=False)
    id: int
    nom: str = Field(min_length=2, alias="name")
    teglar: list[str] = Field(default_factory=list)

    @field_validator("nom")
    @classmethod
    def tozala(cls, v: str) -> str: return v.strip()

    @model_validator(mode="after")
    def tekshir(self) -> "Model": return self
Model.model_validate(dict)     Model.model_validate_json(str)
m.model_dump()  m.model_dump_json()  m.model_copy(update=...)
Model.model_json_schema()      TypeAdapter(list[Model])
xato.errors() → loc, type, msg, input

4. Batafsil misollar

Misol 1 — BaseModel asoslari

python
"""Validatsiya va turlarni keltirish; ichma-ich model; xatolar; qat'iy rejim; ortiqcha maydonlar."""

from decimal import Decimal

from pydantic import BaseModel, ConfigDict, ValidationError


class Manzil(BaseModel):
    shahar: str
    kocha: str | None = None


class Buyurtma(BaseModel):
    id: int
    mijoz: str
    summa: Decimal
    manzil: Manzil | None = None


print("=== 1. Oddiy validatsiya ===")
b = Buyurtma(id=1, mijoz="Aziz", summa="250000.50", manzil={"shahar": "Toshkent"})
print(f"  model: {b}")
print(f"  summa turi: {type(b.summa).__name__}, qiymati: {b.summa}")
print(f"  ichma-ich model: {type(b.manzil).__name__}, shahar: {b.manzil.shahar}")

print("\n=== 2. Turlarni keltirish ===")
namunalar = [("5", "satr son"), (5, "butun son"), (5.0, "kasr son"), (True, "bool")]
for qiymat, tavsif in namunalar:
    try:
        m = Buyurtma(id=qiymat, mijoz="A", summa=1)
        print(f"  id={qiymat!r:6} ({tavsif:10}) → {m.id!r} ({type(m.id).__name__})")
    except ValidationError as xato:
        print(f"  id={qiymat!r:6} ({tavsif:10}) → xato: {xato.errors()[0]['type']}")

print("\n=== 3. Noto'g'ri ma'lumot ===")
try:
    Buyurtma(id="besh", mijoz="A", summa="ikki yuz ming")
except ValidationError as xato:
    print(f"  xatolar soni: {xato.error_count()}")
    for e in xato.errors():
        print(f"    {'.'.join(map(str, e['loc'])):8} {e['type']:18} kiruvchi: {e['input']!r}")

print("\n=== 4. ⚠️ float dan Decimal ga ===")
kasr = Buyurtma(id=1, mijoz="A", summa=250000.1)
satr = Buyurtma(id=1, mijoz="A", summa="250000.1")
print(f"  float dan:  {kasr.summa}")
print(f"  satrdan:    {satr.summa}")
print(f"  teng emas: {kasr.summa != satr.summa} — pul uchun satr yuboring yoki strict rejim")

print("\n=== 5. Qat'iy rejim va ortiqcha maydonlar ===")


class QatiyBuyurtma(BaseModel):
    model_config = ConfigDict(strict=True, extra="forbid")

    id: int
    summa: Decimal


print(f"  to'g'ri: {QatiyBuyurtma(id=1, summa=Decimal('10.5'))}")
for xom, tavsif in [({"id": "1", "summa": Decimal("10")}, "id satr sifatida"),
                    ({"id": 1, "summa": 10.5}, "summa float sifatida"),
                    ({"id": 1, "summa": Decimal("10"), "qoshimcha": True}, "ortiqcha maydon")]:
    try:
        QatiyBuyurtma(**xom)
        print(f"  {tavsif:24} → o'tdi")
    except ValidationError as xato:
        print(f"  {tavsif:24} → {xato.errors()[0]['type']}")

Natijaning muhim qismi:

text
=== 1. Oddiy validatsiya ===
  model: id=1 mijoz='Aziz' summa=Decimal('250000.50') manzil=Manzil(shahar='Toshkent', kocha=None)
  summa turi: Decimal, qiymati: 250000.50
  ichma-ich model: Manzil, shahar: Toshkent

=== 2. Turlarni keltirish ===
  id='5'    (satr son  ) → 5 (int)
  id=5      (butun son ) → 5 (int)
  id=5.0    (kasr son  ) → 5 (int)
  id=True   (bool      ) → 1 (int)

=== 3. Noto'g'ri ma'lumot ===
  xatolar soni: 2
    id       int_parsing        kiruvchi: 'besh'
    summa    decimal_parsing    kiruvchi: 'ikki yuz ming'

=== 4. ⚠️ float dan Decimal ga ===
  float dan:  250000.1
  satrdan:    250000.1
  teng emas: False — pul uchun satr yuboring yoki strict rejim

=== 5. Qat'iy rejim va ortiqcha maydonlar ===
  to'g'ri: id=1 summa=Decimal('10.5')
  id satr sifatida         → int_type
  summa float sifatida     → is_instance_of
  ortiqcha maydon          → extra_forbidden

Nima ko'rsatdi: 2.1, 2.2-bo'limlar — kirishdagi float muammosi aynan shunday yuz bergan.

Misol 2 — Field cheklovlari va validatorlar

python
"""Field bilan cheklovlar; before/after validatorlar; model_validator; default_factory; aniq xato xabarlari."""

import datetime as dt
from decimal import Decimal
from typing import Annotated, Literal

from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator


class Mijoz(BaseModel):
    ism: str = Field(min_length=2, max_length=50, description="To'liq ism")
    yosh: int = Field(ge=18, le=120)
    email: Annotated[str, Field(pattern=r"^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$")]   # katta harflar ham, keyin validator kichraytiradi
    teglar: list[str] = Field(default_factory=list)

    @field_validator("ism")
    @classmethod
    def ismni_tozala(cls, qiymat: str) -> str:
        return " ".join(qiymat.split()).title()

    @field_validator("email")
    @classmethod
    def emailni_kichik(cls, qiymat: str) -> str:
        return qiymat.lower()


class Buyurtma(BaseModel):
    id: int = Field(gt=0)
    summa: Decimal = Field(gt=0, le=Decimal("100000000"))
    holat: Literal["yangi", "jarayonda", "yakunlangan"] = "yangi"
    yetkazish_sanasi: dt.date | None = None

    @field_validator("summa", mode="before")
    @classmethod
    def summani_tozala(cls, qiymat: object) -> object:
        """Xom ma'lumotni turga o'girishdan oldin tozalash: '250 000,50 so'm' → '250000.50'."""
        if isinstance(qiymat, str):
            tozalangan = qiymat.replace(" ", "").replace("so'm", "").replace(",", ".")
            return tozalangan or qiymat
        return qiymat

    @model_validator(mode="after")
    def sana_talab(self) -> "Buyurtma":
        if self.holat == "yakunlangan" and self.yetkazish_sanasi is None:
            raise ValueError("yakunlangan buyurtmada yetkazish sanasi bo'lishi shart")
        return self


print("=== 1. Field cheklovlari ===")
print(f"  to'g'ri: {Mijoz(ism='  aziz   karimov ', yosh=30, email='Aziz@Wisar.UZ')}")
for xom, tavsif in [
    ({"ism": "A", "yosh": 30, "email": "a@b.uz"}, "ism juda qisqa"),
    ({"ism": "Aziz", "yosh": 15, "email": "a@b.uz"}, "yosh 18 dan kichik"),
    ({"ism": "Aziz", "yosh": 30, "email": "notogri"}, "email formati"),
]:
    try:
        Mijoz(**xom)
    except ValidationError as xato:
        e = xato.errors()[0]
        print(f"  {tavsif:22} → [{e['type']}] {e['msg']}")

print("\n=== 2. default_factory ===")
m1, m2 = Mijoz(ism="Aziz", yosh=30, email="a@b.uz"), Mijoz(ism="Vali", yosh=40, email="v@b.uz")
m1.teglar.append("vip")
print(f"  m1 teglari: {m1.teglar}, m2 teglari: {m2.teglar} — umumiy ro'yxat emas (11.6-dars)")

print("\n=== 3. before validator: xom ma'lumotni tozalash ===")
for xom in ["250 000,50", "250000.50", " 1 000 so'm", "nomalum"]:
    try:
        b = Buyurtma(id=1, summa=xom)
        print(f"  {xom!r:16} → {b.summa}")
    except ValidationError as xato:
        print(f"  {xom!r:16} → xato: {xato.errors()[0]['type']}")

print("\n=== 4. model_validator: maydonlararo qoida ===")
try:
    Buyurtma(id=1, summa="1000", holat="yakunlangan")
except ValidationError as xato:
    print(f"  sanasiz yakunlangan: {xato.errors()[0]['msg']}")
tayyor = Buyurtma(id=1, summa="1000", holat="yakunlangan", yetkazish_sanasi="2026-09-20")
print(f"  sana bilan: {tayyor.holat}, {tayyor.yetkazish_sanasi} ({type(tayyor.yetkazish_sanasi).__name__})")

print("\n=== 5. Barcha xatolar birdan ===")
try:
    Mijoz(ism="A", yosh=200, email="yomon")
except ValidationError as xato:
    print(f"  {xato.error_count()} ta muammo bir vaqtda:")
    for e in xato.errors():
        print(f"    {e['loc'][0]:6} {e['msg']}")

Natijaning muhim qismi:

text
=== 1. Field cheklovlari ===
  to'g'ri: ism='Aziz Karimov' yosh=30 email='aziz@wisar.uz' teglar=[]
  ism juda qisqa         → [string_too_short] String should have at least 2 characters
  yosh 18 dan kichik     → [greater_than_equal] Input should be greater than or equal to 18
  email formati          → [string_pattern_mismatch] String should match pattern '^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$'

=== 2. default_factory ===
  m1 teglari: ['vip'], m2 teglari: [] — umumiy ro'yxat emas (11.6-dars)

=== 3. before validator: xom ma'lumotni tozalash ===
  '250 000,50'     → 250000.50
  '250000.50'      → 250000.50
  " 1 000 so'm"    → 1000
  'nomalum'        → xato: decimal_parsing

=== 4. model_validator: maydonlararo qoida ===
  sanasiz yakunlangan: Value error, yakunlangan buyurtmada yetkazish sanasi bo'lishi shart
  sana bilan: yakunlangan, 2026-09-20 (date)

=== 5. Barcha xatolar birdan ===
  3 ta muammo bir vaqtda:
    ism    String should have at least 2 characters
    yosh   Input should be less than or equal to 120
    email  String should match pattern '^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$'

Nima ko'rsatdi: 2.3, 2.4-bo'limlar.

Misol 3 — Serializatsiya, alias va sxema

python
"""JSON dan yuklash va JSON ga yozish; tashqi nomlar uchun alias; exclude; JSON Schema; TypeAdapter."""

import json
from decimal import Decimal

from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, ValidationError


class BuyurtmaAPI(BaseModel):
    """Tashqi shakl: kalitlar snake_case va boshqa nomlar bilan (13.6-dars)."""

    model_config = ConfigDict(populate_by_name=True)

    id: int = Field(alias="order_id")
    mijoz: str = Field(alias="customer_name")
    summa: Decimal = Field(alias="total_amount")
    ichki_izoh: str | None = Field(default=None, exclude=True)


XOM_JSON = '{"order_id": 101, "customer_name": "Aziz", "total_amount": "250000.00"}'

print("=== 1. JSON dan yuklash ===")
b = BuyurtmaAPI.model_validate_json(XOM_JSON)
print(f"  model: {b}")
print(f"  ichki nomlar bilan ishlaymiz: b.id={b.id}, b.mijoz={b.mijoz!r}")

print("\n=== 2. Serializatsiya ===")
print(f"  model_dump():            {b.model_dump()}")
print(f"  model_dump(mode='json'): {b.model_dump(mode='json')}")
print(f"  by_alias=True:           {b.model_dump(by_alias=True, mode='json')}")
b.ichki_izoh = "ichki eslatma"
print(f"  exclude=True maydon chiqishda yo'q: {'ichki_izoh' in b.model_dump()}")
print(f"  JSON satri: {b.model_dump_json(by_alias=True)}")

print("\n=== 3. Nusxa va o'zgartirish ===")
yangi = b.model_copy(update={"summa": Decimal("300000.00")})
print(f"  asl: {b.summa}, nusxa: {yangi.summa}, bir xil obyektmi: {b is yangi}")

print("\n=== 4. JSON Schema ===")
sxema = BuyurtmaAPI.model_json_schema()
print(f"  majburiy maydonlar: {sxema['required']}")
print(f"  xususiyatlar: {sorted(sxema['properties'])}")
print(f"  'total_amount' turi: {sxema['properties']['total_amount'].get('anyOf', sxema['properties']['total_amount'])}")

print("\n=== 5. TypeAdapter: model bo'lmagan turlar ===")
royxat_adapter = TypeAdapter(list[BuyurtmaAPI])
xom_royxat = json.loads(f'[{XOM_JSON}, {{"order_id": 102, "customer_name": "Dilnoza", "total_amount": "99000"}}]')
buyurtmalar = royxat_adapter.validate_python(xom_royxat)
print(f"  {len(buyurtmalar)} ta buyurtma: {[b.mijoz for b in buyurtmalar]}")
jami_adapter = TypeAdapter(dict[str, Decimal])
print(f"  dict[str, Decimal]: {jami_adapter.validate_python({'jami': '349000.00'})}")
try:
    royxat_adapter.validate_python([{"order_id": "x", "customer_name": "A", "total_amount": "1"}])
except ValidationError as xato:
    e = xato.errors()[0]
    print(f"  ro'yxat ichidagi xato joyi: {e['loc']} → {e['type']}")

Natijaning muhim qismi:

text
=== 1. JSON dan yuklash ===
  model: id=101 mijoz='Aziz' summa=Decimal('250000.00') ichki_izoh=None
  ichki nomlar bilan ishlaymiz: b.id=101, b.mijoz='Aziz'

=== 2. Serializatsiya ===
  model_dump():            {'id': 101, 'mijoz': 'Aziz', 'summa': Decimal('250000.00')}
  model_dump(mode='json'): {'id': 101, 'mijoz': 'Aziz', 'summa': '250000.00'}
  by_alias=True:           {'order_id': 101, 'customer_name': 'Aziz', 'total_amount': '250000.00'}
  exclude=True maydon chiqishda yo'q: False
  JSON satri: {"order_id":101,"customer_name":"Aziz","total_amount":"250000.00"}

=== 3. Nusxa va o'zgartirish ===
  asl: 250000.00, nusxa: 300000.00, bir xil obyektmi: False

=== 4. JSON Schema ===
  majburiy maydonlar: ['order_id', 'customer_name', 'total_amount']
  xususiyatlar: ['customer_name', 'ichki_izoh', 'order_id', 'total_amount']
  'total_amount' turi: [{'type': 'number'}, {'pattern': '^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$', 'type': 'string'}]

=== 5. TypeAdapter: model bo'lmagan turlar ===
  2 ta buyurtma: ['Aziz', 'Dilnoza']
  dict[str, Decimal]: {'jami': Decimal('349000.00')}
  ro'yxat ichidagi xato joyi: (0, 'order_id') → int_parsing

Nima ko'rsatdi: 2.6-bo'lim.

Misol 4 — Amaliy: API chegarasidan domen modeliga

To'liq zanjir: xom JSON → pydantic validatsiyasi → domen dataclass → biznes mantiq. Xatolar API javobiga (422) aylanadi. Oxirida — narx o'lchovi va "chegarada validatsiya, ichkarida dataclass" qoidasining asosi.

python
"""Xom JSON → pydantic → domen dataclass; xatolarni API javobiga aylantirish; narx o'lchovi."""

import dataclasses
import json
import time
from decimal import Decimal
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, ValidationError


class BuyurtmaSorovi(BaseModel):
    """Chegara modeli: tashqi shakl va barcha tekshiruvlar shu yerda."""

    model_config = ConfigDict(extra="forbid", populate_by_name=True)

    id: int = Field(gt=0, alias="order_id")
    mijoz: str = Field(min_length=2, max_length=60, alias="customer_name")
    summa: Decimal = Field(gt=0, alias="total_amount")
    holat: Literal["yangi", "yakunlangan"] = "yangi"


@dataclasses.dataclass(frozen=True, slots=True)
class Buyurtma:
    """Domen modeli: validatsiya qilingan, tez, o'zgarmas."""

    id: int
    mijoz: str
    summa: Decimal
    holat: str

    def komissiya(self, foiz: Decimal = Decimal("0.02")) -> Decimal:
        return (self.summa * foiz).quantize(Decimal("0.01"))


def domenga(sorov: BuyurtmaSorovi) -> Buyurtma:
    return Buyurtma(sorov.id, sorov.mijoz, sorov.summa, sorov.holat)


def api_javob(xom: object) -> tuple[int, dict[str, object]]:
    """HTTP ishlovchisining soddalashtirilgan modeli."""
    try:
        sorov = BuyurtmaSorovi.model_validate(xom)
    except ValidationError as xato:
        return 422, {"xatolar": [{"maydon": ".".join(map(str, e["loc"])), "sabab": e["type"], "xabar": e["msg"]}
                                 for e in xato.errors()]}
    buyurtma = domenga(sorov)
    return 200, {"id": buyurtma.id, "komissiya": str(buyurtma.komissiya())}


XOM = json.loads('''[
  {"order_id": 101, "customer_name": "Aziz", "total_amount": "250000.00"},
  {"order_id": 102, "customer_name": "Dilnoza", "total_amount": "99000", "holat": "yakunlangan"},
  {"order_id": -1, "customer_name": "X", "total_amount": "0"},
  {"order_id": 104, "customer_name": "Jasur", "total_amount": "1000", "qoshimcha": true},
  {"customer_name": "Kalitsiz", "total_amount": "5000"}
]''')

print("=== 1. API javoblari ===")
qabul: list[Buyurtma] = []
for xom in XOM:
    kod, javob = api_javob(xom)
    if kod == 200:
        qabul.append(domenga(BuyurtmaSorovi.model_validate(xom)))
        print(f"  200 → {javob}")
    else:
        sabablar = ", ".join(f"{x['maydon']}:{x['sabab']}" for x in javob["xatolar"])
        print(f"  422 → {sabablar}")

print("\n=== 2. Ichkarida — domen modeli ===")
for b in qabul:
    print(f"  {b.id}: {b.mijoz:8} {b.summa:>10} komissiya {b.komissiya()}")
print(f"  domen modeli o'zgarmas: {Buyurtma.__dataclass_params__.frozen}, slots: {Buyurtma.__slots__}")

print("\n=== 3. Narx: validatsiya va oddiy yaratish ===")
XOM_BITTA = {"order_id": 101, "customer_name": "Aziz", "total_amount": "250000.00"}
N = 20_000


def olch(f, *args) -> float:
    eng = float("inf")
    for _ in range(3):
        bosh = time.perf_counter()
        for _ in range(N):
            f(*args)
        eng = min(eng, time.perf_counter() - bosh)
    return eng


t_pydantic = olch(BuyurtmaSorovi.model_validate, XOM_BITTA)
t_dataclass = olch(Buyurtma, 101, "Aziz", Decimal("250000.00"), "yangi")
print(f"  {N:,} marta validatsiya dataclass yaratishdan sekinroq: {t_pydantic > t_dataclass}")
print(f"  kamida 3 barobar: {t_pydantic / t_dataclass >= 3}")
print("  ⭐ shuning uchun: validatsiya faqat chegarada, ichki chaqiruvlarda takrorlanmaydi")

print("\n=== 4. Qoidalar ===")
print("  chegara (API, fayl, konfiguratsiya) → pydantic: tekshiradi, o'giradi, xato beradi")
print("  ichkarida (biznes mantiq)           → dataclass: tez, o'zgarmas, xulq bilan")
print("  statik tekshiruv (mypy)             → ikkala qatlamda ham ishlaydi")

Natijaning muhim qismi:

text
=== 1. API javoblari ===
  200 → {'id': 101, 'komissiya': '5000.00'}
  200 → {'id': 102, 'komissiya': '1980.00'}
  422 → order_id:greater_than, customer_name:string_too_short, total_amount:greater_than
  422 → qoshimcha:extra_forbidden
  422 → order_id:missing

=== 2. Ichkarida — domen modeli ===
  101: Aziz      250000.00 komissiya 5000.00
  102: Dilnoza       99000 komissiya 1980.00
  domen modeli o'zgarmas: True, slots: ('id', 'mijoz', 'summa', 'holat')

=== 3. Narx: validatsiya va oddiy yaratish ===
  20,000 marta validatsiya dataclass yaratishdan sekinroq: True
  kamida 3 barobar: True
  ⭐ shuning uchun: validatsiya faqat chegarada, ichki chaqiruvlarda takrorlanmaydi

=== 4. Qoidalar ===
  chegara (API, fayl, konfiguratsiya) → pydantic: tekshiradi, o'giradi, xato beradi
  ichkarida (biznes mantiq)           → dataclass: tez, o'zgarmas, xulq bilan
  statik tekshiruv (mypy)             → ikkala qatlamda ham ishlaydi

Nima ko'rsatdi: 2.5, 2.7-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"mypy bo'lsa, validatsiya kerak emas" mypy tashqi ma'lumotni ko'rmaydi
"pydantic — dataclass ning yaxshiroq varianti" Boshqa maqsad: validatsiya; narxi ham bor
"Har joyda BaseModel ishlatish kerak" Chegarada; ichkarida dataclass tez
"Validatsiya turlarni o'girmaydi" Sukut bo'yicha o'giradi (strict=True bilan yo'q)
"float bilan Decimal xavfsiz" Aniqlik yo'qoladi — satr yuboring
"Xato bitta bo'lib qaytadi" ValidationError hammasini birdan beradi
"Ortiqcha maydonlar xato beradi" Sukut bo'yicha e'tiborsiz qoldiriladi (extra="forbid" kerak)
"pydantic sekin" v2 yadrosi Rust'da; lekin dataclass dan baribir qimmat

6. Keng tarqalgan xatolar va yechimlari

1. Tashqi ma'lumotni tekshirmasdan ishlatish

python
buyurtma = Buyurtma(**json.loads(tana))      # ❌ har qanday narsa kelishi mumkin
sorov = BuyurtmaSorovi.model_validate_json(tana)   # ✅

2. Pul uchun float

python
summa: float                                  # ❌ aniqlik yo'qoladi
summa: Decimal                                # ✅ va satr sifatida yuboring

3. Ortiqcha maydonlarga ruxsat berish

python
class Sorov(BaseModel): ...                   # ⚠️ noto'g'ri yozilgan maydon jim o'tadi
model_config = ConfigDict(extra="forbid")     # ✅

4. O'zgaruvchan sukut qiymat

python
teglar: list[str] = []                        # ❌ (pydantic ogohlantiradi)
teglar: list[str] = Field(default_factory=list)   # ✅

5. Ichki qatlamda qayta validatsiya

python
def hisobla(sorov: BuyurtmaSorovi) -> Decimal:
    yangi = BuyurtmaSorovi.model_validate(sorov.model_dump())   # ⚠️ behuda narx

6. Xatolarni foydalanuvchiga xom holda ko'rsatish

python
return {"error": str(xato)}                   # ⚠️ ichki tafsilotlar va texnik matn
return {"xatolar": [...]}                     # ✅ maydon + tushunarli xabar (Misol 4)

7. Domen mantiqini modelga tiqish

python
class Buyurtma(BaseModel):
    def bazaga_yoz(self) -> None: ...         # ⚠️ chegara modeli — faqat shakl va tekshiruv

8. alias bilan populate_by_name ni unutish

python
id: int = Field(alias="order_id")             # ⚠️ endi Buyurtma(id=1) ishlamaydi
model_config = ConfigDict(populate_by_name=True)   # ✅ ikkalasi ham qabul qilinadi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 9-qism (o'tilgan): xatolar va foydalanuvchiga xabar
  • 13.1-dars (o'tilgan): annotatsiyalar ish vaqtida tekshirilmaydi — pydantic shuni o'zgartiradi
  • 13.6-dars (o'tilgan): TypedDict bilan shakl; pydantic — validatsiya bilan
  • 13.7-dars (o'tilgan): statik tekshiruv bilan birga ishlatish
  • 13.9-dars: qatlamlar va turlar bilan dizayn
  • 16-qism: JSON, YAML, konfiguratsiya fayllari
  • 20-qism: FastAPI — pydantic ustiga qurilgan
  • 23-qism: baza yozuvlari va DTO lar

8. Eng yaxshi amaliyotlar

  1. Chegarada validatsiya qiling — API, fayl, konfiguratsiya, navbat xabarlari.

  2. Ichkarida dataclass — validatsiyani takrorlamang.

  3. extra="forbid" — noto'g'ri maydon nomlari jim o'tmasin.

  4. Pul uchun Decimal va satr ko'rinishidagi kirish.

  5. Xatolarni tushunarli javobga aylantiring (loc, type, xabar).

  6. Maydonlararo qoidalar — model_validator(mode="after").

  7. Tashqi nomlarni alias bilan chegarada qoldiring.

  8. mypy bilan birga ishlating — statik va dinamik tekshiruv bir-birini to'ldiradi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class M(BaseModel):
    id: int
    nom: str = "yoq"
1.  print(M(id=5))
2.  print(M(id="5").id, type(M(id="5").id).__name__)
3.  try:
        M(id="besh")
    except ValidationError as e:
        print(e.errors()[0]["type"])
4.  print(M(id=1, qoshimcha=2).model_dump())
5.  class S(BaseModel):
        model_config = ConfigDict(extra="forbid")
        id: int
    try:
        S(id=1, qoshimcha=2)
    except ValidationError as e:
        print(e.errors()[0]["type"])
6.  print(M(id=1).model_dump_json())
7.  print(M.model_validate({"id": 3}).id)
8.  print(M.model_validate_json('{"id": 4}').nom)
9.  class D(BaseModel):
        summa: Decimal
    print(D(summa="10.50").summa, type(D(summa="10.50").summa).__name__)
10. class F(BaseModel):
        yosh: int = Field(ge=18)
    try:
        F(yosh=10)
    except ValidationError as e:
        print(e.errors()[0]["type"])
11. print(M(id=1).model_copy(update={"nom": "yangi"}).nom)
12. print(sorted(M.model_json_schema()["properties"]))
Javoblar
  1. id=5 nom='yoq'
  2. 5 int — satr butun songa o'girildi
  3. int_parsing
  4. {'id': 1, 'nom': 'yoq'} — ortiqcha maydon sukut bo'yicha e'tiborsiz
  5. extra_forbidden
  6. {"id":1,"nom":"yoq"}
  7. 3
  8. yoq
  9. 10.50 Decimal
  10. greater_than_equal
  11. yangi
  12. ['id', 'nom']

Vazifa 2: Xatolarni tuzating

python
1.  class Tolov(BaseModel):
        summa: float

2.  class Sozlama(BaseModel):
        teglar: list[str] = []

3.  def ishlov(tana: str) -> Buyurtma:
        malumot = json.loads(tana)
        return Buyurtma(**malumot)

4.  class Sorov(BaseModel):
        id: int = Field(alias="order_id")
    Sorov(id=1)

5.  try:
        Sorov.model_validate(xom)
    except ValidationError as e:
        return {"xato": str(e)}
Javoblar
python
1.  class Tolov(BaseModel):
        summa: Decimal = Field(gt=0)          # pul — Decimal, satr sifatida yuboriladi

2.  class Sozlama(BaseModel):
        teglar: list[str] = Field(default_factory=list)

3.  def ishlov(tana: str) -> Buyurtma:
        sorov = BuyurtmaSorovi.model_validate_json(tana)   # validatsiya bilan
        return domenga(sorov)

4.  class Sorov(BaseModel):
        model_config = ConfigDict(populate_by_name=True)   # ikkala nom ham ishlaydi
        id: int = Field(alias="order_id")

5.  except ValidationError as e:
        return {"xatolar": [{"maydon": ".".join(map(str, x["loc"])), "xabar": x["msg"]} for x in e.errors()]}

Vazifa 3: Konfiguratsiya modeli

Ilova konfiguratsiyasi uchun pydantic model yozing:

  1. Baza URL, port (1–65535), log darajasi (Literal), ishchi jarayonlar soni, xususiyat bayroqlari (dict[str, bool])
  2. Muhit o'zgaruvchilaridan va YAML/JSON fayldan yuklash
  3. Noto'g'ri konfiguratsiyada ilovani ishga tushirmasdan aniq xato chiqarish
  4. Sirlarni (parol, token) model_dump dan chiqarib tashlash
  5. Testlar: to'g'ri, noto'g'ri va qisman konfiguratsiyalar

Vazifa 4: Tashqi API mijozi

Tanlagan ochiq API uchun:

  1. Javob modellarini yozing (ichma-ich tuzilmalar bilan)
  2. alias bilan tashqi nomlarni ichki nomlarga bog'lang
  3. Kutilmagan maydonlar kelganda nima bo'lishini sinab ko'ring (extra sozlamasi bilan)
  4. Domen modellariga o'giruvchi funksiya yozing
  5. Yozib olingan (fixture) javoblar bilan testlar yozing

Vazifa 5: Xatolar tarjimasi

  1. ValidationError ni foydalanuvchiga tushunarli o'zbekcha xabarlarga aylantiruvchi funksiya yozing
  2. type kodlari bo'yicha xabarlar lug'atini tuzing (int_parsing, string_too_short, missing, ...)
  3. Ichma-ich maydonlar uchun yo'lni o'qiladigan ko'rinishga keltiring (manzil.shahar)
  4. Formadagi maydonlarga bog'lash uchun JSON javob formatini loyihalang
  5. 10 xil xato holati uchun natijani ko'rsating

Vazifa 6: Narx o'lchovi

  1. 100 000 yozuvni uch usulda yarating: dataclass, pydantic (validatsiya bilan), pydantic model_construct (validatsiyasiz)
  2. Vaqt va xotirani o'lchang
  3. model_validate_json va json.loads + model_validate ni solishtiring
  4. Natijalar asosida "qayerda validatsiya qilish kerak" qoidasini asoslang

Vazifa 7: O'ylash

Go'da encoding/json teglari va qo'lda tekshiruv, Java'da Bean Validation (@NotNull, @Size), TypeScript'da zod yoki io-ts, Rust'da serde + validator. Python'da pydantic shu vazifani annotatsiyalar orqali bajaradi. Annotatsiyalarni ham statik tekshiruv, ham ish vaqtidagi validatsiya uchun ishlatish qanday afzallik va xavflarga ega?

Javob

Qisqa javob: bitta manba (annotatsiya) ikki maqsadga xizmat qiladi — bu takrorlanishni yo'qotadi va model bilan tekshiruv doim mos bo'ladi. Xavf esa shundaki, statik va dinamik semantika har doim ham bir xil emas: pydantic turlarni o'giradi, mypy esa o'girmaydi; natijada kod "tekshiruvdan o'tgan", lekin ish vaqtida boshqacha xulq qilishi mumkin.

1. Boshqa ekotizimlar

Platforma Validatsiya Manba
Go Qo'lda yoki validator teglari Struct teglari (alohida til)
Java Bean Validation annotatsiyalari Annotatsiyalar (turlardan alohida)
TypeScript zod, io-ts Sxemadan tur chiqariladi (teskari yo'nalish)
Rust serde + validator Atributlar (makroslar)
Python pydantic Turlar annotatsiyasining o'zi

TypeScript'dagi zod yondashuvi qiziq: u yerda sxema birlamchi, tur esa undan chiqariladi (z.infer). Python'da esa aksincha — tur birlamchi.

2. Afzalliklari

  1. Bitta manba: model o'zgarsa, validatsiya ham o'zgaradi
  2. Hujjat tekin: JSON Schema/OpenAPI avtomatik (FastAPI, 20-qism)
  3. IDE qo'llab-quvvatlashi: maydonlar avtoto'ldiriladi
  4. O'rganish oson: yangi sintaksis yo'q — oddiy annotatsiyalar

3. Xavflari

Xavf Misol
Statik va dinamik semantika farqi mypy: summa: Decimal ga float bermaysiz; pydantic: o'giradi
Import paytidagi narx Modellar yaratish yig'ish vaqtini oshiradi
Ish vaqtidagi narx Har validatsiya — hisob; issiq yo'lda qimmat
"Hamma joyda model" vasvasasi Domen mantiqi chegara modeliga yopishib qoladi
Kutubxonaga bog'lanish Model ta'riflari pydantic ga bog'liq bo'lib qoladi

4. Amaliy qoidalar

  1. Chegarada pydantic, ichkarida dataclass — semantika farqi bitta joyda qoladi
  2. strict=True bilan o'girishni cheklang — statik va dinamik xulq yaqinlashadi
  3. Modelni shakl va tekshiruv uchun ishlating, biznes mantiqni alohida qatlamga chiqaring
  4. mypy va pydantic ni birga ishlating: biri kodni, ikkinchisi ma'lumotni tekshiradi

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda ish vaqtidagi validatsiyani o'rgandik.

Eng muhim uch fikr:

  1. Statik tekshiruv tashqi ma'lumotni ko'rmaydi. mypy sizning kodingizdagi shartnomalarni tekshiradi; JSON so'rov, forma yoki konfiguratsiya fayli esa ish vaqtida keladi. pydantic annotatsiyalarni ish vaqtida majburlanadigan shartnomaga aylantiradi: turlarni tekshiradi, keltiradi va noto'g'ri bo'lsa ValidationError beradi — barcha muammolar ro'yxati bilan.

  2. Sozlamalar xulqni belgilaydi. Sukut bo'yicha pydantic mos qiymatlarni o'giradi ("5" → 5) va ortiqcha maydonlarni e'tiborsiz qoldiradi — shuning uchun extra="forbid" va kerak bo'lsa strict=True qo'shiladi. Pul uchun Decimal va satr ko'rinishidagi kirish: float orqali o'tgan qiymat aniqlikni yo'qotadi. Field cheklovlari, field_validator va model_validator qolgan qoidalarni beradi.

  3. Chegarada validatsiya, ichkarida dataclass. Har ichki chaqiruvda qayta validatsiya — behuda narx (o'lchov bo'yicha bir necha barobar). To'g'ri zanjir: xom JSON → pydantic modeli (tekshiruv, alias, o'girish) → domen dataclass i (tez, o'zgarmas, xulq bilan) → biznes mantiq; xatolar esa foydalanuvchiga tushunarli javobga aylanadi.

Keyingi darsda 13-qismni yakunlaymiz: turlar bilan loyiha dizayni — qatlamlar, portlar va adapterlar, "noto'g'ri holatni ifodalab bo'lmaydigan" modellar va turlar madaniyatini jamoada joriy qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
13.8-dars: Runtime validatsiya (pydantic bilan tanishuv) — IlmHamroh