Change to FastAPI

์žํ›ˆยท2023๋…„ 10์›” 12์ผ
0

๊ฐœ์ธํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
8/10
post-thumbnail

๐Ÿ“Œ Basic

์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์ ํ”„ ํˆฌ API๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. 

๊ธฐ๋ณธ์ ์ธ ๋‚ด์šฉ์€ ์ ํ”„ ํˆฌ Fastapi์˜ ๋‚ด์šฉ์„ ๊ณต๋ถ€ํ•˜๋ฉฐ ์•Œ๊ฒŒ ๋œ ๋‚ด์šฉ๋“ค์„ ์„ค๋ช…ํ•œ๋‹ค. ๊ตฌ๊ธ€์— ๊ฒ€์ƒ‰ํ•˜์—ฌ ๊ณต๋ถ€ํ•ด๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ํ˜ผ์ž์„œ ๋”ฐ๋ผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์™„์„ฑ๋œ ์ฝ”๋“œ๋“ค์ด ์ฃผ์–ด์ง„๋‹ค๋Š” ์ ์—์„œ, ๊ฒฐ๊ณผ๋ฌผ์„ ๋น ๋ฅด๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šฐ๋ฆฌ๋Š” ์ฑ…์— ๋‚˜์˜ค๋Š” ์ฃผ์ œ์— ๊ตญํ•œํ•˜์—ฌ ๊ฐœ๋ฐœ์„ ํ•  ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ๋Šฅ๋“ค์ด ์–ผ๋งˆ๋‚˜ ๋ฒ”์šฉ์„ฑ ์žˆ๊ฒŒ ์“ฐ์ผ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๋ณด์•„์•ผํ•˜๊ณ , FLASK์™€์˜ ์†๋„์ฐจ์ด๊ฐ€ ๋งค์šฐ ์œ ์˜๋ฏธํ•  ์ •๋„๋กœ ๋‚˜๊ธฐ ๋•Œ๋ฌธ์—, ์–ด๋Š ๋ถ€๋ถ„์—์„œ ์ด ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ํšจ์œจ์ ์ธ ์ž‘์—…์„ ๋ณด์—ฌ์ฃผ๋Š”์ง€, ์–ด๋Š ๋ถ€๋ถ„์—์„œ ๋ถˆํŽธํ•œ์ง€๋ฅผ ์ธ์ง€ํ•˜๋ฉด, ๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ FLASK ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์žฌ๊ตฌ์„ฑ์„ ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

๐Ÿ“Œ ์ด์ „ ํฌ์ŠคํŒ… ํ•„๋…

ํ•ด๋‹น ํฌ์ŠคํŒ…์„ ์ž‘์„ฑํ•˜๋ฉฐ, ์•Œ๊ณ  ์‹ถ์€ ๋ถ€๋ถ„๋“ค์€, ์ด์ „ ํฌ์ŠคํŒ…์—์„œ flask๋กœ ํšŒ์›๊ฐ€์ž… ํผ์„ ์ž‘์„ฑํ•˜์˜€๋˜ ๊ธฐ๋Šฅ๋“ค์„ Fastapi๋กœ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค ๊ธฐ๋Šฅ๋“ค์ด ์–ด๋–ค ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€์— ์ค‘์ ์„ ๋‘๊ณ  ์ž‘์„ฑํ•˜์˜€๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ํšŒ์›๊ฐ€์ž… ํผ์ด ์–ด๋–ค ์›๋ฆฌ๋กœ ์ž‘๋™ํ•˜๊ณ , ์›นํŽ˜์ด์ง€์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉฐ, ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋“ค์€ ๋ฌด์—‡์ด๊ณ , ์ฒ˜๋ฆฌ๊ณผ์ •์—์„œ ์–ด๋–ค ๊ฒƒ๋“ค์„ ๊ฒ€์‚ฌํ•˜์—ฌ์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์‚ฌ์ „ ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น๊ธ€์„ ์ดํ•ดํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ๊ฒƒ์ด๋‹ค. Flaks ์™€ Fastapi๋Š” ๋น„์Šทํ•œ ์ ์ด ๊ต‰์žฅํžˆ ๋งŽ๋‹ค๋Š” ์ ์„ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

