Darslik · 2026-08-16 · ~7 daqiqa o‘qiladi · boshlang'ich
SQLModel: bitta klass — ham jadval, ham sxema
Mundarija
FastAPI o'rganishni boshlagan odam bir joyga kelib to'xtaydi: bazadagi jadval uchun SQLAlchemy modeli yozdi, endi API'ga chiqarish uchun yana o'shaning Pydantic nusxasini yozishi kerak. Bir xil maydonlar ikki joyda, biri o'zgarsa ikkinchisini unutish oson. Men buni Doctolib loyihamda yaqqol his qilganman — shifokor, bemor, qabul degan tushunchalarning har biri ikki nusxada yashay boshlagan edi.
SQLModel aynan shu og'riq uchun yaratilgan (muallifi ham FastAPI'ning o'zini yozgan Sebastián Ramírez). G'oyasi sodda: bitta klass yozasiz, u ham baza jadvali, ham Pydantic sxemasi bo'lib xizmat qiladi.
O'rnatish va birinchi model#
uv add sqlmodel
Loyihamiz kichik shifokor qabuli tizimi bo'ladi — fayl tuzilishi shunday:
app/
├── __init__.py
├── models.py ← SQLModel klasslar
├── database.py ← engine va session
└── main.py ← FastAPI endpoint'lar
# app/models.py
from datetime import datetime
from sqlmodel import Field, SQLModel
class Doctor(SQLModel, table=True):
# table=True — bu klass bazada jadval bo'ladi degani.
# Usiz u faqat sxema bo'lib qoladi (buni keyinroq ishlatamiz)
id: int | None = Field(default=None, primary_key=True)
name: str
specialty: str = Field(index=True) # mutaxassislik bo'yicha qidiramiz
class Appointment(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
doctor_id: int = Field(foreign_key="doctor.id")
patient_name: str
starts_at: datetime
E'tibor bering, bu oddiy Python klasslar — type hint'lar bilan yozilgan.
id: int | None bo'lishining sababi ham tushunarli: yangi yozuv yaratilayotganda
id hali yo'q, uni baza beradi.
Engine va session#
# app/database.py
from sqlmodel import Session, SQLModel, create_engine
# boshlanish uchun SQLite yetarli; keyin PostgreSQL'ga faqat shu satr o'zgaradi
engine = create_engine("sqlite:///clinic.db", echo=True) # echo: SQL'ni ko'rsatib turadi
def init_db() -> None:
# hamma table=True klasslar bo'yicha jadvallarni yaratadi
SQLModel.metadata.create_all(engine)
def get_session():
# FastAPI dependency: har so'rovga alohida session, ish tugagach yopiladi
with Session(engine) as session:
yield session
echo=Trueni o'rganish paytida yoqib qo'ying: har amalda qanday SQL
ketayotganini ko'rib borasiz va ORM sehr emas, shunchaki tarjimon ekanini
his qilasiz.
Endpoint'lar: yozish va o'qish#
# app/main.py
from fastapi import Depends, FastAPI, HTTPException
from sqlmodel import Session, select
from app.database import get_session, init_db
from app.models import Doctor
app = FastAPI()
@app.on_event("startup")
def on_startup():
init_db()
@app.post("/doctors/", response_model=Doctor)
def create_doctor(doctor: Doctor, session: Session = Depends(get_session)):
# bitta klass ikki rolda: kirishda Pydantic tekshiruvi,
# session.add'da esa baza qatori
session.add(doctor)
session.commit()
session.refresh(doctor) # bazadan id qaytib keladi
return doctor
@app.get("/doctors/", response_model=list[Doctor])
def list_doctors(specialty: str | None = None, session: Session = Depends(get_session)):
query = select(Doctor)
if specialty:
query = query.where(Doctor.specialty == specialty)
return session.exec(query).all()
@app.get("/doctors/{doctor_id}", response_model=Doctor)
def get_doctor(doctor_id: int, session: Session = Depends(get_session)):
doctor = session.get(Doctor, doctor_id)
if doctor is None:
raise HTTPException(404, "Bunday shifokor yo'q")
return doctor
Ishga tushirib sinaymiz:
uv run fastapi dev app/main.py
curl -X POST http://127.0.0.1:8000/doctors/ \
-H "Content-Type: application/json" \
-d '{"name": "Aziza Karimova", "specialty": "kardiolog"}'
# {"id": 1, "name": "Aziza Karimova", "specialty": "kardiolog"}
curl "http://127.0.0.1:8000/doctors/?specialty=kardiolog"
Bitta klass bilan validatsiya ham, saqlash ham, hujjat ham (FastAPI'ning
/docs sahifasini oching — sxema o'sha yerda tayyor turibdi) ishladi.
Kirish va chiqishni ajratish kerak bo'lganda#
Loyiha o'sgani sari "hamma maydonni hammaga ko'rsatmaslik" ehtiyoji chiqadi.
Masalan, yaratishda mijoz id yubormasligi kerak, javobda esa ichki
maydonlar ko'rinmasligi mumkin. SQLModel'da bu meros bilan chiroyli
yechiladi — table=Truesiz klasslar sof sxema bo'lib xizmat qiladi:
# app/models.py (davomi)
class DoctorBase(SQLModel):
name: str
specialty: str
class Doctor(DoctorBase, table=True): # jadval: id qo'shiladi
id: int | None = Field(default=None, primary_key=True)
class DoctorCreate(DoctorBase): # kirish sxemasi: id yo'q
pass
Endpoint'da esa def create_doctor(data: DoctorCreate, ...) deb yozasiz va
Doctor.model_validate(data) bilan jadval obyektiga aylantirasiz. Umumiy
maydonlar baribir bitta joyda — DoctorBaseda — turadi, takrorlanish yo'q.
SQLModel ostida tanish narsalar yotibdi
SQLModel — yangi ORM emas: ostida SQLAlchemy'ning o'zi, sxema tomonida Pydantic'ning o'zi. Demak SQLAlchemy hujjatidagi bilim shu yerda ham ishlaydi va murakkab holatda (masalan qo'shma so'rovlar) o'sha darajaga bemalol tushib olsangiz bo'ladi.
Bitta savol ochiq qoldi#
Loyihani ishga tushirdik, jadvallar create_all bilan yaratildi. Lekin
ertaga Doctorga yangi maydon qo'shsangiz nima bo'ladi? create_all mavjud
jadvalni o'zgartirmaydi — u faqat yo'qni yaratadi. Ishlab turgan bazani
buzmasdan o'zgartirish alohida hunar va uning asbobi ham tayyor: Alembic
migratsiyalari. Keyingi qismda aynan shuni ko'ramiz.