๐Ÿผ ๋ฐฑ์•ค๋“œ/Fast API

Fast API - ๊ธฐ์ดˆ

๊ณ„๋ž€์†Œ๋…„ 2025. 6. 18. 17:22

๋งŽ์€ ํ”„๋ ˆ์ž„ ์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด์™”์ง€๋งŒ, ๊นŠ์ด๊ฐ€ ๋ถ€์กฑํ•˜๋‹ค๊ณ  ํ•ญ์ƒ ์ƒ๊ฐํ–ˆ๋‹ค. ์ด์ œ๋Š” ๊ทธ๊ฒƒ๋“ค์— ๋Œ€ํ•œ ๊นŠ์ด๋ฅผ ๋” ์ฑ„์›Œ๋ณด๊ณ ์ž ํ•œ๋‹ค.

๋ฌด์—‡๋ณด๋‹ค ์–ด๋–ค ์งˆ๋ฌธ์— ๊ด€ํ•ด์„œ๋“  ์ด์œ ๋ฅผ ๋ฌผ์„ ๋•Œ ๋ฐ”๋กœ ๋‹ต ํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค๋ ฅ์„ ๊ฐ‡๊ธฐ๋ฅผ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ฐ€์žฅ ๋จผ์ € ๊นŠ๊ฒŒ ํŒŒ๋ณผ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” Fast API์ด๋‹ค. 

์ด์œ ๋Š” Python์„ ์ฃผ๋ ฅ์œผ๋กœ ํ•˜๊ณ , ์ œ์ผ ๋งŽ์ด ๋งก์•„ ์˜จ ์—ญํ• ์ด ๋ฐฑ์•ค๋“œ์ด๊ธฐ์—, ๋น ๋ฅด๊ฒŒ ์ •๋ฆฌํ•˜๊ณ  ๊นŠ์ด๋ฅผ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

Fast API๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํ™•์žฅํ•˜๋ฉฐ ์ •๋ฆฌํ•˜๊ฒ ๋‹ค.

 

๊ธฐ๋ณธ ๊ฐœ๋…

 

Fast API๋Š” Python์˜ async / await ๋ฌธ๋ฒ•์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€์›

-> ์ด ๋•๋ถ„์— ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด, ๋†’์€ ํŠธ๋ž˜ํ”ฝ ์ƒํ™ฉ์—์„œ๋„ ๋น ๋ฅธ ์‘๋‹ต์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Flask์ฒ˜๋Ÿผ ๋™๊ธฐ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ์šฐ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ž‘์—…๋งŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ์— ๋ถ€์กฑ

"ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ž‘์—…"๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…(์™ธ๋ถ€ API ํ˜ธ์ถœ, DB ๋Œ€๊ธฐ)์ด ์žˆ์œผ๋ฉด ๊ทธ๋™์•ˆ ๋‹ค๋ฅธ ์š”์ฒญ์€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋œ๋‹ค.


๋ฐ˜๋ฉด FastAPI๋Š” ๋น„๋™๊ธฐ(async) ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์—ฌ, ์™ธ๋ถ€ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ์—๋„ ๋‹ค๋ฅธ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด → ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋น ๋ฅด๊ณ , ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ํšจ์œจ์ด ๋†’๋‹ค.

ํ”„๋ ˆ์ž„์›Œํฌ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๋ฐฉ์‹ ์˜ˆ์‹œ ์ฝ”๋“œ
FastAPI app = FastAPI() from fastapi import FastAPI
app = FastAPI()
Flask app = Flask(name) from flask import Flask
app = Flask(name)
Django ์ง์ ‘ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ X
ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ ์„ค์ • ํŒŒ์ผ๋กœ ๊ด€๋ฆฌ
django-admin startproject ...
manage.py runserver

 

์Šค์›จ๊ฑฐ ๋ณด์œ 

from fastapi import FastAPI

app = FastAPI() # Fastapi ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ


@app.get("/")
async def root():
    return {"message": "Hello World"}


@app.get("/hello/{name}")
async def say_hello(name: str):
    return {"message": f"Hello {name}"}


์„œ๋ฒ„ ์‹คํ–‰

๋กœ์ปฌ ์‹คํ–‰: ASGI ๊ตฌํ˜„์ฒด ์ค‘ ๋น„๋™๊ธฐ ์›น ์„œ๋ฒ„์ธ Uvicorn

๋”๋ณด๊ธฐ

1. ASGI๋ž€?

  • ASGI(Asynchronous Server Gateway Interface)๋Š” ํŒŒ์ด์ฌ ์›น ์„œ๋ฒ„์™€ ์›น ํ”„๋ ˆ์ž„์›Œํฌ(์˜ˆ: FastAPI, Django) ์‚ฌ์ด์˜ "๋น„๋™๊ธฐ ํ†ต์‹ "์„ ์œ„ํ•œ ํ‘œ์ค€ ์ธํ„ฐํŽ˜์ด์Šค
  • ๊ธฐ์กด์˜ WSGI(Web Server Gateway Interface)๋Š” ๋™๊ธฐ ๋ฐฉ์‹๋งŒ ์ง€์›ํ•ด์„œ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ, ์›น์†Œ์ผ“, ๋น„๋™๊ธฐ ์š”์ฒญ ๋“ฑ ํ˜„๋Œ€์  ์›น ๊ธฐ๋Šฅ์— ํ•œ๊ณ„

2. Uvicorn์ด๋ž€?

  • Uvicorn์€ ASGI ํ‘œ์ค€์„ ๊ตฌํ˜„ํ•œ ๋Œ€ํ‘œ์ ์ธ ํŒŒ์ด์ฌ ๋น„๋™๊ธฐ ์›น ์„œ๋ฒ„
  • FastAPI, Starlette, ์ตœ์‹  Django(ASGI ๋ชจ๋“œ) ๋“ฑ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋ฉฐ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํ˜„๋Œ€์  ์›น ์„œ๋น„์Šค์— ์ ํ•ฉ

Flask์—์„œ ์‚ฌ์šฉํ•œ Gunicorn์€ WSGI ์„œ๋ฒ„

