2022年11月14日月曜日

SQLAlchemyでカラムに独自のクラスを設定する方法

SQLAlchemyでカラムに独自のクラスを設定する方法

概要

Custom Types という機能を使います

環境

  • macOS 11.7.1
  • Python 3.10.2
    • sqlalchemy 1.4.43
    • mysqlclient 2.1.1

インストール

  • pipenv install sqlalchemy
  • pipenv install mysqlclient

サンプルコード

"""TypeDcoratorを使ったサンプルモジュール."""
from dataclasses import dataclass

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.types import (Integer,
                              String)
from sqlalchemy.types import TypeDecorator


# セッション作成
engine = create_engine("mysql://root:@localhost/test?charset=utf8mb4")
SessionClass = sessionmaker(engine)
db_session = SessionClass()

Base = declarative_base()


class ProfileType(TypeDecorator):
    """カラム用の独自のプロファイルクラスを管理."""

    impl = String

    def __init__(self, *args, **kwargs):
        """クラスを指定する引数の定義."""
        self.profile_class = kwargs.pop('profile_class')
        TypeDecorator.__init__(self, *args, **kwargs)

    def process_bind_param(self, value, dialect):
        """レコードのデータの型チェックをして返却."""
        if value is not None:
            if not isinstance(value, self.profile_class):
                raise TypeError("Value should %s type" % self.profile_class)
            return value.value

    def process_result_value(self, value, dialect):
        """レコードのデータをオブジェクトに変換."""
        if value is not None:
            if not isinstance(value, str):
                raise TypeError("value should have str type")
            lang, framework = value.split(',')
            return self.profile_class(lang, framework)


@dataclass
class Profile():
    """このクラスを使った独自カラムを定義する."""

    lang: str
    framework: str


class User(Base):
    """テーブル定義."""

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    age = Column(Integer)
    profile = Column(ProfileType(profile_class=Profile))  # ここに独自のクラスを使ったカラムを定義


# 動作確認
users = db_session.query(User).all()
for user in users:
    print(user.profile.lang)
    print(user.profile.framework)

ちょっと解説

ProfileType が sqlalchemy の Custom Types を使った独自の型になります
この型をカラム定義に使うことができます

データを受け取る際のクラスは Profile クラスになります
MySQL の内部的には CSV で保存されていることを想定しておりそれを分割して各フィールドに設定しています (process_result_value)

impl は独自実装元の型を定義します

process_bind_param と process_result_value は必ず実装するメソッドになります

最後に

わざわざ取り出したあとでオブジェクトに変換する必要がなくなるので便利です

参考サイト

0 件のコメント:

コメントを投稿