Fixture + GoogleAppEngine

fixtureをGoogleAppEngineのDataStoreテストで使う。json編

参考 Using Fixture To Test A Google App Engine Site

DataSetsをjson化するのにfixture.dataset.converterでdataset_to_jsonが提供されていたりもする。ただdate_created(auto_now_add=True)のようなフィールドはDataSetsで前もって値を指定できないので、ここでjson化したデータを、実際(date_createdの付いた)のjsonのレスポンスと比較しても同じにはならない。

.zshrc

gaedir=/usr/local/google_appengine
if [ -d $gaedir ] ; then ;
    export PATH=${PATH}:$gaedir
    export PYTHONPATH=${PYTHONPATH}:${gaedir}:${gaedir}/lib/antlr3:\
${gaedir}/lib/cacerts:${gaedir}/lib/django:${gaedir}/lib/ipaddr:\
${gaedir}/lib/webob:${gaedir}/lib/yaml/lib
fi;
$ source ~/.zshrc
$ pip install WebTest
$ pip install nose
$ pip install NoseGAE

app.yaml

application: sampleblog # _ はapplicationの識別子として使えないので注意
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: blog.py

blog.py

#coding:utf-8
import logging
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from django.utils import simplejson


class Entry(db.Model):
    title = db.StringProperty()
    body = db.TextProperty()
    date_created = db.DateTimeProperty(auto_now_add=True)


class Comment(db.Model):
    entry = db.ReferenceProperty(Entry)
    comment = db.TextProperty()
    date_created = db.DateTimeProperty(auto_now_add=True)


