[FastAPI] 라우팅

귤티·2024년 5월 7일
0

FastAPI

목록 보기
2/10

라우팅은 웹 애플리케이션을 구축하는 데 있어 핵심적인 부분으로, 클라이언트가 서버로 보내는 HTTP 요청을 처리하는 프로세스이다. HTTP 요청이 지정한 라우트로 전송되면 미리 정의된 로직이 해당 요청을 처리해서 반환(응답)한다.

라우트는 HTTP 요청 메서드의 요청을 수락하고 선택적으로 인수를 받을 수 있도록 정의된다.
요청이 특정 라우트로 전달되면 애플리케이션은 라우트 처리기(route handler)가 요청을 처리하기 전에 해당 라우트가 정의되어 있는지 확인한다. 라우트 처리기는 서버로 전송된 요청을 처리하는 함수다.
라우팅은 요청을 HTTP 메소드 종류와 라우트에 맞게 분배하는 것을 의미한다.

  • HTTP 요청 메소드
    표준 메소드에는 GET, POST, PUT, PATCH, DELETE 등이 있다.

라우팅 예
단일 라우트 애플리케이션:

from fastapi import FastAPI
app = FastAPI()

@app.get("/")

async def welcome() -> dict:
	return{
    	"message": "Hello World"
    }

app 변수로 초기화한 FastAPI() 인스턴스를 사용해서 라우팅을 처리했다.
그리고 uvicorn 도구가 FastAPI() 인스턴스를 가리키도록 했다.

FastAPI() 인스턴스를 라우팅 작업에 사용할 수 있으나 라우팅 중에 단일 경로만 고려하는 애플리케이션에서 일반적으로 사용된다.
고유한 함수를 처리하는 각각의 라우트가 FastAPI() 인스턴스를 사용하는 경우 애플리케이션은 한 번에 여러 라우트를 처리할 수 없다. uvicorn이 하나의 엔트리 포인트만 실행할 수 있기 때문이다.

APIRouter 클래스를 사용한 라우팅

APIRouter 클래스는 다중 라우팅을 위한 경로 처리 클래스로, fastapi 패키지에 포함되어 있다.
APIRouter 클래스를 통해 애플리케이션 라우팅과 로직을 독립적으로 구성하고 모듈화할 수 있다.
fastapi 패키지에서 APIRouter 클래스를 import한 후 APIRouter() 인스턴스를 생성할 수 있다.

from fastapi import APIRouter

router = APIRouter()

@router.get("/hello")
async def say_hello() -> dict:
	return {
		"message": "hello"
}

APIRouter 클래스를 사용해서 새로운 라우트와 라우트 처리기를 만들어보자.
todos를 생성 및 추출하는 함수를 만든다.

touch todo.py

todos 폴더에 todo.py 파일 생성

생성된 파일 안에 코드를 작성:

from fastapi import APIRouter

todo_router = APIRouter() // APIRouter 인스턴스 만들기

다음으로 앱 내부 데이터베이스를 임시로 만들고 todos를 생성 및 추출하는 라우트를 정의:

from fastapi import APIRouter

todo_router = APIRouter()

todo_list = []

@todo_router.post("/todo")
async def add_todo(todo: dict) -> dict:
	todo_list.append(todo)
	return{
    	"message": "Todo added successfully."
    }
    
@todo_router.get("/todo")
async def retrieve_todos() -> dict:
	return{
    	"todos": todo_list
    }
    

첫 번째 라우트는 todo_list에 todo를 추가하는 POST 메소드이고, 두 번째 라우트는 모든 todo 아이템을 todo_list에서 조회하는 GET 메소드이다.

APIRouter 클래스는 FastAPI 클래스와 동일한 방식으로 작동한다.
하지만 uvicorn은 APIRouter() 인스턴스를 사용해서 애플리케이션을 실행할 수 없다.
APIRouter 클래스를 사용해 정의한 라우트를 FastAPI() 인스턴스에 추가해야 외부에서 접근 가능하다.

