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をお持ちの方ご迷惑をおかけして申し訳ありません。

2008-09-17

GQLのWHERE条件にINを使うときの注意点

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GQLのWHERE条件にINを使うときに配列を渡すわけですが、その配列に要素が一つもないとどうもその条件がまったく聞いていない気配。

search_list = []
aaa = db.GqlQuery("SELECT * FROM AAA WHERE colcolIN :list",list = search_list)

aaa = db.GqlQuery("SELECT * FROM AAA")
と同じことになっている気配。
個人的には、ひっかかるものが何もないという感じで返してくれる方がうれしかったのですが。

なのでこの動作だとちょっとイヤだなと思う人は、
事前に渡す配列の要素数が0であることをチェックする
か、
絶対にひっかからないダミーの要素を一個入れておく
などする必要がありそうです。

2008-09-16

SOBARCOを作る時に参考にしたサイト

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
参考にしたサイトをメモ

Google App Engineのドキュメント
http://code.google.com/appengine/docs/
http://groups.google.co.jp/group/google-app-engine-japan

Pythonのドキュメント
http://www.python.jp/doc/release/index.html

iアプリのドキュメント
http://www.ep.u-tokai.ac.jp/~nakazato/API/504i/base/jguide504_apiref020517/javadoc/overview-summary.html

jQueryのドキュメント
http://www.openspc2.org/JavaScript/Ajax/jQuery_study/ver1.2.6/index.html

エンジニアのためのWebデザイン教室
http://itpro.nikkeibp.co.jp/article/COLUMN/20080214/293856/

WebプログラマのためのHTMLデザイン(その2)
http://fromnorth.blogspot.com/2008/08/webhtml2.html

HTMLタグ・CSS・JavaScript - Web制作のインデックスサイト
http://www.tagindex.com/index.html

Dynamic Drive CSS Library
http://www.dynamicdrive.com/style/

アイコン集
http://speckyboy.com/2008/07/28/96-best-ever-free-icon-sets-for-web-designers-developers-and-bloggers/

モバイルサイトをPCで見るためのツールやFirefoxアドオン
http://blog.livedoor.jp/ld_directors/archives/51079649.html

Firefoxでモバイル端末をシミュレートするアドオン「FireMobileSimulator」
http://ke-tai.org/blog/2008/09/04/firemobilesimlator/

iアプリでバーコードを読む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iアプリでバーコードやQRコードを読むのは結構簡単なのです。

前にどっかで検索して調べたはずなのですが見つからなかったので書いておきます。
以下のような感じです。
と思ったら、過去に見たものよりも別のもっとよいページがありました。
http://www.geocities.jp/inu_poti/makeiapp/tuika/codeReader.htm


CodeReader cr;
cr = CodeReader.getCodeReader(0);
boolean use_auto = false;
int can_use_codes[];
can_use_codes=cr.getAvailableCodes();
int i=0;
while(true){
try{
int use_code=can_use_codes[i];
if(use_code==CodeReader.CODE_AUTO) use_auto=true;
i++;
}catch(Exception e){
break;
}
}
if(use_auto) {
cr.setCode(CodeReader.CODE_AUTO);
} else {
cr.setCode(CodeReader.CODE_QR);
}
try {
cr.read();
}catch(InterruptedOperationException e){
}
//読み取った値を取得
String read_code = cr.getString();
//その他付加情報(あんまり使い道なし)
int code = cr.getResultCode();
int type = cr.getResultType();

CODE_AUTOは機種によっては問題があるようです。
なので利用できるかどうかをきちんとチェックした方がよさげです。
あと、ドコモのマニュアルを見るとCODE_OCR(テキスト認識)というコード種別もセットできるようなのでひょっとしたら機種によってはOCR機能を利用できる可能性もあります。

2008-09-12

SOBARCOというのを作ってみました

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
SOBARCOというものを作ってみました。
携帯でバーコードを読み取って公開するものです。

