May 15, 2009

A patch for Werkzeug debugger on GAE

I wrote about using werkzeug on gae, but actually there are still several problems in debug console. So I wrote a patch for it.



diff -r 8ffe4e637e17 werkzeug/debug/console.py
--- a/werkzeug/debug/console.py Sat Apr 25 17:06:40 2009 +0000
+++ b/werkzeug/debug/console.py Fri May 15 00:40:32 2009 +0900
@@ -32,6 +32,12 @@
def close(self):
pass

+ def flush(self):
+ pass
+
+ def seek(self, n, mode=0):
+ pass
+
def reset(self):
val = ''.join(self._buffer)
del self._buffer[:]
@@ -48,12 +54,18 @@
def writelines(self, x):
self._write(escape(''.join(x)))

+ def readline(self):
+ if len(self._buffer) == 0:
+ return ''
+ ret = self._buffer[0]
+ del self._buffer[0]
+ return ret

class ThreadedStream(object):
"""Thread-local wrapper for sys.stdout for the interactive console."""

def push():
- if sys.stdout is sys.__stdout__:
+ if not isinstance(sys.stdout, ThreadedStream):
sys.stdout = ThreadedStream()
_local.stream = HTMLStringO()
push = staticmethod(push)

May 14, 2009

Using Werkzeug's debugger on GAE dev server

There is a very good web framework called Werkzeug. The debugger of Werkzeug is pretty nice and we can use inline console on our web browser with it.

The author of Werkzeug wrote an article of how to use this debugger with GAE dev server. However according this article, the inline console is not usable.

After a little struggling, finally I can use the debugger on GAE dev server. GAE dev server creates an instance of DebuggedApplication on every request. I think its the reason why. So, it seems that specifying the instance as the module global singleton made it work.



_debugged_app = None
app = ... build your wsgi app ...

def main():
# Only run the debugger in development.
import os, sys
if 'SERVER_SOFTWARE' in os.environ and os.environ['SERVER_SOFTWARE'].startswith('Dev'):
# use our debug.utils with Jinja2 templates
import debug.utils
sys.modules['werkzeug.debug.utils'] = debug.utils

# don't use inspect.getsourcefile because the imp module is empty
import inspect
inspect.getsourcefile = inspect.getfile

# wrap the application
from werkzeug import DebuggedApplication
global _debugged_app
if _debugged_app is None:
_debugged_app = app = DebuggedApplication(app, evalex=True)
else:
app = _debugged_app

Google App Engine で Werkzeug のデバッガを使う

Werkzeug という Python Web Framework があります。Werkzeug のデバッガはなかなかすぐれもので Web アプリでエラーが出るとインライン console が使えたりします。

このデバッガを GAE の dev server で使う方法を作者の方が書いているのですが、この通りやるとインライン console が使えなかったりします。

ちょっと調べてみたら意外と簡単に使えました。GAE の dev server だとリクエスト毎に DebuggedApplication のインスタンスが作り直されてしまうので、これを module global のシングルトンにしてやればオッケイでした。



_debugged_app = None
app = ... build your wsgi app ...

def main():
# Only run the debugger in development.
import os, sys
if 'SERVER_SOFTWARE' in os.environ and os.environ['SERVER_SOFTWARE'].startswith('Dev'):
# use our debug.utils with Jinja2 templates
import debug.utils
sys.modules['werkzeug.debug.utils'] = debug.utils

# don't use inspect.getsourcefile because the imp module is empty
import inspect
inspect.getsourcefile = inspect.getfile

# wrap the application
from werkzeug import DebuggedApplication
global _debugged_app
if _debugged_app is None:
_debugged_app = app = DebuggedApplication(app, evalex=True)
else:
app = _debugged_app

March 20, 2009

Android hack-a-thon

3/20に Android hack-a-thon に参加してきました。

みなさん一日で色々作っています。すごいな〜

私の方は、全くの初心者なので午前中は HelloWorld をやってみました。午後からは gdata のライブラリを使って Google Apps のグループアドレスを作成できるようなアプリケーションにトライ。

gdata の jar をプロジェクトに取り込み、必要な画面を作るところまではうまくいったけど、 GroupService の認証をするところでうまくいかず、当日はここで断念。原因はSSL証明書の検証が失敗している事まではつきとめたんですけど...

せっかくなので今日がんばって動かす事ができたので書きます。

まず gdata の Java クライアントのソースを落としてきて、下記のパッチを当てます。内容はSSL証明書の検証が必ず成功するようにしているだけです。


diff -uNr gdata/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java gdata.mine/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java
--- gdata/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java 2009-02-12 05:09:34.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java 2009-03-22 13:49:42.000000000 +0900
@@ -16,6 +16,13 @@

