2008/04/10


追記
日本語表記を返す様に修正しました。japaneseという属性で返ります。
例) 35 : さんじゅうご

JSONとXMLをサポートしています。
template engineでjavascriptとXMLがどう処理出来るかを調べてる間に、いつのまにか作ってしまっていました...。 JSONの場合は以下の様なテンプレート

nabeatzz.json
{% if callback %}{{ callback }}({% endif %}{ number: {{ number }}, japanese: '{{ japanese }}', nabeatzz : [{% if nabeatzz %}'{{ nabeatzz|join:"','" }}'{% endif %}] }{% if callback %}){% endif %}
「value|join: "xxx"」とか便利ですね。
次にXMLの場合は

nabeatzz.xml
<?xml version="1.0" encoding="UTF-8"?>
<response>
    <number>{{ number }}</number>
    <japanese>{{ japanese }}</japanese>{% for c in nabeatzz %}
    <nabeatzz>{{ c }}</nabeatzz>{% endfor %}
</response>
こんな感じです。これはすごい!フツーすぎます。
なお、templateについてはdjangoのtemplate engineと同じ物ですので「テンプレート作者のための Django テンプレート言語ガイド : Django オンラインドキュメント和訳」を参考にすると良いかと思います。

でpythonコードは以下

nabeatzz.py
#!-*- coding:utf-8 -*-
import os
import cgi
from google.appengine.ext.webapp import template

ndg = [
  {0: ''},
  {0: 'じゅう'},
  {0: 'ひゃく', 3: 'ぴゃく', 6: 'ぴゃく', 8: 'ぴゃく'},
  {0: 'せん', 3: 'ぜん'},
]

dig = [
  {0: ''},
  {0: 'まん'},
  {0: 'おく'},
  {0: 'ちょう'},
  {0: 'けい'},
  {0: 'がい'},
  {0: 'じょ'},
  {0: 'じょう'},
  {0: 'こう'},
  {0: 'かん'},
  {0: 'せい'},
  {0: 'さい'},
  {0: 'ごく'},
  {0: 'こうがしゃ'},
  {0: 'あそうぎ'},
  {0: 'なゆた'},
  {0: 'ふかしぎ'},
  {0: 'むりょうたいすう'}
]

num = [
  {0: 'ぜろ'},
  {0: 'いち'},
  {0: ''},
  {0: 'さん'},
  {0: 'よん'},
  {0: ''},
  {0: 'ろく', 3: 'ろっ'},
  {0: 'なな'},
  {0: 'はち', 3: 'はっ', 4: 'はっ'},
  {0: 'きゅう'}
]

def num2ja(arg):
  sn = str(arg)
  ln = len(sn)
  if sn == 0:
    return num[0][0]
  if ln >= 17*4+1:
    return dig[17][0]
  n = 0
  ret = ""

  while n < ln:
    if sn[n] != 0 and (sn[n] != 1 or (ln-n)%4 == 1):
      ret += num[int(sn[n])].has_key(ln-n) and num[int(sn[n])][ln-n] or num[int(sn[n])][0]
    if sn[n] != 0:
      ret += ndg[(ln-n-1)%4].has_key(sn[n]) and ndg[(ln-n-1)%4][int(sn[n])] or ndg[(ln-n-1)%4][0]
      ret += dig[(ln-n-1)/4].has_key(sn[n]) and dig[(ln-n-1)/4][int(sn[n])] or dig[(ln-n-1)/4][0]
    n += 1

  return ret

try:
    form = cgi.FieldStorage()
    number = ""
    format = "json"
    nabeatzz = []
    nabeatzz_3 = "アホになる"
    nabeatzz_5 = "犬っぽくなる"
    callback = ""
    mime = {
      'json': 'text/javascript',
      'xml': 'text/xml',
    }

    if form:
        if form.has_key('number'):   number = int(form['number'].value)
        if form.has_key('format'):   format = form['format'].value
        if form.has_key('callback'): callback = form['callback'].value

    if number and format in mime.keys():
        if (number % 3) == 0 or str(number).find("3") != -1: nabeatzz.append(nabeatzz_3)
        if (number % 5) == 0: nabeatzz.append(nabeatzz_5)
        template_values = {
            'number'   : number,
            'japanese' : num2ja(number),
            'nabeatzz' : nabeatzz,
            'callback' : callback,
        }
        print "Content-Type: %s; charset=UTF-8" % mime[format]
        print ""
        path = os.path.join(os.path.dirname(__file__), 'nabeatzz.%s' % format)
        print template.render(path, template_values)
    else:
        print "Content-Type: text/html; charset=UTF-8"
        print ""
        path = os.path.join(os.path.dirname(__file__), 'nabeatzz.html')
        print template.render(path, {})
