이전포스트 에서 이어진다…
지금은 MySQL + SQLAlchemy 설정까지는 끝났고, 오프라인 데이터베이스 마이그레이션을 하기위해 alembic
을 설정하려고 한다.
pip install alembic
pip freeze > requirements.txt
혼자 하는 프로젝트지만 나중에 dockerize해야하기 때문에 requirements.txt
도 잘 관리해야 한다. 여담이지만 파이썬 레포를 생성하면 무조건 conda를 사용해서 가상환경부터 만드는 습관이 있다. 이제 alembic을 적용해본다.
alembic init alembic
그러면 아래와 같은 메세지가 나온다
Creating directory /Users/byeongjinkang/personal/development/mukkang_server/alembic ... done
Creating directory /Users/byeongjinkang/personal/development/mukkang_server/alembic/versions ... done
Generating /Users/byeongjinkang/personal/development/mukkang_server/alembic/script.py.mako ... done
Generating /Users/byeongjinkang/personal/development/mukkang_server/alembic/env.py ... done
Generating /Users/byeongjinkang/personal/development/mukkang_server/alembic/README ... done
Generating /Users/byeongjinkang/personal/development/mukkang_server/alembic.ini ... done
Please edit configuration/connection/logging settings in '/Users/byeongjinkang/personal/development/mukkang_server/alembic.ini'
before proceeding.
이제 디렉토리 구조는 아래와 같이 바뀐다
app
├── __init__.py
├── main.py
├── alembic
│ ├── env.py
│ ├── script.py.mako
│ └── versions
├── alembic.ini
├── routers
│ ├── __init__.py
│ └── users.py
└── sql
├── __init__.py
├── crud.py
├── database.py
├── models.py
└── schemas.py
이제 위 메세지에서 나온 .ini
파일을 수정한다. 가장 먼저 database.py
에 선언했던 데이터베이스 인터페이스 주소를 입력한다.
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = mysql+mysqlconnector://root:password@localhost:3306/database
나중에 환경변수화 해야겠지만 일단은 그냥 넣어둔다.
장고에서 사용하는
python manage.py makemigrations
와 유사한 기능을 하는 명령어는
alembic revision --autogenerate -m "CUSTOM MESSAGE"
이다.
autogenerate
를 사용하지 않으려면 revision파일을 만들고 일일이 테이블을 선언해줘야 하는데, 이러면 models.py
에 작성한 내용을 포맷에 맞게 입력해야한다. 따라서 autogenerate
를 사용하기 위해서 alembic 디렉토리 내 env.py
의 target_metadata
라는 변수를 추가로 설정해줘야한다.
예제를 보면 model
에서 Base
를 import 한다. 따라서 기존에 database.py
에서 선언한 declarative_base
를 models.py
로 옮기고 env.py
파일을 업데이트 한다
# env.py
from sql.models import Base
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# models.py
from sqlalchemy import (
Boolean,
Column,
Date,
Integer,
String,
)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
password = Column(String)
is_active = Column(Boolean, default=True)
phone = Column(String, unique=True)
birth = Column(Date, unique=True)
gender = Column(String, default='M')
그러고 위에 명령어를 다시 날려준다
alembic revision --autogenerate -m "Added user table"
위 명령어를 실행하면 MySQL 서버에 데이터베이스가 없다는 에러가 난다. 일단 데이터베이스를 먼저 만들어주고 명령어를 다시 돌린다. 장고 마이그레이션 파일과 유사한 파일이 생긴다
# alembic/versions/
"""Added user table
Revision ID: 3829d0b7507e
Revises:
Create Date: 2022-07-06 00:09:19.842063
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3829d0b7507e'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(), nullable=True),
sa.Column('password', sa.String(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('phone', sa.String(), nullable=True),
sa.Column('birth', sa.Date(), nullable=True),
sa.Column('gender', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('birth'),
sa.UniqueConstraint('phone')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
# ### end Alembic commands ###
이제 마이그레이션을 디비에 적용한다.
alembic upgrade head
이러면 또 에러가 발생하는데,
(in table 'users', column 'email'): VARCHAR requires a length on dialect mysql
FastAPI에서 제공한 예제는 SQLITE 용이고, mysql은 string에서 length를 지정해줘야 하기 때문이다.
따라서 models.py
를 수정한다
# models.py
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255), unique=True, index=True)
password = Column(String(255))
is_active = Column(Boolean, default=True)
phone = Column(String(13), unique=True)
birth = Column(Date, unique=True)
gender = Column(String(1), default='M')
기존 파일을 지우고 이제 다시 autogenerate를 해본다
alembic revision --autogenerate -m "Added user table"
String
들에 length 정보가 추가된 것을 확인할 수 있다.
# /versions
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=True),
sa.Column('password', sa.String(length=255), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('phone', sa.String(length=13), nullable=True),
sa.Column('birth', sa.Date(), nullable=True),
sa.Column('gender', sa.String(length=1), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('birth'),
sa.UniqueConstraint('phone')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
# ### end Alembic commands ###
이제 마이그레이션을 디비에 적용한다.
alembic upgrade head
MySQL에도 잘 적용된 것을 확인할 수 있다.
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| email | varchar(255) | YES | UNI | NULL | |
| password | varchar(255) | YES | | NULL | |
| is_active | tinyint(1) | YES | | NULL | |
| phone | varchar(13) | YES | UNI | NULL | |
| birth | date | YES | UNI | NULL | |
| gender | varchar(1) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
서버도 잘 돌아가고 Hello World!도 잘 찍힌다. 이어서 다음 포스트에서는 회원가입을 다뤄보겠다.