[FastAPI] 2. SQLAlchemy๋ฅผ ์ด์ฉํ ๊ฐ๋จํ CRUD API ๋ง๋ค๊ธฐ
์ด๋ฒ ๊ธ์์๋ ORM์ ๋ํ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ORM์ Object Relation Mapping์ ์ฝ์๋ก ๊ฐ์ฒด๋ฅผ ์ด์ฉํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค Entity์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ณดํต ์ ํ๋ฆฌ์ผ์ด์  ๋ ๋ฒจ์์ DB์ ์ ๊ทผํ ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋๋ผ์ด๋ฒ๋ฅผ ์ด์ฉํ์ฌ SQL Query๋ฅผ ๋์ ธ ์คํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ง๋ง SQL Query๋ ์ํํธ์จ์ด ์์ง๋์ด์๊ฒ ์์ด ๋ฌ๋ ์ปค๋ธ๋ฅผ ์ฆ๊ฐ ์ํค๊ณ , ์ํํธ์จ์ด ์ฝ๋ ๊ฐ๋ ์ฑ์ ์ ํ์ํค๋ ์์ธ์ด ๋์์ต๋๋ค.
ํ์ง๋ง ORM์ ์ด์ฉํ๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD๋ฅผ ํฌํจํ ๊ฐ๋จํ ์ฟผ๋ฆฌ์ ๋ํด SQL Query๋ฅผ ํ๋ก๊ทธ๋๋ฐ ์ฝ๋์ ์ง์ํ์ง ์์๋ ํ๋ก๊ทธ๋๋ฐ ์ฝ๋ ์์์ ์ฒ๋ฆฌํ ์ ์๋ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค.
Python ์ธ์ด์์์ ๋ํ์ ์ธ ORM ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก SQLAlchemy๊ฐ ์์ต๋๋ค.
SQLAlchemy
SQLAlchemy๋ ๋ณธ๋ Flask์์ Flask์ฉ SQLAlchemy ๋ํ๋์๊ฐ ์์ ์ ๋๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ํ๊ณ๊ฐ ๊ต์ฅํ ๋์ต๋๋ค. ๊ทธ๋ ๋ค๊ณ ํด์ Flask์์๋ง ์ฌ์ฉํ ์ ์๋ ๊ฒ์ ์๋๋๋ค. JPA์ฒ๋ผ ์ด๋ ํ๋ ์์ํฌ๋ , ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ ๋ํ๋์๋ฅผ ์ฌ๋ฆด ์ ์๋ ๊ณณ์ด๋ผ๋ฉด ์ด๋ค ๊ณณ์ด๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ฉฐ JPA์ ๋ง์ฐฌ๊ฐ์ง๋ก Connection Pool, Lazy loading ๋ฑ์ ์ง์ํฉ๋๋ค.
[tool.poetry]
name = "fastapiexample"
version = "0.1.0"
description = ""
authors = ["Neon K.I.D <contact@neonkid.xyz>"]
[tool.poetry.dependencies]
python = "^3.8.5"
fastapi = "^0.63.0"
uvicorn = "^0.13.2"
SQLAlchemy = "^1.3.22"
psycopg2-binary = "^2.8.6"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"์ฐ๋ฆฌ๋ ์ด ๊ธ์์ PostgreSQL์ ์ฐ๋ํ๋ค๊ณ ๊ฐ์ ํ๊ณ , SQLAlchemy์ Python์ฉ Postgres ๋๋ผ์ด๋ฒ์ธ psycopg2-binary ๋ํ๋์๋ฅผ ์ถ๊ฐํด๋ณด๋๋ก ํฉ์๋ค.
Create Model
ํ์ํ ๋ํ๋์๋ฅผ ๋ชจ๋ ์ค์นํ๋ค๋ฉด ์ด์  ์ฌ์ฉํ ํ ์ด๋ธ์ ๋ง๋ค์ด๋ณด๋๋ก ํ์ฃ . ORM์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ๊ณ ์ถ์ ํ ์ด๋ธ์ class๋ก ๋ง๋ค์ด ์ ์ํ ์ ์์ต๋๋ค.
from sqlalchemy import Boolean, Column, String, Text
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Base.metadata.create_all(bind=engine)
class Memo(Base):
    __tablename__ = 'memos'
    id = Column(String(120), primary_key=True, default=lambda: str(uuid.uuid4()))
    title = Column(String(80), default='No title', nullable=False, index=True)
    content = Column(Text, nullable=True)
    is_favorite = Column(Boolean, nullable=False, default=False)๊ฐ๋จํ ๋ฉ๋ชจ์ฅ ๋ง๋ค๊ธฐ ์ํด Memo๋ผ๋ Entity๋ฅผ ์์ฑํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ๋ฉ๋ชจ์ฅ์ ๊ณ ์ ID์ ์ ๋ชฉ, ๋ด์ฉ ๊ทธ๋ฆฌ๊ณ ์ฆ๊ฒจ์ฐพ๊ธฐ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ is_favorite๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ฑ ์คํ์ ํ ์ด๋ธ์ ์๋์ผ๋ก ๋ง๋ค๋๋ก create_all ํจ์๋ฅผ ํธ์ถํด์ค๋๋ค.
