본문 바로가기

쿼리 매개변수 처리

1. 라우팅 및 세팅

1.1 URL 정보

이번 챕터의 URL 구성은 아래와 같습니다.

경로함수명메서드설명쿼리 매개변수
/indexGET들어온 값을 그대로 출력합니다.-
/itemsread_itemsGET물품 목록을 반환합니다.skip, limit

/items에서 사용할 수 있는 쿼리 매개변수는 아래와 같습니다.

매개변수타입필수 여부설명기본값
skipinteger선택건너뛸 아이템 수0
limitinteger선택반환할 최대 아이템 수10

예시는 아래와 같습니다.

  • /items?skip=0&limit=10: 처음부터 10개의 아이템을 반환
  • /items?skip=10&limit=5: 11번째 아이템부터 5개의 아이템을 반환

이러한 skip과 limit 매개변수를 사용하여 게시판 하단에 페이지를 넘기는 페이지네이션을 구현할 수 있습니다. 이를 통해 게시판 등을 구현할 수 있습니다.

1.2 기본 세팅

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

mkdir 02_4_query
cd 02_4_query
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에서 ? 뒤에 오는 key=value 형태의 문자열입니다. URL의 구조는 아래와 같습니다.

URL은 프로토콜, 호스트, 경로, 쿼리 스트링 등으로 나뉩니다.

여기서 쿼리 스트링은 ? 뒤에 오는 key=value 형태의 문자열입니다. 쿼리 스트링은 URL의 일부로, 서버에 전달되는 데이터를 담고 있습니다. 쿼리 스트링은 key=value 형태로 &로 연결하여 여러 개의 값을 전달할 수 있습니다. 예를 들어, http://example.com/items?skip=0&limit=10에서 skip=0과 limit=10이 쿼리 매개변수입니다.

경로 매개변수와의 차이를 정리하면 아래와 같습니다.

경로 매개변수쿼리 매개변수
형태/items/5/items?limit=5
주 용도자원을 특정할 때목록을 거르거나 정렬할 때
필수 여부항상 필수보통 선택
FastAPI에서경로에 {name}이 있으면경로에 없는 이름이면

FastAPI는 함수 매개변수의 이름이 데코레이터 경로 안에 있으면 경로 매개변수로, 없으면 쿼리 매개변수로 처리합니다. 따로 선언하지 않아도 됩니다.

3. 선택적 쿼리 매개변수

선택적 쿼리 매개변수는 사용자가 제공하지 않아도 되는 매개변수입니다. FastAPI에서는 기본값을 설정하여 구현할 수 있습니다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/items")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

이 예제에서 skip과 limit은 선택적 쿼리 매개변수입니다. 사용자가 값을 제공하지 않으면 기본값이 사용됩니다.

아래 URL을 통해 이 함수에 접근할 수 있습니다.

  • http://127.0.0.1:8000/items: 기본값인 skip=0과 limit=10이 사용됩니다.
  • http://127.0.0.1:8000/items?skip=5: skip=5와 기본값인 limit=10이 사용됩니다.
  • http://127.0.0.1:8000/items?skip=5&limit=20: skip=5와 limit=20이 사용됩니다.

skip과 limit의 이름을 변경하거나 기본값을 변경 또는 타입힌트를 변경하여 어떤 값이 출력되는지 확인해보세요.

from fastapi import FastAPI

app = FastAPI()


@app.get("/items")
async def read_items(hello: str = "world", limit: int = 10):
    return {"hello": hello, "limit": limit}

3.1 값이 없을 수도 있는 매개변수

기본값이 없어도 되지만 "값이 아예 없는 상태"를 표현해야 할 때가 있습니다. 검색어처럼 안 넣으면 전체를 보여주는 경우입니다.

@app.get("/items")
async def read_items(q: str | None = None):
    if q is None:
        return {"message": "전체 목록"}
    return {"message": f"'{q}'로 검색한 결과"}

str | None은 "문자열이거나 없을 수 있다"는 뜻입니다.

