この記事は MongoDB Advent Calendar 2013 の三日目です。
MongoEngineってなんですか?
MongoEngineは ODM == Object Document Mapper というやつでMongoDB用のドキュメントをオブジェクト風に操作出来るようにするライブラリ。ドキュメントスキーマ定義、取得、保存を抽象化してくれます。
利用方法はDjangoのORMによく似ており、DjangoユーザであればMongoDB独特のクエリを学習しなくてもMongoDBを使用することが出来るでしょう。たぶん。
ただし、突っ込んだことをしようとするとMongoDBの知識が必要になります。
MongoEngineは何が出来て、何が出来ないのか
できること
- ドキュメントスキーマの定義、制約の定義
- ドキュメントの保存
- ドキュメントの取得
- カスタムクエリの発行
できないこと or めんどうくさいこと
- バックアップ&リストアなどシステムレベルのコマンド実行
- MongoDB固有のコマンド発行(出来なくも無いけど、とても面倒)
さわってみよう
インストール
pypiに登録されているのでpipでインストールします。
$ pip install mongoengine
接続先の設定
MongoDBへの接続はmongoengine.connect
を使用します。 mongotest というデータベースに接続するには次のようにします。
from mongoengine import connect
connect('mongotest')
ホストやポートを指定する場合は引数を足してください。
connect('mongotest', host='192.168.0.10', port=999)
ドキュメントクラスを作る
mongoengine.document.Document
クラスを継承したサブクラスを定義します。各フィールドにはmongoengine.fields.*
を割り当てます。フィールドクラスはIntFieldやStringFieldなど、pythonのオブジェクト名に似た名前で定義されています。
from mongoengine.document import Document
from mongoengine import fields
class Athlete(Document):
name = fields.StringField()
age = fields.IntField()
def __unicode__(self):
return self.name
MongoDBはスキーマレスなのが良いところですが、個人的にはドキュメントクラス == スキーマを作って操作するほうが楽なように思います。余談ですが、1つのコレクションで複数のドキュメントクラスを操作することが出来るので、スキーマレスであるメリットはスポイルされません。
ドキュメントの追加
Athleteクラスをインスタンス化してsave()
メソッドを呼び出します。
from documents import Athlete
# taro yamadaとhanako yamadaを追加する
athlete = Athlete(name=u"taro yamada", age=30)
athlete.save()
athlete = Athlete(name=u"hanako yamada", age=28)
athlete.save()
mongoコマンドで見てみると…
$ mongo mongotest
MongoDB shell version: 2.4.6
connecting to: mongotest
> show dbs
local 0.078125GB
mongotest 0.203125GB
データベースが自動的に作成されています。コレクションはというと、
> show collections
athlete
system.indexes
> db.athlete.find()
{ "_id" : ObjectId("529db366bdbf568299123013"), "name" : "taro yamada", "age" : 30 }
{ "_id" : ObjectId("529db458bdbf568299123014"), "name" : "hanako yamada", "age" : 28 }
というように、athleteコレクションにドキュメントが保存されています。
ドキュメントの取得
ドキュメント取得もAthleteクラスから操作を行います。objects
プロパティ経由でall()
メソッドにより全件取得してみましょう(Djangoユーザにはおなじみですね)。
>>> Athlete.objects.all()
[<Athlete: taro yamada>, <Athlete: hanako yamada>]
取得条件を追加するにはfilter()
メソッドを使います。
>>> Athlete.objects.filter(age=28)
[<Athlete: hanako yamada>]
ドキュメントを一つだけ取得する場合にはget()
メソッドが使えます。
>>> Athlete.objects.get(age=28)
<Athlete: hanako yamada>
get()
メソッド実行時にドキュメントが存在しないと例外が投げられます。
>>> Athlete.objects.get(name=u"inoki")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/key/.virtualenvs/mongoenginetest/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 186, in get
raise queryset._document.DoesNotExist(msg)
documents.DoesNotExist: Athlete matching query does not exist.
ドキュメント数を数えるためにcount()
メソッドも使えます。
>>> Athlete.objects.all().count()
2
ドキュメントの更新
ドキュメントの更新は、インスタンスのsave()
メソッドを実行します。ドキュメントがなければ追加されるし、あれば更新されるという具合ですね。
>>> athlete = Athlete.objects.get(name=u"taro yamada")
>>> athlete.age = 50
>>> athlete.save()
<Athlete: taro yamada>
>>> athlete.age
50
ドキュメントの削除
Athleteクラスのインスタンスに対してdelete()
メソッドを発行します。
>>> hanako = Athlete.objects.get(age=28)
>>> hanako.delete()
>>> Athlete.objects.all()
[<Athlete: taro yamada>]
hanakoが削除されました:)
うっかりall()
メソッドに続けてdelete()
メソッドをチェインすると、全て消えてしまいます(filter()
を使用した際も同様)。
>>> Athlete.objects.all().delete()
>>> Athlete.objects.all()
[]
taroも削除されています:)