IlmHamroh
Python kursi/Web backend FastAPI9/16-dars20 daqiqa
Mundarija (22)

20.9-dars: CRUD

20-QISM — WEB-BACKEND (FastAPI) · 9-dars


1. Kirish va motivatsiya

Oldingi darslarda barcha qismlarni o'rgandik: parametrlar, Pydantic modellari, status kodlar, bog'liqliklar, baza ulanishi. Endi ularni CRUD ga birlashtirami: Create (yaratish), Read (o'qish), Update (yangilash), Delete (o'chirish) — har API'ning to'rt asosiy amali.

CRUD — resurs bilan ishlashning to'liq to'plami. "Kurs" resursi uchun: kurs yaratish (POST), ro'yxat va bitta kurs o'qish (GET), kurs yangilash (PUT/PATCH), kurs o'chirish (DELETE). Bu naqsh har resursda (foydalanuvchi, izoh, buyurtma) takrorlanadi — bir marta o'rgansangiz, hamma joyda qo'llaysiz.

Real vaziyat. Bir jamoa har resurs uchun endpointlarni turlicha yozardi: biri POST /kurs_qosh, boshqasi PUT /update_user, uchinchisi GET /get-comments — nomlar, status kodlar, javob shakllari har xil edi. Mijoz ilovasi har resurs uchun boshqacha kod yozishga majbur edi. Jamoa CRUD standartiga o'tdi: har resurs POST /X, GET /X, GET /X/{id}, PUT /X/{id}, DELETE /X/{id} — bir xil naqsh, bir xil status kodlar. Endi yangi resurs qo'shish — tayyor shablonni to'ldirish. Kod izchil, mijoz oddiy bo'ldi.

Bu darsda to'liq CRUD'ni — to'rt amal, toza tuzilma va to'g'ri status kodlar bilan o'rganamiz.

Bu darsda:

  • CRUD to'rt amali (Create, Read, Update, Delete)
  • Kirish va chiqish modellari
  • To'liq yangilash (PUT) va qisman (PATCH)
  • exclude_unset bilan qisman yangilash
  • To'g'ri status kodlar (201, 200, 204, 404)
  • Ro'yxat va bitta resurs
  • Toza kod tuzilishi
  • Amaliy: to'liq CRUD API

ℹ Misollarda FastAPI TestClient va temp baza bilan sinaladi.


2. Nazariya — chuqur tushuntirish

2.1. CRUD va HTTP metodlari

CRUD to'rt amali HTTP metodlariga mos keladi 20.2-bob:

