Programming/Project Log

Fastapi와 svelte를 이용한 게시판 개발 #2

minarae7 2023. 7. 27. 22:36
728x90
반응형

데이터베이스 연결

데이터베이스에 연결하여서 게시판에서 생성되는 내용을 mysql에 저장하도록 할 것이다.
앞에 Setup.md에서 Database에 연결하기 위한 설정을 해두었다.
이제 관련 내용을 하나씩 채워나가보도록 하겠다.

alembic 초기화

alembic을 사용하기 위해서 다음 명령어를 통해서 초기화를 진행한다.

alembic init migrations

이 명령어를 입력하고 나면 migrations라는 디렉토리가 생성되며 그 디렉토리 안에 alembic을 사용하기 위한 내용들이 채워진다.

alembic 코드 수정

alembic을 사용할 준비를 하였다. 위의 명령어에서 migrations라는 디렉토리를 생성하였고 여기에 필요한 코드와 이후 마이그레이션에서 사용되는 생성 코드들이 저장된다.

alembic.ini

초기화를 실행했던 루트 디렉토리에는 alembic.ini 파일이 생성된다. 여기에는 초기에 데이터베이스에 접근하기 위한 기본 설정이 들어있다.

# sqlalchemy.url = driver://user:pass@localhost/dbname

위의 코드와 같이 sqlalchemy.url로 시작하는 코드를 찾아서 주석 처리를 하도록 한다.

env.py

이제 migrations 디렉토리 아래에 위치한 env.py 코드를 수정할 것이다.
이 파일에 데이터베이스에 접속하기 위해서 DB 접속 정보를 읽어와서 접속하도록 코드를 작성하며, 메타정보를 읽어올 파일을 지정하도록 할 것이다.

# 생략
import os
import models
from starlette.config import Config

from sqlalchemy.ext.declarative import declarative_base

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
Base = declarative_base()


BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(BASE_DIR, '../config/.env')
db_config = Config(CONFIG_FILE)
DB_USER = db_config('DB_USER')
DB_PW = db_config('DB_PW')
DB_HOST = db_config('DB_HOST')
DB_PORT = db_config('DB_PORT')
DB_DATABASE = db_config('DB_DATABASE')

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
if not config.get_main_option("sqlalchemy.url"):
    config.set_main_option(
        "sqlalchemy.url",
        f"mysql+pymysql://{DB_USER}:{DB_PW}@{DB_HOST}:{DB_PORT}/{DB_DATABASE}",
    )

# 생략
target_metadata = models.Base.metadata

# 생략

config/.env 파일을 읽어와서 DB 접속 정보를 만들어서 alembic에서 접속할 url의 주소를 생성하도록 하였다.
더불어 target_metadata가 기존의 None으로 설정되어 있는 것을 기존 Repository에서 복사해온 models로 연결하였다.

이와 같이 코드를 수정하면 alembic 명령어를 통해서 코드에서 models를 수정하면 Database에 적용할 수 있는 준비가 된 것이다.
다음 명령어를 입력해서 정상적으로 작동하는지 확인해보도록 하자.

alembic revision --autogenerate

이 명령어는 models.py를 수정한 후에 테이블에 적용하기 위해서 alembic으로 자동으로 스키마를 생성하도록 하는 명령어이다.
이 명령어를 입력해서 정상적으로 작동하면 코드 수정이 정상적으로 이루어진 것이다.

이 과정이 정상적으로 이루어졌다면 migrations 디렉토리 아래 versions 아래 스키마로 이루어진 파일이 생성되었을 것이다.
이 파일은 queue 형태로 버전이 서로 연결되는 구조를 갖게 된다.

이제 생성된 파일을 삭제하고 models.py 파일을 수정할 것이다.

Schema 적용

원본이 되는 Repository에서는 DB를 sqlite를 사용하였는데 여기서는 mysql을 사용하기로 했다.
mysql에서는 sqlite를 사용할 때와 다르게 각 컬럼을 지정할 때 좀 더 세부적으로 옵션을 지정해야 한다.

예를 들어 다음 코드를 보자

class Question(Base):
    __tablename__ = "question"

    id = Column(Integer, primary_key=True)
    subject = Column(String, nullable=False)
    content = Column(Text, nullable=False)
    create_date = Column(DateTime, nullable=False)
    user_id = Column(Integer, ForeignKey("user.id"), nullable=True)
    user = relationship("User", backref="question_users")
    modify_date = Column(DateTime, nullable=True)
    voter = relationship('User', secondary=question_voter, backref='question_voters')