todo 라우트를 외부로 공개하기 전에 include_router() 메소드를 사용하여 todo_router를 FastAPI() 인스턴스에 추가해보자.

from fastapi import FastAPI, APIRouter
from todo import todo_router

app = FastAPI()

@app.get("/")
async def welcome() -> dict:
    return{
        "message": "Hello World"
    }

app.include_router(todo_router)

먼저 api.py 파일에 todo.py 파일에서 생성한 todo_router를 import한다.

todo_router를 애플리케이션에 추가하려면 FastAPI() 인스턴스의 include_router() 메소드를 사용해야 한다.

이제 터미널에서 애플리케이션 실행:

uvicorn api:app --port 8000 --reload

애플리케이션이 실행되면서 애플리케이션 로그가 실시간으로 표시된다.

curl로 GET 요청을 전송해 애플리케이션이 제대로 실행되는지 테스트해보자.

curl http://127.0.0.1:8000/

응답이 콘솔에 뜬다.

todo 라우트가 제대로 실행되는지 확인:

curl -X 'GET' 'http://127.0.0.1:8000/todo' -H 'accept: application/json'

응답이 콘솔에 뜬다.

todo route가 실행된다면 POST 요청을 전송해서 아이템을 todo_list에 추가해보자.

curl -X 'POST' \
'http://127.0.0.1:8000/todo' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{ \
"id": 1, \
"item": "First Todo is to finish this book!" \
}'

실행하면 다음과 같은 응답이 콘솔에 표시된다.

{
	"message": "Todo added successfully/"
}

pydantic 모델을 사용한 요청 바디 검증

FastAPI에서는 정의된 데이터만 전송되도록 요청 바디를 검증할 수 있다.
요청 데이터가 적절한지 확인하고 악의적인 공격의 위험을 줄여주기 때문에 매우 중요한 기능이다.

FastAPI에서 모델은 데이터가 어떻게 전달되고 처리되어야 하는지를 정의하는 구조화된 클래스이다. 모델은 pydantic의 BaseModel 클래스의 하위 클래스로 생성된다.

pydantic: 파이썬의 type annotation을 사용해서 데이터를 검증하는 파이썬 라이브러리이다.
모델은 요청 바디 객체와 요청 응답 객체의 유형에 관한 힌트를 제공한다.

예제 모델:

from pydantic import BaseModel

class PacktBook(BaseModel):
	id: int
    Name: str
    Publishers: str
    Isbn: str

PacktBook 모델은 pydantic의 BaseModel 클래스의 하위 클래스로 정의되며 네 개의 필드만 갖는다.

앞선 todo 애플리케이션에서는 아이템을 todo_list에 추가하는 라우트를 정의했으며 이 라우트는 다음과 같이 요청 바디를 dictionary로 받는다.

async def add_todo(todo: dict) -> dict:
	...

POST 요청 예제에서는 다음과 같이 데이터를 보낸다:

{
	"id": id,
    "item": item
}

하지만 빈 dictionary를 보내도 아무런 오류가 발생하지 않는다. 이 예제와 다른 형태의 요청 바디를 보낼 수도 있다..
필요한 요청 바디 구조를 모델로 만들어서 요청 바디의 유형(type)에 할당하면 모델에 정의된 데이터 필드만 처리한다.

이전 예에서 사용한 데이터만 요청 바디의 필드에 포함시키려면 model.py라는 파일을 새로 만들고 다음 코드를 추가하면 된다.

from pydantic import BaseModel

class Todo(BaseModel):
	id: int
    item: str

두 개의 필드만 허용하는 pydantic 모델을 만든다.

  • int인 id
  • str인 item

이 모델을 POST 라우트에 사용:
todo.py 파일에 모델을 import해 요청 바디의 변수 유형을 dict에서 Todo로 변경한다.

빈 dictionary를 요청 바디로 보내서 모델이 제대로 검증되는지 확인:

curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{}'

모델과 일치하는 데이터 보내기:

curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"id": 2, "item": "Validation models help with input types"}'

정상적인 응답을 받게 된다.

중첩 모델

