카카오클라우드 AIaaS 교육/AIaaS를 위한 머신러닝&AI

[스나이퍼팩토리] AIaaS 마스터 클래스 9주차 DAY - FastAPI

mangji2 2025. 6. 3. 23:46

이번 주차에는 fastapi에 대해서 배웠다.

배웠던 내용을 간추려서 예제코드들을 실행해보고 결과를 보면서 정리해보도록 하겠다.


1. FastAPI 정의와 기본 예제

FastAPI란? 

fastapi란 python 웹 프레임워크로, 빠르게 API를 만들 수 있고, 자동 검증 + 문서화 + 비동기 처리까지 지원하는 초고속 웹 서버이다.

 

 

이제 fastapi를 이용한 간단한 기본 예제들을 활용해 보면서 이해를 해보겠다.

 

uvicorn이란?

여기서 fastapi를 실행시키려면 uvicorn이 필요한데 uvicorn은 ASGI를 지원하는 비동기 Python 웹 서버로 FastAPI,starlette,Django ASGI 앱을 실행할 수 있다.

쉽게 말하자면 FastAPI는 ASGI 앱인데 ASGI앱은 그 자체로 실행되는 게 아니고, ASGI 서버가 실행시켜줘야 하는데 그 서버가 Uvicorn인 것이다.

 

uvicorn main:app --reload

 

이런 식으로 서버를 실행할 수 있는데

main은 파일이름

app은 FastAPI 인스턴스 

--reload는 코드 변경 시 서버 자동 재시작하는 기능을 가지고 있다.

 

- FastAPI get 구성하고 실행

fastapi를 get 방식으로 http호출해서 실행시켜보는 예제를 해보았다.

from fastapi import FastAPI

app = FastAPI()

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

@app.get("/home")
def home():
    return {"message": "home"}

 

fastapi 인스턴스를 우선 생성해주고, 두 개의 path 경로를 지정해줘서 각각의 message값이 return되게 해주었다.

 

서버를 실행하면

 

이렇게 http 링크가 나오고 이 링크로 들어가면 return된 값이 출력된 걸 볼 수 있다.

 

 - 경로 매개 변수

경로 매개변수를 활용해서 get함수에게 값을 넘겨줄 수도 있다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/home/{name}")
def read_name(name: str):
    return {'name': name}

@app.get("/home_err/{name}")
def read_name_err(name: int):
    return {'name': name}

 

사용자가 이름을 입력하면 변수가 /home 경로 뒤에 붙어서 값을 받을 수 있다.

 


 

2. FastAPI post와 비동기 async 함수

http get vs post

http 통신을 제대로 사용하기 위해서는 둘의 차이를 아는 게 중요하다고 생각한다. 대충은 알긴 하지만 이번 기회에 확실하게 구분해서 알아보도록 하겠다.

 

우선 GET이란 

클라이언트에서 서버로 어떤 리소스로부터 정보를 요청하기 위해서 사용되는 메소드이다.

예를들면 게시판의 게시물을 조회할 때 쓸 수 있다. 

www.www.example.com/show?name1=value1&name2=value2

 

get을 통한 요청은 url 주소 끝에 파라미터로 포함되어 전송되며 이 부분을 쿼리 스트링이라고 한다.

url 끝에 ?를 붙이고 변수명1=값&변수명2=값 이런식으로 붙어서 전송되는 것을 알 수 있다.

get은 캐시가 가능하고 쿼리 스트링때문에 url에 노출이 되기때문에 중요한 정보를 다루면 안된다고 본다.

또한 get은 데이터를 요청할때만 사용된다고 한다.

 

POST란

클라이언트 ----> 서버로 

리소스를 생성하거나 업데이트하기 위해 데이터를 보낼 때 사용되는 메소드이다.

예를들면 게시판에 게시글을 작성하는 작업 등을 할 때 사용된다고 한다. 

 get 방식에서 url의 파라미터로 보냈던 쿼리 스트링이 post 방식에서는 http body에 담겨 보내진다.

 

- get post 두 가지 방식으로 구성하기

app = FastAPI()

class DataInput(BaseModel):
	name:str
    
@app.get("/")
def home():
	return {"Hello" : "GET"}

@app.post("/")
def home_post(data_Input : DataInput):
	return {"Hello" : "POST","MSG" : data_Input.name}

 

 

근데 여기서 궁금한 점이

pydantic을 쓰지 않는 것과 차이점이 뭔지 모르겠다.

