본문 바로가기
파이썬/FastAPI

[Python] FastAPI 사용법

by 책 읽는 개발자_테드 2022. 4. 17.
반응형

목차

· 경로 매개변수

· 쿼리 매개변수

· Query 클래스를 통한 쿼리 매개변수 검증

· Path 클래스를 통한 경로 매개변수 검증

· Request Body

 

 


FastAPI란? FastAPI 시작하기

 

경로 매개변수


1. 파이썬 문자열 포맷과 동일한 문법으로 매개변수를 경로에 선언할 수 있다.

from fastapi import FastAPI

app = FastAPI()


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

- 경로 매개변수 item_id의 값은 함수의 item_id 인자로 전달된다.

 

2. 파이썬 표준 타입 애너테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있다.

from fastapi import FastAPI

app = FastAPI()


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

- 여기서 item_id는 int로 선언되었다. int가 아닌 값을 요청하면, 에러가 발생한다. ex) http://127.0.0.1:8000/items/ted 

- 모든 데이터 검증은 Pydatic에 의해 내부적으로 수행된다.

 

3. 경로 동작은 순차적으로 평가된다.

- 경로 동작을 만들때 고정 경로와 동적 경로가 모두 필요한 경우가 있을 수 있다. 예를 들어 /users/me에서는 현재 사용자의 데이터를, /users/{user_id}에서는 사용자 ID를 이용해 특정 사용자 정보를 가져와야 할 수 있다. 경로 독작은 순차적으로 순차적으로 평가되므로 /users/me를 앞에, /users/{user_d}를 뒤에 선언하면 문제를 해결할 수 있다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}


@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}

 

4. 경로 매개변수 값을 열거형 멤버로 사용할 수 있다.

from enum import Enum
from fastapi import FastAPI
import uvicorn

class PayType(str, Enum):
    kakao_pay = "카카오페이"
    naver_pay = "네이버페이"
    toss = "토스"

app = FastAPI()


@app.get("/pay-type/{pay_type}")
async def get_model(pay_type: PayType):
    if pay_type == PayType.kakao_pay:
        return {"pay_type": pay_type, "message": "마음 놓고 금융하다."}

    if pay_type.value == "토스":
        return {"pay_type": pay_type, "message": "금융의 모든것 토스입니다."}

    return {"pay_type": pay_type, "message": "카카오페이, 토스가 아닌 결재 수단을 이용했습니다."}

 

위 코드를 실행하고,

http://127.0.0.1:8000/pay-type/카카오페이

http://127.0.0.1:8000/pay-type/토스

http://127.0.0.1:8000/pay-type/네이버페이

http://127.0.0.1:8000/pay-type/쿠팡페이

로 브라우저를 통해 각각 접속하고 결과를 확인하자.

 

경로에 열거형 멤버로 미리 정의된 값이 입력되면 올바른 결과가 출력되지만, 미리 정의한 열거형 멤버가 아닌 값(쿠팡페이)을 입력하면 오류가 발생하는걸 확인할 수 있다.

 

쿼리 매개변수


· 쿼리는 URL에서 ? 후에 나오고 &으로 구분되는 키-값 쌍의 집합이다.

ex) http://127.0.0.1:8000/items/?skip=0&limit=10

 

1. 경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언할 때, 쿼리 매개변수로 자동 해석된다.

import uvicorn
from fastapi import FastAPI

app = FastAPI()

fake_items_db = [{"name": "Ted"}, {"name": "Robin"}, {"name": "Barney"}]


@app.get("/items/")
async def read_item(skip: int, limit: int):
    return fake_items_db[skip : skip + limit]

위 코드를 실행하고, 다음 링크에 접속하자. http://127.0.0.1:8000/items/?skip=0&limit=1

2. 쿼리 매개변수는 경로에서 고정된 부분이 이니며, 기본값을 가질 수 있다.

read_item을 아래와 같이 기본값을 가지도록 수정한다.

@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 2):
    return fake_items_db[skip : skip + limit]

 

그리고 다음 링크에 접속한다. http://127.0.0.1:8000/items/

위와 같이 쿼리 매개변수를 생략하고 요청을 보내면, 기본값으로 설정한 대로 데이터를 반환하게된다.

 

3. 기본값을 None으로 설정하여 선택적 매개변수를 선언할 수 있다.

from fastapi import FastAPI
from typing import Optional

app = FastAPI()


@app.get("/tv-shows/{tv_show_name}")
async def read_tv_show(tv_show_name: str, character: Optional[str] = None):
    if character:
        return {"tv_show_name": tv_show_name, "character": character}
    return {"tv_show_name": tv_show_name}

위 코드를 실행하면, 다음과 같이 접속 링크에 따라 반환값이 다르게 된다. 

 

http://127.0.0.1:8000/tv-shows/HIMYM

http://127.0.0.1:8000/tv-shows/HIMYM?character=Ted

4. 여러 경로의 매개변수와 쿼리매개변수를 동시에 선언할 수 있다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/tv-shows/{tv_show_name}/characters")
async def read_tv_show_and_character(tv_show_name: str, character: str):
    return {"tv_show_name": tv_show_name,"character:": character}