Amal HTTP Yo'l Status
Create POST /kurslar 201
Read (ro'yxat) GET /kurslar 200
Read (bitta) GET /kurslar/{id} 200 / 404
Update (to'liq) PUT /kurslar/{id} 200 / 404
Update (qisman) PATCH /kurslar/{id} 200 / 404
Delete DELETE /kurslar/{id} 204 / 404

CRUD — REST resurs naqshi: bir resurs (/kurslar), turli metodlar turli amal. Bu izchil va bashoratli.

2.2. Kirish va chiqish modellari

CRUD uchun bir necha model 20.5-bob:

python
class KursKirish(BaseModel):        # yaratish uchun
    nom: str
    narx: float

class KursYangilash(BaseModel):     # qisman yangilash (barcha ixtiyoriy)
    nom: str | None = None
    narx: float | None = None

class KursJavob(BaseModel):         # chiqish (id bilan)
    id: int
    nom: str
    narx: float

Kirish (yaratish), yangilash (qisman, ixtiyoriy) va chiqish (id bilan) — alohida modellar. Har biri o'z maqsadiga mos.

2.3. Create (POST)

Yangi resurs yaratish — 201 va yaratilgan resurs:

python
@app.post("/kurslar", status_code=201, response_model=KursJavob)
def yarat(kurs: KursKirish, db: ...):
    cur = db.execute("INSERT INTO kurslar (nom, narx) VALUES (?, ?)", (kurs.nom, kurs.narx))
    db.commit()
    return {"id": cur.lastrowid, "nom": kurs.nom, "narx": kurs.narx}

POST — yaratadi (201 Created), yaratilgan resursni (id bilan) qaytaradi. response_model javobni shakllaydi.

2.4. Read (GET)

O'qish — ro'yxat va bitta:

python
@app.get("/kurslar", response_model=list[KursJavob])
def royxat(db: ..., hajm: int = 10):
    return [dict(q) for q in db.execute("SELECT * FROM kurslar LIMIT ?", (hajm,)).fetchall()]

@app.get("/kurslar/{kurs_id}", response_model=KursJavob)
def bitta(kurs_id: int, db: ...):
    q = db.execute("SELECT * FROM kurslar WHERE id = ?", (kurs_id,)).fetchone()
    if q is None:
        raise HTTPException(404, "topilmadi")
    return dict(q)

Ro'yxat — list[Model]; bitta — topilsa resurs, topilmasa 404. GET — hech narsani o'zgartirmaydi (xavfsiz, 20.2).

2.5. Update (PUT — to'liq)

PUT — resursni to'liq almashtiradi:

python
@app.put("/kurslar/{kurs_id}", response_model=KursJavob)
def yangila(kurs_id: int, kurs: KursKirish, db: ...):
    cur = db.execute("UPDATE kurslar SET nom = ?, narx = ? WHERE id = ?", (kurs.nom, kurs.narx, kurs_id))
    db.commit()
    if cur.rowcount == 0:
        raise HTTPException(404, "topilmadi")
    return {"id": kurs_id, "nom": kurs.nom, "narx": kurs.narx}

PUT — barcha maydonni beradi (to'liq almashtirish). rowcount == 0 — resurs yo'q → 404.

2.6. Update (PATCH — qisman)

PATCH — faqat berilgan maydonni o'zgartiradi (exclude_unset):

python
@app.patch("/kurslar/{kurs_id}", response_model=KursJavob)
def qisman(kurs_id: int, kurs: KursYangilash, db: ...):
    maydonlar = kurs.model_dump(exclude_unset=True)   # faqat berilganlar
    if not maydonlar:
        raise HTTPException(400, "hech narsa berilmadi")
    set_qism = ", ".join(f"{k} = ?" for k in maydonlar)
    qiymatlar = list(maydonlar.values()) + [kurs_id]
    cur = db.execute(f"UPDATE kurslar SET {set_qism} WHERE id = ?", qiymatlar)
    db.commit()
    ...

model_dump(exclude_unset=True) — foydalanuvchi bergan maydonlarnigina qaytaradi. PATCH bilan bir maydonni (masalan, faqat narx) yangilash mumkin.

set_qism ustun nomlari koddan (foydalanuvchi kiritganidan emas) keladi — Pydantic model maydonlari. Qiymatlar esa har doim parametrlangan (?). Bu SQL injection'dan xavfsiz.

2.7. Delete (DELETE)

O'chirish — 204 (tanasiz):

python
@app.delete("/kurslar/{kurs_id}", status_code=204)
def ochir(kurs_id: int, db: ...):
    cur = db.execute("DELETE FROM kurslar WHERE id = ?", (kurs_id,))
    db.commit()
    if cur.rowcount == 0:
        raise HTTPException(404, "topilmadi")

DELETE — o'chiradi (204 No Content, tanasiz). Yo'q resursni o'chirishda rowcount == 0 → 404.

2.8. PUT va PATCH farqi

Xususiyat PUT PATCH
Ma'no To'liq almashtirish Qisman o'zgartirish
Maydonlar Barchasi Faqat berilgan
Model Barcha majburiy Barcha ixtiyoriy
Misol Butun kursni yangilash Faqat narxni o'zgartirish

PUT — resursni butunlay beradi; PATCH — bir qismini. Ikkalasi ham idempotent bo'lishi maqsad 20.2-bob, lekin PATCH moslashuvchanroq.


3. Tez ma'lumotnoma

python
# Create
@app.post("/kurslar", status_code=201, response_model=KursJavob)
def yarat(kurs: KursKirish, db: ...): ...

# Read (ro'yxat + bitta)
@app.get("/kurslar", response_model=list[KursJavob])
def royxat(db: ...): ...
@app.get("/kurslar/{id}", response_model=KursJavob)
def bitta(id: int, db: ...): ...

# Update (to'liq + qisman)
@app.put("/kurslar/{id}", response_model=KursJavob)
def yangila(id: int, kurs: KursKirish, db: ...): ...
@app.patch("/kurslar/{id}", response_model=KursJavob)
def qisman(id: int, kurs: KursYangilash, db: ...): ...   # exclude_unset

# Delete
@app.delete("/kurslar/{id}", status_code=204)
def ochir(id: int, db: ...): ...

Status kodlar

POST → 201 · GET → 200 / 404 · PUT/PATCH → 200 / 404 · DELETE → 204 / 404

4. Batafsil misollar

Misollarda FastAPI TestClient va temp baza bilan sinaladi.

Misol 1 — Create va Read

python
"""Create (POST 201); Read ro'yxat (GET list); Read bitta (GET 404); response_model bilan chiqish; id qaytarish."""

import warnings
warnings.filterwarnings("ignore")

import os
import sqlite3
import tempfile
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

TMP = tempfile.mkdtemp()
DB = os.path.join(TMP, "wisar.db")


def sozla() -> None:
    conn = sqlite3.connect(DB)
    conn.execute("CREATE TABLE IF NOT EXISTS kurslar (id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT, narx REAL)")
    conn.commit()
    conn.close()


def baza():
    conn = sqlite3.connect(DB)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
    finally:
        conn.close()


app = FastAPI()
sozla()


class KursKirish(BaseModel):
    nom: str
    narx: float


class KursJavob(BaseModel):
    id: int
    nom: str
    narx: float


@app.post("/kurslar", status_code=201, response_model=KursJavob)
def yarat(kurs: KursKirish, db: Annotated[sqlite3.Connection, Depends(baza)]):
    cur = db.execute("INSERT INTO kurslar (nom, narx) VALUES (?, ?)", (kurs.nom, kurs.narx))
    db.commit()
    return {"id": cur.lastrowid, "nom": kurs.nom, "narx": kurs.narx}


@app.get("/kurslar", response_model=list[KursJavob])
def royxat(db: Annotated[sqlite3.Connection, Depends(baza)]):
    return [dict(q) for q in db.execute("SELECT id, nom, narx FROM kurslar ORDER BY id").fetchall()]


@app.get("/kurslar/{kurs_id}", response_model=KursJavob)
def bitta(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)]):
    q = db.execute("SELECT id, nom, narx FROM kurslar WHERE id = ?", (kurs_id,)).fetchone()
    if q is None:
        raise HTTPException(status_code=404, detail="kurs topilmadi")
    return dict(q)


def main() -> None:
    client = TestClient(app)

    print("=== 1. Create (POST → 201) ===")
    javob = client.post("/kurslar", json={"nom": "Python", "narx": 100})
    print(f"  status {javob.status_code} → {javob.json()}")
    client.post("/kurslar", json={"nom": "Go", "narx": 120})

    print("\n=== 2. Read ro'yxat (GET) ===")
    print(f"  → {[(k['id'], k['nom']) for k in client.get('/kurslar').json()]}")

    print("\n=== 3. Read bitta (GET) ===")
    print(f"  /kurslar/1 → {client.get('/kurslar/1').json()}")

    print("\n=== 4. ⚠️ Topilmadi (404) ===")
    print(f"  /kurslar/999 → status {client.get('/kurslar/999').status_code}")
    print("  ⭐ POST → 201; GET ro'yxat → list; GET bitta → resurs yoki 404")


if __name__ == "__main__":
    main()
    import shutil
    shutil.rmtree(TMP, ignore_errors=True)

Natijaning muhim qismi:

text
=== 1. Create (POST → 201) ===
  status 201 → {'id': 1, 'nom': 'Python', 'narx': 100.0}

=== 2. Read ro'yxat (GET) ===
  → [(1, 'Python'), (2, 'Go')]

=== 3. Read bitta (GET) ===
  /kurslar/1 → {'id': 1, 'nom': 'Python', 'narx': 100.0}

=== 4. ⚠️ Topilmadi (404) ===
  /kurslar/999 → status 404
  ⭐ POST → 201; GET ro'yxat → list; GET bitta → resurs yoki 404

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

Misol 2 — Update (PUT) va Delete

python
"""Update to'liq (PUT); rowcount bilan 404; Delete (204); yo'q resursni o'chirish 404; idempotentlik."""

import warnings
warnings.filterwarnings("ignore")

import os
import sqlite3
import tempfile
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.testclient import TestClient
from pydantic import BaseModel

TMP = tempfile.mkdtemp()
DB = os.path.join(TMP, "wisar.db")


def sozla() -> None:
    conn = sqlite3.connect(DB)
    conn.execute("CREATE TABLE IF NOT EXISTS kurslar (id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT, narx REAL)")
    conn.executemany("INSERT INTO kurslar (nom, narx) VALUES (?, ?)", [("Python", 100), ("Go", 120)])
    conn.commit()
    conn.close()


def baza():
    conn = sqlite3.connect(DB)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
    finally:
        conn.close()


app = FastAPI()
sozla()


class KursKirish(BaseModel):
    nom: str
    narx: float


@app.put("/kurslar/{kurs_id}")
def yangila(kurs_id: int, kurs: KursKirish, db: Annotated[sqlite3.Connection, Depends(baza)]):
    cur = db.execute("UPDATE kurslar SET nom = ?, narx = ? WHERE id = ?", (kurs.nom, kurs.narx, kurs_id))
    db.commit()
    if cur.rowcount == 0:
        raise HTTPException(status_code=404, detail="kurs topilmadi")
    return {"id": kurs_id, "nom": kurs.nom, "narx": kurs.narx}


@app.delete("/kurslar/{kurs_id}", status_code=status.HTTP_204_NO_CONTENT)
def ochir(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)]):
    cur = db.execute("DELETE FROM kurslar WHERE id = ?", (kurs_id,))
    db.commit()
    if cur.rowcount == 0:
        raise HTTPException(status_code=404, detail="kurs topilmadi")