このブログで書いていたのは、このSOBARCOを作るために調べたもののメモです。

蔵書管理に使うこともできると思いますし、バーコードを利用した一言ブログとしても利用できると思っています。

もしよろしければ使ってみてください。
今後いろいろ改良を加えてみたいと思っています。
よろしくお願いします。

Google app engineのmemcacheの期限の上限

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google app engineでmemcacheを利用しているのですが、キャッシュ期限を
expire = 60*60*24*30
と30日のつもりで設定していてローカル環境ではうまく動いていたのですが、本番にアップしたらこの期限は駄目でした。
expire = 60*60*24*29
にしたら問題なく動きました。
マニュアルには、
up to 1 month
と書いてあったので、1ヵ月間ということで30日いけるだろうと思ったのです・・・
なので上限は、上記二つの間にあるはずです。

2008-09-05

モーダルダイアログなフォームを作る

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
正式にはなんていうのかわからないのですが、更新作業などをする時にページ遷移をせずに現在のページが暗くなって、その上にモーダルダイアログな感じのフォームが出てくる感じのことをやりたいわけです。
こういうものは、どうもLightbox風と呼ばれているような気配もします。

実際にやるには、jQueryと組み合わせるならば
ThickboxというjQueryのプラグインを使うといいようです。

実際にフォームを表示させるには、
<a class="thickbox" title="Update" href="http://www.blogger.com/update?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=400&width=600&modal=true"> Update </a>
みたいな感じで普通にリンクに書いてclassにthickboxと書けばよいようです。

こうすると開きたいたいフォーム(この場合はupdate)をiframeで開いてくれます。
?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=400&width=600&modal=true
の部分を実際に表示させたいurlにつけるのがポイントです。

そして開いたiframe内でiframeを閉じたいときには
<input onclick="self.parent.tb_remove();" type="button" value="閉じる">
って感じに普通にフォーム内に書いておけば閉じてくれます。
&modal=true
をつけておかないとこの機能は使えないようです。
&modal=true
がなければ、Thinkboxの方で閉じるボタンをつけてくれます。

更新作業などが終わって、iframeでのフォームを閉じる時に元の画面の情報を更新したい時には、以下のような感じのボタンを用意することで対応できました。
<input onclick="self.parent.location.reload();" type="button" value="閉じる">

同じようなものをやるものとして、
jqModalというのもあるらしいです。こちらの方がサイズが小さいけど高機能らしいです。

2008-09-04

IEでjQueryを利用してxmlをパース

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
サーバからxmlを受け取るajaxアプリを最近firefoxで確認しながら作っていたのですが、ふとIEで見てみたらxmlのパースがうまく出来ていないことが判明しました。

どうもIEでは、明示的にヘッダーに
Content-Typeにtext/xml; charset=utf-8としてあげないとだめみたいです。

なので、google app engineでxmlを返す際には
きちんと以下のような感じでヘッダーを指定しないといけないようです。
self.response.headers["Content-Type"] = "text/xml; charset=utf-8"
self.response.out.write(template.render(path, template_values))

ちなみにクライアント側のコードは以下のような感じです。

function hogehoge(val) {
if (!confirm('よろしいですか?')) {
return;
}
$.ajax({
url: '/hogehoge/hoge?key=' + val,
type: 'GET',
error: cant_connect,
success: get_xml_message
});
}

function cant_connect() {
alert('サーバーと接続できませんでした');
}

function get_xml_message(xml) {
var message;
$(xml).find('message').each(function(){
message = $(this).text();
});
alert(message);
}



