Skip to content

์š”์ฒญ ๋ณธ๋ฌธ

ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €๋ผ๊ณ  ํ•ฉ์‹œ๋‹ค)์—์„œ API๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์•ผ ํ•  ๋•Œ, ์š”์ฒญ ๋ณธ๋ฌธ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

์š”์ฒญ ๋ณธ๋ฌธ์€ ํด๋ผ์ด์–ธํŠธ์—์„œ API๋กœ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต ๋ณธ๋ฌธ์€ API์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.

API๋Š” ๋Œ€๋ถ€๋ถ„ ์‘๋‹ต ๋ณธ๋ฌธ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ญ์ƒ ์š”์ฒญ ๋ณธ๋ฌธ์„ ๋ณด๋‚ผ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

์š”์ฒญ ๋ณธ๋ฌธ์„ ์„ ์–ธํ•˜๋ ค๋ฉด, ๊ฐ•๋ ฅํ•œ ํž˜๊ณผ ์žฅ์ ์„ ๊ฐ€์ง„ Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ •๋ณด

๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: POST (์ข€ ๋” ์ผ๋ฐ˜์ ), PUT, DELETE ๋˜๋Š” PATCH.

GET ์š”์ฒญ์œผ๋กœ ๋ณธ๋ฌธ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์€ ์‚ฌ์–‘์— ์ •์˜๋˜์ง€ ์•Š์€ ๋™์ž‘์ด ์žˆ์ง€๋งŒ, ๊ทธ๋Ÿผ์—๋„ FastAPI๋Š” ๋งค์šฐ ๋ณต์žก/๊ทน๋‹จ์  ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋Œ€ํ•ด์„œ๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Swagger UI ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ๋Š” GET์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ณธ๋ฌธ์— ๋Œ€ํ•œ ๋ฌธ์„œ๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๊ณ , ์ค‘๊ฐ„์— ์žˆ๋Š” ํ”„๋ก์‹œ๊ฐ€ ์ง€์›ํ•˜์ง€ ์•Š์„์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Pydantic์˜ BaseModel ์ž„ํฌํŠธ

์šฐ์„ , pydantic์—์„œ BaseModel๋ฅผ ์ž„ํฌํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ƒ์„ฑ

์ด์ œ BaseModel์„ ์ƒ์†ํ•˜๋Š” ํด๋ž˜์Šค๋กœ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— ํ‘œ์ค€ ํŒŒ์ด์ฌ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋ชจ๋ธ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์œผ๋ฉด ํ•„์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“ค๋ ค๋ฉด None์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์œ„์—์„œ ์ด ๋ชจ๋ธ์€ JSON "object"(๋˜๋Š” ํŒŒ์ด์ฌ dict)๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค:

{
    "name": "Foo",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}

...description๊ณผ tax๋Š” (None ๊ฐ’์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ•˜๋ฉด์„œ) ์„ ํƒ์ ์ด๋ฉฐ, ์ด JSON "object" ๋˜ํ•œ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค:

{
    "name": "Foo",
    "price": 45.2
}

๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์„ ์–ธ

์ด๋ฅผ ๊ฒฝ๋กœ ๋™์ž‘์— ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ๊ฒฝ๋กœ ๋ฐ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

...๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑํ•œ Item ๋ชจ๋ธ๋กœ ํƒ€์ž…์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ

ํŒŒ์ด์„  ํƒ€์ž… ์„ ์–ธ๋งŒ์œผ๋กœ FastAPI๋Š”:

  • ์š”์ฒญ ๋ณธ๋ฌธ์„ JSON์œผ๋กœ ์ฝ์Šต๋‹ˆ๋‹ค.
  • (ํ•„์š”ํ•˜๋‹ค๋ฉด) ํ•ด๋‹น ํƒ€์ž…์„ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด, ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋””์—์„œ ๋ฌด์—‡์ธ์ง€ ์ •ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œํ•˜๋Š” ๋ฉ‹์ง€๊ณ  ๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งค๊ฐœ๋ณ€์ˆ˜ item์— ์ˆ˜์‹ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
    • ํ•จ์ˆ˜์— Item ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•จ์œผ๋กœ์จ, ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์™€ ๊ทธ ํƒ€์ž…์— ๋Œ€ํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›(์ž๋™์™„์„ฑ ๋“ฑ) ์—ญ์‹œ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋ธ์˜ JSON ์Šคํ‚ค๋งˆ ์ •์˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉํ•œ ๊ฒฝ์šฐ ์›ํ•˜๋Š” ๊ณณ ์–ด๋””์—์„œ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด ์Šคํ‚ค๋งˆ๋“ค์€ ์ƒ์„ฑํ•œ OpenAPI ์Šคํ‚ค๋งˆ์˜ ์ผ๋ถ€๊ฐ€ ๋˜๋ฉฐ ์ž๋™ UI ๋ฌธ์„œํ™”์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ž๋™ ๋ฌธ์„œ

๋ชจ๋ธ์˜ JSON ์Šคํ‚ค๋งˆ๋Š” OpenAPI ์ƒ์„ฑ ์Šคํ‚ค๋งˆ์˜ ์ผ๋ถ€๊ฐ€ ๋˜๋ฉฐ ๋Œ€ํ™”ํ˜• API ๋ฌธ์„œ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค:

๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฐ ๊ฒฝ๋กœ ๋™์ž‘ ๋‚ด์˜ API ๋ฌธ์„œ์—์„œ๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค:

ํŽธ์ง‘๊ธฐ ์ง€์›

ํŽธ์ง‘๊ธฐ์—์„œ ํ•จ์ˆ˜ ๋‚ด๋ถ€ ๋ชจ๋“  ๊ณณ(Pydantic ๋ชจ๋ธ ๋Œ€์‹  dict๋ฅผ ๋ฐ›์€ ๊ฒฝ์šฐ์—๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค)์—์„œ ํƒ€์ž… ํžŒํŠธ์™€ ์ž๋™์™„์„ฑ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

์ž˜๋ชป๋œ ํƒ€์ž… ์ž‘์—…์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ๊ฒ€์‚ฌ๋„ ๋ฐ›์Šต๋‹ˆ๋‹ค:

์ด๋Š” ์šฐ์—ฐ์ด ์•„๋‹ˆ๋ฉฐ, ์ „์ฒด ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ด๋Ÿฌํ•œ ์„ค๊ณ„๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ ์ฒ ์ €ํžˆ ํ…Œ์ŠคํŠธํ•˜์—ฌ ๋ชจ๋“  ํŽธ์ง‘์ž์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Pydantic ์ž์ฒด์—๋„ ์•ฝ๊ฐ„์˜ ๋ณ€๊ฒฝ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ „ ์Šคํฌ๋ฆฐ์ƒท์€ ๋น„์ฃผ์–ผ ์ŠคํŠœ๋””์˜ค ์ฝ”๋“œ์—์„œ ์ฐ์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ PyCharm ๋ฐ ๋Œ€๋ถ€๋ถ„์˜ ๋‹ค๋ฅธ ํŒŒ์ด์ฌ ํŽธ์ง‘๊ธฐ์—์„œ ๋™์ผํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

ํŒ

PyCharm์„ ํŽธ์ง‘๊ธฐ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, Pydantic PyCharm ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์„ ํ†ตํ•ด Pydantic ๋ชจ๋ธ์— ๋Œ€ํ•œ ํŽธ์ง‘๊ธฐ ์ง€์›์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค:

  • ์ž๋™์™„์„ฑ
  • ํƒ€์ž… ๊ฒ€์‚ฌ
  • ๋ฆฌํŒฉํ† ๋ง
  • ๊ฒ€์ƒ‰
  • ๊ฒ€์‚ฌ(Inspection)

๋ชจ๋ธ ์‚ฌ์šฉ

ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๋ชจ๋ธ ๊ฐ์ฒด์˜ ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜

๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์š”์ฒญ ๋ณธ๋ฌธ์„ ๋™์‹œ์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

FastAPI๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ผ์น˜ํ•˜๋Š” ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๊ฒฝ๋กœ์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ์„, Pydantic ๋ชจ๋ธ๋กœ ์„ ์–ธํ•œ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์š”์ฒญ ๋ณธ๋ฌธ์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค.

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()}

์š”์ฒญ ๋ณธ๋ฌธ + ๊ฒฝ๋กœ + ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜

๋ณธ๋ฌธ, ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ณ  ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ „๋ถ€๋ฅผ ๋™์‹œ์— ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

FastAPI๋Š” ๊ฐ๊ฐ์„ ์ธ์‹ํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: Union[str, None] = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result

ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋‹ค์Œ์œผ๋กœ ์ธ์‹๋ฉ๋‹ˆ๋‹ค:

  • ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๊ฒฝ๋กœ์—๋„ ์„ ์–ธ๋˜์—ˆ๋‹ค๋ฉด ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๋‹จ์ˆ˜ํ˜•(int, float, str, bool ๋“ฑ)์ด๋ฉด ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค.
  • ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ Pydantic ๋ชจ๋ธ ํƒ€์ž…์œผ๋กœ ์„ ์–ธ๋˜์—ˆ๋‹ค๋ฉด ์š”์ฒญ ๋ณธ๋ฌธ์œผ๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ 

FastAPI๋Š” q๊ฐ€ = None์ด๋ฏ€๋กœ ์„ ํƒ์ ์ด๋ผ๋Š” ๊ฒƒ์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Optional[str]์— ์žˆ๋Š” Optional์€ FastAPI(FastAPI๋Š” str ๋ถ€๋ถ„๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค)๊ฐ€ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ์ง€๋งŒ, Optional[str]์€ ํŽธ์ง‘๊ธฐ์—๊ฒŒ ์ฝ”๋“œ์—์„œ ์˜ค๋ฅ˜๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.

Pydantic ์—†์ด ์‚ฌ์šฉํ•˜๊ธฐ

Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด, ๋ณธ๋ฌธ(Body) ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ๋ฌธ - ๋‹ค์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜: ๋ณธ๋ฌธ์˜ ๋‹จ์ˆ˜ํ˜• ๊ฐ’ ๋ฌธ์„œ๋ฅผ ๋ณด์„ธ์š”.