2018年7月29日日曜日

SQLAlchemy のクラス構成はこんな感じでどうだろうか

概要

SQLAlchemy を使う際にいろいろなコンポーネントが必要になります
多くのサンプルではそれぞれを 1 つファイルで扱っているようですが、あまりキレイな書き方ではないかなと個人的には思います
なので、各役割ごとにクラスを作成し管理するようにしてみました
提案的な記事なので参考程度に見ていただけると嬉しいです

環境

  • macOS X 10.13.6
  • Python 3.6.5
  • SQLAlchemy 1.2.10
  • MySQL Server 5.7.22

engine を管理するクラスの作成

DB サーバに接続する engine を管理します
SQLAlchemy の場合 engine は create_engine で作成します
DB サーバに接続する必要があるので DB サーバのへの接続情報 (ユーザ名、パスワード、ホスト名、データベース名など) はこのクラスで管理します

  • vim base_engine.py
from sqlalchemy import create_engine


class BaseEngine(object):
    def __init__(self):
        username = 'username'
        password = 'password'
        hostname = 'localhost'
        dbname = 'db_server'
        url = 'mysql+mysqldb://{}:{}@{}/{}?charset=utf8'.format(username, password, hostname, dbname)
        self.engine = create_engine(url, echo=True)

DB サーバに接続するための情報はサンプルなので決めうちにしていますが環境変数や設定ファイルから持ってくるようにしても良いと思います
キーワード引数で受け取れるようにしても良いと思います

Session を管理するクラスも追加する

Session は DB への CRUD 操作するために必須のオブジェクトになります
Session は engine を元に生成する必要があるため先ほど作成した BaseEngine クラスを継承します
今回は先ほど作成したファイル (base_engine.py) で管理します (特に理由はないので分けても OK です)
ただし、クラスは Session を管理するためのクラスを作成します

  • vim base_engine.py
from sqlalchemy.orm import sessionmaker

class BaseSession(BaseEngine):
    def __init__(self):
        super().__init__()
        Session = sessionmaker(bind=self.engine)
        self.session = Session()

実際にアプリケーションから CRUD 操作をしたい場合はこのクラスを使います
使い方は後ほど紹介します

モデルを管理するクラスを作成

テーブルの構成を管理するクラスです
SQLAlchemy で言うところの Base を継承して作成するクラスになります
Column などを使ってテーブルのスキーマを定義します

  • vim models.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String(20))
    age = Column(Integer)

このクラスで肝になるのは Base です
この Base クラスは DB に対してテーブルを作成する場合にも使います
いろいろなところで Base を declarative_base するのは気持ち悪いので Base はここだけで扱うようにします

モデルクラスを元にマイグレートするクラスを作成する

作成したスキーマ情報を元に DB サーバに対してテーブルを作成したいと思います
DB サーバに対してテーブルを作成する場合には Base と engine が主に必要になります
それらは専用で管理しているクラスとしてすでに作成済みなのでそれらを import して使います

from base import BaseEngine
from models import User, Base

class Migration(object):
    def __init__(self):
        self.e = BaseEngine().engine

    def users(self):
        Base.metadata.create_all(self.e)

if __name__ == '__main__':
    Migration().users()
  • pipenv run python3 migrate.py

エンジンを作成して、そのエンジンを元に create_all をコールするだけです
これで BaseEngine で指定したデータベース配下にテーブルを作成することができます

基本的にマイグレートするためのクラスは一度しか実行しない想定です
(SQLAlchemy の場合、すでにテーブルが存在する場合は何もしないので何度実行しても問題はないです)

CRUD してみる

作成したクラスを元に実際に SELECT 文を発行するクラスを作成してみます
必要になるのは Session を管理しているクラスと操作したいテーブルを管理しているモデルのクラスになります

  • vim crud.py
from base import BaseSession
from models import User

class Users(BaseSession):
    def __init__(self):
        super().__init__()

    def select(self):
        for i in self.session.query(User).order_by(User.id):
            print(i.name)

if __name__ == '__main__':
    cli = Users()
    cli.select()

サンプルはただ print しているだけです
このメソッドの返り値を User の配列などにしてもいいかなと思います

今回の構成の場合、こんな感じで CRUD したいテーブルごとにクラスを作成できるので、コードの管理がわかりやすくなるかなと思っています
必要に応じて更に CRUD 用のベースクラスなども作成できるかなと思います

最後に

SQLAlchemy を使う場合にどのようなモジュール、クラスの構成にしたほうが良いか考えてみました
もしかしたら探せばベストプラクティスが出てくるかもしれません
今回紹介したのは最低限の役割のみになります

トランザクション処理や Alter 処理、外部キー制約が絡んでくる場合にはもう少し考慮することが増えますが基本的には今回の構成を応用するだけかなと思っています

SQLAlchemy を使う人の参考になれば幸いです

0 件のコメント:

コメントを投稿