ちなみにIEでは、きちんとheaderでContent-Typeを指定してないと
$(xml).find('message').each(function(){
の部分がうまく動いてくれないのでした。

2008-08-24

pythonで文字列の中に携帯の絵文字が含まれているかどうかを確認する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
pythonで文字列の中に絵文字が含まれるかどうかを確認しようとして以下のような関数を作ってみました。
とりあえず、受け取るフォームはutf-8で作られているとしています。

def include_emoji(str):
'''指定文字列内に絵文字が含まれるかどうかを確認する

  引数
str String チェック対象文字列(utf-8)
  戻り値
Boolean'''
if str is None:
return False
try:
dummy = unicode(str,'utf-8').encode('euc_jp')
except UnicodeEncodeError,UnicodeDecodeError:
return True
return False

本当にこんなんでいいのかどうかよくわからないけど、簡易的に試した限りはうまくいく気配。
でも、本当は携帯の絵文字をキャリア毎に変換することをやりたいと思ったのですが、pythonでのやり方がわからず挫折。
perlやPHPでは変換モジュールみたいのがあるのですが、pythonでは見つからず。。。
作り直すには技術が足らず。。。

cssでドロップダウンメニュー

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
とりあえず、

http://www.dynamicdrive.com/style/csslibrary/item/inverted-shift-down-menu/
http://blog.faro.main.jp/?eid=369489
にお世話になりドロップダウンメニューを作ってみました。

こんな感じ

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!--ここからCSS-->
<style type="text/css">
body {
margin: 0; /*ブラウザデフォルトの空白を除去*/
}

.menu {
position: absolute;
top: 100px;
width: 100%;
padding: 0;
border-top: 5px solid #D00;
font: bold 12px Arial;
}

.menu_list {
margin:0;
margin-left: 40px; /*メニューの横の隙間*/
}

.menu dl {
margin:0;
padding: 0;
float: left;
}

.menu dt {
margin: 0 2px 0 0; /*メニュー間の隙間*/
}

.menu dd {
display: none;
margin: 0;
padding: 5px 10px 5px 10px;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
background-color: #DDD;
}

.menu dl:hover dt {
background-color: #DAA;
padding-top: 9px;
padding-bottom: 5px;
color: #FFF;
}

.menu dl:hover dd {
display: block;
}

.menu a {
display: block;
color: #000;
text-decoration: none;
}

.menu a:hover {
color: #FFF;
}

.menu dd a:hover {
color: #D00;
}

.menu .current {
background-color: #D00;
padding: 9px 10px 5px 10px;
color: #FFF;
}

.menu dl:hover dt.current {
background-color: #D00;
}

.menu .menu_item {
padding: 5px 10px 9px 10px;
}
</style>
<!--ここまでCSS-->
</head>

<body>
<div class="menu">
<div class="menu_list">
<dl><dt class="current">Menu1</dt></dl>
<dl><dt class="menu_item"><a href="">Menu2</a></dt></dl>
<dl><dt class="menu_item"><a href="">Menu3</a></dt></dl>
<dl><dt class="menu_item">Menu4</dt>
<dd><a href="">Menu4-1</a></dd>
<dd><a href="">Menu4-2</a></dd>
<dd><a href="">Menu4-3</a></dd>
</dl>
<dl><dt class="menu_item"><a href="">Menu5</a></dt></dl>
</div>
</div>
</body>
</html>


FireFox3とIE7では動くみたいですが、IE6はひょっとしたら
http://blog.faro.main.jp/?eid=369489
に書いてあるjavaスクリプトも入れないといけないのかもしれないです。

そして関係ないですが、
色指定で
#D00
と書いたら
#DD0000
の意味になるらしいですね。

2008-08-02

Google App Engineで日本時間を表示する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engineでdateime型を利用すると日本時間が表示されないでちょっと困ります。
そこでいろいろ調べた結果
http://webdba.blogspot.com/2008/04/google-app-engine4python.html
を参考に以下のような感じで解決することにしました。

モデルを以下のように作成します。
モデル内にget_create_tという関数を作成してここで時間の表示したい形式でフォーマッティングします。

from datetime import *
from google.appengine.ext import db

class Samples(db.Model):
name = db.StringProperty(required=True)
create_t = db.DateTimeProperty(required=True,auto_now_add=True)

def print_create_t(self):
jd = self.create_t + timedelta(hours=9)
return jd.strftime("%Y-%m-%d %H:%M:%S")


その後、実際に出力するさいには
sample.print_create_t()
とすることで利用できます。

2008-07-27

GQLでReference型の列でIN条件を利用する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GQLでReference型の列に対してINを利用するのにちょっと悩んでしまったので、メモです。
以下のようなモデルがある時の場合です。

class AAA(db.Model):
name = db.StringProperty(required=True)

class BBB(db.Model):
memo = db.StringProperty(required=True)
ref = db.ReferenceProperty(AAA)

AAAからデータを一回引っこ抜いて、そこでBBB用検索用のIN条件を作成する場合ですが、以下のようにする必要があります。

aaa = db.GqlQuery("SELECT * FROM AAA")
search_list = []
for aa in aaa:
search_list.append(aa.key())
bbb = db.GqlQuery("SELECT * FROM BBB WHERE ref IN :list",list = search_list)

search_list.append(aaa.key())
の部分を
search_list.append(aaa)
としていたら、以下のようなエラーが出てしまったのです。

Traceback (most recent call last):
....
File "C:\...\google\appengine\ext\db\__init__.py", line 1257, in __iter__
return self.run()
File "C:\...\google\appengine\ext\db\__init__.py", line 1589, in run
query_run = self._proto_query.Run(*self._args, **self._kwds)
File "C:\...\google\appengine\ext\gql\__init__.py", line 563, in Run
bind_results = self.Bind(args, keyword_args)
File "C:\...\google\appengine\ext\gql\__init__.py", line 254, in Bind
query.update(enumerated_query)
File "C:\...\google\appengine\api\datastore.py", line 1035, in update
self.__setitem__(filter, value)
File "C:\...\google\appengine\api\datastore.py", line 978, in __setitem__
datastore_types.ToPropertyPb(' ', value)
File "C:\...\google\appengine\api\datastore_types.py", line 958, in ToPropertyPb
'Unsupported type for property %s: %s' % (name, proptype))
BadValueError: Unsupported type for property :


あと、Google App Engineのバグなのかどうかわかりませんが、GQLでINを使ったクエリーの結果に対して、count()を利用するとエラーが出るようです。
これは、ReferencePropertyと関係なくでるようです。StringPropertyでも出ることを確認しました。
ちなみに以下のようなエラーメッセージが出ます。

Traceback (most recent call last):
File "C:\...\google\appengine\ext\webapp\__init__.py", line 499, in __call__
handler.get(*groups)
File "C:\...\myapp.py", line X, in get
xxx = items.count()
File "C:\...\google\appengine\ext\db\__init__.py", line 1286, in count
return self._get_query().Count(limit=limit)
File "C:\...\google\appengine\api\datastore.py", line 957, in Count
self._ToPb(limit=limit), resp)
File "C:\...\google\appengine\api\datastore.py", line 1110, in _ToPb
pb.set_kind(self.__kind.encode('utf-8'))
AttributeError: 'NoneType' object has no attribute 'encode'

