2009年12月25日

Descriptor:__get__とかそのあたりのスペシャルメソッド

スペシャルメソッドの__get__や__set__に注目してみる。
代入したり、されたりする場合に呼ばれる。
こいつらをオーバーライドすれば、代入時の処理を追加できるというわけ。
propertyもこれを使って実装できる。
試しに、intのみ受け取り、最大値を制限できるプロパティを作ってみた。


""" descriptor test
>>> class Sample(object):
... value = IntProperty('value', max=10)
... def __init__(self):
... self.value = 10
>>> s = Sample()
>>> s.value
10
>>> s.value = 9
>>> s.value
9
>>> s.value = 'a'
Traceback (most recent call last):
...
TypeError
>>> s.value = 11
Traceback (most recent call last):
...
ValueError: 11 is greater than 10
"""

class IntProperty(object):
def __init__(self, name='var', max=None):
self.name = name
self.max = max

def _assert(self, value):
if not isinstance(value, int):
raise TypeError

if self.max is not None and self.max < value:
raise ValueError, '%d is greater than %d' % (value, self.max)

def __get__(self, obj, objtype):
return obj.__dict__[self.name]

def __set__(self, obj, value):
self._assert(value)
obj.__dict__[self.name] = value

if __name__ == '__main__':
import doctest
doctest.testmod()


__dict__に値を入れるための名前が必要なのがいまいち><


参考:How-To Guide for Descriptors



2009年12月11日

