๐Ÿšจ FastAPI์—์„œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์™„์ „ ์ •๋ณต

๐Ÿ•’ ์•ฝ 1๋ถ„ ์ฝ๋Š” ๋ฐ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค

์ด์ „ ๊ธ€ ๋ณด๊ธฐ(FastAPI ์ƒํƒœ ๊ด€๋ฆฌ์™€ ์˜์กด์„ฑ ์ฃผ์ž…)

โ€“ HTTPException๋ถ€ํ„ฐ ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ตํ˜€๋ณด์ž

โ€œ์—๋Ÿฌ๋Š” ์ˆจ๊ธฐ์ง€ ๋ง๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ๋‹ค๋ค„์•ผ ํ•œ๋‹ค.โ€
FastAPI๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋˜ํ•œ ๋ชจ๋˜ํ•˜๊ณ  ๊น”๋”ํ•˜๊ฒŒ ์ œ๊ณตํ•œ๋‹ค. ๋‹จ์ˆœํžˆ ์„œ๋ฒ„์—์„œ ํ„ฐ์ง€๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ๋ฟ ์•„๋‹ˆ๋ผ, ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์œ ์šฉํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , API์˜ ์‹ ๋ขฐ์„ฑ์„ ๋†’์ด๋Š” ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค.


โœ… ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ธฐ๋ณธ ๊ตฌ์กฐ

FastAPI๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Python์˜ ์˜ˆ์™ธ ์‹œ์Šคํ…œ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ์—ฌ๊ธฐ์— ์›น ํ‘œ์ค€์ธ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ ‘๋ชฉ์‹œ์ผœ HTTPException์ด๋ผ๋Š” ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

1. ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id > 100:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id}
  • HTTPException์€ FastAPI๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์™ธ ํด๋ž˜์Šค์ด๋‹ค.
  • status_code๋Š” ๋ฐ˜ํ™˜ํ•  HTTP ์ƒํƒœ ์ฝ”๋“œ์ด๋‹ค.
  • detail์€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ œ๊ณตํ•  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์ด๋‹ค.

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด item_id๊ฐ€ 100์„ ์ดˆ๊ณผํ•  ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ๋Š” 404 ์—๋Ÿฌ์™€ ํ•จ๊ป˜ "Item not found" ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๊ฒŒ ๋œ๋‹ค.


๐Ÿšฆ ๋‹ค์–‘ํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

FastAPI์—์„œ๋Š” ํ‘œ์ค€ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋งˆ์Œ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ์ƒํƒœ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์ƒํƒœ ์ฝ”๋“œ์˜๋ฏธ
400Bad Request โ€“ ์ž˜๋ชป๋œ ์š”์ฒญ์ด๋‹ค
401Unauthorized โ€“ ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค
403Forbidden โ€“ ์ ‘๊ทผ์ด ๊ธˆ์ง€๋˜์—ˆ๋‹ค
404Not Found โ€“ ์ž์›์„ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค
500Internal Server Error โ€“ ์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜์ด๋‹ค

์‚ฌ์šฉ ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

if not authorized:
    raise HTTPException(status_code=401, detail="๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")

์ด๋ ‡๊ฒŒ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ๋„ API์˜ ์˜๋„๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿงฐ ์ถ”๊ฐ€ ํ•„๋“œ: headers, content

HTTPException์€ ๋‹จ์ˆœํžˆ ์ƒํƒœ ์ฝ”๋“œ์™€ ๋ฉ”์‹œ์ง€๋งŒ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด ์‘๋‹ต ํ—ค๋”๋‚˜ ์ƒ์„ธ ๋‚ด์šฉ์„ ๋” ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

raise HTTPException(
    status_code=403,
    detail="Access denied",
    headers={"X-Error": "Permission denied"}
)
  • ์œ„ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ํ—ค๋”์— X-Error๋ฅผ ์ถ”๊ฐ€ํ•ด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋กœ๊น…์ด๋‚˜ ํŠธ๋ž˜ํ‚น์— ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ๋ณด์•ˆ์ด๋‚˜ ์‚ฌ์šฉ์„ฑ ์ธก๋ฉด์—์„œ ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ›  ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ ์ž‘์„ฑํ•˜๊ธฐ

์–ด๋–ค ์˜ˆ์™ธ๋“ ์ง€ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ •์˜ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜ค๋ฅ˜๋‚˜ ์‚ฌ์šฉ์ž ์ •์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ๊ณตํ†ต๋œ ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด **์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ(Custom Exception Handler)**๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1. ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

class MyCustomException(Exception):
    def __init__(self, name: str):
        self.name = name
  • ์ผ๋ฐ˜์ ์ธ Python ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.
  • ํ•„์š”์— ๋”ฐ๋ผ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„˜๊ธฐ๊ฑฐ๋‚˜ ๋ฉ”์‹œ์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋กํ•˜๊ธฐ

FastAPI์—์„œ๋Š” @app.exception_handler() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

from fastapi.responses import JSONResponse
from fastapi.requests import Request

