ブラウザで動くGUIアプリをつくろう

ExtJSとpasterで作るGUIアプリ開発手法の紹介

tags:python, web, gui, tips
created:2008-04-29T04:18:44

py2jsを用いたpythonコードだけで作れるGUI開発手法の構築

Webアプリにはいろんな要素が必要

クライアントサイドを凝りたいと思ったときいろんな選択肢があります。 それらの中から私がいいなぁと思ったものをまとめてみました。

ExtJSという選択

ExtJS はJavaScript製のGUIフレームワーク。

これを使うと一つのページ内にウインドウをいくつも開いたりといった ページをデスクトップの様に扱うことが出きるようになります。

py2jsをつかったJavaScriptフリーな開発

py2jsというモジュールを こちら の記事から知りました。

これを使うと、pythonコードをJavaScriptに変換できるようになります。 ExtJSの初期化やイベントハンドリングをPythonで記述し、変換したJavaScriptを利用します。 このpy2jsをうまく使うことでJavaScriptに触れずにExtJSを使えるようになります。

JSON-RPCを使ったクライアント-サーバ通信

以上2つのモジュールを活用することで、クライアント側からサーバー側の関数呼び出しが可能です。 サーバー側の実装はクラスメソッドを用意して定型処理するだけで公開できます。 クライアント側はプロキシオブジェクトを生成、メソッド呼び出しするだけで利用可能になります。

Pasteは実用的なWSGIサーバー

Python Paste というモジュールが公開されています。 Paste-DeployやPaste-Scriptなどと併用し、 Pylonsなどのベースにもなっているわけなんですが、 PasteモジュールそのものはWSGIサーバーやWSGIミドルウェアを 素直にあつめたモジュールになっています。

WSGIだけでサーバを構築したいと思ったとき、 Python2.5付属のwsgirefモジュールでは不足しているものが一式揃っています。 かといってでしゃばったフレームワークの形を押し付けてこないところが好感触です。 あくまで、WSGIに則った形を徹底しています。

ソース一式

ソース一式を置いておきます。

desktop_app.zip

サーバーサイドコード

RPCサーバーとして作成します。 wsgi_jsonrpcの固有の問題ですが、wsgi_jsonrpcのlistmethods機能では一般に メソッド名リストを返すんですが、丁寧に辞書リストの形で返してきます。 名前リストに変換するようオーバーライドしています。 (jsorbのjsonrpc.jsモジュールの動作が、listmethodsの名前リストを利用するため。)

以上に注意が必要ですが、その他はなんでもregisterすればクライアントから呼び出せます。

#!/usr/bin/env python
# encoding: utf-8

import logging
import wsgi_jsonrpc
import random

class RpcServer(object):
  def __init__(self, server):
    self.server = server
    self.server.register_method(self._listMethods, 'system.listMethods')
    self.server.register_instance(self)

  def _listMethods(self):
    return [i['name'] for i in self.server.system_list_methods()]

  def data(self):
    names = ['hoge', 'moge', 'test', 'ほげ', 'もげ', 'テスト']
    return [[random.choice(names), random.random()*500] for i in range(120)]

application = wsgi_jsonrpc.WSGIJSONRPCApplication()
RpcServer(application)

クライアントコード

py2jsによるJavaScript作成によるクライアントコードの作成方法を紹介します。

慣れないと最初は難しいのですが、 以下のようにJavaScriptで処理したい内容をPythonで記述します。

rpc = JSONRpcClient('jsonrpc')
win = null

def main():
  def on_click():
    store = Ext.data.SimpleStore({
      'fields': [
        {'name': 'company'},
        {'name': 'price', 'type': 'float'}
      ]})
    store.loadData(rpc.data())
    grid = Ext.grid.GridPanel({
      'region': 'center',
      'sm': Ext.grid.RowSelectionModel({ 'singleSelect': true }),
      'store': store,
      'columns': [
        { 'id':'company',
          'header': "Company",
          'width': 200,
          'sortable': true,
          'dataIndex': 'company'},
        { 'header': "Price",
          'width': 75,
          'sortable': true,
          'renderer': 'usMoney',
          'dataIndex': 'price',
          'align':'right'}
      ],
      'stripeRows': true,
      'autoExpandColumn': 'company',
    })
    win = Ext.Window({
      'title':'Sample Window',
      'layout':'border',
      'width':320,
      'height':240,
      'items': [
        grid,
      ]
    })
    win.show(this)

  viewport = Ext.Viewport({
    'items' : Ext.Button({'id':'button1', 'text':'Test'})
  })
  Ext.get('button1').on('click', on_click)