uvicorn main:app --reload
  • main = main.py
  • app: app=FastAPI(), FastAPI ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฐ์ฒด์˜ ๋ณ€์ˆ˜ ์ด๋ฆ„ 
  • --reload: ์ฝ”๋“œ ์ˆ˜์ •์‹œ ์„œ๋ฒ„ ์ž๋™ ์žฌ์‹œ์ž‘

http://127.0.0.1:8000/docs ๋ฅผ ํ†ตํ•ด ์ž๋™ ๋ฌธ์„œํ™”

ํ•ญ๋ชฉ Swagger ReDoc
๊ธฐ๋Šฅ ๋ชฉ์  ๊ฐœ๋ฐœ ๋ฐ ํ…Œ์ŠคํŠธ ์ค‘์‹ฌ ๋ฌธ์„œํ™” ๋ฐ ๋ฐฐํฌ ์ค‘์‹ฌ
๋””์ž์ธ ์ง๊ด€์ ์ด๋ฉฐ ๊ธฐ๋Šฅ ์œ„์ฃผ UI ์ •๋ˆ๋œ ๋ฌธ์„œ ์Šคํƒ€์ผ UI
Try it out ๊ธฐ๋Šฅ ์žˆ์Œ → API ์ง์ ‘ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ ์—†์Œ → ์ฝ๊ธฐ ์ „์šฉ ๋ฌธ์„œ
์‚ฌ์šฉ์ž์ธต ์ฃผ๋กœ ๊ฐœ๋ฐœ์ž ๊ฐœ๋ฐœ์ž + ๋น„๊ฐœ๋ฐœ์ž(๊ธฐํš์ž ๋“ฑ)
FastAPI ๊ธฐ๋ณธ ๊ฒฝ๋กœ /docs /redoc
๊ธฐ๋ฐ˜ ์ŠคํŽ™ OpenAPI (Swagger) OpenAPI (ReDoc ๊ธฐ๋ฐ˜)
  • /docs (Swagger UI): API๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•ด๋ณด๋ฉฐ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ → ๊ฐœ๋ฐœ ์ค‘์— ์œ ์šฉํ•จ
  • /redoc: ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌ๋œ ๋ฌธ์„œ ์ œ๊ณต → ๋ฐฐํฌ ํ›„ ์™ธ๋ถ€ ๊ณต๊ฐœ์šฉ์— ์ ํ•ฉ


Swagger

Paths ๊ฐ API ์—”๋“œํฌ์ธํŠธ (GET, POST ๋“ฑ)
Parameters Path, Query, Header, Cookie ๋“ฑ์— ๋“ค์–ด๊ฐ€๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ
Request body ์š”์ฒญ ์‹œ ์ „์†กํ•  JSON ์Šคํ‚ค๋งˆ (pydantic ๋ชจ๋ธ ๊ธฐ๋ฐ˜)
Responses ์‘๋‹ต ์ฝ”๋“œ๋ณ„ ๋ฐ˜ํ™˜ ๊ฐ’ ์˜ˆ์‹œ ๋ฐ ์Šคํ‚ค๋งˆ
Schemas ์ „์ฒด API์—์„œ ์“ฐ์ด๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๊ตฌ์กฐ ์ •์˜
Try it out ์ง์ ‘ ํŒŒ๋ผ๋ฏธํ„ฐ ์ž…๋ ฅํ•˜๊ณ  ์š”์ฒญ ๋ณด๋‚ด๊ธฐ ๊ฐ€๋Šฅ

 

๋ผ์šฐํŒ…

 

์ •์˜: ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” HTTP ์š”์ฒญ์„ ์ ์ ˆํ•œ ํ•จ์ˆ˜(handler)๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ

์ฆ‰, GET /users/123 ์š”์ฒญ์ด ์˜ค๋ฉด ์ด๋ฅผ ์ฒ˜๋ฆฌํ•  FastAPI ํ•จ์ˆ˜์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ณผ์ •

 

๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜

  • URL ๊ฒฝ๋กœ์˜ ์ผ๋ถ€๋ฅผ ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ์‚ฌ์šฉ
  • ๋™์ ์ธ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉ
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}
  • ์š”์ฒญ: GET /users/7
  • ๊ฒฐ๊ณผ: { "user_id": 7 }

 

์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜

  • URL์—์„œ ? ์ดํ›„์— ๋‚˜์˜ค๋Š” key=value ํ˜•ํƒœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜
@app.get("/search")
def search_items(q: str = None):
    return {"query": q}
  • ์š”์ฒญ: GET /search?q=apple 
  • ๊ฒฐ๊ณผ: { "query": "apple" }

 

ํƒ€์ž… ํžŒํŠธ

 

๋ณ€์ˆ˜๋‚˜ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋‚˜ ๋ฐ˜ํ™˜๊ฐ’์— ํƒ€์ž…์„ ๋ช…์‹œํ•ด์ฃผ๋Š” ๋ฌธ๋ฒ•์œผ๋กœ, ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ + FastAPI์—์„œ๋Š” ์ž๋™ ๋ฌธ์„œํ™” ๋ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‚ฌ์šฉ

from typing import Dict
from fastapi import FastAPI

app = FastAPI()

@app.post("/create-item/")
def create_item(item: Dict[str, int]):
    return item



from typing import List

@app.post("/items/")
def create_items(items: List[str]):
    return {"received": items}

 

๋”๋ณด๊ธฐ

ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ํƒ€์ž…

 

1. ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•

int 1, -5, 100 ์ •์ˆ˜ํ˜•
float 3.14, -0.1 ์‹ค์ˆ˜ํ˜• (๋ถ€๋™์†Œ์ˆ˜์ )
complex 1+2j ๋ณต์†Œ์ˆ˜ํ˜•
bool True, False ๋ถˆ๋ฆฌ์–ธํ˜•
str "hello", 'a' ๋ฌธ์ž์—ดํ˜•

 

2. ์‹œํ€€์Šค ํƒ€์ž…

