SQLAlchemy 2.0
ずっとSQLAlchemy1.4を使ってきたがそろそSQLAlchemy2.0を使おうかなと思いドキュメントを見たら書き方が結構変わっていて混乱したのでまとめておく。
ドキュメントへのリンクは基本的にSQLAlchemy2.0のドキュメントへのリンクです。
公式サイトの「SQLAlchemy 2.0 - Major Migration Guide」を見ると1.4から2.0への移行のためのガイドが書かれている。
SQLAlchemyの公式サイトはとても見づらい。
SQLAlchemy 2.0 - Major Migration Guide
https://docs.sqlalchemy.org/en/20/changelog/migration_20.html2.0 Migration - ORM Usage
https://docs.sqlalchemy.org/en/14/changelog/migration_20.html#migration-orm-usageSQLAlchemy Unified Tutorial
https://docs.sqlalchemy.org/en/20/tutorial/index.htmlORM Querying Guide
https://docs.sqlalchemy.org/en/14/orm/queryguide.html
SQLAlchemyの基本的な使い方
SQLAlchemyの基本的な使い方を参考にしてください。
ネットで検索すると他にもたくさんの記事が出てくるかと思います。
ここでは基本的な使い方の説明は記載しません。
SQLAlchemy1.4→2.0で変更された部分についての記載と行います。
engineの作り方(変更なし)
# SQLAlchem 1.0 from sqlalchemy import create_engine engine = create_engine(DATABASE_URL, echo=True) # SQLAlchem 2.0 from sqlalchemy import create_engine engine = create_engine(DATABASE_URL, echo=True)
モデルの作成方法とmigrateの方法
モデルの作成方法(結構変わっている)
- モデルを作る際にdeclarative_baseを継承して作っていたが、新しい書き方ではDeclarativeBaseを継承してベースクラスを作成する。
- カラムの定義を行う際にColumnを利用していたがmapped_columnを利用してカラムの定義を行い、型情報を持たせることができるようになった。
- 型情報の定義にはMappedを利用し、定義方法はpythonの型アノテーションを利用する。
- 型情報を持たせることができるようになったので、誤った型のデータを入れようとすると警告を出すことができるようになった。
migrateの方法(変更なし)
- Baseの作り方は変わったがmigrateをする際の方法自体に変更はなし。
# SQLAlchem 1.0 # SQLAlchem 2.0 Base.metadata.create_all(engine)
モデルの定義方法とmigrateのサンプル
以下を実行するとsample.sqlite3としてusersテーブルが作成される。
SQLAlchemy1.0
from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base DATABASE_URL = "sqlite:///sample.sqlite3" engine = create_engine(DATABASE_URL, echo=True) Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) age = Column(Integer) Base.metadata.create_all(engine)
SQLAlchemy2.0
型の定義ができるのがとても良い。
from typing import Optional from sqlalchemy import Integer, String, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column DATABASE_URL = "sqlite:///sample.sqlite3" engine = create_engine(DATABASE_URL, echo=True) class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(50), nullable=False) fullname: Mapped[Optional[str]] = mapped_column(String) nickname: Mapped[Optional[str]] = mapped_column(String(30)) Base.metadata.create_all(bind=engine)
セッション
- Session Basics
https://docs.sqlalchemy.org/en/20/orm/session_basics.html
変更点
2.0になってsessionmakerの引数からautocommitが廃止された。
Sessionにはパラメーターとしては残っているがFalseにして置く必要がある。
https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.Session.params.autocommit
セッションの種類
通常のアプリケーションの場合
- sessionmaker
- sessionmakerで作ったsessionはsessionのファクトリ関数のため、実行するたびに別のsessionが作られる。
- sessionのclose, commitを手動で行う必要がある。
- sessionmaker
マルチスレッドのアプリケーション(Flake等)の場合
- scoped_session
- scoped_sessionで作ったsessionは何度sessionを作っても同一スレッド内であれば同じsessionが取得できる。
- 処理が終わる際にsessionが自動的にcloseされる。
- scoped_session
セッションの作り方
SQLAlchemy1.4
from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker DATABASE_URL = "sqlite:///sample.sqlite3" engine = create_engine(DATABASE_URL, echo=True) Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) age = Column(Integer) Base.metadata.create_all(engine) def create_session(): # 追加するデータを作る u = User(id=1, name="name", age=11) # sesson(ファクトリー関数)を作る session_local = sessionmaker(bind=engine, autoflush=False, autocommit=False) # scoped_sessionを使用する場合 # session_local = scoped_session(sessionmaker(bind=engine, autoflush=False, autocommit=False)) # sessionを作る session = session_local() # データを追加してコミットする session.add(u) session.commit() session.close() # (別の方法)with 文を使ってsession(ファクトリー関数から)作成 # データを追加してコミットする with session_local() as session: u = User(id=2, name="name2", age=12) session.add(u) session.commit()
SQLAlchemy2.0
from sqlalchemy import Integer, String, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, scoped_session, sessionmaker DATABASE_URL = "sqlite:///sample.sqlite3" engine = create_engine(DATABASE_URL, echo=True) class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(50), nullable=False) age: Mapped[int] = mapped_column(Integer) Base.metadata.create_all(bind=engine) def create_session(): # 追加するデータを作る u = User(id=1, name="name", age=11) # session(ファクトリー関数)を作る # autoflushのパラーメータが廃止された session_local = sessionmaker(bind=engine, autoflush=False) # scoped_sessionを使用する場合 # session_local = scoped_session(sessionmaker(bind=engine, autoflush=False)) # sessionを作る session = session_local() session.add(u) session.commit() session.close() # (別の方法)with文を使ってsessionを作成(1.4と違ってengieを渡して一気にsessionを作れる) # 後方五感のためにautocommitが残っているが指定する場合はFalseにする必要がある with Session(engine, autoflush=False, autocommit=False) as session: u = User(id=2, name="name2", age=12) session.add(u) session.commit() session.close()
データの追加
毎回コードの全文を記載すると長くなるので、ここからは必要な部分のコードのみを記載します。
- Using INSERT Statements
https://docs.sqlalchemy.org/en/20/tutorial/data_insert.html#tutorial-core-insert
変更点
SQLAlchemy1.4とSQLAlchemy2.0で大きな違いはなし。
データの追加方法
共通 SQLAlchemy1.4, SQLAlchemy2.0
- session.addでmodelオブジェクトを1件を追加する
- session.add_allで複数のmodelオブジェクトを一括で追加する
- session.bulk_save_objectsでmodelオブジェクトを一括で追加する
SQLAlchemy1.4
- session.executeでDBのテーブルと同じキー名を持つdictを一括で追加する
- session.bulk_insert_mappingsでmodelオブジェクトを指定してList[Dict]を一括で追加する
SQLAlchemy2.0
- session.executeでinsertでモデルを指定してList[Dict]を一括で追加する
SQLAlchemy1.4, SQLAlchemy2.0
# 1件のデータを追加するパターン def insert_data_basic(): """発行されるSQL SQL : INSERT INTO users (id, name, age) VALUES (?, ?, ?) parameter: (1, 'name', 11) """ session = get_session() # 登録するオブジェクトをモデルから作成 u = User(id=1, name="name", age=11) # 作成したオブジェクトをセッションに追加 session.add(u) # セッションへ変更内容を反映する(コミットはしていないため、rollbackすることが可能) # flushをしたところでSQLが発行される session.flush() # セッションの内容をrollbackして取り消す session.rollback() # セッションnを閉じる session.close() def insert_all_data(): """発行されるSQL SQL : INSERT INTO users (id, name, age) VALUES (?, ?, ?) parameter: ((1, 'name', 11), (2, 'name2', 12)) """ session = get_session() # 登録するオブジェクトをモデルから作成 u = [ User(id=1, name="name", age=11), User(id=2, name="name2", age=12), ] # リスト内のUserオブジェクトを一括で追加する session.add_all(u) # セッションへ変更内容を反映する(コミットはしていないため、rollbackすることが可能) # flushをしたところでSQLが発行される session.flush() # セッションの内容をrollbackして取り消す session.rollback() # セッションnを閉じる session.close() def insert_bulk_save(): """発行されるSQL SQL : INSERT INTO users (id, name, age) VALUES (?, ?, ?) parameter: ((1, 'name', 11), (2, 'name2', 12)) """ session = get_session() # 登録するオブジェクトをList[Dict]の形式で作成 u = [ User(id=1, name="name", age=11), User(id=2, name="name2", age=12), ] # リスト内のUserオブジェクトを一括で追加する # bulk_save_objectsをしたところでSQLが発行される session.bulk_save_objects(u) # bulk_save_ojbectsでSQLが発行されているので、flushの効果はない session.flush() # セッションの内容をrollbackして取り消す session.rollback() # セッションnを閉じる session.close()
SQLAlchemy1.4
def insert_core(): """発行されるSQL SQL : INSERT INTO users (id, name, age) VALUES (?, ?, ?) parameter: ((1, 'name', 11), (2, 'name2', 12)) """ session = get_session() # 登録するデータをList[Dict]の形式で作成 u = [ dict(id=1, name="name", age=11), dict(id=2, name="name2", age=12), ] # リスト内のdict形式のオブジェクトを一括で追加する session.execute(User.__table__.insert(), u) # session.executeでSQLが発行されているので、flushの効果はない session.flush() # セッションの内容をrollbackして取り消す session.rollback() # セッションnを閉じる session.close()
SQLAlchemy2.0
def insert_core(): """発行されるSQL SQL : INSERT INTO users (id, name, age) VALUES (?, ?, ?) parameter: ((1, 'name', 11), (2, 'name2', 12)) """ session = get_session() # 登録するデータをList[Dict]の形式で作成 u = [ dict(id=1, name="name", age=11), dict(id=2, name="name2", age=12), ] # リスト内のdict形式のオブジェクトを一括で追加する session.execute(insert(User), u) # session.executeでSQLが発行されているので、flushの効果はない session.flush() # セッションの内容をrollbackして取り消す session.rollback() # セッションnを閉じる session.close()