[FastAPI] 전체 흐름 정리하기2 + 종속성(dependency)

hodu·2023년 1월 12일
1

fastapi

목록 보기
2/2

fastapi 흐름을 다시한번 정리해보고 종속성에 대한 이야기를 해보고자 한다.


우선 용어와 더불어 전체적인 정리를 먼저 정리하고 가자

세션

  • 데이터베이스 접속을 시작으로 여러가지 작업을 수행한 뒤 접속 종료까지 전체 기간을 의미한다.

즉 이번 게시글에서 다루는 session은 데이터베이스의 접속 시작부터 종료까지 일련 과정을 일컫는 것이다.

그러면 코드를 다시 봐보자.

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 통신 설정

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

어떤 DB와 어떻게 연결할 것인지 설정을 먼저 해주어야 한다.
database.py파일에서 create_engine 객체에 다양한 설정값을 넣어서 설정을 해준다.

  • SQLALCHEMY_DATABASE_URL : 어떤 데이터 베이스를 사용할 것인지
  • connect_args={"check_same_thread": False} : SQLite를 사용하게 되면 넣어주어야 함. SQLite는 스레드 통신이 불가능하므로 해당 옵션으로 스레드가 하나만 돌게끔 만들어주어야 한다.
    ✅ SQLite에서는 각 스레드가 독립적인 요청을 처리한다고 가정함, 그래서 하나의 스레드만 통신할 수 있도록 허용한다. 왜냐하면 실수로 동일한 연결을 공유해버릴 경우 충돌이 날 수 있기 때문이며 이를 방지하기 위함.

sessionmaker : DB 세션 설정

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
  • sessionmaker를 이용하여 SessionLocal 클래스를 생성한다.
  • 이때 클래스 자체는 아직 데이터베이스 세션이 아니다.
    단지 sessionmaker를 이용한 설정값을 넘겨받아서 선언된 새로운 클래스다.
  • 그러면 언제 세션이 되느냐? 👉 SessionLocal 클래스의 인스턴스 객체가 생성되고 호출이
    되면 그 인스턴스 객체가 실질적인 데이터베이스 세션 역할을 하게된다!
  • 클래스를 받아오는 변수를 Session이 아니라 SessionLocal이라는 이름으로 지은 이유 ? 👉 나중에 SQLAlchemy에서 Session이라는 이름을 사용하게 됨. 그것과 구분짓기 위해서임

🤔 왜 세션이 아니라 클래스로 만들어줄까?

세션으로 바로 만들어줘도 될 것 같은데 왜 sessionmaker는 "클래스"를 반환하는걸까?

Each instance of the SessionLocal class will be a database session. The class itself is not a database session yet.
But once we create an instance of the SessionLocal class, this instance will be the actual database session.
We name it SessionLocal to distinguish it from the Session we are importing from SQLAlchemy.
https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-sessionlocal-class

바로 sessionmaker안에 있는 인자들을 매번 호출할 필요 없이 SessionLocal을 이용해서 인스턴스를 만들고자 함이다.

declarative_base()

Base = declarative_base()

# schema.py
class User(Base):
    __tablename__ = "users"

declarative_base()는 상속 클래스들을 자동으로 인지하고 알아서 매핑해준다. sessionmaker()와 마찬가지로 "클래스"를 리턴해주게 됨.

이제 SessionLocal과 마찬가지로 Base도 클래스이다.

🤔 그럼 결국 'Base' 가 뭐하는건데?

declarative_base()를 통해서 Base라는 클래스를 만들었다.
Base클래스의 용도는 Table Object와 mapper()함수를 제공하는 것이다.

Construct a base class for declarative class definitions.
The new base class will be given a metaclass that produces appropriate Table objects and makes the appropriate mapper() calls based on the information provided declaratively in the class and any subclasses of the class.
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#sqlalchemy.ext.declarative.declarative_base

  • Table Object : 데이터베이스 테이블
  • mapper : 사용자가 정의한 클래스와 테이블 메타데이터를 연결해준다.

즉, 사용자가 정의한 클래스를 가지고 테이블을 매핑시켜준다고 생각하면 될 것같다.

그리고 Base 클래스를 더 구체적으로 customize 할 수 있음

from sqlalchemy.ext.declarative import as_declarative

@as_declarative()
class Base(object):
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()
    id = Column(Integer, primary_key=True)

정리하기

이제까지 얘기했던 내용들의 흐름은 아래 사진을 참고하자.

세션 객체

  • 위에서 sessionmaker를 이용하여 SessionLocal 클래스까지 만들어주었다. 그럼 이 인스턴스 객체를 만들려면 어떻게 해야할까?
# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

이렇게 작성하면 된다.

  • get_db는 호출될 때 마다 db라는 변수를 생성해냄. 이 db는 SessionLocal 클래스의 인스턴스이다.
  • 이렇게 생성된 db는 데이터베이스 세션으로 동작하며 한 번의 db연결동안만 존재한다. 모든 연결과 응답이 종료되면(get_db를 호출한 함수가 종료되면) finally를 이용하여 close가 된다.
  • yield문을 이용하기 때문에 get_db를 이용한다고 해도 전체를 종료하지 않고 메인 루틴으로 돌아가게 된다.

SessionLocal vs Session

그러면 SessionLocal과 Session의 차이점은 무엇일까?

1. Session Local

SessionLocal은 위에서 설명한대로 단지 클래스이며 인스턴스가 생성이 되었을 때 DB세션을 생성해주는 "클래스"이다.

2. Session

Session은 Type Hinting을 위한 것. 엔드포인트에서 Session의 자동완성 기능을 사용하기 위해 Session을 사용하고 있다.

def get_series_content(
		*,
        ...(생략)...
        db: Session = Depends(deps.get_db)) -> Any:

여기서 Type Hinting을 Session으로 주고 있으므로 기존 Session에서 사용하던 자동완성 기능이 제공된다.

왜 굳이 Session을 사용할까? sessionmaker로 초기화 된 SessionLocal은 create_engine을 바인딩하여 Session을 매핑한 클래스인데, 어짜피 SessionLocal이 생성하는 객체는 SQLAlchemy의 Session 객체이다.

다만 sessionmaker로 감싸져있기 때문에 IDE가 Session임을 인지하지 못하는 것이다.


참고 링크

profile
안녕 세계!

0개의 댓글