2009-02-13

Google App Engineの本番環境のMemcacheエントリをいじる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engineのローカルの開発環境には、以下のURLでアクセスできる
http://localhost:8080/_ah/admin/memcache
Memcacheのエントリを見たりいじったりできる環境があるのですが、
本番環境にありません。
SOBARCOでは、携帯キャリアのIPアドレスをMemcacheにキャッシュさせているのですが、クローラーに対応していなかったため、携帯用のクローラーが来てもPCサイトの方を表示していました。
ちょっともったいなぁと思って、携帯用クローラーのIPも対応できるように改造したのですが、本番用のMemcacheのキャッシュが切れるまで待つのいやだなぁと思っていたのです。
自分でMemcacheをいじる管理画面を作るしかないのかなぁとか思っていたのですが、
http://www.moongift.jp/2009/02/app_engine_console/
で、App Engine Consoleという本番環境でのPythonコンソールがあることを知り、これを利用することにしました。

導入方法は、簡単です。
1.以下のサイトからApp Engine Consoleのzipファイルをダウンロードする。
http://www.proven-corporation.com/software/app-engine-console/
2.zipファイルを展開して、展開して出来たconsoleディレクトリを自分のGoogle App Engineアプリケーションのディレクトリの直下に置きます。
 私の場合は、
 sobarco
  |-console
  |-app.yaml
 という感じです。
3.app.yamlにエントリを追加する。
 以下のような感じで追加です。

- url: /console/static
static_dir: console/app/view/static
login: admin

- url: /console.*
script: console/app/console.py
login: admin

ここでlogin: adminをつけた方がよいと思います。これでアプリケーションに対してadmin権限があるユーザでログインしていない限り表示されることがなくなります。
4.おまけでGoogle Analyticsを利用している場合は、console/config.pyのanalytics_idにAnalytics用のIDを記載する。
 書いておくと、App Engine ConsoleからAnalyticsに飛べるだけなので別に無理に記述する必要はないです。
5.本番環境へデプロイする。

本番環境にデプロイしたら、
/console
をつけてアクセスするとgoogleへのログイン画面が出てくるのでadmin権限のユーザでログインすると表示されます。
そこで表示されたコンソールで
from google.appengine.api import memcache
print memcache.get('key')
とすると現在keyに設定されている値を取得できます。
memcache.delete('key')
とするとkeyに設定されている値をキャッシュから消すことができます。

App Engine Consoleは、他にも普通にコンソールとして使えるのでMemcache以外にも便利に使えそうです。
普通にApp Engineの管理サイトに盛り込んでくれればいいのに・・・
と思ってしまうぐらいです。

2009-02-11

lxmlでのxmlnsが指定されているタグをxpathで検索する方法

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
pythonでxmlをいじるのにlxmlを使ってみているのですが、普通に使う分には
xmlを解析した後のオブジェクトで普通にxpathに渡してやればよいわけです。
xml.xpath('//rss/channel/title')
みたいな感じです。
しかし、xmlnsが指定されているタグの場合はただ指定しても取れないのです。ちゃんとネームスペースを指定する指定があるようでした。
xml.xpath('//t:feed/t:title',namespaces={'t':'http://www.w3.org/2005/Atom'})
みたいな感じです。

あと、ちなみにxpathであるタグの属性を取りたいときは、
xml.xpath('//t:feed/t:entry/t:category/@term',namespaces={'t':'http://www.w3.org/2005/Atom'})
な感じでまずはタグを指定して、その後に@で属性名をつけるととれるようです。

参考になるサイトは、本家ですが
http://codespeak.net/lxml/xpathxslt.html
です。

GreasemonkeyのAutoPagerizeにサイトを対応させる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Firefoxを使っているとGreasemonkeyという任意のjavaスクリプトを実行させる環境をアドオンできるわけですが、その上でうごくAutoPagerizeというスクリプトがすごく便利なのです。
これは、次のページがある場合、先に読み込んでくれてページの下の方につなげてくれるもので次のページをがんがん画面をスクロールするだけで見ていけるものなのです。
そんな便利なツールにSOBARCOも対応させてみたいと思ってみたわけです。

本来ならば、SITEINFOというものをどっかに準備するみたいなのですが、記法もよくわからないし、どこに準備すればよいのかもいまいちわからなかったのです。

でも、SITEINFOというものがなくても対応させることができるようです。
その方法は、
http://d.hatena.ne.jp/swdyh/20070701/1183239979
が参考になります。