@app.exception_handler(MyCustomException)
async def my_exception_handler(request: Request, exc: MyCustomException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name}์ด๋ผ๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."}
    )
  • Request ๊ฐ์ฒด์™€ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ exc๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.
  • JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต์„ ์ง์ ‘ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋ฅผ ๊ณตํ†ต ํฌ๋งท์œผ๋กœ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ ์˜ˆ: ์ปค์Šคํ…€ ์˜ˆ์™ธ๋ฅผ ์‹ค์ œ API์— ์ ์šฉํ•ด๋ณด์ž

@app.get("/error-test/{name}")
def error_test(name: str):
    if name == "bad":
        raise MyCustomException(name)
    return {"name": name}
  • /error-test/bad๋กœ ์š”์ฒญํ•˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ MyCustomException์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • ์ด ์˜ˆ์™ธ๋Š” ์•ž์„œ ๋“ฑ๋กํ•œ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์ƒํƒœ ์ฝ”๋“œ 418๊ณผ ํ•จ๊ป˜ "Oops! bad์ด๋ผ๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๊ฒŒ ๋œ๋‹ค.

๐Ÿคน ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํŒ๊ณผ ์ฃผ์˜์‚ฌํ•ญ

  1. ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ํ•ธ๋“ค๋งํ•˜์ž.
    • FastAPI๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์˜ˆ์™ธ๋ฅผ JSON ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ๋ช…ํ™•ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ์ข‹๋‹ค.
  2. ๊ณตํ†ต ์˜ค๋ฅ˜ ํฌ๋งท์„ ์‚ฌ์šฉํ•˜์ž.
    • API์—์„œ ์ผ๊ด€๋œ ํ˜•ํƒœ์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ํŒŒ์‹ฑ๊ณผ ์ฒ˜๋ฆฌ๋„ ์‰ฌ์›Œ์ง„๋‹ค.
  3. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋‹ด์ง€ ๋ง์ž.
    • DB ์ฟผ๋ฆฌ ์˜ค๋ฅ˜๋‚˜ ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•˜๋ฉด ๋ณด์•ˆ์ƒ ์œ„ํ—˜ํ•˜๋‹ค.
  4. ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๊ฒฝ๋กœ์—์„œ ์˜ˆ์™ธ๊ฐ€ ์ž˜ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธํ•˜์ž.
    • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ์ž˜๋ชป๋˜๋ฉด 500 ์˜ค๋ฅ˜๋กœ ๋„˜์–ด๊ฐ€ ์‚ฌ์šฉ์ž๋Š” ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์—†๋‹ค.

๐Ÿงพ ์ •๋ฆฌํ•ด๋ณด์ž

FastAPI์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์€ ๋‹จ์ˆœํ•œ ์˜ˆ์™ธ ๋ฐœ์ƒ์„ ๋„˜์–ด, ์›น ์„œ๋น„์Šค ์ „๋ฐ˜์˜ ์‹ ๋ขฐ์„ฑ๊ณผ ํ’ˆ์งˆ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋„๊ตฌ์ด๋‹ค. HTTPException์€ ์ฆ‰๊ฐ์ ์ธ ์˜ค๋ฅ˜ ์‘๋‹ต์— ์ข‹๊ณ , ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ณต์žกํ•œ ๋กœ์ง์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค€๋‹ค.

๊ธฐ๋Šฅ์‚ฌ์šฉ๋ฒ•๋ชฉ์ 
HTTPExceptionraise HTTPException(status_code, detail)๋น ๋ฅธ HTTP ์˜ค๋ฅ˜ ๋ฐ˜ํ™˜
์ปค์Šคํ…€ ์˜ˆ์™ธ ํด๋ž˜์Šคclass MyException(Exception)์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง ํ‘œํ˜„
์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ@app.exception_handler(MyException)์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

FastAPI์—์„œ๋Š” ์˜ˆ์™ธ๊ฐ€ ๊ณง ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด๋‹ค. ์–ด๋–ป๊ฒŒ ์—๋Ÿฌ๋ฅผ ์ „๋‹ฌํ• ์ง€์— ๋”ฐ๋ผ ์„œ๋น„์Šค์˜ ์ „๋ฌธ์„ฑ์ด ๋“œ๋Ÿฌ๋‚œ๋‹ค.
๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์ „์—, ์ž์‹ ์ด ๋งŒ๋“  ๋ชจ๋“  API ๋ผ์šฐํŠธ์— ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ์žˆ๋Š”์ง€ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ ๊ฒ€ํ•ด๋ณด์ž.

FastAPI ๊ณต์‹ ๋ฌธ์„œ : https://fastapi.tiangolo.com/ko/

fastapi-logo,fastapi-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ-์„ค์ •, fastapi-์•ฑ-๋งŒ๋“ค๊ธฐ, fastapi-๋ผ์šฐํŒ…, fastapi-request-response, fastapi-์˜ˆ์™ธ-์ฒ˜๋ฆฌ