Ачааллж байна...
Өмнөх хичээлийн төгсгөлд бид тодорхой асуудалтай үлдсэн. Танд нөхцөлт status code хэрэгтэй: ном олдвол 200, олдохгүй бол 404. Decorator дээрх status_code параметр үүнийг хийж чадахгүй, учир нь тэр нь функц ажиллахаас өмнө шийдэгддэг. Response объектыг гараар өөрчлөх арга ажилладаг ч эвгүй, давтагддаг, гүнзгий функцээс хэрэглэхэд төвөгтэй.
Зөв шийдэл нь HTTPException юм. Энэ бол ганц мөрөөр алдааг зогсоож, зөв status code болон тайлбартай хариу буцаадаг хэрэгсэл. Энэ хичээлийн дараа таны API эцэст нь үнэн ярьдаг болно.
Курс 2-ын Бүлэг 13-т та raise гэдгийг сурсан. Санаа нь ийм байсан: алдаа гарвал үүнийг буцаах биш, шидэх.
python
def divide(a: int, b: int) -> float:
if b == 0:
raise ValueError("Тэгд хуваах боломжгүй")
return a / bЯагаад return биш raise гэж? Учир нь return нь "энэ бол хариу" гэсэн үг; raise нь "энэ бол хариу биш, ажил зогсож байна" гэсэн үг. Ялгаа нь чухал.
raise хийхэд Python функцээс шууд гардаг. Дараагийн мөрүүд ажиллахгүй. Алдаа нь дуудсан функц рүү, түүнээс дээш, try/except олдтол дамжина.
Яг тэр механизмыг FastAPI ашигладаг. Та HTTPException шидэхэд FastAPI түүнийг барьж авч, зөв HTTP хариу болгож хувиргадаг.
Зөөгч захиалга авахаар гал тогоо руу явлаа. Гэнэт мэдэв: тэр хоол дууссан байна.
Муу зөөгч гал тогоонд үлдэж, юу хийхээ бодож, эцэст нь хоосон таваг барьж ирээд зочны өмнө тавьдаг. Зочин "энэ юу вэ?" гэж асуухад "хоол дууссан" гэж хэлдэг. Зочин аль хэдийн хутга сэрээгээ авсан байна.
Сайн зөөгч тэр даруй зогсдог, эргэж ирээд шууд хэлдэг: "Уучлаарай, тэр хоол дууссан байна." Хоосон таваг байхгүй. Зочин ойлгож, өөр зүйл захиална.
HTTPException бол хоёр дахь зөөгч юм. Асуудал илэрсэн даруйд зогсож, тодорхой мессежтэй буцна. Хоосон таваг, будлиантай хариу байхгүй.
main.py файлаа дараах кодоор солино:
python
# main.py
from fastapi import FastAPI, HTTPException
app = FastAPI(title="Номын API")
books = [
{"id": 1, "title": "Цог хийморь", "year": 1962},
{"id": 2, "title": "Цагаан хэрэм", "year": 1971},
{"id": 3, "title": "Хөх судар", "year": 1971},
]
@app.get("/books")
def get_books():
return books
@app.get("/books/{book_id}")
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail="Ном олдсонгүй")Хадгална.
Кодын задаргаа
Эхний мөрөнд шинэ import байна:
python
from fastapi import FastAPI, HTTPExceptionHTTPException бол FastAPI-аас ирж байгаа class. Түүнийг тусад нь import хийх ёстой.
Функцийн төгсгөлийн мөр бол гол зүйл:
python
raise HTTPException(status_code=404, detail="Ном олдсонгүй")Гурван хэсэгтэй. raise бол Python-ы түлхүүр үг — Курс 2-оос танил. HTTPException(...) нь тэр class-аас объект үүсгэж байна. Дотор нь хоёр параметр: status_code (ямар тоо буцаах вэ) болон detail (юу тайлбарлах вэ).
Функцийн бүтцийг ажиглаарай. for давталт номыг хайна. Олдвол return book — функц тэндээ дуусна. Олдохгүй бол давталт дуусаж, доорх raise мөр ажиллана.
raise мөрөнд хүрэх нь зөвхөн олдоогүй тохиолдолд болно. Учир нь return нь функцээс шууд гаргадаг.
Ном олдсон тохиолдол
http://127.0.0.1:8000/books/2Хариу (статус 200):
json
{"id":2,"title":"Цагаан хэрэм","year":1971}Ердийн ажиллагаа. raise мөр хүртэл хүрээгүй.
Ном олдоогүй тохиолдол
http://127.0.0.1:8000/books/99Хариу (статус 404):
json
{"detail":"Ном олдсонгүй"}Хоёр зүйл зөв болсныг анзаараарай.
Нэгд, status code нь 404 — 200 биш, 500 биш. Таны API "хайсан зүйл тань байхгүй" гэж үнэн зөв хэлж байна.
Хоёрт, агуулга нь {"detail": ...} бүтэцтэй. Та detail гэсэн түлхүүрийг бичээгүй — FastAPI өөрөө тэр бүтцийг үүсгэсэн. HTTPException-д өгсөн detail параметрийн утга тэр талбарт орсон.
Энэ бүтэц яагаад чухал вэ? Учир нь FastAPI-ийн бүх алдаа ижил бүтэцтэй байдаг. Бүлэг 2-т үзсэн 422 validation алдааг санаж байна уу? Тэр ч бас detail гэсэн түлхүүртэй байсан. Одоо таны 404 ч ижил.
Тиймээс таны API-г ашиглаж байгаа программ ганц дүрэм бичихэд хангалттай:
python
response = requests.get(url)
if response.status_code >= 400:
error = response.json()["detail"]
print("Алдаа:", error)Энэ код 404, 422, 403 — бүх алдаанд ажиллана. Тогтвортой бүтэц бол сайн API-ийн шинж юм.
Энд нэг чухал ялгаа байна, түүнийг тодруулъя.
Бүлэг 2-т 422 гарахад таны функц огт дуудагдаагүй. FastAPI хүсэлтийг хаалган дээр зогсоосон, validation амжилтгүй болсон тул дотогш оруулаагүй.
Одоо 404 гарахад таны функц дуудагдсан. Тэр ажилласан, жагсаалтыг хайсан, олоогүй, дараа нь raise хийсэн.
Хоёр өөр газар, хоёр өөр цаг. Үүнийг тодорхой болгохын тулд print() нэмж үзье:
python
@app.get("/books/{book_id}")
def get_book(book_id: int):
print(f"Хайж байна: {book_id}")
for book in books:
if book["id"] == book_id:
return book
print("Олдсонгүй, 404 буцааж байна")
raise HTTPException(status_code=404, detail="Ном олдсонгүй")/books/99 руу хандвал терминал дээр хоёр мөр гарна:
Хайж байна: 99
Олдсонгүй, 404 буцааж байнаХарин /books/abc руу хандвал терминал дээр юу ч гарахгүй — validation хаалган дээр зогссон тул функц ажиллаагүй.
Энэ ялгааг ойлгох нь debug хийхэд тустай: 422 харвал таны кодыг битгий хай, type hint-ээ хар. 404 харвал таны код ажилласан, логикоо хар.
Нэг функцэд олон raise байж болно:
python
# main.py (нэмэлт)
@app.get("/books/{book_id}/chapter/{number}")
def get_chapter(book_id: int, number: int):
if number < 1:
raise HTTPException(status_code=400, detail="Бүлгийн дугаар 1-ээс их байх ёстой")
book = None
for b in books:
if b["id"] == book_id:
book = b
break
if book is None:
raise HTTPException(status_code=404, detail="Ном олдсонгүй")
if number > 20:
raise HTTPException(
status_code=404,
detail=f"'{book['title']}' номд {number}-р бүлэг байхгүй",
)
return {
"book": book["title"],
"chapter": number,
"content": "Энэ бүлгийн агуулга...",
}Гурван өөр алдааны нөхцөл байна.
Хүсэлт 1: буруу бүлгийн дугаар
http://127.0.0.1:8000/books/1/chapter/0Хариу (статус 400):
json
{"detail":"Бүлгийн дугаар 1-ээс их байх ёстой"}400 — Bad Request. Хэрэглэгч утга учиргүй зүйл хүслээ. 0 бол хүчинтэй бүхэл тоо тул validation өнгөрсөн, гэхдээ бизнесийн логикийн хувьд буруу.
Энэ ялгаа чухал: 422 бол төрлийн алдаа, 400 бол утгын алдаа. Хэрэглэгч abc илгээвэл 422 (тоо биш). 0 илгээвэл 400 (тоо мөн, гэхдээ утгагүй тоо).
Хүсэлт 2: байхгүй ном
http://127.0.0.1:8000/books/99/chapter/3Хариу (статус 404):
json
{"detail":"Ном олдсонгүй"}Хүсэлт 3: байхгүй бүлэг
http://127.0.0.1:8000/books/1/chapter/50Хариу (статус 404):
json
{"detail":"'Цог хийморь' номд 50-р бүлэг байхгүй"}Анзаараарай: detail талбарт f-string ашиглан динамик мессеж үүсгэсэн. Номын нэр болон бүлгийн дугаар мессежэд орсон. Энэ нь хэрэглэгчид илүү тустай — "Ном олдсонгүй" гэхээс "'Цог хийморь' номд 50-р бүлэг байхгүй" гэдэг нь хамаагүй тодорхой.
Хүсэлт 4: бүх зүйл зөв
http://127.0.0.1:8000/books/1/chapter/3Хариу (статус 200):
json
{"book":"Цог хийморь","chapter":3,"content":"Энэ бүлгийн агуулга..."}Гурван шалгалтыг бүгдийг өнгөрөөд эцсийн return мөрөнд хүрлээ.
Бүтцийг ажиглах
Энэ функцийн хэлбэрийг сайн ажиглаарай, учир нь энэ бол таны цаашид бичих бүх endpoint-ийн загвар юм:
1. Шалга -> буруу бол raise
2. Шалга -> буруу бол raise
3. Шалга -> буруу бол raise
4. Бүх зүйл зөв -> returnБүх алдааг эхэнд барьж, эцэст нь зөв хариу буцаана. Энэ загварыг англиар "guard clauses" гэж нэрлэдэг: хаалга бүрт нэг шалгалт, бүгдийг өнгөрсөн хүн л дотогш ордог.
Хувилбар нь ийм байж болох байсан:
python
if number >= 1:
if book is not None:
if number <= 20:
return {...}
else:
...
else:
...
else:
...Гүн үүрлэсэн, уншихад хэцүү, засахад аюултай. raise ашигласнаар код хавтгай болж, унших нь амархан болно. Курс 2-ын Бүлэг 13-т ярьсан зарчим энд бодит ашиг өгч байна.
detail параметрт dictionary ч өгч болно:
python
raise HTTPException(
status_code=404,
detail={
"code": "BOOK_NOT_FOUND",
"message": "Ном олдсонгүй",
"book_id": book_id,
},
)Хариу:
json
{"detail":{"code":"BOOK_NOT_FOUND","message":"Ном олдсонгүй","book_id":99}}Энэ нь илүү баялаг алдааны мэдээлэл өгч байна. code талбар нь машинд зориулагдсан (программ түүнийг шалгаж болно), message нь хүнд зориулагдсан, book_id нь юу хайсныг сануулж байна.
Бид сургалтын турш голдуу энгийн текст ашиглана, учир нь энэ нь хангалттай. Гэхдээ том API-д ийм бүтэцтэй алдаа түгээмэл байдгийг мэдэж байх нь зүйтэй. Бүлэг 9-д бид алдааны хариуг өөриймсүүлэх талаар илүү ярина.
/docs хуудсаа нээж, GET /books/{book_id} дээр товшино.
Responses хэсэгт хоёр мөр харагдана:
200 Successful Response
422 Validation Error404 харагдахгүй байна. Энэ бол дутагдал.
Яагаад? Учир нь raise HTTPException(...) нь функцийн дотор, ажиллах үед болдог зүйл. FastAPI таны кодыг гүйцэтгэхгүйгээр "энэ функц 404 шидэж магадгүй" гэдгийг мэдэх боломжгүй.
Үүнийг засах арга бий — decorator дээр responses гэсэн параметр ашиглана:
python
@app.get(
"/books/{book_id}",
responses={404: {"description": "Ном олдсонгүй"}},
)
def get_book(book_id: int):
...Одоо /docs дээр гурван мөр харагдана:
200 Successful Response
404 Ном олдсонгүй
422 Validation ErrorЭнэ нь зөвхөн баримт бичигт нөлөөлнө — ажиллагаанд огт нөлөөлөхгүй. Гэхдээ таны API-г ашиглах хүн ямар алдаа гарч болохыг урьдчилан мэдэх нь маш ашигтай.
Бид сургалтын турш үүнийг үргэлж бичихгүй (код урт болно), гэхдээ бодит төсөлд энэ нь сайн зуршил юм. Capstone-д бид түүнийг ашиглана.
Нэг чухал ялгаа. Дараах хоёр кодыг харьцуулъя:
python
# А хувилбар
raise HTTPException(status_code=404, detail="Ном олдсонгүй")
# Б хувилбар
raise ValueError("Ном олдсонгүй")А хувилбар буцаана:
Статус: 404
Агуулга: {"detail":"Ном олдсонгүй"}Б хувилбар буцаана:
Статус: 500
Агуулга: {"detail":"Internal Server Error"}Мөн терминал дээр улаан traceback гарна:
ValueError: Ном олдсонгүйЮу болов? FastAPI зөвхөн HTTPException-ыг таньдаг. Тэр нь FastAPI-ийн өөрийн class; FastAPI түүнийг барьж, зөв HTTP хариу болгодог.
Бусад бүх exception (ValueError, KeyError, TypeError) нь FastAPI-ийн хувьд гэнэтийн осол юм. Тэдгээр нь таны кодод алдаа гарсныг илтгэнэ, тиймээс FastAPI 500 буцаана.
Мөн анзаараарай: Б хувилбарт таны мессеж ("Ном олдсонгүй") хэрэглэгчид очоогүй. Хэрэглэгч зөвхөн "Internal Server Error" гэсэн ерөнхий текст харсан. Энэ бол зориудаар хийгдсэн — таны кодны дотоод алдааны мессеж гадагш алдагдах нь аюулгүй байдлын эрсдэлтэй.
Дүрэм: хэрэглэгчид зориулсан алдаанд HTTPException ашигла. Бусад exception нь таны кодны алдааг илтгэнэ.
return HTTPException (raise биш)
python
return HTTPException(status_code=404, detail="Ном олдсонгүй")Энэ бол маш түгээмэл алдаа бөгөөд хамгийн муу нь — ямар ч анхааруулга гардаггүй.
Хариу (статус 200):
json
{"status_code":404,"detail":"Ном олдсонгүй","headers":null}Юу болов? Та exception-ыг шидээгүй, зүгээр л объект үүсгээд буцаасан. FastAPI түүнийг ердийн Python объект гэж үзээд, JSON болгож, 200 статустай илгээсэн.
Хариу дотор 404 гэсэн тоо байгаа боловч тэр нь зөвхөн текст — жинхэнэ status code нь 200. Таны API дахин худал ярьж байна.
Дүрэм: HTTPException-ыг үргэлж raise хийнэ, хэзээ ч return хийхгүй.
Санаж авах арга: HTTPException бол хариу биш, ажлын зогсолт. Зогсолтыг буцааж болохгүй, шидэх ёстой.
Import мартах
python
from fastapi import FastAPI
# HTTPException import хийгээгүй
@app.get("/books/{book_id}")
def get_book(book_id: int):
raise HTTPException(status_code=404, detail="Ном олдсонгүй")Терминал дээр:
NameError: name 'HTTPException' is not definedЭнгийн засвар:
python
from fastapi import FastAPI, HTTPExceptionraise-ийн дараа код бичих
python
raise HTTPException(status_code=404, detail="Ном олдсонгүй")
print("Энэ мөр хэзээ ч ажиллахгүй")raise хийсний дараа функц шууд дуусдаг. Доорх мөр бол үхсэн код — хэзээ ч ажиллахгүй. Python анхааруулга өгөхгүй, зүгээр л алгасна.
Буруу status code сонгох
python
raise HTTPException(status_code=500, detail="Ном олдсонгүй")Техникийн хувьд ажиллана, гэхдээ утга нь буруу. 500 нь "би эвдэрлээ" гэсэн үг. Ном олдоогүй нь таны эвдрэл биш — хэрэглэгч байхгүй зүйл хайсан. Зөв тоо нь 404.
Status code сонгохдоо асуугаарай: "буруу нь хэн бэ?" Хэрэглэгч бол 4xx. Би бол 5xx.
Өмнөх хичээлд бид Response объект ашигласан түр зуурын шийдэл бичсэн. Одоо түүнийг цэвэрлэе. Хэрэв танд тэр код байгаа бол дараах хэлбэрээр солино:
python
# Хуучин (түр зуурын)
@app.get("/books/{book_id}")
def get_book(book_id: int, response: Response):
for book in books:
if book["id"] == book_id:
return book
response.status_code = 404
return {"detail": "Ном олдсонгүй"}
# Шинэ (зөв)
@app.get("/books/{book_id}")
def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
raise HTTPException(status_code=404, detail="Ном олдсонгүй")Код богино болсон. response параметр алга болсон. Response import хэрэггүй болсон. Мөн {"detail": ...} бүтцийг гараар бичих шаардлагагүй болсон — FastAPI өөрөө хийж өгнө.
Энэ бол сайн засварын шинж: бага код, илүү тодорхой утга.
Хүсвэл raise-ыг return болгож солиод үзээрэй (дээрх алдааны хэсэгт үзсэнээр). /docs дээр status code-ыг ажиглаарай — 200 гарахыг харна уу. Дараа нь буцааж raise болгоод дахин туршаарай. Энэ алдааг нэг удаа өөрийн нүдээр харсан хүн түүнийг дахин хийхгүй.
Сонирхвол detail талбарт dictionary өгч үзээрэй (дээр үзсэн хэлбэрээр) — хариу хэрхэн өөрчлөгдөхийг хараарай.
HTTPException нь алдааг зогсоож, зөв status code болон тайлбартай хариу буцаадаг. from fastapi import HTTPException.
Хэрэглэх хэлбэр: raise HTTPException(status_code=404, detail="...").
Үргэлж raise, хэзээ ч return хийхгүй. return хийвэл 200 статустай утгагүй хариу гарна.
FastAPI detail талбарыг автоматаар үүсгэдэг — бүх алдаа (404, 422, 403) ижил бүтэцтэй.
422 бол төрлийн алдаа (функц дуудагдахгүй); 404/400 бол логикийн алдаа (функц ажилласан, дараа нь шидсэн).
Загвар: эхэнд бүх шалгалт (raise), эцэст нь зөв хариу (return) — код хавтгай, унших амархан.
Зөвхөн HTTPException танигдана; бусад exception (ValueError, KeyError) нь 500 өгнө.
/docs дээр 404 харуулахын тулд decorator-т responses={404: {...}} нэмнэ (заавал биш).
Бүлэг 3-ын онолын хэсэг дууслаа. Та одоо JSON хариу удирдаж, status code сонгож, алдааг үнэнчээр мэдээлж чадна. Path parameter, query parameter, validation, HTTPException — Түвшин 1-ийн бүх эд анги таны гарт байна.
Дараагийн хичээлд бид тэдгээрийг бүгдийг нэг дор ашиглаж, бүрэн ажиллагаатай API-г эхнээс нь дуустал хамтдаа бүтээнэ. Энэ бол Түвшин 1-ийн бүтээн байгуулалт: Мэдээллийн API — жагсаалт өгдөг, дэлгэрэнгүй өгдөг, хайлт хийдэг, байхгүй зүйл дээр зөв 404 буцаадаг жинхэнэ API. Гэрийн даалгавар биш — бид түүнийг мөр мөрөөр нь хамтдаа бичнэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.