Ext.onReady(main)

クロージャが使えないのでそこはPythonらしく イベントハンドラにはローカル関数を定義して設定しています。 以下のコマンドで「site-js.py」から「site.js」を生成します。

python py2js.py site-js.py > ../statics/js/site.js

注釈

Windowsユーザーは「/(スラッシュ)」を「(バックスラッシュ)」に置き換えてください。 (配布ファイルにはbuild.cmdというバッチファイルがありますのでその内容を参考にしてください。)

Pasteでつくるサーバー

Pasteのインストールは、

Windowsなら

easy_install paste

Linux系なら

sudo easy_install paste

でOKです。

easy_installが使えないという場合は こちら を参照してください。 Windowsの人は「Windows Notes」にも注意してください。

application/main.pyにapplicationというWSGIアプリが定義されていて、 そのたもろもろの静的ファイルを公開するというサーバーを構築する場合、 Pasteモジュールだけで以下のようにすれば、 シンプルなWSGIサーバーを稼働できます。

from paste.urlmap import URLMap
from paste.cgiapp import CGIApplication
from paste.urlparser import StaticURLParser
from paste.fileapp import FileApp
import paste.httpserver
from paste.evalexception.middleware import EvalException
import paste.translogger
import paste.reloader

from application import main

app = URLMap()
app['/'] = main.application
app['/images'] = StaticURLParser('statics/images')
app['/js'] = StaticURLParser('statics/js')
app['/css'] = StaticURLParser('statics/css')
app['/favicon.ico'] = FileApp('statics/favicon.ico')

if __name__=='__main__':
  paste.reloader.install()
  app = EvalException(app)
  format = ('[%(time)s] "%(REQUEST_METHOD)s '
    '%(REQUEST_URI)s %(HTTP_VERSION)s" '
    '%(status)s %(bytes)s')
  app = paste.translogger.make_filter(app, {}, format=format)
  paste.httpserver.serve(app, port=8080, host='localhost')
paste.urlmap.URLMapクラス
url毎に違うWSGIアプリを呼び出すミドルウェアです。
paste.cgiapp.CGIApplicationクラス
CGI向けのPythonモジュールをラッピングするミドルウェアです。
paste.urlpaser.StaticURLPaserクラス
静的ファイル群のあるフォルダをパブリッシュするミドルウェアです。
paste.fileapp.FileAppクラス
単独の静的ファイルをパブリッシュするミドルウェアです。
paste.evalexception.middleware.EvalExceptionクラス
例外をキャッチしたときにレポートを出力するミドルウェア
paste.translogger.make_filter関数
ロギングミドルウェア
paste.reloaderモジュール
関連するコードが更新されたのを検出して 終了 するのを支援するツール。 この時のプロセス終了ステータスで繰り返し起動するようにシェルスクリプトを組んでおけば 自動再起動する仕組みを作ることができます。

他にもいろんなツールやミドルウェアが満載です。 それらのほとんどがWSGIのパターンで実装されているので、利用が容易です。

サーバーのスタート

Windowsでは以下の内容のバッチファイルを実行します。 ソース一式にある「serve.cmd」がそうです。

@echo off
:repeat
python server.py
if %errorlevel% == 3 goto repeat

Linuxでは以下の内容のシェルスクリプトを実行します。 ソース一式にある「serve.sh」がそうです。

#!/bin/sh
err=3
while test "$err" -eq 3 ; do
    python server.py
    err="$?"
done

ブラウザで表示

http://localhost:8080 を開くとボタンだけのページが表示されます。 ボタンを押すと以下のようなウインドウを表示します。

../../_images/web_app1.png

グリッドコントロールに表示する内容はJSONRPCでpythonサーバに問い合わせて取得しています。 当然イベントに対してpythonサーバのメソッドを呼ぶことも同様にできます。

まとめ

今回配布するファイルセットのうち、 「application/rpc.py」にサーバーサイドコード。 「apprication/site-js.py」にクライアントサイドコードを記述するかたちで GUIアプリを開発できます。

当然AdobeのAirとしての定義ファイルを書き加えれば、単独のGUIアプリとしても使えますよ〜。