๐Ÿ“Œ FastAPI

FastAPI

์ด๋ฆ„๋ถ€ํ„ฐ ๊ต‰์žฅํžˆ ๋น ๋ฅด๋‹ค๊ณ  ๋งํ•ด์ค€๋‹ค. ๋งž๋‹ค. ๋งค์šฐ ๋น ๋ฅด๋‹ค. ์†๋„๊ฐ€ ๋Š๋ฆฐ๊ฒŒ ๋‹จ์ ์ด๋ผ๊ณ  ๋ฌธ์ œ๋ฅผ ์ง€์ ๋ฐ›๋Š” ํŒŒ์ด์ฌ์˜ ์ž…์žฅ์—์„œ ์ด๊ฒƒ์€ ํ˜์‹ ์ ์ธ ๋‚ด์šฉ์ด ์•„๋‹ ์ˆ˜ ์—†๋‹ค. ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•ด๋„ ๋‚˜์˜ค๊ฒ ์ง€๋งŒ, ๊ธฐ๋ณธ์ ์ธ ์†๋„๋Š” ๋‚ด๊ฐ€ ์‚ฌ์šฉํ–ˆ๋˜ flask์˜ ๋‘๋ฐฐ๋„ ์•„๋‹ˆ๊ณ  ๋ช‡ ๋ฐฐ๋‹ค ๋ช‡ ๋ฐฐ. ๊ต‰์žฅํžˆ ๋งŽ์ด ์ฐจ์ด๊ฐ€ ๋‚˜๊ธฐ๋„ ํ•˜๊ณ , flask์™€ ์œ ์‚ฌํ•˜๋‹ค๋ผ๋Š” ์ ์—์„œ ๊ณต๋ถ€๋ฅผ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์— ํฌ์ŠคํŒ…์„ ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ FastAPI import๋Š” flaks์—์„œ Flask importํ•˜๋Š”๊ฑฐ๋ž‘ ๋˜‘๊ฐ™๋‹ค๊ณ  ๋ณด๋ฉด๋œ๋‹ค.

from fastapi import APIRouter, Depends, HTTPException, FastAPI
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from starlette import status

from database import get_db
from domain.question import question_schema, question_crud

# ์ผ๋‹จ ์ž„ํฌํŠธ ๊ด€๋ จ ์—๋Ÿฌ ๋ฐ‘์ค„๋‚˜๋Š”๊ฑด ๋‹ค๋ฌด์‹œํ•˜๋ฉด ๋ ๋“ฏ

router = APIRouter(
    prefix="/api/question",
)

# ๋ผ์šฐํŒ…์ด๋ž€ FastAPI๊ฐ€ ์š”์ฒญ๋ฐ›์€ URL์„ ํ•ด์„ํ•˜์—ฌ ๊ทธ์— ๋งž๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ–‰์œ„๋ฅผ ๋งํ•œ๋‹ค.
'''
db: Session ๋ฌธ์žฅ์˜ ์˜๋ฏธ๋Š” db ๊ฐ์ฒด๊ฐ€ Session ํƒ€์ž…์ž„์„ ์˜๋ฏธ
FAST API์˜ Depends๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋ฐ›์€ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚จ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. db ๊ฐ์ฒด์—๋Š” get_db ์ œ๋„ค๋ ˆ์ด์…˜์— ์˜ํ•ด ์ƒ์„ฑ๋œ
๊ฐ์ฒด๊ฐ€ ์ฃผ์ž…๋œ๋‹ค. get_db ํ•จ์ˆ˜์— ์ž๋™์œผ๋กœ contextmanager๊ฐ€ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์—(Depends์—์„œ ์ ์šฉํ•˜๊ฒŒ๋” ์„ค๊ณ„๋˜์–ด์žˆ๋‹ค) database์˜
get_db ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์šฉํ•œ (@contextlib.contextmanager ์–ด๋…ธํ…Œ์ด์…˜์„ ์ œ๊ฑฐํ•ด์•ผํ•œ๋‹ค)
--
@router.get ์–ด๋…ธํ…Œ์ด์…˜์— response_model ์ถ”๊ฐ€ 
question_list ํ•จ์ˆ˜์˜ ๋ฆฌํ„ด ๊ฐ’์€ Question schema๋กœ ๊ตฌ์„ฑ๋œ ๋ฆฌ์ŠคํŠธ์ž„์„ ๋œปํ•œ๋‹ค. 
'''