最初、ずっとcountしていて何をしてもエラーが出るから原因がなかなかつかめませんでした。

Google App EngineのModelの制限

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App EngineでModelを定義するときに、同じモデルに対するReferenceProperty
を二つ設定できない模様(同じモデルに限らないのかもしれないのですが・・・)。

具体的には、

class Users(db.Model):
name = db.StringProperty(required=True)

class Friends(db.Model):
owner = db.ReferenceProperty(Users)
friend = db.ReferenceProperty(Users)

っていうのは、エラーになってしまいました。
しょうがないので、以下のように片方をStringPropertyにしてkey値を文字列に変換して入れて対処してみました。

class Friends(db.Model):
owner = db.StringProperty()
friend = db.ReferenceProperty(Users)

2008-07-08

GQLの制限メモ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
なにかと便利なGoogle App Engineですが、GQLはなれないといけない感じです。
ちょっと試してGQLの制限をメモ
・ORは使えない
・INの使いっぷりはSQLのつもりで書いてはいけない
 以下のような感じで使う

list = ['a1','a2','a3']
ret = db.GqlQuery("SELECT * FROM aaa WHERE col1 IN :1",list)

2008-07-06

pythonで携帯IP制限

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
携帯専用ページにするには、各携帯キャリアのIPアドレスを利用して制限をかけるみたいです。
通常は、htaccessとかでwebブラウザにこのお仕事を任せるのが通常のやり方のようなのですが、pythonのプログラム側で実装してみたいなぁと思ったので試してみました。

