규도자 개발 블로그

FastAPI endpoint구성 응용 - 1. Path의 Datatype별로 분기 나누기 본문

Python/FastAPI

FastAPI endpoint구성 응용 - 1. Path의 Datatype별로 분기 나누기

규도자 (gyudoza) 2022. 10. 30. 18:50

FastAPI endpoint구성 응용 - 1. Path의 Datatype별로 분기 나누기

이미 누군가가 포스팅해놨으면 안쓰려고 했는데 아쉽게도 하나도 발견하질 못해서 내가 직접 작성한다. 영어로도 이부분에 대해서 포스팅을 해둔 사람이 없고 심지어는 FastAPI 공식 튜토리얼에도 누락된 부분이라 나중에 기회가 된다면 직접 작성해넣을 예정이다.

 

알만한 사람들은 다 아는 사실이지만 FastAPI는 거의 Pydantic과 Starlette으로 만들어진 프레임워크이다. 그래서 여기에서 제공하는 기능들은 거진 제공한다고 생각하면 된다. 그리고 거기엔 FastAPI 문서에는 없지만 Starlette에만 적혀있는 부분이있다. 그중 하나가 endpoint에서 Pathd의 DataType별로 분기를 치는 부분이다.

 

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item_by_id(
    item_id: int,
):
    return {"item_id": item_id}


@app.get("/items/{item_name}")
async def read_item_by_name(
    item_name: str,
):
    return {"item_name": item_name}

예를 들면 이렇게 구성된 라우터가 있다.

자동으로 작성된 swagger문서를 보면

이렇게 생겨서 마치 {item_id}자리에 int를 넣으면 위의 라우터가 실행되고 {item_name}에 string을 넣으면 아래 라우터가 실행될 것 같지만

 

item_id를 사용할 땐 괜찮아도

 

item_name을 이용할 땐 문제가 생긴다. 에러내역을 보면 알겠지만 item_id에 들어온 value가 integer가 아니라서 오류가 발생했다는 내용인데 딱봐도 알겠지만 사용자가 의도한 엔드포인트가 작동되지 않은 것임을 알 수 있다.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item_by_id(
    item_id: int = Path(0, description="The ID of the item to get"),
):
    return {"item_id": item_id}


@app.get("/items/{item_name}")
async def read_item_by_name(
    item_name: str = Path("", description="The name of the item to get"),
):
    return {"item_name": item_name}

이렇게 FastAPI에서 제공하는 Path타입 패러미터로 default data를 초기화시켜줘도

내부에서는 여전히 item_id를 받는 위의 함수(read_item_by_id)가 실행되는 것을 알 수 있다.

 

원인은 짐작하다시피 FastAPI앱이 실행되면 엔드포인트가 내부에서 정렬이되는데 위에 있는 함수일수록 우선순위가 높기 때문에 같은 엔드포인트에 여러 개의 함수를 작성하면 맨 위에 있는 함수가 실행된다. 이것 또한 단순하게 테스트해볼 수 있는데

from fastapi import FastAPI

app = FastAPI()


@app.get("/hello")
async def hello1(
):
    return {"hello": "world1"}


@app.get("/hello")
async def hello2(
):
    return {"hello": "world2"}

이렇게 똑같은 이름의 엔드포인트를 2개 구성하면

함수 이름은 Hello2로 떠서 마치 나중에 선언한 함수가 우선순위를 갖는 것 같지만

막상 실행을 해보면 위에 선언한 hello1함수가 실행된 걸 확인해볼 수 있을 것이다.

 

그럼 원인은 알았으니 이걸 어떻게 해결할까? 간단하다. route에서 path parameter를 지정하는 부분에서 data_type을 명시해주면 된다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id:int}")
async def read_item_by_id(
    item_id: int,
):
    print("worked with item_id")
    return {"item_id": item_id}


@app.get("/items/{item_name:str}")
async def read_item_by_name(
    item_name: str,
):
    print("worked with item_name")
    return {"item_name": item_name}

이렇게 말이다. 이렇게 구성한 라우터를 실행해도면 의도대로 잘 동작하는 걸 확인해볼 수 있다.

int를 넣으면 int router로 작동하고,

 

 

문자열을 넣으면 string으로 작동한다.

 

 

그리고 이런 엔드포인트 작동방식은 FastAPI Tutorial에 없다. Starlette의 Tutorial에 있다. https://www.starlette.io/routing/ 이부분을 참조하면 확인할 수 있다.

 

그러니까 결국 FastAPI를 제대로 쓰기 위해선 Pydantic과 Starlette, +@로 SqlAlchemy를 알아야 한다...는 생각이 들었다.

 

 

다음편은 계층형(Hierarchy) 엔드포인트를 구성할 때 마주치는 문제, 예를 들어 post/{post_id}를 하면 포스트의 개요가 보이고 post/{post_id}/detail를 조회하면 post의 디테일한 정보를 조회하는 형태의 엔드포인트를 구성하고 싶을 때 post/{post_id}/detail를 조회하면 엔드포인트 주소가 위에서 먹혀서 실행되지 않는 현상을 해결해볼 것이다.

 

'Python > FastAPI' 카테고리의 다른 글

FastAPI의 Fast하지 않은 부분  (1) 2022.10.14
FastAPI contributor가 되다  (2) 2022.05.11
FastAPI Schema를 제대로 다루는 방법  (3) 2022.02.19
FastAPI 기여하기  (10) 2022.02.18
젖과 꿀이 흐르는 기회의 땅. FastAPI  (6) 2022.02.12
Comments