はじめに
ある Flask プロジェクトで、データベース (Flask-SQLAlchemy) の ER 図を作成しようとしたところ、設定準備に意外と手間をかけさせられたため、同じ境遇の方や未来の自分の参考になればと思い忘備録を残した。
前提
- python: 3.12.0
- flask: 3.0.2
- flask-sqlalchemy: 3.1.1
設定
グラフ描画ライブラリの追加
brew install graphviz
graphviz の python インターフェースパッケージの追加
以下のコマンドを実行し、先ほど追加した graphviz
を python 環境で使用できるようにするパッケージをインストールする。
python3 -m pip install --use-pep517 \
--config-settings="--global-option=build_ext" \
--config-settings="--global-option=-I<graphviz_dir>/include/" \
--config-settings="--global-option=-L<graphviz_dir>/lib/" \
pygraphviz -t <install_dir>
オプションで指定するパスに必要な情報ついて解説する。
-
graphviz_dir
: graphviz がインストールされているディレクトリ。以下のコマンドで確認する。ls -la $(brew --prefix graphviz) /opt/homebrew/opt/graphviz@ -> ../Cellar/graphviz/11.0.0
シンボリックリンクの
../Cellar/graphviz/11.0.0
が該当箇所である。必要に応じて読み替える必要あり。
また、絶対パスで指定する必要があり: e.g./opt/homebrew/Cellar/graphviz/11.0.0
-
install_dir
:pygraphviz
をインストールしたい場所。
以下のコマンドで出力される結果を確認する。poetry env info Virtualenv Python: 3.12.0 Implementation: CPython Path: /<path_to_user_dir>/Library/Caches/pypoetry/virtualenvs/<prj_dir>-<hash>-py3.12 <-----コレ Executable: /<path_to_user_dir>/Library/Caches/pypoetry/virtualenvs/<prj_dir>-<hash>-py3.12/bin/python Valid: True ...
上記の
Path
の末尾に/lib/python3.12/site-packages
を append したものを指定する。
最終的には、このようなコマンドを実行することになる。
python3 -m pip install --use-pep517 \
--config-settings="--global-option=build_ext" \
--config-settings="--global-option=-I/opt/homebrew/Cellar/graphviz/11.0.0/include/" \
--config-settings="--global-option=-L/opt/homebrew/Cellar/graphviz/11.0.0/lib/" \
pygraphviz -t /<path_to_user_dir>/Library/Caches/pypoetry/virtualenvs/<prj_dir>-<hash>-py3.12/lib/python3.12/site-packages
ER 図ジェネレーターパッケージの追加
こちら と同じ手順で、eralchemy2
というパッケージをインストールする。
python3 -m pip install --use-pep517 \
--config-settings="--global-option=build_ext" \
--config-settings="--global-option=-I/opt/homebrew/Cellar/graphviz/11.0.0/include/" \
--config-settings="--global-option=-L/opt/homebrew/Cellar/graphviz/11.0.0/lib/" \
eralchemy2 -t /<path_to_user_dir>/Library/Caches/pypoetry/virtualenvs/<prj_dir>-<hash>-py3.12/lib/python3.12/site-packages
ER 図の生成
※データベースサーバーをあらかじめ起動しておく必要がある。
以下のようなコードで Flask アプリが動いているとする。
import click
from eralchemy2 import render_er
from flask import current_app
from flask.cli import with_appcontext
def generate_db_schema():
try:
render_er("postgresql://admin:password@localhost:5432/test_db", "db_schema.png")
except Exception as e:
print(f"エラー発生: {e}")
@click.command("generate-db-schema")
@with_appcontext
def generate_db_schema_command():
"""データベースのスキーマを生成します。"""
generate_db_schema()
generate_db_schema()
が ER 図生成の実態である。
render_er
は、第一引数にデータベースの URI を、第二引数に出力ファイル名をとる。
この関数を CLI から呼び出されるように、下記のように app.py に登録する。
from flask import Flask
def create_app():
app = Flask(__name__)
# コマンドの追加
from .commands import (
generate_db_schema_command,
)
app.cli.add_command(generate_db_schema_command)
return app
また、出力対象となるデータベースのスキーマの定義はこんな感じである。
from datetime import datetime
from enum import Enum
from .database import db
class SecurityLevel(Enum):
PUBLIC = "public"
INTERNAL_ONLY = "internal_only"
INTERNAL_RESTRICTED = "internal_restricted"
class Project(db.Model):
__tablename__ = "projects"
id = db.Column(db.Integer, primary_key=True)
client_name = db.Column(db.String, nullable=False)
title = db.Column(db.String, nullable=False)
uploaded_date = db.Column(db.Date, nullable=False, default=datetime.utcnow)
updated_date = db.Column(
db.Date, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow
)
security_level = db.Column(db.Enum(SecurityLevel), nullable=False)
usecase_en = db.Column(db.Text)
usecase_ja = db.Column(db.Text)
one_pager_link = db.Column(db.String)
ended_date = db.Column(db.Date)
industry_id = db.Column(db.Integer, db.ForeignKey("industries.id"))
squad_id = db.Column(db.Integer, db.ForeignKey("squads.id"))
product_id = db.Column(db.Integer, db.ForeignKey("products.id"))
industry = db.relationship("Industry", back_populates="projects")
product = db.relationship("Product", back_populates="projects")
squad = db.relationship("Squad", back_populates="projects")
class Product(db.Model):
__tablename__ = "products"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
projects = db.relationship("Project", back_populates="product", lazy="dynamic")
class Industry(db.Model):
__tablename__ = "industries"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
projects = db.relationship("Project", back_populates="industry", lazy="dynamic")
class Squad(db.Model):
__tablename__ = "squads"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
contact_email = db.Column(db.String)
contact_name = db.Column(db.String)
is_active = db.Column(db.Boolean, default=True)
projects = db.relationship("Project", back_populates="squad", lazy="dynamic")
最後に、このコマンドで出力が実行される。
flask generate-db-schema
生成に成功すると、db_schema.png
というファイルが出力される。