list [1, 2, 3] ๊ฐ€๋ณ€ํ˜• ์ˆœ์ฐจ ์ž๋ฃŒํ˜•
tuple (1, 2) ๋ถˆ๋ณ€ํ˜• ์ˆœ์ฐจ ์ž๋ฃŒํ˜•
range range(5) ์ •์ˆ˜ ์‹œํ€€์Šค (๋ฐ˜๋ณต์— ์‚ฌ์šฉ)

 

3. ๋งคํ•‘ ํƒ€์ž…

dict {"a": 1} ํ‚ค-๊ฐ’ ์Œ์˜ ์ง‘ํ•ฉ

 

4. ์ง‘ํ•ฉ ํƒ€์ž… 

set {1, 2, 3} ์ค‘๋ณต ์—†๋Š” ์ง‘ํ•ฉ
frozenset frozenset([1,2]) ๋ถˆ๋ณ€ ์ง‘ํ•ฉ

  

5. ๋ฐ”์ด๋„ˆ๋ฆฌ ํƒ€์ž…

bytes b"abc" ๋ฐ”์ดํŠธ ๋ฐ์ดํ„ฐ
bytearray bytearray(5) ๊ฐ€๋ณ€ ๋ฐ”์ดํŠธ
memoryview memoryview(b"abc") ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„ํผ ๋ทฐ

 

6.  None ํƒ€์ž…

NoneType None ๊ฐ’์ด ์—†์Œ์„ ์˜๋ฏธ

 

7. ํƒ€์ž… ํžŒํŠธ์šฉ ํƒ€์ž…(typing ๋ชจ๋“ˆ)

Any ์–ด๋–ค ํƒ€์ž…์ด๋“  ๊ฐ€๋Šฅ
Union[int, str] int ๋˜๋Š” ๋ฌธ์ž์—ด (์—ฌ๋Ÿฌ ํƒ€์ž… ์ค‘ ํ•˜๋‚˜)
Optional[int] int ๋˜๋Š” None (๊ฐ’์ด ์žˆ๊ฑฐ๋‚˜ None)
List[str] ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ
Dict[str, int] ๋ฌธ์ž์—ด-์ •์ˆ˜ ๋”•์…”๋„ˆ๋ฆฌ
Tuple[int, str] ๊ณ ์ •๋œ ๊ตฌ์กฐ์˜ ํŠœํ”Œ
Callable[[int,int],int] ๋‘ ์ •์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›๊ณ  ์ •์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ (ํ•จ์ˆ˜ ํƒ€์ž…)
Literal["A", "B"] ํŠน์ • ๊ฐ’๋“ค๋งŒ ํ—ˆ์šฉ
TypedDict ๊ตฌ์กฐ ์ •์˜๋œ ๋”•์…”๋„ˆ๋ฆฌ
Annotated ํƒ€์ž… + ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ

 

8. ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…

class User:
    name: str

 

HTTP Method(NO Pydantic) 

 

from fastapi import FastAPI, HTTPException
from typing import Dict, List

app = FastAPI()

# ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ ๊ฐ€์งœ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ(์ง€๊ธˆ DB ์—†์ด ํ•˜๋‹ˆ๊นŒ)
items_db: Dict[int, Dict[str, str or int or float]] = {}


# 1. POST - ์•„์ดํ…œ ์ถ”๊ฐ€
@app.post("/items/")
def create_item(item: Dict[str, str or int or float]):
    item_id = item.get("id")
    if item_id in items_db:
        raise HTTPException(status_code=400, detail="Item already exists")
    items_db[item_id] = item
    return {"message": "Item created", "item": item}


# 2. GET - ์•„์ดํ…œ ์ „์ฒด ์กฐํšŒ
@app.get("/items/")
def get_all_items():
    return list(items_db.values())


# 3. GET - ํŠน์ • ์•„์ดํ…œ ์กฐํšŒ
@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]


# 4. PUT - ์•„์ดํ…œ ์ˆ˜์ •
@app.put("/items/{item_id}")
def update_item(item_id: int, updated_item: Dict[str, str or int or float]):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    items_db[item_id] = updated_item
    return {"message": "Item updated", "item": updated_item}


# 5. DELETE - ์•„์ดํ…œ ์‚ญ์ œ
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    del items_db[item_id]
    return {"message": f"Item {item_id} deleted"}

 

์ฃผ์˜์ 

  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—†์Œ → ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž˜๋ชป๋œ ํƒ€์ž…์„ ๋ณด๋‚ด๋„ ์„œ๋ฒ„๊ฐ€ ๋ฐ›์•„๋ฒ„๋ฆด ์ˆ˜ ์žˆ์Œ
  • FastAPI์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ(๋ฌธ์„œ ์ž๋™ํ™”, ํƒ€์ž… ์ฒดํฌ ๋“ฑ)์„ ์ถฉ๋ถ„ํžˆ ํ™œ์šฉํ•˜๋ ค๋ฉด Pydantic ์‚ฌ์šฉ์„ ๊ถŒ์žฅ

 

Pydantic

 

Python์˜ ํƒ€์ž… ํžŒํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž…๋ ฅ ๊ฐ’์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ๊ณผ ์ž๋™ ๋ณ€ํ™˜, ์ง๋ ฌํ™”, ์—ญ์ง๋ ฌํ™”๋ฅผ ๋„์™€์ฃผ๋Š” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

FastAPI๋Š” Pydantic์„ ์‚ฌ์šฉํ•ด์„œ ๋‹ค์Œ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ

๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ(Validation) ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…, ํ˜•์‹, ํ•„์ˆ˜ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์‚ฌ price: float์— ๋ฌธ์ž์—ด "abc"์ด ๋“ค์–ด์˜ค๋ฉด ์˜ค๋ฅ˜ ๋ฐ˜ํ™˜
๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” (Serialization) ๋‚ด๋ถ€ Python ๊ฐ์ฒด๋ฅผ JSON ๋“ฑ์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜ Python์˜ Item ๊ฐ์ฒด → JSON ์‘๋‹ต์œผ๋กœ ๋ณ€ํ™˜
๋ฐ์ดํ„ฐ ์—ญ์ง๋ ฌํ™” (Parsing) JSON → Python ๊ฐ์ฒด๋กœ ์ž๋™ ๋ณ€ํ™˜ JSON์œผ๋กœ ์˜จ {"id": 1}Item(id=1) ๊ฐ์ฒด ์ƒ์„ฑ
๋ฌธ์„œํ™” ์ž๋™ํ™” (Schema ์ƒ์„ฑ) Swagger UI(/docs)์—์„œ ๋ชจ๋ธ ๊ตฌ์กฐ ์ž๋™ ํ‘œ์‹œ ํ•„๋“œ ํƒ€์ž…, ํ•„์ˆ˜ ์—ฌ๋ถ€ ๋“ฑ ๋ฌธ์„œํ™”
๊ธฐ๋ณธ๊ฐ’ ๋ฐ ํ•„์ˆ˜ ํ•„๋“œ ๊ด€๋ฆฌ ์„ ํƒ/ํ•„์ˆ˜ ํ•„๋“œ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ name: Optional[str] = None
ํƒ€์ž… ๋ณ€ํ™˜ ์ง€์› "10" ๊ฐ™์€ ๋ฌธ์ž์—ด ์ˆซ์ž → int๋กœ ์ž๋™ ๋ณ€ํ™˜ age: int์— "10" ๋„ฃ์–ด๋„ int ์ฒ˜๋ฆฌ
  • ์ง๋ ฌํ™”
    • ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ฑฐ๋‚˜ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํŠน์ •ํ•œ ํฌ๋งท(์˜ˆ: JSON, XML, CSV ๋“ฑ)์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •.
    • ์ฆ‰, ํŒŒ์ด์ฌ ๊ฐ์ฒด์™€ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋„คํŠธ์›Œํฌ ๋“ฑ์œผ๋กœ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ์ผ๋ จ์˜ ๋ฐ”์ดํŠธ๋‚˜ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๋ฐ˜๋Œ€๋กœ, ์ง๋ ฌํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ํŒŒ์ด์ฌ ๊ฐ์ฒด๋กœ ๋ณต์›ํ•˜๋Š” ๊ณผ์ •์„ ์—ญ์ง๋ ฌํ™”๋ผ๊ณ  ํ•œ๋‹ค.
ํ•ญ๋ชฉ Pydantic ์‚ฌ์šฉ (BaseModel) Pydantic ๋ฏธ์‚ฌ์šฉ (dict, ํƒ€์ž…ํžŒํŠธ๋งŒ)
์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ํƒ€์ž…/๊ฐ’ ์ž๋™ ๊ฒ€์ฆ, ์ƒ์„ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜ ์ˆ˜๋™ ๊ฒ€์ฆ ํ•„์š”, ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ง์ ‘ ๊ตฌํ˜„
์ž๋™ ๋ณ€ํ™˜/ํŒŒ์‹ฑ JSON → ๊ฐ์ฒด ์ž๋™ ๋ณ€ํ™˜ ์ง์ ‘ ํŒŒ์‹ฑ/๋ณ€ํ™˜ ์ฝ”๋“œ ์ž‘์„ฑ ํ•„์š”
์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” dict(), json() ๋“ฑ์œผ๋กœ ๊ฐ์ฒด ๋ณ€ํ™˜ ์ž๋™ ์ˆ˜๋™ ๋ณ€ํ™˜ ํ•„์š”, ์ผ๊ด€์„ฑ ๋–จ์–ด์ง
Swagger ๋ฌธ์„œ ์ž๋™ํ™” ์ž…๋ ฅ/์ถœ๋ ฅ ๊ตฌ์กฐ, ์ œ์•ฝ์กฐ๊ฑด, ์˜ˆ์‹œ ์ž๋™ ๋ฐ˜์˜ ๋‹จ์ˆœ ํƒ€์ž…๋งŒ ํ‘œ์‹œ, ๊ตฌ์กฐํ™” ์–ด๋ ค์›€
๋ณต์žกํ•œ ๊ตฌ์กฐ ์ง€์› ์ค‘์ฒฉ, ๋ฆฌ์ŠคํŠธ, ์˜ต์…˜ ๋“ฑ ๋ณต์žกํ•œ ๊ตฌ์กฐ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌ ๋‹จ์ผ ๊ฐ’/๊ฐ„๋‹จ ๊ตฌ์กฐ๋งŒ ์ฒ˜๋ฆฌ
์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜/ํ™•์žฅ์„ฑ ๋ชจ๋ธ ์ค‘์‹ฌ ๊ด€๋ฆฌ, ์žฌ์‚ฌ์šฉ/ํ™•์žฅ ์šฉ์ด ํ•„๋“œ ๋งŽ์•„์ง€๋ฉด ์ฝ”๋“œ ๋ณต์žก, ๊ด€๋ฆฌ ์–ด๋ ค์›€
์—๋Ÿฌ ์‘๋‹ต 422 ๋“ฑ ํ‘œ์ค€ํ™”๋œ ์—๋Ÿฌ ๋ฐ ์ƒ์„ธ ๋ฉ”์‹œ์ง€ ์ž๋™ ๋ฐ˜ํ™˜ 500 ๋“ฑ ์„œ๋ฒ„ ์—๋Ÿฌ, ์ƒ์„ธ ํ”ผ๋“œ๋ฐฑ ์–ด๋ ค์›€
์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ validator, Field ๋“ฑ์œผ๋กœ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ด ์ง์ ‘ if๋ฌธ ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌ
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# 1. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜
class Item(BaseModel):
    id: int
    name: str
    price: float

# 2. POST ์š”์ฒญ ์ฒ˜๋ฆฌ
@app.post("/items/")
def create_item(item: Item):
    return {"message": "Item created", "item": item}
curl -X POST "http://localhost:8000/items/" \
     -H "Content-Type: application/json" \
     -d '{"id": 1, "name": "apple", "price": 3.5}'
{"message":"Item created","item":{"id":1,"name":"apple","price":3.5}}

