概要
Python の ORM を調査中。最初に自分が良く利用している「Elixir」の使い方から。
Elixirに関して
- 公式サイト:http://elixir.ematia.de/trac/
- pypi:http://pypi.python.org/pypi/Elixir/
- ライセンス:MIT
- 最新:0.7.1(2009-11-16)
- Python3対応:◯(2to3による)
- 対応RDB:DB2,ODBC,Oracle,PostgreSQL,MS SQL Server,MySQL,SQLite3,Sybase等(SQLAlchemyと同じ。Supported Databases参照)
「Elixir」は「SQLAlchemy」のラッパ。気軽にSQLAlchemyの機能を使いたい場合に便利。
SQLAlchemyのラッパなので、オープンソースのデータベースは無論、商用データベースにも多数に対応しているのが特徴。
残念な事にpypi 上の最終リリースが2009-11-16で、最新の SQLAlchemy を利用すると 多少の不具合が存在しているので、手元では改造バージョンを利用したりしている。
ドキュメント
「公式サイト」にそれなりにドキュメントが存在する。
部分的に「SQLAlchemy」ドキュメント参照になっている部分があるが、SQLAlchemyのドキュメントは多すぎる。
入門であれば「Elixirのチュートリアル」を見ると大体の機能はわかるはず。
インストール
pipでインストールする。
pip install Elixir
サンプルソース
自分は ORM を利用する場合あまり DDL を発行しないので、DML を中心としたサンプルとする。
今回は MySQL でデータベースとテーブル用意する。
> mysql -u root
mysql> CREATE DATABASE example DEFAULT CHARACTER SET utf8;
mysql> GRANT ALL PRIVILEGES ON example.*
-> TO username@localhost
-> IDENTIFIED BY 'password';
mysql> exit;
> mysql -u username -p example
mysql> CREATE TABLE tweets (
-> id serial PRIMARY KEY,
-> status_id VARCHAR(255) UNIQUE NOT NULL,
-> from_user_id VARCHAR(255) NOT NULL,
-> text VARCHAR(140) NOT NULL,
-> created_at VARCHAR(50) NOT NULL,
-> datetime DATETIME NOT NULL
->) engine=innodb default charset=utf8;
DML を発行するサンプルは以下。unittest で書いてある。MySQL ドライバはMySQL-pythonを利用する。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
from datetime import datetime
from elixir import (
Entity,
using_options,
setup_all,
metadata,
session,
)
class Tweets(Entity):
# autoload を True にすると DB から定義を自動読み込み
using_options(tablename='tweets', autoload=True)
class TestElixir(unittest.TestCase):
def setUp(self):
# DB 接続情報 MySQL の場合デフォルトでは MySQL-python ドライバを利用する
metadata.bind = 'mysql://username:password@localhost/example'
# 詳細表示
metadata.bind.echo = True
setup_all()
def test_main(self):
# SELECT
self.assertEqual(0, Tweets.query.count())
# INSERT
# autocommit=False になる
Tweets(
status_id='251298602096xxxxx1',
from_user_id='43172xxx1',
text='さんぷるでーた',
created_at='Mon, 1 Oct 2012 12:33:50 +0000',
datetime=datetime.utcnow(),
)
# この場合インスタンスが生成される度に INSERT が発行されるが、COMMITはされない。
self.assertEqual(1, Tweets.query.count())
Tweets(
status_id='251298602096xxxxx2',
from_user_id='43172xxx2',
text='さんぷるでーた その 2',
created_at='Mon, 1 Oct 2012 12:33:50 +0000',
datetime=datetime.utcnow(),
)
# コミットすると確定
# session.commit()
# commit しなくても件数は取れる
self.assertEqual(2, Tweets.query.count())
self.assertEqual(1, Tweets.query.filter_by(from_user_id='43172xxx1').count())
self.assertEqual(1, Tweets.query.filter_by(from_user_id='43172xxx2').count())
self.assertEqual(0, Tweets.query.filter_by(from_user_id='43172xxx3').count())
# UPDATE
for tweet in Tweets.query.filter_by(from_user_id='43172xxx2'):
tweet.from_user_id = '43172xxx3'
self.assertEqual(0, Tweets.query.filter_by(from_user_id='43172xxx2').count())
self.assertEqual(1, Tweets.query.filter_by(from_user_id='43172xxx3').count())
# DELETE
for tweet in Tweets.query.all():
tweet.delete()
self.assertEqual(0, Tweets.query.count())
# bulk INSERT
insert_lst = [
{
'status_id': '251298602096xxxxx3',
'from_user_id': '43172xxx3',
'text': 'さんぷるでーた その 3',
'created_at': 'Mon, 1 Oct 2012 12:33:50 +0000',
'datetime': datetime.utcnow(),
},
{
'status_id': '251298602096xxxxx4',
'from_user_id': '43172xxx4',
'text': 'さんぷるでーた その 4',
'created_at': 'Mon, 1 Oct 2012 12:33:50 +0000',
'datetime': datetime.utcnow(),
},
]
# Elixir 0.7.1 は以下のロジックだと autocommit=True なので注意
Tweets.table.insert().execute(insert_lst)
if __name__ == '__main__':
unittest.main()
まとめ
速度に関しては、「metadata.bind.echo」を「False」にして簡易に計測してみたが、ドライバ直接利用するのに比較すると、やはり遅くなる。また、メモリもそれなりに利用する。
本気で速度を求める場合は、ORMの利用を考慮しない方が良いだろう、
使いごこちとしては、「autoload=True」等の機能が非常に便利。
また、いざとなったら、SQLAlchemy の機能が利用できるので、ほとんどの場合、機能が不足することはない。
SQLAlchemy の機能を利用しているため、UPDATE、DELETEの発行が、SELECT してからでないとできないのは、やや不便かもしれないがORMだと比較的一般的な動作と思われる。
最近メンテナンスされていないは、懸念事項になる。
2012/10/16追記:
この記事は Python のORM 調査の記事の一部となる。以下が関連記事。