위 코드를 실행하고, 다음 링크로 접속하자.

http://127.0.0.1:8000/tv-shows/HIMYM/characters?character=Ted

 

5. 쿼리 매개변수의 기본값을 선언하지 않으면, 해당 쿼리 매개변수는 필수 쿼리 매개변수가 된다.

- 선택적 쿼리 매개변수를 만들려면, 쿼리 매개변수의 기본값을 None으로 설정하면 된다.

from fastapi import FastAPI

app = FastAPI()


@app.get("/movies/{movie}")
async def read_user_item(movie: str, character: str):
    item = {"movie": movie, "needy": character}
    return item

위 코드에서 character 쿼리 매개변수는 필수 쿼리 매개변수다.

브라우저에서 다음 URL을 열어보자. http://127.0.0.1:8000/movies/comet

위에 보이는 것 처럼 필수 매개변수인 character를 넣지않아 오류가 발생한다.

 

다음 URL을 열어보면, 정상적인 데이터를 볼 수 있다. http://127.0.0.1:8000/movies/comet?character=Kimberley

 

Query 클래스를 통한 쿼리 매개변수 검증


· Query 클래스를 통해 쿼리 매개변수의 값을 검증하고, 제한할 수 있다.

import uvicorn
from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

@app.get("/tv-shows/{tv_show_name}")
async def read_tv_show(tv_show_name: str, character: Optional[str] = Query(None)):
    if character:
        return {"tv_show_name": tv_show_name, "character": character}
    return {"tv_show_name": tv_show_name}

 

위 코드를 실행한 후 다음 URL에 접속한다. http://127.0.0.1:8000/tv-shows/HIMYM?character=itistoolongcharactername

위 코드에서 캐릭터 이름이 10자 이내로 제한되었기 때문에 다음과 같은 오류가 발생한다.

· Query를 단순히 기본값으로 사용할 수 있다.

 즉, 아래 두 선언은 같은 역할을 한다.

character: Optional[str] = Query(None)
character: Optional[str] = None

 

- None이 아닌 다른 값을 기본값으로 정할 수도 있다.

ex) character: Optional[str] = Query("ted", min_length=3)

 

· Query를 통해 매개변수가 일치해야 하는 정규식을  정의할 수 있다.

@app.get("/tv-shows/{tv_show_name}")
async def read_tv_show(tv_show_name: str, character: Optional[str] = Query(None, min_length=3, max_length=10, regex="^fixedquery$")):
    if character:
        return {"tv_show_name": tv_show_name, "character": character}
    return {"tv_show_name": tv_show_name}

- 위 코드에서 regex로 표현된 정규 표현식은 수신된 매개변수 값이 다음과 같은지 확인한다.

1. ^:  다음 문자로 시작하고, 이전 문자가 없다.

2. fixedquery: 정확한 값인 fixedquery를 갖는다.

3.$: fixedquery 이후에 더 이상 문자가 없다.

 

위 코드를 실행한 후 URL로 요청을 보내면, 쿼리 매개변수에 따라 다음과 같은 결과를 받을 수 있다.

 

· Query를 사용하여 필수값을 지정하려면, 첫 번째 매개변수로 ...을 입력한다. 

import uvicorn
from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

@app.get("/tv-shows/{tv_show_name}")
async def read_tv_show(tv_show_name: str, character: Optional[str] = Query(..., min_length=3)):
    if character:
        return {"tv_show_name": tv_show_name, "character": character}
    return {"tv_show_name": tv_show_name}

 

위 코드를 실행하고, character 쿼리 메서드를 지정하지 않은 URL로 요청을 보낸다. http://127.0.0.1:8000/tv-shows/HIMYM

필수값인 character 쿼리 메서드를 지정하지 않았기 때문에 다음과 같은 오류가 발생한다. 

· Query로 쿼리 매개변수를 명시적으로 정의하면 list 값을 받도록 선언하거나, 여러 값을 받도록 선언할 수 있다.

import uvicorn
from fastapi import FastAPI, Query
from typing import List, Optional

app = FastAPI()

@app.get("/characters/")
async def read_characters(q: Optional[List[str]] = Query(None)):
    query_items = {"q": q}
    return query_items

 

위 코드를 실행하고, 다음 URL로 요청을 보낸다. http://127.0.0.1:8000/characters/?q=ted&q=robin&q=barney

 

· Query로 기본 값 list를 제공할 수도 있다.

import uvicorn
from fastapi import FastAPI, Query
from typing import List, Optional

app = FastAPI()

@app.get("/characters/")
async def read_characters(q: Optional[List[str]] = Query(['ted', 'robin'])):
    query_items = {"q": q}
    return query_items

위 코드를 실행하고, 쿼리 메서드 없는 URL로 요청을 보낸다. http://127.0.0.1:8000/characters/

다음과 같이 기본값이 반환된다.

 

Path 클래스를 통한 경로 매개변수 검증