def main() -> None:
    client = TestClient(app)

    print("=== 1. Update (PUT) ===")
    javob = client.put("/kurslar/1", json={"nom": "Python 3.14", "narx": 150})
    print(f"  status {javob.status_code} → {javob.json()}")

    print("\n=== 2. ⚠️ Yo'q resursni yangilash (404) ===")
    print(f"  PUT /kurslar/999 → status {client.put('/kurslar/999', json={'nom': 'X', 'narx': 1}).status_code}")

    print("\n=== 3. Delete (204) ===")
    javob = client.delete("/kurslar/2")
    print(f"  DELETE /kurslar/2 → status {javob.status_code}, tana bo'sh: {javob.text == ''}")

    print("\n=== 4. ⚠️ Yo'q resursni o'chirish (404) ===")
    print(f"  DELETE /kurslar/2 (qayta) → status {client.delete('/kurslar/2').status_code}")
    print("  ⭐ PUT/DELETE — rowcount 0 → 404; DELETE → 204 (tanasiz)")


if __name__ == "__main__":
    main()
    import shutil
    shutil.rmtree(TMP, ignore_errors=True)

Natijaning muhim qismi:

text
=== 1. Update (PUT) ===
  status 200 → {'id': 1, 'nom': 'Python 3.14', 'narx': 150.0}

