FastAPI 이해하기

생각하는 마리오네트·2023년 10월 1일
0

삽지니어링

목록 보기
5/10
post-thumbnail

FastAPI를 왜 공부하는가?

FastAPI를 통하여 비즈니스 로직을 실제로 구현하고, 분석결과물을 다양한 방식으로 공유하기 위함, 또한 간단하게 Streamlit과 Tableau를 함께 활용하여 재밌는 프로젝트를 할 수 있겠다는 생각을 하게되었다.

또한 다른것과 달리 FastAPI장점으로 직관적이라는 부분과 스웨거를 따로 작성할 필요가 없어서 개인프로젝트나 POC단계에서 사용하기 적합한것같다.

기본 코드 구성

기본적으로 rest기반으로 CRUD를 가지고 있다.

Create(데이터 생성) : Post
Read(데이터 조회) : Get
Update(데이터 수정) : Put
Delete(데이터 삭제) : DELETE

main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
	return {"message":"Hello World"}
 

보통 main파일 내에 다음과 같이 작성하는데 @app.get("/")을 통해서 endPoint를 지정해주게 된다. 해당 코드의 경우 추가 url이 없다고 보면된다.

또한 async는 로직을 병렬로 실행해주는 것으로 비동기처리를 해주는것이다.
병렬처리가 필요하지 않다면 async를 제외하고 def root(): 로 시작해도 된다.

run server

uvicorn main:app -reload
  • uvicorn : uvicorn을 통해서 http에서 로딩을 시킨다
  • app : 실행시킬 파일에 생성된 객체로 app = FastAPI()로 사전에 정의된 객체이다.
  • --reload : 코드에 변경사항이 생길때 서버를 재시작 하는 옵션으로 필요에따라 사용여부를 결정하면된다.

실행화면

실행하면 다음과같이 창이 뜨는것을 볼 수 있다.

여기서 FastAPI의 장점인 자동으로 스웨거를 짜주는 부분이 있는데 url뒤에 docs를 붙여주면된다.

이렇게 끝에 docs를 붙여주면 아래와같이 자동으로 생성되는것을 볼 수 있다.

참고

@app에서 사용한 @는 데코레이터라고 부른다.

파이썬에서 함수를 수정하지 않은 상태에서 추가 기능을 구현할 경우에 @를 붙여서 활용할 수 있는데 함수를 받아서 어떠한 로직을 처리하는것이다. 

@app.get("/")의 경우에는 함수가 경로 "/" 에 해당하는 get(조회) 동작을 하라는 것이다.

즉 데코레이터 아래에 있는
async def root():
	return {"message" : "Hello World"}
    
함수를 실행하는것이다.(async를 붙이지 않으면 비동기 아닌 상태로 사용이가능하다)

Path Parameter

FastAPI에서 파이썬 기본 문법으로 매개변수를 경로에 삽입하여 사용할 수 있다.

from fastapi import FastAPI

app = FastAPI()

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

위의 코드를 main.py에 작성하고 실행후에 다음과 같이 엔드포인트를 구성해보면된다.

http://127.0.0.1:8000/items/안녕

이렇게 url을 치고 입력하면 아래와 같은 return값이 생긴다

{"item_id":안녕}

또한 FastAPI에서는 파이썬 표준 타입 annotations을 통해서 Path Parameter의 타입을 선언할 수 있는데 아래와 같이 item_id의 값을 정수형으로 명시해보자

from fastapi import FastAPI

app = FastAPI()

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

해당 코드를 실행할경우 정수형만이 호출을 할 수 있다. 만약에 그렇지않다면 아래와 같은 에러가 생긴다.

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "안녕",
            "url": "https://errors.pydantic.dev/2.4/v/int_parsing"
        }
    ]
}

따라서 annotations을 통해서 특정 타입의 인자를 받게 만들 수 있다.

사전정의 값

만약에 path파라미터를 받는 API가 있고, 미리 파라미터의 값을 정의하기를 원한다면 파이썬문법 Enum을 사용할 수 있다.

  1. Enum을 import 하고 str타입으로 지정해주고 Enum을 상속하는 서브 클래스를 만든다. -> str을 같이 상속함으로서 API문서는 값이 문자열 형태가 되야함을알게 되므로 제대로 렌더링 할 수 있게 된다.
from enum import Enum
from fastapi import FastAPI

class ModelName(str, Enum):
	alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"
    
 app = FastAPI()
 
 @app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    if model_name is ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}

    if model_name.value == "lenet":
        return {"model_name": model_name, "message": "LeCNN all the images"}

    return {"model_name": model_name, "message": "Have some residuals"}
        

