読者です 読者をやめる 読者になる 読者になる

AssignMapperを使う

Python SQLAlchemy

 SQLAlchemyのAssignMapperの使い方の練習をしてみます。SQLAlchemyは、実行しているSQLや、SQLが発行されるタイミングが、実行ログを通して詳細に把握することができるので、内部の処理がイメージしやすい点が優れていると思います。


 練習用に次のようなテーブルを用意しました。使用しているDBはMySQL5.1です。

create table cat(
  id integer primary key auto_increment,
  name varchar(100) not null,
  has_owner smallint not null default false
 ) engine=InnoDB default charset=utf8;


 SQLAlchemyはセッション・オブジェクトorセッション・コンテクストを作り、マッパー(通常のMapper or AssignMapper)を作るまでが核心だと思います。それ以降は難しくありません。
 「エンジン(データベースへの物理的接続を抽象化したオブジェクト)を作る」、「テーブル(データベースの物理設計を抽象化したオブジェクト)をつくる」、「セッション(セッションの中で実際のSQLを発行する)を作る」という手順を踏んで、最後にPythonオブジェクトとデータベース・テーブルのマッピングを行うマッパーを生成します。

# -*- coding: utf-8 -*-
from sqlalchemy import *
from sqlalchemy.ext.sessioncontext import SessionContext
from sqlalchemy.ext.assignmapper import assign_mapper

# モデルオブジェクト
class Cat(object):
    pass

# エンジンを作る
# echo=Trueにすると、実行時に詳細なログを出力してくれるので、
# デバッグ時や学習時には便利
engine = create_engine('mysql://username:password@localhost/dbname', echo=True)

# テーブルを作る
# DBに作成済みのテーブル'cat'から、自動的にスキーマを取得しています
cat_table = Table('cat', BoundMetadata(engine), autoload=True)

# SessionContextを作る
ctx = SessionContext(lambda : create_session(bind_to=engine))

# AssignMapperを作る
cat_mapper = assign_mapper(ctx, Cat, cat_table)


 sqlalchemy.ext.assignmapper.assign_mapper()を使用してAssignMapperを生成します。assign_mapperには、引数として、順に、SessionContextのインスタンス(ctx)、任意のPythonオブジェクトのクラス(Cat)、テーブルオブジェクト(cat_table)を与えます。
 第二引数に与えるべきPythonクラスは、ごく普通のクラスで構いません。必要に応じて、コンストラクタや__str__, __repr__、あるいはその他のメソッドを実装します。


 いろいろ出てきましたが、オブジェクトとテーブル(リレーション)の対応付けは、以降、すべて魔法のクラスAssignMapperが面倒を見てくれるので、ctxとCatクラスを相手にするだけですみます。


 新しくCatオブジェクトのインスタンスを作ります。

c = Cat()
c.name = "Rudolf"
c.has_owner = True

# ここで実際にテーブルに挿入
c.flush()


 普通にオブジェクトを生成し、コミットするときにflush()メソッドを呼びます。結果をMySQLシェルから確認してみます。

mysql> select * from cat;
+----+--------+-----------+
| id | name   | has_owner |
+----+--------+-----------+
|  1 | Rudolf |         1 | 
+----+--------+-----------+
1 row in set (0.05 sec)


 トランザクション内で複数のクエリを実行する場合は、現在のコンテクスト(ctx.current)をflush()します。

c1 = Cat()
c1.name = "Ippaiattena"

c2 = Cat()
c2.name = "Butcher"
c2.has_owner = True

# ここでクエリを実行
ctx.current.flush()


 結果、catテーブルに新たに2行挿入されました。

mysql> select * from cat;
+----+-------------+-----------+
| id | name        | has_owner |
+----+-------------+-----------+
|  1 | Rudolf      |         1 | 
|  2 | Ippaiattena |         0 | 
|  3 | Butcher     |         1 | 
+----+-------------+-----------+
3 rows in set (0.00 sec)


 テーブルから行を選択し、Pythonオブジェクトとして結果を取得するには、select_by(), get_by(), select(), get()等のメソッドを使用します。

# 主キー(id)で選択
rudolf = Cat.get(1)

# nameで選択
ippaiattena = Cat.get_by(name="Ippaiattena")

# has_ownerで選択(複数行)
cats = Cat.select_by(has_owner=True)

# where句を指定せず、すべての行を取得
cats = Cat.select()

# where句を使う
# has_ownerがTrueである猫たち
cats = Cat.select(Cat.c.has_owner==True)

# nameがIpで始まる猫たち
cats = Cat.select(Cat.c.name.startswith("Ip"))


 行数を取得するには、count(), count_by()メソッドを使用します。

# 総行数
Cat.count()
# 3L

# has_ownerがTrueである猫たちの数
Cat.count_by(has_owner=True)
# 2L

# has_ownerがTrue、かつnameがRで始まる猫たちの数
Cat.count(and_(Cat.c.has_owner==True, Cat.c.name.startswith("R")))
# 1L


 更新や削除は、AssignMapperを通して取得したオブジェクトに対して行います。

# 名前を変更
ip = Cat.get_by(name="ippaiattena")
ip.name = u"イッパイアッテナ".encode("utf-8")

# オブジェクトを削除
b = Cat.get_by(name="Butcher")
b.delete()

# ここですべての更新、削除をコミット
ctx.current.flush()