API๋ฅผ ๋ง๋ค ๋ ๊ฐ์ฅ ์ํํ ์๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋๋ค.
์ ์ ์๋ต์ ๋๊ตฌ๋ ์ ๊ฒฝ ์ด๋ค. ํ์ง๋ง ์ค๋ฌด์ ํ์ง์ ์๋ฌ ์ฒ๋ฆฌ์์ ๊ฐ๋ฆฐ๋ค.
์ด๋ฒ ๊ธ์์๋ FastAPI์์ ์๋ฌ๊ฐ ์ ํ์ํ์ง, HTTPException์ ๋ฌด์์ธ์ง, ๊ทธ๋ฆฌ๊ณ โ์ฌ๋ฐ๋ฅธ ์๋ฌโ๋ ๋ฌด์์ธ์ง๋ฅผ ๋จ๊ณ์ ์ผ๋ก ์ ๋ฆฌํ๋ค.
ํ๋ก๊ทธ๋๋ฐ์์ ์๋ฌ๋ ํ๋ก๊ทธ๋จ์ ์ ์์ ์ธ ํ๋ฆ์ด ๊นจ์ง ์ํ๋ค.
FastAPI ๊ธฐ์ค์ผ๋ก ๋ณด๋ฉด, ์๋ฌ๋ ํฌ๊ฒ ๋ ์ข ๋ฅ๋ก ๋๋๋ค.
| ๊ตฌ๋ถ | ์๋ฏธ | ์์ |
|---|---|---|
| ํด๋ผ์ด์ธํธ ์๋ฌ | ์์ฒญ์ด ์๋ชป๋จ | 404, 400 |
| ์๋ฒ ์๋ฌ | ์๋ฒ ๋ด๋ถ ๋ฌธ์ | 500 |
์ค์ํ ์์น์ด ํ๋ ์๋ค.
HTTP ์ํ ์ฝ๋๋ ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ด๋ โ๊ฒฐ๊ณผ ์์ฝโ์ด๋ค.
| ๋ฒ์ | ์๋ฏธ | ๋ํ ์ฝ๋ |
|---|---|---|
| 200๋ฒ๋ | ์ฑ๊ณต | 200 OK, 201 Created |
| 400๋ฒ๋ | ํด๋ผ์ด์ธํธ ์๋ฌ | 400, 404 |
| 500๋ฒ๋ | ์๋ฒ ์๋ฌ | 500 |
์๋ฅผ ๋ค์ด,
500 ์๋ฌ๋ฅผ ๊ทธ๋๋ก ๋ ธ์ถํ๋ ๊ฒ์ ์ต์ ์ด๋ค.
์ผ๋จ ์๋ฌด ์๋ฌ ์ฒ๋ฆฌ ์์ด API๋ฅผ ํ๋ ๋ง๋ค์ด๋ณด์.
from fastapi import FastAPI
app = FastAPI()
items = {
"foo": "ํฌ์์์์์์
!"
}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
return {"item": items[item_id]}
์ด ์ํ์์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
| ์์ฒญ | ๊ฒฐ๊ณผ |
|---|---|
| /items/foo | 200 OK |
| /items/bar | 500 Internal Server Error |
๋ฌธ์ ๋ ์ฌ๊ธฐ ์๋ค.
์ด๊ฑด ์์ ํ ์๋ชป๋ API๋ค.
FastAPI๋ ์ด๋ฅผ ์ํด HTTPException์ด๋ผ๋ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ค.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {
"foo": "ํฌ์์์์์์
!"
}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found"
)
return {"item": items[item_id]}
์ด์ ๊ฒฐ๊ณผ๋ ์ด๋ ๊ฒ ๋ฐ๋๋ค.
| ์์ฒญ | ์๋ต |
|---|---|
| /items/foo | 200 OK |
| /items/bar | 404 Not Found |
if user_id <= 0:
raise HTTPException(
status_code=400,
detail="User ID must be positive"
)
raise HTTPException(
status_code=400,
detail="Invalid request"
)
| ํ๋ | ์ญํ |
|---|---|
| status_code | HTTP ์ํ ์ฝ๋ |
| detail | ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ ๋ฉ์์ง |
detail์๋ ๋ฌธ์์ด๋ฟ ์๋๋ผ JSON์ผ๋ก ๋ณํ ๊ฐ๋ฅํ ๋ชจ๋ ๊ฐ์ ๋ฃ์ ์ ์๋ค.
raise HTTPException(
status_code=404,
detail={
"error": "ITEM_NOT_FOUND",
"message": "์์ดํ
์ ์ฐพ์ ์ ์์ต๋๋ค"
}
)
[ํด๋ผ์ด์ธํธ ์์ฒญ]
โ
[์๋ํฌ์ธํธ ํจ์ ์คํ]
โ
์กฐ๊ฑด ์คํจ
โ
raise HTTPException
โ
FastAPI๊ฐ ์๋ต ์์ฑ
โ
HTTP ์ํ ์ฝ๋ + ์๋ฌ ๋ฉ์์ง
โ
[ํด๋ผ์ด์ธํธ]
์๋ฌ๋ฅผ if / return์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์์ํ๋ฉด
์ฝ๋๋ ๊ธ๋ฐฉ ์ง์ ๋ถํด์ง๊ณ , ์๋ต ํ์๋ ์ ๊ฐ๊ฐ์ด ๋๋ค.
FastAPI์ ์ ์ญ ์์ธ ํธ๋ค๋ฌ๋ ์ด ๋ฌธ์ ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ํด๊ฒฐํ๋ค.
404, 400์ ๋๋ ๋น์ฆ๋์ค ์๋ฌ๊ฐ ์กด์ฌํจโ ์๋ชป๋ ๊ตฌ์กฐ
--------------------------------
if ์์ก ๋ถ์กฑ:
return {"error": "..."} # ์๋ํฌ์ธํธ๋ง๋ค ์ค๋ณต
โ
์ฌ๋ฐ๋ฅธ ๊ตฌ์กฐ
--------------------------------
raise CustomException
โ ์ ์ญ ํธ๋ค๋ฌ์์ ์๋ต ์์ฑ
๋น์ฆ๋์ค ์๋ฏธ๋ฅผ ๋ด์ ์์ธ ํด๋์ค๋ฅผ ์ง์ ๋ง๋ ๋ค.
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
# ์ฌ์ฉ์ ์ ์ ์์ธ ํด๋์ค
class InsufficientBalanceException(Exception):
"""์์ก ๋ถ์กฑ ์์ธ"""
def __init__(self, balance: int, required: int):
self.balance = balance # ํ์ฌ ์์ก
self.required = required # ํ์ํ ๊ธ์ก
app = FastAPI()
# ์์ธ ํธ๋ค๋ฌ ๋ฑ๋ก
# InsufficientBalanceException์ด ๋ฐ์ํ๋ฉด ์ด ํจ์๊ฐ ์ฒ๋ฆฌํจ
@app.exception_handler(InsufficientBalanceException)
async def insufficient_balance_handler(
request: Request,
exc: InsufficientBalanceException
):
return JSONResponse(
status_code=400,
content={
"error": "INSUFFICIENT_BALANCE",
"message": "์์ก์ด ๋ถ์กฑํฉ๋๋ค",
"current_balance": exc.balance,
"required_amount": exc.required,
"shortage": exc.required - exc.balance
}
)
@app.post("/purchase/{item_id}")
async def purchase_item(item_id: str, user_balance: int = 1000):
item_price = 1500
if user_balance < item_price:
# ์ปค์คํ
์์ธ ๋ฐ์
raise InsufficientBalanceException(
balance=user_balance,
required=item_price
)
return {"message": "๊ตฌ๋งค ์๋ฃ", "remaining": user_balance - item_price}
[Client] | | POST /purchase/item1 v [Endpoint] | | ์์ก ์ฒดํฌ (1000 < 1500) | | raise InsufficientBalanceException v [Exception Handler] | | JSONResponse ์์ฑ v [Client] 400 Bad Request + ์์ธ ์๋ฌ
| ์ด์ | ์ค๋ช |
|---|---|
| ์ค๋ณต ์ ๊ฑฐ | ๋ชจ๋ ์๋ํฌ์ธํธ์์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ์ ๊ฑฐ |
| ์ผ๊ด์ฑ | ์๋ฌ ์๋ต ํฌ๋งท ํต์ผ |
| ๊ด์ฌ์ฌ ๋ถ๋ฆฌ | ๋น์ฆ๋์ค ๋ก์ง โ ์๋ฌ ์ฒ๋ฆฌ ๋ถ๋ฆฌ |
FastAPI๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ๊ฐ์ง๋ฅผ ์๋ ์ฒ๋ฆฌํ๋ค.
| ์์ธ | ๋ฐ์ ์์ |
|---|---|
| HTTPException | ๋ช ์์ ์ผ๋ก raise |
| RequestValidationError | ์์ฒญ ๋ฐ์ดํฐ ๊ฒ์ฆ ์คํจ |
๊ธฐ๋ณธ ๊ฒ์ฆ ์๋ฌ๋ ์ฌ๋์ด ์ฝ๊ธฐ ํ๋ค๊ธฐ ๋๋ฌธ์, ์๋ต์ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ์ฌ์ฉํ๋ค.
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
# ๊ฒ์ฆ ์๋ฌ๋ฅผ ์ฌ์ฉ์ ์นํ์ ์ธ ํ์์ผ๋ก ๋ณํ
errors = []
for error in exc.errors():
errors.append({
"field": " โ ".join(str(loc) for loc in error["loc"]),
"message": error["msg"],
"type": error["type"]
})
return JSONResponse(
status_code=422,
content={
"error": "VALIDATION_ERROR",
"message": "์
๋ ฅ๊ฐ์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค",
"details": errors
}
)
@app.get("/items/{item_id}")
async def read_item(item_id: int): # int ํ์
๊ธฐ๋
return {"item_id": item_id}
/items/abc ์์ฒญ ์ ๊ธฐ๋ณธ ์๋ต vs ์ปค์คํ
์๋ต
๊ธฐ๋ณธ ์๋ต:
{
"detail": [{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}]
}
์ปค์คํ ์๋ต:
{
"error": "VALIDATION_ERROR",
"message": "์
๋ ฅ๊ฐ์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค",
"details": [{
"field": "path โ item_id",
"message": "value is not a valid integer",
"type": "type_error.integer"
}]
}
FastAPI์ HTTPException์ Starlette์ HTTPException์ ์์๋ฐ๋๋ค.
์์ธ ํธ๋ค๋ฌ๋ฅผ ๋ฑ๋กํ ๋๋ Starlette์ HTTPException์ผ๋ก ๋ฑ๋กํด์ผ FastAPI ๋ด๋ถ ์ฝ๋๋ Starlette ํ๋ฌ๊ทธ์ธ์์ ๋ฐ์ํ๋ ์์ธ๋ ์ก์ ์ ์๋ค.
| ๊ตฌ๋ถ | FastAPI | Starlette |
|---|---|---|
| ์ฉ๋ | ๋น์ฆ๋์ค ๋ก์ง | ์ ์ญ ํธ๋ค๋ฌ |
| detail ํ์ | JSON ๊ฐ๋ฅ | ๋ฌธ์์ด |