I am Charmie

メモとログ

SQLAlchemy: Working with Database Metadata

このチュートリアル

  • SQL Expression LanguageはSQLAlchemy CoreとORMの中心的な要素となり,SQLクエリの構成を可能とする
  • これらクエリの基礎になるのが,データベースの要素を表・列のように表現するPythonオブジェクト

Table型オブジェクトでMetaDataを用意

  • SQLQlchemyにおけるデータベースメタデータの基本オブジェクト
    • MetaData
    • Table
      • データベースのテーブルを表す
      • 自身をMetaData collectionへと割り当てる
    • Column
      • データベースのテーブルのカラムを表す
      • 自身をテーブルオブジェクトに割り当てる
      • str型の名前とタイプオブジェクトを含む
    • Table.columnsでテーブルのColumnCollectionにアクセス
    • Table.columns.keys(): 全カラムの名前をList[str]として受け取れる
    • Table.primary_key: テーブルの主キー
# テーブルの定義
>>> from sqlalchemy import MetaData
>>> metadata = MetaData()

>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
...     "user_account",
...     metadata,
...     Column('id', Integer, primary_key=True),
...     Column('name', String(30)),
...     Column('fullname', String)
... )

>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)

>>> user_table.c.keys()
['id', 'name', 'fullname']

# 別テーブルのカラムを使う
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
...     "address",
...     metadata,
...     Column('id', Integer, primary_key=True),
...     Column('user_id', ForeignKey('user_account.id'), nullable=False),
...     Column('email_address', String, nullable=False)
... )

データベースにData Definition Language (DDL)を送る?

  • 作成したメタデータを元にデータベースにデータをinsertする/からデータをqueryするためには以下のどちらかをSALite データベースにemitする
    1. CREATE TABLE statement
    2. Data Definition Language (DDL)
  • 以下のコードを実行すると,対象とするデータベースを参照しているEngineにメタデータを送る
metadata.create_all(engine)

ORMを使ってテーブルのメタデータを定義

上述した例をORMを使って実現

  • mappedクラスを宣言する過程でTableメタデータを宣言
  • mappedクラスはどのようなPythonのクラスでも良く,データベーステーブル内のカラムにリンク付けされる属性を持つ
  • 何種類かの方法があるけど,最も一般的なスタイルはdeclarative

  • Registryのセットアップ

  • MetaDataコレクションがありORMを使う時,registryを生成する
  • registryを生成するときに,自動的にMetaDataオブジェクトがインクルードされる
  • Tableオブジェクトを直接宣言する代わりに,mappedクラスへのdirectivesを通して間接的に宣言する
  • この間接的な宣言をする一般的なやり方は,registry.generate_base()を使ってdeclarative baseを取得する
  • Mapped classの宣言
  • BaseオブジェクトはORM mapped classのための基底クラスになる
  • テーブル毎にORM mapped classを定義する
  • init()は自動的に生成される (メンバ変数はクラス直下にベタ書き)
  • 全てのカラム名と同じ変数名のメンバ変数を定義し,そのタイプはColumn型のオブジェクト
  • repr()はフォーマット文字列(f"...")で,そのクラスのオブジェクトを返す
  • メンバ変数 table を使ってdeclarative mapped classからTableオブジェクトを見れる
  • ORMでDDLをデータベースへemit
  • Coreと同様にMetaData.create_all()を使う
  • registryと関連付けアレたMetaDataとORM declarative base classを利用
# registryの生成
>>> from sqlalchemy.orm import registry
>>> mapper_registry = registry()

# 自動的にインクルードされるMetaData (何も含んでいないことが分かる)
>>> mapper_registry.metadata
MetaData()

# declarative baseを取得
>>> Base = mapper_registry.generate_base()

>>> from sqlalchemy.orm import relationship

# userテーブルのためのORM mapped class
>>> class User(Base):
...     __tablename__ = 'user_account'
...
...     id = Column(Integer, primary_key=True)
...     name = Column(String(30))
...     fullname = Column(String)
...
...     addresses = relationship("Address", back_populates="user")
...
...     def __repr__(self):
...        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

# addressテーブルのためのORM mapped class
>>> class Address(Base):
...     __tablename__ = 'address'
...
...     id = Column(Integer, primary_key=True)
...     email_address = Column(String, nullable=False)
...     user_id = Column(Integer, ForeignKey('user_account.id'))
...
...     user = relationship("User", back_populates="addresses")
...
...     def __repr__(self):
...         return f"Address(id={self.id!r}, email_address={self.email_address!r})"

# emit CREATE statements given ORM registry
mapper_registry.metadata.create_all(engine)

# the identical MetaData object is also present on the
# declarative base
Base.metadata.create_all(engine)