=== 2. ⚠️ Yo'q resursni yangilash (404) ===
  PUT /kurslar/999 → status 404

=== 3. Delete (204) ===
  DELETE /kurslar/2 → status 204, tana bo'sh: True

=== 4. ⚠️ Yo'q resursni o'chirish (404) ===
  DELETE /kurslar/2 (qayta) → status 404
  ⭐ PUT/DELETE — rowcount 0 → 404; DELETE → 204 (tanasiz)

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

Misol 3 — Qisman yangilash (PATCH)

python
"""PATCH qisman yangilash; exclude_unset (faqat berilgan maydon); bir maydonni o'zgartirish; bo'sh so'rov 400; PUT vs PATCH."""

import warnings
warnings.filterwarnings("ignore")

import os
import sqlite3
import tempfile
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

TMP = tempfile.mkdtemp()
DB = os.path.join(TMP, "wisar.db")


def sozla() -> None:
    conn = sqlite3.connect(DB)
    conn.execute("CREATE TABLE IF NOT EXISTS kurslar (id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT, narx REAL, til TEXT DEFAULT 'uz')")
    conn.execute("INSERT INTO kurslar (nom, narx, til) VALUES ('Python', 100, 'uz')")
    conn.commit()
    conn.close()


def baza():
    conn = sqlite3.connect(DB)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
    finally:
        conn.close()


app = FastAPI()
sozla()

RUXSAT_MAYDONLAR = {"nom", "narx", "til"}


class KursYangilash(BaseModel):
    nom: str | None = None
    narx: float | None = None
    til: str | None = None


@app.patch("/kurslar/{kurs_id}")
def qisman(kurs_id: int, kurs: KursYangilash, db: Annotated[sqlite3.Connection, Depends(baza)]):
    maydonlar = kurs.model_dump(exclude_unset=True)
    if not maydonlar:
        raise HTTPException(status_code=400, detail="hech qanday maydon berilmadi")
    # ustun nomlari model'dan (ishonchli), qiymatlar parametrlangan
    set_qism = ", ".join(f"{k} = ?" for k in maydonlar if k in RUXSAT_MAYDONLAR)
    qiymatlar = [v for k, v in maydonlar.items() if k in RUXSAT_MAYDONLAR] + [kurs_id]
    cur = db.execute(f"UPDATE kurslar SET {set_qism} WHERE id = ?", qiymatlar)
    db.commit()
    if cur.rowcount == 0:
        raise HTTPException(status_code=404, detail="kurs topilmadi")
    q = db.execute("SELECT id, nom, narx, til FROM kurslar WHERE id = ?", (kurs_id,)).fetchone()
    return dict(q)