Create Connection
์๋ฒ๋ฅผ ์ฐ๊ฒฐํ๊ธฐ ์ํ ์ธ์ ์ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine('postgresql://{username}:{password}@{host}:{port}/{db_name}'.format(
    username='postgres', password='postgres', host='127.0.0.1', port='5432', db_name='nkmemo'
))
db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))์ธ์ ์ ์๋ฒ์์ DB์๊ฒ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ํ ํต๋ก ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค. ๊ฐ๊ฐ์ ํ๋ผ๋ฏธํฐ์ ์๋ง๋ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๊ณ , ์ฝ๋๋ฅผ ์์ฑํด์ค๋๋ค.
autoCommit์ ๋นํ์ฑํํ๋ ์ด์ ๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์์ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ฌ๋ฌ ์ค์ SQL ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ ๋ ํ ๋ฒ์ ๋ฐ์์ํค๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค. ๊ฐ๋ น ์๋ฅผ ๋ค์ด ๋ ํ ์ด๋ธ์ด ๋์์ ๋ณ๊ฒฝ ์์ ์ด ์ด๋ฃจ์ด์ง๋ค๊ณ ํ์ ๋, ํ๋์ ORM ์ฝ๋์์ ํธ๋์ญ์ ์ด ๋ฐ์ํ๋๋ผ๋ ์ฝ๋์์ commit ํจ์๋ฅผ ํธ์ถํ ๋๊น์ง๋ commit์ด ์ผ์ด๋์ง ์์ต๋๋ค.
autoFlush๋ DB์์ flush๋ฅผ ์๋ํํ๊ฒ ๋๋ ์ฌ๋ถ์ธ๋ฐ, ์ด ์ญ์ commit์ ์ฌ๋ฌ ์ค๋ก ๋ฐ์์ํค๊ธฐ ์ํด ์์ํ๋ฅผ ์๋ํ ์ํค์ง ์๊ฒ ๋ค๋ ์กฐ๊ฑด์ผ๋ก commit ํจ์๊ฐ ๋ฐ์ํ์ ๋๋ง ์์ํ ํ๊ฒ ๋ค๋ ์ต์ ์ ๋๋ค.
def get_db():
    db = db_session()
    try:
        yield db
    finally:
        db.close()FastAPI์์ ๊ฐ API๋ง๋ค DB์ ์ข ์๋์์ ๋ ํจ์๋ณ๋ก ์ธ์ ์ ๋ถ์ฌํ๊ณ , ์์ ์ด ๋๋๋ฉด close ๋ ์ ์๋๋ก ์์กด ํจ์๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
Apply function
์ด์  API ํจ์๋ฅผ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from typing import Optional, List
app = FastAPI()
class ResponseMemo(BaseModel):
    id: str
    title: str
    content: Optional[str] = None
    is_favorite: bool
    class Config:
        orm_mode = True
