본문 바로가기
Back-end/Fastapi

#3. Fastapi로 모델 서빙부터 DevOps 까지 : Query, Param, Body

by hsloth 2024. 4. 20.

본 포스팅은 AI 개발자 분에게 Fastapi을 이용해 간단한 모델을 서빙해보고, AWS에 올려 서버 배포 및 CI/CD 자동화까지 하는 것을 경험시켜주기 위해 제작되었습니다.

 

 

이전 시간에서는 간단하게 router를 설정하는 방법에 대해 배워보았다.

 

https://suloth.tistory.com/192

 

#2. Fastapi로 모델 서빙부터 DevOps 까지 : Router 설정

해당 포스팅부터 본 시리즈의 이름을 "Fastapi 배우기" -> "Fastapi로 모델 서빙부터 DevOps 까지"로 변경하였습니다. 이전 시간에서는 간단하게 Fastapi의 서버 설정 방법에 대해서 배워보았다. https://sulo

suloth.tistory.com

 

 

이번 시간에는 URL의 param, query, body를 다루는 법에 대해서 배울 예정이다.

 

...

먼저 지난 시간의 과제에 대한 정답을 공개합니다!!

/post 경로로 GET 요청이 들어오면 "Hello, Post"를 사용자에게 리턴하도록 만드는 것

이 목표였다.

 

생각보다 간단하다.

먼저 router폴더 안에 post.py 파일을 만든 후,

 

다음과 같이 코드를 작성해 준다.

# post.py
from fastapi import APIRouter

post_router = router = APIRouter()

@router.get("/")
async def getHelloPost():
  return "Hello, Post!"

 

 

그리고 index.py 파일에 router.include_router 함수를 추가해주면 끝!

# router/index.py

from fastapi import APIRouter
from src.routers.user import user_router

# 추가된 코드
from src.routers.post import post_router

index_router = router = APIRouter()

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

# 추가된 코드
router.include_router(post_router, prefix="/post")

 

 

그리고 가상환경을 실행시키고 서버를 실행시켜보면...

/docs (스웨거) 경로로 가면 해당 함수를 실행시킬 수 있을 것이다.

혹은 http://127.0.0.1:3000/api/post 주소를 브라우저에 입력해도 된다.

 

 


Query


 

Query란 url 주소 맨 뒤에 ? 이후에 붙는 녀석들을 말한다.

예를들어, http://127.0.0.1:3000/api/user?nickname=suloth&age=20이라는 url 주소가 있다면, 뒤의 nickname=suloth와 age=20 이라는 녀석이 query에 해당된다.

query는 Http Request를 보낼 때, 부가적인 정보들을 전달하는 역할을 한다. (Key-Value 형식이다)

따라서 Get요청이라고 가정했을 때 위의 주소를 해석해보면, "User를 가져오는데, nickname은 suloth이고 age(나이)는 20살인 녀석을 가져올거야" 라는 뜻이다.

물론 해당 요청에 대한 함수는 Fastapi로 만들어 두어야 동작한다.

 

그리고 query끼리는 &으로 구분할 수 있다.

 

자, 그럼 함수를 만들어 보자.

먼저, user.py로 가서 getUser 함수를 다음과 같이 고쳐보자.

# router/user.py

@router.get("/")
async def getUser(nickname: str, age: int):
  return {"nickname": nickname, "age": age}

 

 

Fastapi의 경우에 함수에 인자(매개변수)를 넣어주면 자동적으로 query로 인식하게 된다.

그리고 이제 서버를 실행시켜보자. (가상환경도 잊지 말고 실행시키자)

http://127.0.0.1:3000/api/user?nickname=suloth&age=20 을 브라우처에 주소창에 적고 요청을 보내면 다음과 같이 응답한다. (? 앞에 /가 있어도 된다)

 

참고

url에서 읽어오는 모든 정보의 기본 타입은 String(str)이다. 그래서 원래 age의 경우에 "20" 으로 브라우저에 표기되는 것이 맞으나, Fastapi에서는 타입힌트에 따라 자동으로 형변환을 해준다. 그래서 getUser함수의 인자에 age: int로 적으면 age가 int형으로 형변환한다.

 

 


Param


 

Param은 Http Request를 보낼 때, 해당 요청에 대한 추가정보를 전달한다는 점에서 Query와 비슷하다.

하지만 Param은 ?로 구분해서 전달하지 않고, /로 구분을 해서 전달한다는 점이 다르다. 그리고 key-value 형식이 아니라 그냥 값만 적는다.

예를들어, http://127.0.0.1:3000/api/post/1 이라는 주소가 있다고 해보자. 여기서 1은 param이다. 바로 앞의 녀석에 대한 부가 정보를 제공한다.

 


