요첨 본문 (Request Body)

HTTP는 다양한 메서드를 정의하고 있다. 자주 사용되는 주요 HTTP 메서드 5개는 아래와 같다.

  • GET: 리소스 조회
  • POST: 요청 데이터 처리, 주로 등록에 사용 (CREATE)
  • PUT: 리소스를 대체, 해당 리소스가 없으면 생성 (UPDATE)
  • PATCH: 리소스 부분 변경 (UPDATE)
  • DELETE: 리소스 삭제

지금까지는 GET 메서드를 활용했는데, GET 메서드는 서버에 전달하고 싶은 데이터가 있다면 query(쿼리 파라미터, 쿼리 스트링)에 담아 보낸다. 쿼리를 날리면 URL 및 Request Header로 값이 넘어왔다. 하지만 POST방식에서는 Request Body로 넘어오기에 이전가지의 방식으로는 데이터를 받을 수 없다. 따라서 POST, PUT, PATCH 등의 메소드를 사용하는 경우에는 경로 매개변수나 쿼리 매개변수 보다는 HTTP 본문(body)를 많이 활용한다.

path 파라미터, query 파라미터는 url에서 어떤 데이터를 보내는지 볼 수 있다. url에서 보이지 않는 데이터를 실어 보낼 때 body 파라미터를 사용한다고 생각하면 된다.

  • Request Body : 클라이언트가 API로 데이터를 보낼때 사용되는 데이터
  • Response Body : API가 request의 응답으로 클라이언트에게 보내는 데이터

FastAPI에서 request body를 만들기 위해 Pydantic models 를 사용한다.

Pydantic으로 요청 본문(Request body) 받기

먼저 Pydantic모듈의 BaseModel을 import하여 Class를 구성한다. 구성한 클래스에서 request body로 받을 데이터를 정의하면 된다.

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl


app = FastAPI()


class User(BaseModel):
    name: str
    password: str
    avatar_url: Optional[HttpUrl] = None

    
@app.post("/users")
def create_user(user: User):
    return user

여기서 HttpUrl은 pydantic 타입이다. 인터넷 환경에는 이메일, 파일 경로, 우편 번호 등 다양한 형태의 값들을 받는다. pydantic은 자주 쓰이는 타입들을 미리 제공하고 해당하는 타입을 검증한다. HttpUrl은 그중 하나이다. 만약에 HttpUrl 없이 avatar_url 을 받는다면 수동으로 데이터를 검증해줘야 한다.

(fastapi) PS D:\workspace> http :8000/users name=syj password=1234
HTTP/1.1 200 OK
content-length: 50
content-type: application/json
date: Sat, 13 Aug 2022 09:44:21 GMT
server: uvicorn

{
    "avatar_url": null,
    "name": "syj",
    "password": "1234"
}

입력과 출력이 같은 에코(ehco) API 였기 때문에 입출력이 같다.

FastAPI에서 처리하는 과정은 다음과 같다.

  1. JSON으로 넘어온 Request Body 내용을 읽는다.
  2. 필요시 해당 데이터를 상응되는 타입으로 변환한다.
  3. 데이터 유효성 검사를 한다. → 만약 invalid하면 error를 반환한다.
  4. 전달받은 데이터를 파라미터 user에 넘겨준다.
  5. User객체에 맞게 JSON Schema를 생성하게 되고, 함수 내부에서 user은 User 객체로서 사용된다.

body 에 두 가지 모델 객체 받기

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

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

class User(BaseModel):
    username: str
    full_name: Optional[str] = None

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results 
{
  "item": {
    "name": "string",
    "description": "string",
    "price": 0,
    "tax": 0
  },
  "user": {
    "username": "string",
    "full_name": "string"
  }
}