2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

ある 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 アプリが動いているとする。

commands.py
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 に登録する。

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

また、出力対象となるデータベースのスキーマの定義はこんな感じである。

models.py
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 というファイルが出力される。

スクリーンショット 2024-07-12 3.04.13.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?