except:
    print "Status: 400"
一応、動く物も用意しました。
NabeAtzz API
おもいっきりネタですね...。
Posted at by



2008/04/09


アプリ第2号です。といってもflickr画像検索同様に有用な物ではありません。
pythonで動作するWebService::Simple「webSimple」を使ってネタバイザーのRSSから最新ネタを取得し、LingrのチャットルームにこれまたwebSimpleで発言するアプリです。
まずテンプレート

lingr.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>ネタバイザーのネタをlingrに転送</title>
<style tyle="text/css"><!--
body {
    font-family: 'メイリオ', 'Osaka'
}
#content {
    margin-left: 50px;
}
--></style>
</head>
    <body>
        <h1>ネタバイザーのネタをlingrに転送</h1>
        <div id="content">
        <p><a href="http://netaviser.woresukebe.com/" class="external" target="_blank">ネタバイザー</a>の最新発言を受信し、<a href="http://www.lingr.com/" class="external" target="_blank">Lingr</a>のチャットルーム「<a href="http://www.lingr.com/room/hO4SmQWTdJ4">LingrAPI Test</a>」に転送します。</p>
        <p>「ネタ転送」ボタンを押下して下さい。</p>
        <form method="post">
            <input type="submit" value="ネタ転送" />
        </form>
        <div>
            {% if neta %}
            ネタ「{{ neta|escape }}」を転送しました。
            {% endif %}
        </div>
        </div>
        <hr />
        <p style="text-align: center">provided by <a href="http://mattn.kaoriya.net">mattn</a>, hosted on google app server.</p>
    </body>
</html>

そしてハンドラ

lingr.py
#!-*- coding:utf-8 -*-
import os
import cgi
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
from webSimple import Simple
import elementtree.SimpleXMLTreeBuilder as xmlbuilder
import xmllib

class MainPage(webapp.RequestHandler):
  def get_neta(self):
      neta = urlfetch.fetch('http://netaviser.woresukebe.com/index.xml').content
      parser = xmlbuilder.TreeBuilder()
      xmllib.XMLParser.__init__(parser, accept_utf8=1)
      parser.feed(neta)
      xml = parser.close()
      return xml.find('channel/item/title').text

  def post(self):
    api = Simple({
        'base_url' : 'http://www.lingr.com',
        'param' : {
            'api_key' : 'your-api-key',
            'format'  : 'xml'
        },
    })

    neta = self.get_neta()

    session = api.get({}, {
        'path' : '/api/session/create',
    }).parse_xml().find('session').text;

    ticket = api.get({
        'session'  : session,
        'id'       : 'hO4SmQWTdJ4',
        'nickname' : 'ネタバイザー転送サーバ',
    }, {
        'path' : '/api/room/enter',
    }).parse_xml().find('ticket').text;

    status = api.get({
        'session'  : session,
        'ticket'   : ticket,
        'message'  : neta,
    }, {
        'path' : '/api/room/say',
    }).parse_xml().find('status').text;

    api.get({
        'session'  : session,
    }, {
        'path' : '/api/session/destroy',
    })

    path = os.path.join(os.path.dirname(__file__), 'lingr.html')
    template_values = {
        'session' : session,
        'ticket'  : ticket,
        'neta'    : neta,
        'status'  : status,
    }
    self.response.out.write(template.render(path, template_values))

  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'lingr.html')
    self.response.out.write(template.render(path, {}))

