WSGI on Qt (PyQt or PySide)

こんにちは、Python 会の。。。思いつきませんでした。初 "Python Web フレームワーク アドベントカレンダー2010" に参加させていただくことになりました。よろしくおねがいします。アドベントカレンダー参加者のブログは cielquis.net に綺麗にまとめられていますよ。

ごめんなさい。Web アプリケーションフレームワークの作成なんて 1 日じゃむりぽ><。メインで使ってるフレームワークは、Djangowebapp ぐらい。。。最近 dis られてるので、Django 記事書くのもあれだ・・・。

ということで本題です。Python は WSGI (Web Server Gateway Interface) という Web アプリケーションと Web サーバ間の共通インターフェースがあります。そのインターフェースを実装したオブジェクトは、Web アプリケーションとして動作させることができます。ということで、デスクトップアプリケーションを Web アプリケーションにする方法を書きたいと思います。今回は Qt の Python binding な PyQt / PySide で WSGI アプリケーションを作ってみたいと思います。実装は PySide ですが、 PyQt でも動くはずです。

STEP1: PySide で Hello World!

ほんと Web アプリケーションじゃなくて申し訳ないですが、最初に PySide で "Hello World!" を表示します。

import sys

from PySide import QtCore, QtNetwork, QtGui

class QWSGILabel(QtGui.QLabel):
    def __init__(self, text, parent=None):
        super(QWSGILabel, self).__init__(text, parent=parent)

class QHttpWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(QHttpWidget, self).__init__(parent=parent)
        self.setLayout(QtGui.QVBoxLayout(self))

        # Create label instance
        qwsgilabel = QWSGILabel("Hello World!", parent=self)
        self.layout().addWidget(qwsgilabel)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = QHttpWidget()
    window.show()
    sys.exit(app.exec_())

QLabel を単純に継承した QWSGILabel に "Hello World!" と表示しています。実行すると、以下のようなウィンドウが表示されます。

step1.png

STEP2: WSGI on PySide で Hello World!

STEP1 で作成した QWSGILabel を、さっそく Web アプリケーションにしてみます。

import sys

from wsgiref.util import setup_testing_defaults
from wsgiref import simple_server

from PySide import QtCore, QtNetwork, QtGui

ENCODING = 'utf-8'

class QWSGILabel(QtGui.QLabel):
    def __init__(self, text, parent=None):
        super(QWSGILabel, self).__init__(text, parent=parent)

    def __call__(self, environ, start_response):
        """WSGI Web application interface"""

        setup_testing_defaults(environ)
        start_response('200 OK', [('Content-type', 'text/html'), ])
        return self.text().encode(ENCODING) # Return label text

class QHttpThread(QtCore.QThread):
    u"""Http threads"""

    def __init__(self, wsgi_server, parent=None):
        super(QHttpThread, self).__init__(parent=parent)
        self.wsgi_server = wsgi_server
        self.__is_run = False

    def run(self):
        self.__is_run = True
        while (self.__is_run):
            self.wsgi_server.handle_request()

    def stop(self):
        self.__is_run = False

class QHttpWidget(QtGui.QWidget):
    def __init__(self, host='localhost', port=8000, parent=None):
        super(QHttpWidget, self).__init__(parent=parent)
        self.setLayout(QtGui.QVBoxLayout(self))

        # Create label instance
        qwsgilabel = QWSGILabel("Hello World!", parent=self)
        self.layout().addWidget(qwsgilabel)

        # Create and start WSGI Web server
        wsgi_server = QHttpThread(
                simple_server.make_server(host, port, qwsgilabel), parent=self)
        wsgi_server.start()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = QHttpWidget()
    window.show()
    sys.exit(app.exec_())

QWSGILabel に __call__ 関数を追加し、ラベルにセットされているテキストを返すシンプルな Web アプリケーションを作成しています。そして、QThread を継承した QHttpThread に Web サーバを登録し、スレッドを開始しています。実行すると、STEP1 と同じウィンドウが起動します。起動中にブラウザからアクセスすると、"Hello World!" が表示されます。

step2.png

STEP 3: WSGI on PySide でフォーム操作

簡単な GET リクエストが動いたので、簡単なフォーム操作をしてみたいと思います。せっかくなので、WSGI サーバの起動と停止もできるようにして見ました。ソースは長くなっちゃったので bitbucket にのっけました。Qwidget を継承した QWSGIFormWidget に __call__ を追加し、フォームを制御しています。QWSGIFormWidget に テキストインプット (QWSGILineEdit) やセレクトボックス (QWSGIQComboBox) を追加し、REQUEST_METHOD が POST だった時にそれぞれを POST された値で更新 (update(value)) するようになってます。html()update(value) を追加した QWidget のサブクラスであれば、他のフォーム部品も動くはずです。起動すると以下のように、デスクトップアプリケーションとブラウザ間で通信することができます。

step3_1.png

"run" ボタンを押すと Web サーバが起動

step3_2.png

ブラウザからアクセス

step3_3.png

値を変えて "submit"

step3_4.png

一瞬で各値が更新されます

もちょっと時間があれば、html()update(value) を動的に追加したり、バリデーション実装して WAF っぽくしたかったです。あと、update(value) 時に emmit() を実行したりすると、PySide 側のイベントと連動することができます。予断ですが、Qt には QtNetwork.QTCPServer クラスや QTCPSocket クラスが用意されており、QTCPServer を継承した Web サーバをごりごり書くこともできます (サンプルはこっちの方が多い)。WSGI いいですね!

誰得

さて、明日はいよいよクリスマスイブですね!明日のアドベントカレンダーですが、英語が得意なイケメンアメリカ県民日本人の Google API エキスパートな @IanMLewis 先生から、素敵なクリスマスプレゼントがあると思います。

コメント

このブログの人気の投稿

Python から Win32 API 経由で印刷する

Disqus のスケール - Django 編

Disqus のスケール - Django で月間80億PVを処理する