pydantic 모델은 다음과 같이 중첩해서 정의할 수 있다.

class Item(BaseModel):
	item: str
    status: str
    
class Todo(BaseModel):
	id: int
    item: item

경로 매개변수와 쿼리 매개변수

경로 매개변수

  • 리소스를 식별하기 위해 API 라우팅에 사용된다. 식별자 역할을 하며 웹 애플리케이션이 추가 처리를 할 수 있도록 연결 고리가 되기도 한다.

앞서 todo를 추가하거나 모든 todo_list를 추출하는 라우트를 만들었다.
하나의 todo 작업만 추출하는 새로운 라우트를 만든다. todo의 ID를 경로 매개변수에 추가한다.

todo.py에 라우트 추가:

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int) -> dict:
    for todo in todo_list:
        if todo.id == todo.id:
            return{
                "todo": todo
            }
    return{
        "message": "Todo with supplied ID doesn't exist."
    }

{todo_id}가 경로 매개변수이다. 이 매개변수를 통해 애플리케이션이 지정한 ID와 일치하는 todo 작업을 반환할 수 있다.

curl -X GET http://127.0.0.1:8000/todo/1 -H "accept: application/json"

이 요청에서 1이 경로 매개변수에 해당한다.

명령을 실행하면 ID가 1인 todo가 반환된다.

Path라는 클래스가 추가된다.
Path는 FastAPI가 제공하는 클래스로, 라우트 함수에 있는 다른 인수와 경로 매개변수를 구분하는 역할을 한다.
Path 클래스는 Swagger와 ReDoc 등으로 OpenAPI 기반 문서를 자동 생성할 때 라우트 관련 정보를 함께 문서화하도록 돕는다.

라우트 정의를 다음과 같이 수정:

from fastapi import APIRouter, Path
from model import Todo

todo_router = APIRouter()

todo_list = []

@todo_router.post("/todo")
async def add_todo(todo: Todo) -> dict:
    todo_list.append(todo)
    return{
        "message": "Todo added successfully"
    }

@todo_router.get("/todo")
async def retireve_todos() -> dict:
    return{
        "todos": todo_list
    }

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to retrieve.")) -> dict:
    for todo in todo_list:
        if todo.id == todo.id:
            return{
                "todo": todo
            }
    return{
        "message": "Todo with supplied ID doesn't exist."
    }

Path(..., kwargs)

  • Path 클래스는 첫 인수로 None 또는 ...을 받을 수 있다. 첫 번째 인수가 ...이면 경로 매개변수를 반드시 지정해야 한다.
  • 또한 경로 매개변수가 숫자이면 수치 검증을 위한 인수를 지정할 수 있다. 예를 들어 gt(greater than, ~보다 큰), le(less than, ~보다 작은)와 같은 검증 기호를 사용할 수 있다. 이를 통해 경로 매개변수에서 사용된 값이 특정 범위에 있는 숫자인지 검증 가능하다.

쿼리 매개변수

선택 사항이며 보통 URL에서 ? 뒤에 온다. 제공된 쿼리를 기반으로 특정한 값을 반환하거나 요청을 필터링할 때 사용된다.

쿼리는 라우트 처리기의 인수로 사용되지만 경로 매개변수와 다른 형태로 정의된다.

FastAPI Query 클래스의 인스턴스를 만들어서 라우트 처리기의 인수로 쿼리를 정의할 수 있다:

async query_route(query: str = Query(None):
	return query

요청 바디

요청 바디는 POST와 UPDATE 등 라우팅 메서드를 사용해 API로 전달되는 데이터이다.

POST 메소드는 새로운 데이터를 서버에 추가할 때 사용되고 UPDATE 메소드는 서버상에 있는 기존 데이터를 변경할 때 사용된다.

curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{ "id": 2, "item": "Validation model help with input types" }'

이 요청에서 요청 바디:

{ "id": 2, "item": "Validation model help with input types" }

FastAPI는 추가 검증을 할 수 있는 Body 클래스를 제공한다.

FastAPI 자동 문서화

모델의 JSON 스키마 정의를 생성하고 라우트, 요청 바디의 유형, 경로 및 쿼리 매개변수, 응답 모델 등을 자동으로 문서화한다. 문서는 두 가지 유형으로 제공된다.

  • Swagger
  • ReDoc

Swagger

  • 인터렉티브 문서(사용자가 실행할 수 있는 문서)를 제공하여 API를 테스트할 수 있도록 돕는다. swagger 문서를 보려면 애플리케이션의 주소의 끝에 /docs를 붙이면 된다.

ReDoc

모델, 라우트, API에 관한 정보를 더 직관적이고 상세하게 전달한다. 애플리케이션 주소의 끝에 /redoc을 추가하면 ReDoc 문서를 볼 수 있다.

JSON 스키마를 올바르게 생성하기 위해 사용자가 입력해야 할 데이터의 샘플을 설정할 수 있다. 샘플 데이터는 모델 클래스 안에 Config 클래스로 정의하면 된다.

class Todo(BaseModel_:
	id: int
    item: str
    
    class Config:
    	schema_extra = {
        	"example": {
            	"id": 1,
                "item": "Example Schema!"
            }
        }

문서화 페이지를 새로고침 하면 추가한 샘플이 오른쪽 패널에 표시된다.

사용자는 이 문서를 기반으로 API에 요청을 보내거나 애플리케이션을 테스트할 수 있다. 이렇게 업데이트된 문서는 그냥 문서로 끝나는 것이 아니라 API 사용법을 알려주는 안내서 역할을 한다.

간단한 CRUD 애플리케이션 개발

  1. UPDATE 라우트의 요청 바디용 모델을 model.py에 추가한다.
class TodoItem(BaseModel):
    item: str

    class Config:
        schema_extra = {
            "example": {
                "item": "Read the next chapter of the book."
            }
        }
  1. todo를 변경하기 위한 라우트를 todo.py에 추가한다.
@todo_router.put("/todo/{todo_id}")
async def update_todo(todo_data: TodoItem, todo_id: int = Path(..., title="The ID of the todo to be updated.")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            todo.item = todo_data.item
            return {
                "message": "Todo updated successfully."
            }
    return{
        "message": "Todo with supplied ID doesn't exist."
    }
  1. 새로 추가한 라우트를 테스트. 먼저 신규 item을 추가
curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{ "id": 3, "item": "Example Schema!" }'
  1. PUT 요청을 보내서 추가한 아이템을 변경
curl -X 'PUT' 'http://127.0.0.1:8000/todo/3' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{ "item": "Read the next chapter of the book" }'
  1. 실제로 아이템이 변경되었는지 확인
curl -X 'GET' 'http://127.0.0.1:8000/todo/3' -H 'accept: application/json'

todo 아이템이 성공적으로 변경된 것을 확인할 수 있다.

  1. todo.py에 삭제를 위한 DELETE 라우트를 추가
@todo_router.delete("/todo/{todo_id}")
async def delete_single_todo(todo_id: int) -> dict:
    for index in range(len(todo_list)):
        todo = todo_list[index]
        if todo.id == todo_id:
            todo_list.pop(index)
            return{
                "message": "Todo deleted successfully."
            }
    return{
        "message": "Todo with supplied ID doesn't exist."
    }

@todo_router.delete("/todo")
async def delete_all_todo() -> dict:
    todo_list.clear()
    return{
        "message": "Todos deleted successfully."
    }
  1. 신규 todo 추가
curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{ "id": 4, "item": "Example Schema!"}'
  1. 이제 추가한 todo를 삭제
curl -X 'DELETE' 'http://127.0.0.1:8000/todo/4' -H 'accept: application/json'
  1. GET 요청을 사용해 삭제한 아이템이 추출되는지 확인
curl -X 'GET' 'http://127.0.0.1:8000/todo/4' -H 'accept: application/json'

명령을 실행하면 지정한 ID의 todo가 존재하지 않는다는 응답을 확인할 수 있다.

profile
취준 진입

0개의 댓글

관련 채용 정보