버튼 수집상

[책] FastAPI를 사용한 파이썬 웹 개발 본문

일상 - 책

[책] FastAPI를 사용한 파이썬 웹 개발

cocokaribou 2024. 2. 7. 16:28

책 읽으면서 메모 업데이트.

 

 

fastapi: 애플리케이션을 구축하기 위해 필요한 프레임워크
uvicorn: 애플리케이션을 실행하기 위한 비동기 asynchronous 방식 서버 게이트웨이 인터페이스 (ASGI)

이에 대해 ChatGPT에게 물어봄.

 

>> FastAPI의 핵심 기능 중 하나는 비동기 코드를 지원하는 것이며, 이를 효과적으로 처리하기 위해 Uvicorn과 같은 ASGI 서버를 사용하는 것이 일반적입니다. Uvicorn은 FastAPI 애플리케이션의 비동기 라우트 및 이벤트 핸들러를 처리하고 실행하는 데 특히 효과적입니다.


>> FastAPI는 비동기 프레임워크이며, 라우트 함수가 async def로 정의될 수 있습니다. 이로써 비동기 코드를 사용하여 I/O 바운드 작업을 효율적으로 처리할 수 있습니다. Uvicorn은 이러한 비동기 코드를 ASGI 인터페이스를 통해 서버에 적절하게 처리할 수 있게 도와줍니다.

 

Q. 그럼 fastapi에서 라우트를 비동기로 처리하고 애플리케이션을 gunicorn (동기식 서버) 으로 돌리면 어떻게 돼?

>> (생략)

>> Gunicorn 설치:

pip install gunicorn

>> Gunicorn을 사용하여 FastAPI 애플리케이션 실행:

gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app

>> 위 명령에서 -w는 worker의 수를 나타내며, -k는 사용할 worker 클래스를 지정합니다. -k uvicorn.workers.UvicornWorker 부분은 Gunicorn이 UvicornWorker를 사용하여 FastAPI 애플리케이션을 실행하도록 지정합니다.

>> 이제 Gunicorn이 FastAPI 애플리케이션을 WSGI 서버로 실행하고, 비동기 코드를 처리할 때는 Gunicorn 내부에서 UvicornWorker를 사용하여 처리합니다.

 


uvicorn 외의 ASGI 웹서버 옵션들

https://fastapi.tiangolo.com/deployment/manually/

 

Run a Server Manually - Uvicorn - FastAPI

FastAPI framework, high performance, easy to learn, fast to code, ready for production

fastapi.tiangolo.com

- Uvicorn

- Hypercorn

- Daphne

 

FastAPI()인스턴스를 사용하는 경우, 애플리케이션은 한 번에 여러 라우트를 처리할 수 없다.
uvicorn이 하나의 엔트리포인트만 실행할 수 있기 때문이다.
그렇다면 여러 함수를 사용하는 연속적인 라우트 처리는 어떻게 할 수 있을까?

-> APIRouter 클래스 사용

 


메서드만 다르게 하면 api 패스를 동일하게 쓸 수도 있다!

@router.post("/todo")
async def add_todo(todo: dict) -> dict:
  #...

@router.get("/todo")
async def retrieve_todos() -> dict:
  # ...

경로 매개변수

@router.get("/todo/{todo_idx}")
async def get_single_todo(todo_idx: int) -> dict:

 

경로 매개변수 검증해주는 Path 클래스

from fastapi import Path

 

쿼리 매개변수 검증해주는 Query 클래스도 있다.

 

POST와 UPDATE 메서드에 데이터 전달하는 리퀘스트 바디

.. 를 검증해주는 Body 클래스도 있다.

 


pydantic BaseModel 클래스에 config 추가 가능
문서화할때 샘플 데이터 보여주기 용도.

from pydantic import BaseModel

class TodoItem(BaseModel) :
item: str

  class Config:
    schema_extra = {
      "example" : {
      "item": "Read the next chapter of the book"
    }
}

그런데 위 샘플대로 적으면 swagger 문서에서 예시 데이터가 뜨지 않는다.

아래처럼 적용해야한다.

from pydantic import BaseModel

class TodoItem(BaseModel) :
  item: str

  #테스트시 기입
  model_config={
    "json_schema_extra":{
    "examples":[
      {
        "item":"Read the next chapter of the book"
      }
    ]
  }
}

 

리턴 값을 검사해서 상태코드를 404 띄울 수도 있음

from fastapi import HTTPException, status

@router.get("/todo/{todo_idx}")
async def get_single_todo(todo_id: int) -> dict:
  for todo in todo_list:
    if todo.id == todo_id:
      return {
        "todo": todo
      }
  raise HTTPException(
    status_code=status.HTTP_404_NOT_FOUND,
    detail="Todo with given ID does not exist"
  )

 

pydantic 모델의 Optional 필드

from typing import Optional, List
from pydantic import BaseModel, EmailStr

class User(BaseModel):
  email: EmailStr
  password: str
  events: Optional[List[Event]]

이메일 값을 검증해주는 EmailStr 클래스는 email-validator 라는 패키지를 설치해야만 한다.

그리고 Optional 클래스는 pydantic 모델 안에서만 써야지, FastAPI 라우트의 쿼리 매개변수를 정의할 때 쓰는 게 아니었다ㅎ.

@router.get("/list", response_model=List[User])
async def get_list(search_keyword:Optional[str]) :
  # ...

위처럼 쓰면 search_keyword 쿼리 파라미터가 없을 때 서버 에러 난다..

아마 이에 대한 처리는 위에서 언급된 Query 클래스가 해주지 않을까 싶다.

아니면 그냥 디폴트값을 넣어두거나..

@router.get("/list", response_model=List[User])
async def get_list(search_keyword:str="") :
  # ...

로그인(signIn) 라우트 정의하기 (예시)

@router.post("/signIn")
async def sign_user_in(user: UserSignIn) -> dict:
  if user.email not in users:
    raise HTTPException(
      status_code=status.HTTP_404_NOT_FOUND,
      detail="User does not exist"
    )

  if users[user.email].password != user.password:
    raise HTTPException(
      status_code=status.HTTP_403_FORBIDDEN,
      detail="Wrong credentials passed"
    )
  return {
    "message" : "User signed in successfully."
  }
여기서는 설명을 위해 패스워드를 암호화하지 않고 일반 텍스트로 저장했지만 소프트웨어 개발 관점에서는 잘못된 방식이다. <CHAPTER6>에서 암호화를 사용한 패스워드 저장방식을 설명한다.

 

라우터 등록할 때 prefix 등록 가능

from route.users import user_router

app = FastAPI()

app.include_router(user_router, prefix="/user")



현재 절반 읽음.

728x90