def main() -> None:
    client = TestClient(app)

    print("=== 1. Faqat narxni yangilash ===")
    javob = client.patch("/kurslar/1", json={"narx": 150})
    print(f"  PATCH narx=150 → {javob.json()}")

    print("\n=== 2. Faqat nomni yangilash ===")
    javob = client.patch("/kurslar/1", json={"nom": "Python 3.14"})
    print(f"  PATCH nom → {javob.json()}")

    print("\n=== 3. ⚠️ Bo'sh so'rov (400) ===")
    print(f"  PATCH {{}} → status {client.patch('/kurslar/1', json={}).status_code}")

    print("\n=== 4. ⚠️ Yo'q resurs (404) ===")
    print(f"  PATCH /kurslar/999 → status {client.patch('/kurslar/999', json={'narx': 1}).status_code}")
    print("  ⭐ PATCH — exclude_unset bilan faqat berilgan maydonni o'zgartiradi")


if __name__ == "__main__":
    main()
    import shutil
    shutil.rmtree(TMP, ignore_errors=True)

Natijaning muhim qismi:

text
=== 1. Faqat narxni yangilash ===
  PATCH narx=150 → {'id': 1, 'nom': 'Python', 'narx': 150.0, 'til': 'uz'}

=== 2. Faqat nomni yangilash ===
  PATCH nom → {'id': 1, 'nom': 'Python 3.14', 'narx': 150.0, 'til': 'uz'}

=== 3. ⚠️ Bo'sh so'rov (400) ===
  PATCH {} → status 400

=== 4. ⚠️ Yo'q resurs (404) ===
  PATCH /kurslar/999 → status 404
  ⭐ PATCH — exclude_unset bilan faqat berilgan maydonni o'zgartiradi

Nima ko'rsatdi: 2.6, 2.8-bo'limlar.

Misol 4 — Amaliy: to'liq CRUD API

Barcha CRUD amallarini bitta toza API'da birlashtiramiz: yaratish, ro'yxat (sahifalash), bitta o'qish, to'liq yangilash (PUT), qisman yangilash (PATCH), o'chirish — har biri to'g'ri status kodi va model bilan. Bu — real API'ning to'liq resurs boshqaruvi.

python
"""to'liq CRUD: Create (201), Read (ro'yxat + bitta), Update (PUT + PATCH), Delete (204); response_model; 404; toza tuzilma."""

import warnings
warnings.filterwarnings("ignore")

import os
import sqlite3
import tempfile
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.testclient import TestClient
from pydantic import BaseModel

TMP = tempfile.mkdtemp()
DB = os.path.join(TMP, "wisar.db")


def sozla() -> None:
    conn = sqlite3.connect(DB)
    conn.execute("CREATE TABLE IF NOT EXISTS kurslar (id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT NOT NULL, narx REAL)")
    conn.commit()
    conn.close()


def baza():
    conn = sqlite3.connect(DB)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
    finally:
        conn.close()


app = FastAPI(title="Wisar Kurslar CRUD API")
sozla()


class KursKirish(BaseModel):
    nom: str
    narx: float


class KursYangilash(BaseModel):
    nom: str | None = None
    narx: float | None = None


class KursJavob(BaseModel):
    id: int
    nom: str
    narx: float


def _kurs_top(db: sqlite3.Connection, kurs_id: int) -> dict:
    q = db.execute("SELECT id, nom, narx FROM kurslar WHERE id = ?", (kurs_id,)).fetchone()
    if q is None:
        raise HTTPException(status_code=404, detail="kurs topilmadi")
    return dict(q)


@app.post("/kurslar", status_code=201, response_model=KursJavob)
def yarat(kurs: KursKirish, db: Annotated[sqlite3.Connection, Depends(baza)]):
    cur = db.execute("INSERT INTO kurslar (nom, narx) VALUES (?, ?)", (kurs.nom, kurs.narx))
    db.commit()
    return {"id": cur.lastrowid, "nom": kurs.nom, "narx": kurs.narx}


@app.get("/kurslar", response_model=list[KursJavob])
def royxat(db: Annotated[sqlite3.Connection, Depends(baza)], hajm: int = 10):
    return [dict(q) for q in db.execute("SELECT id, nom, narx FROM kurslar ORDER BY id LIMIT ?", (hajm,)).fetchall()]


@app.get("/kurslar/{kurs_id}", response_model=KursJavob)
def bitta(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)]):
    return _kurs_top(db, kurs_id)