@app.get('/memos', response_model=List[ResponseMemo])
async def get_memos(db: Session = Depends(get_db)):
    memos = db.query(Memo).all()
    return memos์์์ ๋ง๋ค์๋ ์ข ์ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ FastAPI์์ ์ ๊ณตํ๋ Depends ํจ์๋ฅผ ์ด์ฉํด API๊ฐ ํธ์ถ๋ ๋๋ง๋ค DB ์ธ์ ์ ์์ฑํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก ๊ตฌํํ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ๊ตฌํํ๋ฉด API ํธ์ถ์ ์ธ์ ์ด ์์ฑ๋๊ณ , ์ฌ์ฉ์ด ๋๋๋ฉด ์๋ ์ธ์ ์ด ํด์ ๋ฉ๋๋ค.
๋ง๋ถ์ฌ์ pydantic์์ ์ ๊ณตํ๋ orm_mode๋ฅผ ์ด์ฉํ์ฌ ๋ฐํ ๋ชจ๋ธ์ ๋ง๋ค ๊ฒฝ์ฐ, ORM JSONEncoder์ ์ํด ์๋์ผ๋ก json์ผ๋ก ๋ณํํด์ฃผ๊ธฐ ๋๋ฌธ์ ๋ณ๋๋ก JSONResponse ๋ฑ์ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ ํ์๊ฐ ์์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก ์ ์ฒด์ ๋ฉ๋ชจ ๋ด์ฉ์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ response_model์ List๋ฅผ ์ด์ฉํ๋๋ก ํฉ๋๋ค.
from pydantic import BaseModel
from typing import Optional
class RequestMemo(BaseModel):
    title: str
    content: Optional[str] = None
    is_favorite: Optional[bool] = False
class ResponseMemo(BaseModel):
    id: str
    title: str
    content: Optional[str] = None
    is_favorite: bool
    class Config:
        orm_mode = True
@app.post('/memos', response_model=ResponseMemo)
async def register_memo(req: RequestMemo, db: Session = Depends(get_db)):
    memo = Memo(**req.dict())
    db.add(memo)
    # ์ด ์ฝ๋๋ฅผ ์ฐ์ง ์์ผ๋ฉด DB์ ๋ฐ์๋์ง ์์
    db.commit()
    return memoid๋ ์๋์ผ๋ก ์์ฑ๋๊ณ , ์ฌ์ฉ์๋ก๋ถํฐ ๋ฐ์์ผํ ๋ด์ฉ์ ์ ๋ชฉ๊ณผ ๋ด์ฉ์ ๋๋ค. ์ ๋ชฉ์ ํ์๊ฐ์ผ๋ก ๋ด์ฉ์ ์ ํ๊ฐ์ผ๋ก ํด์ค๋๋ค.
POST ๋ฉ์๋ ์์ฑ์, ์ฐ๋ฆฌ๋ ์์์ autoCommit์ ์ค์ ํด์ฃผ์ง ์์์ผ๋ฏ๋ก ์๋์ผ๋ก commit ํจ์๋ฅผ ํธ์ถํด์ผ๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ดํฐ๊ฐ ์์๋ฉ๋๋ค. ๋ฐ๋ผ์ commit ํจ์๋ฅผ ํธ์ถํด์ฃผ๋๋ก ํฉ์๋ค.
@app.put('/memos/{item_id}', response_model=ResponseMemo)
async def mod_memo(item_id: str, req: RequestMemo, db: Session = Depends(get_db)):
    memo = db.query(Memo).filter_by(id=item_id)
    req_dict = req.dict()
    req_dict['id'] = item_id
    
    req = {k: v for k, v in req_dict.items()}
    for key, value in req.items():
        setattr(memo, key, value)
    db.commit()
    return memoPUT ๋ฉ์๋๋ฅผ ์์ฑํ ๋๋ ๊ธฐ์กด ๋ฉ๋ชจ ๋ฐ์ดํฐ๊ฐ ์๋์ง๋ฅผ filter_by ํน์ filter ๋ฉ์๋๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ ํ ์งํํ๋๋ก ํฉ๋๋ค.
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
@app.delete('/memos/{item_id}')
async def del_memo(item_id: str, db: Session = Depends(get_db)):
    memo = db.query(Memo).filter_by(id=item_id).first()
    db.delete(memo)
    db.commit()
    return Response(status_code=HTTP_204_NO_CONTENT)Session์์ ์ ๊ณตํ๋ delete ํจ์๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ๋๋ ๋ฐ๋์ filter, filter_by ๋ฑ์ ํตํด ๋ชจ๋ธ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋ค์ ์งํํฉ๋๋ค.
