June 7, 2011

PythonとGoogle App EngineでTwitter Botを作る (Part5): Datastore




Twitter Botに何かしらTweetをさせる際、一番に気をつけることというのは重複してTweetしてしまうこと、あるいは同一のTweetに対してReplyをしてしまうことではないでしょうか。
実際にはTwitter API側で、直前数個までの内と同一のTweetをPost出来ない仕様があるので、メッセージ自体が同じであればAPIをCallした時点でErrorとなります。
メッセージを動的に変更するケースだと、これもやはり同じTweetに対して連続してReplyするのを避けたいです。

これらのために、重複排除の機構をBotに取り入れる必要があります。



最初に思いついたのが、直前にReplyしたStatus.idをローカルファイルに記録し、次回にこれと同一のTweetに対してはReplyしない条件文を入れることです。これをLocalhostで実行する分には問題ありませんが、Google App Engine上では制限に引っかかって実行できません。どうやらWrite modeでローカルファイルをopen()出来ないようです。
f = open('hoge.log', 'w') #==>Error

参考: how to write or create (when no exist) a file using python and Google AppEngine

そこで、永続的に扱いたいデータを格納するために、App Engine Datastoreを使ってみることにしましょう。

自由に定義したクラスによって生成されたエンティティと呼ばれるデータオブジェクトを利用して、メソッドを実行したりプロパティに値を入出力できます。まずはチュートリアルなどに従って操作に慣れてみてください。



今回の説明用のコードはこれです。
from google.appengine.ext import db

class Replied(db.Model):
  name = db.StringProperty()
  id = db.IntegerProperty()

def create_entity(test_s, test_i):
  entity = Replied(name=test_s, id=test_i)
  entity.put()

def test_datastore(test_name):
  q = Replied.all()
  q.filter("name =", test_name)
  results = q.fetch(1)
  for x in results:
    print x.name, x.id, x.key()
  obj = db.get(x.key())
  obj.id = obj.id + 1
  print obj.name, obj.id, obj.key()
  obj.put()

if __name__ == '__main__':
  # Put your method here to run

Twitter BotがReplyしたStatus.idを記憶しておくためにデータストアを利用すると想定しています。


まずは、単独でcreate_entity()を実行して、エンティティを作成します。
スクリプトベースで実行する場合、以下を最終行に追加してください。
if __name__ == '__main__':
  create_entity('hogeo', 1234)

そして、シェルからローカルにGoogle App Engineを立ち上げます。
$ ./google_appengine/dev_appserver.py ./test_datastore/

ここまでは特に標準出力するものはありませんが、http://localhost:8080/_ah/admin/datastoreにアクセスしてください。


このdatastore Development ConsoleでList Entitiesのボタンを押すと、現在データストア中に生成されているエンティティを表示することが出来ます。これで先ほど実行して作成したエンティティを確認できるはずです。また、この画面から個別のエンティティを削除することも出来ます。

このデータは永続的にストアされていますので、これをキーにして重複排除などのフラグとして利用できます。



では、このエンティティに対する操作をいくつか試してみます。

先程のスクリプトの最下部を以下のように編集してください。プロパティ名'name'に'hogeo'というプロパティ値が入っているエンティティをフィルターし、マッチしたエンティティに対してデータの入出力を行うテストをします。

if __name__ == '__main__':
  test_datastore('hogeo')

q = Replied.all()
まずはQueryインターフェースを作成します。

q.filter("name =", test_name)
results = q.fetch(1)

フィルターをセットして、マッチするエンティティをFetchします。都合上1つだけ出力していますが、通常はList型で複数のエンティティが出力されます。"name = 'hogeo'"にマッチするエンティティがresults[]に出力されます。

for x in results:
  print x.name, x.id, x.key()

取得したエンティティに対して簡単な操作を行います。nameとidのプロパティ値の出力(print)と、そのエンティティのKeyを出力します。これらはdatastore Development Consoleでも同様に確認できます。

obj = db.get(x.key())
エンティティのkeyプロパティをキーにして、エンティティを取得してみます。同一のKeyで呼び出したエンティティは、同一のエンティティオブジェクトです。

obj.id = obj.id + 1
print obj.name, obj.id, obj.key()
obj.put()

idプロパティに+1した値を代入し、改めて3つの値を出力します。
最後にput()メソッドを実行してエンティティに値を入力します。



では、http://localhost:8080/をReloadしてみましょう。id値が1ずつ増えていく様子が確認できるはずです。



このデータストアを利用すると、動的に生成する一次データや継続して保存したいデータなどを保存することが出来ます。しかも使い慣れたPython APIを通して、オブジェクトのプロパティにして扱え、エンティティのメソッドなどでこれらデータベースの機能を実行することが出来ます。

データベースと言っても、このようにプログラムコードの一部に親和的に取り込めてしまうApp Engine Datastore、ビギナーにも使いやすく設計されていて、非常によく出来ている思います。