ML/AI/SW Developer

FastAPI - FastAPI 기본지식, Pydantic

0. 강의 소개

이번 강의에서는 백엔드 프로그래밍에 사용되는 Fast API에 대해 다룰 예정입니다.

1. FastAPI 기본지식

1.0 Path Parameter? Query Parameter?

  • 웹에서 GET Method를 사용해 데이터를 전송할 수 있음
  • E.g. ID가 402인 사용자 정보를 가져오고 싶은 경우 방식
    • Path Parameter
      • /users/402
      • 서버에 402라는 값을 전달하고 변수로 사용
    • Query Parameter
      • /users?id=402
      • Query String
      • API 뒤에 입력 데이터를 함께 제공하는 방식으로 사용
      • Query String은 Key, Value의 쌍으로 이루어지며 &로 연결해 여러 데이터를 넘길 수 있음
  • 언제 어떤 방식을 사용해야 할까?
    • 상황마다 다르다!!
    • Resource를 식별해야 하는 경우 : Path Parameter가 더 적합
    • 정렬, 필터링을 해야 하는 경우 : Query Parameter가 더 적합
  • 차이점!!
    • Path Parameter : 저 경로에 존재하는 내용이 없으므로 404 Error 발생
    • Query Parameter : 데이터가 없는 경우 빈 리스트가 나옴 => 추가로 Error Handling이 필요

1.1 Path Parameter

  • GET Method : 정보를 READ하기 위해 사용
  • 유저 정보에 접근하는 API 만들기

      from fastapi import FastAPI
      import uvicorn
    
      app = FastAPI()
    
      @app.get("/users/{user_id}")
      def get_user(user_id):
          return {"user_id": user_id}
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

1.2 Query Parameter

  • 아이템 리스트를 반환하는 API 만들기

      from fastapi import FastAPI
      import uvicorn
    
      app = FastAPI()
      fake_items_db = [{"item_name":"Foo"}, {"item_name":"Bar"}, {"item_name":"Baz"}]
    
      @app.get("/items/")
      def read_item(skip: int=0, limit: int=10):
          return fake_items_db[skip: skip + limit]
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

1.3 Optional Parameter

  • 있어도 되고 없어도 되는 변수 사용해보기

      from typing import Optional
      from fastapi import FastAPI
      import uvicorn
    
      app = FastAPI()
      fake_items_db = [{"item_name":"Foo"}, {"item_name":"Bar"}, {"item_name":"Baz"}]
    
      @app.get("/items/{item_id}")
      def read_item(item_id: str, q: Optional[str]=None):
          if q:
              return {"item_id": item_id, "q": q}
          return {"item_id": item_id}
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

1.4 Request Body

  • 클라이언트에서 API에 데이터를 보낼 때, Request Body를 사용함
    • 클라이언트 => API : Request Body
    • API의 Response => 클라이언트 : Response Body
  • Request Body에 데이터가 항상 포함되어야 하는 것은 아님
  • Request Body에 데이터를 보내고 싶다면 POST Method를 사용
    • (참고) GET Method는 URL, Request Header로 데이터 전달
  • POST Method는 Request Body에 데이터를 넣어 보냄
  • Body의 데이터를 설명하는 Content-Type이란 Header 필드가 존재하고, 어떤 데이터 타입인지 명시해야 함
  • 대표적인 컨텐츠 타입
    • application/x-www-form-urlencoded : BODY에 Key, Value 사용. & 구분자 사용
    • text/plain : 단순 txt 파일
    • multipartform-data : 데이터를 바이너리 데이터로 전송
  • POST 요청으로 item 생성해보기

      from typing import Optional
      from fastapi import FastAPI
      import uvicorn
    
      from pydantic import BaseModel
    
      class Item(BaseModel):
          name: str
          description: Optional[str] = None
          price: float
          tax: Optional[float] = None
    
      app = FastAPI()
    
      @app.post("/items/")
      def creat_item(item: Item):
          return item
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

1.5 Response Body

  • API의 Response => 클라이언트 : Response Body
  • Decorator의 response_model 인자로 주입 가능
  • 역할
    • Output Data를 해당 정의에 맞게 변형
    • 데이터 Validation
    • Response에 대한 Json Schema 추가
    • 자동으로 문서화
  • 확인해보자!!

      from typing import Optional
      from fastapi import FastAPI
      import uvicorn
    
      from pydantic import BaseModel
    
      class ItemIn(BaseModel):
          name: str
          description: Optional[str] = None
          price: float
          tax: Optional[float] = None
    
      class ItemOut(BaseModel):
          name: str
          price: float
          tax: Optional[float] = None
    
      app = FastAPI()
    
      @app.post("/items/", response_model=ItemOut)
      def creat_item(item: ItemIn):
          return item
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