위와 같이 함수를 구성했을때 "http://127.0.0.1:8000/models/alexnet"를 입력하게 되면 아래와 같이 결과가 떨어진다.

{
    "model_name": "alexnet",
    "message": "Deep Learning FTW!"
}

Path convertor

path파라미터에 해당 파라미터가 경로 즉, path임을 알려줄 수 있는 방법이있다.
만약에 이러한 과정없이 "/storage/my/myfile.txt" 같은 url을 입력한다면 에러가 난다. 따라서 path임을 지정해줘야한다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/files/{file_path:path}")
async def read_file(file_path : str):
	return {"file_path":file_path}
  • 먼저 {file_path :path} 처럼 file_path파라미터를 path로 지정을 해준 모습이다. 이렇게 하면 해당 변수를 path로 인식하게된다.
  • read_file에 들어가는 file_path를 str타입으로 받아서 경로가 제대로 들어갈수있게 타입 지정을 해준다.

Query Parameter

지금까지 path파라미터를 활용해서 조회를 해봤다. path파라미터가 아닌 값을 함수의 파라미터로 선언하게 된다면, FastAPI는 자동으로 Query파라미터로 인식하게된다.

Query는 URL에서 ? 이후에 key-value쌍으로 이루어지고 &(엔드)로 분리가 된다.

코드 예시와 URL예시를 살펴보자


from fastapi import FastAPI

app = FastAPI()

fake_items_db = [{"item_name":"Foo"}, {"item_name":"Bar"}, {"item_name":"Baz"}]

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

예를들어서 아래와 같은 URL이 있다고 하자.

http://127.0.0.1:8000/items/?skip=1&limit=1

query파라미터는 아래와 같다

  • skip : value는 0
  • limit : value는 0
    (URL의 일부이기 때문에 타입은 자연스럽게 STRING이 된다)

만약에 파이썬 타입을 선언할 경우 해당타입으로 변환이된다.
(위와 같은 경우 query파라미터 skip과 limit에 대한 value를 int로 타입지정을 한것이다.)

Default

query파라미터는 경로에서 고정된 부분이 아니고, 선택적일 수 있기 때문에 기본값을 가질 수 있다. 위에서 보여준 코드를 예를 들면 skip = 0, limit = 10 으로 기본값을 가지고 있다.

그렇기 때문에 아래와 같은 코드입력시

http://127.0.0.1:8000/items/

기본값으로 skip과 limit이 적용이 되어 아래와 같이 URL을 이동한것과 똑같다.

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

만약에 아래처럼 기본값의 쿼리값을 변경한다면 어떻게될까

http://127.0.0.1:8000/items/?skip=4
  • skip = 4
  • limit = 10 : default값으로 10이 지정되어 있기 때문

Optional Parameters

query값을 선택적으로 파라미터를 받을 수 있는 방법이 있다.

from typing import Union
from fastapi import FastAPI

app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

위의 함수의 경우 query파라미터는 q이며, optional한 파라미터입니다.
default값으로 None을 가지게 됩니다. 만약에 q의 값을 사용하기 위해서는
str타입으로 생성됩니다.

Query parameter type conversion

bool타입으로도 선언을 할 수 있으며, 다음과 같이 변환이 됩니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: str, q : str |None, short : bool = False):
	item = {"item_id":item_id}
    if q:
    	item.update({"q":q})
    if not short:
    	item.update(
        	{"description":"This is an amazing item that has a long description"}
        )
    return item

Multiple path and query parameters

한 API에서 다중으로 path파라미터와 query파라미터를 동시에 받을경우 FastAPI는 순서와 상관없이 이름으로 구분한다.

from typing import Union

from fastapi import FastAPI

app = FastAPI()

**@app.get("/users/{user_id}/items/{item_id}")**
async def read_user_item(
    **user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False**
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

Required query parameters

query파라미터를 선택적으로 받기 위해서는 default값을 None으로 설정하여 해결할 수 있었다. 하지만, query파라미터를 필수로 하기 위해서는 기본값을 선언해야한다.

from fastapi import FastAPI

app = FastAPI()

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

이렇게 만들게 되면 path파라미터인 item_id이후에 needy라는 query파라미터를 반드시 넣어주어야 한다 그렇지 않으면 에러가 뜬다.

정리

from typing import Union

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_user_item(
    **item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None**
):
    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
    return item

일부 query 파라미터는 필수, default값 혹은 optional하게 받을 수 있다.
위의 코드에서는 3가지의 query파라미터가 있따.

  • needy : 필수 파라미터
  • skip : default가 0인 int형 파라미터
  • limit : optional한 값이며 값이 있다면 int형이다.
profile
문제를해결하는도구로서의"데이터"

0개의 댓글