본문 바로가기

매개변수 라우팅

1. 라우팅 및 세팅

1.1 URL 정보

경로 매개변수를 사용하면 URL의 일부를 변수로 사용할 수 있습니다. 이번 챕터의 URL 구성은 아래와 같습니다.

경로함수명설명
/index인사말을 반환합니다.
/blogblog_list블로그 목록을 반환합니다.
/blog/{post_id}blog_detail블로그 상세 정보를 반환합니다.
/blog/tag/{post_tag}tag_list{post_tag} 태그를 가지고 있는 게시글을 반환합니다.
/blog/tag/{post_tag}/{post_author}tag_author_list{post_tag} 태그 게시물 중, 저자가 {post_author}인 것을 조회합니다.

태그 경로에 /tag/가 들어간 이유

/blog/{post_id}와 /blog/{post_tag}를 함께 두면 두 경로의 모양이 완전히 같아서 FastAPI가 구분할 수 없습니다. 앞의 것이 항상 이깁니다. 이럴 때는 /blog/tag/{post_tag}처럼 자원의 종류를 URL에 드러내 구분합니다. 설계 단계에서 표를 먼저 그리면 이런 충돌을 코드를 짜기 전에 발견할 수 있습니다.

1.2 기본 세팅

이번에도 새로운 폴더를 만들어서 진행하겠습니다. 이번 실습 폴더는 02_2_param입니다. VSC 터미널에서 사용할 명령어 입니다. 가상환경은 벗어난 상태에서 실행해야 합니다. 앞 실습의 서버가 실행 중이면 Ctrl + C로 멈추고, 터미널 입력창 앞에 (venv)라고 되어 있다면 deactivate 명령어로 가상환경을 나간 상태에서 cd ..으로 상위 폴더로 나와 아래 명령어를 실행해주세요.

mkdir 02_2_param
cd 02_2_param
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install "fastapi[standard]"

macOS/Linux에서는 python -m venv venv 대신 python3 -m venv venv를, 활성화 명령 대신 source ./venv/bin/activate를 사용합니다. 이후 명령은 가상환경이 활성화된 상태에서 실행합니다.

2. 경로 매개변수 사용하기

경로 매개변수를 사용하면 URL의 일부를 변수로 사용할 수 있습니다. 이를 통해 동적인 라우팅이 가능해집니다. main.py 파일을 다음과 같이 작성해보겠습니다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/blog/{post_id}")
async def blog_detail(post_id: int):
    return {"게시물 번호": post_id + post_id}

이 예제에서 {post_id}는 경로 매개변수입니다. post_id: int는 이 매개변수가 정수형이어야 함을 명시합니다. 서버를 실행하고 브라우저에서 http://127.0.0.1:8000/blog/5에 접속하면 {"게시물 번호": 10}이라는 응답을 받게 됩니다.

fastapi dev

blog_detail(post_id: int) 문법이 익숙치 않은 분도 있으실겁니다. 여기서 post_id는 경로 매개변수의 이름이며, int는 이 매개변수의 타입을 의미합니다. 이는 파이썬의 기본 문법이며, 아래와 같이 변수를 선언할 때 사용할 수 있습니다.

x: int = 5
y: str = "hello"
print(x + x)
print(y + y)

다만 파이썬에서는 타입힌트가 필수가 아니므로, 위와 같이 타입을 명시하지 않아도 되며 변수의 타입을 강제하지도 않습니다.

2.1 타입 힌트를 빼면 무슨 일이 생기나

FastAPI에서는 타입힌트를 사용함으로써 자동으로 데이터 검증을 수행합니다. 따라서 FastAPI에서 일부 타입힌트는 필수사항이며 이를 통해 코드의 안정성을 높일 수 있습니다. 실습했었던 코드에서 타입힌트를 제외하고 실행해보세요.

from fastapi import FastAPI

app = FastAPI()


@app.get("/blog/{post_id}")
async def blog_detail(post_id):
    return {"게시물 번호": post_id + post_id}

http://127.0.0.1:8000/blog/5에 접속하면 이번에는 {"게시물 번호": "55"}가 나옵니다. URL로 들어온 값은 원래 전부 문자열이고, 타입 힌트가 없으면 문자열 그대로 함수에 전달되기 때문입니다. "5" + "5"는 "55"가 됩니다.