Optional을 쓴 코드를 봤다면

파이썬 3.9 이전에는 | 연산자를 타입에 쓸 수 없어서 아래처럼 적었습니다.

from typing import Optional

async def read_items(q: Optional[str] = None):
    ...

지금도 동작하지만 str | None이 더 짧고 import도 필요 없습니다. 이 책은 | 방식으로 통일합니다.

4. 필수 쿼리 매개변수

필수 쿼리 매개변수는 사용자가 반드시 제공해야 하는 매개변수입니다. FastAPI에서는 기본값을 설정하지 않음으로써 필수 매개변수를 정의할 수 있습니다.

from fastapi import FastAPI

app = FastAPI()


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

이 예제에서 item_id는 필수 쿼리 매개변수입니다. 사용자가 이 값을 제공하지 않으면 FastAPI는 자동으로 오류를 반환합니다.

아래 URL을 통해 이 함수에 접근할 수 있습니다.

  • http://127.0.0.1:8000/items?item_id=5: item_id=5를 제공하면 {"item_id": 5}가 반환됩니다.
  • http://127.0.0.1:8000/items: item_id를 제공하지 않으면 422 오류가 발생합니다.

두 번째 경우의 응답을 확인해보세요.

{
  "detail": [
    {
      "type": "missing",
      "loc": ["query", "item_id"],
      "msg": "Field required"
    }
  ]
}

loc이 ["query", "item_id"]입니다. 앞 절에서 봤던 path, body와 함께 세 가지가 모두 나왔습니다.

5. 여러 쿼리 매개변수 사용하기

여러 개의 쿼리 매개변수를 동시에 사용할 수 있습니다. 선택적 매개변수와 필수 매개변수를 조합하여 사용할 수 있습니다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/items")
async def read_items(item_id: int, q: str | None = None):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

이 예제에서 item_id는 필수 매개변수이고, q는 선택적 매개변수입니다.

6. 타입 변환 및 검증

FastAPI는 쿼리 매개변수의 타입을 자동으로 변환하고 검증합니다. 예를 들어, int로 선언된 매개변수에 문자열이 입력되면 FastAPI는 자동으로 정수로 변환을 시도하고, 실패하면 오류를 반환합니다.

from fastapi import FastAPI

app = FastAPI()


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

이 예제에서 item_id는 정수로, price는 부동소수점 숫자로 자동 변환됩니다.

7. 조건과 설명 붙이기

경로 매개변수에 Path를 썼듯이, 쿼리 매개변수에는 Query를 씁니다. 페이지네이션에 안전장치를 걸어보겠습니다.

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()

items = [{"name": f"item{i}", "price": i * 1000} for i in range(1, 101)]


@app.get("/items")
async def read_items(
    skip: Annotated[int, Query(ge=0, description="건너뛸 개수")] = 0,
    limit: Annotated[int, Query(ge=1, le=50, description="한 번에 가져올 개수")] = 10,
):
    return {
        "total": len(items),
        "skip": skip,
        "limit": limit,
        "items": items[skip : skip + limit],
    }

limit에 le=50을 건 이유가 중요합니다. 이 제한이 없으면 누군가 ?limit=1000000을 보냈을 때 서버가 100만 건을 한 번에 읽어 응답을 만들려다 멈춰버립니다. 실제 서비스에서 흔히 발생하는 사고이며, 대부분 이 한 줄로 막을 수 있습니다.

api.http 파일로 확인해보세요.

@baseUrl = http://127.0.0.1:8000

### 기본값으로 조회
GET {{baseUrl}}/items

### 두 번째 페이지
GET {{baseUrl}}/items?skip=10&limit=10

### 상한을 넘긴 요청
GET {{baseUrl}}/items?limit=99999

### 음수 요청
GET {{baseUrl}}/items?skip=-1

마지막 두 요청이 422로 거부되는 것을 확인할 수 있습니다.

8. 여러 값을 받는 쿼리 매개변수

