Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. CRUD va HTTP metodlari
- 2.2. Kirish va chiqish modellari
- 2.3. Create (POST)
- 2.4. Read (GET)
- 2.5. Update (PUT — to'liq)
- 2.6. Update (PATCH — qisman)
- 2.7. Delete (DELETE)
- 2.8. PUT va PATCH farqi
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Create va Read
- Misol 2 — Update (PUT) va Delete
- Misol 3 — Qisman yangilash (PATCH)
- Misol 4 — Amaliy: to'liq CRUD API
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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_unsetbilan 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
TestClientva 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:
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: floatKirish (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:
@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:
@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:
@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):
@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_qismustun 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):
@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
# 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 / 4044. Batafsil misollar
Misollarda FastAPI
TestClientva temp baza bilan sinaladi.
Misol 1 — Create va Read
"""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:
=== 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 404Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.4-bo'limlar.
Misol 2 — Update (PUT) va Delete
"""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:
=== 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)
"""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:
=== 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'zgartiradiNima 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.
"""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:
=== 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 tuzilmaNima 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
@app.post("/kurslar") # ⚠️ 200
@app.post("/kurslar", status_code=201) # ✅2. PATCHda exclude_unset unutish
maydonlar = kurs.model_dump() # ❌ None'lar ham
maydonlar = kurs.model_dump(exclude_unset=True) # ✅3. rowcount tekshirmaslik
db.execute("UPDATE ... WHERE id = ?", ...) # ⚠️ yo'q id sezilmaydi
if cur.rowcount == 0: raise HTTPException(404) # ✅4. Kirish va chiqish bir model
class Kurs(BaseModel): # ⚠️ id kirishda keraksiz
# ✅ KursKirish (id yo'q) + KursJavob (id bilan)5. PATCHda to'liq model (majburiy maydon)
class KursYangilash(BaseModel):
nom: str # ❌ majburiy
nom: str | None = None # ✅ ixtiyoriy6. Takror topilmadi mantigi
# har endpointda if q is None: raise ... # ⚠️ takror
def _kurs_top(db, id): ... # ✅ yordamchi funksiya7. commit unutish (yangilash saqlanmaydi)
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
CRUD standart naqshini ishlating (POST/GET/PUT/PATCH/DELETE).
Kirish, yangilash va chiqish modellarini ajrating.
To'g'ri status kodlar: 201, 200, 204, 404.
PATCHdaexclude_unset— faqat berilgan maydon.Har o'zgartirishda
rowcountbilan404.Takror mantiqni yordamchi funksiyaga (
_kurs_top).response_modelbilan javobni shakllang.Har
INSERT/UPDATE/DELETEdan keyincommit.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
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
- Create, Read, Update, Delete
- 201 (Created)
- 204 (No Content)
- PUT — to'liq almashtirish, PATCH — qisman
- Faqat berilgan maydonni qaytaradi
- 404
- Har biri o'z maqsadiga (kirishda id yo'q, chiqishda bor)
- Resurs yo'q (o'zgarmadi/o'chmadi) → 404
- Yo'q (xavfsiz)
- Barchasi ixtiyoriy (
| None = None) - Javobni shakllaydi va hujjatlaydi
- Takror
404mantiqini bir joyga (DRY)
Vazifa 2: Xatolarni tuzating
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
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:
- Modellar:
Kirish(ism, email, parol),Yangilash(ixtiyoriy),Javob(parolsiz) - Barcha 5 amal
- To'g'ri status kodlar
TestClientbilan test
Vazifa 4: PUT vs PATCH
Bir resursda ikkalasi:
PUT— barcha maydon majburiyPATCH— faqat berilgan- Farqni ko'rsatish (bir maydon o'zgartirish)
- Har biri uchun test
Vazifa 5: Sahifalash va filtr bilan CRUD
Kurslar CRUD + Read boyitish:
- Ro'yxatda sahifalash (
sahifa,hajm) - Filtr (
til,daraja) - Saralash (
sort) - To'liq test
Vazifa 6: Ikki resurs CRUD
Kurslar va izohlar:
- Har biri uchun to'liq CRUD
- Izoh kursga bog'liq (
kurs_id) - Kurs izohlarini olish
- 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
- Naqsh — takrorlanuvchi yechim, qayta o'ylash shart emas
- CRUD — resurs uchun standart, tez va izchil
- Naqsh — jamoa umumiy tili
- Standartni bilish — tezlik va muloqot
6. Xulosa
- CRUD — har resursda bir xil naqsh
- Bir marta o'rgan, cheksiz qo'lla
- Naqsh — tezlik, izchillik, muloqot
- 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:
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.Modellar va status kodlar. CRUD bir necha model ishlatadi: kirish (yaratish,
idsiz), yangilash (qisman, barcha maydon ixtiyoriy), chiqish (response_model,idbilan). To'g'ri status kodlar HTTP semantikasini beradi. Har o'zgartirishdarowcount == 0tekshiriladi — resurs yo'q bo'lsa404.PUT, PATCH va toza tuzilma.
PUTresursni to'liq almashtiradi (barcha maydon majburiy);PATCHqisman 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_topkabi 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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!