본문 바로가기
Python/Python Web Framework

FastAPI 03. 리퀘스트 바디

by mintropy 2023. 1. 10.

API 서버에서 사용자 입력을 받기 위하여 경로 매개변수, 퀘리 매개변수도 사용하지만 일반적으로 리퀘스트 바디를 많이 사용할 것이다. 특히 많은 입력이 필요한 경우 매개변수를 활용하여 URL을 조작하는 것보다 JOSN을 활용하여 바디로 통신하는 것이 수월하다. 이전에 리퀘스트 바디는 Pydantic을 사용한다고 하였다. 이번에는 바디를 더욱 다양하게 활용하는 방법을 알아볼것이다.

여러 개의 매개변수, 바디 사용하기

@app.post("/items/{item_id}")
async def get_item(
    *,
    item_id: int = Path(title="The ID of item to get", ge=0, le=10000),
    q: str | None = Query(default=None, max_length=50),
    item: Item | None = None,
):
    ...

경로, 쿼리 매개변수와 바디를 모두 사용할 경우 모두 각각의 형태에 맞추어 변수로 선언하면 된다. FastAPI제공하는 Path, Query함수는 선택적으로 사용할 수 있지만, 위와 같이 더 많은 변수를 사용하는 경우가 생기면 구분하기가 쉽지 않을 수 있다. 따라서 여러 개의 매개변수와 바디를 같이 사용하는 경우 Path, Query함수를 함께 사용하면 편할 수 있다.

@app.post("/items/")
async def get_item(
    item: Item,
    user: User,
    importance: int = Body(),
):
    ...

만약에 바디에 여러 값을 사용하는 경우에도 같은 방식으로 변수로 할당하면 된다. Pydantic으로 정의한 모델이라면 item: Item과 같은 방식으로 변수의 타입으로 해당 모델을 기입하면 된다. 만약 특정 모델로 정의할 정도로 중요하지 않거나, 한 군데에서만 임시적으로 사용되는 경우라면 Body()함수를 사용할 수 있다. 이전에 소개한 Path, Query 함수와 같은 방식으로 구성할 수 있다. 이경우 request는 다음과 같이 사용할 수 있다.

{
    "item": {
        "id": 1,
        "name": "Foo",
        "price": 5000
    },
    "user": {
        "id": 10,
        "name": "Bar"
    },
    "importance": 5
}

단일 모델을 사용하기

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

만약 위와 같이 하나의 모델만 사용하는 경우가 있을 수 있는데, 이때 FastAPI는 중첩된 JSON이 아니라 Item모델의 각 필드가 JSON의 최상위 키, 값으로 표현된다.

{
    "id": 1,
    "name": "Foo",
    "price": 5000
}

위와 같이 사용이 되는데, 같은 모델을 여러 번 사용하면서 “item”을 키로 중첩된 값을 가질 때와 가지지 않는 때로 구분되면 사용자입장에서 불편하게 느껴질 수 있다.

@app.post("/items/")
async def get_item(item: Item = Body(embed=True)):
    ...

위에서 소개한 Body함수를 활용, embed값을 참으로 부여하면 하나의 모델을 사용하더라도 중첩된 JSON값으로 표현 및 사용된다.

{
    "item": {
        "id": 1,
        "name": "Foo",
        "price": 5000
    }
}

Field를 바디에 활용하기

from pydantic import BaseModel, Field

class Item(BaseModel):
    id: int
    name: str
    description: str | None = Field(default=None, max_length=300)
    price: int

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    ...

지금까지는 매개변수와 바디를 다루는 것을 보았는데, 바디에서 각 필드의 검증은 다루지 않았다. 이는 Pydantic에서 제공하는 Field를 사용하여 해결할 수 있다.

중첩된 바디 모델

{
    "item": {
        "id": 1,
        "name": "Foo",
        "price": 5000,
        "images": [
            {
                "url": "https://example.com/1",
                "name": "image1"
            },
            {
                "url": "https://example.com/2",
                "name": "image2"
            }
        ]
    }
}

모델이 더 다양화되면서 위와 같이 중첩된 형태를 사용하는 경우가 있다. 위의 경우에는 image모델을 리스트로 가지는 경우이다.

from pydantic import BaseModel, Field, HttpUrl

class Image(BaseModel):
    url: HttpUrl
    name: str

class Item(BaseModel):
    id: int
    name: str
    description: str | None = Field(default=None, max_length=300)
    price: int
    image: list[Image] | None = None

@app.post("/items/")
async def get_item(item: Item | None = None):
    return item

다음과 같은 API에 대하여 위의 JSON 형태의 요청을 할 수 있다. Pydantic모델을 위와 같이 정의한다면 더 중첩된 경우에도 적용할 수 있다.

댓글