[FastAPI] 5. Database & SQLAlchemy & Pydantic

Judy·2023년 7월 14일
0

FastAPI

목록 보기
5/8

번역 서비스의 백엔드에서는 문장을 번역하는 일 이외에 번역 결과를 저장하는 DB 작업도 수행합니다.
사용자가 입력한 문장과 번역 결과 등을 DB에 저장해 두었다가 추후에 모델 추가 학습 또는 품질 개선에 이용할 수 있거든요.

이번 토이 프로젝트에서는 CRUD 작업 중 Create, Read 만 구현하려 합니다.

(참고) 간략한 SQLAlchemy 정리

RDBMS

저는 간단한 토이프로젝트이기 때문에 RDBMS 로 MySQL 을 이용하겠습니다.
MongoDB 는 문서를 다루기에 좋지만 저의 프로젝트에서는 문서를 이용하지 않으므로 장점이 될 수 없고,
Postgre 도 널리 사용되고 FastAPI 공식문서에서 이용하고 있지만 제가 써 본 적이 없어서 따로 익혀서 써야 합니다.
결과적으로 이번 토이프로젝트는 아주 작은 프로젝트이므로 새 RDBMS 를 사용하는 것이 리소스 대비 효율이 좋지 않다고 판단했고,
개인적으로 프로젝트 수행 기한이 촉박해서 기존에 써 보았던 MySQL 을 선택했습니다.

추가로 DB 관리 툴은 현업에서 많이 사용하는 DBeaver 를 이번에 처음으로 사용했습니다.

ORM

개념

ORM(Object-relational mapping)은 객체지향 프로그래밍(Object-Oriented-Programming)과 관계형 데이터베이스(Relational-Database)사이의 호환되지 않는 데이터를 변환하는 기술입니다. 즉, ORM은 이렇게 서로 다른 객체와 관계형 데이터 시스템(RDBMS)을 연결해주는 중간 매개체 역할을 합니다.

장점

ORM을 사용하며 생기는 장점은 선언문, 할당, 종료 등과 같은 부수적인 코드가 사라짐으로써 개발의 생산성이 향상된다는 점입니다. 그리고 문법(Syntax) 실수 없이 잘 짜여진 SQL 쿼리문을 ORM의 힘을 빌려 사용하는 것이기 때문에 버그와 실수가 감소하게 되며, 개발자 친화적인 간결하고 직관적인 코드작성이 가능해집니다. 이로써 가독성 또한 높아지는 효과를 누릴 수 있습니다.

그리고 대부분의 ORM 서비스는 특정 RDBMS(예시; mysql)에만 종속되는 것이 아니라 다양한 데이터베이스에 유연하게 연결 될 수 있습니다. 따라서 개발자는 어떠한 데이터베이스 시스템을 선택했는지 상관없이 코드 속에서 다루어야 하는 데이터 객체 위주로 집중할 수 있는 이점이 있습니다.

단점

하지만 이런 ORM도 사용에 있어서 물론 단점이 존재합니다. 미세한 수정 및 디버깅이 상대적으로 어려워지며, 복잡한 쿼리문 작성이 필요할 때는 ORM에서 미리 선언해 놓은 패턴에 따른 SQL 쿼리 명령만 내릴 수 있다는 점입니다. 따라서 내가 의도한 목표치에 가장 가까운 지름길이 아닌 먼 길을 돌아가야하는 경우가 생길 수 있습니다.

아울러, DB에 직접 명령을 내리지 않고 한단계 중간 단계를 거쳐가기 때문에 SQL Raw 쿼리문에 비해 실행 속도가 다소 느려질 수 있습니다. 왜냐하면 ORM은 데이터베이스 드라이버(mysqlclient, mysql2)를 한번 추상화(드라이버와 ORM 사이에 중간 수준)하고 이를 다시 한 번 추상화하는 고수준의 추상화 개념이 적용되기 때문입니다.

이밖에도, RDBMS와의 데이터 관련 상호작용 내역을 Black Box(외부에서 쉽게 확인할 수 없게 숨기는 기능)로 만들어 버리기 때문에 내부에서 실제 어떤 쿼리를 실행하는지 파악하기 쉽지 않습니다. 그리고 경우에 따라서 2차원 테이블의 레코드를 1건만 읽어도 될 쿼리를 불필요하게 수 천, 수 만 레코드를 읽게하여 과도하게 많은 쿼리를 생성하는 경우도 발생할 수 있습니다.

SQLAlchemy

공식문서에서는 일반적인 ORM 의 예로 Django-ORM, SQLAlchemy ORM 등을 제시한 후
예시로 SQLAlchemy ORM 을 이용하고 있습니다.
SQLAlchemy 는 python 에서 이용 가능한 ORM 이므로 FastAPI 뿐만이 아니라 Flask 로 개발할 때에도 쓰입니다.

Tutorial

File Tree

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

Create the SQLAlchemy parts

