국문과 유목민

[FastAPI] FastAPI 기초 지식 본문

IT 견문록/ProductServing

[FastAPI] FastAPI 기초 지식

논곰 2022. 5. 25. 00:32

FastAPI란

 FastAPI는 최근 떠오르는 Python Web Framework이다. Flask와 Django가 유명하기는 하지만 2020년부터는 FastAPI가 떠오르고 있다. Node.js, go와 대등한 성능을 보여주지만, Flask와 비슷한 구조이기 때문에 Micro Service에 적합하다고 한다. 또한 Swagger자동 생성해주고, Pydantic을 이용한 Serialization이 쉽다. (ML서비스 만들 때는 FastAPI가 Django보다 좋다고 한다.)

장점

  • Flask보다 간결한 Router문법을 가지고 있다.
  • Asynchronous(비동기)지원이 가능하다.
  • Built-in API Documentation(Swagger): 웹서버를 띄우기만 하면 Document 생성이 가능하다.
  • Pydantic을 이용한 Serialization 및 Validation이 가능하다는 장점이 있다. (추가 이해 필요)Py

Pydantic (추가 이해 필요)

Pydantic은 Data Validation /Settings Management 라이브러리로, FastAPI에서 Class 사용할 때 주로 보인다. Type Hint를 런타임에서 강제해 안전하게 데이터 핸들링이 가능하다는 장점이 있다. 파이썬 기본 타입 부터 List, Dict, Tuple에 대한 Validation을 지원한다. 기존 Validation 라이브러리보다 빠르고, Config를 효과적으로 관리하도록 도와준다. 머신러닝 Feature Data Validation으로도 활용 가능하다. Pydantic의 두 가지 기능으로는 ValidationConfig관리가 있다. (해당 부분은 추후 설명)

FastAPI 기본

Path Parameter, Query Parameter

웹에서는 GET Method를 사용해 데이터를 전송할 수 있다. 이렇게 데이터를 가져올 수 있는 방법으로 Path Parameter방식과 Query Parameter 방법이 있다. 만약 id가 202인 사용자 정보를 가져오고 싶을 때, 두 Parameter의 방법은 다음과 같이 차이가 있다.

  • Path Parameter: /users/202 서버에 직접 202라는 값을 전달하고 변수로 사용한다.
  • Query Parameter:  /users?id=202 API 뒤에 입력 데이터를 함께 제공하는 방식으로 사용한다. Query String이라고 하며, Key와 Value 쌍으로 '&'로 연결해 여러 데이터를 넘길 수 있다. 

언제 어떤 방식을 사용해야 할 지는 상황마다 다르지만 Resource를 식별해야 하는 경우 Path Parameter가 더 적합하고,  정렬 및 필터링을 해야 하는 경우 Query Parameter가 더 적합하다고 한다.

Path Parameter

- FastAPI는 데코레이터로 GET, POST를 표시한다. @app.get, @app.post

- GET Method의 인자로 있는 {user_id}가 함수의 값으로 주입된다.

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)

Path Parameter 사용방법

Query Parameter

- Query Parameter를 적용하는 방법은 URL 뒤에 ?를 붙이고 Key=Value 형태로 값을 주면 된다. Query String을 여러 개 주고 싶을 때는 &로 연결하면 된다.

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): # skip과 limit 변수 이용
    return fake_items_db[skip: skip + limit]

if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)

Query Paramter 사용방법

Optional Parameter

특정 파라미터를 Optional(선택적)으로 하고 싶은 경우에 사용하는 파라미터이다. 

  • 값이 들어있으면 사용하고, 없으면 사용하지 않게끔 분기문을 주면 된다.
from typing import Optional  # Typing모듈의 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):  # Optional임을 명시하는 게 좋다.
    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)

Optional Parameter 사용방법

Request Body, Response Body

Request Body는 클라이언트에서 API에 데이터를 보낼 때 사용하는 것이다. 반대로 Response Body는 API에서 클라이언트에 Response를 보낼 때 사용하는 것이다. 

Request Body와 Response Body

Request Body