def main():
  application = webapp.WSGIApplication([('/lingr/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

で、動いている物がこちら
ネタバイザーのネタをlingrに転送
ネタバイザーに負荷が掛かりますので、あまりにリクエストが多い場合には停止させて頂く所存です。またネタバイザーの方が苦情があれば、これまた停止させて頂く所存です。

なお、先ほど修正したのですがPyWrapperのTreeBuilderはlibxml.XMLParserを初期化する際にutf-8を許可するかどうかのフラグ、「accept_utf8」を0のまま渡してしまっています。よってutf-8なXMLが通りませんでした。
初期化が冗長ですが、作ったTreeBuilderをlibxml.XMLParser.__init__で再初期化する様修正しています。
Posted at by




Google App Engineを弄り始めています。
昨日はGoogle App EngineにXMLパーサが入っていない(実際にはpyexptが入っていない)為、python版のWebService::Simpleが動きませんでしたが、PyWrapperを使う事で解決する事が分かりました。結構知れれていないと思いますので、実は結構有益情報かもしれません。
PyWrapper - Trac
これはすごい!

さっそく昨日作ったpython版WebService::Simple「webSimple」を改良し
  • google.appengine.api.urlfetchがimport出来るならばfetchを使用
  • elementtree.SimpleXMLTreeBuilderがimport出来るならTreeBuilderを使用
となる様にしました。
そしてflickr検索のコードは以下の様になりました。
api = Simple({
    'base_url' : 'http://api.flickr.com/services/rest',
    'param' : {
        'api_key' : 'your-api-key'
    },
})
res = api.get({
    'method'   : 'flickr.photos.search',
    'text'     : keyword.encode('utf-8', 'replace'),
    'per_page' : 3,
});
print res.parse_xml().find("photos").getchildren()
あとは、WSGIApplicationに仕立てる為に以下の様なテンプレートを用意します。

flickr.html
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>flickr画像検索</title>
    </head>
    <body>
        <form method="post">
            <input type="text" name="keyword" value="{{ keyword }}" />
            <input type="submit" />
        </form>
        <div id="content"></div>
        {% for e in photos %}
        <a href="http://www.flickr.com/photos/{{ e.attrib.owner }}/{{ e.attrib.id }}/">
            <img src="http://static.flickr.com/{{ e.attrib.server }}/{{ e.attrib.id }}_{{ e.attrib.secret }}_m.jpg" title="{{ e.attrib.title }}" />
        </a>
        <br />{% endfor %}
        <!--
        <pre>{{ content|escape }}</pre>
        -->
    </body>
</html>
さらに以下の様なハンドラを書きます。

flickr.py
#!-*- coding:utf-8 -*-
import os
import cgi
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from webSimple import Simple

class MainPage(webapp.RequestHandler):
  def post(self):
    keyword = cgi.escape(self.request.get('keyword'))
    api = Simple({
        'base_url' : 'http://api.flickr.com/services/rest',
        'param' : {
            'api_key' : 'your-api-key'
        },
    })
    res = api.get({
        'method'   : 'flickr.photos.search',
        'text'     : keyword.encode('utf-8', 'replace'),
        'per_page' : 3,
    });
    path = os.path.join(os.path.dirname(__file__), 'flickr.html')
    photos = []
    template_values = {
        'keyword' : keyword,
        'photos'  : res.parse_xml().find("photos").getchildren(),
        #'photos'  : [],
        'content' : res.content,
    }
    self.response.out.write(template.render(path, template_values))

  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'flickr.html')
    self.response.out.write(template.render(path, {}))

def main():
  application = webapp.WSGIApplication([('/flickr/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

最後に「dev_appserver.py」で動作確認し、「appcfg.py」で更新したら出来上がり。
flickr検索ページが出来上がりました。現状は写真3枚までしか検索出来ないようにしてあります。
flickr画像検索

なお、ディレクトリ構造は ─mattn
  │  flickr.html
  │  flickr.py
  │  webSimple.py
  │  
  └─elementtree
          ElementInclude.py
          ElementPath.py
          ElementPath.pyc
          ElementTree.py
          ElementTree.pyc
          HTMLTreeBuilder.py
          SgmlopXMLTreeBuilder.py
          SimpleXMLTreeBuilder.py
          SimpleXMLTreeBuilder.pyc
          SimpleXMLWriter.py
          TidyHTMLTreeBuilder.py
          TidyTools.py
          XMLTreeBuilder.py
          __init__.py
          __init__.pyc
となっており、elementtreeはPyWrapperから部分的に使用しています。
またwebSimpleのコードはcodereposに上げておきますので、興味のある方は見てください。
/lang/python/webSimple/webSimple.py - CodeRepos::Share - Trac

I love python!
Posted at by