@router.get("/list", response_model=list[question_schema.Question])
def question_list(db: Session = Depends(get_db)):
    # ์˜ค๋ฅ˜ ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด with๋ฌธ์„ ๋ฒ—์–ด๋‚˜๋Š” ์ˆœ๊ฐ„ db.close()๊ฐ€ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋ณด๋‹ค ์•ˆ์ „ํ•œ ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์ด๋‹ค.
    _question_list = question_crud.get_question_list(db)
    return _question_list


@router.get("/detail/{qeustion_id}", response_model=question_schema.Question)
def question_detail(question_id: int, db: Session = Depends(get_db)):
    question = question_crud.get_question(db, question_id=question_id)
    return question


@router.post("/create", status_code=status.HTTP_204_NO_CONTENT)
def question_create(_question_create: question_schema.QuestionCreate,
                    db: Session = Depends(get_db)):
    question_crud.create_question(db=db, question_create=_question_create)

Router

Flask์—์„œ๋Š” blueprint๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ๋ผ์šฐํŒ…์„ ๊ตฌ๋ถ„ํ•˜๊ณ , ๋ฉ”์ธ ์•ฑ์œผ๋กœ ํ•ด๋‹น๊ธฐ๋Šฅ์„ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ๊ทธ ๊ธฐ๋Šฅ๋“ค์„ fastapi์—์„œ๋Š” router๋ฅผ ์ด์šฉํ•ด์„œ ์กฐ์ ˆ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ณ„๋„์˜ ์ด๋ฆ„์ง€์ •๋„ ํ•„์š” ์—†์ด, url_prefix ๊ธฐ๋Šฅ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ต‰์žฅํžˆ ํŽธ๋ฆฌํ•œ ๊ฒƒ ๊ฐ™๋‹ค. (ํ•ด๋‹น ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ flask์„ค๋ช…๋ถ€๋ถ„์— ๋‚˜์™€์žˆ์œผ๋‹ˆ ์ฐธ์กฐํ•˜๋„๋ก ํ•˜์ž). blueprint ๋ฒ„ํผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์„ ๊ตฌ๋ถ„ํ•  ํ•„์š” ์—†์ด, ๋ฉ”์ธํ•จ์ˆ˜์—์„œ inlcude_router ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋‹ˆ ์ฐธ์กฐํ•˜๋„๋ก ํ•˜์ž.

Dependency Injection

์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํŠน๋ณ„ํ•œ ๋„๊ตฌ์ด๋‹ค. ์˜์กด์„ฑ ์ฃผ์ž…์€ ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค ๋ฉ”์„œ๋“œ์— ๋‹ค๋ฅธ ๊ฐ์ฒด, ํ•จ์ˆ˜ ๋˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋” ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์˜์กด์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค€๋‹ค.

์•„๋ž˜๋Š” FastAPI ๊ณต์‹๋ฌธ์„œ ์˜ˆ์‹œ์ฝ”๋“œ์ด๋‹ค.