class EntriesHandler(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'application/json;charset=utf-8'
        entries = []
        for entry in Entry.all():
            comments = []
            for comment in Comment.all().filter("entry =", entry):
                comment_dict = \
                    dict(comment=comment.comment,
                         date_created=comment.date_created.isoformat())
                comments.append(comment_dict)
            entry_dict = dict(title=entry.title,
                              body=entry.body,
                              date_created=entry.date_created.isoformat(),
                              comments=comments)
            entries.append(entry_dict)
        json = simplejson.dumps(entries, indent=True)
        #logging.info(json)
        self.response.out.write(json)


routing = [('/entries', EntriesHandler)]

application = webapp.WSGIApplication(
        routing,
        debug=True)


def main():
    run_wsgi_app(application)

if __name__ == '__main__':
    main()

tests/datasets.py (http://fixture.googlecode.com/hg/fixture/examples/google_appengine_example/tests/datasets.py)

from fixture import DataSet


class EntryData(DataSet):
    class great_monday:
        title = "Monday Was Great"
        body = """\
Monday was the best day ever.  I got up (a little late, but that's OK) then I ground some coffee.
Mmmm ... coffee!  I love coffee.  Do you know about
<a href="http://www.metropoliscoffee.com/">Metropolis</a> coffee?  It's amazing.  Delicious.
I drank a beautiful cup of french pressed
<a href="http://www.metropoliscoffee.com/shop/coffee/blends.php">Spice Island</a>, had a shave
and went to work.  What a day!
"""


class CommentData(DataSet):

    class monday_liked_it:
        entry = EntryData.great_monday
        comment = """\
I'm so glad you have a blog because I want to know what you are doing everyday.  Heh, that sounds
creepy.  What I mean is it's so COOL that you had a great Monday.  I like Mondays too.
"""

    class monday_sucked:
        entry = EntryData.great_monday
        comment = """\
Are you serious?  Mannnnnn, Monday really sucked.
"""

load_data_locally.py
(http://fixture.googlecode.com/hg/fixture/examples/google_appengine_example/load_data_locally.py)一部修正

import sys
import os
import optparse
from fixture import GoogleDatastoreFixture
from fixture.style import NamedDataStyle


def main():
    p = optparse.OptionParser(usage="%prog [options]")
    default = "/tmp/dev_appserver.datastore"
    p.add_option("--datastore_path", default=default, help=(
            "Path to datastore file.  This must match the value used for "
            "the same option when running dev_appserver.py if you want to view the data.  "
            "Default: %s" % default))
    default = "/tmp/dev_appserver.datastore.history"
    p.add_option("--history_path", default=default, help=(
            "Path to datastore history file.  This doesn't need to match the one you use for "
            "dev_appserver.py.  Default: %s" % default))
    default = "/usr/local/google_appengine"
    p.add_option("--google_path", default=default, help=(
            "Path to google module directory.  Default: %s" % default))
    (options, args) = p.parse_args()

    if not os.path.exists(options.google_path):
        p.error("Could not find google module path at %s.  You'll need to specify the path" % options.google_path)

    groot = options.google_path
    sys.path.append(groot)
    sys.path.append(os.path.join(groot, "lib/django"))
    sys.path.append(os.path.join(groot, "lib/webob"))
    sys.path.append(os.path.join(groot, "lib/yaml/lib"))

    from google.appengine.tools import dev_appserver
    import blog
    from tests import datasets

    config, explicit_matcher = dev_appserver.\
        LoadAppConfig(os.path.dirname(__file__), {})
    dev_appserver.SetupStubs(
        config.application,
        clear_datastore=False,  # just removes the files when True
        datastore_path=options.datastore_path,
        history_path=options.history_path,
        blobstore_path=None,  # 追加 KeyError: 'blobstore_path' を避ける
        login_url=None)

    datafixture = GoogleDatastoreFixture(env={'EntryData': blog.Entry,
                                              'CommentData': blog.Comment})

    data = datafixture.data(datasets.CommentData, datasets.EntryData)
    data.setup()
    print "Data loaded into datastore %s" % \
        (options.datastore_path or "[default]")

if __name__ == '__main__':
    main()

tests/test_entries.py

#coding:utf-8
import unittest
from fixture import GoogleDatastoreFixture
from webtest import TestApp
import blog
from datasets import CommentData, EntryData
from django.utils import simplejson

datafixture = GoogleDatastoreFixture(env={'EntryData': blog.Entry,
                                          'CommentData': blog.Comment})


class TestListEntries(unittest.TestCase):
    def setUp(self):
        self.app = TestApp(blog.application)
        self.data = datafixture.data(CommentData, EntryData)
        self.data.setup()

    def tearDown(self):
        self.data.teardown()

    def test_entries(self):
        response = self.app.get("/entries")
        assert simplejson.dumps(EntryData.great_monday.title) in response
        assert simplejson.dumps(EntryData.great_monday.body) in response
        assert simplejson.dumps(CommentData.monday_liked_it.comment) \
            in response
        assert simplejson.dumps(CommentData.monday_sucked.comment) \
            in response

Create custom datasets.

$ ./load_data_locally.py --datastore_path=./my.datastore

Run server with custom data.

$ dev_appserver.py . --datastore_path=./my.datastore

Run tests.

$ nosetests -v --with-gae

test_entries (tests.test_entries.TestListEntries) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.296s
OK
Posted: May 26th, 2010 | Author: | Filed under: 技術 | Tags: , , , , , | No Comments »

Twitter OAuth on GoogleAppEngine

Google App EngineでTwitter OAuthを使う。
自分のタイムラインを取得してtweetできるまでのサンプル。

参考
tweepyでtwitterの3-legged OAuth認証を試してみた(GoogleAppEngine)

コードのなかで使用しているsimple_cookieは
Google Cookbook – Google App Engine A simple Cookie class のもの。

注意する点は
Twitter application settingでcallback URLを正しく設定すること。
スクリーンショット(2010-05-23 2010-5-23-Sunday 18.00.31)
callback URLに127.0.0.1を指定しているからかもしれないけど、なにも入力しないと
TweepError: HTTP Error 401: Unauthorized になる。

tweepy
はapp engineのディレクトリにモジュールのディレクトリをコピーして使った。

以下サンプルコードと実行

Read the rest of this entry »

Posted: May 23rd, 2010 | Author: | Filed under: 技術 | Tags: , , , , | No Comments »