· Path 클래스를 사용하여 경로 매개변수에 유효성 검사 및 메타데이터를 선언할 수 있다.

 

1. 사용을 위해 fastapi의 Path를 import 한다.

from fastapi import FastAPI, Path

 

2. 메타데이터를 선언할 수 있다.

 

다음은 경로 매개변수 movie_id에 title 메타데이터 값을 선언한 것이다. 

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/movies/{movie_id}")
async def get_movies(movie_id : int = Path(..., title="The ID of the movie to get")):
    results = {"movie_id": movie_id}
    return results

- 경로 매개변수는 경로의 일부이므로 언제나 필수적이다. None으로 선언하거나 기본값을 지정해도 아무 영향을 끼치지 않는다.

- 따라서 위의 코드처럼 ...을 선언해서 필수임을 나타내는것이 좋다.

 

3. 숫자와 문자열 제약을 선언할 수 있다.

 

다음은 movie_id가 100이상으로 제약 조건을 생성한 예시다.

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/movies/{movie_id}")
async def get_movies(movie_id : int = Path(..., title="The ID of the movie to get", ge=100)):
    results = {"movie_id": movie_id}
    return results

제약조건을 따르지 않고, movie_id를 100 미만으로 요청하면 다음과 같이 오류가 발생한다.

- gt: 큰(greater than)

- ge: 크거나 같은(greater than or equal)

- lt: 작은(less than)

- le: 작거나 같은(less than or equal)

 

Request Body


· 클라이언트에서 API로 데이터를 보내야할 때, Request Body를 통해 보낼 수 있다.

· FastAPI는 Request Body를 선언하기 위해 Pydatic 모델을 사용한다.

· 데이터를 보낼때 POST, PUT, DELETE, PATCH 중 하나를 사용한다.

· 요청에 대한 API 응답을 Response Body라고 한다.

 

· 파이썬 타입을 선언하면, FastAPI는 다음을 수행한다.

1. Request Body를 JSON으로 읽는다.

2. 필요한 경우에 해당 유형을 변환한다.

3. 데이터를 검증한다.

- 데이터가 유효하지 않은 경우 명확한 오류를 반환하고, 정확한 위치와 무엇이 잘못되었는지 나타낸다.

4. 파라미터 항목에 수신된 데이터를 제공한다.

5. 모델에 대한 JSON 스키마 정의를 생성하고 프로젝트에 적합한 경우 원하는 다른 곳에서 사용할 수도 있다. 

- 이러한 스키마는 생성된 OpenAPI 스키마의 일부가 되며 자동 문서 UI에서 사용된다.

 

· pydantic으로 부터 BaseModel을 import하고, 데이터 모델을 BaseModel에서 상속하는 클래스로 선언한다. 모든 속성은 표준 파이썬 타입을 사용한다.

- 쿼리 매개변수를 선언할 때와 같이 모델 속성에 기본 값을 적용할 수 있고, 기본 값으로 None을 적용하면 해당 속성은 선택 사항이된다. 그렇지 않으면 필수사항이다.

 

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

class Drama(BaseModel):
    name: str
    description: Optional[str] = None
    score: float
    channel: Optional[str] = None


app = FastAPI()


@app.post("/dramas/")
async def create_drama(drama: Drama):
    return drama

 

위 코드를 실행하고, 127.0.0.1:8000/dramas URL 주소에 Post 메서드로  아래 JSON 데이터를 담아 전송한다.

{
    "name": "How I Met Your Mother",
    "description": "제목대로 주인공 테드 모스비가 2030년, 자식들에게 자기 아내를 어떻게 만났는지 이야기해 주는 액자형 구성을 취하고 있다. 기본적으로 매 화의 내용은 보통 시청자 입장에서 어제 일어났을 이야기들이다",
    "score": 9.9,
    "channel": "CBS"
}

 

· 모델의 JSON 스키마는 OpenAPI 생성 스키마의 일부이며, 대화형 API 문서에 표시된다.

 

· 경로 매개변수와 Request Body를 동시에 선언할 수 있다.

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

class Drama(BaseModel):
    name: str
    description: Optional[str] = None
    score: float
    channel: Optional[str] = None


app = FastAPI()


@app.put("/dramas/{drama_id}")
async def create_drama(drama_id: int, drama: Drama):
    return {"drama_id": drama_id, **drama.dict()}

 

위 코드를 실행하고, 127.0.0.1:8000/dramas/1 URL 주소에 Post 메서드로  아래 JSON 데이터를 담아 전송한다.

 

· 쿼리 매개변수와 경로 매개변수 그리고 Request Body를 동시에 선언할 수 있다.

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

class Drama(BaseModel):
    name: str
    description: Optional[str] = None
    score: float
    channel: Optional[str] = None


app = FastAPI()


@app.put("/dramas/{drama_id}")
async def create_drama(drama_id: int, drama: Drama, q: Optional[str] = None):
    result = {"drama_id": drama_id, **drama.dict()}
    if q:
    	result.update({"q": q})
    return result

 

 

출처

https://fastapi.tiangolo.com

반응형

댓글