같은 이름의 쿼리 매개변수를 여러 번 붙여 목록을 받을 수도 있습니다. 태그로 필터링할 때 자주 쓰는 형태입니다.

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items")
async def read_items(tags: Annotated[list[str] | None, Query()] = None):
    return {"tags": tags or []}

http://127.0.0.1:8000/items?tags=python&tags=fastapi로 요청하면 {"tags": ["python", "fastapi"]}가 반환됩니다.

여기서 두 가지를 확인하고 넘어가겠습니다.

  • Query()를 생략할 수 없습니다: tags: list[str]이라고만 적으면 FastAPI가 이를 요청 본문으로 오해합니다. 리스트를 쿼리로 받을 때는 반드시 Annotated[list[str], Query()] 형태로 적어야 합니다.
  • 기본값은 = []가 아니라 = None입니다: 파이썬에서 리스트를 기본값으로 쓰면 그 리스트 하나를 모든 호출이 공유하게 되어, 한쪽에서 값을 추가하면 다음 요청에도 남아 있는 버그가 생깁니다. 기본값이 필요한 자리에는 None을 두고 함수 안에서 처리하는 것이 안전합니다.

9. 불리언 타입 쿼리 매개변수

불리언 타입의 쿼리 매개변수도 사용할 수 있습니다. FastAPI는 다양한 형태의 불리언 값을 인식합니다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/items")
async def read_items(item_id: int, is_available: bool = True):
    return {"item_id": item_id, "is_available": is_available}

아래 값들이 모두 인식됩니다.

입력결과
true, True, 1, yes, onTrue
false, False, 0, no, offFalse

이 엔드포인트는 http://127.0.0.1:8000/items?item_id=123&is_available=true 또는 http://127.0.0.1:8000/items?item_id=123&is_available=1과 같은 URL을 처리할 수 있습니다. URL로 오는 값은 전부 문자열인데 파이썬에서는 bool로 받아진다는 점이 이 기능의 핵심입니다.

연습문제

  1. 상품 목록 리스트를 만들어서 페이지네이션을 구현해보세요. page(기본값 1)와 size(기본값 10, 최대 50)를 쿼리 매개변수로 받고, 응답에 전체 개수와 전체 페이지 수를 함께 담아보세요.

    items = [
        {"name": "item1", "price": 1000, "available": True},
        {"name": "item2", "price": 2000, "available": False},
        {"name": "item3", "price": 3000, "available": True},
        {"name": "item4", "price": 4000, "available": False},
        {"name": "item5", "price": 5000, "available": True},
        {"name": "item6", "price": 6000, "available": False},
        {"name": "item7", "price": 7000, "available": True},
        {"name": "item8", "price": 8000, "available": False},
        {"name": "item9", "price": 9000, "available": True},
        {"name": "item10", "price": 10000, "available": False},
    ]
    
  2. 상품 검색 API를 만들어보세요. 아래 쿼리 매개변수를 받아야 하며, 여러 조건이 동시에 들어오면 모두 만족하는 상품만 반환해야 합니다.

    • name (선택): 상품 이름에 포함된 문자열로 검색
    • min_price (선택): 최소 가격
    • max_price (선택): 최대 가격
    • available (선택): 재고 여부
  3. 사용자 목록을 반환하는 API를 만들어보세요. 이 API는 다음과 같은 쿼리 매개변수를 받아야 합니다.

    • page (선택): 페이지 번호 (정수, 기본값 1)
    • size (선택): 페이지당 사용자 수 (정수, 기본값 10)
    • sort_by (선택): 정렬 기준 ('name' 또는 'age', 기본값 'name'). 두 값 외의 값이 들어오면 422 에러가 나도록 Query(pattern=...)을 사용해보세요.
  4. 2번에서 만든 검색 API의 api.http 파일을 작성하고, 조건을 하나씩 늘려가며 결과가 어떻게 줄어드는지 확인해보세요.

쿼리 매개변수 처리 - FastAPI 베이스캠프 | 위니버시티