FastAPI에서는 타입힌트가 단지 가독성을 높이는 것 뿐만 아니라 실질적인 데이터 변환과 검증을 진행한다는 것을 알 수 있습니다.

2.2 검증 실패 응답

타입 힌트를 되살린 상태에서 http://127.0.0.1:8000/blog/hello에 접속해보세요. 아래와 같은 응답이 옵니다.

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["path", "post_id"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "hello"
    }
  ]
}

응답 코드는 422 Unprocessable Content입니다. 함수 안에 검증 코드를 한 줄도 쓰지 않았는데 잘못된 요청이 함수에 도달하기 전에 걸러졌습니다.

항목의미
type어떤 종류의 검증에 실패했는지
loc어디에 있는 값이 문제인지 (path, query, body 중 하나와 필드 이름)
msg사람이 읽을 수 있는 설명
input실제로 들어온 값

이 형식은 앞으로 계속 만나게 됩니다. 에러가 났을 때 loc만 봐도 어디를 고쳐야 할지 바로 알 수 있습니다.

3. 여러 경로 매개변수 사용하기

여러 개의 경로 매개변수를 동시에 사용할 수도 있습니다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/blog/tag/{post_tag}/{post_author}")
async def tag_author_list(post_tag: str, post_author: str):
    return {"태그": post_tag, "저자": post_author}

이 엔드포인트는 http://localhost:8000/blog/tag/jeju/hojun와 같은 URL에 응답하며, {"태그": "jeju", "저자": "hojun"}를 반환합니다.

4. 값의 범위까지 검증하기

타입만으로는 부족할 때가 있습니다. "정수이긴 한데 1 이상이어야 한다" 같은 조건입니다. 이럴 때 Path를 사용합니다.

from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/blog/{post_id}")
async def blog_detail(
    post_id: Annotated[int, Path(ge=1, le=1000, description="게시물 번호")],
):
    return {"post_id": post_id}

Annotated는 "이 값은 int인데, 추가로 이런 조건이 붙는다"를 표현하는 파이썬 표준 문법입니다. 타입은 앞에, 조건은 뒤에 적습니다.

조건의미
gegreater than or equal, 이상
gtgreater than, 초과
leless than or equal, 이하
ltless than, 미만
min_length / max_length문자열 길이 제한
pattern정규표현식으로 형식 지정

http://127.0.0.1:8000/blog/0에 접속하면 정수인데도 검증에 실패하는 것을 확인할 수 있습니다. 또한 description에 적은 설명은 /docs 문서에 그대로 표시됩니다.

Annotated가 없던 시절의 코드

FastAPI 초기에는 기본값 자리에 조건을 적었습니다.

async def blog_detail(post_id: int = Path(ge=1)):
    ...

이 방식은 "기본값이 있는 것처럼 보이는데 사실은 필수"라는 혼동을 만들고, 파이썬의 다른 도구들과도 잘 맞지 않아 지금은 Annotated 방식이 표준입니다. AI가 만들어 준 코드에서 위 형태를 보면 Annotated로 바꿔주시면 됩니다. 이 책은 앞으로 계속 Annotated를 사용합니다.

연습문제

  1. 처음에 기획된 URL 경로를 모두 구현하세요.

  2. /hello/{name} 경로에 대한 GET 요청을 처리하는 엔드포인트를 만들어보세요. 이 엔드포인트는 주어진 이름을 사용하여 인사말을 반환해야 합니다. name은 1자 이상 20자 이하만 허용하도록 Path를 사용해보세요.

  3. /calculate/{operation}/{a}/{b} 형식의 경로를 처리하는 엔드포인트를 만들어보세요. 여기서 operation은 "add", "subtract", "multiply", "divide" 중 하나이며, a와 b는 정수입니다. 해당 연산의 결과를 반환해야 합니다. operation이 네 가지 중 하나인지 검사하는 부분은 pattern을 사용해 처리해보세요.

  4. 3번에서 0으로 나누는 요청이 들어오면 어떻게 되는지 확인해보세요. 이 상황을 제대로 처리하는 방법은 6장 에러 처리에서 다룹니다.

매개변수 라우팅 - FastAPI 베이스캠프 | 위니버시티