repoze.bfgで遊びながら(略 (2)

前回repoze.bfgをbuildoutで使う環境を作ったので、なんか実装してみましょう。
repozeは、一般的にmodel-view-templateの構成をするようですが、viewだけの実装も可能です。

viewを実装する
src/board/views.py がすでにviewを実装するモジュールとして存在しているので、ここに追加してみます。

import webob
def hello(request):
return webob.Response(body='Hello, world', content_type="text/plain")

既に、my_viewという関数が実装されていますが、こちらはtemplate使ってるので、dictを返しています。
今回は、templateなしで実装するので、自分でResponseまで作成します。
Responseのコンストラクタで、bodyに渡した内容が、httpレスポンスのボディとなります。
content typeの指定も可能です。

テストする
あまりテストする必要もないですが、一応やってみましょう。

import webob
def hello(request):
"""
>>> req = webob.Request({})
>>> res = hello(req)
>>> res.body
'Hello, world'
>>> res.content_type
'text/plain'
"""
return webob.Response(body='Hello, world', content_type="text/plain")

doctestで書いています。
前回作っておいた、webtestを実行します。

bin/webtest

※テストが2つ実行されますが、1つは自動生成されたテストです。

viewを登録する
作成したviewをWebアプリケーションから呼び出されるように登録します。
viewの登録方法は2種類あります。

  • zcmlファイルに記述する
  • Configuratorのadd_viewメソッドを呼ぶ

今回はConfiguratorを使うことにしましょう。
src/board/run.py の中でConfiguratorを作成しているので、make_wsgi_appが呼ばれる前に、add_viewメソッドを追加します。

import views
config.add_view(views.hello, name='hello')

この場合は、helloという名前でURLにマッピングされます。

Webアプリケーションのテストを追加します。
tests.pyがすでにありますが、中身を消して、改めてテストを書きます。

import webtest
from paste.deploy.loadwsgi import loadapp
import os
config = os.path.join(os.path.dirname(__file__), '..')

def test_hello():
app = webtest.TestApp(loadapp('config:board.ini', relative_to=config))
res = app.get('/hello')
assert res.status_int == 200
assert res.body == 'Hello, world!'


repoze.bfgはPasteDeployを利用しているので、WSGIアプリケーションもPasteDeploy経由で取得します。paste.deploy.loadwsgi:loadappで取得できますが、board.iniは一段上のディレクトリにあるので、os.pathなどを使って特定しています。
wsgiアプリケーションを取得したら、webtest:TestAppでラップします。
TestAppは、getやpostなどの呼び出しが簡単にできるようになっています。
Responseが帰ってくるので、レスポンスステータスやボディを確認します。
このテストも、webtestから実行されます。

bin/webtest


アプリケーションを実行する
アプリケーションは、bin/pasterで実行できます。
※前回のbuildout.cfgでは、実行に失敗します。以下のように直しましょう。

[repoze]
recipe = zc.recipe.egg
eggs =
PasteScript
repoze.bfg
board




bin/paster src/board.ini

これで、http://localhost:6543/hello にアクセスすると、"Hello, world!"と表示されます。

2009年12月7日

repoze.bfgで遊びながら、pasteの復習しつつ、buildoutの使い方に慣れる(1)

id:ytakeuchさんからZope/Ploneアドベントカレンダーのバトンが回ってきました。
repozeは、zope由来のコンポーネントをWSGIアプリケーションで利用できるようにしたコンポーネント集です。
それらのコンポーネントを再構成したrepoze.bfgというフレームワークが提供されています。
repoze.bfgは、ZODBやURLトラバーサルなど、Zopeのベースとなるアーキテクチャを利用しつつ、必要なコンポーネントはその都度取り込むことができます。

環境構築
distribute, virtualenv, buildoutを利用した環境構築をします。
この辺りの話は@shimizukawa先生のこちらのエントリが詳しいです。
まずは、virtualenvで仮想python環境を作ります。今回は、--distributeオプションを使ってsetuptoolsの代わりにdistributeを使います。
virtualenv --no-site-packages --distribute env
できあがった仮想環境に入る。
. env/bin/activate
作業用ディレクトリを作ります。
mkdir board
cd board
buildout環境を作ります。bootstrap.pyはこことかここにありますので、適当に。
bootstrap.pyにも--distributeオプションを付けます。
cp /path/to/bootstrap.py .
python bootstrap.py --ditribute

buildout.cfgを作成。
PasteScriptでソースベースを生成するために、repoze.bfgとPasteScriptを入れます。
buildout.cfg
[buildout]
parts = repoze
[repoze]
recipe = zc.recipe.egg
eggs =
 PasteScript
 repoze.bfg

ローカルのbuildoutを実行して環境を更新
bin/buildout
ローカルにpasterコマンドが入ります。また、プロジェクトテンプレートにrepoze.bfgのものが追加されるので、これらを使ってプロジェクトを作成します。
bin/paster create -t bfg_zodb board
mv board src

次にテスト用にnoseを導入します。
また、テスト対象のプロジェクトもeggで追加しないと参照できないので、作成したboardプロジェクトのsrcディレクトリをdevelopに追加してeggとして取り扱えるようにします。

buildout.cfg
[buildout]
parts =
 repoze
 webtest
develop = src
[repoze]
recipe = zc.recipe.egg
eggs =
 PasteScript
 repoze.bfg

[webtest]
recipe = zc.recipe.egg:scripts
eggs =
 distribute
 board
 nose
 WebTest
initialization =
 sys.argv[1:1] = ['-w', 'board', '--with-doctest']
scripts =
 nosetests=webtest
上の設定では、WebTestもテストで利用できるようにeggsに追加しています。
また、コマンドラインを編集して、srcディレクトリ以下でdoctestを有効にしたテストを実行するようにして、コマンド名をwebtestにしています。
試しにテスト実行してみます。
bin/webtest

生成されたプロジェクトを探索する
pasterで生成されたプロジェクトの中身を確認してみます。
PasteScriptでは、PasteDeployによる設定ファイルを読み込んでWSGIアプリケーションを実行します。
repoze_zodbテンプレートで生成されたプロジェクトでは、[パッケージ名].iniが設定ファイルになっています。
pipeline:main がwsgiアプリの起点になります。
pipelineでは、WSGIミドルウェアの設定が書かれており、さらにapp:zodbで設定されているWSGIアプリを参照します。
app:zodbでは、use = egg:board#appと書かれている部分が実際のWSGIアプリです。その他は、WSGIアプリを呼び出すときに渡される設定値です。
egg:board#appってなんでしょう?
まずは、eggパッケージです。
eggには、entry_pointsという仕組みがあり、eggパッケージが提供するインターフェイスのようなものを公開しています。
boardのegg情報は、src/board.egg_info以下にあり、entry_points.txtにentry_pointsの情報が記述されています。
      [paste.app_factory]
      app = board.run:app
board.runモジュールのappオブジェクトがpaste.app_factoryインターフェイスとして公開されています。
これが、board.iniに書かれているegg:blog#appの正体です。

paste.app_factoryは、設定を受け取り、wsgiアプリケーションを返す関数が求められます。

では、src/board/run.pyを見てみましょう。
appは関数で定義されています。

def app(global_config, **settings):

global_configはboard.iniのDEFAULTセクションで設定されている内容です。
settingsは、board.iniのapp:zodbセクションで設定されている内容です。
appはglobal_configやsettingsの内容を使って、WSGIアプリケーションを作成します。

動かしてみる
pasterコマンドで、wsgiアプリを実行します。
bin/paster serve src/blog.ini
このときのhttpサーバーは、board.iniでserver:mainセクションに設定されています。






追記


次のバトンはnyusukeにお願いしました。

2009年12月5日

repoze.whoを調べてみた

repozeというzope由来のコンポーネントをwsgiアプリで使えるようにしているプロジェクトがあります。

ということで、(第6回)Zope/Plone開発勉強会行ってきました。
今回は、ユーザー認証用のコンポーネント repoze.who を調べました。
whoのポイントは、細分化されたプラグイン構造。
ひとまずの理解は以下のとおり。Metadata Providerだけちょっとわからない><

Identifier

リクエスト、レスポンスでの認証情報を取り扱います。
リクエストから認証情報を取り出したり、レスポンスに認証情報を追加したりする部分です。
既存のプラグインには、Basic認証(のヘッダ情報)や、Cookieなどを使ったものがあります。

Authenticator
Identifierの情報を受けて、実際に認証する部分です。
既存のプラグインでは、htpasswdを使うものや、SQLクエリ、XMLファイルが提供されています。

Metadata Provider
認証以外のユーザーデータ(電話番号とか)を取り扱う?

Challenger
認証が必要であることを通知する部分です。
既存のプラグインでは、HTTPヘッダで通知するものや、ログインフォーム画面を表示するものなどがあります。

さらに権限をとりあつかうrepoze.whatとというものがあるので、こちらも調べる!


buildoutでwebtestする


buildoutのzc.recipe.eggは、スクリプトにデフォルトの引数をつけたりできる。



src以下にテスト対象があるって想定で以下のように。




[webtest]
recipe = zc.recipe.egg:scripts
eggs =
myapp
setuptools
nose
WebTest
initialization =
sys.argv[1:1] = ["-w", "src", "--with-doctest"]
scripts = nosetests=webtest

GAEのテストをしたければ以下のような。


[webtest]
recipe = zc.recipe.egg:scripts
eggs =
myapp
setuptools
nose
NoseGAE
WebTest
initialization =
sys.argv[1:1] = ["-w", "src", "--with-gae", "--with-doctest"]
scripts = nosetests=webtest


スライスに代入するのって、文法上できるってだけで、普通やらないよなと思ってましたが…



2009年11月14日

Python Hack-a-thon

Python Hack-a-thonに参加中。
発表資料的なものを作ったので、おいとく。

2009年11月5日

delegateのシグネチャを調べる


delegateのシグネチャを調べる方法を探している人がいたので、調べてみた。



class Program
{
static void Main(string[] args)
{
Type t = typeof(MyEventHandler);
Console.WriteLine(t);
MethodInfo m = t.GetMethod("Invoke");
Console.WriteLine(m.ReturnType);
foreach (var p in m.GetParameters())
{
Console.WriteLine(p);
}
}
}


public delegate bool MyEventHandler(object sender);


ildasmで調べてみると、
Invokeメソッドがdelegateの宣言と一致してるようなので、リフレクションでパラメータと戻り値の型を調べればOKっぽい。