package com.google.gdata.client;

+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import javax.security.cert.X509Certificate;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLException;
+
import com.google.gdata.util.common.base.CharEscapers;
import com.google.gdata.util.common.base.StringUtil;
import com.google.gdata.client.GoogleService.AccountDeletedException;
@@ -450,7 +457,27 @@

// Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
-
+ if ( urlConnection instanceof HttpsURLConnection ) {
+ HttpsURLConnection ucs = (HttpsURLConnection) urlConnection;
+ ucs.setHostnameVerifier( new X509HostnameVerifier(){
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true;
+ }
+ @Override
+ public void verify(String arg0, SSLSocket arg1) throws IOException {
+ }
+ @Override
+ public void verify(String arg0,
+ java.security.cert.X509Certificate arg1)
+ throws SSLException {
+ }
+ @Override
+ public void verify(String arg0, String[] arg1, String[]
+ arg2) throws SSLException {
+ }
+ });
+ }
// Set properties of the connection
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
diff -uNr gdata/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java gdata.mine/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java
--- gdata/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java 2009-02-12 05:09:34.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java 2009-03-22 14:10:47.000000000 +0900
@@ -37,7 +37,7 @@
public class AppsGroupsService extends AppsPropertyService {

public static final String BASE_URL =
- "http://apps-apis.google.com/a/feeds/group/2.0/";
+ "https://apps-apis.google.com/a/feeds/group/2.0/";
public final String baseDomainUrl;

public static final String APPS_PROP_GROUP_ID = "groupId";
diff -uNr gdata/java/src/com/google/gdata/client/http/HttpGDataRequest.java gdata.mine/java/src/com/google/gdata/client/http/HttpGDataRequest.java
--- gdata/java/src/com/google/gdata/client/http/HttpGDataRequest.java 2009-02-12 05:09:35.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/http/HttpGDataRequest.java 2009-03-22 13:49:39.000000000 +0900
@@ -16,6 +16,13 @@

package com.google.gdata.client.http;

+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import javax.security.cert.X509Certificate;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLException;
+
import com.google.gdata.util.common.xml.XmlWriter;
import com.google.gdata.client.AuthTokenFactory;
import com.google.gdata.client.GDataProtocol;
@@ -312,6 +319,29 @@
}
HttpURLConnection uc = (HttpURLConnection) requestUrl.openConnection();

+ // Open connection
+ if ( uc instanceof HttpsURLConnection ) {
+ HttpsURLConnection ucs = (HttpsURLConnection) uc;
+ ucs.setHostnameVerifier( new X509HostnameVerifier(){
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true;
+ }
+ @Override
+ public void verify(String arg0, SSLSocket arg1) throws IOException {
+ }
+ @Override
+ public void verify(String arg0,
+ java.security.cert.X509Certificate arg1)
+ throws SSLException {
+ }
+ @Override
+ public void verify(String arg0, String[] arg1, String[]
+ arg2) throws SSLException {
+ }
+ });
+ }
+
// Should never cache GData requests/responses
uc.setUseCaches(false);






あと gdata/java/build-src/core.xml のコンパイル箇所で android.jar に classpath を通せばコンパイルが通ります。(メッセージに従って mail.jar と activation.jar を用意する必要もあるけど)

でき上がった gdata-appsforyourdomain-1.0.jar と gdata-core-1.0.jar を使うときちんと動かすことができました。手軽に Google Apps の Group を追加できるだけのしょーもないアプリですが...



これを進化させて Google Apps の管理コンソールが作れそう。でも需要は無さそうだなorz

March 5, 2009

Werkzeug の easteregg



僕も最近話題の Werkzeug を使ってみようと思いました。しかしまだ使うまでにいたらず、とりあえず easteregg を発見して喜んでいる状態です。

February 23, 2009

Gaebarを使ってみた


先日 App Gallery という GAE 上のサービスを始めてみました。サービスをやるからにはデータをバックアップしたいと思い、Gaebar を試してみたのでご紹介します。

なんと Gaebar の画面は django pony です!

Gaebar は Google App Engine Backup And Restore の略で、文字通り Google App Engine 用のバックアップ・リストアツールです。ゲイバーと読むそうです。この名前が良いですねw

Gaebar は Django のアプリケーションで簡単に使えます。GAE上で動く既存の Django アプリケーションにプラグインする事で、バックアップ・リストア機能を追加する事ができます。

残念ながら今は Django で動いているアプリケーションでしか使えませんが、少し頑張れば同じ事は別のフレームワークでも可能だと思います。

