[FastAPI] 전체 흐름 정리하기

hodu·2023년 1월 11일
4

fastapi

목록 보기
1/2

Fastapi에서 api가 동작하는 방법을 정리해보겠다.
크게
1. DB모델 만들기
2. Pydantic 만들기
3. DB 모델과 CRUD 동작하도록 만들기
4. 특정 path로 들어왔을 때 어떤 동작을 할 것인지 만들기

ORM

우선 ORM이 무엇인지 부터 정리해보자.

FastAPI는 라이브러리의 데이터베이스, 스타일이 동작하기 위해서는 데이터베이스와 소통해야 한다.
이때 ORM을 이용하는데 ORM은 Object Relational Mapping 라이브러리이다.

ORM은 객체와 데이터베이스의 관계를 매핑해준다.
예를 들자면 클래스 Pet은 SQL 테이블 pets를 표현할 수 있다.

그리고 해당 클래스의 인스턴스 객체들은 데이터베이스의 행을 나타내게 된다.
Pet의 인스턴스 orion_catorion_cat.type으로 속상 값을 가져올 수 있다.
마찬가지로 orion_cat.name을 하게 되면 이름을 가져올 수 있다.

ORM은 django-ORM, sqlalchemy ORM, peewee 등이 있다.
여기서는 sqlalchemy ORM에 대해서 알아볼 것이다.

Tip

└── sql_app
    ├── __init__.py
    ├── crud.py
    ├── database.py
    ├── main.py
    ├── models.py
    └── schemas.py

여기서 __init__.py는 빈 파일임. 하는일은 Python을 실행할 때 sql_app이 package라는 것을 알려준다.

SQLAlchemy

우선 sqlalchemy를 설치하자.

pip install sqlalchemy

설치를 하고나면 database.py에서 설정이 필요하다.

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

SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
  • create_engine : 엔진을 만듦 -> 커넥션 풀 생성(DB에 접속하는 객체를 일정 갯수만큼 만들어 놓고 돌려가며 사용하는 것)
    커넥션 풀은 DB에 접속하는 세션수를 제어하고, 세션 접속에 소요되는 시간을 줄이고자 하는 용도로 사용한다.
  • declarative_base : 데이터베이스 모델이나 클래스(ORM모델)들을 상속해온다. 상속 클래스들을 자동으로 인지하고 알아서 매핑해줌.
  • sessionmaker : 클래스 자체는 아직 DB세션이 아님 -> sessionmaker를 이용하여 DB세션을 만들어줌

세션? 👉 데이터베이스 접속을 시작으로, 여러 작업을 수행한 후 접속 종료까지의 전체 기간

database Model 만들기

이제 본격적으로 models.py에서 데이터베이스 모델을 만들도록 할 것이다.

from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship

from .database import Base


class User(Base):
    __tablename__ = "users"

	...(생략)

database.py 에서 만든 from .database import Base를 가져오고 이를 상속하는 모델 클래스들을 만든다.

여기서 __tabelname__은 각 모델에 대해 데이터베이스에서 사용할 테이블의 이름을 SQLAlchemy에게 알려주게 됨.

Pydantic

schemas.py파일에다 User가 입력받아야 할 값들을 정의한다.
pydantic을 이용하면 type annotation을 이용하여 데이터를 검증하고 설정들을 관리할 수 있다.

from typing import List, Union
from pydantic import BaseModel

class UserBase(BaseModel):
    email: str
    
class User(UserBase):
    id: int
    is_active: bool
    items: List[Item] = []

    class Config:
        orm_mode = True
...(생략)...

User 클래스 맨 아래에 Config를 이용하여 orm_mode를 정의하고 있을 것이다. 이 속성은 Pydantic 모델이 dict 데이터가 아닌 ORM 모델을 읽도록 지시한다.

orm_mode 사용하지 않을 경우

id = data["id"]

orm_mode를 사용할 경우

id = data.id

추가적으로 pydantic 모델은 response_model 인수로 설정할 수 있다. 사용하게 될 경우 데이터베이스 모델을 읽고 반환한다.

ORM 모드에 대한 추가 정보

SQLAlchemy는 기본적으로 "지연 로딩"을 함

예를 들어

current_user.item

이 명령어를 실행했을 때, item이라는 테이블로 이동하여 current_user에 대한 항목을 가져오지만 실행하지 않으면 가져오지 않는다.

그리고 orm_mode를 사용하지 않으면 관계 데이터들을 가져올 수 없음

CRUD

여기서는 사용자의 요청에 따라(CRUD) 데이터베이스를 CRUD 하도록 한다.

from sqlalchemy.orm import Session
from . import models, schemas

def get_user(db: Session, user_id: int):
    return db.query(models.User).filter(models.User.id == user_id).first()
    
def create_user(db: Session, user: schemas.UserCreate):
    fake_hashed_password = user.password + "notreallyhashed"
    db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user
    
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
    db_item = models.Item(**item.dict(), owner_id=user_id)
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item
...(생략)...
  • add : DB 세션에다 인스턴스 객체를 넣는다.
  • commit : 데이터베이스 변경 사항을 커밋한다.(여기서 저장됨)
  • refresh : 인스턴스를 새로고침한다.

+) 추가

create_user_item

	db_item = models.Item(**item.dict(), owner_id=user_id)

이 부분을 뜯어보자.

**item.dict() 는 item을 dict형태로 변환하고 애스터리스크(*)를 이용하여 username='hello', password='abcd1234'형태로 넘어가게 된다.
단, item은 반드시 schemas.ItemCreate 즉 pydantic 모델로 선언된 값들만 들어갈 수 있다.
선언되지 않은 값(여기서는 user_id)은 따로 넣어주어야 한다.

main.py

이제 url을 연결해주도록 하자.

from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()


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


@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    db_user = crud.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return crud.create_user(db=db, user=user)
    
    
...(생략)...

의존성 만들기

여기서 가장 중요한 내용은 SessionLocal()을 이용하여 dependency를 만들어주고 있다는 것이다.

그럼 이 코드를 찬찬히 뜯어보자.

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

세션을 열어두고 get_db가 call될때 마다 yield문을 통해서 db를 가져오게 된다.
그럼 finally에 있는 db.close()를 이용해서 데이터베이스를 언제 닫을 수 있을까?
바로 get_db를 부른 그 함수의 동작이 끝났을 때이다.

추가적으로 Fastapi에서는 Depend 라는 자체 라이브러리를 이용하여 의존성 주입을 매우 쉽게 할 수 있다.

왜 의존성 주입이 필요할까?, SessionLocal은 무엇일까?
더 자세한 내용은 다음 포스트에서 작성해보도록 하겠다.

여기까지가 Fastapi의 흐름이다.


참고 링크

profile
안녕 세계!

0개의 댓글