원본 models.py에서는 Column을 지정할 때 단순히 Integer, Text, String과 같은 형태로만 지정하였다. sqlite를 사용할 때는 이렇게만 지정하여도 무방하다.
하지만 mysql을 사용할 때는 String의 경우 각 컬럼의 길이를 지정하여야 한다.

Question 클래스는 다음과 같이 재정의하였다.

class Question(Base):
    __tablename__ = "question"

    id = Column(INTEGER(unsigned=True), primary_key=True)
    subject = Column(String(length=255), nullable=False)
    content = Column(Text, nullable=False)
    create_date = Column(DateTime, nullable=False)
    user_id = Column(INTEGER(unsigned=True), ForeignKey("user.id"), nullable=True)
    user = relationship("User", backref="question_users")
    modify_date = Column(DateTime, nullable=True)
    voter = relationship('User', secondary=question_voter, backref='question_voters')

String 경우는 length를 지정하여 mysql에서 생성할 때 이 길이를 따르도록 하였다.
더불어 Integer를 선언할 때도 unsigned를 True로 선언하여서 양수만 사용하도록 지정하였다.

아래는 models.py을 mysql에서 사용할 수 있도록 재정의한 코드이다.
참고하면 된다.

from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Table
from sqlalchemy.dialects.mysql import INTEGER
from sqlalchemy.orm import relationship

from database import Base

question_voter = Table(
    'question_voter',
    Base.metadata,
    Column('user_id', INTEGER(unsigned=True), ForeignKey('user.id'), primary_key=True),
    Column('question_id', INTEGER(unsigned=True), ForeignKey('question.id'), primary_key=True)
)

answer_voter = Table(
    'answer_voter',
    Base.metadata,
    Column('user_id', INTEGER(unsigned=True), ForeignKey('user.id'), primary_key=True),
    Column('answer_id', INTEGER(unsigned=True), ForeignKey('answer.id'), primary_key=True)
)


class Question(Base):
    __tablename__ = "question"

    id = Column(INTEGER(unsigned=True), primary_key=True)
    subject = Column(String(length=255), nullable=False)
    content = Column(Text, nullable=False)
    create_date = Column(DateTime, nullable=False)
    user_id = Column(INTEGER(unsigned=True), ForeignKey("user.id"), nullable=True)
    user = relationship("User", backref="question_users")
    modify_date = Column(DateTime, nullable=True)
    voter = relationship('User', secondary=question_voter, backref='question_voters')


class Answer(Base):
    __tablename__ = "answer"

    id = Column(INTEGER(unsigned=True), primary_key=True)
    content = Column(Text, nullable=False)
    create_date = Column(DateTime, nullable=False)
    question_id = Column(INTEGER(unsigned=True), ForeignKey("question.id"))
    question = relationship("Question", backref="answers")
    user_id = Column(INTEGER(unsigned=True), ForeignKey("user.id"), nullable=True)
    user = relationship("User", backref="answer_users")
    modify_date = Column(DateTime, nullable=True)
    voter = relationship('User', secondary=answer_voter, backref='answer_voters')


class User(Base):
    __tablename__ = "user"

    id = Column(INTEGER(unsigned=True), primary_key=True)
    username = Column(String(length=30), unique=True, nullable=False)
    password = Column(String(length=100), nullable=False)
    email = Column(String(length=150), unique=True, nullable=False)

이 내용을 그대로 참조해서 생성하여도 되고 각자 다른 길이를 갖도록 추가적으로 수정하여도 무방하다.
mysql에서 사용할 수 있는 옵션으로 생성하면 된다.

테이블 생성

이제 model.py 파일을 재정의하였으니 실제로 DB에 테이블을 생성하면 된다.
위에서 사용하였던 명령어를 다시 사용해보겠다.

alembic revision --autogenerate

이 명령어를 실행하고 나면 다시 migrations/versions 아래 파일이 새로 생성된다.
새로 생성된 파일을 열어보면 처음에 생성되었던 구조와 약간 차이가 있는 것을 알 수 있다.
이 구조는 mysql에서 사용하기 위한 구조이다.

이제 이렇게 생성된 Table schema를 적용도록 하겠다.

alembic upgrade head

위의 명령어를 입력하면 적용이 안된 파일부터 찾아서 자동으로 Table 수정 Schema가 적용된다.
여기서는 version 파일이 하나만 있으면 해당 version이 적용된다.

이 내용이 정상적으로 동작하였으면 에러 없이 테이블이 생성된 것을 확인할 수 있을 것이다.
이렇게 하고 나면 테이블 생성까지 마무리했고 실제로 서비스를 동작시키기 위한 진행을 하도록 하겠다.

728x90
반응형