Skip to content

Python 초보자를 위한 FastAPI(2) 1

파이썬의 기본 타이핑

프론트엔드 개발에 Typescript를 사용하기 시작한 이후로 다시는 돌아가지 않겠다고 스스로에게 약속했다... 물론 여전히 일할 때 사용하던 Javascript가 코드베이스에 남아 있긴 하다.

Python은 Javascript와 마찬가지로 동적으로 타입이 지정되는 언어이다. 공식 Python 문서에 따르면 Typing 시스템은 Python 3.5에 도입되었다. 따라서 Python은 Typescript처럼 정적으로 타입이 지정된 언어처럼 '느껴진다'.

지금까지는 일부 내장 타입만 사용해봤는데, 이미 매우 포괄적으로 느껴진다.

a: int = 1
b: float = 1.0
c: bool = True
d: str = "string"
e: bytes = b"data"

다음은 몇 가지 기본 타입으로, 저자는 float 타입과 bytes 타입이 꽤 괜찮다는 것을 알았다. Typescript에는 number로만 모든 숫자를 표현할 수 있기 때문에 정수든 부동 소수점 숫자인지 상관하지 않는다. bytes 타입은 데이터 전송에 좋은 추가 기능인 것 같다.

a: list[int] = [1]
b: set[int] = {1, 2}
c: dict[str, int] = {"count": 1}
d: tuple[str, int, float] = ("string", 1, 1.0)
e: tuple[int, ...] = (1, 2, 3, 4) # tuple with variable size

내장 데이터 구조에 적절하게 입력하는 방법도 매우 간단해 보인다.

a: int | str = 1
b: int | str = "string"
c: int | None = None # a union type with None makes it optional

그리고 union 또는 optional 타입은 |를 사용하여 수행할 수 있다.

위의 기본 타입을 사용하여 첫 번째 API 경로 작업을 시작할 수 있다(hello world는 실제로 중요하지 않다).

Pydantic을 사용한 데이터 검증

앞에서 도커 이미지에 몇 가지 종속성을 설치하였다.

fastapi==0.103.1
pydantic==2.3.0
uvicorn==0.23.2

우리는 이 모든 여정이 FastAPI에 관한 것임을 알고 있다. 그리고 서버를 실행하기 위해 uvicorn 명령을 사용했다. 아직 알려지지 않은 것이 하나 있는데, 바로 Pydantic이다.

Pydantic은 Python용 유효성 검사 라이브러리이다. 저자는 꽤 많은 Javascript/Typescript 유효성 검사 라이브러리를 사용해 왔다. Pydantic은 정말 훌륭하다고 말하지 않을 수 없다. 정말 잘 작동한다. 끝내준다.

데이터의 유효성을 검사하려면 먼저 사용자의 입력을 받는 새로운 엔드포인트가 필요하다. 이 시점에서 작은 음악 플레이어 어플리케이션을 만들 수 있을지도 모른다고 생각하고 있다. 그러려면 새로운 트랙 클래스가 필요하다...

from pydantic import BaseModel

class Track(BaseModel):
    title: str
    artist: str
    album: str | None = None
    year: int
    label: str | None = None

먼저 Pydantic에서 BaseModel을 import하고, BaseModel이 상속하는 Track 클래스를 생성한다. 이 단계에서는 너무 복잡하게 만들지 않으려고 한다. 그래서 현재 Track에는 몇 가지 간단한 속성만 있다. 보시다시피 albumlabel 속성은 str 또는 None일 수 있으므로 선택 사항으로 만들었다. 그리고 a = None을 추가하면 기본값이 None으로 설정된다.

이제 /tracks 엔드포인트를 만들 차례이다.

@app.post("/tracks")
async def create_track(track: Track):
  return track

/tracks 엔드포인트는 트랙 클래스의 타입인 사용자의 요청 본문 track을 받는다. 그리고 실제로는 아무것도 하지 않고 사용자의 입력을 응답으로 반환한다.

이제 브라우저에서 http://localhost/docs로 이동하면 다음과 같은 내용을 볼 수 있다.

schema 섹션에서 track 아코디언을 토글하여 열면...

title, artistyear는 필수로 표시되어 있는 것을 볼 수 있다. albumlabel은 우리가 원하는 대로 optional이다.

이제 녹색 게시물 track 아코디언을 토글하여 열고 "Try it out" 버튼을 누른다. 유효한 예제 요청 본문 스니펫이 있는 것을 볼 수 있다. 직접 실행하면 응답에 의미 없는 예제 요청 본문이 모두 포함된 200 OK가 반환된다.

artist를 제거하고 titletrue로 변경한 다음 "Execute"을 누르면, 이제 다음과 같은 응답이 포함된 422 처리할 수 없는 엔티티가 생긴다.

{
    "detail": [
        {
            "type": "string_type",
            "loc": [
                "body",
                "title"
            ],
            "msg": "Input should be a valid string",
            "input": true,
            "url": "https://errors.pydantic.dev/2.3/v/string_type"
        },
        {
            "type": "missing",
            "loc": [
                "body",
                "artist"
            ],
            "msg": "Field required",
            "input": {
                "title": true,
                "album": "string",
                "year": 0,
                "label": "string"
            },
            "url": "https://errors.pydantic.dev/2.3/v/missing"
        }
    ]
}

프론트엔드 친화적인 오류 메시지는 아닐 수도 있습니다. 하지만 모든 오류(loc 속성 내부)와 오류의 원인(typemsg로 파악할 수 있음)을 지적한다.

현재로서는 오류의 형식은 크게 신경 쓰지 않는다. 따라서 이대로 유지한다.

이제 새 트랙을 생성할 방법이 생겼는데 여기서 멈출 이유가 있을까? 우리가 만든 모든 트랙을 검색하는 get 함수의 간단한 프로토타입을 만들어 보자.

tracks: list[Track] = []

@app.get("/tracks")
async def get_tracks():
    return tracks

먼저 타입이 list[Track]인 트랙 변수를 생성한다. 그리고 /track 경로에서 tracks 리스트만 반환하는 간단한 get 연산을 수행한다.

이것은 잘 작동하지만 사용자 입력을 tracks에 저장해야 한다.

@app.post("/tracks")
async def create_track(track: Track):
    tracks.append(track)
    return track

반환문 앞에 tracks.append(track)를 추가한다. append 메서드는 Javascript의 Array.push()와 동일하다. 이제 새 트랙을 생성하고 tracks 변수에 데이터를 임시 저장할 수 있다.

이 팡트에서는 여기까지이다. 다음에는 OpenAPI 사양에 대해 좀 더 자세히 알아보도록 하겠다...

모든 소스 코드는 github에서 확인할 수 있다.

1: 이 페이지는 FastAPI by A Python Beginner (2)을 편역한 것임.