まずは、各携帯キャリアの利用IPアドレスが必要なわけですが、それは
http://d.hatena.ne.jp/tomisima/20070903/1188836400
を利用することで取得することができます。
これで取得した結果を適当にファイルなどに保存したりして利用します。

あんまりpython詳しくないのでいまいちな書き方の可能性が高いけど、私はこんな感じでやってみました。
各キャリアのサブネットは24より小さいことはありえないだろうという勝手な思い込みが前提です。


from mobilejp import MobileJP

m = MobileJP()
ret = m.get_cidr()

iplist = []
for carrier in ret :
for cidr in ret[carrier] :
parts = cidr.split("/")
quads = parts[0].split(".")
quads.append(parts[1])
iplist.append(quads)

chkip = os.getenv('REMOTE_ADDR')

q = chkip.split(".")
find = False
for cidr in iplist :
if int(cidr[0]) != int(q[0]) :
continue;
if int(cidr[1]) != int(q[1]) :
continue;
if int(cidr[2]) != int(q[2]) :
continue;
chk4 = int(q[3])
chk4 = chk4 >> (32 - int(cidr[4]))
chk4 = chk4 << (32 - int(cidr[4]))
if int(cidr[3]) == chk4 :
print "OK"
find = True
break;
if find == False :
print "NO"


こんな感じでできていそうな気がします。

2008-07-01

Amazonの個別商品ページを携帯で見る

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
AmazonのWEBサービスを利用することでアフィリエイトつきの商品個別のページのURLを取得することができますが、携帯でこのURLを見ようとするとどうも長すぎて見れないようです。
なので携帯で見るには、別のURLを個別に生成する必要があるようです。

以下が参考になります。
http://tech.bayashi.jp/archives/entry/techweb/2006/001376.html
http://aws.typepad.com/aws_jp/2005/07/amazon_.html

携帯用には、以下のようなURLでいけるようです。
http://www.amazon.co.jp/gp/aw/rd.html?uid=NULLGWDOCOMO&at=[ASSOCIATE_TAG]&a=[ASIN]&o=Add&dl=1&url=%2Fgp%2Faw%2Fc.html
この[ASIN]の部分をWEBサービスで取得してうめることで携帯で見ることができるようです。

iアプリでxmlを読む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iアプリでxmlを読むにはkXML2というのを使うといいらしいです。
その方法は、
http://www.ntts.co.jp/products/mobile/detail/article.html

第6回「ケータイ+RSSでドコデモ最新情報ゲット」
というのを見ればわかります。
iアプリのサイズを小さくするためにkXML2の一部のファイルのみを組み込ませる方法が書いてあって役に立ちます。
他の記事もiアプリ情報としてはとても役に立ちますよ。

そしてXMLを使っていれば当たり前のかもしれませんが、
XMLデータ内に&があるとkXML2が解析に失敗するようです。
なのでXMLデータに&を入れたいときは、データをURLエンコードしておく必要があるようです。

2008-06-24

Google App EngineでUnitTest

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engineで提供されている機能もあわせてUnitTestをしたいなぁと思って調べたところ
http://coreblog.org/ats/3-tips-to-perform-test-driven-development-with-google-app-engine
がとても参考になりました。

ちなみにmemcacheの機能をテストするには、
from google.appengine.api.memcache import memcache_stub
apiproxy_stub_map.apiproxy.RegisterStub('memcache', memcache_stub.MemcacheServiceStub())
を加えることでテストコードは動くようになりました。


