Ачааллж байна...
Таны API одоо гурван төрлийн алдааны хариу буцаадаг. HTTPException-оос ирдэг {"detail": "Тэмдэглэл олдсонгүй"}. Pydantic-аас ирдэг {"detail": [{...}, {...}]}. Мөн гэнэтийн алдаанаас ирдэг {"detail": "Internal Server Error"}.
Эдгээр нь бүгд ажилладаг бөгөөд ихэнх тохиолдолд хангалттай. Гэхдээ заримдаа та алдааны хариугаа өөрийнхөөрөө хэлбэржүүлэхийг хүсдэг: нэмэлт талбар оруулах, тогтвортой бүтэц үүсгэх, эсвэл алдаа гарсан цагийг тэмдэглэх.
Энэ хичээлд бид exception handler сурна — FastAPI-ийн стандарт алдааны хариуг өөрчлөх механизм. Мөн хэзээ үүнийг хийх нь зөв, хэзээ илүүц болохыг шударгаар ярина.
Эхлээд одоо юу болж байгааг тодорхой болгоё. Таны API-ийн алдааны хариунууд ийм харагдана.
404 (HTTPException-оос):
json
{"detail":"Тэмдэглэл олдсонгүй"}422 (Pydantic-аас):
json
{"detail":[{"type":"missing","loc":["body","text"],"msg":"Field required","input":{}}]}500 (гэнэтийн алдаанаас):
json
{"detail":"Internal Server Error"}Гурвуулаа detail гэсэн түлхүүртэй — энэ бол сайн, тогтвортой. Гэхдээ detail-ийн утга нь өөр өөр: заримдаа текст, заримдаа жагсаалт.
Таны API-г ашиглаж байгаа программ ингэж бичих ёстой болно:
python
error = response.json()["detail"]
if isinstance(error, str):
print(error)
else:
print(error[0]["msg"])Эвгүй. Хэрэв бүх алдаа яг ижил бүтэцтэй байсан бол илүү хялбар байх байсан.
Зоогийн газарт асуудал гарахад зөөгч янз бүрээр хариулж болно. "Тийм хоол байхгүй." "Захиалга буруу байна." "Гал тогоонд асуудал гарлаа."
Гурвуулаа ойлгомжтой. Гэхдээ том зоогийн газар албан ёсны хариуны хэлбэр тогтоодог: асуудал бүрд дугаар, тайлбар, цаг, юу хийхийг санал болгосон мөр. Тэгвэл зочин ямар ч асуудалд ижил бүтэцтэй хариу авдаг бөгөөд түүнийг ойлгох, бүртгэх, шийдвэрлэх нь хялбар болдог.
Exception handler бол тэр албан ёсны хэлбэрийг тогтоох механизм юм.
FastAPI-д exception handler нь @app.exception_handler(...) гэсэн decorator-аар зарлагддаг. Тэр нь "ийм төрлийн алдаа гарвал ийм хариу буцаа" гэж хэлдэг.
main.py файлаа дараах кодоор солино:
python
# main.py
import os
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
load_dotenv()
APP_NAME = os.getenv("APP_NAME", "Тэмдэглэлийн API")
app = FastAPI(title=APP_NAME)
notes = [
{"id": 1, "text": "Сүү авах", "done": False},
{"id": 2, "text": "Номоо буцаах", "done": True},
]
class NoteCreate(BaseModel):
text: str
done: bool = False
@app.exception_handler(HTTPException)
def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": True,
"status": exc.status_code,
"message": exc.detail,
"path": str(request.url.path),
},
)
@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="Тэмдэглэл олдсонгүй")Хадгална.
Кодын задаргаа
Хоёр шинэ import:
Request — ирж буй хүсэлтийн бүх мэдээллийг агуулсан объект. Хаяг, header, method — бүгд түүнд байна. Бид түүнээс замыг (request.url.path) авна.
JSONResponse — гараар JSON хариу үүсгэх class. Ердийн endpoint-д бид зөвхөн dictionary буцаадаг ба FastAPI түүнийг хувиргадаг. Handler дотор бид status code-ыг өөрөө удирдах ёстой тул бүрэн хариу объект үүсгэнэ.
python
@app.exception_handler(HTTPException)
def http_exception_handler(request: Request, exc: HTTPException):Decorator нь "HTTPException гарвал энэ функцийг дууд" гэж хэлж байна. Функц хоёр параметр авна: request (ямар хүсэлт байсан) болон exc (ямар алдаа гарсан).
exc нь таны шидсэн HTTPException объект. Түүнээс exc.status_code (404) болон exc.detail ("Тэмдэглэл олдсонгүй") гаргаж авна.
python
return JSONResponse(
status_code=exc.status_code,
content={...},
)status_code=exc.status_code — анхны status code-ыг хадгална. 404 бол 404 хэвээр. Хэрэв энд тогтмол тоо бичвэл бүх алдаа ижил код буцаана — буруу.
content={...} — хариуны бие. Энэ бол таны шинэ бүтэц.
Дөрвөн талбар: error (энэ бол алдаа гэсэн тодорхой тэмдэг), status (тоо), message (тайлбар), path (хаана гарсан).
Туршина
http://127.0.0.1:8000/notes/99Хариу (статус 404):
json
{
"error": true,
"status": 404,
"message": "Тэмдэглэл олдсонгүй",
"path": "/notes/99"
}Алдааны хариу өөрчлөгдлөө. {"detail": "..."} биш, таны тогтоосон дөрвөн талбартай бүтэц.
Status code нь 404 хэвээр — зөвхөн хариуны бие өөрчлөгдсөн. Энэ чухал: status code бол HTTP-ийн стандарт, түүнийг өөрчлөх ёсгүй.
path талбар нь /notes/99 — request объектоос ирсэн. Энэ нь debug хийхэд ашигтай: log уншиж байхад алдаа яг хаана гарсныг харна.
Endpoint-ийн кодод гар хүрээгүй. raise HTTPException(...) хэвээр. Handler нь бүх HTTPException-ыг барьж, шинэ хэлбэрт оруулж байна — таны хаана ч бичсэн байсан.
Бусад алдаа ч баригдана
http://127.0.0.1:8000/байхгүй-замХариу (статус 404):
json
{
"error": true,
"status": 404,
"message": "Not Found",
"path": "/байхгүй-зам"
}Энэ 404-ыг та шидээгүй — FastAPI өөрөө шидсэн (тийм зам байхгүй тул). Гэхдээ тэр ч бас HTTPException тул таны handler барьсан.
Энэ бол handler-ийн хүч: бүх HTTPException, хаанаас ирсэн ч, нэг хэлбэрт орно.
Одоо 422 алдааг харъя:
http://127.0.0.1:8000/notes/abcХариу (статус 422):
json
{"detail":[{"type":"int_parsing","loc":["path","note_id"],...}]}Өөрчлөгдөөгүй. Учир нь validation алдаа нь HTTPException биш — тэр нь RequestValidationError гэсэн өөр төрөл. Таны handler зөвхөн HTTPException-ыг барьдаг.
Түүнийг ч барихыг хүсвэл тусдаа handler бичнэ:
python
# main.py (нэмэлт)
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": True,
"status": 422,
"message": "Илгээсэн өгөгдөл буруу байна",
"path": str(request.url.path),
"details": exc.errors(),
},
)Хадгална.
Кодын задаргаа
from fastapi.exceptions import RequestValidationError — validation алдааны төрлийг import хийж байна.
exc.errors() — Pydantic-ийн олсон бүх алдааны жагсаалт. Энэ нь өмнө нь detail талбарт байсан яг тэр агуулга.
Бүтэц нь өмнөх handler-тэй ижил — error, status, message, path. Нэмэлт details талбар нь техникийн дэлгэрэнгүйг агуулна.
Туршина
http://127.0.0.1:8000/notes/abcХариу (статус 422):
json
{
"error": true,
"status": 422,
"message": "Илгээсэн өгөгдөл буруу байна",
"path": "/notes/abc",
"details": [
{
"type": "int_parsing",
"loc": ["path", "note_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc",
"url": "https://errors.pydantic.dev/2.x/v/int_parsing"
}
]
}Одоо бүх алдаа ижил бүтэцтэй. 404 ч, 422 ч, гурван ижил талбартай (error, status, message, path) бөгөөд 422 нь нэмэлт details агуулна.
Таны API-г ашиглаж байгаа программ одоо ингэж бичиж чадна:
python
response = requests.get(url)
if response.status_code >= 400:
data = response.json()
print(f"Алдаа {data['status']}: {data['message']}")Ганц дүрэм, бүх алдаанд ажиллана. isinstance шалгах шаардлагагүй. Энэ бол тогтвортой бүтцийн ашиг.
Handler-ийн бас нэг ашиг: техникийн алдааг хүнд ойлгомжтой болгох.
Pydantic-ийн "Input should be a valid integer, unable to parse string as an integer" гэдэг нь англиар, техникийн. Хэрэглэгчид (ялангуяа Монгол хэрэглэгчид) энэ нь тодорхойгүй.
Handler дотор түүнийг орчуулж эсвэл хялбаршуулж болно:
python
@app.exception_handler(RequestValidationError)
def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = exc.errors()
first = errors[0] if errors else {}
field = first.get("loc", ["талбар"])[-1]
return JSONResponse(
status_code=422,
content={
"error": True,
"status": 422,
"message": f"'{field}' талбар буруу байна",
"path": str(request.url.path),
"details": errors,
},
)first.get("loc", ["талбар"])[-1] — алдааны байршлын сүүлчийн элемент, өөрөөр хэлбэл талбарын нэр. ["path", "note_id"] бол "note_id"; ["body", "author", "name"] бол "name".
Хариу:
json
{
"error": true,
"status": 422,
"message": "'note_id' талбар буруу байна",
"path": "/notes/abc",
"details": [...]
}Хүнд ойлгомжтой мессеж дээд талд, техникийн дэлгэрэнгүй details-д. Хоёулаа байгаа нь хамгийн сайн: хүн эхнийхийг уншина, программ хоёр дахийг ашиглана.
Гурав дахь төрөл: таны кодод гарсан гэнэтийн алдаа (500).
python
# main.py (нэмэлт, туршилт)
@app.get("/crash")
def crash():
x = 1 / 0 # зориудаар алдаа
return {"x": x}/crash руу хандвал терминал дээр traceback, browser дээр:
json
{"detail":"Internal Server Error"}Үүнийг ч барьж болно:
python
# main.py (нэмэлт)
@app.exception_handler(Exception)
def general_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"error": True,
"status": 500,
"message": "Серверт алдаа гарлаа. Дараа дахин оролдоно уу.",
"path": str(request.url.path),
},
)Exception бол Python-ы бүх алдааны эцэг class (Курс 2-ын Бүлэг 13-аас). Тиймээс энэ handler нь бусад handler барьж чадаагүй бүх зүйлийг барина.
Хариу (статус 500):
json
{
"error": true,
"status": 500,
"message": "Серверт алдаа гарлаа. Дараа дахин оролдоно уу.",
"path": "/crash"
}Чухал аюулгүй байдлын зарчим
Энд нэг зүйлийг сайтар анзаараарай. Handler нь алдааны жинхэнэ мессежийг харуулаагүй. ZeroDivisionError: division by zero гэж бичээгүй, зөвхөн ерөнхий "Серверт алдаа гарлаа" гэсэн.
Энэ бол зориудаар. Дотоод алдааны мессеж нь таны системийн бүтцийг илчилж болно: файлын зам, өгөгдлийн сангийн бүтэц, ашиглаж буй сангууд. Халдагч тэр мэдээллийг ашиглаж болно.
Дүрэм: хэрэглэгчид ерөнхий мессеж, log-д бүрэн дэлгэрэнгүй.
Тиймээс бодит handler-т log бичих нь зүйтэй:
python
@app.exception_handler(Exception)
def general_exception_handler(request: Request, exc: Exception):
print(f"АЛДАА: {request.url.path} — {type(exc).__name__}: {exc}")
return JSONResponse(
status_code=500,
content={
"error": True,
"status": 500,
"message": "Серверт алдаа гарлаа. Дараа дахин оролдоно уу.",
"path": str(request.url.path),
},
)print(...) нь терминал дээр (танд) бүрэн мэдээлэл өгнө; хэрэглэгч зөвхөн ерөнхий мессеж авна. Хоёр өөр үзэгч, хоёр өөр түвшний дэлгэрэнгүй.
(Бодит production-д print биш, logging модуль ашигладаг — тэр нь илүү зохион байгуулалттай, файлд бичдэг, түвшин ялгадаг. Гэхдээ энэ курсын хүрээнээс гадуур. print нь ойлголтыг харуулахад хангалттай.)
Туршилтын дараа /crash endpoint-ыг устгаарай.
Одоо шударга ярилцъя. Ихэнх API-д exception handler хэрэггүй.
FastAPI-ийн стандарт {"detail": "..."} бүтэц нь бүрэн ажиллагаатай, ойлгомжтой, дэлхий нийтэд танил. Түүнийг өөрчлөх нь нэмэлт код, нэмэлт төвөг үүсгэдэг.
Handler дараах үед утгатай:
Таны API-г олон программ ашигладаг бөгөөд тэдгээрт тогтвортой бүтэц хэрэгтэй.
Танд нэмэлт мэдээлэл хэрэгтэй (алдааны код, цаг, request id).
Хэрэглэгчид ойлгомжтой, орчуулагдсан мессеж хэрэгтэй.
Дотоод алдааны мэдээллийг нуух шаардлагатай (500 handler — энэ нь бараг үргэлж зөв).
Handler дараах үед илүүц:
Жижиг, дотоод API.
Зөвхөн та өөрөө ашиглана.
Стандарт бүтэц хангалттай.
Хамгийн ашигтай нэг handler: Exception-ы handler (500 нуух). Энэ нь аюулгүй байдлын ашигтай бөгөөд бараг бүх production API-д байдаг. Бусад нь сонголт.
Бид Тэмдэглэлийн API v2-т энгийн, тогтвортой handler ашиглана — гэхдээ хэт төвөгтэй болгохгүй. Capstone-д мөн адил.
status_code-ыг тогтмол бичих
python
@app.exception_handler(HTTPException)
def handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=400, # тогтмол — буруу!
content={"message": exc.detail},
)Одоо бүх HTTPException 400 буцаана — 404 ч, 403 ч, 409 ч. Таны API худал ярина.
Засвар: status_code=exc.status_code.
JSONResponse-ыг import хийхгүй
python
from fastapi import FastAPI, HTTPException, Request
# JSONResponse дутууГаралт:
NameError: name 'JSONResponse' is not definedЗасвар: from fastapi.responses import JSONResponse.
Handler дотор dictionary буцаах
python
@app.exception_handler(HTTPException)
def handler(request: Request, exc: HTTPException):
return {"message": exc.detail} # JSONResponse бишHandler нь Response объект буцаах ёстой, ердийн dictionary биш. Учир нь handler нь status code-ыг өөрөө удирдах ёстой; dictionary түүнийг агуулж чадахгүй.
Ердийн endpoint-д dictionary буцаах нь зөв (FastAPI хувиргана), гэхдээ handler-т JSONResponse заавал.
Handler-ийн параметрийг мартах
python
@app.exception_handler(HTTPException)
def handler(exc: HTTPException): # request дутуу
...Handler нь хоёр параметр авах ёстой: request эхэлж, exc дараа нь. Дараалал ч чухал.
Exception handler-ыг хэт өргөн болгох
python
@app.exception_handler(Exception)
def handler(request: Request, exc: Exception):
return JSONResponse(status_code=500, content={"message": str(exc)})str(exc) — алдааны жинхэнэ мессеж хэрэглэгчид очно. Дээр ярьсан аюулгүй байдлын зарчмыг зөрчиж байна. Дотоод мессежийг log-д, хэрэглэгчид ерөнхий мессеж.
Хүсвэл handler-т "timestamp" талбар нэмж, datetime.now().isoformat() утга өгч үзээрэй. Алдаа гарсан цагийг хариунд оруулах нь бодит API-д түгээмэл бөгөөд debug хийхэд ашигтай.
Сонирхвол handler-үүдээ түр зуур тайлбар (#) болгож, стандарт FastAPI хариуг харна уу. Дараа нь буцааж идэвхжүүлээд өөрийн хариугаа хараарай. Хоёрыг зэрэгцүүлэн харах нь handler яг юу өөрчилж байгааг тодруулна.
Exception handler нь тодорхой төрлийн алдаа гарахад буцаах хариуг өөрчилнө: @app.exception_handler(ТӨРӨЛ).
Handler нь хоёр параметр авна: request (хүсэлт) болон exc (алдаа); JSONResponse буцаана, dictionary биш.
status_code=exc.status_code — анхны кодыг хадгална, тогтмол тоо бичихгүй.
Гурван түгээмэл handler: HTTPException (404/403 гэх мэт), RequestValidationError (422), Exception (500).
Тогтвортой бүтэц (error, status, message, path) нь API ашиглагчийн кодыг хялбарчилна.
Аюулгүй байдал: 500 алдаанд дотоод мессежийг хэрэглэгчид бүү харуул — log-д бүрэн, хэрэглэгчид ерөнхий.
Endpoint-ийн кодод гар хүрэхгүй — handler нь бүх алдааг төвлөрсөн байдлаар барина.
Ихэнх жижиг API-д handler илүүц. Хамгийн ашигтай нь Exception handler (дотоод мэдээлэл нуух).
Бүлэг 9-ийн онолын хэсэг дууслаа. Та одоо нууцаа кодоос салгаж, алдааны хариугаа хэлбэржүүлж чадна. Түвшин 3-ын бүх эд анги — router (Бүлэг 7), Depends болон API key (Бүлэг 8), тохиргоо болон handler (Бүлэг 9) — таны гарт байна.
Дараагийн хичээлд бид тэдгээрийг бүгдийг нэгтгэж, Тэмдэглэлийн API v2 бүтээнэ. Түвшин 2-ын v1-ийг санаж байна уу — нэг файл, далан мөр, хамгаалалтгүй, тохиргоогүй? Бид түүнийг олон файлт мэргэжлийн бүтэц рүү шилжүүлж, API key-ээр хамгаалж, тохиргоог .env-ээс уншиж, алдааны хариуг нэгтгэнэ. Зан төлөв нь бараг ижил хэвээр — гол нь бүтэц. Тэр нь энэ түвшний гол сургамж: ижил ажлыг мэргэжлийн хэлбэрээр хийх.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.