1.記事概要
この記事は、PythonだけでWebアプリを作ることができるライブラリ「Reflex」に関する記事です。
今回は、DBマイグレーションの方法を整理しました。公式ドキュメントにある手順を少し詳しく説明しています。
Reflexのマイグレーションは、PythonのDBマイグレーションライブラリの「Almenbic」を間接的に利用しています。
また、ReflexのORMについては「SQLAlchemy」を利用しています。それ故、テーブル定義の詳細な設定をする際には「SQLAlchemy」の設定方法に即して行います。
2.前提条件
手順の前提となる環境について説明します。
(1)開発環境とフォルダ構成
- 開発環境は、VSCodeのDevContainerを利用して構築
- ReflexをはじめPythonライブラリはPoetryを利用してパッケージ管理
- 「project」フォルダを、DevContainerのワークスペースとする
- 「app」フォルダを、Reflexのプロジェクトフォルダとする
※DevContainerの構築方法はこの記事では省略
project
├── .devcontainer
├── app # Reflexのアプリケーションソースを格納
├── poetry.lock
├── pyproject.toml
└── README.md
(2)Reflexのプロジェクトフォルダ(app)を初期化
以下の手順で初期化します。
アプリケーションの名前は「screen」とし、テンプレートはサイドメニューのあるものを採用します。
# Reflexのプロジェクトフォルダに移動
cd app
# Reflexの必要ファイルを作成
poetry run reflex init --name screen --template sidebar
上記の操作で以下のようにReflexのプロジェクトフォルダが初期化されます。
アプリ名として指定した「screen」フォルダ内に、アプリケーションのソースファイルが作成されます。
アプリ名と同じファイル名の「screen.py」がアプリケーションの実行起点となります。
app
├── .web # nodeやNext.jsなどのWebのフロントに関するモジュールやビルドソースが格納される
├── assets # 画像ファイルなどが格納される
├── screen # コーディングするファイルを格納するフォルダ
│ ├── components
│ ├── pages
│ ├── templates
│ ├── screen.py # アプリケーションの実行起点ソース
│ └── styles.py
├── .gitignore
├── README.md
├── requirements.txt
└── rxconfig.py # Reflexの設定ファイル
上記の初期化の手順ついては、公式ドキュメントの以下のページに記載があります。
(3)DBはDockerを利用して構築済みとする
docker-composeにてDBコンテナを定義し、コンテナ作成時にDBやアクセスユーザーは作成されます。
以下、定義のサンプルです。
services:
db:
image: mysql:8.0
container_name: db
restart: unless-stopped
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: d_sample
MYSQL_USER: db_user
MYSQL_PASSWORD: password
networks:
- app-net
ports:
- 3306:3306
volumes:
db-data:
networks:
app-net:
driver: bridge
3.マイグレーション関連ファイルの作成
最初に、Reflexのプロジェクトフォルダ(app)で、以下のコマンドを実行します。
# Reflexのプロジェクトフォルダ(app)にて以下を実行
reflex db init
上記のコマンドを実行すると、以下のTree図でコメントをつけたフォルダやファイルが作成されます。
作成されたフォルダやファイルはAlmenbicのリソースであり、これらの細かい説明は、Alembicに関するドキュメントを参照してください。
app
├── .web
├── alembic # このフォルダが追加
│ ├── versions
│ ├── env.py
│ ├── README
│ └── script.py.mako
├── assets
├── screen
├── .gitignore
├── alembic.ini # このファイルが追加
├── README.md
├── requirements.txt
└── rxconfig.py
4. DBの接続情報の設定
Almenbicと接続方法は同じです。almebic.iniファイルに以下のように設定します。
sqlalchemy.url = mysql://db_user:password@db:3306/d_sample
5. モデルクラスの作成
RailsやLaravelなどのWebフレームワークと同じように、モデルクラスを利用してDBテーブルを作成します。
ただ、Reflexにはモデルを作成するコマンドはなく、作成場所も決まっていません。ですので、任意の場所にモデルクラスを作成してください。今回は、おそらく一般的であろう以下の場所に作成します。
app
├── .web
├── alembic
├── assets
├── screen
│ ├── components
│ ├── models # modelsというフォルダを作成し、この中に格納
│ │ └── user.py
│ ├── pages
│ ├── templates
│ ├── screen.py
│ └── styles.py
├── .gitignore
├── alembic.ini
├── README.md
├── requirements.txt
└── rxconfig.py
モデルファイルを作成したら、モデルクラスの定義をします。
Reflexのモデルクラスを継承し、列名をクラスのプロパティとして定義してください。以下は、公式ドキュメントにある簡易的な定義方法です。
import reflex as rx
# table=Trueでテーブル作成対象となる
class User(rx.Model, table=True):
username: str
email: str
password: str
6. モデルクラスをReflexアプリ内でインポートする
ここで注意が必要です。これを知らないとマイグレーションファイルができずにハマってしまいます。
マイグレーションする前に、必ずモデルファイルをアプリケーション内でインポートしておく必要があります。
何故このような手続きが必要かというと、Railsのようにモデルファイルが特定の場所にないため、インポートという手続きを通してモデルファイルの場所を知らせる必要があるのだと思います。
すでにページファイルにおいてモデルクラスをインポートしていれば問題ないです。もし、どのページで使うかわからない段階ならば、アプリケーションの起動ファイル内でインポートすればよいと思います。当記事の例では、「app/screen/screen.py」が該当します。
# Import all the pages.
from screen.pages import *
import reflex as rx
# ★ ここでインポート
from screen.models.user import User
class State(rx.State):
"""Define empty state to allow access to rx.State.router."""
# Create the app.
app = rx.App()
アプリからimportされなくなると、drop tableされてしまうので、一度つくったモデルのimport文は必ず残しておく。
正直、この仕様はやや微妙な気もします。Railsみたいにモデルを格納する場所をフレームワークで決めて、そこにコマンドを入力して作成できればよいと思います。ただ、Reflexはまだメジャーバージョン(2024.07.13時点ではv0.5.6)ではないので、そのうちこうした対応もされるのかもしれません。すごく不便ではないですが、知らないと結構ハマります。
7. マイグレーションファイルの作成
次にマイグレーションファイルを作成します。
Reflexのプロジェクトフォルダ(appフォルダ)にて、以下のコマンドを実行します。
「--message」はファイル名に付与されるラベルを指定します。和名よりは半角英数字にしておくのがよいと思います。
reflex db makemigrations --message 'create-user'
# 成功すると以下のようにメッセージが表示される
Generating /<プロジェクトルートパス>/app/alembic/versions/bc3c7b5d443e_create_user.py ... done
上記の手順で以下のように、「alembic/versions」フォルダ以下に、マイグレーションファイルが作成されます。
app
├── .web
├── alembic
│ ├── versions # ここに作成される。
│ │ └── bc3c7b5d443e_create_user.py # 作成されたマイグレーションファイル
│ ├── env.py
│ ├── README
│ └── script.py.mako
├── assets
├── screen
│ ├── components
│ ├── models
│ │ └── user.py # モデルファイル
│ ├── pages
│ ├── templates
│ ├── screen.py # モデルファイルをインポート
│ └── styles.py
├── .gitignore
├── alembic.ini
├── README.md
├── requirements.txt
└── rxconfig.py
8.マイグレーションの実行
以下のコマンドでマイグレーションが実行できます。
reflex db migrate
9. ロールバック、アップグレード
テーブル定義の状態を戻したり、最新や特定の状態にしたい場合は、reflexのコマンドではなくalembicのコマンドで行います。
reflexのマイグレートを行ったフォルダ(alembic.iniが存在する)にて以下のコマンドを実行するとロールバック、アップグレードができます。この例では、「app」フォルダが該当します。
コマンドの詳細は、公式リファレンスや以下の記事をご確認ください。
# マイグレーションの履歴:revisionを表示(ここで表示されたrevisionをつかって特定の状態に変更する)
alembic history
# 初期状態にロールバック
alembic downgrade base
# 一つ前ににロールバック
alembic downgrade -1
# 特定のrevisionにロールバック
alembic downgrade 3f1cb7f09b7a
# 最新までアップグレード
alembic upgrade head
# 1つ進める
alembic upgrade +1
# 特定のrevisionまで進める
alembic upgrade 3f1cb7f09b7a
10. 補足:詳細なテーブル定義
テーブル定義が簡易的でよければ上記のような簡単なモデル定義で十分だと思います。
ただ、データ型、サイズ、コメントをつけたりなどをしたい場合、SQLAlchemyの設定情報を与える必要があります。
たとえば、以下のようにすることで、データ型、サイズ、コメントを付けることができます。その他の設定についても、SQLAlchemyのパラメーターを参考に設定すればよいと思います。
こちらも公式に記載はあります。
import reflex as rx
from typing import Any
import sqlmodel as model
import sqlalchemy as sql
from sqlalchemy.types import BIGINT, BLOB, VARCHAR
from sqlalchemy.sql.sqltypes import TypeEngine
"""
列の情報を簡便に記載するための関数
"""
def attr(
label: str,
data_type: TypeEngine,
is_primary_key: bool = False
) -> Any:
# sa_colmunがSQLAlchemyのパラメーター。ここで細かいDBの定義情報を設定
return model.Field(
sa_column=
sql.Column(
data_type,
comment=label,
primary_key=is_primary_key,
autoincrement=is_primary_key
)
)
"""
モデルクラス
"""
class Crew(rx.Model, table=True):
__tablename__ :str = "crews" # クラス名と別のテーブル名(SQLAlchemyと同じ仕様)
id: int = attr('担当者ID', BIGINT(), True)
crew_department_id: int = attr('所属部署ID', BIGINT())
name: str = attr('名前', VARCHAR(30))
email: str = attr('メールアドレス', VARCHAR(255))
password: str = attr('passowrd', BLOB())
上記のコードで、以下のようにテーブルが作成されます。
ちなみに、Modelとテーブル名は同じ名前にしておくほうが、SQLAlchemyで簡単なコードを書けるので、テーブル名は別途指定しない方がよいと思います。
Railsでは、モデル名の複数形がテーブル名になりますが、そのルールを踏襲する必要はないと思います。Railsはフレームワークの仕組みとしてそうなっているので仕方ないですが、Reflex、SQLAlchemyの場合、それに従う必要はないし、従わない方がよいと思います。
10. さいごに:困ったときの検索・質問先
検索や質問方法について、別記事に整理しました。参考になればと思います。
ちなみに、Reflexのdiscordサーバは以下のリンクから参加できます。