pythonのUnitTestのこともよくわかっていなかったのですが、それは
http://www.python.jp/doc/release/lib/module-unittest.html
を見れば大丈夫でした。

2008-06-23

Google App EngineでWEBサービスを使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App EngineでWEBサービスを使うには、
http://d.hatena.ne.jp/hamatsu1974/20080420/1208632502
http://webdba.blogspot.com/2008/05/urlfetch-rss-img.html
http://yamashita.dyndns.org/blog/google-app-engine-xml/
を見れば基本的にはOKです。

ようはサービスを提供してくれているところへのアクセスには、
Google App Engineで提供されているurlfetchを利用して、
戻ってきたXMLデータの解析には、ElementTreeというのを使うということです。


たいていは普通に使えたのですが、AmazonのWEBサービスを使う際に
例の通りに

import xml.etree.ElementTree as etree
from google.appengine.api import urlfetch

result = urlfetch.fetch(url)
if result.status_code != 200:
 return False
dom = etree.fromstring(result.content)
items = dom.find('.//Items')

とやってもItems以下の情報がとれなかったのです。
いろいろ調べた結果、
Amazonからの戻りXML内に
<ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2008-04-07">
というのがあって、ここのxmlnsで指定されているネームスペースを利用しないといけないということでした。
ネームスペースが利用されていると、ElementTreeは各XMLタグを
{ネームスペース}タグ
として扱っているようです。
なので、
items = dom.find('.//Items')
ではなく、
namespace ='http://webservices.amazon.com/AWSECommerceService/2008-04-07'
items = dom.find('.//{%s}Items' % namespace)
とすることで取れるようになりました。

2008-06-17

iアプリでHTTP通信してデータをPOSTする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iアプリからPOSTでデータを渡す方法は、
http://www.okisoft.co.jp/esc/i-appli.html#6
がずばりで参考になります。
通常は、iアプリでデータ送信に利用する文字コードはSJISになっているみたいなのですが、
UTF-8でやりたいなぁと思っていたところ、
OutputStreamWriter(out);

InputStreamReader(in);
の部分を
OutputStreamWriter(out,"UTF-8");

InputStreamReader(in,"UTF-8");
に変更すれば、サーバー側はUTF-8で受け取ることができました。

2008-06-14

Datastoreでの重複値チェック

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engineでデータベースの役割をするDatastoreではデータを追加すると勝手に主キーにあたるKeyを設定してくれる。
ただしこのKey値はなんかランダム(?)な文字列と一応id(数値)が対応づけられている。
idの変わりにnameというこちらで指定した文字列を設定しておくことができるようです。
なので、このname部分を重複値チェック用に使ってみることにしてみた。

Key値を自分で作るには
Key.from_path
を利用するみたい。
利用する際には
k = db.Key.from_path(キーを設定するモデル名, 設定するキー値)
って感じで使えばよいみたい。
ここの設定するキー値は数値で始まってはいけないようです。

以下は、トランザクションを利用して登録前に複数のカラムで重複チェックをしている例

------------------
from google.appengine.ext import db

class Samples(db.Model):
 col1 = db.StringProperty(required=True)
 col2 = db.StringProperty(required=True)

def add_new(c1,c2):
 #c1の最初が数値で始まるかもしれないので念のため固定値を付与しておく
 kname = "id" + c1 + "__" + c2
 obj = db.get(db.Key.from_path("Samples", kname))
 if not obj:
  obj = Samples(key_name = kname,col1 = c1,col2 = c2)
  obj.put()
 else:
  raise db.TransactionFailedError

c1 = "aaa"
c2 = "bbb"
try :
 db.run_in_transaction(add_new,c1,c2)
 print '登録成功'
except db.TransactionFailedError:
 print 'すでに利用されている組み合わせです'
try :
 db.run_in_transaction(add_new,c1,c2)
 print '登録成功2'
