Ачааллж байна...
Одоо танд CRUD-ийн хоёр үсэг байна: Create (POST) болон Read (GET). Энэ хичээлд бид үлдсэн хоёрыг нэмнэ: Update (PUT) болон Delete (DELETE). Тэдгээрийг сурснаар та бүрэн CRUD-тай болно — өгөгдлийг үүсгэх, унших, засах, устгах бүх чадвар.
Энэ бол Түвшин 2-ын бүтээн байгуулалт болох Тэмдэглэлийн API v1 руу хийх сүүлчийн бэлтгэл юм. Хичээлийн төгсгөлд бид мөн idempotency гэсэн нэг чухал ойлголтыг энгийн үгээр авч үзнэ — POST, PUT, DELETE-ийн зан төлөвийг ялгах гүнзгий санаа.
PUT бол "байгаа зүйлийг өөрчил" гэсэн method. POST шинэ зүйл үүсгэдэг бол PUT байгаа зүйлийг шинэчилдэг.
PUT хоёр төрлийн мэдээлэл шаарддаг: аль зүйлийг засах (path parameter, id) болон юу болгож засах (request body, шинэ өгөгдөл). Тиймээс PUT нь path болон body-г хоёуланг ашигладаг.
main.py файлаа дараах кодоор солино:
python
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Тэмдэглэлийн API")
notes = []
next_id = 1
class Note(BaseModel):
text: str
priority: str = "энгийн"
done: bool = False
@app.get("/notes")
def get_notes():
return notes
@app.get("/notes/{note_id}")
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
@app.post("/notes", status_code=201)
def create_note(note: Note):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_note
@app.put("/notes/{note_id}")
def update_note(note_id: int, note: Note):
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="Тэмдэглэл олдсонгүй")Хадгална.
PUT-ийн задаргаа
python
@app.put("/notes/{note_id}")
def update_note(note_id: int, note: Note):@app.put — шинэ decorator. note_id: int нь path-аас (аль тэмдэглэл). note: Note нь body-оос (шинэ өгөгдөл). Бүлэг 5-ын Хичээл 2-т сурсанчлан FastAPI хоёрыг автоматаар ялгадаг.
Функцийн логик: id-гаар тэмдэглэлийг хайна. Олдвол түүний талбаруудыг шинэ утгаар дарж бичнэ. Олдохгүй бол 404.
existing["text"] = note.text — олдсон тэмдэглэлийн text-ыг хэрэглэгчийн илгээсэн шинэ text-ээр солино. Ижил зүйлийг priority, done-д хийнэ. id-г өөрчлөхгүй — id хэзээ ч өөрчлөгддөггүй, тэр бол тэмдэглэлийн байнгын таних тэмдэг.
Туршина
Эхлээд POST-оор нэг тэмдэглэл үүсгэнэ (/docs):
json
{"text": "Сүү авах", "priority": "чухал"}Хариу: {"text":"Сүү авах","priority":"чухал","done":false,"id":1}.
Одоо PUT-оор түүнийг засна. /docs -> PUT /notes/{note_id}. Path талбарт 1, body-д:
json
{"text": "Сүү ба талх авах", "priority": "энгийн", "done": true}Server response (статус 200):
json
{
"text": "Сүү ба талх авах",
"priority": "энгийн",
"done": true,
"id": 1
}Тэмдэглэл өөрчлөгдсөн. text шинэчлэгдсэн, priority "чухал"-аас "энгийн" болсон, done true болсон. id нь 1 хэвээр.
GET-ээр батлана: http://127.0.0.1:8000/notes — жагсаалтад ганц тэмдэглэл, шинэчлэгдсэн утгатай.
PUT-ийн нэг зүйлийг сайн ойлгох ёстой. Дээрх PUT дуудлагад бид text, priority, done гурвыг бүгдийг илгээсэн. Хэрэв зөвхөн нэгийг илгээвэл яах вэ?
/docs -> PUT /notes/1, body-д зөвхөн:
json
{"text": "Зөвхөн текст өөрчлөх"}Server response (статус 200):
json
{
"text": "Зөвхөн текст өөрчлөх",
"priority": "энгийн",
"done": false,
"id": 1
}Анхаараарай: priority нь "энгийн" болж, done нь false болсон. Гэтэл өмнө нь бид тэдгээрийг "энгийн" биш, true болгосон байсан (өмнөх PUT дээр done: true байсан).
Юу болов? PUT model нь Note бөгөөд priority болон done нь default утгатай. Хэрэглэгч тэдгээрийг илгээгээгүй тул Pydantic default-ыг ("энгийн", false) хэрэглэсэн. Дараа нь PUT функц тэр default утгуудаар хуучин утгыг дарж бичсэн.
Энэ бол PUT-ийн жинхэнэ утга: PUT нь бүтэн зүйлийг солино. "Зөвхөн текстийг өөрчил, бусдыг нь хэвээр үлдээ" гэсэн үг биш. "Энэ бүтэн шинэ хувилбараар сольж тавь" гэсэн үг. Илгээгээгүй талбарууд default-руу буцна.
Тиймээс PUT хийхэд бүх талбарыг илгээх нь зөв — эс бөгөөс илгээгээгүй талбарууд default-руу буцаж, санамсаргүй өгөгдөл алдагдана.
"Зөвхөн нэг талбарыг өөрчил, бусдыг хэвээр үлдээ" гэсэн үйлдэлд PATCH гэсэн өөр method байдаг. Гэхдээ PATCH нь илүү нарийн (аль талбар илгээгдсэн, аль нь илгээгдээгүйг ялгах ёстой) тул энэ сургалтын хүрээнээс гадуур. Одоохондоо PUT-ийн "бүтэн солих" зан төлөвийг мэдэж байх нь хангалттай.
DELETE бол "энэ зүйлийг арил" гэсэн method. Түүнд зөвхөн аль зүйлийг гэдэг мэдээлэл хэрэгтэй — path parameter (id). Body шаардлагагүй, учир нь устгахад нэмэлт өгөгдөл хэрэггүй.
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.delete("/notes/{note_id}")
def delete_note(note_id: int):
for i, note in enumerate(notes):
if note["id"] == note_id:
notes.pop(i)
return {"message": f"{note_id} дугаартай тэмдэглэл устгагдлаа"}
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
DELETE-ийн задаргаа
@app.delete("/notes/{note_id}") — шинэ decorator. Зөвхөн path parameter (note_id), body байхгүй.
enumerate(notes) — Курс 2-ын Бүлэг 2-оос. Жагсаалтын элемент бүрийг индекстэй нь хамт өгдөг. Яагаад индекс хэрэгтэй вэ? Учир нь устгахын тулд элемент жагсаалтын хэддүгээрт байгааг мэдэх ёстой.
notes.pop(i) — жагсаалтаас i-дугаар элементийг устгана.
Олдохгүй бол 404 — устгах гэсэн зүйл байхгүй бол хэрэглэгчид мэдэгдэх ёстой.
Туршина
Эхлээд GET-ээр одоо ямар тэмдэглэл байгааг харна: http://127.0.0.1:8000/notes. Нэг тэмдэглэл (id 1) байх ёстой.
/docs -> DELETE /notes/{note_id}. Path талбарт 1, Execute:
Server response (статус 200):
json
{"message":"1 дугаартай тэмдэглэл устгагдлаа"}GET-ээр батлана: http://127.0.0.1:8000/notes — одоо хоосон жагсаалт ([]).
Дахин ижил DELETE хийвэл (DELETE /notes/1):
Server response (статус 404):
json
{"detail":"Тэмдэглэл олдсонгүй"}Хоёр дахь удаад 404 — учир нь тэмдэглэл аль хэдийн устсан.
DELETE амжилттай болоход ямар status code буцаах вэ? Хоёр түгээмэл сонголт бий.
200 (OK) — мессежтэй. Дээрх кодонд бид {"message": "..."} буцаасан, status нь default 200. Хэрэглэгч устгагдсаныг батлах мессеж авна.
204 (No Content) — юу ч буцаахгүй. Устгал амжилттай, буцаах юм алга. Body огт байхгүй.
204 хувилбар ийм харагдана:
python
@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 болон return None — 204 нь "агуулга байхгүй" гэсэн үг тул юу ч буцаахгүй.
Аль нь зөв бэ? Хоёулаа хүчинтэй. 204 нь илүү цэвэрхэн, стандартад ойр (RESTful гэж нэрлэдэг хэв маягт тохирдог). 200 мессежтэй нь хэрэглэгчид илүү тодорхой хариу өгдөг. Бид энэ сургалтад голдуу 204-ыг ашиглана, учир нь энэ нь илүү мэргэжлийн зан, гэхдээ хоёулаа зөв гэдгийг мэдэж байгаарай.
Анхаар: 204 буцаахад browser эсвэл /docs дээр хоосон хариу харагдана — энэ бол зөв, алдаа биш. 204 гэдэг нь яг "агуулга байхгүй" гэсэн үг.
Одоо нэг чухал ойлголтыг энгийн үгээр авч үзье. Энэ нь POST, PUT, DELETE гурвыг ялгах гүнзгий санаа юм.
Idempotent гэдэг үг нь "нэг удаа хийсэн ч, олон удаа хийсэн ч ижил үр дүнд хүрдэг" гэсэн утгатай. Method бүрийг энэ шинжээр нь харцгаая.
POST idempotent биш
POST-ыг гурван удаа дуудвал гурван шинэ зүйл үүснэ. Ижил body-тэй ч гэсэн гурван өөр тэмдэглэл (id 1, 2, 3) бий болно. Нэг удаа ба гурван удаа хийхэд өөр үр дүн. Тиймээс POST idempotent биш.
Практик утга: хэрэглэгч "нэмэх" товч гурван удаа дарвал гурван давхардал үүснэ. Бид үүнийг Бүлэг 5-ын Хичээл 1-т дурдсан.
PUT idempotent
PUT-ыг ижил body-тэйгээр гурван удаа дуудвал юу болох вэ? Эхний удаад тэмдэглэл шинэчлэгдэнэ. Хоёр дахь удаад — ижил утгаар дахин шинэчлэгдэнэ, өөрчлөлт байхгүй. Гурав дахь удаад — мөн адил. Нэг удаа ба гурван удаа хийхэд ижил үр дүн (тэмдэглэл нь тэр утгатай болно). Тиймээс PUT idempotent.
Практик утга: интернэт удаашраад хэрэглэгч PUT товчийг хоёр удаа дарвал ямар ч хор хөнөөлгүй — үр дүн ижил.
DELETE — бараг idempotent
DELETE-ийг гурван удаа дуудвал? Эхний удаад тэмдэглэл устана. Хоёр дахь удаад — тэмдэглэл аль хэдийн байхгүй тул 404. Гурав дахь удаад — мөн 404. Эцсийн байдал ижил (тэмдэглэл байхгүй), гэхдээ status code өөр (эхнийх 200/204, дараагийнх 404). Тиймээс DELETE-ийг ихэвчлэн idempotent гэж үздэг — эцсийн үр дүн (зүйл байхгүй) нэг удаа ба олон удаа хийхэд ижил.
Хүснэгтээр
Method | Idempotent үү | Гурван удаа дуудвал |
|---|---|---|
GET | Тийм | Ижил өгөгдөл гурван удаа |
POST | Үгүй | Гурван шинэ зүйл |
PUT | Тийм | Нэг л удаагийн үр дүн |
DELETE | Тийм (голдуу) | Устгагдсан хэвээр |
Яагаад энэ ойлголт чухал вэ? Учир нь бодит ертөнцөд сүлжээ найдваргүй. Хүсэлт илгээгдсэн ч хариу эргэж ирэхгүй байвал хэрэглэгч дахин илгээдэг. Idempotent method-д энэ нь аюулгүй; POST-д давхардал үүсгэдэг. Method-ийн энэ шинжийг мэдэх нь найдвартай систем зохиоход тусалдаг.
Одоо танд бүрэн CRUD байна. Дөрвөн method, дөрвөн үйлдэл:
Method | Зам | CRUD | Юу хийх | Status |
|---|---|---|---|---|
POST |
| Create | Шинэ тэмдэглэл үүсгэх | 201 |
GET |
| Read | Бүх тэмдэглэл унших | 200 |
GET |
| Read | Нэг тэмдэглэл унших | 200 / 404 |
PUT |
| Update | Тэмдэглэл засах | 200 / 404 |
DELETE |
| Delete | Тэмдэглэл устгах | 204 / 404 |
Энэ таван endpoint бол бараг бүх API-ийн үндэс юм. Ямар ч нөөц (тэмдэглэл, ном, хэрэглэгч, захиалга) дээр ижил дардас давтагдана: үүсгэх, бүгдийг унших, нэгийг унших, засах, устгах. Та энэ дардсыг нэг удаа эзэмшсэн бол хаана ч ашиглана.
/docs хуудсаа нээж, бүхэлд нь хараарай. Таван мөр, өнгөөр ялгагдсан:
GET /notes — ногоон
POST /notes — шар/улбар шар
GET /notes/{note_id} — ногоон
PUT /notes/{note_id} — цэнхэр/шар
DELETE /notes/{note_id} — улаан
Swagger UI method бүрийг өнгөөр ялгаж, DELETE-ийг улаанаар (аюултай үйлдэл) харуулдаг. Хуудсыг хараад л таны API юу хийж чадахыг бүрэн ойлгоно — та ганц мөр баримт бичиг бичээгүй.
PUT-д body мартах
python
@app.put("/notes/{note_id}")
def update_note(note_id: int): # note: Note байхгүй
...PUT нь засах ёстой, гэхдээ юугаар засахаа мэдэхгүй — body параметр байхгүй. Логик утгагүй. PUT-д бараг үргэлж body хэрэгтэй.
DELETE-д body хүлээх
python
@app.delete("/notes/{note_id}")
def delete_note(note_id: int, note: Note): # body шаардлагагүй
...DELETE-д body ихэвчлэн хэрэггүй — устгахад нэмэлт өгөгдөл шаардлагагүй, зөвхөн id хангалттай. Body нэмэх нь хэрэглэгчийг төөрөгдүүлнэ. DELETE-ийг path parameter-тэй, body-гүй байлга.
enumerate-гүйгээр устгах гэж
python
@app.delete("/notes/{note_id}")
def delete_note(note_id: int):
for note in notes:
if note["id"] == note_id:
notes.remove(note) # давталтын дунд жагсаалт өөрчлөх
return {"message": "устгагдлаа"}notes.remove(note) нь ажиллаж болох ч давталтын дунд жагсаалтыг өөрчлөх нь эрсдэлтэй — нарийн тохиолдолд элемент алгасах алдаа гардаг. enumerate + pop(i), эсвэл олдсоны дараа давталтаас break хийж, дараа нь устгах нь илүү найдвартай. (Түвшин 4-т өгөгдлийн сан энэ бүхнийг илүү цэвэрхэн болгоно.)
id-г PUT дээр өөрчлөх
python
@app.put("/notes/{note_id}")
def update_note(note_id: int, note: Note):
for existing in notes:
if existing["id"] == note_id:
existing["id"] = 999 # id өөрчлөх — буруу!
...id-г хэзээ ч өөрчлөх ёсгүй. Тэр бол тэмдэглэлийн байнгын, давтагдашгүй таних тэмдэг. id өөрчлөгдвөл түүнийг лавласан бусад бүх зүйл эвдэрнэ. PUT-д зөвхөн агуулгыг засна, id-г хэвээр үлдээнэ.
Хүсвэл бүрэн CRUD-ийг дараалан туршиж үзээрэй: хоёр тэмдэглэл POST-оор үүсгэ, GET-ээр жагсаалтыг хар, нэгийг PUT-оор зас, нөгөөг DELETE-ээр устга, дараа нь GET-ээр эцсийн байдлыг хар. Энэ бол бодит API-ийн бүтэн амьдралын мөчлөг.
Сонирхвол DELETE-ийн status code-ыг 204 болгож өөрчлөөд, /docs дээр туршаад үзээрэй. Хариу body хоосон байхыг ажиглаарай — 204 гэдэг нь яг тэр утгатай.
PUT — засах. Path (аль зүйл) + body (шинэ өгөгдөл). @app.put.
PUT нь бүтэн зүйлийг солино — илгээгээгүй талбарууд default-руу буцна. Тиймээс бүх талбарыг илгээ.
Зөвхөн нэг талбар өөрчлөх нь PATCH-ийн ажил (энэ сургалтын хүрээнээс гадуур).
DELETE — устгах. Зөвхөн path (id), body шаардлагагүй. @app.delete.
DELETE-ийн status: 204 (No Content, цэвэрхэн) эсвэл 200 (мессежтэй). Бид голдуу 204.
Idempotency: POST idempotent биш (давхардана); PUT, DELETE idempotent (давтахад аюулгүй).
id-г хэзээ ч өөрчлөхгүй — тэр бол байнгын таних тэмдэг.
Бүрэн CRUD = POST/GET/GET-нэг/PUT/DELETE — бараг бүх API-ийн үндэс.
Бүлэг 5 дууслаа. Та одоо бүрэн CRUD бичиж чадна — өгөгдөл үүсгэх, унших, засах, устгах. Pydantic шалгалттай, зөв status code-той, зөв method-той.
Гэхдээ нэг зүйл дутуу байна. Одоогоор таны endpoint-ууд хүлээж авсан бүх зүйлээ буцааж байна — id, бүх талбар, бүх дотоод өгөгдөл. Заримдаа энэ нь асуудалтай: хэрэглэгчид харуулах ёсгүй талбар (нууц үг, дотоод тэмдэглэл) байвал яах вэ? Мөн хариуны бүтэц яг ямар байхыг хэрхэн баталгаажуулах вэ?
Дараагийн бүлэгт бид response_model сурна — Pydantic-ийн нөгөө үүрэг, гарцын шүүлтүүр. Бүлэг 4-т би Pydantic-ийн хоёр үүргийг дурдсан: оролтын харуул (сурсан) болон гарцын шүүлтүүр (одоо). Бид хариугаа хянаж, зөвхөн зарлаcан талбарыг гаргаж, model доторх model-ыг ашиглаж, эцэст нь Түвшин 2-ын гол бүтээн байгуулалт — Тэмдэглэлийн API v1 руу орно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.