やるべきことは、以下のたったの二つです。
・次のページのリンク先をわかるようにする
・くっつけるブロックを指定する。
くっつけるブロックを指定したら、
最初のページのブロックのすぐ下に
次のページの先読みしたくっつけるブロックがくっつくことになります。

まず次のページのリンクには、rel="next"という属性をつけてやればよいようです。
具体的には、以下のような感じです。


くっつけるブロックの指定は、
class="autopagerize_page_element"
とつけてやればよいようです。
具体的には、

な感じでよいようです。

2009-01-29

Google App Engineで構築したサイトの更新情報をtwitterに通知する方法

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engine上に構築したSOBARCOに新規にデータが登録されたらtwitterで通知してみたいなぁと思って調べてみました。

http://handasse.blogspot.com/2008/06/google-app-engine-twitter.html
にやり方が紹介されていたのですが、それと一緒にGoogle App Engineからtwitterへの通信が遮断されているとの情報も紹介してくれていました。
2008/06ごろの話なので、今はひょっとしたら問題なくできるかもしれませんが、とりあえずこちらで紹介されていた方法はあきらめてみました。

サイトが更新された時に利用されるものといったらRSSだろうということでまずはRSSを配信できるようにしてみようと思ってみました。
そしてRSS配信できれば、blogが更新されたらtwitterに通知してくれる仕組みがあるのでそれを利用することでサイトの更新をtwitterに通知できそうだなぁと思ってみました。
実際やってみると更新してから通知されるまで設定によってはかなり遅く感じたりもしますが、とにかく通知できるということでよしとしてみました。
(その後、どういうわけか初回しか適用されないで、その後の更新がうまくいっていない感じです。ちなみにRSSでなくてAtomに変えてみましたが同じでした。)

なのでまずはRSS配信ですが、そのやり方は、
http://d.hatena.ne.jp/furyu-tei/20090108/1231437373
に紹介してくれていました。
こちらではAmazonさんを利用した例を紹介してくれていますし、肝心のところだけ抜粋して書いてくれています。
私の方も抜粋ですが、こちらのサイトを参考に以下のようにしてみました。

from django.utils import feedgenerator

fncfeed=feedgenerator.Rss201rev2Feed
#トップ要素を作成
feed=fncfeed(
title='SOBARCO'
, link='http://sobarco.appspot.com/'
, description='SOBARCO is Social Barcode!ケータイでバーコードを撮って公開しよう! '
, language='ja'
)

#必要に応じてitemを追加していく
feed.add_item(
title=タイトルに表示したい内容です
, categories=[アイテムのカテゴリをリスト形式で指定することで複数のcategoryを作ってくれます。]
, link=リンク先
, description=説明文みたなもの
, pubdate=日付データGAEのデータストアの日付型のカラムをそのまま突っ込めます。
)
#出力
self.response.headers['Content-Type']='text/xml; charset=utf-8'
self.response.out.write(feed.writeString('utf-8'))


ここで使っているdjangoのfeedgeneratorの説明として
http://djangoproject.jp/doc/ja/1.0/ref/contrib/syndication.html#django.contrib.syndication.django.utils.feedgenerator.Rss201rev2Feed
を参考にさせていただきました。

他にもRSSを作ったならば、サイトで自動でRSSを発見しもらうためにRSS auto-discoveryの設定を行うために
http://www.infomaker.jp/blog/archives/2005/individual/05_10_2343.html
を参考にさせていただき、
念のためRSSの仕様を確認するため
http://www.futomi.com/lecture/japanese/rss20.html
を参考にさせていただきました。

最後にブログの更新をtwitterに通知する方法は、
http://www.ideaxidea.com/archives/2008/05/twitter_1.html
を参考にさせていただきました。

2009-01-02

JAN/EANバーコードからそれが何かを判別する方法

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
SOBARCOでは、携帯の専用アプリを利用してカメラでJAN/EANバーコードを撮り、それから撮影したものがなんであるかを判別しています。
(別にカメラで撮らなくてもバーコードを入力する場所は実際にあるのですけどね)

判別は基本的に、amazonのWEBサービスを利用して行っています。
本とか限定でバーコードで検索する方法は、比較的簡単にわかったのですが本とかCDとかカテゴリを指定せずにカテゴリ横断でバーコードを利用した検索する方法がなかなか見つからなかったのですが、
http://postcard.blog.ocn.ne.jp/itazura/2006/10/cddvd_c5e4.htmlにやり方が書いてありました。

要約するとItemSearchを使うときにSearchIndexをBlendedにしてKeywordsにバーコードを与えてやれば、カテゴリを問わず検索ができるということです。