1.6 Form, File

  • python-multipart를 설치해야 함
    • 프론트도 간단히 만들기 위해 Jinja2 설치
      pip install python-multipart
      pip install Jinja2
    
  • 사용해보기

      from fastapi import FastAPI, Form
      from fastapi.templating import Jinja2Templates
      import uvicorn
    
      app = FastAPI()
      templates = Jinja2Templates(directory='./')
    
      # 주의!! url으로 접근 get method를 요청하기 때문에 없으면 오류가 난다
      @app.get("/login/")
      def get_login_form(request: Request):
          return templates.TemplateResponse('login_form.html', context={'request': request})
    
      # ...은 Python ellipsis : Required(꼭 필수 요소)를 의미
      @app.post("/login/")
      def login(username: str = Form(...), passwrod: str = Form(...)):
          return {"username": username}
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    
  • 파일 업로드를 위한 기능 만들어 보기

      from typing import List
    
      from fastapi import FastAPI, File, UploadFile
      from fastapi.response import HTMLResponse
    
      import uvicorn
    
      app = FastAPI()
    
      @app.post("files/")
      def create_files(files: List[bytes] = File(...)):
          return {'file_size': [len(file) for file in files]}
    
      @app.post("uploadfiles/")
      def create_upload_files(files: List[UploadFile] = File(...)):
          return {'filenames': [file.filename for file in files]}
    
      # “/”로 접근할 때 보여줄 HTML 코드
      @app.get("/")
      def main():
          content = """
      <body>
      <form action="/files/" enctype="multipart/form-data" method="post">
      <input name="files" type="file" multiple>
      <input type="submit">
      </form>
      <form action="/uploadfiles/" enctype="multipart/form-data" method="post">
      <input name="files" type="file" multiple>
      <input type="submit">
      </form>
      </body>
          """
    
          return HTMLResponse(content=content)
    
      if __name__ == '__main__':
          uvicorn.run(app, host="0.0.0.0", port=8000)
    

2. Pydantic

2.1 Pydantic

  • FastAPI에서 Class 사용할 때 보이던 Pydantic
  • Data Validation / Settings Management 라이브러리
  • Type Hint를 런타임에서 강제해 안전하게 데이터 핸들링
  • 파이썬 기본 타입(String, Int 등) + List, Dict, Tuple에 대한 Validation 지원
  • 기존 Validation 라이브러리보다 빠름 Benchmark
  • Config를 효과적으로 관리하도록 도와줌
  • 머신러닝 Feature Data Validation으로도 활용 가능

2.2 Pydantic Validation

  • Machine Learning Model Input Validation
    • Online Serving에서 Input 데이터를 Validation하는 Case
  • Validation Check Logic
    • 조건 1: 올바른 url을 입력 받음 (url)
    • 조건 2: 1-10 사이의 정수 입력 받음 (rate)
    • 조건 3: 올바른 폴더 이름을 입력 받음(target_dir)
  • 사용할 수 있는 방법
    • 1) 일반 Python Class를 활용한 Input Definition 및 Validation
      • Python Class로 Input Definition 및 Validation => 의미 없는 코드가 많아짐
      • 복잡한 검증 로직엔 Class Method가 복잡해지기 쉬움
      • Exception Handling을 어떻게 할지 등 커스텀하게 제어할 수 있는 있지만 메인 로직(Input을 받아서 Inference를 수행하는)에 집중하기 어려워짐
    • 2) Dataclass를(python 3.7 이상 필요) 활용한 Input Definition 및 Validation
      • 인스턴스 생성 시점에서 Validation을 수행하기 쉬움
      • 여전히 Validation 로직들을 직접 작성해야 함
      • Validation 로직을 따로 작성하지 않으면, 런타임에서 type checking을 지원하지 않음
    • 3) Pydantic을 활용한 Input Definition 및 Validation
      • 훨씬 간결해진 코드 (6라인)(vs 52라인 Python Class, vs 50라인 dataclass)
      • 주로 쓰이는 타입들(http url, db url, enum 등)에 대한 Validation이 만들어져 있음
      • 런타임에서 Type Hint에 따라서 Validation Error 발생
      • Custom Type에 대한 Validation도 쉽게 사용 가능
  • 참고

2.3 Pydantic Config

  • Pydantic은 Config을 체계적으로 관리할 방법을 제공
  • 기존에 다른 라이브러리들은 어떻게 Config를 설정하고 있을까?
    • 애플리케이션은 종종 설정을 상수로 코드에 저장함
    • 이것은 Twelve-Factor를 위반
      • Twelve-Factor는 설정을 코드에서 엄격하게 분리하는 것을 요구함
      • Twelve-Factor App은 설정을 환경 변수(envvars나 env라고도 불림)에 저장함
      • 환경 변수는 코드 변경 없이 쉽게 배포 때마다 쉽게 변경할 수 있음
      • 참고
  • 관리하기!
    • 1) .ini, .yaml 파일 등으로 config 설정하기
    • 2) flask-style config.py
    • 3) pydantic base settings

Reference

  • AI boot camp 2기 서빙 강의