Ачааллж байна...
Одоо танд хоёр бүрэн чадвар байна. Нэг талд FastAPI — endpoint, validation, хамгаалалт, мэргэжлийн бүтэц. Нөгөө талд SQLModel — хүснэгт, session, бүрэн CRUD.
Энэ хичээлд бид тэдгээрийг холбоно.
Бүлэг 8-т сурсан Depends нь эцсийн, хамгийн чухал хэрэглээгээ олно: session-ыг dependency болгож өгөх. Тэнд yield гэдэг үг гарч ирнэ — Курс 2-ын generator мэдлэг эцэст нь энд ажиллана.
Бүлэг 8-ын Хичээл 2-т би "энэ загвар Бүлэг 12-т яг ижил ашиглагдана" гэж амласан. Тэр амлалт биелэх цаг иржээ.
Бүлэг 11-ийн код ийм харагдаж байсан:
python
with Session(engine) as session:
note = session.get(Note, 1)
...Одоо үүнийг endpoint-д хийвэл:
python
@app.get("/notes/{note_id}")
def get_note(note_id: int):
with Session(engine) as session:
note = session.get(Note, note_id)
if note is None:
raise HTTPException(status_code=404, detail="Олдсонгүй")
return noteЭнэ ажиллана. Гэхдээ дөрвөн асуудалтай.
Нэгд, давталт. Endpoint бүрд with Session(engine) as session: гэсэн мөр давтагдана. Арван endpoint байвал арван удаа. Бүлэг 8-ын эхний хичээлд ярьсан яг тэр асуудал.
Хоёрт, индент нэмэгддэг. Бүх кодыг with блокийн дотор бичих ёстой тул нэмэлт түвшин үүснэ. Урт функцэд энэ нь уншихад хүндрүүлдэг.
Гуравт, engine-ээс хамаарна. Endpoint бүр engine гэсэн глобал объектыг шууд мэддэг байх ёстой. Хэрэв та түүнийг өөрчлөх (жишээ нь тест хийхэд өөр өгөгдлийн сан ашиглах) шаардлагатай бол — бүх endpoint-ыг засах ёстой.
Дөрөвт, алдаа зохицуулах. Хэрэв endpoint дунд алдаа гарвал session яаж хаагдах вэ? with нь хааж өгнө, гэхдээ баталгаажаагүй өөрчлөлтийг зохих ёсоор буцаах логик нь тодорхойгүй.
Эдгээрийг бүгдийг dependency шийднэ.
Бүлэг 8-ын Хичээл 1-ийн зүйрлэлээ санаарай: нийтийн бэлтгэл станц. Тогооч бүр өөрөө ногоо угаадаггүй; станцаас бэлэн орц авдаг.
Одоо тэр зүйрлэлийг өргөтгөе. Гал тогоонд зөвхөн орц биш, хэрэгсэл ч хуваалцдаг: хайруулын таваг, хутга, самбар.
Тогооч ажлаа эхлэхэд түүнд цэвэр таваг өгөгдөнө. Тэр ажиллана. Дуусахад таваг буцааж авагдаж, угаагдана. Тогооч тавгаа өөрөө авчирдаггүй, өөрөө угаадаггүй — зөвхөн ашигладаг.
Session бол тэр таваг юм. Хүсэлт ирэхэд FastAPI цэвэр session бэлдэж, endpoint-д өгнө. Endpoint ажиллана. Дуусахад FastAPI session-ыг хааж, цэвэрлэнэ.
Endpoint нь session хаанаас ирснийг, яаж хаагдахыг мэдэх шаардлагагүй. Зөвхөн ашиглана.
Одоо шинэ зүйл. Session-ийн dependency нь ердийн return биш, yield ашигладаг.
Курс 2-ын Бүлэг 13-т та generator сурсан. Богино сануулга: yield нь функцээс утга түр зуур гаргаж, дараа нь тэр газраасаа үргэлжлүүлдэг. return нь функцийг бүрмөсөн дуусгадаг; yield нь түр зогсоодог.
Яагаад энд yield хэрэгтэй вэ? Учир нь бид хоёр цагт ажиллах ёстой:
Endpoint-ээс өмнө — session нээх.
Endpoint-ийн дараа — session хаах.
return ашиглавал зөвхөн эхнийхийг хийж чадна — функц дуусаад буцахгүй. yield ашиглавал session-ыг гаргаж, endpoint ажиллахыг хүлээж, дараа нь үргэлжлүүлж, хааж чадна.
Одоо кодыг харъя:
python
def get_session():
with Session(engine) as session:
yield sessionДөрвөн мөр. Гэхдээ эдгээр дөрвөн мөрөнд их зүйл байна.
Гүйцэтгэлийн дараалал
Хүсэлт ирэхэд юу болохыг алхам алхмаар харъя.
1. FastAPI get_session()-ыг дуудна. Функц эхэлнэ.
2. with Session(engine) as session: ажиллана. Session нээгдэнэ, өгөгдлийн сантай холболт үүснэ.
3. yield session хүрнэ. Функц зогсоно. Session нь endpoint рүү дамжина.
4. Endpoint ажиллана. Тэр session-ыг ашиглаж, өгөгдөл уншиж, бичиж, commit хийнэ.
5. Endpoint дуусна. Хариу үүснэ.
6. get_session үргэлжилнэ. yield мөрөөс цааш явна. with блок дуусна — session хаагдана.
Зургаан алхам. Гурав дахь алхам дээр функц зогсож, зургаа дахь алхам дээр үргэлжилж байгааг анзаараарай. Тэр бол yield-ийн гол шинж.
Дүрслэн харуулбал:
FastAPI: get_session() дуудна
-> Session нээгдэнэ
-> yield session ────────┐
│
Endpoint ажиллана <──────────┘
-> session ашиглана
-> commit хийнэ
-> хариу буцаана
┌─────────
<─────────────────────────┘
-> with блок дуусна
-> Session хаагданаEndpoint нь yield болон with-ийн хооронд ажиллаж байна. Тэр бол "тогооч таваг ашиглаж байгаа" мөч юм.
Алдаа гарвал ч хаагдана
with ашигласны нэг гол ашиг: алдаа гарсан ч session хаагдана.
Хэрэв endpoint дунд HTTPException шидэгдвэл, эсвэл гэнэтийн алдаа гарвал — with блок нь дуусах үедээ session-ыг хаана. Курс 2-ын context manager-ийн үндсэн баталгаа.
Энэ нь чухал. Хэрэв session хаагдахгүй бол холболт нээлттэй үлдэж, олон удаа давтагдвал өгөгдлийн сан холболт дуусна.
Одоо ажиллах кодыг бүтнээр нь харъя. main.py файлаа дараах кодоор солино:
python
# main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlmodel import SQLModel, Field, Session, create_engine, select
app = FastAPI(title="Тэмдэглэлийн API")
class Note(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
text: str
priority: str = "энгийн"
done: bool = False
engine = create_engine("sqlite:///notes.db")
def get_session():
with Session(engine) as session:
yield session
@app.on_event("startup")
def on_startup():
SQLModel.metadata.create_all(engine)
@app.get("/notes")
def get_notes(session: Session = Depends(get_session)):
return session.exec(select(Note)).all()
@app.get("/notes/{note_id}")
def get_note(note_id: int, session: Session = Depends(get_session)):
note = session.get(Note, note_id)
if note is None:
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
return note
@app.post("/notes", status_code=201)
def create_note(note: Note, session: Session = Depends(get_session)):
session.add(note)
session.commit()
session.refresh(note)
return noteХадгална.
Санамж: хэрэв notes.db файл өмнөх бүлгээс үлдсэн бол устгаад цэвэр эхлэх нь зүйтэй.
Кодын задаргаа
python
def get_notes(session: Session = Depends(get_session)):Энэ бол гол мөр. session параметрийн default нь Depends(get_session).
Бүлэг 8-ын загвар яг ижил: Depends(функцийн_нэр), хаалтгүй. FastAPI функцийг дуудна, үр дүнг параметрт хийнэ.
Ялгаа нь: буцаах утга нь dictionary эсвэл model биш, харин өгөгдлийн сантай холбогдсон session.
python
return session.exec(select(Note)).all()Endpoint дотор with Session(...) байхгүй. Session аль хэдийн бэлэн, параметрээр ирсэн. Индент багассан, код цэвэрхэн болсон.
Бүлэг 11-ийн код яг ижилхэн ажиллаж байна — зөвхөн session хаанаас ирж байгаа нь өөр.
startup event
python
@app.on_event("startup")
def on_startup():
SQLModel.metadata.create_all(engine)Энэ нь сервер асахад нэг удаа ажилладаг функц. Хүснэгтүүдийг үүсгэнэ.
Яагаад ингэж хийж байна вэ? Учир нь create_all-ыг хаа нэгтээ дуудах ёстой, гэхдээ хүсэлт бүрд дуудах нь илүүц. Сервер асахад нэг удаа хангалттай.
Бүлэг 11-т сурсанчлан create_all нь давтагдахад аюулгүй — хүснэгт байгаа бол юу ч хийхгүй. Тиймээс сервер дахин асах бүрд дуудагдах нь асуудалгүй.
Тэмдэглэл: @app.on_event("startup") нь FastAPI-ийн хуучин бичлэг бөгөөд орчин үеийн хувилбарт lifespan гэсэн шинэ арга байдаг. Хоёулаа одоогоор ажиллаж байгаа. Хуучин бичлэг нь илүү энгийн, ойлгоход хялбар тул бид түүнийг ашиглана. Хэрэв та интернэтээс lifespan гэсэн код харвал — тэр нь ижил зорилготой, зөвхөн шинэ хэлбэр гэдгийг мэдэж байгаарай. Хэрэв танай FastAPI хувилбар анхааруулга өгвөл (DeprecationWarning), тэр нь ажиллахгүй гэсэн үг биш — зөвхөн ирээдүйд өөрчлөгдөнө гэсэн сануулга.
Сервер ажиллуулна:
fastapi dev main.pyТерминал дээр сервер асахад create_all ажиллаж, notes.db файл үүснэ (эсвэл байгаа бол нээгдэнэ).
1. Хоосон жагсаалт
http://127.0.0.1:8000/notesХариу:
json
[]2. Тэмдэглэл нэмэх
/docs -> POST /notes -> Try it out:
json
{
"text": "Сүү авах",
"priority": "чухал"
}Хариу (статус 201):
json
{
"id": 1,
"text": "Сүү авах",
"priority": "чухал",
"done": false
}id: 1 — өгөгдлийн сан өгсөн. next_id тоолуур байхгүй.
Дахин хоёр нэмнэ:
json
{"text": "Номоо буцаах"}json
{"text": "Тайлан бичих", "priority": "яаралтай"}3. Жагсаалт
http://127.0.0.1:8000/notesХариу:
json
[
{"id":1,"text":"Сүү авах","priority":"чухал","done":false},
{"id":2,"text":"Номоо буцаах","priority":"энгийн","done":false},
{"id":3,"text":"Тайлан бичих","priority":"яаралтай","done":false}
]4. Нэг тэмдэглэл ба 404
http://127.0.0.1:8000/notes/2Хариу (статус 200): хоёр дугаартай тэмдэглэл.
http://127.0.0.1:8000/notes/999Хариу (статус 404):
json
{"detail":"Тэмдэглэл олдсонгүй"}session.get нь None буцаасан, бид HTTPException шидсэн. Бүлэг 11-ийн None шалгалт нь одоо 404 болж хувирлаа.
5. Хамгийн чухал туршилт
Одоо энэ хичээлийн гол мөч.
Серверээ унтраа — терминал дээр Ctrl+C.
Дахин асаа:
fastapi dev main.pyДахин жагсаалт харна:
http://127.0.0.1:8000/notesХариу:
json
[
{"id":1,"text":"Сүү авах","priority":"чухал","done":false},
{"id":2,"text":"Номоо буцаах","priority":"энгийн","done":false},
{"id":3,"text":"Тайлан бичих","priority":"яаралтай","done":false}
]Тэмдэглэлүүд газраасаа хөдлөөгүй.
Түвшин 2-ын төгсгөлд, Түвшин 3-ын төгсгөлд бид ижил туршилт хийж, хоосон жагсаалт харсан. Одоо гурав дахь удаа — өгөгдөл үлдлээ.
Энэ бол Түвшин 4-ийн зорилго. Бид түүнд хүрлээ.
(Бүрэн баяр ёслолыг Хичээл 3-т, Тэмдэглэлийн API v3 бэлэн болоход хийнэ. Одоохондоо механизм ажиллаж байгааг батлав.)
Одоо нэг чухал зүйлийг ойлгоё: session нь хүсэлт бүрд шинээр үүсдэг.
Батлахын тулд print нэмнэ:
python
def get_session():
print("Session нээгдэж байна")
with Session(engine) as session:
yield session
print("Session хаагдлаа")Хадгалаад, /notes руу гурван удаа хандвал терминал дээр:
Session нээгдэж байна
Session хаагдлаа
Session нээгдэж байна
Session хаагдлаа
Session нээгдэж байна
Session хаагдлааГурван хүсэлт, гурван session. Тус бүр нээгдэж, хаагдсан.
Яагаад ингэх ёстой вэ
Хоёр шалтгаан бий.
Нэгд, тусгаарлал. Хоёр хэрэглэгч зэрэг хүсэлт илгээвэл тус бүр өөрийн session-тэй байна. Нэгнийх нь баталгаажаагүй өөрчлөлт нөгөөд нь харагдахгүй. Бүлэг 10-ын "зэрэг бичих" асуудал зохих ёсоор зохицуулагдана.
Хоёрт, цэвэр байдал. Session нь өөртөө объектуудыг санаж байдаг (өөрчлөлт мөрдөх зорилгоор). Хэрэв нэг session мөнхөд ажиллавал тэр санамж хуримтлагдаж, санах ой дүүрнэ. Хүсэлт бүрд шинэ session нээх нь бүх зүйлийг цэвэрхэн байлгана.
Дүрэм: session бол хүсэлтийн урттай — хүсэлт эхлэхэд нээгдэж, дуусахад хаагдана. Энэ бол вэб аппликейшны стандарт загвар.
Туршилтын дараа print-үүдийг устгаарай.
Нэг далд боловч чухал ашгийг тэмдэглэе.
Endpoint-үүдийг сайтар хараарай:
python
@app.get("/notes")
def get_notes(session: Session = Depends(get_session)):
return session.exec(select(Note)).all()engine гэсэн үг энд байхгүй. Endpoint нь өгөгдлийн сан хаана байгаа, ямар төрлийн болохыг мэдэхгүй. Тэр зөвхөн session ашиглана.
Энэ нь юу өгч байна вэ?
Хэрэв та маргааш SQLite-аас PostgreSQL руу шилжвэл — зөвхөн create_engine(...) мөр өөрчлөгдөнө. Endpoint-үүд бүгд хэвээр.
Хэрэв та тест бичих юм бол — тестэд өөр get_session өгч, санах ойн өгөгдлийн сан ашиглаж болно. Endpoint-үүд мэдэхгүй.
Энэ бол dependency injection-ийн гүн ашиг: хэрэглэгч нөөц хаанаас ирснийг мэдэхгүй тул эх сурвалжийг чөлөөтэй солиж болно.
Бүлэг 8-ын Хичээл 1-т би "энэ ойлголт эхэндээ хийсвэр санагддаг, гэхдээ Түвшин 4-т байрандаа орно" гэж хэлсэн. Энэ бол тэр мөч.
Одоогийн кодод нэг дутагдал байна, түүнийг тэмдэглэе.
python
@app.post("/notes", status_code=201)
def create_note(note: Note, session: Session = Depends(get_session)):Оролтод Note — table model ашиглаж байна. Энэ нь ажиллана, гэхдээ хоёр асуудалтай.
Нэгд, хэрэглэгч id илгээж чадна. Note model-д id талбар байгаа тул /docs дээр гарна. Хэрэглэгч {"id": 999, "text": "..."} гэж илгээж болно. Бид Бүлэг 5-6-т "id-г сервер үүсгэдэг" гэж тогтоосон.
Хоёрт, гаралтыг хянаагүй. response_model байхгүй тул бүх талбар гарна. Одоохондоо нуух зүйл байхгүй, гэхдээ ирээдүйд байж болно.
Шийдэл нь гурван model. Бүлэг 6-ын оролт-гаралтын хос загвар одоо гуравт өргөжинө:
NoteCreate (table биш) — хэрэглэгч илгээх зүйл, id-гүй.
Note (table=True) — өгөгдлийн сангийн хүснэгт.
NotePublic (table биш) — хэрэглэгч рүү буцах зүйл, id-тэй.
Бид үүнийг дараагийн хичээлд бүрэн хийнэ. Одоохондоо нэг model-оор ажиллаж, механизмд анхаарлаа төвлөрүүлье.
return ашиглах (yield биш)
python
def get_session():
with Session(engine) as session:
return session # yield бишЭнэ нь ажиллахгүй. return хийхэд with блок шууд дуусаж, session хаагдана. Дараа нь endpoint хаагдсан session хүлээж авна:
sqlalchemy.exc.ResourceClosedError: This session is closedЭсвэл бүр нарийн, ойлгомжгүй алдаа гарна.
Заавал yield. Тэр нь session-ыг нээлттэй байлгаж, endpoint ажилласны дараа хаана.
Depends дотор хаалт
python
session: Session = Depends(get_session()) # хаалт — буруу
session: Session = Depends(get_session) # зөвБүлэг 8-аас танил алдаа. Функцийг дуудахгүй, зөвхөн заана.
create_all дуудахгүй
Хэрэв startup event эсвэл өөр газар create_all дуудахгүй бол:
sqlalchemy.exc.OperationalError: no such table: noteХүснэгт байхгүй. create_all заавал хаа нэгтээ дуудагдах ёстой.
commit мартах
python
@app.post("/notes")
def create_note(note: Note, session: Session = Depends(get_session)):
session.add(note)
return note # commit байхгүйХариу нь амжилттай харагдана (201), гэхдээ юу ч хадгалагдахгүй. Session хаагдахад өөрчлөлт хаягдана.
Гурав дахь удаа дурдаж байна. Учир нь тэр үнэхээр түгээмэл.
refresh мартах
python
session.add(note)
session.commit()
return note # refresh байхгүйИхэнх тохиолдолд энэ нь ажиллана (FastAPI объектыг уншихад session нь дахин ачаалдаг), гэхдээ нарийн тохиолдолд id нь null болж буцаж болно.
Загвар: add -> commit -> refresh. Гурвуулаа бичих нь найдвартай.
Session-ыг глобал болгох
python
session = Session(engine) # глобал — буруу
@app.get("/notes")
def get_notes():
return session.exec(select(Note)).all()Энэ нь ажиллаж болох ч буруу. Бүх хүсэлт нэг session хуваалцана: зэрэг хандалтад асуудал үүснэ, санах ой хуримтлагдана, нэгний алдаа нөгөөд нөлөөлнө.
Үргэлж dependency ашигла. Хүсэлт бүр өөрийн session-тэй байх ёстой.
Хүсвэл get_session-д print нэмээд, /docs дээр хэд хэдэн endpoint дуудаж, session хэдэн удаа нээгдэж хаагдаж байгааг ажиглаарай. yield-ийн өмнөх болон дараах хэсэг хэзээ ажиллаж байгааг харах нь механизмыг тодруулна.
Сонирхвол create_engine-д echo=True нэмээд, endpoint дуудаж, үүсэх SQL-ыг хараарай. GET /notes нь SELECT болж, POST /notes нь INSERT болохыг өөрийн нүдээр харна. Бүлэг 10-ын SQL мэдлэг тань энд шууд ашигтай.
Endpoint бүрд with Session(engine) бичих нь давтагдмал — dependency шийднэ.
Session dependency нь yield ашиглана, return биш — учир нь endpoint-ээс өмнө ба дараа ажиллах ёстой.
Дараалал: session нээх -> yield -> endpoint ажиллана -> with дуусна -> session хаагдана.
with нь алдаа гарсан ч session хаагдахыг баталгаажуулна (Курс 2-ын context manager).
Бичлэг: session: Session = Depends(get_session) — Бүлэг 8-ын загвартай яг ижил.
Хүсэлт бүр өөрийн session-тэй — тусгаарлал, цэвэр байдал. Session бол хүсэлтийн урттай.
Endpoint нь engine-ээс салсан — өгөгдлийн сан солиход endpoint өөрчлөгдөхгүй.
create_all-ыг startup event-д нэг удаа дуудна; давтагдахад аюулгүй.
Загвар: add -> commit -> refresh. commit мартах нь хамгийн түгээмэл алдаа.
Session-ыг глобал болгохгүй — үргэлж dependency.
Өгөгдөл эцэст нь үлдэж эхэллээ. Сервер унтарч асахад тэмдэглэлүүд газраасаа хөдөлсөнгүй.
Дараагийн хичээлд бид үлдсэн endpoint-үүдийг өгөгдлийн санд шилжүүлнэ: PUT (засах), DELETE (устгах), мөн хайлт, шүүлт, хуудаслалтыг өгөгдлийн сангийн эсрэг бичнэ. Тэнд Python-ы [n for n in notes if ...] нь SQL-ын .where(...) болж хувирахыг харна — тэр ялгаа нь сая мөртэй өгөгдлийн санд бүх зүйлийг шийддэг. Дараа нь бид тэдгээрийг мэргэжлийн бүтэцтэй нэгтгэж, Тэмдэглэлийн API v3-ыг бүрэн бүтээнэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.