Ачааллж байна...
Энэ хичээлд бид Түвшин 2-ын бүх зүйлээ нэг бодит программд нэгтгэнэ: Тэмдэглэлийн API. Энэ бол бүрэн CRUD-тай, Pydantic-аар хамгаалагдсан, response_model-оор шүүгдсэн жинхэнэ API — тэмдэглэл үүсгэх, бүгдийг унших, нэгийг унших, засах, устгах.
Гэхдээ энэ бол зүгээр нэг дасгал биш. Энэ Тэмдэглэлийн API нь бүх сургалтын турш дагалдах гол утас юм. Өнөөдөр бид түүний v1-ийг бүтээнэ — санах ойд ажилладаг хувилбар. Түвшин 3-т бид түүнийг олон файлд задалж, цэгцэлж, хамгаална (v2). Түвшин 4-т бид түүнд жинхэнэ өгөгдлийн сан залгана (v3). Ижил программ, гурван удаа, тавьсан суурин дээрээ өсөж төлөвших болно.
Энэ бол уншиж, дагаж бичих хичээл. Гэрийн даалгавар биш. Бид мөр мөрөөр хамтдаа бичих бөгөөд алхам бүрийн дараа шалгах цэг байна.
Эхлээд юу бүтээхээ тодорхой болгоё, мөн юу бүтээхгүйгээ ч.
Бүтээх: тэмдэглэлийн бүрэн CRUD — таван endpoint. Оролт болон гаралтад тусдаа Pydantic model. Зөв status code. Зөв 404. Хайлт ба шүүлт.
Бүтээхгүй (одоохондоо): байнгын хадгалалт. Тэмдэглэлүүд зөвхөн санах ойд, Python жагсаалтад амьдарна. Серверээ унтраахад бүх тэмдэглэл алга болно. Энэ бол зориудаар — Түвшин 4 үүнийг засна. Хичээлийн төгсгөлд бид энэ хязгаарыг өөрийн нүдээр харна.
Бид нэг файлаар ажиллана: main.py. Олон файлт бүтцийг Түвшин 3-т сурна.
Бодит төсөл эхлүүлэхийн өмнөх ажил — юу хийхээ мэдэх:
Method | Зам | Юу хийх вэ | Status |
|---|---|---|---|
POST |
| Шинэ тэмдэглэл үүсгэх | 201 |
GET |
| Бүх тэмдэглэл (хайлт/шүүлттэй) | 200 |
GET |
| Нэг тэмдэглэл | 200 / 404 |
PUT |
| Тэмдэглэл засах | 200 / 404 |
DELETE |
| Тэмдэглэл устгах | 204 / 404 |
Таван endpoint. /notes/{note_id}-д тогтмол зам байхгүй тул route ordering занга энд гарахгүй — гэхдээ болгоомжтой байх нь зүйтэй.
main.py файлаа дараах кодоор бүхэлд нь солино:
python
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(
title="Тэмдэглэлийн API",
description="Тэмдэглэл хадгалах энгийн API (v1 — санах ойд)",
version="1.0.0",
)
notes: list[dict] = []
next_id = 1
class NoteCreate(BaseModel):
text: str
priority: str = "энгийн"
done: bool = False
class NotePublic(BaseModel):
id: int
text: str
priority: str
done: boolХадгална.
Кодын задаргаа
notes: list[dict] = [] — тэмдэглэлүүдийг хадгалах хоосон жагсаалт. Type hint (list[dict]) нь заавал биш, гэхдээ энэ жагсаалт dictionary-үүд агуулна гэдгийг тодорхой болгож байна. next_id = 1 — дараагийн id-г хянана.
Хоёр model, хоёр үүрэг.
NoteCreate — оролтын model. Хэрэглэгч илгээх зүйл: text (заавал), priority болон done (default-тай, заавал бус). id байхгүй — Бүлэг 5-6-т сурсанчлан id-г сервер үүсгэдэг, хэрэглэгч илгээх ёсгүй.
NotePublic — гаралтын model. Хэрэглэгч авах зүйл: id, text, priority, done. Энд id байна — гаралтад id хэрэгтэй.
Энэ бол Бүлэг 6-ын оролт-гаралтын хос загвар. Одоохондоо хоёр model бараг ижил (зөвхөн id ялгаатай), гэхдээ тэдгээрийг тусад нь байлгах нь мэргэжлийн зуршил — ирээдүйд оролт-гаралт өөр болоход бэлэн.
Шалгах цэг
Терминалаа шалгаад /docs хуудсаа нээнэ. Дээд талд Тэмдэглэлийн API 1.0.0 гарчиг, доор нь тайлбар харагдана. Endpoint-ийн жагсаалт хоосон — бид хараахан бичээгүй. Энэ бол хэвийн; model зарлах нь endpoint үүсгэдэггүй.
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.post("/notes", status_code=201, response_model=NotePublic)
def create_note(note: NoteCreate):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_noteХадгална.
Кодын задаргаа
response_model=NotePublic — гарцад NotePublic-аар шүүнэ. Энд функц бүх талбартай (id нэмсэн) dictionary буцаах ба хариу нь NotePublic-ийн бүтэцтэй болно.
note: NoteCreate — оролтод NoteCreate model. Хэрэглэгчийн өгөгдлийг шалгана.
note.model_dump() — Pydantic объектыг dictionary болгоно. new_note["id"] = next_id — id нэмнэ. notes.append(...) — хадгална. next_id += 1 — тоолуур нэмэгдүүлнэ. global next_id — гаднах хувьсагчийг өөрчилж байгаа тул заавал.
Энэ бол Бүлэг 5-ын Хичээл 2-ын жинхэнэ үүсгэх загвар.
Шалгах цэг
/docs -> POST /notes -> Try it out. Хоёр тэмдэглэл дараалан үүсгэнэ.
Эхнийх:
json
{"text": "Сүү авах", "priority": "чухал"}Хариу (статус 201):
json
{"id":1,"text":"Сүү авах","priority":"чухал","done":false}id: 1 нэмэгдсэн, done: false (default).
Хоёр дахь:
json
{"text": "Номоо буцаах"}Хариу (статус 201):
json
{"id":2,"text":"Номоо буцаах","priority":"энгийн","done":false}id: 2, priority: "энгийн" (default).
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.get("/notes", response_model=list[NotePublic])
def get_notes(search: str = "", done: bool | None = None):
result = notes
if search:
result = [n for n in result if search.lower() in n["text"].lower()]
if done is not None:
result = [n for n in result if n["done"] == done]
return resultХадгална.
Кодын задаргаа
response_model=list[NotePublic] — гаралт нь NotePublic объектуудын жагсаалт. FastAPI жагсаалтын элемент бүрийг шүүнэ.
Хоёр query parameter. search: str = "" — текст хайлт, заавал бус. done: bool | None = None — төлөвөөр шүүх.
done-ийн type hint-ыг сайтар анзаараарай: bool | None = None. Яагаад энгийн bool = False биш вэ? Учир нь done-д гурван утга хэрэгтэй:
None (default) — шүүлт хийхгүй, бүх тэмдэглэл.
True — зөвхөн дууссан тэмдэглэл.
False — зөвхөн дуусаагүй тэмдэглэл.
Хэрэв done: bool = False байсан бол хэрэглэгч юу ч өгөхгүй үед автоматаар зөвхөн дуусаагүйг шүүх байсан — буруу. None-ийг default болгосноор "шүүлт хийхгүй" гэсэн гурав дахь утга нэмэгдэнэ. Бүлэг 4-ийн str | None мэдлэг энд query parameter дээр ажиллаж байна.
if done is not None: — is not None ашигласныг анзаараарай, if done: биш. Учир нь False бол хүчинтэй шүүлт (дуусаагүйг харах), гэтэл if done: нь False-ыг алгасах байсан. is not None нь "хэрэглэгч ямар нэг утга өгсөн үү" гэдгийг шалгаж байна. Энэ бол нарийн боловч чухал ялгаа.
Шалгах цэг 1: бүх тэмдэглэл
http://127.0.0.1:8000/notesХариу:
json
[{"id":1,"text":"Сүү авах","priority":"чухал","done":false},{"id":2,"text":"Номоо буцаах","priority":"энгийн","done":false}]Шалгах цэг 2: хайлт
http://127.0.0.1:8000/notes?search=сүүХариу:
json
[{"id":1,"text":"Сүү авах","priority":"чухал","done":false}]Шалгах цэг 3: төлөвөөр шүүх
http://127.0.0.1:8000/notes?done=falseХоёр тэмдэглэл (хоёулаа дуусаагүй). Дараа нь ?done=true туршвал хоосон жагсаалт (одоохондоо дууссан тэмдэглэл алга).
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.get("/notes/{note_id}", response_model=NotePublic)
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Шалгах цэг
http://127.0.0.1:8000/notes/1Хариу (статус 200):
json
{"id":1,"text":"Сүү авах","priority":"чухал","done":false}Байхгүй id:
http://127.0.0.1:8000/notes/99Хариу (статус 404):
json
{"detail":"Тэмдэглэл олдсонгүй"}Буруу төрөл:
http://127.0.0.1:8000/notes/abcХариу (статус 422): танил int_parsing бүтэц, loc нь ["path", "note_id"].
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.put("/notes/{note_id}", response_model=NotePublic)
def update_note(note_id: int, note: NoteCreate):
for existing in notes:
if existing["id"] == note_id:
existing["text"] = note.text
existing["priority"] = note.priority
existing["done"] = note.done
return existing
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Кодын задаргаа
note: NoteCreate — PUT нь оролтод ижил NoteCreate model ашиглаж байна. Учир нь засахад ч бид ижил талбаруудыг (text, priority, done) хүлээж авдаг.
Функц id-гаар хайж, олдвол гурван талбарыг дарж бичнэ. id-г өөрчлөхгүй — байнгын таних тэмдэг. Бүлэг 5-ын Хичээл 4-ийн PUT загвар.
Шалгах цэг
/docs -> PUT /notes/{note_id}. Path талбарт 1, body-д:
json
{"text": "Сүү ба талх авах", "priority": "чухал", "done": true}Хариу (статус 200):
json
{"id":1,"text":"Сүү ба талх авах","priority":"чухал","done":true}Тэмдэглэл шинэчлэгдсэн, done: true. id хэвээр.
Байхгүй id (PUT /notes/99) -> 404.
Одоо ?done=true шүүлт ажиллах болсон:
http://127.0.0.1:8000/notes?done=trueХариу:
json
[{"id":1,"text":"Сүү ба талх авах","priority":"чухал","done":true}]Нэг тэмдэглэл одоо дууссан тул шүүлтэд орж ирлээ.
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.delete("/notes/{note_id}", status_code=204)
def delete_note(note_id: int):
for i, note in enumerate(notes):
if note["id"] == note_id:
notes.pop(i)
return None
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Кодын задаргаа
status_code=204 — No Content, устгал амжилттай, буцаах юм алга. return None — юу ч буцаахгүй. response_model энд байхгүй — 204 нь агуулгагүй тул шаардлагагүй.
enumerate + pop(i) — Бүлэг 5-ын Хичээл 4-ийн устгах загвар.
Шалгах цэг
GET-ээр одоо байгаа тэмдэглэлийг хар: http://127.0.0.1:8000/notes — id 1 болон 2.
/docs -> DELETE /notes/{note_id}. Path талбарт 2, Execute.
Хариу (статус 204): хоосон. Энэ бол зөв — 204 гэдэг нь агуулгагүй.
GET-ээр батлана: http://127.0.0.1:8000/notes — одоо зөвхөн id 1.
Дахин DELETE /notes/2 -> 404 (аль хэдийн устсан).
Энэ бол таны бүрэн main.py — Тэмдэглэлийн API v1:
python
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(
title="Тэмдэглэлийн API",
description="Тэмдэглэл хадгалах энгийн API (v1 — санах ойд)",
version="1.0.0",
)
notes: list[dict] = []
next_id = 1
class NoteCreate(BaseModel):
text: str
priority: str = "энгийн"
done: bool = False
class NotePublic(BaseModel):
id: int
text: str
priority: str
done: bool
@app.post("/notes", status_code=201, response_model=NotePublic)
def create_note(note: NoteCreate):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_note
@app.get("/notes", response_model=list[NotePublic])
def get_notes(search: str = "", done: bool | None = None):
result = notes
if search:
result = [n for n in result if search.lower() in n["text"].lower()]
if done is not None:
result = [n for n in result if n["done"] == done]
return result
@app.get("/notes/{note_id}", response_model=NotePublic)
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
@app.put("/notes/{note_id}", response_model=NotePublic)
def update_note(note_id: int, note: NoteCreate):
for existing in notes:
if existing["id"] == note_id:
existing["text"] = note.text
existing["priority"] = note.priority
existing["done"] = note.done
return existing
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
@app.delete("/notes/{note_id}", status_code=204)
def delete_note(note_id: int):
for i, note in enumerate(notes):
if note["id"] == note_id:
notes.pop(i)
return None
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Далан мөр орчим. Таван endpoint. Бүрэн CRUD. Pydantic хамгаалалт. Тусдаа оролт-гаралтын model. Зөв status code. Зөв 404.
/docs хуудсаа нээж, бүхэлд нь хараарай. Таван endpoint, өнгөөр ялгагдсан:
POST /notes — үүсгэх (шар)
GET /notes — жагсаалт (ногоон)
GET /notes/{note_id} — нэг (ногоон)
PUT /notes/{note_id} — засах (цэнхэр)
DELETE /notes/{note_id} — устгах (улаан)
POST /notes дээр Request body нь NoteCreate (id-гүй), Responses нь NotePublic (id-тэй) — оролт-гаралт тусдаа. Хэрэглэгч юу илгээх, юу авахаа тодорхой харна.
Одоо бүх endpoint-ийг дараалан туршаарай: үүсгэ, жагсаа, нэгийг хар, зас, устга. Энэ бол бодит API-ийн бүтэн амьдралын мөчлөг.
Одоо хамгийн чухал мөч. Хичээлийн эхэнд би "серверээ унтраахад бүх тэмдэглэл алга болно" гэж хэлсэн. Түүнийг өөрийн нүдээр харъя.
Эхлээд хэдэн тэмдэглэл үүсгэнэ (хэрэв устгасан бол). /docs дээр хоёр гурван POST хийнэ. Дараа нь GET-ээр байгаа эсэхийг батлана:
http://127.0.0.1:8000/notesТэмдэглэлүүд байна.
Одоо серверээ унтраа. Терминал дээр Ctrl+C дарна. Дараа нь дахин асаа:
fastapi dev main.pyДахин GET хий:
http://127.0.0.1:8000/notesХариу:
json
[]Бүх тэмдэглэл алга. Хоосон жагсаалт.
Юу болов? Таны тэмдэглэлүүд notes = [] гэсэн Python жагсаалтад амьдарч байсан. Тэр жагсаалт нь программын санах ойд (RAM) байдаг. Программ зогсоход санах ой цэвэрлэгдэж, жагсаалт шинээр [] болж эхэлдэг. Хадгалагдсан юу ч байхгүй.
Энэ бол v1-ийн үндсэн хязгаар. Таны API төгс ажиллаж байна — CRUD, validation, бүх зүйл зөв. Гэхдээ санах ой түр зуурынх. Жинхэнэ API-д өгөгдөл байнга үлдэх ёстой: сервер дахин асахад, эвдэрч дахин эхлэхэд, шинэчлэгдэхэд — өгөгдөл газраасаа хөдлөхгүй байх ёстой.
Энэ API-д Түвшин 2-ын бүх зүйл ашиглагдсан.
Pydantic model — оролтын харуул (NoteCreate), гаралтын шүүлтүүр (NotePublic) (Бүлэг 4).
Бүрэн CRUD — POST, GET, GET-нэг, PUT, DELETE (Бүлэг 5).
Зөв status code — 201, 200, 204, 404, 422 (Бүлэг 3, 5).
response_model — оролт-гаралтын хос, list[NotePublic], id хэрхэн зохицуулах (Бүлэг 6).
Query parameter — хайлт, bool | None гурван утгын шүүлт (Бүлэг 2, 4).
HTTPException — зөв 404 (Бүлэг 3).
Мөн Курс 1-2-оос: list comprehension, enumerate, global, is not None, f-string, dictionary-үүдийн list.
Эдгээр нь заавал биш. Дараагийн хичээл тэднийг хийсэн гэж таамаглахгүй.
Нэгдүгээр өргөтгөл: priority шүүлт. GET /notes-д priority: str = "" query parameter нэмж, тодорхой priority-тай тэмдэглэлийг шүүнэ.
Хоёрдугаар өргөтгөл: тоолуур endpoint. GET /notes/stats нэмж, нийт тоо, дууссаны тоо, дуусаагүйн тоог буцаана. Санамж: sum(1 for n in notes if n["done"]).
Гуравдугаар өргөтгөл: priority шалгах. priority талбар нь зөвхөн "энгийн", "чухал", "яаралтай" байх ёстой гэж хязгаарлах. Одоохондоо хэрэглэгч ямар ч текст өгч болно. Санамж: PUT/POST дотор if note.priority not in [...]: raise HTTPException(400, ...). (Pydantic-д үүнийг илүү цэвэрхэн хийх арга бий — Enum — гэхдээ энэ сургалтын хүрээнээс гадуур.)
Түвшин 2-ын бүх зүйлийг нэг бодит API-д нэгтгэлээ: бүрэн CRUD, Pydantic хамгаалалт, response_model шүүлт.
Оролт-гаралтын хос model: NoteCreate (id-гүй), NotePublic (id-тэй).
response_model=list[NotePublic] — жагсаалт бүхэлдээ шүүгдэнэ.
done: bool | None = None — гурван утгын шүүлт; is not None ашиглана (if done: биш).
Таван endpoint бол бараг бүх нөөцийн (ном, хэрэглэгч, захиалга) стандарт дардас.
v1-ийн хязгаар: санах ой түр зуурынх — сервер унтраахад бүх өгөгдөл алга.
Түвшин 2 дууслаа. Та одоо бүрэн ажиллагаатай, хамгаалалттай, зөв бүтэцтэй CRUD API бичиж чадна.
Гэхдээ таны бүх код нэг файлд, далан мөр болж хураагдсан байна. Хэрэв энэ API өсөж, хорин endpoint, арван model болбол нэг файл хэдэн зуун мөр болж, удирдахад бэрх болно. Бодит төсөл ингэж бүтдэггүй — код нь олон файлд, ойлголтоор нь бүлэглэгдэн тархдаг.
Түвшин 3 бүхэлдээ бүтэц ба цэгцлэл-д зориулагдана. Бид APIRouter сурч, кодоо олон файлд задалж, Depends-ээр хуваалцсан логик үүсгэж, API key-ээр бичих үйлдлээ хамгаалж, тохиргоогоо кодоос салгана. Дараа нь бид Тэмдэглэлийн API-г дахин барьж, түүнийг v2 болгоно — ижил зан төлөв, гэхдээ мэргэжлийн бүтэцтэй. Тэр нь энэ жижиг v1-ийн шууд үргэлжлэл юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.