SQLAlchemy 0.5.2

メインゴールはあなたのデータベースとSQLへの考え方を変えること!

ということを目標に作られている、O'REILLYから本(ISBN:0596516142)も出てる有名O/Rマッパです。
本家のチュートリアルWEB+DB PRESS Vol.46の記事でもSQLiteを使ってましたので、MySQL5.1で使ってみます。

準備

SQLAlchemyのインストール

普通にeasy_installで大丈夫です。これを書いている時点ではバージョン指定なしで、0.5.2がインストールされます。


easy_install sqlalchemy

DB APIのインストール

SQLAlchemy以外に、それぞれのRDBM用のDB APIが必要になります。
今のところサポートしているRDBMとそのAPIの一覧はここに。
今回MySQLなので、あらかじめMySQLdbをインストールしておきます。PyPIからの通常のやり方ではインストールできなかったので、DLページ指定でインストールします。


easy_install -f "http://sourceforge.net/project/showfiles.php?group_id=22307&package_id=15775&release_id=491012" mysql_python

セッションを作る

まずエンジンを定義して、それを元にセッションを生成〜という流れになります。
で、エンジンはsqlalchemy.create_engine関数に、RFC-1738形式のURLを渡すことで生成します。
MySQLlocalhosthogeデータベースに接続する場合は


engine = create_engine('mysql://user:password@localhost/hoge', echo=True)
という感じで。
echo=True としておくと実行したSQLやらを標準出力に出してくれます。デフォルトはFalseです。

sqlalchemy.orm.sessionmaker()でセッションクラスを生成します。
この戻り値がセッションのサブクラスになっているので、これをアプリケーションのグローバルスコープでやってくれとのことです。つまり、


Session = sqlalchemy.orm.sessionmaker(bind=engine)
としておけば、後はセッションがほしくなったときにその場所で

sess = Session()
すればセッションゲットだぜ!という寸法です。クラスをオブジェクトとして利用できるPythonらしい方法だなーと。

ちなみにsessionmaker()関数はキーワード引数をとり、エンジンを指定するbind以外にも
autocommit(bool型でデフォルトはFalse)やautoflushなどいろいろ指定できますようで。詳しくはこちら

テーブル定義

テーブルはsqlalchemy.schemaパッケージのTableクラスのインスタンスとして定義します。
インスタンス化するときに、カラムの定義としてColumnクラスを、型の定義に型用のクラスを使います。この辺のものはsqlalchemy/__init__.pyでインポートされているので、特に型についてはそれを見るとどんな型があるのか一目でわかると思います。

でまぁ、こんなソースを書いて


import sqlalchemy
from sqlalchemy import Table, Column, Integer, String
metadata = sqlalchemy.MetaData()
hoge_table = Table('hoges', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(50)),
Column('level', Integer, server_default='1'),
mysql_engine='MyISAM',
mysql_charset='utf8')

if __name__ == '__main__':
from sqlalchemy import create_engine
engine = create_engine('mysql://user:pass@localhost/hoge', echo=True)
metadata.create_all(engine)

んでこれを実行すると、

CREATE TABLE hoges (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(50),
level INTEGER DEFAULT '1',
PRIMARY KEY (id)
)ENGINE=MyISAM CHARSET=utf8
というSQLが実行されて

mysql> describe hoges;

                                                                                                                            • +
Field Type Null Key Default Extra
                                                                                                                            • +
id int(11) NO PRI NULL auto_increment
name varchar(50) YES NULL
level int(11) YES 1
                                                                                                                            • +

となりました。

Columnクラスのキーワード引数、server_defaultに値を指定しておけば、デフォルトになるんですが、カラムの型がIntegerでもstr型で指定しないとエラーになります。
MySQLの場合はエンジンやらcharsetやらありますので、mysql_ほにゃらら=ごにょごにょ という感じで指定します。

metadataは、なんか特殊なことをするときに利用するそうで、引数なしでやっておけばデフォルト値が全部使われます。

Tableクラスのインスタンスとして定義したテーブルの情報はクラスとマッピングするときに使いますので、上で書いたソースは別のファイルからimportして使えるようにしています。

ちなみにテーブルを作るだけならセッションを生成する必要はありません。

テーブルとクラスのマッピング

Tableクラスのインスタンスとして定義したテーブルを、クラスに紐付けます。
クラスは全部の属性を定義する必要は必ずしもなく、中身がpassしてるだけのクラスでも、カラム名をそのままオブジェクトの属性として使ってくれます。

マッピングには sqlalchemy.orm.mapperを使い、


sqlalchemy.orm.mapper(クラス名, テーブルインスタンス)
とすることでマッピングできます。

レコードの追加

というわけでhoge_tableのレコードをHogeクラスとして定義し、マッピング
その後、てきとうなデータを3つインサートし、コミットしてみます。
マッピングするクラスはどうやら新スタイルクラスである必要があるみたいです。


import sqlalchemy.orm
from mytables import hoge_table

engine = sqlalchemy.create_engine('mysql://user:password@localhost/hoge', echo=True)
Session = sqlalchemy.orm.sessionmaker(bind=engine)
sess = Session()

class Hoge(object):
def __init__(self, name, level=None):
self.name = name
self.level = level

sqlalchemy.orm.mapper(Hoge, hoge_table)

hoge1 = Hoge('ジャン')
sess.add(hoge1)

hoge2 = Hoge('ポール', 3)
sess.add(hoge2)

hoge3 = Hoge('ベルモント', 5)
sess.add(hoge3)

sess.commit()

これを実行すると、


2009-02-01 05:13:04,062 INFO sqlalchemy.engine.base.Engine.0x...37f0 BEGIN
2009-02-01 05:13:04,078 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name) VALUES (%s)
2009-02-01 05:13:04,092 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\xb3']
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name, level) VALUES (%s, %s)
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x83\x9d\xe3\x83\xbc\xe3\x83\xab', 3]
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name, level) VALUES (%s, %s)
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x83\x99\xe3\x83\xab\xe3\x83\xa2\xe3\x83\xb3\xe3\x83\x88', 5]
2009-02-01 05:13:04,187 INFO sqlalchemy.engine.base.Engine.0x...37f0 COMMIT
と標準出力にアウトプットされます。

あら?と思うかもしれませんがテーブルのエンジンはMyISAMでも、commitしないとデータが反映されないみたい。commit()したときにSQL発行している模様です。


複数レコードを同時に投入する場合は


sess.add_all([hoge1, hoge2, hoge3])
という感じでリストで渡した方がスマートです。