Request Body에 데이터를 넣어 보내고 싶다면 POST Method를 활용하면 된다. 단, Request Body에 데이터가 항상 포함되어야 하는 것은 아니다. (참고! GET Method는 URL, Request Header로 데이터를 전달한다)

Body의 데이터를 설명하는 컨텐츠 타입이란 Header 필드가 존재하고, 어떤 데이터 타입인지 명시해야 한다. 대표적인 컨텐츠 타입으로는 다음과 같은 것이 있다. 

  • application/x-www-form-urlencoded : BODY에 Key, Value 사용. & 구분자 사용 
  • text/plain : 단순 txt 파일
  • multipartform-data : 데이터를 바이너리 데이터로 전송
from typing import Optional
from fastapi import FastAPI
import uvicorn
from pydantic import BaseModel

# 1.Pydantic으로 Request Body 데이터 정의
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

app = FastAPI()

# 2.Type inting으로 위에서 생성한 Item Class 주입
# 3.Request Body 데이터를 Validation
@app.post("/items/")
def create_item(item: Item):
    return item

=
if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)
  • curl 등으로 POST를 실행할 수 있지만, localhost:8000/docs를 활용하면, Schemas에서 Pydantic으로 정의한 내용도 볼 수 있고 실행도 가능하다. 

docs 내 Schemas
docs 내에서 기본 Execute를 해볼 수 있다.

Response Body

POST Method를 사용하는 것은 동일하지만, Decorator의 response_model 인자로 Response Body 주입이 가능하다. Request Body와 Response Body를 같이 사용하면, Request Body로 받았던 내용들 중에서도 일부만 Response Body로 반환할 수도 있다. (이를 정보은닉이나 보안 등에 사용할 수 있어 보인다)

from typing import Optional
from fastapi import FastAPI
import uvicorn

from pydantic import BaseModel

# Request Body로 들어갈 부분
class ItemIn(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

# Response Body로 들어갈 부분
class ItemOut(BaseModel):
    name: str
    price: float
    tax: Optional[float] = None

app = FastAPI()

@app.post("/items/", response_model=ItemOut)
def create_item(item: ItemIn):
    return item
if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)

Response Body 결과: Request Body와 Response Body의 결과가 다름

> BaseModel의 역할에 대해서 더 알아보기

Form

Form(입력) 형태로 데이터를 받고 싶은 경우 사용한다. Form을 사용하려면 python-multipart를 설치해야 한다. (추가적으로 프론트도 간단히 만들기 위해 Jnja2도 같이 설치한다)

pip install python-multipart
pip install Jinja2

- Form 클래스를 사용하면 Request의 Form Data에서 값을 가져와야 한다. 

- Form(...) 에서 ...의 의미: Python ellipsis라고 하며, Required(꼭 필수 요소임)를 의미한다.

from fastapi import FastAPI, Form, Request
from fastapi.templating import Jinja2Templates

import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory='./')

@app.get("/login/") # Request 객체로 Request를 받는다.
def get_login_form(request: Request):	# login_form.html 파일이 필요하다
    return templates.TemplateResponse('login_form.html', context={'request': request})

@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)
  • localhost:8000/login/ 으로 이동하면, login으로 접근하면서 GET Method가 요청된다
  • FastAPI 웹 서버를 실행한 후 Swagger(localhost:8000/docs)로 이동하면 Required를 볼 수도 있다

Form 사용법
swagger

File

File을 업로드 하고 싶은 경우에 사용하고, Form과 같이 python-multipart를 설치해야 한다. (위에서 설치)

  • fastapi 라이브러리에서 UploadFile을 import해야 한다.
  • HTML 단(main)에서 action으로 files나 uploadfiles로 넘길 수 있다.
  • 파일을 Bytes로 표현(create_files)하고, 여러 파일(create_upload_files)은 LIST에 설정
from typing import List

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

import uvicorn

app = FastAPI()

@app.post("/files/")
def create_files(files: List[bytes] = File(...)):
    return {"file_sizes": [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)

'IT 견문록 > ProductServing' 카테고리의 다른 글

[Backend] 백엔드 기초 지식  (0) 2022.05.24
[Streamlit] Streamlit 명령어  (0) 2022.05.18
[Ipywidget] Ipywidget 명령어  (0) 2022.05.18