๐ฐ FastAPI ๊ณต์๋ฌธ์๋ฅผ ๋ณด๋ฉด์ ๊ฐ์ธ์ ์ผ๋ก ์ ๋ฆฌํ ๊ธ ์
๋๋ค.
๋จผ์ DB๋ฅผ ์ค๋นํ๋ค. ์ด ํฌ์คํธ์์๋ MariaDB๋ฅผ ์ฌ์ฉํ๋ค.
DB์ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค.
- DB๋ช : test
- Table ๋ช : User
idUser | username | password | nickname |
1 | admin1 | SHA2(admin1,256) | Admin1 |
2 | admin2 | SHA2(admin2,256) | Admin2 |
3 | pupba12 | SHA2(qwer1234,256) | Pupba |
ORM(Object-Relational Mapping)
FastAPI๋ ORM์ SQLAlchemy๋ฅผ ์ฌ์ฉํด ๊ตฌํํ๋ค.
pip install sqlalchemy
๋ง๋ ORM ๋ชจ๋์ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
1. SQLAlchemy - database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
import json
path = "./sql_app/secret.json"
se = json.loads(open(path).read())
R_SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{se.get('S_USER')}:{se.get('S_PW')}@{se.get('HOST')}:{se.get('PORT')}/{se.get('DB')}?charset=utf8"
CUD_SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{se.get('M_USER')}:{se.get('M_PW')}@{se.get('HOST')}:{se.get('PORT')}/{se.get('DB')}?charset=utf8"
r_engine = create_engine(R_SQLALCHEMY_DATABASE_URL, pool_pre_ping=True)
cud_engine = create_engine(CUD_SQLALCHEMY_DATABASE_URL, pool_pre_ping=True)
r_SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=r_engine)
cud_SessionLocal = sessionmaker(
autocommit=False, autoflush=False, bind=cud_engine)
Base = declarative_base()
create_engine(url, **kwargs)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๊ฒฐ์ ์ค์ ํ๋ ์์ง์ ์์ฑ
- url : ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ URL
- **kwargs : ์ถ๊ฐ์ ์ธ ์ค์ ์ ์ํ ๋งค๊ฐ๋ณ์(ํ๋ง, ๋ฌธ์์ธ์ฝ๋ฉ ๋ฑ)
sessionmaker(class_=None,bind=None,autoflush=True,autocommit=False,expire_on_commit=True,**kwargs)
- ์ธ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ์ํ ํฉํ ๋ฆฌ ํจ์, ์ธ์ ์ SQLAlchemy์์ DB์์ ํธ๋์ญ์ ๋จ์๋ก ์์ ํ๊ธฐ ์ํ ํต์ฌ ๊ฐ์ฒด
- class_ : ์์ฑ๋ ์ธ์ ํด๋์ค์ ๋ถ๋ชจ ํด๋์ค
- bind : ์ฐ๊ฒฐ ์์ง์ ์ง์ ํ์ฌ ์ธ์ ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ฐ์ธ๋ฉ
- autoflush : ์ธ์ ์์ ๋ณ๊ฒฝ๋ ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ ์ง ์ฌ๋ถ๋ฅผ ์ค์
- autocommit : ์ธ์ ์ด ์๋์ผ๋ก ์ปค๋ฐํ ์ง ์ฌ๋ถ๋ฅผ ์ค์
- expire_on_commit : ์ปค๋ฐ ํ์ ์ธ์ ์์ ๋ก๋๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ฃ(expire)ํ ์ง ์ฌ๋ถ๋ฅผ ์ค์
- **kwargs : ์ถ๊ฐ์ ์ธ ์ค์ ์ ์ํ ๋งค๊ฐ๋ณ์
declarative_base(cls=object,name='Base',metadata=None,mapper=None,class_registry=None,metaclass=DeclarativeMeta)
- ORM์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ํ์ด์ฌ ํด๋์ค๋ฅผ ๋งคํํ๊ธฐ ์ํ ๊ธฐ๋ณธ ํด๋์ค ์์ฑ
- cls : ์์ฑ๋ ๊ธฐ๋ณธ ํด๋์ค์ ๋ถ๋ชจ ํด๋์ค
- name : ์์ฑ๋ ๊ธฐ๋ณธ ํด๋์ค์ ์ด๋ฆ
- metadata : ๋ฉํ๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ์ง์ ํ์ฌ ํ ์ด๋ธ ์์ฑ, ๋ณ๊ฒฝ ๋ฑ์ ์์ ์ ์ํํ ์ ์์
- mapper : ์ฌ์ฉ์ ์ ์ ๋งคํผ๋ฅผ ์ง์ ํ์ฌ ํด๋์ค์ ํ ์ด๋ธ ๊ฐ์ ๋งคํ์ ์ ์ํ ์ ์์
- class_registry : ํด๋์ค ๋ ์ง์คํธ๋ฆฌ ๊ฐ์ฒด๋ฅผ ์ง์ ํ์ฌ ํด๋์ค๋ค์ ๋ฑ๋กํ ์ ์์
- metaclass : ์์ฑ๋ ํด๋์ค์ ๋ฉํํด๋์ค๋ฅผ ์ง์
2. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ ์์ฑ - models.py
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ SQLAlchemy ๋ชจ๋ธ ์์ฑ์ ์ํด database.py์์ ์์ฑํ Base ํด๋์ค๋ฅผ ๊ฐ์ ธ์ ์์ํ์ฌ ๋ชจ๋ธ์ ์ ์ํ๋ค.
# Table type
from sqlalchemy import Column, Integer, VARCHAR
# ํ
์ด๋ธ ๊ด๊ณ๋ฅผ ์ํ import
# from sqlalchemy.orm import relationship
from sql_app.database import Base
class User(Base):
"""
User Model
- id(INT,PRI,UNIQ,NOT NULL): id
- username(String,UNIQ,NOT NULL) : login id
- password(String,UNIQ,NOT NULL) : login pw, save to SAH2 HashData
- nickname(String,UNIQ,NOT NULL) : nickname
Description : User Table
"""
# SQLAlchemy์๊ฒ ๊ฐ ๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฌ์ฉํ ํ
์ด๋ธ ์ด๋ฆ
__tablename__ = "User"
idUser = Column(Integer, primary_key=True, unique=True,
nullable=False, autoincrement=True)
username = Column(VARCHAR(45), unique=True, nullable=False)
password = Column(VARCHAR(64), nullable=False)
nickname = Column(VARCHAR(45), unique=True, nullable=False)
"""
- ํ
์ด๋ธ๊ฐ ๊ด๊ณ ์ค์ -
relationship(target, **kwargs)->target ๋ชจ๋ธ์ ์กฐ์ํ ์ ์๋ ๊ฐ์ฒด
- back_populates : ๊ด๊ณ์ ๋ฐ๋์ชฝ์์ ํด๋น ๊ด๊ณ๋ฅผ ์ญ์ฐธ์กฐํ ์ ์๋ ์์ฑ์ ์ง์ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์๋ฐฉํฅ ๊ด๊ณ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
- uselist : ๊ด๊ณ๊ฐ ๋ฆฌ์คํธ(list) ํํ์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋
๋๋ค. ๊ธฐ๋ณธ๊ฐ์ True์ด๋ฉฐ, False๋ก ์ค์ ํ๋ฉด ๊ด๊ณ๊ฐ ์ค์นผ๋ผ(scalar) ํํ๋ก ์ ์๋ฉ๋๋ค.
- lazy : ๊ด๊ณ์ ๋ก๋ฉ ์ ๋ต์ ์ง์ ํฉ๋๋ค. select๋ก ์ค์ ํ๋ฉด ํ์ํ ๊ฒฝ์ฐ์๋ง ๊ด๋ จ๋ ๊ฐ์ฒด๋ฅผ ๋ก๋ฉํ๊ณ , joined๋ก ์ค์ ํ๋ฉด ์ฟผ๋ฆฌ ์ ์กฐ์ธ์ ์ฌ์ฉํ์ฌ ํ ๋ฒ์ ๊ด๋ จ๋ ๊ฐ์ฒด๋ฅผ ๋ก๋ฉํฉ๋๋ค.
- cascade : ๊ด๊ณ๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ์ฐ๊ด๋ ๊ฐ์ฒด์ ์ด๋ค ๋์์ ์ ํํ ์ง ์ง์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, delete๋ฅผ ์ค์ ํ๋ฉด ๋ถ๋ชจ ๊ฐ์ฒด๊ฐ ์ญ์ ๋ ๋ ์ฐ๊ด๋ ์์ ๊ฐ์ฒด๋ ํจ๊ป ์ญ์ ๋ฉ๋๋ค.
"""
3. Pydantic ๋ชจ๋ธ ๋ง๋ค๊ธฐ - schemas.py
์ฝ๊ธฐ์ฉ ๋ชจ๋ธ์ ์ํด pydantic ๋ชจ๋ธ์ ๋ง๋ ๋ค.
from typing import List, Union
from pydantic import BaseModel
# Only One User
class UserIn(BaseModel):
"""
UserIn
- id : int or None
- passwd : str
- username : str
- nickname : str
"""
idUser: Union[int, None] = None
username: str
password: str
nickname: str
class Config:
from_attributes = True
class UserOut(BaseModel):
"""
UserOut
- id : int
- username : str
- nickname : str
"""
idUser: int
username: str
nickname: str
class Config:
from_attributes = True
# Users
class UsersOut(BaseModel):
"""
UsersOut
- users : List[UserOut]
"""
users: List[UserOut]
class Config:
from_attributes = True
์ฌ๊ธฐ์ orm_mode๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ์ ํ๋์ Pydantic ๋ชจ๋ธ์ ํ๋๋ฅผ ์ผ์น์์ผ, ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๋นํ์ฑํํ๊ณ ORM๊ณผ ํจ๊ป ์ฌ์ฉํ ๋ ๋ฐ์ดํฐ์ ๋ณํ ๋ฐ ์ง๋ ฌํ ์์ ์ ๋ณด๋ค ํธ๋ฆฌํ๊ฒ ์ํํ ์ ์๋ค.
4. CRUD ์ ํธ๋ฆฌํฐ - crud.py
CRUD๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ดํฐ๋ฅผ "์์ฑ, ์ฝ๊ธฐ, ์ ๋ฐ์ดํธ, ์ญ์ " ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
from sqlalchemy.orm import Session
from sql_app import models, schemas
from hashlib import sha256
# Create
def createUser(db: Session, user: schemas.UserIn):
sha2PW = sha256(user.password.encode()).hexdigest()
db_user = models.User(
idUser=user.idUser,
username=user.username,
password=sha2PW,
nickname=user.nickname
)
db.add(db_user) # ์ธ์คํด์ค๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์
์ ์ถ๊ฐ
db.commit() # ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฐ
db.refresh(db_user) # ์์ฑ๋ ID์ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋๋ก ์ธ์คํด์ค ์์ฑ
return db_user
# Read
def getUser(db: Session, username: str):
return db.query(models.User).filter(models.User.username == username).first()
def getUsers(db: Session):
return db.query(models.User).offset(0).limit(100).all()
# Update
def updateUser(db: Session, target: models.User, updateUser: schemas.UserIn):
# To Dict
updatedData = updateUser.model_dump(exclude_unset=True)
if updatedData['username'] != target.username:
target.username = updatedData['username']
if updatedData['password'] != target.password:
target.password = updatedData['password']
if updatedData['nickname'] != target.nickname:
target.nicknmae = updatedData['nickname']
db.commit()
db.refresh(target)
return target
# Delete
def deleteUser(db: Session, target: models.User):
db.delete(target)
db.commit()
return target
5. ์ฌ์ฉ - main.py
from typing import Union, Dict
from sql_app import crud, models, schemas
from sql_app.database import r_SessionLocal, cud_SessionLocal, r_engine, cud_engine, R_SQLALCHEMY_DATABASE_URL, CUD_SQLALCHEMY_DATABASE_URL
from sqlalchemy.orm import Session
from fastapi import FastAPI, Depends, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware
models.Base.metadata.create_all(bind=r_engine)
models.Base.metadata.create_all(bind=cud_engine)
app = FastAPI()
# Dependency
app.add_middleware(
CORSMiddleware,
allow_origins=[R_SQLALCHEMY_DATABASE_URL, CUD_SQLALCHEMY_DATABASE_URL],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
def get_rdb():
db = r_SessionLocal()
try:
yield db
finally:
db.close()
def get_cuddb():
db = cud_SessionLocal()
try:
yield db
finally:
db.close()
@app.post('/user/', response_model=schemas.UserOut, tags=['GET'])
async def get_user(username: str, db: Session = Depends(get_rdb)):
result = crud.getUser(db, username=username)
if result is None:
raise HTTPException(status_code=404, detail="User not found")
return result
@app.post('/users/', response_model=schemas.UsersOut, tags=['GET'])
async def get_users(db: Session = Depends(get_rdb)):
results = crud.getUsers(db)
return {"users": results}
forms = {
'target': {'min_length': 5, 'max_length': 15, 'title': 'Target', 'description': "Change Target ID"},
'username': {'min_length': 5, 'max_length': 15, 'title': 'ID', 'description': "Input ID"},
'password': {'min_length': 8, 'max_length': 15, 'title': 'Password', 'description': "Input Password"},
'nickname': {'min_length': 2, 'max_length': 12, 'title': 'NickName', 'description': "Input Nickname"}
}
@app.post('/signin/', response_model=Union[schemas.UserOut, Dict[str, str]], tags=['Create'])
async def signin(
username: str = Form(..., **forms['username']),
password: str = Form(..., **forms['password']),
nickname: str = Form(..., **forms['nickname']),
rdb: Session = Depends(get_rdb),
cuddb: Session = Depends(get_cuddb)):
result = crud.getUser(rdb, username=username)
if (result.username if result is not None else None) == username:
return {'msg': "ID is Already...."}
else:
user = schemas.UserIn(
username=username, password=password, nickname=nickname)
result = crud.createUser(cuddb, user)
return result
@app.post('/update/', response_model=Union[schemas.UserOut, Dict[str, str]], tags=['Update'])
async def update(
target: str = Form(..., **forms['target']),
username: str = Form(..., **forms['username']),
password: str = Form(..., **forms['password']),
nickname: str = Form(..., **forms['nickname']),
rdb: Session = Depends(get_rdb),
cuddb: Session = Depends(get_cuddb)):
result = crud.getUser(rdb, username=target)
if result == None:
return {'msg': "No Signed Sign In Please....."}
else:
# cud ์ธ์ ์ result ์ถ๊ฐ
rdb.expunge(result) # ์ธ์
๋ถ๋ฆฌ
cuddb.add(result) # ์ธ์
์ถ๊ฐ
user = schemas.UserIn(
username=username, password=password, nickname=nickname)
result = crud.updateUser(cuddb, result, user)
return result
@app.post('/delete/', response_model=Union[schemas.UserOut, Dict[str, str]], tags=['Delete'])
async def deleteUser(username: str, rdb: Session = Depends(get_rdb), cuddb: Session = Depends(get_cuddb)):
result = crud.getUser(rdb, username=username)
if result == None:
return {'msg': "No User..."}
else:
# cud ์ธ์ ์ result ์ถ๊ฐ
rdb.expunge(result) # ์ธ์
๋ถ๋ฆฌ
cuddb.add(result) # ์ธ์
์ถ๊ฐ
result = crud.deleteUser(cuddb, result)
return result
jsonable_encoder
FastAPI๋ JSON ํธํ ๊ฐ๋ฅ ๋ฐ์ดํฐ๋ง ์์ ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํต์ ์ ์ํด jsonable_encoder() ํจ์๋ฅผ ์ ๊ณตํ๋ค.
ex. datetime ๊ฐ์ฒด๋ JSON๊ณผ ํธํ๋์ง ์์ผ๋ฏ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ถ๊ฐ๋ฅ, str๋ก ๋ณํ๋์ด์ผ ํจ.
jsonable_encoder()์ ๊ฐ์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๋ฉด ์๋์ผ๋ก ๋ฐ๊ฟ์ค๋ค.
from fastapi.encoders import jsonable_encoder
from datetime import datetime
from pydantic import BaseModel
class Model(BaseModel):
title: str
timestamp: datetime
desc: str
d = datetime.now()
m = Model(title='HI', timestamp=d, desc="test....")
print(f'{d}-> type: {type(d)}')
print(f'{m}-> type: {type(m)}')
print()
print(f'{jsonable_encoder(d)}-> type: {type(jsonable_encoder(d))}')
print(f'{jsonable_encoder(m)}-> type: {type(jsonable_encoder(m))}')
Model์ dict์ผ๋ก datetime์ str๋ก ๋ฐ๊ฟ์ค๋ค.
'Back-end & Server > FastAPI' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[FastAPI] Background Task (0) | 2024.02.23 |
---|---|
[FastAPI] ํ์ผ ๋ถํ (0) | 2024.02.23 |
[FastAPI] ๋ฏธ๋ค์จ์ด (0) | 2024.02.20 |
[FastAPI] ๊ฒฝ๋ก ์๋ ์ค์ (0) | 2024.02.20 |
[FastAPI] Form (0) | 2024.02.20 |