AppEngineとbpmappersでJSON形式のデータを返すAPIを作る

アドベントカレンダーにコッソリ登録したつもりだったのに、 id:kuenishi から翌日に指名されてしまった @tokibito です。
今日はAppEngineで書きます。最近仕事でも少し触ってます。
他の人みたいにボリュームたっぷりの記事は書けないので軽めに。
モデルから辞書にデータをマッピングするのに便利なbpmappersを、AppEngineのwebappフレームワークで使う例を書きます。
SDKのバージョンは1.4.0です。
bpmappers 0.8.2 : Python Package Index

bpmappersを使えるようにする

Google App Engine Launcher で新規アプリケーションを作成します。
bpmappersのtarballをダウンロード、展開して中のbpmappersディレクトリをアプリケーションのディレクトリにコピーします。

コードを書く

実装するもの
  • nameとageを持ったPersonモデルを扱う
  • "/" でGETすると、nameとidの一覧を返す
{"people": [{"id": 1, "name": "tokibito"}, {"id": 1001, "name": "test"}]}
  • nameとageをPOSTするとデータを追加する
  • GETパラメータにid=123のように指定すると指定IDのid,name,ageを返す
{"id": 1, "name": "tokibito", "age": 25}
models.py

Personモデルを作ります。

from google.appengine.ext import db

class Person(db.Model):
    name = db.StringProperty(required=True)
    age = db.IntegerProperty()
forms.py

POST時のデータ検証と保存にModelFormを使います。

from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms

from models import Person

class PersonForm(djangoforms.ModelForm):
    class Meta:
        model = Person
mappers.py

Personのマッピング用クラスを作ります。

from bpmappers import Mapper, RawField, ListDelegateField

class PersonSummaryMapper(Mapper):
    id = RawField('key', callback=lambda v: v.id())
    name = RawField()

class PersonMapper(PersonSummaryMapper):
    age = RawField()

class PeopleMapper(Mapper):
    people = ListDelegateField(PersonSummaryMapper)
main.py
# coding: utf-8
try:
    import simplejson
except ImportError:
    from django.utils import simplejson

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

class MainHandler(webapp.RequestHandler):
    def initialize(self, request, response):
        super(MainHandler, self).initialize(request, response)
        self.response.headers['Content-Type'] = 'application/json; charset=utf-8'

    def get(self):
        """
        idが指定されていれば詳細
        そうでなければサマリーで一覧を返す
        """
        from models import Person
        from mappers import PersonMapper, PeopleMapper

        obj_id = self.request.get('id')
        if obj_id:
            person = Person.get_by_id(int(obj_id))
            if person:
                result_dict = PersonMapper(person).as_dict()
            else:
                result_dict = {'response': 'Does not exist.'}
        else:
            people = Person.all()
            result_dict = PeopleMapper(dict(people=people)).as_dict()
        self.response.out.write(simplejson.dumps(result_dict))

    def post(self):
        """
        データの追加
        """
        from forms import PersonForm

        form = PersonForm(self.request.POST)
        if form.is_valid():
            person = form.save()
            result_dict = {'response': 'OK'}
        else:
            result_dict = {'response': 'Invalid'}
        self.response.out.write(simplejson.dumps(result_dict))


def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()
最終的なファイル構成
>tree /F
.
│  app.yaml
│  forms.py
│  index.yaml
│  main.py
│  mappers.py
│  models.py
│
└─bpmappers
        djangomodel.py
        fields.py
        mappers.py
        utils.py
        __init__.py
動かしてみる

AppEngine LauncherのRunボタンをクリックして起動しました。dev_appserver.pyで起動してもよいです。
データを投入するためにPOSTを行わないといけないですが、Firefox拡張のPosterを使うと便利です。
Poster :: Add-ons for Firefox
もしくはPOST用のフォームを書いたHTMLファイルから行います。

<form action="http://localhost:8080/" method="post">
name: <input type="text" size="30" name="name" /><br/>
age: <input type="text" size="5" name="age"/><br/>
<input type="submit"/>
</form>

データを投入したあと、参照用のURLにアクセスするとJSON形式のデータを得られます。

おわりに

次は、最近仕事でもお世話になっている @shimizukawa に頼みます。素敵な記事に期待!