from typing import Union

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(
    q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

async๋Š” ๋น„๋™๊ธฐ ๋ผ๋Š” ๊ฒƒ์ธ๋ฐ, ํ•ด๋‹น ๊ฐœ๋…์€ ์ง€๊ธˆ ์ค‘์š”ํ•œ๊ฒŒ ์•„๋‹ˆ๋ฏ€๋กœ ๋„˜๊ฒจ๋‘๋„๋กํ•˜์ž.

Depends์— ๋Œ€ํ•œ ์ดํ•ด:
๊ฒŒ์ž„์„ ์˜ˆ๋กœ ๋“ค์–ด๋ณด์ž. ์˜ˆ๋ฅผ ๋“ค์–ด, A๋ผ๋Š” ์Šคํ‚ฌ์„ ์“ฐ๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ B๋ผ๋Š” ์Šคํ‚ฌ์ด ์„ ํ–‰๋˜์–ด์•ผํ•˜๊ณ , ๋‚ด๊ฐ€ ๊ฐ€์ง„ ์Šคํ‚ฌ์€ ๋งค์šฐ๋งŽ๋‹ค.
๋งŒ์•ฝ Depends๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, A๋ผ๋Š” ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด, ์ผ์ผ์ด B์Šคํ‚ฌ์˜ ์ปค๋งจ๋“œ๋ฅผ ์ž…๋ ฅํ•ด์•ผํ• ๊ฒƒ์ด๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ Depends๋ฅผ ํ†ตํ•ด, A ๊ธฐ์ˆ ์— B ๊ธฐ์ˆ ์„ ์˜์กด์‹œํ‚ค๋ฉด, ๋‚ด๊ฐ€ A ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, ์•Œ์•„์„œ B ๊ธฐ์ˆ ์ด ๋‚˜๊ฐ€๊ณ  A ๊ธฐ์ˆ ์ด ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด๋‹ค.
B A B A B A ์ˆœ์„œ๋Œ€๋กœ ์“ฐ๋˜ ์ด๋Ÿฐ ๋ถˆํ•„์š”ํ•œ ์ž‘์—…๋“ค์€ Depends์— ์˜ํ•ด
A A A A๋กœ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ด๋‹ค. ์ด ์–ผ๋งˆ๋‚˜ ํŽธ๋ฆฌํ•œ๊ฐ€??
๋ณ„๊ฑฐ ์•„๋‹Œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒ ์ง€๋งŒ, ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ์ƒ๊ฐ๋ณด๋‹ค ๋ช‡๊ฐ€์ง€ ์•ˆ๋˜๋Š” ์œ ์ €์˜ ์ •๋ณด๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

HTTPException

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋„์ค‘, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๋‹น ์˜ค๋ฅ˜์ฝ”๋“œ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋„์™€์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

Copy code
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 42:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id}

๋งŒ์•ฝ ์ž…๋ ฅ๋ฐ›์€ item_idrk 42์ผ ๊ฒฝ์šฐ์— 404 ์ƒํƒœ์ฝ”๋“œ์™€ "Item not found"๋ฌธ๊ตฌ๊ฐ€ ํ•จ๊ป˜ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ˜ํ™˜์ด ๋œ๋‹ค.

๐Ÿ“Œ Database

ํ•ด๋‹น ์˜ˆ์ œ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ฝ”๋“œ๋กœ sqlalchemy์™€ sqlalchemy.orm์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘์†์ฃผ์†Œ
SQLALCHEMY_DATABASE_URL = 'sqlite:///./myapi.db'

'''
create_engine sessionmaker ๋Š” ๋”ฐ๋ผ์•ผํ•  ์ •ํ•ด์ง„ ๊ทœ์น™์ด์ง€๋งŒ
autocommit์€ ์ž˜ ์•Œ์•„์•ผํ•œ๋‹ค. autocommit=False ๋ผ๊ณ  ์ €์žฅํ•˜๋ฉด
commit์ด๋ผ๋Š” ์‚ฌ์ธ์„ ์ค„ ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  rollback์ด ๊ฐ€๋Šฅํ•˜๋‹ค. 
auto commit=True ์ผ ๊ฒฝ์šฐ์—๋Š” rollback ์€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค
'''

engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
# base ํด๋ž˜์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ ์ƒ์„ฑ์— ์“ธ ๊ฐ์ฒด์ด๋‹ค.


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

create_engine

SQLAlchemy์—์„œ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค URL์„ ์ธ์ˆ˜๋กœ ๋ฐ›์•„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๊ฒฐํ•˜๊ณ  ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ํ’€๋ง๊ณผ ์Šค๋ ˆ๋”ฉ๋“ฑ ๊ณ ๊ธ‰ ์„ค์ •์„ ์ง€์›ํ•œ๋‹ค.

declarative_base

SQLAlchemy์˜ ORM(Object-Relational Mapping)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. declarative_base ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ์˜ ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ, ์ด ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ ํ†ตํ•ด ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

ORM ์˜ ๊ฒฝ์šฐ SQL๋ฌธ๋ฒ•์„ ๋ฐฐ์šฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์žฅ์ ์ด ์žˆ์œผ๋ฉด ๋‹จ์ ๋„ ์žˆ๊ฒ ์ฃ ? ใ…Žใ…Ž

sessionmaker, session

Session = sessionmaker(bind=engine)
bind์™€ engine์€ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๊ฒฐํ• ์ง€ ์ •ํ•˜๋Š” ๋ถ€๋ถ„์ด๊ณ  sessionmaker๋ฅผ ํ†ตํ•ด, ์„ธ์…˜ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

session์€ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์™€ ํŠธ๋žœ์žญ์…˜์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด์ด๋‹ค. ํ•ด๋‹น sessionmaker๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์„ ์“ธ๋ ค๋ฉด
session = Session()์ฒ˜๋Ÿผ ์„ ์–ธํ•ด์„œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.
session์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๋Š” ๋“ฑ์˜ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ๋“ค์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ pydantic

BaseModel

BaseModel์€ ๋ชจ๋ธ์„ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ์ด๋ฅผ ์ƒ์†ํ•˜๊ณ  ํ•„๋“œ๋ฅผ ํด๋ž˜์Šค ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ํ•„๋“œ์˜ ํƒ€์ž…ํžŒํŠธ์™€ ๊ธฐ๋ณธ ๊ฐ’ ์„ค์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค

from pydantic import BaseModel

class User(BaseModel):
    username: str
    email: str
์•„๋งˆ๋„ c๋‚˜ c++ ์จ๋ดค๋˜ ์‚ฌ๋žŒ๋“ค์€ ์ต์ˆ™ํ• ๋งŒํ•œ ์ž๋ฃŒํ˜• ์„ ์–ธ๋“ค์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. 

validator

@validator
๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํ˜•์‹์œผ๋กœ ์„ ์–ธํ•˜์—ฌ, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ํ•„๋“œ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž ์ง€์ • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

from pydantic import BaseModel, validator

class User(BaseModel):
    username: str
    email: str

    @validator('email')
    def validate_email(cls, value):
        if not value.endswith('@example.com'):
            raise ValueError('์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” example.com ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.')
        return value

@validator('email')๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” email ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์ •์˜ํ•˜๋ฉฐ, ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ example.com ๋„๋ฉ”์ธ์œผ๋กœ ๋๋‚˜์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ๋‹ค.

cls๋Š” ํด๋ž˜์Šค ์ž์ฒด, value๋Š” email ํ•„๋“œ์— ํ• ๋‹น๋œ ๊ฐ’์„ ๋งํ•œ๋‹ค.
ํ˜„์žฌ ์˜ˆ์‹œ์—์„œ๋Š”
ํด๋ž˜์Šค๋Š” User๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๊ณ  value๋Š” email ํ•„๋“œ์— ๋“ค์–ด์žˆ๋Š” ๊ฐ’์ด๋‹ค. value๋ฅผ ํ†ตํ•ด ์œ ํšจ์„ฑ ์ž‘์—…์ด ์ด๋ฃจ์–ด์ง„๋‹ค.