except db.TransactionFailedError:
 print 'すでに利用されている組み合わせです2'
------------------

実際にちゃんと動かして試してないですが、こんな感じで使えると思います。
以下のように出てくると思います。
登録成功
すでに利用されている組み合わせです2


ちょっとこれでいいのかどうかわからないのが、トランザクションを中止するときに
db.TransactionFailedError
をraiseしていいのかどうかがよくわからないのですけどね。
実際は、自分用のエラーとかを作った方がよいのかな?

2008-06-11

pythonで携帯の機種判定

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
pythonで携帯サイトを作ってみようかなぁとか、ちょっと思ってみたので調べたところ
Pythonで携帯の機種判別をする
があったので、ちょっと試してみました。
まだちゃんと試してないですが、最近始まったiモードIDにも対応しているようです。
以下のような感じで使えるようです。
------------------------
from uamobile import detect
dev = detect({'HTTP_USER_AGENT':os.getenv('HTTP_USER_AGENT'),'HTTP_X_DCMGUID':os.getenv('HTTP_X_DCMGUID')})
iid = dev.guid
------------------------
これで、携帯の機種判定ができるようです。
後は、IPでのキャリア判定もしたい気がするので、その方法をそのうち調べてみましょう。

2008-06-04

Google App Engine

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
最近、pythonをいじり始めたのも、Google App Engineを利用してなんか作ってみたいなぁと思ったからなわけです。
で、いろいろと参考になるサイトもあるなぁというわけでメモです。

Eclipseで開発できるようにするためには、
http://d.hatena.ne.jp/c9katayama/20080412/1207995411

Google App Engineの公式ページの日本語訳でかなりお世話になっている
http://d.hatena.ne.jp/hamatsu1974/searchdiary?word=%2a%5bGoogle%20App%20Engine%5d

そして当然、公式ページはここいらへんで
http://code.google.com/appengine/docs/whatisgoogleappengine.html

そしてPythonがわからないことにはどうしようも・・・ということで
http://www.python.jp/Zope/links/python_documents

そしてテンプレートエンジンにDjangoとかいうのが使われているようなので、その説明ページ
http://ymasuda.jp/python/django/docs/templates.html

いろいろ実例をみていいくならば、
http://www10.atwiki.jp/appengine/
http://mattn.kaoriya.net/?-tags=google%20app%20engine&-technorati-hack=/google%20app%20engine

皆さんの行動の早さに尊敬です。自分ももうちょっと早くやらなくては!とちょっと思ってみました。

2008-06-02

easy_install

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
最近、pythonをちょっといじり始めたりしています。

pythonで便利なパッケージなどを使う際には、
python setup.py install
やら
easy_install
やらを使ってインストールする模様。

そのためには、easy_installをインストールする必要があるらしい
linuxだったら
wget http://peak.telecommunity.com/dist/ez_setup.py

python ez_setup.py
を実行すればいいみたいです。

Windowsでもたぶんダウンロードして実行すればいいもようです。

2008-05-31

Subversionのインストール

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
なんやかんやとSubversionが利用できる便利そうなのでCentOS4にインストールしてみた。

Subversionをインストールする前にneon(WebDAVクライアント)をインストールしておかないと、どっかのサイトからのファイルをSubversionを利用して落とそうとする際に、
「URL スキームを認識できません」
というエラーがでちゃうことがあるのです。

なので最初はneonをインストール
ちなみにSubversion1.4.6で0.28.2は使えないっぽい
wget http://www.webdav.org/neon/neon-0.25.5.tar.gz
tar xvzf neon-0.25.5.tar.gz
cd neon-0.25.5
./configure --with-ssl
make
su
make install

その後、Subversionをインストール
wget http://subversion.tigris.org/downloads/subversion-1.4.6.tar.gz
tar xvzf subversion-1.4.6.tar.gz
cd subversion-1.4.6
./configure --with-ssl
make
su
make install

これでOK