@app.put("/kurslar/{kurs_id}", response_model=KursJavob)
def yangila(kurs_id: int, kurs: KursKirish, db: Annotated[sqlite3.Connection, Depends(baza)]):
    _kurs_top(db, kurs_id)
    db.execute("UPDATE kurslar SET nom = ?, narx = ? WHERE id = ?", (kurs.nom, kurs.narx, kurs_id))
    db.commit()
    return {"id": kurs_id, "nom": kurs.nom, "narx": kurs.narx}


@app.patch("/kurslar/{kurs_id}", response_model=KursJavob)
def qisman(kurs_id: int, kurs: KursYangilash, db: Annotated[sqlite3.Connection, Depends(baza)]):
    _kurs_top(db, kurs_id)
    maydonlar = kurs.model_dump(exclude_unset=True)
    if maydonlar:
        set_qism = ", ".join(f"{k} = ?" for k in maydonlar)
        db.execute(f"UPDATE kurslar SET {set_qism} WHERE id = ?", list(maydonlar.values()) + [kurs_id])
        db.commit()
    return _kurs_top(db, kurs_id)


@app.delete("/kurslar/{kurs_id}", status_code=status.HTTP_204_NO_CONTENT)
def ochir(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)]):
    _kurs_top(db, kurs_id)
    db.execute("DELETE FROM kurslar WHERE id = ?", (kurs_id,))
    db.commit()


def main() -> None:
    client = TestClient(app)

    print("=== 1. Create ===")
    client.post("/kurslar", json={"nom": "Python", "narx": 100})
    client.post("/kurslar", json={"nom": "Go", "narx": 120})
    print(f"  ro'yxat: {[(k['id'], k['nom']) for k in client.get('/kurslar').json()]}")

    print("\n=== 2. Update (PUT to'liq) ===")
    print(f"  PUT /kurslar/1 → {client.put('/kurslar/1', json={'nom': 'Py', 'narx': 150}).json()}")

    print("\n=== 3. Update (PATCH qisman) ===")
    print(f"  PATCH narx=200 → {client.patch('/kurslar/1', json={'narx': 200}).json()}")

    print("\n=== 4. Delete va yakuniy holat ===")
    print(f"  DELETE /kurslar/2 → status {client.delete('/kurslar/2').status_code}")
    print(f"  qolgan: {[k['nom'] for k in client.get('/kurslar').json()]}")
    print("  ⭐ to'liq CRUD — 5 amal, izchil status kodlar, toza tuzilma")


if __name__ == "__main__":
    main()
    import shutil
    shutil.rmtree(TMP, ignore_errors=True)

Natijaning muhim qismi:

text
=== 1. Create ===
  ro'yxat: [(1, 'Python'), (2, 'Go')]