curl Tset

  1. -H "accept: application/json"
    • ์„ค๋ช…: ์„œ๋ฒ„์—๊ฒŒ "๋‚˜๋Š” JSON ํ˜•์‹์˜ ์‘๋‹ต์„ ๋ฐ›๊ณ  ์‹ถ๋‹ค."๋ผ๊ณ  ์š”์ฒญํ•˜๋Š” HTTP ํ—ค๋”
    • ์—ญํ• : ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐ์ดํ„ฐ ํ˜•์‹(XML, HTML ๋“ฑ)์œผ๋กœ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์„ ๋•Œ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›ํ•˜๋Š” ํ˜•์‹์„ ์ง€์ •ํ•˜๋Š” ์—ญํ• 
  2.  -H "Content-Type: application/json"
    • ์„ค๋ช…: ์„œ๋ฒ„์—๊ฒŒ "๋‚ด๊ฐ€ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ๋Š” JSON ํ˜•์‹์ด๋‹ค."๋ผ๊ณ  ์•Œ๋ฆฌ๋Š” HTTP ํ—ค๋”
    • ์—ญํ• : ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ ๋ณธ๋ฌธ์— ๋‹ด๊ธด ๋ฐ์ดํ„ฐ์˜ ํ˜•์‹์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
  3.  -d
    • ์„ค๋ช…: curl ๋ช…๋ น์–ด์—์„œ -d๋Š” "data"์˜ ์•ฝ์ž๋กœ, ์„œ๋ฒ„๋กœ ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ
    • ์—ญํ• : POST, PUT ๊ฐ™์€ ์š”์ฒญ์—์„œ ๋ณธ๋ฌธ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์•„ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉ

curl์€ “Client URL”์˜ ์ค„์ž„๋ง๋กœ, ํ„ฐ๋ฏธ๋„/๋ช…๋ น์–ด ๊ธฐ๋ฐ˜ HTTP ์š”์ฒญ ๋„๊ตฌ

  • REST API๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉ
  • FastAPI ์„œ๋ฒ„์— ์š”์ฒญ์„ ์ง์ ‘ ๋‚ ๋ฆด ์ˆ˜ ์žˆ์Œ
  • GUI ์—†์ด ๋ช…๋ น์ค„์—์„œ ๋น ๋ฅด๊ฒŒ ํ™•์ธ ๊ฐ€๋Šฅ
@app.post("/items")
def create_item(item: dict):
    return item
curl -X GET "http://localhost:8000/users/123"

curl -X GET "http://localhost:8000/search"

curl -X POST "http://localhost:8000/items" \
     -H "Content-Type: application/json" \
     -d '{"name": "banana", "price": 1000}'
{"name":"banana","price":1000}

 

Item์ด Pydantic ๋ชจ๋ธ id, name, price๊ฐ€ ํ•„๋“œ

 

ํ•„๋“œ ์ œ์•ฝ์กฐ๊ฑด