๐Ÿ“Œ starlette

์ด ๋…€์„ ๋•Œ๋ฌธ์— ์• ์ข€ ๋จน์—ˆ๋‹ค. ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ์•Š๊ณ  ๊ฐ„๋‹ค๋ฉด, ํ›„์— FastAPI๋กœ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ผ ๊ฒƒ ๊ฐ™๋‹ค. ๋™์ž‘ํ•˜๋Š” url๊ณผ ๊ด€๋ จํ•œ ๋ฏผ๊ฐํ•œ ๋ถ€๋ถ„์ด๋‹ค.

CORSMiddleware, status

์šฐ์„  ์˜ˆ์‹œ์ฝ”๋“œ์ด๋‹ค.

from starlette.middleware.cors import CORSMiddleware
from starlette import status

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # ๋ณ„ํ‘œ(*)๋Š” ๋ชจ๋“  ๋„๋ฉ”์ธ์—์„œ์˜ ์š”์ฒญ์„ ํ—ˆ์šฉ
    allow_credentials=True,  # ์ž๊ฒฉ ์ฆ๋ช… ์ •๋ณด (์˜ˆ: ์ฟ ํ‚ค, ์ธ์ฆ)๋ฅผ ํ—ˆ์šฉ
    allow_methods=["*"],  # ๋ชจ๋“  HTTP ๋ฉ”์„œ๋“œ ํ—ˆ์šฉ
    allow_headers=["*"],  # ๋ชจ๋“  HTTP ํ—ค๋” ํ—ˆ์šฉ
    expose_headers=["Content-Disposition"],  # ๋ธŒ๋ผ์šฐ์ €์— ๋…ธ์ถœํ•  ํ—ค๋” ์ง€์ •
)

@app.post("/api/some_endpoint", status_code=status.HTTP_201_CREATED)
async def create_resource():
    # ์—ฌ๊ธฐ์„œ ์ž์› ์ƒ์„ฑ ๋…ผ๋ฆฌ๋ฅผ ๊ตฌํ˜„
    return {"message": "Resource created successfully"}

CORSMiddleware:
Cross-Origin Resource Sharing (CORS)๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด์ด๋‹ค.
CORS๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ๋ถ€ํ„ฐ์˜ HTTP ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ•˜๋Š” ๋ณด์•ˆ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„œ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ ์ž์›์„ ์š”์ฒญํ•  ๋•Œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณด์•ˆ ์ •์ฑ…์„ ์ค€์ˆ˜ํ•˜๋„๋ก ๋•๋Š” ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค. ์ผ์ข…์˜ ๋ณด์•ˆ๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์ธ๋ฐ, ์ ํ”„ํˆฌ FASTAPI๋ฅผ ๋”ฐ๋ผ์˜ค๋‹ค ๋ณด๋ฉด ์•„๋งˆ orgin๋ถ€๋ถ„์—์„œ ๋ฌธ์ œ๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋  ๊ฒƒ ์ด๋‹ค. ์›๋ž˜ ๋ชจ๋“ ๋„๋ฉ”์ธ์˜ ์š”์ฒญ์„ ํ—ˆ๋ฝํ•˜์ง€ ์•Š๊ณ ,

orgins [ 
 #์—ฌ๊ธฐ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ—ˆ์šฉํ•  URL๋“ค์„ ๋„ฃ์Šต๋‹ˆ๋‹ค.
]