準備する

まずは Gaebar をダウンロードします。
Gaebar github から Download 機能を使ってアーカイブをダウンロードするか、下記のように git で clone しても良いです。

$ git clone git://github.com/aral/gaebar.git

またはあなたのプロジェクトが git を使っているなら下記のコマンドが良いかもしれません。

$ git submodule add git://github.com/aral/gaebar.git


方法はともあれ、プロジェクト直下に gaebar というディレクトリができていれば大丈夫です。

次に GAE の SDK にパッチを当てます。これは Gaebar がデータを python のファイルとしてダウンロードしてきてローカルのファイルシステムに保存するために必要なのです。方法が AralのBlog Entry に乗っていますが、この情報は少し古いので、SDK-1.1.9では下記のパッチを使ってください。

--- ../google_appengine/google/appengine/tools/dev_appserver.py.org 2009-02-26 11:33:33.000000000 +0900
+++ ../google_appengine/google/appengine/tools/dev_appserver.py 2009-02-20 17:04:46.000000000 +0900
@@ -1274,6 +1274,7 @@
allowed_symbols = self._WHITE_LIST_PARTIAL_MODULES[module.__name__]
for symbol in set(module.__dict__) - set(allowed_symbols):
if not (symbol.startswith('__') and symbol.endswith('__')):
+ module.__dict__['old_'+symbol] = module.__dict__[symbol]
del module.__dict__[symbol]

if module.__name__ in self._MODULE_OVERRIDES:


注意) app-engine-patch を使う場合は、settings.py で DJANGO_STYLE_MODEL_KIND = False にしないと動きませんでした。(少なくとも私は動かせませんでした。)動かせた方がいたら教えてください :-)

1. settings.py は下記のようにします。

INSTALLED_APPS = (
# Other apps...
'gaebar',
)


2. urls.py は下記のとおりです

urlpatterns = patterns('',

# ...other URLs

url(r'^gaebar/', include('gaebar.urls')),
)


3. app.yaml に static folder を追加します。

# Static: Gaebar
- url: /gaebar/static
static_dir: gaebar/static


4. index.yaml にインデックスを追加します。一回 dev server で Gaebar を動かせば自動で追加されます。

- kind: GaebarCodeShard
properties:
- name: backup
- name: created_at


5. Gaebar の設定を settings.py に追加します。

GAEBAR_LOCAL_URL: ローカル開発サーバの絶対 URL を書きます。
GAEBAR_SECRET_KEY: 秘密の文字列を記入します。誰にも秘密にしてください。
GAEBAR_SERVERS: dict でサーバーを列挙します。
GAEBAR_MODELS: tuple でバックアップするモデルを列挙します。

App Gallery で使用している設定のサンプルを下記に掲載します。

GAEBAR_LOCAL_URL = 'http://localhost:8000'
GAEBAR_SECRET_KEY = 'replace_with_your_secret_key'

GAEBAR_SERVERS = {
u'Deployment': u'http://app-gallery.appspot.com',
u'Staging': u'http://app-gallery-staging.appspot.com',
u'Local Test': u'http://localhost:8080',
}

GAEBAR_MODELS = (
(
'appgallery.models',
(u'Application', u'ThumbnailImage'),
),
(
'tags.models',
(u'Tag', u'TagApplication')
),
(
'favorites.models',
(u'Favorite',)
),
(
'ragendja.auth.custom_models',
(u'User',),
),
)


あと注意点がもう一つ。ローカル開発サーバーで django.contrib.csrf.middleware.CsrfMiddleware を使っているとバックアップがうまく行きませんでした。なので私はローカル開発サーバーでだけ CsrfMiddleware をオフにしています。

やった!これで Gaebar が使用できます。使う前に、Gaebar がどんな風に動くか説明しておきますね。

Gaebar は datastore のデータを Python code としてバックアップします。その Python code を実行する事でリストアします。バックアップは長くかかりますが、GAE はそれを許さないので、Gaebar は バックアップとリストアのプロセスを細切れにして ajax を使って繰り返し呼び出します。

デフォルトでは Gaebar は 5 つの Entity をバックアップし Python code に変換し、1MB の制限を回避するために 300KB くらいのコード断片として datastore に保存します。バックアップの過程では、このすぐ後に、コード断片をローカルサーバー側にダウンロードして保存する事になります。

もしあなたがもっと高い Quota を持っているなら、views.py をいじる事でこれらのデフォルト値を変えられます。

Gaebar を使う時は、十分にテストしてから使ってくださいね。万が一データが消失しても、私や Aral は責任を持てませんので。

使い方

A. リモートバックアップを取るには