Test
ํ ์คํธ๋ฅผ ์งํํ๊ธฐ ์ ์, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํ๋ก๊ทธ๋๋ฐ ์ฝ๋์ ์ ๋ ฅํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ฑํ ํ ์งํํด์ฃผ์ธ์. ํ ์คํธ์๋ Postman ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํด๋ดค์ต๋๋ค.

PyCharm Professional์ ์ฌ์ฉํ์ ๋ค๋ฉด Database ๊ธฐ๋ฅ์ ์ด์ฉํ์๋ ๊ฒ๋ ์ข์ ๊ฒ ๊ฐ๋ค์.

์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ฉด ์์ ๊ฐ์ด ์ฃผ์์ ํฌํธ๋ฅผ ์๋ ค์ฃผ๋ฉฐ,

์์ ๊ฐ์ด ํ ์ด๋ธ์ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋๋ค.

๋จผ์  GET ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ฌด๋ฐ ๋ด์ฉ์ด ์์ผ๋ฏ๋ก ์๋ฌด๊ฒ๋ ์ถ๋ ฅํ์ง ์์ต๋๋ค.

๋ฉ๋ชจ๋ฅผ ํ๋ ๋ฑ๋กํ๊ธฐ ์ํด POST ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์๋์ ๊ฐ์ด ๋ด์ฉ์ด ์ถ๋ ฅ๋๊ณ ๋ฉ๋ชจ์ ๋ํ ID๊ฐ ๋ถ์ฌ๋ฉ๋๋ค.

๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค.

PUT ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ๋ด์ฉ์ ์์ ํ ๋ ์ญ์ ์ ๋์ํ๋ฉด ์์ ๊ฐ์ด ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค.

๋ง์ง๋ง์ผ๋ก DELETE ๋ฉ์๋์ ๊ฒฝ์ฐ ์๊น ์ ๋ฌ๋ฐ์ ID๋ฅผ ์ฃผ์ ๋ค์ ๋ฃ๊ณ ์คํํ์ ๋ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ์๋ค๋ฉด 204 No Content ์ฝ๋์ ํจ๊ป ์๋ฌด๋ฐ ๋ด์ฉ์์ด ์ถ๋ ฅํ๊ฒ ๋ฉ๋๋ค.

๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ์ ์ญ์ ๊ฐ ๋๋ ๋ชจ์ต์ด์ฃ .
๋ง์น๋ฉฐ...
์ฌ๊ธฐ๊น์ง FastAPI์ SQLAlchemy๋ฅผ ์ด์ฉํ์ฌ DB ์ฐ๋ ํ API๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ดค์ต๋๋ค.
SQLAlchemy์ ๋ด์ฉ์ ์ด๋ณด๋ค ๋ ๋ฐฉ๋ํฉ๋๋ค. ์ง๊ธ์ ๊ฐ๋จํ๊ฒ ํ๋์ ํ ์ด๋ธ์ ์ด์ฉํ์์ง๋ง ๋ ๊ฐ ์ด์์ ๋ณตํฉ ํ ์ด๋ธ์ ์ด์ฉํ๋ค๋ฉด ์ค๊ณ๋ฅผ ํ ๋ ์ข ๋ ์ฌ์คํ๊ณ ๋น ๋ฅธ ์ต์ ํ๋ฅผ ์ํ ์ค๊ณ๊ฐ ํ์ํฉ๋๋ค.
๊ฐ์ธ์ ์ผ๋ก Marshmallow๋ฅผ ์ฌ์ฉํ์ ๋๋ orm_mode๊ฐ ๋ณ๋๋ก ์ ๊ณต๋์ด ์์ง ์์ JSON์ด๋ dict๋ก ๋ณํํ๊ธฐ ์ํ ์ถ์ ํจ์๋ฅผ ๋ณ๋๋ก ์ฌ๊ตฌํ ํด์ผ ํ์ต๋๋ค. ํน์ JSONEncoder๋ฅผ ๋ณ๋๋ก ๋ง๋๋ ๊ฒ๋ ํ๋์ ๋ฐฉ๋ฒ์ด์ฃ .
ํ์ง๋ง Pydantic์ ์ด๋ฌํ JSON, dict ๋ณํ์ ๋ํ ๋ก์ง์ ๊ฐ๋ฐ์๊ฐ ๋ณ๋๋ก ๊ตฌํํ์ง ์์๋ SQLAlchemy๊ฐ ์ ๊ณตํ๋ ๊ฐ์ฒด์ ๋ง๊ฒ ์๋์ผ๋ก ์ฝ๋ ํ ์ค๋ง ์์ฑํด์ ๋ฐํ ๋ชจ๋ธ์ JSON์ผ๋ก ๋ณํํด์ฃผ๋ ๋ก์ง์ ์ ๊ณตํด์ฃผ๋ ์ ์ ๊ต์ฅํ ํธํ์ต๋๋ค.
๋ํ Depends ํจ์๋ฅผ ์ ๊ณตํด์ฃผ์ด DB ์ธ์ ์ ๋ํ ๊ด๋ฆฌ๊ฐ ์ข ๋ ํธํ๋๋ฐ์. ๋ฏธ๋ค์จ์ด๋ฅผ ์ด์ฉํ๊ฑฐ๋ decorator ๋ฑ์ ์ด์ฉํด์๋ ๋น์ทํ๊ฒ ๊ตฌํํ ์ ์์ง๋ง ์ ํ๋ฆฌ์ผ์ด์  ๊ฐ๋ฐ์๊ฐ App context์ ๋ผ์ดํ ์ฌ์ดํด์ ๋ง์ถฐ์ ๊ณ ๋ คํด์ผ ํ๋ ์ ์ด ๋ ํฐ ๊ณผ์ ์๋๋ฐ, ์ด๋ฅผ ํ๋ ์์ํฌ์์ ํจ์๋ก ์ ๊ณตํด์ฃผ๋ ๊ฒ์ ๊ฐ๋ฐ์๊ฐ ์ ํ๋ฆฌ์ผ์ด์  ๋ ๋ฒจ ๊ฐ๋ฐ์์๋ง ์ ๊ฒฝ์ฐ๋๋ก ๋ ธ๋ ฅํ๋ค๋ ์ ์ ๋์ ๋์์ต๋๋ค.
ํ ๊ฐ์ง ๋ ๋ฐ๋ผ๋ ์ ์ด ์๋ค๋ฉด ์์ง๊น์ง ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ฒ๋ฆฌ์ ๋ํด ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋ฏธํกํ๋ค๋ ์ ์ด ์์ต๋๋ค. ๋ฌผ๋ก ORM ๋ ๋ฒจ์์๋ ๊ทธ ์ง์์ด ๋ฏธํกํ๋ค๋ ์ ์ด ์์ง๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์คํด์ค์์๋ ์ง์ํ์ง ์๋๋ค๋ ๊ฒ์ ์ฐธ ์์ฌ์ด ์ด์ผ๊ธฐ ์ ๋๋ค. Java ์ง์์์๋ R2DBC๋ผ๋ ์ปค๋ฅ์ ์ ์ด์ฉํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฒจ์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ์ง๋ง ์์ง๊น์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฏธ๋ค์จ์ด์์ ์ง์ํ์ง ์์ ๋ง์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ด์ ์ ๋๋ฆด ์ ์์ต๋๋ค.
Python ์ง์์์๋ SQLAlchemy๊ฐ 1.4๋ถํฐ ๋น๋๊ธฐ ์ง์์ ํ๊ณ ์๊ณ , ํ์ฌ ๋ฒ ํ ๋ฒ์ ์ด ๋ฆด๋ฆฌ์ฆ๋ ์ํ์ ๋๋ค. ๋น๋๊ธฐ ํ๋ ์์ํฌ๊ฐ ๋ง์ด ๋ณดํธํ ๋์๊ธด ํ์ง๋ง ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๊ฒฐ์์ ๋น๋๊ธฐ๊ฐ ์ง์๋์ง ์๋๋ค๋ ์ ์ ์์ด์ ์์ฌ์ด ์ ์ด ๋ง์ด ์๋๋ฐ์. ์ฐจํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ์์ ์ ์ผ๋ก ๋ ์ ์๋ ๋ ์ด ์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.

 
			 
			 
		 
                     
                     
                     
			