Post

FastAPI 공식문서 따라하기[16] - Response Model

https://fastapi.tiangolo.com/tutorial/response-model/ 공식문서 따라하는 글

☑️ Response Model

Responsemodel을 담아 리턴할 수 있다 어떤 요청에 대해서도 가능하다.(@app.post(), @app.get(), @app.put(), @app.delete()…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


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

코드를 보면, Item이라는 객체를 보내면 이를 받아 그대로 리턴해버린다.

response_model에 리턴할 자료형을 입력해주었다.

SwaggerUI에서도 확인이 가능하지만, 처음인만큼 뭔가 Postman으로 시험해보았다.

그대로 리턴해준다.

공식문서에서 주의해야한다고 지적한 점은 위치이다. response_model은 인자에서 선언한 것이 아닌, decorator에서 선언했다.

  • 리스트로도 받을 수 있다고 한다. ex) List[Item]

response_model을 사용함으로써 다음과 같은 이점을 얻을 수 있다고 한다.

  • 출력 데이터를 선언한 타입으로 변환할 수 있다.
  • 그로인해서 데이터 검증을 하니까 데이터 검증은 덤이다.
  • OpenAPI같은 문서도구에 JSON Schema를 만들어 등록한다.

가장 중요한 점은

출력 데이터 모델을 지정해줄 수 있다는 것이다.

☑️ Return the same input data

여기서 예제로 UserIn이라는 모델을 선언했다. 이 모델은 password라는 속성값을 str형식으로 받는다.

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

그리고 UserIn 모델을 받고, 그대로 리턴해버리는 코드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


# Don't do this in production!
# 추가
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
    return user

유저가 직접 password를 입력하는 경우에도 이 때는 문제가 생기지 않을 수 있다.

하지만 만약 이 모델이 다른 요청들의 필요에 의해 그대로 넘겨버리면 항상 password를 전송하는 위험부담이 생겨버린다.

공식문서에서는 절대로 password값을 그대로 저장하지 말라고 써있다. 암호화는 필수다.

☑️ Add an output model

password를 제외하고 데이터를 보내버리게 모델을 새로 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user

UserOutpassword값을 포함하지 않고 response_model=UserOut을 통해 userOut모델을 보내버린다.

어떻게 이런 일이? 요청에는 password를 보냈는데, 돌아온 값은 password가 포함되지 않았다.

FastAPIPydantic을 이용하여 내보낼 데이터에 포함되지 않은 속성값들을 필터하는 기능을 가진다고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str| None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []

items = {
    "kms": {"name": "bag","price" : 350},
    "bjh": {"name": "watch","price": 60, "description": "test des", "tax" : 25.3},
    "jyb": {"name": "neck", "price":30, "description": None,"tags":["test tag","test2 tag2"]}
}

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

요청을 쿼리파라미터로 받기에

1
2
3
http://localhost:8000/items/bjh
http://localhost:8000/items/kms
http://localhost:8000/items/jyb

셋 중 하나로 보내면 그에 맞는 데이터값을 보내게 된다.

위의 구문중 크게 어려운 문법은 없지만 response_model_exclude_unset은 다소 생소하다.

☑️ Use the response_model_exclude_unset parameter

뜻은 간단하다. 반환 모델에서 default value가 설정되지 않은 속성값들은 반환할 때 포함시키지 않는다. 즉, 설정된 값만 리턴한다.

만약 kms로 요청을 보낸다면 이렇게 default value값이 설정된 것들(description,tags,tax)은 응답에 포함시키지 않는다. 다른 속성값들은 필수로 채워줘야한다.

결과

1
2
3
4
{
  "name": "bag",
  "price": 350
}

또한

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

도 있다.

여기서 헷갈리는 점이 있다.

items와 Item과 어떤 연관관계가 있을까?

없다. 그저 이름이 비슷할뿐이다. itemstest로 바꾸고 리턴값에 test[item_id]로 바꿔도 돌아간다.

해당 옵션을 빼고 jyb요청을 보내면 default value를 설정한 tax가 포함되어있다.

None으로 설정하지 않는다면 그 어떤값도 default value라고 판단하지 않고 보낸다.

☑️ response_model_include and response_model_exclude

이름에서 보면 유추할 수 있듯이, 어떤 값을 포함할지 안할지를 정할 수 있다.

공식문서에서는 이러한 방식을 추천한다기 보다는 새로운 클래스를 만들어서 반환하는 것을 추천한다. 그 이유는 결국 이렇게 데코레이션을 단 모델도, JSON Shcema를 생성할때 포함되기 때문이다. 또한 respone_model_by_alias도 마찬가지다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


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


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

요청

1
http://localhost:8000/items/baz/name

결과

1
2
3
4
{
  "name": "Baz",
  "description": "There goes my baz"
}

즉, response_model_include는, 지정된 값이 존재할 시 리턴한다. 없으면 null을 리턴한다.

반대로 response_model_exclude는 위에서 지정한 tax만 제외하고 값이 있다면 전부 리턴한다.

http://localhost:8000/items/foo/public로 요청을 보내면 foo에는 없는 descriptionnull로 입력되는데, 이는 respone_model=Item으로 리턴 모델을 지정해놔서 그렇다.

This post is licensed under CC BY 4.0 by the author.