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()
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는 스레드 통신이 불가능하므로 해당 옵션으로 스레드가 하나만 돌게끔 만들어주어야 한다.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
세션으로 바로 만들어줘도 될 것 같은데 왜 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을 이용해서 인스턴스를 만들고자 함이다.
Base = declarative_base()
# schema.py
class User(Base):
__tablename__ = "users"
declarative_base()
는 상속 클래스들을 자동으로 인지하고 알아서 매핑해준다. sessionmaker()
와 마찬가지로 "클래스"를 리턴해주게 됨.
이제 SessionLocal과 마찬가지로 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
즉, 사용자가 정의한 클래스를 가지고 테이블을 매핑시켜준다고 생각하면 될 것같다.
그리고 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)
이제까지 얘기했던 내용들의 흐름은 아래 사진을 참고하자.
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
이렇게 작성하면 된다.
get_db
는 호출될 때 마다 db라는 변수를 생성해냄. 이 db
는 SessionLocal 클래스의 인스턴스이다.db
는 데이터베이스 세션으로 동작하며 한 번의 db연결동안만 존재한다. 모든 연결과 응답이 종료되면(get_db를 호출한 함수가 종료되면) finally를 이용하여 close가 된다.그러면 SessionLocal과 Session의 차이점은 무엇일까?
SessionLocal은 위에서 설명한대로 단지 클래스이며 인스턴스가 생성이 되었을 때 DB세션을 생성해주는 "클래스"이다.
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임을 인지하지 못하는 것이다.
참고 링크