1. Gaebar をあなたのアプリケーションと一緒に GAE にデプロイします

注意: デプロイする時に、gaebar/backups フォルダ内のバックアップデータも一緒にアップロードされる事を頭にいれておいてください。不必要ならバックアップデータは別の場所に移動した状態でデプロイすると良いでしょう。

2. 本番サーバで Gaebar にアクセスします。(例: http://myapp.appspot.com/gaebar/ )

3. Create New Backup ボタンをクリックします。

* バックアップをする時に、ローカル開発サーバーが動作している必要があります。

注意: reference error が発生した場合(存在しない Entity への ReferenceProperty)は、バックアップ時には無視されます。リストアすれば reference error は消滅します。

注意: 今までバックアップした一番大きなデータストアは 18995 行で 223 code 断片、35MB ありました。ローカル開発サーバーへのリストアは一晩中かかりました(ローカル開発サーバーの datastore は激遅なので)。もし記録を破ったら aral@aralbalkan.com に教えてあげてください :-)

注意2: バックアップ中の例外を詳しく見たい場合は、本番サーバーの settings.py で DEBUG_PROPAGATE_EXCEPTIONS = True と設定してください。


B. ローカルでリストアする

開発中に、リアル世界のデータを使ってテストするために、ローカルの開発サーバーにデータをリストアできます。

1. ローカル開発サーバの Gaebar ページに行きます。(例: http://localhost:8000/gaebar/ )
2. リストからリストアしたいバックアップデータを選びます。
3. 終わるまで待ちます。

tips: あらかじめ /var/tmp/ にある datastore の history ファイルのバックアップを取った方が良いです。

ローカルのデータストアは非常に遅いので、覚悟してください。

C. ステージング環境にリストアする

GAE にはバージョニング機能がありますが、datastore は別バージョン間でも共通です。ですからステージング用の別アプリを使用するのは良い考えでしょう。

本番とは別のアプリケーションスロットを用意して、そこにリストアしたいバックアップデータと一緒に Gaebar をデプロイします。後はローカル開発サーバーでのリストアと同じです。

D. 本番環境にリストアする

本番環境でトラブルがあった場合などは、バックアップからリストアできます。データストアのデータは上書きされるので注意してください。


実際に App Gallery でもバックアップ・リストアしてみましたが、完璧に動いています。Gaebar はかなり便利なのでみなさんも使ってみてはいかがでしょうか。

February 11, 2009

Mercurial deps extension

Mercurial で svn:externals みたいな事できないのか #mercurial irc で聞いてみたところ、deps extensionを使うと良いらしい。

Mercurial に同梱されてないので別途このへんから落として来て使う。

設定


deps.py を適当なところに置いて、hgrc に下記のように設定する。
[extensions]
hgext.deps =
# 又は deps.py を hgext dir 以外に置いたなら下記のようにする
# deps = /path/to/deps.py


ロケーション設定


依存関係のロケーションを設定する。これも hgrc に書く。ここで紹介する例は libfoo が外部 Mercurial リポジトリにあって、libbar は外部の CVS で管理されている場合。

[deps]
aliases = libfoo, libbar
alias.libfoo = /path/to/libfoo
alias.libbar = :pserver:anonymous@cvs.server.org:/sources/bar
alias.libbar.command = cvs -z3 -d$source co -r $rev -d $dest bar


実際の依存関係を設定


これはコマンドで行う。
$ hg deps -a libfoo -r 3a9b061bada1 -d lib/foo 0.9.1
$ hg deps -a libbar -r v0r7_8 -d lib/bar 0.9.1

$ hg deps -a libfoo -r f24139319bdb -d lib/foo 1.0
$ hg deps -a libbar -r v0r8_0 -d lib/bar 1.0


書式はこう

hg deps -a <ロケーション設定のalias名> -r <外部のリビジョン> -d  <本体のバージョン>


本体のバージョンに応じて外部リポジトリのリビジョンを変えたりできるわけか。

で、これをすると下記のような .hgdeps というファイルが出来る。これも hg add するのが良いみたい。
[1.0]
f24139319bdb libfoo lib/foo
v0r8_0 libbar lib/bar

[0.9.1]
3a9b061bada1 libfoo lib/foo
v0r7_8 libbar lib/bar


依存関係を持ってくる



下記のようにして clone するくらいしか機能は無いみたい。
$ hg depsclone 1.0


この例では、バージョン 1.0 用の外部依存関係を取ってくる事ができる。

まあすごく便利っていうわけでも無いし、使いたい人も限られてるだろうけど、Mercurial で svn:externals みたいな事がしたい人は試してみてください。