LLM 엔지니어링13장 · REST API 와 FastAPI7 / 22← 이전목차다음 →MetaContents

13장. REST API 와 FastAPI

FastAPI 의 기본

자판기는 버튼마다 할 일이 정해져 있습니다. 1번 버튼은 커피, 2번 버튼은 우유입니다. FastAPI 에서는 함수 위에 @app.get("/health") 처럼 주소와 메서드를 적어, "이 주소로 이 메서드가 오면 이 함수를 부른다"를 정합니다.

앱을 만들고, 경로 함수를 붙이고, uvicorn 으로 띄우고, 자동 문서로 확인하는 네 단계 흐름도

1번에서 FastAPI() 로 앱을 만듭니다. 2번에서 함수에 @app.get, @app.post 를 붙여 경로 함수로 만듭니다. 3번에서 uvicorn 파일이름:app 으로 서버를 띄웁니다. 4번에서 브라우저로 /docs 를 열면 FastAPI 가 만든 문서와 시험 화면이 나옵니다. 여기서 자판기의 버튼이 엔드포인트, 버튼에 붙은 일이 경로 함수입니다.

데코레이터와 메서드

데코레이터메서드예
@app.get("/health")GET서버가 살아 있나
@app.post("/predict")POST예측 하나
@app.put("/models/{name}")PUT모델 바꾸기
@app.delete("/cache")DELETE캐시 지우기

경로 함수가 사전을 돌려주면 FastAPI 가 JSON 으로 바꿔 보냅니다. 주소의 {name} 같은 자리는 함수의 같은 이름 인자로 들어옵니다.

서버 없이 시험하기

TestClient 는 서버를 띄우지 않고 앱에 요청을 보내 봅니다. 실습에서는 이것으로 시험하고, 실습 4에서 실제 서버를 띄웁니다.

Python
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/health")
def health():
    return {"상태": "ok"}

@app.get("/items/{item_id}")
def read_item(item_id: int):                  # 주소의 {item_id} 가 정수로 바뀌어 들어온다
    return {"번호": item_id}

client = TestClient(app)
print(client.get("/health").json(), client.get("/items/7").json(), client.get("/items/칠").status_code)

코드에 나오는 함수

코드쉬운 뜻
app = FastAPI()API 앱 하나를 만든다
@app.get("/health")데코레이터(decorator, 꾸미개)다. 바로 아래 함수를 "GET /health 가 오면 부를 함수"로 등록한다
"/items/{item_id}"중괄호 자리는 경로 매개변수(path parameter)다. 주소의 그 자리 글이 같은 이름의 인자로 들어온다
item_id: int타입 힌트다. FastAPI 는 이것을 보고 정수로 바꾸고, 바꿀 수 없으면 422 를 돌려준다
TestClient(app)서버를 띄우지 않고 앱에 요청을 보내 보는 시험용 손님이다
client.get("/health")GET 요청을 보내고 응답을 받는다
.json(), .status_code응답 본문을 JSON 에서 파이썬 사전으로 바꾼 것과 상태 코드 숫자다

이 코드가 뜻하는 것

자판기를 공장에서 시험할 때는 가게에 설치하지 않고 버튼을 하나씩 눌러 봅니다. 여기서 자판기가 app, 버튼이 @app.get 으로 붙인 엔드포인트, 버튼을 눌러 보는 시험 담당자가 TestClient 입니다.

줄하는 일쉽게 말하면
app = FastAPI()앱을 만든다빈 자판기
@app.get("/health") 와 health()살아 있는지 묻는 주소를 단다"작동 중" 확인 버튼
@app.get("/items/{item_id}") 와 read_item번호를 받아 돌려주는 주소를 단다번호를 누르는 버튼
client = TestClient(app)시험용 손님을 만든다시험 담당자
client.get(...) 세 번세 요청을 보내 결과를 본다버튼을 세 번 눌러 본다

실행하면 {'상태': 'ok'} {'번호': 7} 422 가 나옵니다. /items/7 의 "7"은 글로 왔지만 정수 7 로 바뀌어 함수에 들어갔고, /items/칠 은 정수로 바꿀 수 없어 함수가 불리기 전에 422 가 돌아왔습니다.

작게 줄여서 눈으로 보기

틀린 요청 세 가지에 FastAPI 가 무엇을 돌려주는지 봅니다.

Python
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"번호": item_id}

client = TestClient(app)
r = client.get("/items/칠")
print(r.status_code, r.json()["detail"][0]["msg"])
r = client.get("/없는주소")
print(r.status_code, r.json())
r = client.post("/items/7")
print(r.status_code, r.json())
출력
422 Input should be a valid integer, unable to parse string as an integer
404 {'detail': 'Not Found'}
405 {'detail': 'Method Not Allowed'}
결과읽는 법
422 Input should be a valid integer, ..."올바른 정수여야 한다, 글을 정수로 풀 수 없다"는 뜻이다. 어느 칸이 왜 틀렸는지 본문의 detail 에 적혀 온다
404 {'detail': 'Not Found'}그런 주소의 버튼이 아예 없다
405 {'detail': 'Method Not Allowed'}405 는 "허락되지 않은 메서드"다. 주소는 있지만 GET 버튼만 있고 POST 버튼은 없다

주의할 점

  • 경로 함수의 타입 힌트는 장식이 아니라 검사 규칙입니다. item_id: int 에 "칠"을 보내면 함수가 불리기 전에 422 가 돌아갑니다.
  • /docs 의 자동 문서는 편리하지만, 사내 서비스를 밖에 열 때는 문서 주소도 막을지 정합니다(FastAPI(docs_url=None)).
← 이전 절13장 · 모델을 애플리케이션과 잇기다음 절 →13장 · Pydantic 으로 입력 검사