์ œ์•ฝ์กฐ๊ฑด ์„ค๋ช… ํƒ€์ž…
min_length, max_length ๋ฌธ์ž์—ด ๊ธธ์ด ์ œํ•œ str
gt, ge, lt, le ์ˆซ์ž ํฌ๊ธฐ ์กฐ๊ฑด(> >= < <=) int, float
regex ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ๋ฌธ์ž์—ด ํ˜•์‹ ์ œํ•œ str
min_items, max_items ๋ฆฌ์ŠคํŠธ์˜ ์ตœ์†Œ/์ตœ๋Œ€ ๊ธธ์ด ์ œํ•œ list
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Product(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    price: float = Field(..., gt=0)  # 0๋ณด๋‹ค ์ปค์•ผ ํ•จ
    description: str = Field(default="", max_length=200)
    tags: list[str] = Field(default=[], min_items=1)

@app.post("/products/")
def create_product(product: Product):
    return {"message": "์ƒํ’ˆ ๋“ฑ๋ก ์™„๋ฃŒ", "product": product}
  • Field(..., ...) : ...(Ellipsis)๋Š” Python์—์„œ "ํ•„์ˆ˜๊ฐ’"์„ ์˜๋ฏธ
  • Field(default=...) : ๊ธฐ๋ณธ๊ฐ’ ์ง€์ •
  • Field(default_factory=list) : ๋ฆฌ์ŠคํŠธ์˜ ๊ธฐ๋ณธ๊ฐ’์„ ๋นˆ ๋ฆฌ์ŠคํŠธ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ง€์ •

์—๋Ÿฌ: (ํ•„์ˆ˜๊ฐ’ ๋ˆ„๋ฝ, price๊ฐ€ 0 ์ดํ•˜, name์ด ์งง์Œ ๋“ฑ) -> 422 Unprocessable Entity ์—๋Ÿฌ ๋ฐœ์ƒ

 

๋ณต์žกํ•œ ๋ฐ์ดํ„ฐํ˜•ํƒœ

 

1. ์ค‘์ฒฉ ๋ชจ๋ธ (Nested Models) - ๋ณต์žกํ•œ JSON ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ๋ชจ๋ธ ์•ˆ์— ๋‹ค๋ฅธ ๋ชจ๋ธ์„ ๋„ฃ๋Š” ๋ฐฉ์‹

๊ฐœ๋…

  • ํด๋ž˜์Šค ์•ˆ์— ๋‹ค๋ฅธ Pydantic ๋ชจ๋ธ ํด๋ž˜์Šค ์‚ฌ์šฉ
  • ์žฌ์‚ฌ์šฉ์„ฑ, ๊ฐ€๋…์„ฑ, ์œ ์ง€๋ณด์ˆ˜
from pydantic import BaseModel
from typing import List

class Address(BaseModel):
    city: str
    zipcode: str

class User(BaseModel):
    name: str
    age: int
    address: Address  # ์ค‘์ฒฉ ์‚ฌ์šฉ

์š”์ฒญ ์‹œ

{
  "name": "Alice",
  "age": 30,
  "address": {
    "city": "Seoul",
    "zipcode": "12345"
  }
}

 

 2. List, Union

  • List: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๋‹ด๋Š” ๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ
  • Union: ์—ฌ๋Ÿฌ ํƒ€์ž… ์ค‘ ํ•˜๋‚˜๋ฅผ ํ—ˆ์šฉ
# List → "hobbies": ["reading", "sports"] ํ˜•ํƒœ ๊ฐ€๋Šฅ

class User(BaseModel):
    name: str
    hobbies: List[str]

# Union → "value": 123 ๋„ ๋˜๊ณ  "value": "abc" ๋„ ๊ฐ€๋Šฅ

from typing import Union

class Item(BaseModel):
    value: Union[int, str]

 

 

3. ์ œ๋„ค๋ฆญ(Generic)

"์•„๋ฌด ํƒ€์ž…์ด๋‚˜ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ์Œ"์„ ์˜๋ฏธ

์ฃผ๋กœ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์œ ์—ฐํ•œ ๊ตฌ์กฐ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ

๊ด€๋ จ ํƒ€์ž…: TypeVar, Generic

 

  • ํƒ€์ž… ๋ณ€์ˆ˜(TypeVar)
    • TypeVar๋Š” ํƒ€์ž… ํžŒํŠธ์—์„œ "์•„์ง ๊ตฌ์ฒด์ ์œผ๋กœ ์ •ํ•ด์ง€์ง€ ์•Š์€ ํƒ€์ž…"์„ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉ
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ๋ฐ›์•„์•ผ ํ•  ๋•Œ, ํƒ€์ž…์˜ ์ž๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์˜ˆ์•ฝํ•ด๋‘๋Š” ์—ญํ• 
    • ์™ผ์ชฝ T: ์‹ค์ œ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•  ํƒ€์ž… ๋ณ€์ˆ˜ ์ด๋ฆ„(ํƒ€์ž… ํžŒํŠธ๋กœ ์‚ฌ์šฉ)
    • ์˜ค๋ฅธ์ชฝ "T": ๋‚ด๋ถ€์ ์œผ๋กœ ์‹๋ณ„์ž ์—ญํ• ์„ ํ•˜๋Š” ๋ฌธ์ž์—ด(์•„๋ฌด ๋ฌธ์ž์—ด์ด๋‚˜ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ณดํ†ต ๋ณ€์ˆ˜๋ช…๊ณผ ๋งž์ถค)
T = TypeVar("T")

 

  • ์ œ๋„ˆ๋ฆญ(Generic) ํƒ€์ž…
    • ์ œ๋„ˆ๋ฆญ์€ ํด๋ž˜์Šค๋‚˜ ํ•จ์ˆ˜๋ฅผ "ํƒ€์ž…์— ๋”ฐ๋ผ ์œ ์—ฐํ•˜๊ฒŒ" ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฆฌ์ŠคํŠธ(List)๋Š” ์–ด๋–ค ํƒ€์ž…์˜ ๊ฐ’์ด๋“  ๋‹ด์„ ์ˆ˜ ์žˆ๋‹ค. → List[int], List[str] 

 

from typing import TypeVar, Generic
from pydantic.generics import GenericModel

T = TypeVar("T")  # ์–ด๋–ค ํƒ€์ž…์ด๋“  ๊ฐ€๋Šฅ

# Response๋Š” ์–ด๋–ค ํƒ€์ž…์˜ data๋“  ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ์ œ๋„ˆ๋ฆญ ๋ชจ๋ธ 
# ์‹ค์ œ ์‚ฌ์šฉํ•  ๋•Œ๋Š” Response[int], Response[str], Response[User] ๋“ฑ์œผ๋กœ ๊ตฌ์ฒดํ™”ํ•ด์„œ ์‚ฌ์šฉ
class Response(GenericModel, Generic[T]):
    code: int
    data: T

class User(BaseModel):
    id: int
    name: str


@app.get("/user", response_model=Response[User])
def get_user():
    return Response(code=200, data=User(id=1, name="Alice"))
    
# → T๊ฐ€ User๋กœ ๊ตฌ์ฒดํ™”๋จ. Response[User]๊ฐ€ ๋˜๋Š” ๊ตฌ์กฐ
{
  "code": 200,
  "data": {
    "id": 1,
    "name": "Alice"
  }
}

 

์š”์ฒญ

 

HTTP ์š”์ฒญ์˜ ๊ธฐ๋ณธ ๊ตฌ์„ฑ

  • method: ์š”์ฒญ ๋ฐฉ์‹ (GET, POST, PUT, PATCH, DELETE)
  • url: ์š”์ฒญ ์ฃผ์†Œ (์˜ˆ: /users/1)
  • headers: ์š”์ฒญ ํ—ค๋” (์˜ˆ: ์ธ์ฆ, Content-Type ๋“ฑ)
  • body: ์š”์ฒญ ๋ณธ๋ฌธ (POST, PUT, PATCH ๋“ฑ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ)

 

FastAPI์—์„œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ฐฉ์‹

 

1. GET ์š”์ฒญ

  • ๋ฐ์ดํ„ฐ ์ „๋‹ฌ: URL ๊ฒฝ๋กœ(Path), ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜(Query), ํ—ค๋”(Header)
from fastapi import FastAPI, Query, Path, Header

app = FastAPI()

@app.get("/users/{user_id}")
def read_user(
    user_id: int = Path(..., description="User ID"), 
    q: str = Query(None, description="๊ฒ€์ƒ‰์–ด"),
    token: str = Header(None)
):
    return {"user_id": user_id, "q": q, "token": token}
  • user_id: URL ๊ฒฝ๋กœ์—์„œ ์ถ”์ถœ (Path)
  • q: ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์ถ”์ถœ (Query)
  • token: ํ—ค๋”์—์„œ ์ถ”์ถœ (Header)

 

2. POST, PUT, PATCH ์š”์ฒญ

  • ๋ฐ์ดํ„ฐ ์ „๋‹ฌ: ์ฃผ๋กœ ์š”์ฒญ ๋ณธ๋ฌธ(Body)
from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
def create_item(
    item: Item = Body(...), # ๋ณธ๋ฌธ ํ•„์ˆ˜
    description: str = Body(None) # ๋ณธ๋ฌธ ์„ ํƒ
):
    return {"item": item, "description": description}

 

๊ตฌ๋ถ„ ๋ฐฉ๋ฒ• ์˜ˆ์‹œ
๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ URL ๋‚ด ๋ณ€์ˆ˜ /items/{item_id}
์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ?key=value /items?limit=10
์š”์ฒญ ๋ณธ๋ฌธ(JSON) Pydantic ๋ชจ๋ธ๋กœ ๋ฐ›์Œ POST, PUT์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ
ํผ ๋ฐ์ดํ„ฐ Form ๊ฐ์ฒด ์‚ฌ์šฉ ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…
ํŒŒ์ผ ์—…๋กœ๋“œ File ๊ฐ์ฒด ์‚ฌ์šฉ ์ด๋ฏธ์ง€, ๋ฌธ์„œ ์—…๋กœ๋“œ
ํ—ค๋” Header๋กœ ๋ฐ›์Œ ์ธ์ฆ ํ† ํฐ ๋“ฑ
์ฟ ํ‚ค Cookie๋กœ ๋ฐ›์Œ ์„ธ์…˜ ์ฒ˜๋ฆฌ ๋“ฑ

 

from fastapi import FastAPI, Form, File, Header, Cookie

@app.post("/login")
def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

@app.post("/upload")
def upload(file: bytes = File(...)):
    return {"file_size": len(file)}

@app.get("/read-header")
def read_header(user_agent: str = Header(None)):
    return {"User-Agent": user_agent}

 

์‘๋‹ต

 

์‘๋‹ต ๋ชจ๋ธ

  • ์‘๋‹ต ๋ชจ๋ธ์€ FastAPI์—์„œ API ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ์™€ ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ ์–ธํ•˜๋Š” ๊ธฐ๋Šฅ
  • Pydantic์˜ BaseModel์„ ์ƒ์†๋ฐ›์•„ ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ๋ฅผ ๋ฏธ๋ฆฌ ์ •์˜ํ•œ๋‹ค.
  • FastAPI์˜ @app.get, @app.post ๋“ฑ ๊ฒฝ๋กœ ํ•จ์ˆ˜์—์„œ response_model=๋ชจ๋ธ๋ช…์œผ๋กœ ์ง€์ •ํ•œ๋‹ค.
  • ์ด ๋ชจ๋ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž… ๋ณ€ํ™˜, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์ง๋ ฌํ™”, ๋ฌธ์„œํ™”๊ฐ€ ์ž๋™์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.
  • ๋ชจ๋ธ์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๋‹ค.

 

๊ตฌ๋ถ„ ๋‹จ์ˆœ response (dict ๋“ฑ) ์‘๋‹ต ๋ชจ๋ธ(response_model)
๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ž์œ ๋กญ๊ฒŒ ๋ฐ˜ํ™˜, ํƒ€์ž… ์ œํ•œ ์—†์Œ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ ์–ธํ•œ ๊ตฌ์กฐ์™€ ํƒ€์ž…์œผ๋กœ ๋ฐ˜ํ™˜
๊ฒ€์ฆ/๋ณ€ํ™˜ ์—†์Œ ํƒ€์ž… ๋ณ€ํ™˜, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ž๋™ ์ˆ˜ํ–‰
๋ฌธ์„œํ™” ์ž๋™ ๋ฌธ์„œํ™” ๋ถˆ๊ฐ€ OpenAPI ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ, ์Šคํ‚ค๋งˆ ๋ช…ํ™•
ํ•„๋“œ ์ œํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜(๋ฏผ๊ฐ์ •๋ณด ๋…ธ์ถœ ์œ„ํ—˜) ๋ชจ๋ธ์— ์ •์˜๋œ ํ•„๋“œ๋งŒ ๋ฐ˜ํ™˜(ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋…ธ์ถœ)
์ง๋ ฌํ™” ์ˆ˜๋™ ์ฒ˜๋ฆฌ ํ•„์š” Pydantic์ด ์ž๋™ ์ง๋ ฌํ™”(JSON ๋ณ€ํ™˜ ๋“ฑ)

 

1. ๊ธฐ๋ณธ ์‘๋‹ต ๋ชจ๋ธ

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    id: int
    name: str

@app.get("/user", response_model=User)
def get_user():
    # password๋Š” User ๋ชจ๋ธ์— ์—†์Œ
    return {"id": 1, "name": "Alice", "password": "secret"}
  • ์‹ค์ œ ๋ฐ˜ํ™˜๊ฐ’์—๋Š” password๊ฐ€ ์žˆ์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฐ›์Œ
{
  "id": 1,
  "name": "Alice"
}

 

2. Generic ์‘๋‹ต ๋ชจ๋ธ

  • ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์‹ธ๋Š” ๊ณตํ†ต ์‘๋‹ต ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ
  • TypeVar์™€ GenericModel์„ ํ™œ์šฉํ•ด, ๋‹ค์–‘ํ•œ ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
from typing import TypeVar, Generic
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.generics import GenericModel

app = FastAPI()
T = TypeVar("T")

class Response(GenericModel, Generic[T]):
    code: int
    data: T

class User(BaseModel):
    id: int
    name: str

@app.get("/user", response_model=Response[User])
def get_user():
    return Response(code=200, data=User(id=1, name="Alice"))
{"code": 200, "data": {"id": 1, "name": "Alice"}}

 

3. Union ์‘๋‹ต ๋ชจ๋ธ

  • ํ•œ ์—”๋“œํฌ์ธํŠธ์—์„œ ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ •์˜
  • Union์„ ํ™œ์šฉํ•˜๋ฉด, ์‘๋‹ต์ด ์—ฌ๋Ÿฌ ๋ชจ๋ธ ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class SuccessResponse(BaseModel):
    result: str

class ErrorResponse(BaseModel):
    error: str

@app.get("/result", response_model=Union[SuccessResponse, ErrorResponse])
def get_result(success: bool = True):
    if success:
        return SuccessResponse(result="ok")
    else:
        return ErrorResponse(error="fail")
# /result?success=true ํ˜ธ์ถœ ์‹œ
{"result": "ok"}

# /result?success=false ํ˜ธ์ถœ ์‹œ
{"error": "fail"}

 

4. List ์‘๋‹ต ๋ชจ๋ธ

  • ์„ค๋ช…:์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฐ์ฒด๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉ
  • response_model=List[Model] ๋˜๋Š” response_model=list[Model]๋กœ ์ง€์ •ํ•œ๋‹ค.
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.get("/items", response_model=List[Item])
def get_items():
    return [
        {"name": "Book", "price": 10.0},
        {"name": "Pen", "price": 2.5},
    ]
[
  {"name": "Book", "price": 10.0},
  {"name": "Pen", "price": 2.5}
]

 

 

์‘๋‹ต ํด๋ž˜์Šค

 

  • ์‘๋‹ต ํด๋ž˜์Šค๋Š” ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•˜๋Š” HTTP ์‘๋‹ต์˜ ํ˜•์‹(ํƒ€์ž…)์„ ์ •์˜
  • FastAPI์—์„œ response_class ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ง€์ •ํ•˜๊ฑฐ๋‚˜, ๊ธฐ๋ณธ๊ฐ’(JSONResponse)์„ ์‚ฌ์šฉ
  • ๋ฐ˜ํ™˜๊ฐ’์„ ํ•ด๋‹น ์‘๋‹ต ํด๋ž˜์Šค์— ๋งž๋Š” ํ˜•ํƒœ(์˜ˆ: JSON, HTML ๋“ฑ)๋กœ ์ž๋™ ๋ณ€ํ™˜ํ•ด ์ค€๋‹ค.
JSON (๊ธฐ๋ณธ) ์ž๋™ ๋ณ€ํ™˜ ๋”•์…”๋„ˆ๋ฆฌ → JSON
HTML HTMLResponse HTML ํ…œํ”Œ๋ฆฟ ๋ฐ˜ํ™˜
ํ…์ŠคํŠธ PlainTextResponse ์ผ๋ฐ˜ ํ…์ŠคํŠธ ๋ฐ˜ํ™˜
๋ฆฌ๋‹ค์ด๋ ‰ํŠธ RedirectResponse URL ์ด๋™ ์ฒ˜๋ฆฌ
ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ FileResponse ํŒŒ์ผ ๋ฐ˜ํ™˜
์ง์ ‘ ์„ค์ • JSONResponse ์ƒํƒœ์ฝ”๋“œ, ํ—ค๋” ์ˆ˜๋™ ์„ค์ • ๊ฐ€๋Šฅ

 

# JSON
@app.get("/json")
def get_json():
    return {"message": "Hello, JSON!"}
    
# HTML
from fastapi.responses import HTMLResponse

@app.get("/html", response_class=HTMLResponse)
def get_html():
    return "<h1>Hello, HTML!</h1>"
    
    
# Plain Text
from fastapi.responses import PlainTextResponse

@app.get("/text", response_class=PlainTextResponse)
def get_text():
    return "Just plain text"

# Redirect
from fastapi.responses import RedirectResponse

@app.get("/redirect")
def redirect():
    return RedirectResponse(url="/new-location")

 

  • ์‘๋‹ต ํด๋ž˜์Šค: "์ด ์‘๋‹ต์„ ์–ด๋–ค ํ˜•์‹(HTML, JSON, ํŒŒ์ผ ๋“ฑ)์œผ๋กœ ์ค„ ๊ฒƒ์ธ๊ฐ€?"
  • ์‘๋‹ต ๋ชจ๋ธ: "์ด ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ(ํ•„๋“œ, ํƒ€์ž…)๋Š” ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”๊ฐ€?"

 

์˜ˆ์™ธ์ฒ˜๋ฆฌ

 

์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด  ์—๋Ÿฌ ์ƒํ™ฉ์— ๋Œ€์‘ํ•˜๊ณ , ์—๋Ÿฌ๋ฉ”์‹œ์ง€์™€ HTTP์ƒํƒœ์ฝ”๋“œ๋กœ API ์— ๋Œ€ํ•œ ์‹ ๋ขฐ์„ฑ๊ณผ UX๋ฅผ ํ–ฅ์ƒ

์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ์ ์—์„œ ํ”„๋กœ๊ทธ๋žจ์ด ๋ฐ”๋กœ ์ข…๋ฃŒ

-> ์„œ๋ฒ„ API๋ผ๋ฉด 500 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ

→ FastAPI๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ try-except ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์„œ๋ฒ„๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฑฐ๋‚˜, ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ(@app.exception_handler)๋ฅผ ๋“ฑ๋กํ•ด์„œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ HTTP ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

try:
    print("์‹œ์ž‘")
    result = 10 / 2
except ZeroDivisionError:
    print("0์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
else:
    print("์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค:", result)
finally:
    print("๋ฌด์กฐ๊ฑด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค")

 

try ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ฝ”๋“œ
except ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ฒ˜๋ฆฌํ•  ์ฝ”๋“œ
else ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ
finally ์˜ˆ์™ธ ๋ฐœ์ƒ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ 

 

HTTPExecption

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id == 0:
        raise HTTPException(status_code=404, detail="Item not found",headers={"WWW-Authenticate": "Bearer"})
    return {"item_id": item_id}
๋งค๊ฐœ๋ณ€์ˆ˜ ์„ค๋ช…
status_code HTTP ์ƒํƒœ ์ฝ”๋“œ (ํ•„์ˆ˜)
detail ์—๋Ÿฌ ์ƒ์„ธ ๋ฉ”์‹œ์ง€ (ํ•„์ˆ˜)
headers ์ถ”๊ฐ€ ์‘๋‹ต ํ—ค๋” (์„ ํƒ)

 

์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ์ฒ˜๋ฆฌ

class NegativeNumberError(Exception):
    pass

def check_positive(n):
    if n < 0:
        raise NegativeNumberError("์Œ์ˆ˜๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")

try:
    check_positive(-5)
except NegativeNumberError as e:
    print(f"์—๋Ÿฌ ๋ฐœ์ƒ: {e}")

 

 

์ฐธ๊ณ 

https://fastapi.tiangolo.com/ko/tutorial/response-model

 

์ง€์†์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ์ค‘์ธ ๊ธ€์ž…๋‹ˆ๋‹ค.