여러분들은 GET /post/1 이라고 하면 무슨 뜻이라고 생각되나요? 바로 떠올리지는 못할 수 있지만, 익숙해지면 "아! 첫 번째 게시글을 가져오라는 요청이구나!" 라고 바로 깨달을 겁니다.

질문 : http://127.0.0.1:3000/api/post?post-number=1 이런 식으로 작성해도 되지 않나요? -> 맞습니다 가능합니다. 상황에 맞춰서 param과 query를 적절히 사용하시면 됩니다...! 다만, 저같은 경우에는 param은 리소스(user나 post같은 것들)에 대해 직접적인 연관이 있는 경우(게시글 번호, 유저이름 등)는 param으로 적는 편이고, 부가적인 정보(user의 age나 height, weight 혹은 post의 작성자, author) 같은 경우는 query로 적는 편입니다.


 

 

그러면 post.py에 다음과 같은 함수를 작성해보자.

# router/post.py
from typing import Optional

@router.get("/{number}")
async def getPost(number: int, content: Optional[str]=None, isArticle: bool = False):
  return { "message" : f"{number} 번째 포스트입니다.", "content": content, "isArticle": isArticle }

 

@router.get 함수의 인자에 {number} 을 넣어주었다. 이러면 이제 /post/something 이라는 url에 대해서 요청이 들어오면 something을 number라는 변수로 인식한다.

그리고 number라는 변수를 getPost함수의 인자(매개변수)에 적어주면 끝이다.

그러면 number라는 변수에 something이 대입되게 되고, 이를 가공하여 리턴할 수 있게 된다.

router.get에 명시되지 않은 변수는 자동으로 query로 인식한다.

 

 

그리고 서버를 실행시키고, http://127.0.0.1:3000/api/post/10?content=콘텐츠입니다. 라는 url을 브라우저에 입력하면, 다음과 같은 결과를 받아볼 수 있다.

 

참고

여기서 isArticle 매개변수에 = False 라는 기본 값을 넣어주면, isArticle 이라는 query가 없어도 자동으로 false가 들어간다.

또한 content의 경우 Optional으로 타입힌트를 주었기 때문에, content가 없으면 null 값이 들어간다. (null 값을 넣으려면 optional을 사용하자)

 


Boby


 

body는 Post나 Put, Patch 요청을 보낼 때 함께 보낼 수 있다.

Post는 생성 요청

Put, Patch는 수정 요청이다.

생성, 수정에는 많은 정보가 필요하기 때문에 Request Body를 따로 추가하여 전달한다.

 

Fastapi에서는 pydantic 패키지를 사용하여 Body를 전달받고, validation할 수 있다.

validation이란 "검사"라는 뜻으로, Request body에 담긴 형식이 서버에서 원하는 형식과 동일한지 검증하는 과정을 말한다.

예를들면, 서버에서는 유저를 생성해달라는 요청을 받을 때, name, age, height를 필요로 하는데 Request body에는 name과 age만 담긴 경우, validation이 통과되지 못하고 해당 요청은 거부된다.

 

하지만 해당 강의에서는 DB와 연동은 하지 않을 예정이기 때문에 일단 body에 전달한 요청을 약간 가공하여 리턴하는 함수를 만들어 보자.

먼저, user.py 파일로 가서 다음과 같은 함수를 작성해보자.

 

# router/user.py
from pydantic import BaseModel

# Body의 타입을 정의한다.
class CreatePostBodyDto(BaseModel):
  name: str
  age: int
  height: int
  

@router.post("/")
async def createUser(body: CreatePostBodyDto):
  
  name = body.name
  age = body.age
  height = body.height
  
  processed_age = f"{age}살"
  processed_height = f"{height}cm"
  
  return {"name": name, "age": processed_age, "height": processed_height}

 

그 다음 Swagger에 들어가보자 (http://127.0.0.1:3000/docs)

 

위와 같이 body를 설정하고 Execute를 하면, 응답이 정상적으로 오는 것을 볼 수 있다.

 

...

 

이렇게 하면 body 요청을 받아서 user를 생성할 수 있다.

물론 지금은 age뒤에 "살"을 붙이고 height 뒤에 "cm"을 붙여서 리턴만 했지만, 추후에 DB를 다룰 수 있게 된다면 직접적으로 생성이 가능하다.

(여유가 된다면 DB를 다루는 법에 대해서고 공부해서 올려보겠습니다 :))

 

 


과제


이번 과제는 http://127.0.0.1:3000/api/user 경로로 Post 요청을 날리면, Param으로 username을 받게끔 하고,

Query로 age를 받게끔하자.

그리고 Body로는 height와 weight를 받아서 이를 리턴하는 함수를 만들자.

 

ex) Post http://127.0.0.1:3000/api/user/suloth?age=20

Request body : { height: 170, weight: 60 }

으로 요청을 보내면

 

리턴 값 : { username: "suloth", age: 20, height: "170cm", weight: "60kg" }

을 리턴하게 만들면 된다.