これの欠点としては、実際には単に全文検索をやっているような気がするので、別にバーコードでないものも普通に検索できます。
なのでEANでないISBNとか入れても検索はできます。
バーコードのみとかのガードをしたいとなればちゃんと以下を参考にチェックデジットのチェックをした方がよいと思います。
http://www.dsri.jp/jan/check_digit.htm#img_01

amazonさんでも残念ながらすべてバーコードが判別できるわけではありません。
そこでSOBARCOではamazonで検索しても分からなかった場合は、
はてなさんのはてなキーワードにJAN/EAN一覧があるのでそちらを利用しています。
http://d.hatena.ne.jp/ean/4909411030681
のように
http://d.hatena.ne.jp/ean/バーコード
のページを取得して、その内容をパースして調べています。
バーコードが登録されていなければ、レスポンスはリダイレクトされるのでレスポンスコードを判別することで登録されているかどうかはわかります。

これでも検索できないものは当然すごくあります。
普通にコカコーラが判別できなかったのが、ちょっと予想外だったりしました。

2008-12-23

WebTestでも使っているBeautifulSoupは便利な感じ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
WebTestを使ってテストを書いているのですが、htmlのパースにBeautifulSoupというものが使われていてこれがいろいろ出来る感じで便利です。
以下、いくつかメモです。

以下は、WebTestを利用しているときのケースです。
res = app.get("/")
soup = res.html
と事前にやっていると思ってください。

classがaaaなdivタグをすべて探す
aaa = soup.findAll('div',{'class':'aaa'})
print aaa[0]
とかで内容が見れます。
タグをはずしたものを見るには、
print aaa[0].string
な感じで見れるようです。

aタグのhrefの値が欲しいときは、
aaa = soup.findAll('a')
ahref = aaa[0]['href']

タグを探すときに正規表現を利用する
lists = soup.findAll('a',{'href':re.compile('^/list')})

これぐらいを使うだけでも結構いろいろなテストがかけちゃう感じです。

BeautifulSoupはWebTest専用のものではなく単独でも利用できるので、これを使ってhtmlのパースとかをしている人もいっぱいるようです。

2008-10-18

Google App EngineでModelのgetでKindError

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App EngineであるModelのgetをkeyオブジェクトを利用して行おうとしてKindErrorが出た時の対処メモです。
結論的には自分の記述ミスなのですが、しばらく原因がつかめずに苦労しました。

出たエラーは

File "・・・..\google\appengine\ext\db\__init__.py", line 759, in get
(instance.kind(), cls.kind()))
KindError: Kind 'MyModel' is not a subclass of kind 'MyModel'

でした。
同じモデルなのに種類が違うってどういうこと?
と思ったわけです。
エラーをraiseしているGoogle App Engineの該当部分のソースは、

for instance in instances:
if not(instance is None or isinstance(instance, cls)):
raise KindError('Kind %r is not a subclass of kind %r' %
(instance.kind(), cls.kind()))

ということで確かにkey値で取得したオブジェクトが想定したクラスのオブジェクトでないときに起こるエラーでした。

自分のコード内ここでやっているようにkind()を利用して調べたら確かに同じ。
そして確かにisinstanceはFalseになっている。

どういうこと?
としばらく悩んでいました。

Pythonをちゃんと知っていれば、すぐに分かったことなのでしょうが
Pythonの仕組みでオブジェクトのクラスを調べる方法として、きちんと
aaa.__class__
というものがあることに気づいて調べてみるとそれぞれ
ddddir.MyModel
MyModel
になっていて、確かに違うことがわかりました。

作成したアプリケーションではddddirディレクトリもファイルを探す対象として以下のようなことをしていました。

import sys
EXTRA_PATHS = [
os.path.join('ddddir'),
]
sys.path = EXTRA_PATHS + sys.path

そして作成したアプリケーションはいくつかのファイルで構成されているのですが、
ある2箇所で同じモデルを定義したファイルを読み込むときに
片方は、
from models import MyModel
としてもう片方が
from ddddir.models import MyModel
としていたためにおきていることが分かりました。

これを統一することで無事にKindErrorが消えました。

まだまだPythonの基本的なことがわかってなさそうな感じです。

2008-09-30

Google App Engineのアプリのテストを書く

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
今まで、特定の関数のテストだけはやっていたのですが、
今更ながら、ちゃんとアプリ全体のテストを書こうと思いGoogle App Engineのテストを調べました。
参考にさせていただいたのは、前もお世話になった
http://coreblog.org/ats/3-tips-to-perform-test-driven-development-with-google-app-engine
です。