app/database.py

  1. 우선 DB에 연결하기 위해 URL 을 지정합니다.

  2. SQLAlchemy 엔진을 생성합니다.
    데이터베이스 엔진은 데이터베이스와 상호작용하기 위한 핵심적인 구성 요소로 데이터베이스에 대한 접근, 데이터의 저장 및 검색, 쿼리 실행 등의 작업을 처리합니다.

  3. SessionLocal class를 생성합니다.

  • DB Session?
    • 데이터베이스 접속을 시작으로, 여러 작업을 수행한 후 접속 종료까지의 전체 기간
    • 세션 내부에는 하나 이상의 트랜잭션이 존재한다
    • 일반적으로 데이터베이스는 여러 곳에서 동시에 접근하여 수많은 세션이 동시에 연결되어 있다
  • 세션은 SQLAlchemy에서 데이터베이스와 상호작용하는 단위입니다.
  • 세션을 통해 데이터베이스에 대한 작업을 수행하고, 변경사항을 관리하고, 트랜잭션을 제어할 수 있습니다.
  • 세션 객체는 코드와 데이터베이스 사이에서 이루어지는 처리를 관리하며 주로 특정 처리를 데이터베이스에 적용하기 위해 사용됩니다.
  • 각각의 SessionLocal 클래스 객체는 데이터베이스의 세션이 된다.
  • 추후 사용할 Session 과 이름 충돌을 막기 위해 변수명은 SessionLocal 로.
  • Session class는 SQL 엔진의 인스턴스를 인수로 사용합니다. (bind=engine)
  1. Base class 를 생성합니다.
    나중에 Base class 를 상속받아 DB 모델이나 ORM 클래스를 생성합니다.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 1. SQLAlchemy 용 DB URL 생성
# mysql db에 연결
SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{username}:{password}@{host}:{port}/{db_name}?charset=utf8"

# 2. 첫 번째 단계는 SQLAlchemy "엔진"을 만드는 것입니다.
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

# 3. Make SessionLocal class
# 생성한 SQLAlchemy engine 을 물려서(?) 세션을 생성합니다.
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 4. Base class 생성
Base = declarative_base()

Create the database models (in SQLAlchemy)

app/models.py

Base class 를 이용해 SQLAlchemy 을 생성합니다.
앞서 database.py 에서 생성한 Base class 를 상속받아 새 클래스를 만듭니다.

Tip
SQLAlchemy는 "Model" 이라는 용어를 사용하여 데이터베이스와 상호 작용하는 클래스 및 인스턴스를 나타냅니다.
그러나 Pydantic은 "Model" 이라는 용어를 사용하여 데이터 유효성 검사, 변환, 문서 클래스 및 인스턴스와 같은 다른 것을 참조합니다.

from sqlalchemy import Column, Integer, TEXT, VARCHAR, TIMESTAMP
from sqlalchemy.sql import func

# databasedatabase.py 로부터 Base class 를 가져와서
from .database import Base

# Base class 를 상속받아 Translate class 를 만듭니다.
# (이러한 클래스는 SQLAlchemy 모델입니다.)
class Translate(Base):
	# 데이터베이스에서 사용할 테이블의 이름을 SQLAlchemy에 알려줍니다.
    __tablename__ = "translate"

	# 모델 클래스의 attribute/column 을 만듭니다.
    id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
    src_lang = Column(VARCHAR(3), nullable=False)
    src = Column(TEXT, nullable=False)
    tgt_lang = Column(VARCHAR(3), nullable=False)
    mt = Column(TEXT, nullable=False)
    created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
    updated_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())

테이블 간 relationship 을 생성하는 부분은 우선 생략하겠습니다.

Pydantic?

FastAPI 는 데이터의 유효성을 검사하고, 데이터를 자동으로 파싱하기 위해 pydantic library 를 활용합니다.
pydantic 의 BaseModel 을 상속받아 데이터 검증 및 파싱을 하는 모델을 만들게 되지요.
'Pydantic 모델을 이용해 스키마를 정의합니다.' 라고도 표현해요.

혼동 금지🤣
name = Column(String) : SQLAlchemy model
name : str : Pydantic model

Create the Pydantic models

Pydantic 모델을 이용해 스키마를 정의합니다.

app/schemas.py

우선 데이터를 생성하거나 읽기 위해 공통 속성을 갖는 Pydantic TranslateBase model(또는 schema) 을 생성 합니다

이어서 Create, 즉 DB에 데이터를 추가하기 위해
TranslateBase 를 상속받아 TranslateCreate 모델을 만듭니다.

from pydantic import BaseModel

class TranslateBase(BaseModel):
    sl : str = "ko"
    tl : str = "en"
    text : str = "내 안의 불꽃들로 이 밤을 찬란히 밝히는 걸 지켜봐"
    
class TranslateCreate(TranslateBase):
    mt: str = "Watch me bring the fire and set the night alight"

정리

SQLAlchemy model

  • ORM 라이브러리
  • DB에 CRUD 할 때 사용
class Translate(Base):
    __tablename__ = "translate"

    id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
    src_lang = Column(VARCHAR(3), nullable=False)
    src_text = Column(TEXT, nullable=False)
    tgt_lang = Column(VARCHAR(3), nullable=False)
    mt_text = Column(TEXT, nullable=False)
    created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
    updated_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())

Pydantic model

  • 데이터 유효성 검사 라이브러리 (자료형 일치 여부 등)
  • 데이터 값을 검증할 때 사용
class TranslateBase(BaseModel):
    sl : str = "ko"
    tl : str = "en"
    text : str = "내 안의 불꽃들로 이 밤을 찬란히 밝히는 걸 지켜봐"

Reference

공식문서
https://fastapi.tiangolo.com/tutorial/sql-databases/#__tabbed_8_2
[Fastapi]SQLAlchemy 이용하여 DB와 연결하기
https://velog.io/@crosstar1228/FastapiSQLAlchemy-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-DB%EC%99%80-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0

profile
NLP Researcher

0개의 댓글