์ด๋ ‡๊ฒŒ orgins ๋ณ€์ˆ˜๋ฅผ ๋”ฐ๋กœ ์„ ์–ธํ•˜์—ฌ, allow_orgins=orgins ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ์˜ˆ์ œ์ฝ”๋“œ์—์„œ๋Š”, 127.0.0.1:5173์— ๋Œ€ํ•œ orgin์€ ํ—ˆ์šฉํ–ˆ์œผ๋‚˜, ๋‚˜์ค‘์— svelte๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๋ฉด ์•„๋Š”๋ฐ localhost:5173์— ๋Œ€ํ•œ ํ—ˆ์šฉ์„ ํ•ด๋†“์ง€ ์•Š์•„์„œ, ์ œ ์•„๋ฌด๋ฆฌ svelte๋ฅผ ์‹คํ–‰์‹œ์ผœ ๋ณด์•„๋„, ์Šค์›จ๊ฑฐ๋ฅผ ํ†ตํ•ด ๋„ฃ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง„์งœ

์ฃฝ์—ˆ๋‹ค ๊นจ์–ด๋‚˜๋„ ์•ˆ๋ณด์ด๋”๋ผ

๊ทธ๋Ÿฌ๋‹ˆ ํ˜น์‹œ ํ•˜๋‹ค๊ฐ€ ๋ง‰ํžˆ๋ฉด ์ด๋ถ€๋ถ„์„ ๋ฐ˜๋“œ์‹œ ์ ์šฉํ•ด๋ณด๊ธธ ๋ฐ”๋ž€๋‹ค... ๋ฌผ๋ก  ํ•˜์ž๋งˆ์ž ๋ฐ”๋กœํ•ด๋ณด์ง€๋Š” ๋ง๊ณ , ์Šค์›จ๊ฑฐ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ž˜ ์ฃผ๊ณ ๋ฐ›๋Š”์ง€ ํ™•์ธํ•œ ํ›„, ํ•ด๋ณด๊ธธ ์ถ”์ฒœํ•œ๋‹ค.
status:
์ด ์นœ๊ตฌ๋Š” ์ต์ˆ™ํ•  ๊ฒƒ์ด๋‹ค. http ์‘๋‹ต์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ์ƒ์ˆ˜๋‹ค. ์ด๊ฑด ๊ฐœ๋…๋ณด๋‹ค ๋ณธ์ธ์ด ์ง์ ‘ ์˜ค๋ฅ˜๋“ค์„ ๋งˆ์ฃผํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋งˆ์ฃผํ•ด๋ณด๊ธธ ์ ๊ทน ๊ถŒ์žฅํ•œ๋‹ค. ํŠนํžˆ 404error(Not Found)๋Š” ์•„๋งˆ ์ผ์ƒ์ƒํ™œ์—์„œ๋„ ํ”ํžˆ ๊ฒฝํ—˜ํ•˜๋Š” ์ฝ”๋“œ์ผ ๊ฒƒ.

๐Ÿ“Œ swagger

๊ทธ๋ƒฅ ์จ๋ด๋ผ

์ œ๋ฐœ ์ข€ ์จ๋ด๋ผ
์ œ๋ฐœ
API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐ ์ง์ ‘ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์˜ˆ์ƒ๋˜๋Š” ์‘๋‹ต์„ ๋ฏธ๋ฆฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์•„์ฃผ ํŒŒ๊ฒฉ์ ์ธ ๊ธฐ๋Šฅ์ด๋‹ค. ๊ทธ๋ƒฅ ๋งค์šฐ๋งค์šฐ๋งค์šฐ ๋งค์šฐ์šฐ์šฐ์šฐ์šฐํŽธ๋ฆฌํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด๋œ๋‹ค. ์ž์„ธํ•œ ๋™์ž‘๊ณผ์ •์€ ์†”์งํžˆ ์•Œ๋ ค์ฃผ๊ธฐ์—” ๋„ˆ๋ฌด ์‰ฝ๋‹ค. ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ๊ผญ ํ•ด๋ณด๊ธธ ๋ฐ”๋ž€๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€