🎃

SQLAlchemyのAutomapで日本語の列名を使う【Python3】

2023/01/16に公開

Pythonを使ってDBを操作するときにSQLAlchmeyを使用することは多くあると思います。今回、既存のDBのテーブル定義をAutomapを利用して自動で引っ張って来たところ、電話番号(市外局番)という名前のカラムにPythonからアクセスできませんでした。

情報が見つけられず数時間ほどハマっていたのですが、テーブルの列名→クラスの属性名のマッピング時のイベントを補足し、列名を置換することでいい感じにマッピングできるようです。

結局公式ドキュメントに書いてある内容だったので、困ったときに公式ドキュメントを読む事の大事さを再認識しました。


想定読者

  • SQLAlchemyの基礎的な使用方法がわかる方

環境

macOS Monterey 12.6.1
MySQL 8.0.29
Python 3.11.1
  SQLAlchemy 1.4.41
  mysqlclient 2.1.1


動機

  • DBを操作するのにSQLAlchemy ORMの機能を使用したい
  • カラム数が100列以上のテーブルもその中には存在している
  • その数のカラム定義を書くのはさすがに面倒
  • Automapは既存DBのデータの定義をDBから引っ張ってくれて便利

問題

  • デフォルトではDBの列名がそのままPythonクラスの変数名(メンバ名?)になる
  • PythonではUnicodeの変数名が使用できるため、ひらがなや漢字等はそのまま使用できる
  • ただし、列名にPythonの変数名に使えない文字があるとアクセスできない列が生じる
    • 全角半角の括弧、中黒、など
    • 使おうとするとSyntaxErrorになる

解決

  • 列名から変数名への割当部分で置換を行う事ができる
  • どの文字をどのように置換するかは、問題となるテーブル次第
  • 今回置換したのは3文字のみ
    • 全角初め括弧   → アンダーバーに
    • 全角閉じ括弧 → 削除
    • 中黒  -> アンダーバーに

置換の例) 「電話番号(市外局番)」 -> 「電話番号_市外局番」

from sqlalchemy import create_engine, event
from sqlalchemy.ext.automap import automap_base

# Engineを定義
engine = create_engine('mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>')

# Baseを定義
Base = automap_base()

# ===== 列名置換部分ここから ======
@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # ここで列名を置換
    column_info['key'] = column_info['name']\
        .replace('・', '')\
	.replace('(', '_')\
	.replace(')', '_')
# ===== 列名置換部分ここまで =====

# DBのデータを読み込み
Base.prepare(engine, reflect=True)

# AutomapされたClassを使用
User = Base.classes.users

まとめ

微妙にマニアックな話題なのか、自分の検索方法が悪いのか、かなり調べるのに時間がかかってしまいました。Googleでいろいろ検索して見る前に、もう少し公式ドキュメントを読むべきだったと反省しています。

より良い方法や、代替手段がある場合にはコメント等で教えていただけると嬉しいです。
読んでいただきありがとうございました。

参考

https://docs.sqlalchemy.org/en/14/orm/extensions/automap.html#intercepting-column-definitions

Discussion