WebTestというのを利用するのですが、インストールはeasy_installでちょろっと入ります。
ただ実際に使うにはWebObというのも必要みたいなのでeasy_installでちょろっと入れます。
WebTestを使うとどっかのページにブラウザでアクセスしたのと同じようなレスポンスが帰ってきます。
その結果をいい感じに利用するためにBeautifulSoupも入れておくと便利な感じなのでeasy_installでちょろっと入れます。

WebTestに関する詳細は、公式の
http://pythonpaste.org/webtest/index.html
が参考になります。

これで準備ができてテストコードを書きます。
テストには下ではnoseを利用してみました。

# -*- coding: utf_8 -*-

import os
import sys
import re
import nose
from nose.tools import *
from webtest import TestApp
import logging

#webappでアプリを書いているならばimport
from google.appengine.ext import webapp

#アプリで利用しているAPIのスタブをimport
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore_file_stub
from google.appengine.api import user_service_stub
from google.appengine.ext import db, search
from google.appengine.api import urlfetch_stub
from google.appengine.api.memcache import memcache_stub

#実際のアプリで利用しているハンドラーを定義してテスト環境を準備する
application = webapp.WSGIApplication([
('/', TopPage),
('/login', LoginPage),
('/about', AboutPage),
('/faq', FaqPage),
],debug=True)

app = TestApp(application)

#テストを書く
def test_about():
#必要になる環境変数やヘッダーの値をセット
os.environ['USER_EMAIL'] = ""
os.environ['HTTP_USER_AGENT'] = "aaaa"
os.environ['REMOTE_ADDR'] = "127.0.0.1"
#GET要求の実施。ここで本当は環境変数とかをセットして渡せるみたいなのですが、うまくいかなかったので、直接環境変数をいじりました
res = app.get("/about")
#結果の確認
assert_equal(res.status,"200 OK")
#本文の取得。このやり方ではBeautifulSoupのオブジェクトが帰ってきます。
#res.bodyで普通に文字列が帰ってくる用のです。
soup = res.html
#classがcurrentのdivタグで囲まれた部分の取得
for cnt in soup('div',{'class':'current'}) :
text = cnt.renderContents().strip()
assert text == "About"

if __name__ == '__main__':
#利用するスタブをセットする
stub = datastore_file_stub.DatastoreFileStub(u'xxx','/dev/null',
'/dev/null')
apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)
#なぜかdatastoreのスタブが_appがないとエラーが出るので環境変数を入れてごまかす
os.environ['APPLICATION_ID'] = "xxx"
apiproxy_stub_map.apiproxy.RegisterStub(
'user',user_service_stub.UserServiceStub())
os.environ['AUTH_DOMAIN'] = "gmail.com"
apiproxy_stub_map.apiproxy.RegisterStub(
'memcache',memcache_stub.MemcacheServiceStub())
apiproxy_stub_map.apiproxy.RegisterStub(
'urlfetch',urlfetch_stub.URLFetchServiceStub())


arg = ["dummy","-v"]
nose.main(argv=arg)

2008-09-22

pythonのunitテストをnoseに置き換えてみる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
pythonではnoseというのをunitテストに使うとステキな気配を感じたので、noseを使ってみました。
しかし、いまいちまだ普通のpythonのunittestに比べてのステキっぷりを把握はできていないのですが、今後はnoseを使ってみることにしてみます。

unittestに比べて
assertEqual(a,b)
みたいに書かずに
assert a == b
と書けるのは直感的にわかりやすいなぁとは思うのですが、エラー発生時に渡されている値を確認するためにはnose.toolsのassert_equalを使った方がわかりやすいらしく、結局
assert_equal(a,b)
と書くとあまり違いがわからなかったりするのです。

あとnoseは基本的には、コマンドラインのツールのようでいろいろなオプションを起動時に渡せるようです。
コマンドラインを指定せずにプログラム内に実行の記述を直接するには

if __name__ == '__main__':
arg = ["dummy","-v"]
nose.main(argv=arg)

とかやると起動オプションを渡して実行することもできるようです。

参考にさせていただいたサイトは、
http://makunouchi.jp/zope3/7396323127
です。

2008-09-18

iアプリ端末バグ?

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
作成したアプリが起動しない端末がありました。
F902i
がそうです。
きちんと調べられていないのですが、
http://www.moreread.net/mario/Any/appli/memo.php?id=14
を見ますと、
画像読み込みに問題がありそうです。
ちょっと対処方法がわからないので、まだ対応できていません。
F902iをお持ちの方ご迷惑をおかけして申し訳ありません。