Skip to content

๋ณธ๋ฌธ - ์ˆ˜์ •

PUT์„ ์ด์šฉํ•œ ์ˆ˜์ •

ํ•ญ๋ชฉ์„ ์ˆ˜์ • ํ•˜๊ธฐ ์œ„ํ•ด HTTP PUT์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

jsonable_encoder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ (์˜ˆ๋ฅผ ๋“ค์–ด, NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ) JSON์œผ๋กœ ์ €์žฅ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, datetime ์ž๋ฃŒํ˜•์„ str๋กœ ๋ณ€ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

from typing import List, Union

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


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


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    update_item_encoded = jsonable_encoder(item)
    items[item_id] = update_item_encoded
    return update_item_encoded

PUT์€ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•œ ํ•ญ๋ชฉ์„ ์ˆ˜์‹ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋Œ€์ฒด ๊ฒฝ๊ณ 

๋ณธ๋ฌธ์ด ํฌํ•จ๋œ PUT์„ ์‚ฌ์šฉํ•˜์—ฌ bar ํ•ญ๋ชฉ์„ ์ˆ˜์ •ํ•˜๋ ค๋ฉด:

{
    "name": "Barz",
    "price": 3,
    "description": None,
}

์ด๋ฏธ ์ €์žฅ๋œ ์†์„ฑ "tax": 20.2๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ๊ฐ’ "tax": 10.5๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋Š” 10.5๋ผ๋Š” "์ƒˆ๋กœ์šด" tax๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

PATCH๋ฅผ ์ด์šฉํ•œ ๋ถ€๋ถ„ ์ˆ˜์ •

๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ถ„์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด HTTP PATCH ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, ์ˆ˜์ •ํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๋‚ด๊ณ , ๋‚˜๋จธ์ง€๋Š” ๊ทธ๋Œ€๋กœ ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ 

์ผ๋ฐ˜์ ์œผ๋กœ PATCH๋ณด๋‹ค PUT์ด ๋” ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๊ณ , ์ž˜ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ํŒ€์ด ๋ถ€๋ถ„ ์ˆ˜์ •์„ ํ•  ๋•Œ๋„, PUT๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์›ํ•˜๋Š” ๋Œ€๋กœ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, FastAPI๋Š” ์ด๋ฅผ ์ œํ•œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ์ง€์นจ์„œ๋Š” ๊ฐ ๋ฉ”์„œ๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š” ์ง€๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

Pydantic์˜ exclude_unset ๋งค๊ฐœ ๋ณ€์ˆ˜ ์‚ฌ์šฉ

Pydantic ๋ชจ๋ธ์˜ .dict()์—์„œ exclude_unset ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ถ€๋ถ„ ์ˆ˜์ •์„ ์œ„ํ•ด ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ item.dict(exclude_unset=True)์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ๋ณธ๊ฐ’์„ ์ œ์™ธํ•˜๊ณ  item ๋ชจ๋ธ์„ ๋งŒ๋“ค ๋•Œ ์„ค์ •ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์žˆ๋Š” dict๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์œ„ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ •๋œ (์š”์ฒญ์—์„œ ์ „์†ก๋œ) ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ dict๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ธฐ๋ณธ๊ฐ’์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

from typing import List, Union

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


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


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

Pydantic์˜ update ๋งค๊ฐœ ๋ณ€์ˆ˜ ์‚ฌ์šฉ

์ด์ œ .copy()๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ธฐ์กด ๋ชจ๋ธ์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค๊ณ  ์ˆ˜์ •ํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ dict์™€ ํ•จ๊ป˜ update ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ) stored_item_model.copy(update=update_data):

from typing import List, Union

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


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


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

๋ถ€๋ถ„ ์ˆ˜์ • ์š”์•ฝ

๋ถ€๋ถ„ ์ˆ˜์ •์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์š”์•ฝํ•˜๋ฉด:

  • (์„ ํƒ) PUT ๋Œ€์‹  PATCH๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์œ„ ๋ฐ์ดํ„ฐ๋ฅผ Pydantic ๋ชจ๋ธ์— ๋„ฃ์Šต๋‹ˆ๋‹ค.
  • ์œ„ ๋ชจ๋ธ์—์„œ (exclude_unset์„ ์‚ฌ์šฉํ•˜์—ฌ) ๊ธฐ๋ณธ๊ฐ’ ์—†๋Š” dict๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋ธ์— ์ด๋ฏธ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ €์žฅ๋œ ๊ฐ’์„ ์žฌ์ •์˜ํ•˜๋Š” ๋Œ€์‹  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‹ค์ œ๋กœ ์„ค์ •ํ•œ ๊ฐ’๋งŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ €์žฅ๋œ ๋ชจ๋ธ์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค๊ณ , (update ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ) ์ˆ˜์‹ ๋œ ๋ถ€๋ถ„ ๋ฐ์ดํ„ฐ๋กœ ์†์„ฑ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  • (์˜ˆ๋ฅผ ๋“ค๋ฉด, jsonable_encoder๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด) ๋ณต์‚ฌ๋œ ๋ชจ๋ธ์„ DB์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๊ฒƒ์€ ๋ชจ๋ธ์˜ .dict() ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ, ์˜ˆ๋ฅผ ๋“ค์–ด, datetime์„ str๋กœ ๋ณ€ํ™˜ํ•˜๋“ฏ, ๊ฐ’์„ JSON์œผ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜•์œผ๋กœ ํ™•์ธ (๋ฐ ๋ณ€ํ™˜) ํ•ฉ๋‹ˆ๋‹ค.
  • DB์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์ˆ˜์ •๋œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
from typing import List, Union

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


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


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

ํŒ

์‹ค์ œ๋กœ HTTP PUT ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด์™€ ๋™์ผํ•œ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ์˜ˆ์ œ๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ •๋„๋งŒ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์œผ๋ฏ€๋กœ PATCH๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ 

์ž…๋ ฅ ๋ชจ๋ธ์˜ ๊ฒ€์ฆ์€ ์—ฌ์ „ํžˆ ์ด๋ค„์ง‘๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„ ์ˆ˜์ •์„ ์œ„ํ•ด์„œ๋Š” ๋ชจ๋“  ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ (๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•˜๊ฑฐ๋‚˜ ํ˜น์€ None์„ ์‚ฌ์šฉํ•˜์—ฌ) ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ํ‘œ์‹œ๋œ ๋ชจ๋ธ์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ •์„ ์œ„ํ•ด ๋ชจ๋“  ์„ ํƒ์  ๊ฐ’์ด ์žˆ๋Š” ๋ชจ๋ธ๊ณผ ์ƒ์„ฑ์„ ์œ„ํ•ด ํ•„์ˆ˜์ ์œผ๋กœ ์š”๊ตฌ๋˜๋Š” ๊ฐ’์ด ์žˆ๋Š” ๋ชจ๋ธ์„ ๊ตฌ๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์ถ”๊ฐ€ ๋ชจ๋ธ์— ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.