왜냐면 

app = FastAPI()

@app.get("/")
def home():
	return {"Hello" : "GET"}

@app.post("/")
def home_post(msg : str):
	return {"Hello" : "POST","MSG" : msg}

 

 

여기서도 post 방식으로 호출하면 msg의 타입을 정해주는 거 아닌가? 라는 생각이 들었다.

그래서 찾아보니 두 코드의 차이는 post 요청의 본문에서 데이터를 어떻게 추출하고 검증하는가에 관한 FastAPI 방식 차이에 있다고 한다.

 

예를 들면 pydantic 모델을 사용하면 

request body에서 json 형식의 데이터를 받지만 

 

그냥 str 파라미터를 사용하면 FastAPI는 msg를 쿼리 파라미터나 폼 데이터에서 찾으려고 한다.

json body에서 msg를 자동으로 추출하진 않는다.

따라서 post 방식으로 JSON 데이터를 보내고 싶을때는 꼭 pydantic을 상속한 클래스를 사용해야되는 것이었다!

 

- 비동기 함수

이번에는 비동기 함수에 대한 예제코드를 실행해보면서 익혀보도록 하겠다.

 

우선 잘못된 코드를 보면

import time
from fastapi import FastAPI
import asycio

async def library(num:int, wait:str):
	s = 0
    for i in range(num):
    	print("wait ......:",wait,i)
        time.sleep(1)
		s += 1
    return s
    
app = FastAPI()

@app.post("/")
async def read_results(wait:str):
	s1 = await library(5,wait)
    return {'data' : 'data', 's1':s1}

 

이 코드는 fastapi를 실행하면 어떤 값을 받아와서 library 함수를 실행하는데 1초 동안 멈췄다가 다시 for문을 실행한다.

 

fastapi를 실행해주고 호출하는 코드를 만든 뒤에 위의 코드를 동시에 두번 실행해 보겠다.

 

이렇게 async을 사용하지 않으면 순서대로 요청이 처리되기 때문에 들어온 요청이 다 끝나야 또 다른 요청이 처리되는 것을 볼 수 있다. 

이제 time.sleep이 아닌 async-await을 제대로 사용하면 비동기적인 방식으로 처리되는 것을 알 수 있다.


3. pydantic

pydantic이란 annotation을 활용해서 data validation,setting을 관리해주는 라이브러리이다.

 

field와 basemodel를 사용한 예제를 보도록 하겠다.

from typing import List, Optional, Union
from datetime import datetime

from pydantic import BaseModel,Field
from pydantic_settings import BaseSettings
from fastapi import FastAPI

#1 기본 사용 방법(BaseModel)
#basemodel : 
class Movie(BaseModel):
    mid: int
    genre: str
    rate: Union[int, float]  # rate는 int와 float 둘 다 가능
    tag: Optional[str] = None  # tag는 str이고 기본 값은 None
    date: Optional[datetime] = None  # date는 datetime을 가지며 기본 값은 None
    some_variable_list: List[int] = []  # 임의의 변수. 리스트 값을 가지고 그 값들은 int여야 함

#2) 데이터 범위 설정 field
class User(BaseModel):
    uid: int
    name:str = Field(min_length=2,max_length=7)
    age: int = Field(gt=1, le=130) #1<age<=130

tmp_data = {
    'mid': '1',
    'genre': 'action',
    'rate': 1.5,
    'tag': None,
    'date': '2023-01-03 19:12:11'
}

tmp_user_data = {
    'uid' : '100',
    'name' : 'soojin',
    'age' : '12'
}

tmp_movie = Movie(**tmp_data)
tmp_user_data = User(**tmp_user_data)
print(tmp_movie.json())
print(tmp_user_data.json())

 

우선 BaseModel이란 pydantic에서 데이터 검증을 담당하는 기본 클래스이다.

basemodel을 상속받은 movie 클래스는 입력 데이터를 구조화하고 검증하는 역할을 한다. 

그리고 User 클래스에는 

각 속성의 제약 조건을 걸기 위해서 field 함수를 사용하여 변수들을 정의하였다.

 

pydantic을 사용하면 tmp_data,tmp_user_data에 있는 문자열로 들어온 숫자들을 pydantic이 알아서 자동으로 타입 캐스팅을 해준다는 것이다. 그리고 모든 필드 유효성을 검사하고 잘못된 값이 있으면 에러를 발생시킨다.

 


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.