=== 2. Update (PUT to'liq) ===
  PUT /kurslar/1 → {'id': 1, 'nom': 'Py', 'narx': 150.0}

=== 3. Update (PATCH qisman) ===
  PATCH narx=200 → {'id': 1, 'nom': 'Py', 'narx': 200.0}

=== 4. Delete va yakuniy holat ===
  DELETE /kurslar/2 → status 204
  qolgan: ['Py']
  ⭐ to'liq CRUD — 5 amal, izchil status kodlar, toza tuzilma

Nima ko'rsatdi: 2.1–2.8-bo'limlar.


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

Noto'g'ri fikr To'g'risi
"Har amal uchun boshqacha nom" CRUD standart naqshi
"Bir model hammaga" Kirish/yangilash/chiqish alohida
"PUT va PATCH bir xil" PUT to'liq, PATCH qisman
"Yaratishda 200" 201 (Created)
"O'chirishda tana" 204 (tanasiz)
"Topilmasa bo'sh javob" 404
"PATCHda barcha maydon" exclude_unset — faqat berilgan
"Takror kod normal" Yordamchi funksiya (_kurs_top)

6. Keng tarqalgan xatolar va yechimlari

1. Noto'g'ri status kod

python
@app.post("/kurslar")                       # ⚠️ 200
@app.post("/kurslar", status_code=201)      # ✅

2. PATCHda exclude_unset unutish

python
maydonlar = kurs.model_dump()               # ❌ None'lar ham
maydonlar = kurs.model_dump(exclude_unset=True)   # ✅

3. rowcount tekshirmaslik

python
db.execute("UPDATE ... WHERE id = ?", ...)  # ⚠️ yo'q id sezilmaydi
if cur.rowcount == 0: raise HTTPException(404)   # ✅

4. Kirish va chiqish bir model

python
class Kurs(BaseModel):                       # ⚠️ id kirishda keraksiz
# ✅ KursKirish (id yo'q) + KursJavob (id bilan)

5. PATCHda to'liq model (majburiy maydon)

python
class KursYangilash(BaseModel):
    nom: str                                 # ❌ majburiy
    nom: str | None = None                   # ✅ ixtiyoriy

6. Takror topilmadi mantigi

python
# har endpointda if q is None: raise ...     # ⚠️ takror
def _kurs_top(db, id): ...                    # ✅ yordamchi funksiya

7. commit unutish (yangilash saqlanmaydi)

python
db.execute("UPDATE ...", ...)                # ⚠️
db.execute("UPDATE ...", ...); db.commit()   # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 20.2-dars (o'tilgan): REST — CRUD ↔ HTTP metodlari
  • 20.5-dars (o'tilgan): Pydantic — kirish/chiqish modellari
  • 20.8-dars (o'tilgan): baza — CRUD SQL asosi
  • 20.11-dars: ruxsatlar — kim qaysi amalni bajaradi
  • 20.15-dars: testlash — CRUD endpointlarini test qilish

8. Eng yaxshi amaliyotlar

  1. CRUD standart naqshini ishlating (POST/GET/PUT/PATCH/DELETE).

  2. Kirish, yangilash va chiqish modellarini ajrating.

  3. To'g'ri status kodlar: 201, 200, 204, 404.

  4. PATCHda exclude_unset — faqat berilgan maydon.

  5. Har o'zgartirishda rowcount bilan 404.

  6. Takror mantiqni yordamchi funksiyaga (_kurs_top).

  7. response_model bilan javobni shakllang.

  8. Har INSERT/UPDATE/DELETEdan keyin commit.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # CRUD to'rt amali?
2.  # Create uchun status kod?
3.  # Delete uchun status kod?
4.  # PUT va PATCH farqi?
5.  # exclude_unset nima qiladi?
6.  # topilmasa status kod?
7.  # kirish va chiqish modeli nega alohida?
8.  # rowcount 0 nima bildiradi?
9.  # GET resursni o'zgartiradimi?
10. # PATCH modelida maydonlar qanday?
11. # response_model nima uchun?
12. # yordamchi funksiya (_kurs_top) nega?
Javoblar
  1. Create, Read, Update, Delete
  2. 201 (Created)
  3. 204 (No Content)
  4. PUT — to'liq almashtirish, PATCH — qisman
  5. Faqat berilgan maydonni qaytaradi
  6. 404
  7. Har biri o'z maqsadiga (kirishda id yo'q, chiqishda bor)
  8. Resurs yo'q (o'zgarmadi/o'chmadi) → 404
  9. Yo'q (xavfsiz)
  10. Barchasi ixtiyoriy (| None = None)
  11. Javobni shakllaydi va hujjatlaydi
  12. Takror 404 mantiqini bir joyga (DRY)

Vazifa 2: Xatolarni tuzating

python
1.  @app.post("/kurslar")               # status?
    def yarat(): ...

2.  maydonlar = kurs.model_dump()        # exclude_unset?

3.  db.execute("UPDATE ...", ...)        # rowcount?
    return {...}

4.  class KursYangilash(BaseModel):
        nom: str                         # PATCH majburiy?

5.  db.execute("DELETE ...", ...)        # commit?
Javoblar
python
1.  @app.post("/kurslar", status_code=201)

2.  maydonlar = kurs.model_dump(exclude_unset=True)

3.  cur = db.execute("UPDATE ...", ...); db.commit()
    if cur.rowcount == 0: raise HTTPException(404)

4.  class KursYangilash(BaseModel):
        nom: str | None = None

5.  db.execute("DELETE ...", ...); db.commit()

Vazifa 3: Foydalanuvchi CRUD

Foydalanuvchi uchun to'liq CRUD:

  1. Modellar: Kirish (ism, email, parol), Yangilash (ixtiyoriy), Javob (parolsiz)
  2. Barcha 5 amal
  3. To'g'ri status kodlar
  4. TestClient bilan test

Vazifa 4: PUT vs PATCH

Bir resursda ikkalasi:

  1. PUT — barcha maydon majburiy
  2. PATCH — faqat berilgan
  3. Farqni ko'rsatish (bir maydon o'zgartirish)
  4. Har biri uchun test

Vazifa 5: Sahifalash va filtr bilan CRUD

Kurslar CRUD + Read boyitish:

  1. Ro'yxatda sahifalash (sahifa, hajm)
  2. Filtr (til, daraja)
  3. Saralash (sort)
  4. To'liq test

Vazifa 6: Ikki resurs CRUD

Kurslar va izohlar:

  1. Har biri uchun to'liq CRUD
  2. Izoh kursga bog'liq (kurs_id)
  3. Kurs izohlarini olish
  4. Test

Vazifa 7: O'ylash

CRUD — takrorlanuvchi naqsh: har resurs (kurs, foydalanuvchi, izoh) bir xil besh amal, bir xil status kodlar, bir xil tuzilma bilan. Bu naqshni bir marta o'rganib, cheksiz resursga qo'llaysiz. "Naqsh" (pattern) tushunchasi dasturlashda nima uchun kuchli, va CRUD kabi standart naqshni bilishning afzalligi nima (yangi resursni yozishda, jamoada ishlashda, kod o'qishda)?

Javob

Qisqa javob: Naqsh — takrorlanuvchi muammoga takrorlanuvchi yechim. CRUD besh amal har resursda bir xil ishlaydi, shuning uchun uni bir marta o'rganib, har resursga qo'llaysiz — har safar noldan o'ylash shart emas. Afzalligi: tezlik (tayyor shablon), izchillik (barcha resurs bir xil), muloqot (jamoa "CRUD" desa, hamma tushunadi), o'qish (kod bashoratli). Standart naqsh — jamoa umumiy tili va tayyor yechim.

1. Naqsh nima

Naqsh — takrorlanuvchi muammo (resursni boshqarish) uchun sinab ko'rilgan, umumiy yechim (CRUD). U "qanday" ni standartlashtiradi.

2. CRUD naqshining afzalligi

Afzallik Tafsilot
Tezlik Tayyor shablon, noldan emas
Izchillik Har resurs bir xil
Muloqot "CRUD" — umumiy til
O'qish Kod bashoratli
Xatosiz Sinab ko'rilgan yechim

3. Naqsh muloqot tili sifatida

Jamoada "bu resursga CRUD kerak" deganda, hamma nimani nazarda tutishni biladi — 5 amal, status kodlar, tuzilma. Naqsh — qisqartma: ko'p ma'lumot bir so'zda.

4. Naqshlar boshqa joyda

Naqsh Muammo
CRUD Resurs boshqaruvi
MVC UI/mantiq ajratish
Observer Hodisaga obuna
Factory Obyekt yaratish
REST API dizayni

5. Muhandislik saboqlari

  1. Naqsh — takrorlanuvchi yechim, qayta o'ylash shart emas
  2. CRUD — resurs uchun standart, tez va izchil
  3. Naqsh — jamoa umumiy tili
  4. Standartni bilish — tezlik va muloqot

6. Xulosa

  1. CRUD — har resursda bir xil naqsh
  2. Bir marta o'rgan, cheksiz qo'lla
  3. Naqsh — tezlik, izchillik, muloqot
  4. Standart naqshni bilish — muhandis kuchi

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda to'liq CRUD'ni o'rgandik — resurs bilan ishlashning to'rt asosiy amali.

Eng muhim uch fikr:

  1. CRUD — resurs naqshi. To'rt amal (Create, Read, Update, Delete) HTTP metodlariga mos keladi: POST (yaratish, 201), GET (o'qish — ro'yxat va bitta, 200/404), PUT/PATCH (yangilash, 200/404), DELETE (o'chirish, 204/404). Bu izchil, bashoratli naqsh har resursda (kurs, foydalanuvchi, izoh) bir xil qo'llanadi — bir marta o'rganib, hamma joyda ishlatiladi.

  2. Modellar va status kodlar. CRUD bir necha model ishlatadi: kirish (yaratish, idsiz), yangilash (qisman, barcha maydon ixtiyoriy), chiqish (response_model, id bilan). To'g'ri status kodlar HTTP semantikasini beradi. Har o'zgartirishda rowcount == 0 tekshiriladi — resurs yo'q bo'lsa 404.

  3. PUT, PATCH va toza tuzilma. PUT resursni to'liq almashtiradi (barcha maydon majburiy); PATCH qisman o'zgartiradi (model_dump(exclude_unset=True) — faqat berilgan maydon). Qisman yangilashda ustun nomlari model'dan (ishonchli) keladi, qiymatlar parametrlangan (?) — injection'dan xavfsiz. Takror mantiq (_kurs_top kabi yordamchi funksiya) bir joyga yig'iladi — bu DRY va toza kod.

Keyingi darsda autentifikatsiya (JWT) ni o'rganamiz: foydalanuvchini tizimga kiritish, JWT token yaratish va tekshirish, va himoyalangan endpointlarni Depends bilan qo'riqlash.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
20.9-dars: CRUD — IlmHamroh