aodag memohttp://pelican.aodag.jp/2014-05-03T00:00:00+02:00複数バージョンのPythonを使い分ける2014-05-03T00:00:00+02:00aodagtag:pelican.aodag.jp,2014-05-03:20140503-python-multiple-version.html<!-- -*- coding:utf-8 -*- -->
<p>なんとなくネタとして。
Pythonは大きく2系と3系になってますが、そろそろ3系に移行しつつも2系のケアをしてあげないといけません。
なので必然的に複数バージョンのPythonを使い分けることになります。
なんかそういうの簡単にできちゃうよーという <a class="reference external" href="https://github.com/yyuu/pyenv">超便利ツール</a> があるらしいですが
なんでわざわざツールの使い方覚えないといけないのかさっぱり理解不能なので、 <em>超便利ツール</em> にさっぱりついてけないなりのやり方をメモっておきます。</p>
<div class="section" id="id2">
<h2>方針とか</h2>
<ul class="simple">
<li>プロジェクト内ごとの環境は virtualenv を使う</li>
<li>tox を使ってバージョンごとのテストなどを実行する</li>
<li>/opt/python-{version} にインストールする</li>
<li>バージョンつきの pythonコマンド(<tt class="docutils literal">python3.4</tt>,``python2.7`` など) は PATHに常に含まれるようにする</li>
</ul>
<p>とりあえずvirtualenvするには python コマンドの場所だけわかれば十分です。
<tt class="docutils literal"><span class="pre">/opt/python-{version}</span></tt> にインストールしてあれば、 <tt class="docutils literal">virtualenv <span class="pre">-p</span> <span class="pre">/opt/python-{version}/bin/python</span></tt> で作れます。</p>
<p><tt class="docutils literal">python3.4</tt> などのバージョンつきのコマンドをPATHに入れるのはtox用です。
toxでデフォルトで使えるtestenvでpy34,py33などは、これらのコマンドがPATHにあると使えます。</p>
</div>
<div class="section" id="id3">
<h2>手順</h2>
<p>まず <tt class="docutils literal">/opt</tt> 以下に インストールします:</p>
<pre class="literal-block">
$ ./configure --prefix=/opt/python-3.4.0
$ make
$ sudo make install
</pre>
<p>$HOME/bin にリンクを貼ります:</p>
<pre class="literal-block">
$ cd $HOME/bin
$ ln -s /opt/python-3.4.0/bin/python3.4 .
</pre>
<p>一応 $HOME/bin がPATHに入るように profile などに設定してあるか確認しましょう。</p>
<pre class="literal-block">
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
</pre>
<p>こんな感じの設定があればOKです。</p>
</div>
<div class="section" id="id4">
<h2>使い方</h2>
<p>virtualenvするときに <tt class="docutils literal"><span class="pre">-p</span></tt> オプションでインタプリタを指定しましょう。</p>
<pre class="literal-block">
$ virtualenv -p python3.4
</pre>
<p>マイナーバージョンまで指定が必要であれば、 /opt 以下のインストール先を指定します。</p>
<pre class="literal-block">
$ virtualenv -p /opt/python-3.3.4/bin/python
</pre>
</div>
<div class="section" id="id5">
<h2>まとめ</h2>
<p>簡単すぎて記事にするまでもなかったですね!</p>
</div>
pipとwheelでテスト環境構築をスピードアップ2014-05-02T00:00:00+02:00aodagtag:pelican.aodag.jp,2014-05-02:20140502-pip-wheel-speedup.html<!-- -*- coding:utf-8 -*- -->
<p>まず最初にお知らせ。
<a class="reference external" href="http://pelican.aodag.jp/20140407-travis-tox.html">travisでtoxをつかうtips</a> で、travisにpython3.4をインストールして、toxの環境振り分けをするってのを書きましたが、すでにtravisでpython3.4使えるようなので、事前のapt-getは不要となりました。</p>
<p>そして、 ubuntus 14.04 trusty では python3.4がデフォルトでインストールされているようですが、 ensure-pip が入っていないという罠により pyvenv するとエラーになります。
とりあえず debian系の python パッケージは変なパッチあたってるのも、そろそろいい加減にしてほしいところなので、もうしばらくはpython3.4はソースインストールでやっていくことにします。</p>
<p>ここから本題。
<a class="reference external" href="https://travis-ci.org/">travis</a> や <a class="reference external" href="https://drone.io/">drone.io</a> などでciを動かしている場合、テストごとに環境を作成しているわけですが、依存ライブラリが増えてくるとこの部分で結構な時間がとられます。
下手するとテストよりも環境構築に時間がかかるわけで、時間の限られた条件の中で利用する貧乏人としては、本質的じゃないところのコストはできるだけ減らしたいもの。
ということでいろいろ試行錯誤の末、以下のようにしました。</p>
<ul class="simple">
<li>依存ライブラリはリポジトリに入れる</li>
<li>toxなどでリポジトリ内の依存ライブラリを参照するように調整する</li>
<li>toxにテストの詳細を記述し、travis.yml にはtox実行に必要なものだけ書く</li>
</ul>
<p>3つ目はすでに <a class="reference external" href="http://pelican.aodag.jp/20140407-travis-tox.html">travisでtoxをつかうtips</a> にも書いたことなので細かい説明はしません。</p>
<div class="section" id="id2">
<h2>依存ライブラリをリポジトリに入れる</h2>
<p>リポジトリに入れる方法ですが、インストール時間の短縮まで狙うことにして、wheel形式のバイナリパッケージを保存するようにします。</p>
<p>個別に行う場合は以下のとおり</p>
<pre class="literal-block">
pip wheel -f wheelhouse <dist>
</pre>
<p>wheel サブコマンドは requirements.txt も使えるので、そのようにしてもOK</p>
<pre class="literal-block">
pip wheel -r requirements.txt -f wheelhouse
</pre>
<p>pip wheel したライブラリをインストールするには、 -f (find-links) オプションで wheelhouseディレクトリを参照するようにします。また、 --no-index オプションで、pypiを見に行かないようにします。</p>
<pre class="literal-block">
pip install -f wheelhouse --no-index -r requirements.txt
</pre>
<p>毎回指定するのは面倒なので、オプションこみのファイルを作ります。
dev-requires.txt という名前で作成してみましょう。</p>
<p>dev-requires.txt:</p>
<pre class="literal-block">
-f wheelhouse
--no-index
-r requirements.txt
</pre>
<p>これで、 pip install -r dev-requires.txt するだけですべてがそろいます。
ダウンロードなし、ソース展開なしのインストールの速さに驚くことでしょう。</p>
</div>
<div class="section" id="tox">
<h2>toxなどでリポジトリ内の依存ライブラリを参照するように調整する</h2>
<p>では、この dev-requires で tox の環境構築も行うようにしましょう。
toxでは tox.ini の testenv セクションで依存ライブラリを指定するようになっています。</p>
<pre class="literal-block">
[testenv]
deps = pytest
pytest-cov
mock
</pre>
<p>このように書いてある場合、 pip によって pypiからのダウンロードとインストールが行われます。
ここで先ほど用意した dev-requires.txt を使うには以下のように書きます。</p>
<pre class="literal-block">
[testenv]
deps = -rdev-requires.txt
</pre>
<p><tt class="docutils literal"><span class="pre">-r</span></tt> と <tt class="docutils literal"><span class="pre">dev-requires.txt</span></tt> の間を開けてはいけません。
(開けてしまうと pipが -r という名前のライブラリを探しにいってしまいます)
このように指定すれば、toxがテスト環境を作る時間が一気に短縮できます。</p>
</div>
<div class="section" id="travistox">
<h2>travisでtoxを使う</h2>
<p>toxにテストを書いてあるので、 script を tox にしておけば実行されます。
また、 matrix を使って、 tox の testenvを切り替えるようにしておけば、travis上でもtoxのtestenvごとの結果を確認できます。</p>
</div>
<div class="section" id="id3">
<h2>サンプル</h2>
<p><a class="reference external" href="https://github.com/aodag/WebDispatch">WebDispatch</a> で使っている .travis.ymlとtox.ini です。</p>
<p><a class="reference external" href="https://travis-ci.org/aodag/WebDispatch">https://travis-ci.org/aodag/WebDispatch</a> で実行結果を見れます。</p>
<p>.travis.yml:</p>
<pre class="literal-block">
language: python
python: 3.3
env:
matrix:
- TOXENV=py27
- TOXENV=py33
- TOXENV=py34
- TOXENV=coverage
- TOXENV=flake8
- TOXENV=pylint
install:
- pip install tox -f wheelhouse
- if test "$TOXENV" = coverage ; then pip install python-coveralls ; fi
script: tox
after_success:
- if test "$TOXENV" = coverage ; then coveralls ; fi
</pre>
<p>coverall 用の通知設定がありますが、それ以外はtoxを動かすためだけの設定になっています。
もちろんtox自体もwheel化してwheelhouseに保存してあるものからインストールします。</p>
<p>tox.ini:</p>
<pre class="literal-block">
[tox]
envlist = py27,py33,py34,pypy,coverage,flake8,pylint
[testenv]
deps = -rdev-requires.txt
commands = py.test webdispatch
[testenv:coverage]
basepython = python3.3
deps = -rdev-requires.txt
commands = py.test webdispatch --cov=webdispatch --cov-report=term-missing
[testenv:flake8]
basepython = python3.3
deps = -rdev-requires.txt
commands = flake8 webdispatch
[testenv:pylint]
basepython = python3.3
deps = -rdev-requires.txt
commands = pylint webdispatch
</pre>
<p>各種バージョンと、品質チェック系のテストなどをしています。
環境構築が速いので、ローカルでもばんばん実行しています。</p>
</div>
<div class="section" id="id4">
<h2>まとめ</h2>
<p>Wheel形式のパッケージをどう活用するかというアイディアは、PyCon Apac 2013 で すでに検討していたのですが、 pipやwheelなどのツールがバージョンアップしてきたことやtox、travisへの理解が深まったことで、上記のような方法に行き着きました。</p>
</div>
travisでtoxをつかうtips2014-04-07T00:00:00+02:00aodagtag:pelican.aodag.jp,2014-04-07:20140407-travis-tox.html<!-- -*- coding:utf-8 -*- -->
<p>さて、python3.4とか <a class="reference external" href="https://pypi.python.org/pypi?%3Aaction=list_classifiers">trove</a> にも登録されたし、自分で書いてるライブラリは対応させてこうかなというところです。</p>
<p>で、各種バージョンでのテストを通すのに <a class="reference external" href="http://tox.readthedocs.org/en/latest/">tox</a> 使うのが最近の常識になってきていると思いますが、travisでciをしようとすると、.travis.ymlに同じようなことを書いてしまうわけです。
さらに、今のところ travis ではpython3.4が入っていない。</p>
<p>最近注目している次期PyPIの実装である <a class="reference external" href="https://github.com/pypa/warehouse">warehouse</a> をみていたら、python3.4でtravisでのテストを実行させている。
しかも、中でtoxを呼ぶようにしている。</p>
<p>python3.4は単にdeadsnakesからapt-getでインストールしているだけです。
tox呼ぶこと自体は簡単で、単に .travis.yml で script に tox を指定すればOK。
ただし、これだと tox ですべてのバージョンを一気にテストするわけで、どこかのバージョンでテストが失敗したのかわからなくなります。
あとなんかせっかくtravisでヴァージョンごととかできるのに、そうならなくなってるのが非常にださい。</p>
<p>warehouseの .travis.yml では、環境変数 <tt class="docutils literal">TOXENV</tt> のmatrixを設定してありました。</p>
<p>ということで、これを利用して以下のような .travis.yml を作ってみました。</p>
<pre class="literal-block">
python: 3.3
env:
matrix:
- TOXENV=py26
- TOXENV=py27
- TOXENV=pypy
- TOXENV=py33
- TOXENV=py34
before_install:
- sudo apt-get update
- sudo apt-get install python3.4 python3.4-dev
install: pip install tox
script: tox
</pre>
<p>最初の <tt class="docutils literal">python: 3.3</tt> はtoxが動けばなんでもいいです。
そのあとのenv:matrix: で、toxでテストを実行したいenv名をそれぞれ環境変数TOXENVで指定、あとは toxインストールとtoxの実行を書いておけばOK。
今のところ <a class="reference external" href="https://travis-ci.org/aodag/WebDispatch">WebDispatch</a> に適用しています。</p>
<p>先端のことやってる人たちは細かいところでいちいちセンスがいいですね。</p>
ubuntuでpyvenvしたときの落とし穴2014-03-10T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-03-10:20140310-pitfalls-ubuntu-pyvenv.html<!-- -*- coding:utf-8 -*- -->
<p>dockerでアプリケーションテスト用の環境を作ろうとしたときにはまったので、誰得メモ。</p>
<p>python3.3のwebアプリケーションのテストをpostgresqlとかredisとかを簡単にリセットできる環境がほしいと思ったので、dockerで試していました。
で、なんかsaucyのイメージって公式っぽい base リポジトリにはなかったので、とりあえずppa:deadsnakesで各種バージョンのPythonが完備されているshimizukawa/python-allを使ってみました。</p>
<p>そして、dockerの中で環境作るためにpyvenvしてからのget-pip.pyをしたところ、pipが <tt class="docutils literal">$VIRTUAL_ENV/bin</tt> じゃなくて、 <tt class="docutils literal">$VIRTUAL_ENV/local/bin</tt> にインストールされてしまう問題に気づきました。
これだとPATHに含まれてないので、最初はpipのインストールに失敗したように見えます。
実際は違うところにインストールされていました。</p>
<p>で、なぜだろうと原因を探りつつ、発生する条件を特定してみると</p>
<ul class="simple">
<li>activateした場合は発生しない。pyvenvしたpythonをパス指定で実行すると発生する。</li>
<li>ソースインストールしたPythonでは発生しない。</li>
<li>deadsnakesでなくともubuntuのパッケージで入れたPython3.3でも発生する。</li>
<li>pyvenvじゃなくて、virtualenvのときは発生しない。</li>
</ul>
<p>まずactivateすると発生するってことで、その前後でsite-packagesを調べると、activateするとpyvenvした環境がsite.getsitepackages()の結果に含まれていることがわかりました。
pipは最終的に、 <tt class="docutils literal">setup.py install</tt> を呼ぶようになってるので、このあたりの情報が狂ってしまうとどうしようもないのでしょう。
ソースインストールしたPythonではそういった動作はしてなかったので、パッケージの場合に当てられてるパッチかなと思い、パッケージソースを確認してみます。</p>
<p>なぜかdebianのほうのソースを確認していますが、多分ubuntuも同じでしょう。</p>
<p><a class="reference external" href="http://bazaar.launchpad.net/~doko/python/pkg3.3-debian/view/head:/patches/site-locations.diff">http://bazaar.launchpad.net/~doko/python/pkg3.3-debian/view/head:/patches/site-locations.diff</a></p>
<p>ここで、dist-packagesとかなんかごちゃごちゃやってますが、 VIRTUAL_ENV環境変数が有効なときに、その環境のsite-packagesを追加するようになっています。
これで、activateすれば発生しない理由がわかりました。</p>
<p>しかし、ansibleとかそういうので環境作ろうとしたら、activateしないでそのままパス指定で実行することもあるので、別の方法はないのかと調べてみると、pipがやっていました。
<tt class="docutils literal">pip</tt> はインストール先の環境を判定するのに <tt class="docutils literal">sys.prefix</tt>, <tt class="docutils literal">sys.base_prefix</tt>, <tt class="docutils literal">sys.real_prefix</tt> をつかっています。
こちらの方法ならactivateに関係なく、パス指定の場合でも正確に判定できそうです。</p>
<p>ちなみに <tt class="docutils literal">virtualenv</tt> の場合に発生しないのは、site.pyを仮想環境専用のsite.payを置いているのでパッチの影響を受けないのだろうと思われます。</p>
<p>とはいえ、この問題の発生条件は ubuntu(多分debianも)で、パッケージインストールされたPython3.3のpyvenvで作った環境以下のpythonをactivateせずにパス指定で実行するという以外に見つからなかったので、まあ virtualenv使うなり、VIRTUAL_ENV環境変数が使われるようにplaybookとかそういうのを書くってことで回避できるでしょう。
3.4だとensure-pipでpyvenv内に最初からpip入るようになるし....</p>
<p>多分まったく同じ問題ではまる人はあまりいないと思いますが、類似の問題のヒントにでもなればと思います。
役に立ったと思った人は、「午後の紅茶 おいしい無糖」でも送ってください。</p>
PillowのバイナリをpipでWindowsで扱う話2014-02-12T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-02-12:20140212-pillow-wheel-windows.html<!-- -*- coding:utf-8 -*- -->
<p>まだeasy_installを使っているというかわいそうな人がいるらしいので、
WindowsでもPillowとかのバイナリをpipでインストールできるよという話。
きっとかわいそうな人はPython2.7とかレガシーなの使ってると思うので、
ちゃんと2.7でvirtualenvしたよ!</p>
<pre class="literal-block">
PS C:\Users\aodag_2> virtualenv.exe C:\Users\aodag_2\envs\pillow
New python executable in C:\Users\aodag_2\envs\pillow\Scripts\python.exe
Installing setuptools, pip...done.
PS C:\Users\aodag_2> .\envs\pillow\Scripts\activate.ps1
(pillow) PS C:\Users\aodag_2> pip --version
pip 1.5.1 from C:\Users\aodag_2\envs\pillow\lib\site-packages (python 2.7)
</pre>
<p>はい。まずはvirtualenvのインストールとpipのバージョン確認です。
pipは1.5.1以上のwheelサポートがデフォルトで有効になっているものですね。</p>
<pre class="literal-block">
(pillow) PS C:\Users\aodag_2> pip install pillow
Downloading/unpacking pillow
Storing download in cache at c:\users\aodag_2\pip\downloads\https%3a%2f%2fpypi.python.org%2fpackages%2fcp27%2fp%2fpill
ow%2fpillow-2.3.0-cp27-none-win32.whl
Installing collected packages: pillow
Successfully installed pillow
Cleaning up...
</pre>
<p>はい。なにも特別なことはなく、 <tt class="docutils literal">pip install pillow</tt> です。
pillowはwheelを各種バージョンで作成してくれているので非常に助かります。</p>
<pre class="literal-block">
(pillow) PS C:\Users\aodag_2> python
Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL.Image
>>> PIL.Image
<module 'PIL.Image' from 'C:\Users\aodag_2\envs\pillow\lib\site-packages\PIL\Image.pyc'>
>>> PIL.Image.Image()
<PIL.Image.Image image mode= size=0x0 at 0x21716C0>
</pre>
<p>とりあえずimportしたりインスタンス作ってみたりした。</p>
<div class="section" id="id1">
<h2>まとめ</h2>
<ul class="simple">
<li>pip は1.5以上でwheelをデフォルトサポート(1.4とかだと <tt class="docutils literal"><span class="pre">--use-wheel</span></tt> オプションで有効)</li>
<li>pillowは、wheel, egg, wininst, sdistと可能なフォーマットを2.6,2.7,3.2,3.3,3.4と幅広く作成してくれている。パッケージメンテナに敬意を表したい。(まじで)</li>
</ul>
<p>ということで次の業務後ティータイムでは、ミルクレープ食べたいですね。</p>
</div>
Pyramidのセキュリティ2014-02-10T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-02-10:20140210-pyramid-security.html<!-- -*- coding:utf-8 -*- -->
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#id1" id="id9">Pyramidのセキュリティの仕組み</a><ul>
<li><a class="reference internal" href="#id2" id="id10">認証</a></li>
<li><a class="reference internal" href="#id3" id="id11">認可</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id4" id="id12">アプリケーションでの実際</a><ul>
<li><a class="reference internal" href="#id5" id="id13">セキュリティ設定</a></li>
<li><a class="reference internal" href="#id6" id="id14">ビューとモデル</a></li>
<li><a class="reference internal" href="#id7" id="id15">ログイン処理</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id8" id="id16">まとめ</a></li>
</ul>
</div>
<p>始めに断っておこう。Pyramidにはファンシーなログインフォームやユーザー管理なんてついてこない。
認証、認可の仕組みはあるが、Pyramidに設定一発で動くような押しつけがましいViewやModelは存在しない。
そういうのが好きな人はDjangoというフレームワークがあるから、そっちにしときな。</p>
<p>このエントリでは、Pyramidで認証、認可の仕組みを使う方法を説明する。
CSRFとかそういうのは扱わないのであしからず。</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id9">Pyramidのセキュリティの仕組み</a></h2>
<p>先に述べたとおりPyramidには認証と認可の仕組みがある。
認証というのは、今アプリケーションを使っているのが誰なのかを特定するもので、
認可は誰がその機能や処理を実行してよいかということである。</p>
<div class="section" id="id2">
<h3><a class="toc-backref" href="#id10">認証</a></h3>
<p>Pyramidの認証では、AuthenticationPolicyがリクエストからprincipalを取り出すという方法で実現している。
princpalというのはユーザーがシステムの中でどのように認識されているかということであり、
例えば単純にログインユーザーで判定されることもあるし、特定のIPアドレスやネットワークからアクセスしているといった情報から判定されることもある。</p>
<p>Pyramidで標準で用意されているAuthenticationPolicyでわかりやすいのはBasicAuthAuthenticationPolicyだろう。
このポリシーが設定されていると、リクエストのAuthorizationヘッダの情報からprincipalを生成する。
このほかに、セッションやクッキーなどで認証情報を保持するポリシーが用意されている。</p>
<p>結局のところ用意されているのは、リクエストからprincipalを取り出す方法だけで、実際にそのprincipalを有効なものだと判定するのは、アプリケーション側にゆだねられている。
有効なprincipalと判定した後に、rememberメソッドによってAuthenticationPolicyに渡すと、それ以降のPyramidのセキュリティメカニズムの中で利用されるというわけだ。</p>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id11">認可</a></h3>
<p>さて、利用者の役割などがわかれば、次はその人に何が許されているかという話になる。
Pyramidでは、許されていること(権限)をpermissionという用語で扱っている。
permissionはPyramidの実装上は単なる文字列である。(ただし、ドキュメントで説明されていないが、実際にはタプルなども利用可能である。今のところ。)</p>
<p>実際に認可が必要になるのはビューを実行するときである。
ビューはadd_viewやview_configで登録するときにpermissionも指定できる。
ビューを実行しようとしたときに指定したpermissionを持っていなければHttpForbiddenExceptionが発生して、例外ビューの処理に飛ばされてしまう。</p>
<p>このpermissionはprincipalに直接結びついているわけではない。
permissionとprincipalを結びつけるのがAuthorizationPolicyの役割である。</p>
<p>ここで、Pyramid標準で唯一用意されているACLAuthorizationPolicyの動きを見てみよう。</p>
<p>まず、なんらかの認証により、"aodag", "authenticated", "group:staff" という3つのprincipalを得ているとしよう。
ここで、あるURLにアクセスするが、ACLAuthorizationPolicyは、コンテキストの__acl__プロパティを使って、permissionを取得する。</p>
<pre class="literal-block">
from pyramid.security import Allow
class SecurityContext(object):
__acl__ = [
(Allow, "authenticated", "view"),
(Allow, "group:staff", "edit"),
]
def __init__(self, request):
self.request = request
</pre>
<p>上記のコンテキストによって、 "authenticated" から "view"パーミッション、 "group:staff"から"edit"パーミッションを得られる。
ではこの状態で以下のようなビューがあるとしよう。</p>
<pre class="literal-block">
@view_config(context=SecurityContext, permission="view")
def protected_view(request):
return dict()
@view_config(context=SecurityContext, permission="edit")
def edit_view(request):
return dict()
@view_config(context=SecurityContext, permission="add_user")
def add_user_view(request):
return dict()
</pre>
<p>二種類のビューそれぞれに異なるパーミッションが設定されている。
今アクセスしているユーザーは "view","edit"の2つのパーミッションを持っているため、
protected_view, edit_viewにアクセスできる。
しかし、"add_user" パーミッションは持っていないため、add_user_viewへのアクセスはできない。</p>
<p>では、パーミッションが要求されたときの流れをまとめてみよう。</p>
<ol class="arabic simple">
<li>AuthenticationPolicyがリクエストからprincipalを取り出す</li>
<li>AuthorizationPolicyがコンテキストからprincipalにマッチするpermissionを取り出す</li>
<li>上記で取り出されたpermission中にviewで指定されたpermissionが含まれているか判定する</li>
</ol>
<p>Pyramidが用意しているのはこの流れだけであり、AuthenticationPolicyやAuthorizationPolicyは標準で用意されているものや、自分でカスタマイズしたものが使えるようになっている。</p>
</div>
</div>
<div class="section" id="id4">
<h2><a class="toc-backref" href="#id12">アプリケーションでの実際</a></h2>
<p>さて、Pyramidが提供する認証、認可の仕組みを理解したところで、実際のアプリケーションでどのように利用するか考えてみよう。</p>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id13">セキュリティ設定</a></h3>
<p>認証、認可ともに、Configuratorでの設定によって有効になる。</p>
<pre class="literal-block">
config.set_authetication_policy(AuthTktAuthenticationPolicy(secret="fieaoji3w-bb#"))
config.set_authorization_policy(ACLAuthorizationPolicy())
</pre>
<p>AuthTktは、クッキーに認証トークンを持たせる方法の認証ポリシーである。
Webアプリケーションサーバーを分散している場合でも、セッションレプリケーションなどを必要とせずに認証情報を扱える。
ACLAuthorizationPolicyはすでに説明したとおり、標準ではこれしか提供されていない。</p>
</div>
<div class="section" id="id6">
<h3><a class="toc-backref" href="#id14">ビューとモデル</a></h3>
<p>例として、ありがちなWikiアプリケーションに以下のようなセキュリティモデルを設定してみよう。</p>
<ul class="simple">
<li>WikiPageは <em>誰でも</em> 読むこと(view)ができる</li>
<li>WikiPageは <em>認証済のユーザー</em> だけ作成(create)できる</li>
<li>WikiPageは <em>作成者</em> によってロック(lock) することができる</li>
<li>WikiPageは <em>作成者</em> によってロック解除(unlock) することができる</li>
<li>ロックされたWikiPageは <em>作成者</em> のみが編集(edit)できる</li>
<li>ロックされていないWikiPageは <em>認証済のユーザー</em> が編集(edit)できる</li>
</ul>
<p>これらを <tt class="docutils literal">__acl__</tt> に定義したモデル、WikiPage:</p>
<pre class="literal-block">
class WikiPage(Persistent):
def __init__(self, pagename, data, owner):
self.__name__ = pagename
self.pagename = pagename
self.data = data
self.owner = onwer
self.locked = False
@property
def __acl__(self):
if self.locked:
return [(Allow, Everyone, "view"),
(Allow, self.owner, "unlock"),
(Allow, self.owner, "edit")]
else:
return [(Allow, Everyone, "view"),
(Allow, self.owner, "lock"),
(Allow, Authenticated, "edit")]
def lock(self):
self.locked = True
def unlock(self):
self.locked = False
class Wiki(PersistentMapping):
@property
def __acl__(self):
return [(Allow, Authenticated, "create")
]
</pre>
<p>上記のようにコンテキストによって、権限が与えられる。
では、これらの権限を要求するビューの定義をしていこう。</p>
<pre class="literal-block">
@view_config(context=WikiPage, permission="view", renderer="wikipage.pt")
def wikipage_view(context, request):
return dict(wikipage=context)
@view_config(context=WikiPage, name="lock", permission="lock", request_method="POST")
def wikipage(context, request):
context.lock()
return HTTPFound(location=request.resource_url(context))
@view_config(context=WikiPage, name="unlock", permission="unlock", request_method="POST")
def wikipage(context, request):
context.unlock()
return HTTPFound(location=request.resource_url(context))
@view_config(context=WikiPage, name="edit", permission="edit", renderer="wikipage_edit.pt")
def wikipage_edit(context, request):
form = Form(context)
if request.method == "POST":
if form.validate(request.POST):
context.data = request.POST['data']
return HTTPFound(location=request.resource_url(context)
return dict(wikipage=context, form=form)
@view_config(context=Wiki, name="create", permission="create", renderer="wikipage_create.pt")
def wikipage_create(context, request):
form = Form()
pagename = request.matchdict['subpath']
if request.method == "POST":
if form.validate(request.POST):
page = WikiPage(data=request.POST['data'], pagename=pagename, owner=authenticated_userid(request))
page.__parent__ = context
context[page.__name__] = page
return HTTPFound(location=request.resource_url(page)
return dict(wiki=context, form=form)
</pre>
<p>このように、ビュー内では権限などを気にすることなく本来の機能的な処理だけを記述できる。
ログイン中のユーザーは authenticated_userid で取得できる。</p>
</div>
<div class="section" id="id7">
<h3><a class="toc-backref" href="#id15">ログイン処理</a></h3>
<p>さて、ここまで話を避けていた感のあるログイン処理である。
認証というとログイン処理のことであろうと思われるかもしれないが、前述のとおりログイン処理はpyramidで提供していないため、アプリケーションで用意しなければならない。</p>
<p>まずは、ユーザーデータである。</p>
<pre class="literal-block">
import hashlib
class User(Persistent):
def __init__(self, username, password):
self.username = username
self.password = password
def set_password(self, password):
self._password = hashlib.sha1(password.encode('utf-8')).hexdigest()
password = property(fset=set_password)
def verify_password(self, password):
return self._password == hashlib.sha1(password.encode('utf-8')).hexdigest()
</pre>
<p>パスワードをハッシュ化して保存する処理があるため分かりにくいが、やれることは usernameに対するpasswordが正しいかどうかだけである。
このUserモデルを使ったlogin APIを作る:</p>
<pre class="literal-block">
def get_user(request, username):
conn = get_connection(request):
root = conn.root()
users = root['users']
return users.get(username)
def login(request, username, password):
user = get_user(request)
if user is None:
return None
if not user.verify_password(password):
return None
return user
</pre>
<p>ログインフォームではこのAPIを利用する:</p>
<pre class="literal-block">
def login_form(request):
if request.POST:
user = login(request, request.POST.get('username'), request.POST.get('password'))
if user is not None:
headers = remember(request, user.username)
return HTTPFound(request.resource_url(request.root), headers=headers)
return dict()
</pre>
<p>rememberは、クッキーやAuthorizationヘッダなど、レスポンスに必要な情報を返してくる。
(認証の種類によっては、ヘッダ情報を使わずにセッションなどに追加する場合もあるが、その場合は空のリストが返ってくる。)
ログインに成功した場合はm、rememberの結果をヘッダに追加したレスポンスを返すようにする。</p>
<p>ログアウトは単純にforgetを呼び、戻り値をレスポンスヘッダに追加する:</p>
<pre class="literal-block">
def logout(request):
headers = forget(request)
return HTTPFound(request.resource_url(request.root), headers=headers)
</pre>
</div>
</div>
<div class="section" id="id8">
<h2><a class="toc-backref" href="#id16">まとめ</a></h2>
<ul class="simple">
<li>Pyramidは認証、認可の仕組みを持っているが、具体的に即使えるような出来合いのものではない</li>
<li>認証はリクエストにどのように認証情報が入ってるのかだけに対応している</li>
<li>認可は認証されたprincipalに対して、現在のコンテキストにどのような権限を与えるかというもの</li>
</ul>
<p>ヽ(´_・ω・)_この内容は、結構しんどかった。ちなみに前回のエントリでシュークリームを2つほどせしめたので、次はミルクティーとかいいなって思った(小並</p>
</div>
Pyramidのコントローラースタイル2014-02-05T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-02-05:20140205-pyramid-controller-style.html<!-- -*- coding:utf-8 -*- -->
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#url" id="id11">URLディスパッチ</a><ul>
<li><a class="reference internal" href="#pyramidurl" id="id12">PyramidでのURLディスパッチ</a></li>
<li><a class="reference internal" href="#view-predicate" id="id13">View Predicate</a></li>
<li><a class="reference internal" href="#id1" id="id14">ルートURL</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id2" id="id15">トラバーサル</a><ul>
<li><a class="reference internal" href="#id3" id="id16">オブジェクトのデフォルトビュー</a></li>
<li><a class="reference internal" href="#id4" id="id17">ビュー名</a></li>
<li><a class="reference internal" href="#id5" id="id18">リソースURL</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id6" id="id19">URLディスパッチとトラバーサルの比較</a></li>
<li><a class="reference internal" href="#id7" id="id20">URLディスパッチとトラバーサルの混合</a><ul>
<li><a class="reference internal" href="#id8" id="id21">軽いURLディスパッチ + がっつりトラバーサル</a></li>
<li><a class="reference internal" href="#id9" id="id22">ガッツリURLディスパッチ + 軽くトラバーサル</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id10" id="id23">まとめ</a></li>
</ul>
</div>
<p>とりあえずコントローラースタイルと書いたが、ようするにWebアプリケーションがリクエストを受け取ってから処理に入るまでの流れである。
Pyramidはあえて複数の方法を採用している。その他のフレームワークから来る人たちがお気に入りの方法をとれるようにするためだ。
大きく分けて、Zope系由来のトラバーサル、DjangoやPylonsが使っているURLディスパッチがある。
(TurboGearsはPylons上のフレームワークだけどURLディスパッチをオミットしてトラバーサルっぽい動きをする)
Pyramidはこの2種類をそれぞれ使えるし、同時に利用することもできる。
とりあえず2種類を説明して、そのあとに混合したパターンを説明しよう。</p>
<div class="section" id="url">
<h2><a class="toc-backref" href="#id11">URLディスパッチ</a></h2>
<p>最近のWebフレームワークはこちらが主流になっているようだ。
URLを正規表現やそれに近いパターンで解析して、パターンごとのコントローラーやビューを呼び出す。
リクエストからレスポンスまでの処理のは以下のようになる。</p>
<ul class="simple">
<li>URLパターンマッチング</li>
<li>パターンに対応するビューの呼び出し</li>
<li>ビューの中でモデルをローディング</li>
<li>ビューがテンプレートにモデルを渡す</li>
</ul>
<div class="section" id="pyramidurl">
<h3><a class="toc-backref" href="#id12">PyramidでのURLディスパッチ</a></h3>
<p>Pyramidはルーティングとビューの登録がわかれている。</p>
<p>ルーティング:</p>
<pre class="literal-block">
config.add_route("user", "/users/{user_id}")
</pre>
<p>"user"ルートのURLパターンの登録である。
"{}" で囲まれている部分はプレースホルダーとなっていて、この部分に相当するパラメータは request.matchdict から取得できる。</p>
<p>ビュー登録:</p>
<pre class="literal-block">
@view_config(route_name="user", renderer="user.mako")
def user_view(request):
user = User.query.filter(User.id==request.matchdict["user_id"]).one()
return dict(user=user)
</pre>
<p>ビューを登録する方法はいくつかあるが、一番簡単であろうデコレータを使った方法である。
デコレータを複数使えば同じビューを異なるルートに登録できる。
また、そのときにテンプレートを別々に指定できる。</p>
</div>
<div class="section" id="view-predicate">
<h3><a class="toc-backref" href="#id13">View Predicate</a></h3>
<p>なぜルーティングとビュー登録がわかれているのか?
Pyramidではビューを決定する情報はルートだけではないのである。
例えば、リクエストメソッドやXHR、特定のヘッダの有無などもビューを決定するための条件となる。
(もっとも重要な条件はトラバーサルと一緒に説明しよう)</p>
<p>ルーティングによる条件はさきほどの例にあるように route_name で指定される。
route_name="user" というのは "user"という名前のルーティングにマッチしたリクエストを条件とするという意味だ。
request_method="POST" とすれば、POSTメソッドの場合だけそのビューが呼ばれることとなるし、xhr=Trueとすればajaxリクエスト(XMLHttpRequest)の場合に呼び出されるという意味だ。</p>
<p>特に xhr プリディケイトは、ビューの切り替えだけでなくrendererの変更でも使われる:</p>
<pre class="literal-block">
@view_config(route_name="user", renderer="user.mako")
@view_config(route_name="user", renderer="json", xhr=True)
def user_view(request):
user = User.query.filter(User.id==request.matchdict["user_id"]).one()
return dict(user={"name": user.name, "type": user.user_type.name})
</pre>
<p>複数のview_configを使っているが上は通常のブラウザからのリクエストで、下はXHRによるリクエストだ。
同じルートに対して同じビューが呼び出されるが、それぞれのリクエストによって最終的なレスポンスボディが変化する。
通常のリクエストではuser.makoというテンプレートが指定されている。これはMakoTemplateエンジンで処理されて、HTMLとしてボディを返すだろう。
XHRの場合はrendererがjsonなので、ビューの戻り値のdictがそのままjson.dumpsされて返される。
処理自体は変わらないがリクエストの種類などに応じてレスポンスのフォーマットを変える良い例だ。</p>
</div>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id14">ルートURL</a></h3>
<p>他のフレームワークではreverse_urlとも呼ばれる機能だが、パターンに値を埋め込んでURLを生成する仕組みである。
Pyramidでは、request.route_urlメソッドを使う。</p>
</div>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id15">トラバーサル</a></h2>
<p>ではコントローラーのもう一方の方法であるトラバーサルである。
トラバーサルの場合はルートオブジェクトが最初に存在する。
このルートから、URLにそってオブジェクトツリーをたどっていくのがトラバーサルだ。
ツリーの末端に行くか、URLを消費しきったときに、このURLに対応したオブジェクトが決定される。
このオブジェクトをPyramidではcontextと呼んでいる。</p>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id16">オブジェクトのデフォルトビュー</a></h3>
<p>さきほどの例と同じようなURLを考えてみよう:</p>
<pre class="literal-block">
/users/1
</pre>
<p>このようなURLが与えられた場合に、rootオブジェクトから以下のようにトラバーサルされる:</p>
<pre class="literal-block">
root['users']['1']
</pre>
<p>Zopeではもっと複雑な経路設定ができるが、Pyramidでは __getitem__ で辿っていくようになっている。
結果として、おそらく Userクラスのインスタンスなどが取得されるはずである。
このリクエストでのcontextが、このUserオブジェクトとなる。</p>
<p>次にPyramidは、Userオブジェクトに対応するビューを探し出す。
URLディスパッチでも説明したとおり、PyramidのビューはURLパターンだけに対応するものではない。
トラバーサルで重要なプリディケイトは、contextとname(route_nameではないことに注意)である。
contextは説明した通りUserオブジェクトである。ひとまずnameのことは忘れて、contextの条件だけを考えよう。</p>
<pre class="literal-block">
@view_config(context=User, renderer="user.pt")
def user_view(context, request):
return dict(user=context)
</pre>
<p>この場合はview_configでcontextのクラスを指定する。
URLディスパッチではビューの中でモデルをローディングしたが、トラバーサルでは先にモデルが決定されてから、ビューが選択される。
そのためモデルはcontextとして、ビューの引数で渡されてくる。
モデルのメソッドを呼ばないのであれば、ビューは単にcontextとrendererを結びつけるだけの役割になる。</p>
</div>
<div class="section" id="id4">
<h3><a class="toc-backref" href="#id17">ビュー名</a></h3>
<p>さて、トラバーサルではURLでコンテキストを決定してそれに合わせたビューが呼び出されるが、このままではコンテキストのクラスごとに1つのビューしか使えない。
トラバーサルでは、コンテキストが決定されたあとに残ったURLをビュー名として条件に使えるようになっている。
例えば、 "/users/1/edit" といったURLがあった場合、 "/users/1" に対応したuserオブジェクトが "edit" という名前で __getitem__ できなければ(__getitem__メソッドがなかったり、KeyErrorが発生したりなど) contextがuserオブジェクトとなり、"edit"がビュー名となる。</p>
<p>結果として、以下のような name="edit" という条件がついたビューが呼び出される:</p>
<pre class="literal-block">
@view_config(context=User, name="edit", renderer="user_edit.pt")
def user_edit_view(context, request):
form = Form(context)
if request.POST and form.validate(request.POST):
.... do something
return dict(user=context, form=form)
</pre>
<p>また、デフォルトではビュー名は "" 空文字として扱われている。</p>
</div>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id18">リソースURL</a></h3>
<p>トラバーサルでたどるオブジェクトツリーのそれぞれのオブジェクトをリソースと呼ぶ。
contextは、リクエストURLによって一意に決定された特別なリソースである。
リソースはトラバーサル中にURLによって辿られるが、逆にリソースからURLの生成も可能である。
リソースからURLを生成するには、親の方向に向かってルートまでオブジェクトをたどり、オブジェクトの名前を "/" で連結していく。
Userオブジェクトから辿っていく場合を考えてみよう。</p>
<p>user,users,root の順に辿っていくが、この道筋はそれぞれのリソースの __parent__ 属性によってきめられている。
rootは __parent__ が Noneになっており、そこがトップレベルだという印となる。
また、オブジェクトごとの名前は __name__属性で取得される。
rootは無名、usersは"users"、userは"1"と名前を持っている場合、生成されるURLは "/users/1" となる。</p>
</div>
</div>
<div class="section" id="id6">
<h2><a class="toc-backref" href="#id19">URLディスパッチとトラバーサルの比較</a></h2>
<p>トラバーサルの場合、URLに対応したオブジェクトが自然に決まる仕組みとなっていて、さらにモデルが自分のURLを知っているようになっている。
ただし、 __parent__や__name__の指定はプログラマが行わなければならない。
ZODBのようなオブジェクトツリーを意識したデータベースであれば自然な方法だが、
RDBMSのようにオブジェクトがテーブルにフラットに保存されている場合は、URLディスパッチを使う方が自然と思われる。</p>
</div>
<div class="section" id="id7">
<h2><a class="toc-backref" href="#id20">URLディスパッチとトラバーサルの混合</a></h2>
<p>PyramidではURLディスパッチしてから、さらにトラバーサル処理を実行する手段が提供されている。
非常に高度な利用法となるが、それぞれの役割を制限して2つ同時に乗りこなしてみよう。</p>
<div class="section" id="id8">
<h3><a class="toc-backref" href="#id21">軽いURLディスパッチ + がっつりトラバーサル</a></h3>
<p>トラバーサルを主体にした使い方である。
通常の "/" からルートオブジェクト以下にトラバーサルするものに付け加えて、 "/admin" など特定用途のプリフィックスからトラバーサルするものを併用する。</p>
<pre class="literal-block">
class User(object):
def __init__(self, parent, id):
self.id = id
self.__name__ = str(id)
self.__parent__ = parent
class UserRepository(dict):
def __init__(self, parent):
self.__parent__ = parent
self.__name__ = 'users'
class Root(dict):
__parent__ = __name__ = None
root = Root()
users = UserRepository(root)
root[users.__name__] = users
user = User(users, 1)
users[user.__name__] = user
def root_factory(request):
return root
@view_config(context=User, renderer="user.pt")
def user_view(context, request):
return dict(user=context)
@view_config(context=User, renderer="admin/user.pt", route_name="admin")
def admin_user_view(context, request):
return dict(user=context)
@view_config(context=User, name="edit", renderer="admin/edit_user.pt", route_name="admin")
def admin_user_edit(context, request):
return dict(user=context)
config = Configurator(root_factory=root_factory)
config.add_route("admin", "/admin/*traversal")
config.scan(".")
</pre>
<p>この場合、 "/users/1" であれば、userがcontextとなり、すでに説明したように context=Userを条件としている user_view が呼び出される。
"/admin/users/1" の場合は "admin" ルートからトラバーサルを実行するため、context=Userかつroute_name="admin"を条件としている admin_user_view が呼び出される。
admin_user_editは、 "/users/1/edit"では呼び出されず、 "/admin/users/1/edit" の場合だけ使えるようになる。
基本はトラバーサルを使いつつ、性格の異なるサイトを同じオブジェクトツリーで構成する場合に使える手段である。</p>
</div>
<div class="section" id="id9">
<h3><a class="toc-backref" href="#id22">ガッツリURLディスパッチ + 軽くトラバーサル</a></h3>
<p>次はURLディスパッチを主体とした方法である。
URLディスパッチで "/users/{user_id}" までを特定して、その後にビュー名を得る部分だけトラバーサルを利用する。
この場合はルートにfactoryで、トラバーサルの主体を渡さなければならない。</p>
<pre class="literal-block">
class User(object):
def __init__(self, id):
self.id = id
class UserRepository(dict):
pass
users = UserRepository(root)
user = User("1")
users[user.id] = user
def user_factory(request):
return users.get(request.matchdict['user_id'])
@view_config(route_name="user", renderer="user.pt")
def user_detai_view(context, request):
return dict(user=context)
@view_config(route_name="user", name="detail", renderer="user_detail.pt")
def user_view(context, request):
return dict(user=context)
config = Configurator(root_factory=root_factory)
config.add_route("user", "/users/{user_id}/*traversal", factory=user_factory)
config.scan(".")
</pre>
<p>"/users/1" は、"user" ルートにマッチして、ビュー名がないため、user_view が呼び出される。
"/users/1/detail" の場合、 "user"ルートにマッチして、さらに "detai" というビュー名を持つため user_detail_viewが呼び出される。</p>
<p>ある特定URLとそれ以下のパス名で扱われるオブジェクトが同じ場合は、このようにビュー名だけでビューを切り替えることが可能である。
末端のパスが1か所だけ違うルートが大量に定義されている場合は利用を検討してみてはどうだろう。</p>
</div>
</div>
<div class="section" id="id10">
<h2><a class="toc-backref" href="#id23">まとめ</a></h2>
<p>ということで、Pyramidが持つ柔軟性とハードルの高さの責任の有力候補である二種類のコントローラースタイルとその混合方法について自分なりに整理してみました。
ここまで必要かどうかって思うかもしれませんが、ぜひ試してみて、これまでいまいちうまく書けないという部分が多少でも解決できれば、きっと僕のところにシュークリームが舞い込んでくることと思います。</p>
</div>
pipのsetuptools離れ2014-01-27T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-01-27:20140127-pip-only.html<p>年明けに pip 1.5 がリリースされ、1.4でのpreリリースの扱いに加えて、外部サイトにおいてあるライブラリがインストールできなくなった(allow-externalオプションが必要)り、httpsじゃないサイトからのダウンロードができなくなった(allow-unverified )り、阿鼻叫喚のようです。</p>
<p>そのあたりはエラーメッセージの通りにしてよね!ってことで、そことは別の話です。</p>
<p>pip1.5以降はpkg_resourcesというsetuptoolsに入ってたモジュールをpip内に同梱するようになりました。
1.4のときにdistlibも同梱されるようになっているため、pythonのパッケージングに関するユーティリティライブラリを新旧ともにpipは同梱するようになったわけです。</p>
<p>さらに get-pip はsetuptoolsがなければそれもインストールするようになりました。
って話で終わると、別にsetuptools離れしてないじゃんということになりますが。
get-pipの動作を見ていると、pipをインストールしてから、setuptoolsをインストールしています。
ここで、1.5以降のChangeLogを見てみると、pipはsdistをインストールするとき以外はsetuptoolsを必要としなくなったと書いてあります。
get-pipはpipとsetuptoolsをwheel形式を使ってインストールするので、pipがsetuptoolsをインストールするということが可能になったわけです。
で、pyvenv/ensure-pipやvirtualenvなどは環境作成時にpipもsetuptoolsもインストールするので、setuptoolsなしでpipのみという環境はそうそう存在しないのです。
とはいえ、できるようになったというのなら確認してやろうじゃありませんか。</p>
<p>今のところの最新安定版のpython3.3に入っているpyvenvはensure-pipがないので、まっさらな環境を作成できます。この環境にpipのみの単独インストールをしてみます。</p>
<p>さて、pipをインストールするといってもpipのsetup.pyはsetuptoolsを使っているので、wheelからのインストールを検討しなければなりません。
が、現状でwheelをインストールできるツールはpipだけ。だからこそget-pipがあるわけですが、今回はそれも使わないことにします。</p>
<p>pipがなければdistlibを使えばいいじゃない!
pipが内部で利用しているパッケージング関連のライブラリであるdistlibを <a class="reference external" href="https://pypi.python.org/pypi/distlib">pypi の distlib</a> からダウンロードしておきます。
また、インストール対象のpipのwheelディストリビューションも <a class="reference external" href="https://pypi.python.org/pypi/pip">pypi の pip</a> からダウンロードしておきます。</p>
<p>この時点で以下のような状態です:</p>
<pre class="literal-block">
$ ls
bin distlib-0.1.7.zip include lib pip-1.5.2-py2.py3-none-any.whl pyvenv.cfg
</pre>
<p>もちろんsetuptoolsは入っていません:</p>
<pre class="literal-block">
$ bin/python -m setuptoools
/home/aodag/works/pip-only/bin/python: No module named setuptoools
</pre>
<p>distlibを解凍して、環境変数PYTHONPATHにdistlib以下のパスを追加します:</p>
<pre class="literal-block">
$ unzip distlib-0.1.7.zip
$ export PYTHONPATH=$PWD/distlib-0.1.7
</pre>
<p>では、distlibを使ってpipをインストールします。</p>
<p>distlib.wheel.Wheelクラスを使って、pipのwheelファイルを読み込みます:</p>
<pre class="literal-block">
>>> from distlib.wheel import Wheel
>>> whl = Wheel('pip-1.5.2-py2.py3-none-any.whl')
>>> whl
<distlib.wheel.Wheel object at 0x7f759e2d3110>
>>> whl.info
{'Wheel-Version': '1.0', 'Generator': 'bdist_wheel (0.22.0)', 'Tag': 'py2-none-any', 'Root-Is-Purelib': 'true'}
>>> whl.metadata.todict()
{'license': 'MIT', 'keywords': 'easy_install distutils setuptools egg virtualenv', 'classifiers': ['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Build Tools', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3'], 'summary': 'A tool for installing and managing Python packages.', 'version': '1.5.2', 'name': 'pip'}
</pre>
<p>インストール先のパスを用意します:</p>
<pre class="literal-block">
>>> import site
>>> site_packages = site.getsitepackages()[0]
>>> import sys
>>> import os
>>> paths = dict(prefix="", purelib=site_packages, platlib=site_packages, data="", scripts=os.path.join(sys.prefix, "bin"), headers="")
</pre>
<p>スクリプトインストールのためにScriptMakerも作成して、installメソッドを呼びます:</p>
<pre class="literal-block">
>>> from distlib.scripts import ScriptMaker
>>> maker = ScriptMaker(None, None)
>>> whl.install(paths, maker)
<InstalledDistribution 'pip' 1.5.2 at '/home/aodag/works/pip-only/lib/python3.3/site-packages/pip-1.5.2.dist-info'>
</pre>
<p>これでpipのインストール完了です。</p>
<pre class="literal-block">
$ pip --version
pip 1.5.2 from /home/aodag/works/pip-only/lib/python3.3/site-packages (python 3.3)
</pre>
<p>では、この状態でwheel形式で配布されているものをインストールしてみます。
依存ライブラリがsdistだと、そこでsetuptoolsが必要になってしまうので、それほど多くのものはインストールできません。
とりあえず2つ試しました。</p>
<pre class="literal-block">
$ pip install webdispatch
Downloading/unpacking webdispatch
Downloading WebDispatch-1.0.1-py2.py3-none-any.whl
Installing collected packages: webdispatch
Successfully installed webdispatch
Cleaning up...
$ pip install pastedeploy
Downloading/unpacking pastedeploy
Downloading PasteDeploy-1.5.2-py2.py3-none-any.whl
Installing collected packages: pastedeploy
Successfully installed pastedeploy
Cleaning up...
</pre>
<p>ちゃんとsetuptoolsなしで実行できています。
とはいえ、例えばmakoやsphinxなどは、それ自体はwheelで配布されていますが、依存ライブラリがsdistなので、pipだけでのインストールはできません。
今後wheel形式の配布が広まらなければ、setuptoolsは不滅なのでしょう。</p>
新年的なアレ2014-01-01T00:00:00+01:00aodagtag:pelican.aodag.jp,2014-01-01:20140101-newyear.html<p>さてでは <a class="reference external" href="http://aodag.posthaven.com/173367290">昨年のアレ</a> をチェック</p>
<ul class="simple">
<li>coffescript,dart,typescriptのどれか1つを勉強する
-> (´・ω・`)やってない..</li>
<li>自炊がんばる
-> (´・ω・`)がんばれてない</li>
<li>ポモドーロとかGTDとか、なんかそんなので改善なやつをする
-> emacs org-modeでのキャプチャを始めたがタイムマネージメントとかタスクマネージメントまでは習慣化してない(´・ω・`)</li>
<li>(´・ω・`)ブログは30エントリ目指す
-> ブログは15エントリですた(´・ω・`)半分か</li>
</ul>
<p>(´・ω・`)惨敗でした。</p>
<p>今年は:</p>
<ul class="simple">
<li>引き続き自炊がんばるようにする
なんか仕組み考えないとやらないだろうな...</li>
<li>キャプチャリングからの週次レビューみたいなのをやる
カレンダーに入れるだけでできるだろうか</li>
<li>開発の効率化とかそういうのを実行していく</li>
<li>jsとかビッグデータとかまあその辺の技術はフォローしていく</li>
<li>Goも話についてけるくらいには手を出す</li>
<li>技術書以外で10冊くらい、これはという本を見つける</li>
</ul>
<p>ということで。</p>
New Year's Python Meme 20142013-12-31T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-12-31:new-years-python-meme-2014.html<p><a class="reference external" href="http://aodag.posthaven.com/new-years-python-meme-2012-2012pythonmeme">Last Year</a></p>
<div class="section" id="whats-the-coolest-python-application-framework-or-library-you-discovered-this-year">
<h2>What’s the coolest Python application, framework or library you discovered this year?</h2>
<ul class="simple">
<li>ansible</li>
<li>testfixtures</li>
<li>Kaa</li>
</ul>
</div>
<div class="section" id="what-new-programming-technique-did-you-learn-this-year">
<h2>What new programming technique did you learn this year?</h2>
<ul class="simple">
<li>Using adapter to split feature and domain</li>
<li>Repository Pattern</li>
</ul>
</div>
<div class="section" id="which-open-source-project-did-you-contribute-to-the-most-this-year-what-did-you-do">
<h2>Which open source project did you contribute to the most this year? What did you do?</h2>
<ul class="simple">
<li>rebecca, the component libraries for pyramid, is my project.</li>
</ul>
</div>
<div class="section" id="which-python-blogs-websites-or-mailing-lists-did-you-read-the-most-this-year">
<h2>Which Python blogs, websites, or mailing lists did you read the most this year?</h2>
<ul class="simple">
<li>Distutils SIG</li>
<li>Pypa ML</li>
</ul>
</div>
<div class="section" id="what-are-the-top-three-things-you-want-to-learn-next-year">
<h2>What are the top three things you want to learn next year?</h2>
<ul class="simple">
<li>Mobile Web & HTML5</li>
<li>Go</li>
<li>asyncio</li>
</ul>
</div>
<div class="section" id="what-is-the-top-software-application-or-library-you-wish-someone-would-write-next-year">
<h2>What is the top software, application or library you wish someone would write next year?</h2>
<ul class="simple">
<li>DB API for asyncio</li>
</ul>
</div>
効果的なunittest - または、callFUTの秘密2013-12-16T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-12-16:xiao-guo-de-naunittest-mataha-callfutnomi-mi.html<!-- -*- coding:utf-8 -*- -->
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#unittest" id="id10"><cite>unittest</cite> を効果的に使うための覚書</a><ul>
<li><a class="reference internal" href="#id1" id="id11">目的</a></li>
<li><a class="reference internal" href="#module-under-test-import" id="id12">ルール: テスト対象のモジュール(module-under-test)をテストモジュールに直接importしない</a></li>
<li><a class="reference internal" href="#id2" id="id13">ガイドライン: モジュールスコープでの依存を最小限にする</a></li>
<li><a class="reference internal" href="#id3" id="id14">ルール: 各テストメソッドでは、1つの事実だけを確認する</a></li>
<li><a class="reference internal" href="#id4" id="id15">ルール: テストメソッドは内容を表すようにしよう</a></li>
<li><a class="reference internal" href="#setupself" id="id16">ガイドライン: setupはヘルパーメソッドで提供しよう。テストケースのselfで共有するのはやめよう。</a></li>
<li><a class="reference internal" href="#id5" id="id17">ガイドライン: フィクスチャは可能な限り簡潔に</a></li>
<li><a class="reference internal" href="#id6" id="id18">ガイドライン: フックやレジストリなどの利用は注意深く</a></li>
<li><a class="reference internal" href="#id7" id="id19">ガイドライン: 依存関係を明確にするためにモックを利用する</a></li>
<li><a class="reference internal" href="#id8" id="id20">ルール: テストモジュール間でテキストを共有しない</a></li>
<li><a class="reference internal" href="#id9" id="id21">まとめ</a></li>
</ul>
</li>
</ul>
</div>
<p><a class="reference external" href="https://twitter.com/tokibito/status/412074246026698753">https://twitter.com/tokibito/status/412074246026698753</a></p>
<p>ということで <cite>_callFUT</cite> とはなんぞって話。
簡単に言えば、 Pylons Project の <a class="reference external" href="http://docs.pylonsproject.org/en/latest/community/testing.html">Unit Testing Guidelines</a> で使われてる用語なんだけど、 FUT = Function Under the Test の略。
callFUT というのは、テスト対象の関数を呼ぶってことだけど、テスト対象を明示するために、Pylons Project では、こういったラッパーのメソッドを作っている。</p>
<p>この規約は元を正せば tres seaver という、ZopeやらCMF, Plone, Repozeと活動している人の書いた <a class="reference external" href="http://palladion.com/home/tseaver/obzervationz/2008/unit_testing_notes-20080724">Avoiding Temptation: Notes on using 'unittest' effectively</a> がもとになっている。</p>
<p>原文書は2008年のものだけど、この内容は今でも間違いじゃないと思う。
また、ユニットテスト自体はやってるけども、より意味のあるテストを書きたいと思うのなら、ぜひ読んでみてほしい。
以下は僕の拙い翻訳だ。</p>
<div class="section" id="unittest">
<h2><a class="toc-backref" href="#id10"><cite>unittest</cite> を効果的に使うための覚書</a></h2>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id11">目的</a></h3>
<p>ここで示すテストガイドラインの目的は、簡潔で、結合度が低く、速いテストを書くことだ</p>
<ul class="simple">
<li>テストは可能な限り簡潔にする。そしてテスト対象のアプリケーション(Application Under Test = AUT)を完全にテストする。</li>
<li>テストは可能な限り速くする。何度も実行できるようにする。</li>
<li>テストは他のテストやアプリケーションの中でテスト対象と関係ないものに依存しないようにする。</li>
</ul>
<p>開発者はこのようなテストでアプリケーションが期待通りに動作することを確かめる。
一方で、アプリケーションの動作を表現するテストケースによって、内部動作を説明するドキュメントは必要なくなる。
エンドユーザー向けのドキュメントは必要だが。</p>
</div>
<div class="section" id="module-under-test-import">
<h3><a class="toc-backref" href="#id12">ルール: テスト対象のモジュール(module-under-test)をテストモジュールに直接importしない</a></h3>
<p>テスト対象モジュール(module-under-test MUT)のimportが失敗するのは、各テストケースにおける失敗とするべきである。
テスト実行を阻むのは避けるようにしよう。
テストランナーにとって、通常のテストの失敗とimportエラーを区別するのは難しいことだ。</p>
<p>例えば以下の場合よりも:</p>
<pre class="literal-block">
# test the foo module
import unittest
from package.foo import FooClass
class FooClassTests(unittest.TestCase):
def test_bar(self):
foo = FooClass('Bar')
self.assertEqual(foo.bar(), 'Bar')
</pre>
<p>こちらのほうがよい:</p>
<pre class="literal-block">
# test the foo module
import unittest
class FooClassTests(unittest.TestCase):
def _getTargetClass(self):
from package.foo import FooClass
return FooClass
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_bar(self):
foo = self._makeOne('Bar')
self.assertEqual(foo.bar(), 'Bar')
</pre>
</div>
<div class="section" id="id2">
<h3><a class="toc-backref" href="#id13">ガイドライン: モジュールスコープでの依存を最小限にする</a></h3>
<p>ユニットテストは、多少環境が完全でなくても実行できなければならない。
この場合は、いくつかのテストケースが失敗になるだろう。
ライブラリモジュールのimportを可能な限り引き延ばすようにしよう。</p>
<p>例えば以下の例だと "qux" ライブラリモジュールがimportできない場合、テスト結果が全くなくなってしまう:</p>
<pre class="literal-block">
# test the foo module
import unittest
import qux
class FooClassTests(unittest.TestCase):
def _getTargetClass(self):
from package.foo import FooClass
return FooClass
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_bar(self):
foo = self._makeOne(qux.Qux('Bar'))
</pre>
<p>しかし、以下のようにすれば、モジュールを利用するテストだけが失敗となる:</p>
<pre class="literal-block">
# test the foo module
import unittest
class FooClassTests(unittest.TestCase):
def _getTargetClass(self):
from package.foo import FooClass
return FooClass
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_bar(self):
import qux
foo = self._makeOne(qux.Qux('Bar'))
</pre>
<p>多くのテストケースで利用される(テスト対象モジュール以外の)モジュールについては、妥協も必要だ。
こういったトレードオフは それらのモジュールの使い方が明確になった後、テストメソッドが安定したころに発生する。</p>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id14">ルール: 各テストメソッドでは、1つの事実だけを確認する</a></h3>
<p>少ない大きなテストを書くのを避けよう。
理想的には、各テストメソッドは、関数やメソッド1つに対する1つの前提条件だけで実行できるようにしよう。
以下のテストメソッドはとても多くのことを確認しようとしている:</p>
<pre class="literal-block">
def test_bound_used_container(self):
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl import Unauthorized
newSecurityManager(None, UnderprivilegedUser())
root = self._makeTree()
guarded = root._getOb('guarded')
ps = guarded._getOb('bound_used_container_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('container_str_ps')
self.assertRaises(Unauthorized, ps)
ps = guarded._getOb('container_ps')
container = ps()
self.assertRaises(Unauthorized, container)
self.assertRaises(Unauthorized, container.index_html)
try:
str(container)
except Unauthorized:
pass
else:
self.fail("str(container) didn't raise Unauthorized!")
ps = guarded._getOb('bound_used_container_ps')
ps._proxy_roles = ( 'Manager', )
ps()
ps = guarded._getOb('container_str_ps')
ps._proxy_roles = ( 'Manager', )
ps()
</pre>
<p>このテストは多くの間違いを犯しているが、一番まずいのはとても多くのことを確認しようとしていることだ(8個の異なるケースが含まれている)</p>
<p>一般化すれば、テストメソッドの前半ではフィクスチャやモック、定数値などの前提条件を設定してから、テスト対象のオブジェクトを作成したりテスト対象の関数をimportしたりする。
その後にテスト対象のメソッドや関数を呼び出す。
テストメソッドの後半では、結果を確認する。
典型的には、戻り値や、フィクスチャやモックの状態を確認する。</p>
<p>各メソッドや関数に対する前提条件をきっちりと区別すると、テスト内容が明確になる。
結果として、簡潔で整った、速い実装を行えるようになる。</p>
</div>
<div class="section" id="id4">
<h3><a class="toc-backref" href="#id15">ルール: テストメソッドは内容を表すようにしよう</a></h3>
<p>テストメソッドの名前は、テストレポートから得られる最初の役立つ情報となるべきだ。
見た人がテストを探すためにgrepするはめにならないようにしよう。</p>
<p>コメントをつけるよりも:</p>
<pre class="literal-block">
class FooClassTests(unittest.TestCase):
def test_some_random_blather(self):
# test the 'bar' method in the case where 'baz' is not set.
</pre>
<p>テストメソッドの名前で表そう:</p>
<pre class="literal-block">
class FooClassTests(unittest.TestCase):
def test_getBar_wo_baz(self):
#...
</pre>
</div>
<div class="section" id="setupself">
<h3><a class="toc-backref" href="#id16">ガイドライン: setupはヘルパーメソッドで提供しよう。テストケースのselfで共有するのはやめよう。</a></h3>
<p>必要のない処理を setUp で行うのはテスト間の依存性を増加させる。これはよくない。
例えばクラスがコンストラクタの引数でcontextを受け取るとしよう。
このcontextをsetUpで作成するより:</p>
<pre class="literal-block">
class FooClassTests(unittest.TestCase):
def setUp(self):
self.context = DummyContext()
# ...
def test_bar(self):
foo = self._makeOne(self.context)
</pre>
<p>contextを作成するヘルパーメソッドを追加しよう。ローカルに保つようにしよう:</p>
<pre class="literal-block">
class FooClassTests(unittest.TestCase):
def _makeContext(self, *args, **kw):
return DummyContext(*args, **kw)
def test_bar(self):
context = self._makeContext()
foo = self._makeOne(self.context)
</pre>
<p>この方法は、別々のテストで別々のモックcontextを用意できるし、依存性を排除できる。
また、contextが必要ないテストでは実行されないので、テスト実行自体も速くなるだろう。</p>
</div>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id17">ガイドライン: フィクスチャは可能な限り簡潔に</a></h3>
<p>ダミークラスを作るときは、空の実装から始めよう:</p>
<pre class="literal-block">
class DummyContext:
pass
</pre>
<p>テストを実行して、テストが成功するための最小限のモックを追加しよう。
テストの実行に関係のない振る舞いは追加しないようにしよう。</p>
</div>
<div class="section" id="id6">
<h3><a class="toc-backref" href="#id18">ガイドライン: フックやレジストリなどの利用は注意深く</a></h3>
<p>アプリケーションが既にプラグインやコンポーネントの登録などに対応しているならば、モックへの交換などに非常に有利だ。
テストごとに環境をリセットするのを忘れないこと!</p>
<p>純粋にテストを簡潔にするためのフックメソッドなども利用できるかもしれない。
たとえば、 datetimeの値をを "now"にする処理を、直接 datetime.now を呼ぶのではなく、モジュールスコープにおいた関数を経由する方法などだ。
テスト時はその関数を既知の値を返すモックに置き換えてしまえばよい(テストごとにその処理をもとに戻すように。)</p>
</div>
<div class="section" id="id7">
<h3><a class="toc-backref" href="#id19">ガイドライン: 依存関係を明確にするためにモックを利用する</a></h3>
<p>アプリケーションの依存性を可能な限り簡潔に維持すると、アプリケーションを書くのが簡単になり、変化に強くなります。
依存関係のためのとても簡単な実装だけを含むモックを書き、忍び寄る依存性からアプリケーションを守りましょう。</p>
<p>例えばRDBMSを使う場合、アプリケーション内のSQLクエリはキーワードパラメータを受け取ってdictのlistを返すモックに置き換えられます:</p>
<pre class="literal-block">
class DummySQL:
def __init__(self, results):
# results should be a list of lists of dictionaries
self.called_with = []
self.results = results
def __call__(self, **kw):
self.called_with.append(kw.copy())
return results.pop(0)
</pre>
<p>依存を簡潔にしている(この場合、SQLオブジェクトは1行ごとにdictへマッピングされたlistを返すだけです)ため、このモックを使ったテストは非常に簡単です:</p>
<pre class="literal-block">
class FooTest(unittest.TestCase):
def test_barflies_returns_names_from_SQL(self):
from foo.sqlregistry import registerSQL
RESULTS = [[{'name': 'Chuck', 'drink': 'Guiness'},
{'name': 'Bob', 'drink': 'Knob Creek'},
]]
query = DummySQL(RESULTS[:])
registerSQL('list_barflies', query)
foo = self._makeOne('Dog and Whistle')
names = foo.barflies()
self.assertEqual(len(names), len(RESULTS))
self.failUnless('NAME1' in names)
self.failUnless('NAME2' in names)
self.assertEqual(query.called_with, {'bar', 'Dog and Whistle'})
</pre>
</div>
<div class="section" id="id8">
<h3><a class="toc-backref" href="#id20">ルール: テストモジュール間でテキストを共有しない</a></h3>
<p>タイピングを減らすためにモックやフィクスチャデータを他のテストモジュールから借りてくる衝動にかられることがあるでしょう。
いったんそうしてしまうと、他のモジュールでも共有されるようになり、汎用的なフィクスチャとなっていきます。
これを禁止する理由は非常に明確です。
ユニットテストはアプリケーションをテストするのと同時に、可能な限り簡潔で明確でなければならないからです。</p>
<ul class="simple">
<li>フィクスチャとテストが同じモジュールにないため、共有されるモックやフィクスチャは読むのが大変です。</li>
<li>様々なテストからの利用に対応するため、1つのテストに使われる場合よりもフィクスチャはどんどん膨れ上がっていきます。
悪化していくと、テスト対象のアプリケーションよりも複雑になってしまいます。</li>
</ul>
<p>ときには、同じモジュール、クラスでのテストメソッドでもフィクスチャを共有を避けたほうがよいことすらあります。</p>
</div>
<div class="section" id="id9">
<h3><a class="toc-backref" href="#id21">まとめ</a></h3>
<p>このようなルールやガイドラインにしたがったテストは以下のような特徴があります。</p>
<ul class="simple">
<li>テストは単刀直入に書かれています</li>
<li>テストは効率よくアプリケーションをカバーします</li>
<li>予測可能な結果を示してくれます</li>
<li>速く実行でき、何度も実行する気になります</li>
<li>まだ実装していない処理などが予想通りに失敗するのを確認できます</li>
<li>予想外の失敗について、すぐに原因特定して修正できます</li>
<li>リグレッションテストに使えば、不具合の原因を調べるのに非常に役に立ちます</li>
<li>こういったテストを書くことでテスト対象の依存性や前提条件を明確にできます</li>
</ul>
</div>
</div>
Python Advent Calendar 2013 10日目 僕が知っているみんなが知ってるはずのこと2013-12-10T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-12-10:python-advent-calendar-2013-10ri-mu-pu-gazhi-tsuteiruminnagazhi-tsuteruhazunokoto.html<!-- -*- coding:utf-8 -*- -->
<p><a class="reference external" href="http://www.adventar.org/calendars/166">Python Advent Calendar 2013</a> の10日目だ。</p>
<p>今年はWebじゃない縛りってことで、生粋のWebプログラマーの僕としては結構苦労するテーマである。
(Webじゃない縛りを提案したのは僕ってことはさておきだね)</p>
<p>で、いろいろ考えたけど結構むずかしい。
py.testとnoseがどっちがいいかなんて3年以上前の話題だし、
pyqtやらnumpyとかopencvもARの集落を見ると解説するのも億劫だ。</p>
<p>で。ちょっと思いついたのは、pycon apac前後で質問されたこの内容だ。</p>
<p>「どこからそういう情報を得るのですか?」</p>
<p>僕にとっては非常に衝撃的な質問だった。
なぜなら僕が知ってることはみんなが知っていても不思議じゃない。
僕は開発者とのつながりってそれほどないし、公開情報をおっかけてるだけなんだ。
どこからと言われても、それは購読しているMLを教えればいいのだろうか?
だって、そのMLの参加資格はなにもないし、そんなにマイナーなMLじゃない。
だって、python.org直下のSIGのMLなんだよ? <a class="reference external" href="http://www.python.org/community/sigs/current/distutils-sig/">Distutil Sig</a></p>
<p>ということで Python Apacで発表したときの元ネタは、 Distutils-SIG ってところだ。
うん。このMLを読めば僕と同じ情報を手にしているはずだ。
たんに継続しただけってことかもしれないし、そこまでの情熱はないけど最新情報を誰かがまとめてくれているってだけで十分かもしれない。</p>
<p>僕も現にそう思っていたし、身近な人間(まあ名前は伏せておくとして、本人が嫌がるので心の中でだけ唯一「先生」と僕がつける存在だ)がそういう情報をみていなければ、
僕だって同じように、まとまった情報を受け入れる立場だったに違いない。</p>
<p>でも、それでも、当事者たちが直接意見を交わしているメールを見ているだけでも違うものだ。
そりゃできればその議論に参加したいし、彼らの目端にとまるような貢献をしたいという欲求はある。
僕にとっての第一歩は、distutils2に対するバグレポートで、2つ目はpycon apac でパッケージングについてまとめた話をすることだった。</p>
<p>僕は特別な存在じゃないし、特別なことをしたわけじゃない。
君たちと同じ立場から、今後のpythonについて知りたくて、ほんの一歩だけ、MLに登録したってことくらいしか違いはないはずだ。</p>
<p>advent clandarに書いてる人たちもpycon apacやpyfesで語る人たちも多い。
これを読んでる人から見たら、こいつらは自分とは違うと思うかもしれない。</p>
<p>でも、違いなんてないし、インターネットが発達した今日もそうだし、それ以前にグローバルな情報にアクセスするのなんて、日本のどこにいるかんなんて関係ないだろう?
来年のpyconでは、SIGの内容を紹介する僕のような発表者が蹴落とされるんじゃないかと毎年びくびくしながらCFPを出しているんだ。</p>
<p>ってことで、これを読んでいる君、SIG MLを読め、まとめろ、それを僕たちに教えて、PyCon で発表するんだ。
僕が発表したことなんてそれくらいにすぎない。
だって、そこからさらに議論に参加するという道だってあるんだから。</p>
<p>ということで、10日目、直前のtwitter <a class="reference external" href="https://twitter.com/aodag/status/410398084665864192">https://twitter.com/aodag/status/410398084665864192</a> での発言から勢いのままに書いた。
いつまでもベクトルの先にいつつ心の中でだけ先生と呼ぶ存在であり続けることを願って(続けろよ!) なんか気づいたらコミュニティでの知り合いとなっているsximadaにバトンします。</p>
Python3.4のensurepip(それとpyvenvの更新)2013-11-30T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-11-30:python34noensurepipsoretopyvenvnogeng-xin.html<!-- -*- coding:utf-8 -*- -->
<div class="section" id="ensurepippyvenv">
<h2>ensurepipとpyvenv</h2>
<p><a class="reference external" href="http://python.org/download/releases/3.4.0/">Python3.4b1</a> が 2013/11/24 にリリースされました。</p>
<p>今回はあまり文法的なアップデートはなく、いくつかの重要なモジュールが追加されています。
とりあえずパッケージ方面で重要なensurepipがb1リリースにぎりぎり間に合ったので、試してみました。</p>
<p>ensurepipモジュールは <a class="reference external" href="http://www.python.org/dev/peps/pep-0453/">PEP 453 -- Explicit bootstrapping of pip in Python installations</a> で提案されています。</p>
<p>現在のPythonパッケージングは、 <a class="reference external" href="https://pypi.python.org/pypi/setuptools">setuptools</a> と
<a class="reference external" href="http://www.pip-installer.org/en/latest/">pip</a> の2つを使うようになっています。
しかし、これらのツールはPythonの標準の配布物には含まれていませんでした。
Pythonインストール後は、 setuptools, pip, virtualenv をそろえるのが最初の作業でした。</p>
<p>これまでのvirtualenvを使うまで:</p>
<pre class="literal-block">
$ wget https://bitbucket.org/pypa/setuptools/src/1.4.1/ez_setup.py
$ wget https://raw.github.com/pypa/pip/develop/contrib/get-pip.py
$ ez_setup.py
$ get-pip.py
$ pip install virtualenv
$ virtualenv myenv
</pre>
<p>virtualenvは作成した環境にpipまでインストールするようになっています。
3.3から既に採用されている <a class="reference external" href="http://www.python.org/dev/peps/pep-0405/">pyvenv</a> は、virtualenvと違い、特に追加インストールするようにはなっていませんでした。
そのため、pyvenvが標準ライブラリに入ったわりにはvirtualvenvよりも不便なものになっていました。</p>
<p>これが、今回採用された <a class="reference external" href="http://www.python.org/dev/peps/pep-0453">ensurepip</a> によって、virtualenv同様に、仮想環境作成後にpipまでインストールされるようになりました。</p>
<p>Python3.4の場合:</p>
<pre class="literal-block">
$ pyvenv myenv
</pre>
<p>これだけです。</p>
<p>さて、ensurepipについてきちんと説明すると、pip自体が直接Pythonに同梱されているわけではありません。
これは pip のリリースサイクルと Pythonのリリースサイクルが異なるためです。
(pipをアップグレードするためにPythonをアップグレードするなんてやりたくないですよね?)</p>
<p>そのため、Pythonにはensurepipという、pipをインストールするモジュールが標準ライブラリに追加されました。
Pythonをインストールすると、最後にensurepipが呼び出されているため、すぐにpipが使えるようになっていますが、あくまでもインストーラーが自動実行されているだけで、pip自体は同梱されていません。</p>
<p>で、pipのアップグレードもこのensurepipを使って行います。</p>
</div>
<div class="section" id="ensurepippip">
<h2>ensurepipでpipがアップグレードされるのを確認してみる</h2>
<p>まず、現時点での pip のバージョン:</p>
<pre class="literal-block">
$ pip --version
pip 1.5rc1 from /home/aodag/hello/env/lib/python3.4/site-packages (python 3.4)
</pre>
<p>1.5rcです。PyPIに上がってるのは1.4.1が最新なので、githubにしか置いていないアグレッシブなバージョンです。
こわいので1.4.1にします。</p>
<pre class="literal-block">
$ pip install -U pip==1.4.1
$ pip --version
pip 1.4.1 from /home/aodag/hello/env/lib/python3.4/site-packages (python 3.4)
</pre>
<p>ensurepipにダウングレードする機能はないようなので、pipでバージョン指定します。</p>
<pre class="literal-block">
$ python -m ensurepip --upgrade
</pre>
<p>ensurepipします。
これで、またpipが最新の 1.5rc になるはずです、が。</p>
<pre class="literal-block">
$ pip --version
-bash: /home/aodag/hello/env/bin/pip: No such file or directory
</pre>
<p>(´・ω・`)なんだと...!?</p>
<pre class="literal-block">
$ pip3 --version
pip 1.5rc1 from /home/aodag/hello/env/lib/python3.4/site-packages (python 3.4)
</pre>
<p>ヽ(´_・ω・)_ pip3はあるのか....</p>
</div>
<div class="section" id="id1">
<h2>まとめ</h2>
<ul class="simple">
<li>もうsetuptoolsとかdistributeとかeasy_installは説明不要</li>
<li>ensurepipはとにかく最新版をインストールすることしかできない?</li>
<li>pip1.5のリリースが近いようなので、そこでもなにか変化があるかもしれない。</li>
</ul>
<p>ひとまずPythonインストールしたら pip が使えるようになったのは非常によろこばしいと思います。</p>
</div>
<div class="section" id="id2">
<h2>追記</h2>
<p>ドキュメントにモジュールの説明があります。 <cite>Bootstrapping the pip installer <http://docs.python.org/3.4/library/ensurepip.html></cite>
Python APIがあるので、環境設定のスクリプトを作るときは、これを呼べばいいのですね。</p>
<p>直接使える場所にはありませんが、pip,setuptooslのwheelが同梱されているようです。 <a class="reference external" href="http://hg.python.org/cpython/file/73a84d8dc544/Lib/ensurepip/_bundled">http://hg.python.org/cpython/file/73a84d8dc544/Lib/ensurepip/_bundled</a></p>
</div>
Pylons/PyramidユーザーのためのZope22013-11-03T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-11-03:pylonspyramidyuzanotamenozope2.html<!-- -*- coding:utf-8 -*- -->
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#zope" id="id9">Zopeとは?</a></li>
<li><a class="reference internal" href="#buildout" id="id10">buildout</a></li>
<li><a class="reference internal" href="#zope2" id="id11">Zope2の環境を構築する</a><ul>
<li><a class="reference internal" href="#buildout-cfg" id="id12">buildout.cfgを書く</a></li>
<li><a class="reference internal" href="#bootstrapbuildout" id="id13">bootstrapして、buildoutして..</a></li>
<li><a class="reference internal" href="#id1" id="id14">Zope2を立ち上げる</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id2" id="id15">Zope2プロダクト開発</a><ul>
<li><a class="reference internal" href="#zope3five-grok" id="id16">Zope3スタイルとfive.grok</a></li>
<li><a class="reference internal" href="#id3" id="id17">プロジェクトの作成</a></li>
<li><a class="reference internal" href="#guestbook" id="id18">Guestbookアプリケーション</a></li>
<li><a class="reference internal" href="#id4" id="id19">テンプレート</a></li>
<li><a class="reference internal" href="#configure-zcml" id="id20">configure.zcml</a></li>
<li><a class="reference internal" href="#zmi" id="id21">ZMIに追加する</a></li>
<li><a class="reference internal" href="#id5" id="id22">ZMIからの追加</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id6" id="id23">まとめ</a></li>
<li><a class="reference internal" href="#id7" id="id24">参考文献</a></li>
</ul>
</div>
<blockquote class="epigraph">
<p>Zopeは、それをモノにしたときのすばらしい悟り体験のために勉強しましょう</p>
<p class="attribution">—How To Become A Pythonista</p>
</blockquote>
<p>嘘です。</p>
<p>まあでも、Zopeを実戦で使うことはなかろうとも、そのコンセプトを知るのは無駄ではないでしょう。
特にPyramidはrepozeというZopeとWSGIの合流を目的にしたプロジェクトが出自なので、Zopeからのアイディアも多く採用されています。
Pyramidを使っていてZopeを再発見することも少なからずあるようなので、今更ながらでもZope2で遊ぶガイドとして、なんか書いてみようと思います。</p>
<div class="section" id="zope">
<h2><a class="toc-backref" href="#id9">Zopeとは?</a></h2>
<p>Zopeの言葉がさまざまに利用されているため、どれがZopeなのかわかりにくくなっています。
だいたい以下のものがZopeな気がします。</p>
<ul class="simple">
<li>Zope2</li>
<li>Zope Corporation</li>
<li>過去にZope3と呼ばれていたBluebreamというフレームワーク</li>
</ul>
<p>ここではZope2だけを対象にします。</p>
<p>Zope3はコンポーネントに分解され、汎用コンポーネントはZope Toolkitに、管理画面などのアプリケーション部分は Bluebreamになりました。
Zope Toolkitは Bluebreamも使っていますが、Zope2やgrok, Ploneなどが使うようになっています。</p>
<p>Zope2はフレームワークではなく、アプリケーションサーバーです。
開発者はZope2上にアプリケーションとなるクラスやオブジェクトを登録して、WebアプリケーションをZope2アプリケーションサーバー上で実行させることになります。</p>
</div>
<div class="section" id="buildout">
<h2><a class="toc-backref" href="#id10">buildout</a></h2>
<p>Zope2に限らずZope Toolkitを使うプロジェクトは多くの場合 zc.buildout を使って開発環境を構築します。</p>
<p>zc.buildoutは、環境構築の手順をパッケージにしたrecipeを使って環境構築を行います。
Zope2の環境構築には、 plone.recipe.zope2instance がよく用いられます。</p>
<p>buildoutによる環境構築は以下のような手順になります。</p>
<p># bootstrap.py を実行してbuildoutを使えるようにする
# buildout.cfg でレシピなどの設定をする
# bin/buildout でbuildout.cfgで設定された内容を実行する</p>
</div>
<div class="section" id="zope2">
<h2><a class="toc-backref" href="#id11">Zope2の環境を構築する</a></h2>
<div class="section" id="buildout-cfg">
<h3><a class="toc-backref" href="#id12">buildout.cfgを書く</a></h3>
<p>まずは環境構築のためにbuildout.cfgを書きましょう。</p>
<p>buildout.cfg:</p>
<pre class="literal-block">
[buildout]
extends = http://download.zope.org/Zope2/index/2.13.21/versions.cfg
parts = zope2
instance
interpreter = zopepy
[zope2]
recipe = zc.recipe.egg
eggs = Zope2
[instance]
recipe = plone.recipe.zope2instance
eggs = ${zope2:eggs}
user = admin:admin
http-address = 8080
</pre>
<p>buildoutでは、このファイルの各セクション(buildout, zope2, instanceなど)をpartといいます。
buildoutパートでは、extends, partsを指定しています。
buiildoutの設定ファイルは継承可能なので、extendsで他の設定ファイルを指定すると、その設定を受け継ぐようになります。
この例では Zope2 2.13.21で利用するライブラリバージョンなどが受け継がれます。
(気になる人は実際に <a class="reference external" href="http://download.zope.org/Zope2/index/2.13.21/versions.cfg">http://download.zope.org/Zope2/index/2.13.21/versions.cfg</a> をダウンロードして中を見てみましょう)</p>
<p>zope2パートでは必要なライブラリを設定しています。
zc.recipe.eggは、ライブラリをまとめて、それらのライブラリをPYTHONPATHに含めるなど、virtualenvとpipを合わせたような環境作成を行います。
interpreterで指定したzopeppyという名前のコマンドが作成されます。
そのコマンドを実行すると、eggsで指定したライブラリが利用可能なpythonインタプリタが起動されます。
ひとまずZope2を動かすだけなので、必要なのはZope2です。</p>
<p>instanceパートではZope2の実行環境構築の設定をしています。
eggsでは、 zope2パートのeggsと同じ内容とするため、 ${zope2:eggs} というプレースホルダーを使っています。
他にもオプションの設定がありますが、とりあえずZope2アプリケーションサーバーを立ち上げるにはこれで十分です。</p>
</div>
<div class="section" id="bootstrapbuildout">
<h3><a class="toc-backref" href="#id13">bootstrapして、buildoutして..</a></h3>
<p>buildoutするには、zc.buildoutをインストールしますが、site_packagesなどにはインストールせずに、プロジェクト直下にインストールします。
そのためのスクリプトが bootstrap.py です。
<a class="reference external" href="http://downloads.buildout.org/1/bootstrap.py">http://downloads.buildout.org/1/bootstrap.py</a> からダウンロードしてきましょう。
bootstrap.pyを先ほど作成したbuildout.cfgと同じディレクトリにコピーします。
また、既存のPython環境から隔離するため、virtualenvを使います。</p>
<p>以下のように、実行します。</p>
<pre class="literal-block">
$ virtualenv env
$ env/bin/python bootstrap.py
$ bin/buildout
</pre>
</div>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id14">Zope2を立ち上げる</a></h3>
<p>buildoutでZope2の環境ができあがりました。
plone.recipe.zope2instance で instance コマンドが作成されています。
instance fg でフォアグランド実行ができます。
ひとまずこれで立ち上げて動作を確認してみましょう。</p>
<pre class="literal-block">
$ bin/instance fg
</pre>
<p><a class="reference external" href="http://localhost:8080">http://localhost:8080</a> でZope2が実行されているのがわかります。
management interface(ZMI) にはbuildoutで設定した admin ユーザーでログインできます。</p>
<p>ZMIの使い方はここでは説明しませんが、 <a class="reference external" href="http://docs.zope.org/zope2/zope2book/">Zope2 Book</a> などが参考になるでしょう。</p>
</div>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id15">Zope2プロダクト開発</a></h2>
<p>Zope2上のアプリケーションは過去に Products フォルダに入れなければなりませんでした。
現在ではそのような制限はなくなりましたが、互換性のためにProducts名前空間を使っているZope2アプリケーションも多くあり、総じてZope2アプリケーションはプロダクトと呼ばれます。</p>
<div class="section" id="zope3five-grok">
<h3><a class="toc-backref" href="#id16">Zope3スタイルとfive.grok</a></h3>
<p>Zope2はすでにZope Toolkitを使っているので、その中で動かすアプリケーションでも利用できます。
便宜上Zope3スタイルと呼んでおきます。</p>
<p>Zope2の伝統的なアプリケーションでは、オブジェクトが直接ビューを返すメソッドを持っています。
Zope3スタイルではこれを分離して扱います。</p>
<p>Zope3スタイルでそのまま実装しようとすると、configure.zcmlに多くのコンポーネント設定を書くことになります。
GrokというZope3フレームワークがあり、これは、configure.zcmlに書くことなく、規約でコンポーネントを構成しています。
この技術をさらにZope2上で利用できるようにしたものがfive.grokです。
(Zope3の技術をZope2にバックポートするものは Five という名前空間を使うのが慣例です。)</p>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id17">プロジェクトの作成</a></h3>
<p>Zope2プロダクトと言えども、Pythonパッケージであることは変わりません。
ただし、プロダクトごとに細かくパッケージを分けたりするので、
<a class="reference external" href="https://pypi.python.org/pypi/mr.developer">mr.developer</a>
で管理してみましょう。
mr.developerを使うと、開発中パッケージをリポジトリから取り寄せたり、developの対象から外したりできます。</p>
<p>プロジェクト直下にsrcディレクトリを作成します。
これから作成するguestbookアプリケーションのためのディレクトリを作成して、setup.pyを書きましょう。</p>
<pre class="literal-block">
$ mkdir -p src/guestbook
</pre>
<p>src/guestbook/setup.py:</p>
<pre class="literal-block">
from setuptools import setup, find_packages
requires = [
"Zope2",
"five.grok",
]
setup(name="guestbook",
install_requires=requires,
packages = find_packages()
)
</pre>
<p>mr.developerをbuildoutに追加します。</p>
<p>buildout.cfg:</p>
<pre class="literal-block">
[buildout]
extensions = mr.developer
[sources]
guestbook = fs guestbook
</pre>
<p>guestbookをinstanceのeggsに追加します。
直接buildout.cfgに追加してもよいですが、guestbook用の設定ファイルを新しく作成して、元のbuildout.cfgを拡張するようにしてみましょう。</p>
<p>guestbook.cfg:</p>
<pre class="literal-block">
[buildout]
auto-checkout = guestbook
[zope2]
eggs += guestbook
</pre>
<p>buildoutで、buildout.cfg以外の設定ファイルを使う場合は <tt class="docutils literal"><span class="pre">-c</span></tt> オプションを使います。</p>
<pre class="literal-block">
$ buildout -c guestbook.cfg
</pre>
<p>これでguestbookアプリケーションを開発する準備が整いました。</p>
</div>
<div class="section" id="guestbook">
<h3><a class="toc-backref" href="#id18">Guestbookアプリケーション</a></h3>
<p>Zope2のアプリケーションは単にZODB上に保存されるオブジェクトです。
が、ZMIから追加できるように登録したり、コンポーネントの構成を登録したりとめんどうが多いので、five.grokを利用してさっくりといきましょう。</p>
<p>まずは guestbookプロジェクトにguestbookパッケージを作ります。</p>
<pre class="literal-block">
$ mkdir -p src/guestbook/guestbook
$ touch src/guestbook/guestbook/__init__.py
</pre>
<p>app.py にアプリケーションを実装します。</p>
<p>src/guestbook/guestbook/app.py:</p>
<pre class="literal-block">
from datetime import datetime
from five import grok
from persistent.list import PersistentList
class Guestbook(grok.Model):
meta_type = 'guestbook'
def __init__(self, *args, **kwargs):
super(Guestbook, self).__init__(*args, **kwargs):
self.greetings = PersistentList()
def add_comment(self, name, comment):
greeting = dict(name=name, comment=comment, create_at=datetime.now())
self.greetings.append(greeting)
</pre>
<p>add_comment メソッドがゲストブックに情報を書きこむメソッドです。
オブジェクトデータベースなので、これだけで情報は保存されます。
meta_typeはZope2のZMI上で表示される文字列です。
あとは、ビューが必要です。
これらも app.py に実装しましょう。
モデルと同じモジュール内でビューを実装すると、モデルとビューの対応付けが自動で行われます。</p>
<p>src/guestbook/guestbook/app.py 後半:</p>
<pre class="literal-block">
class Index(grok.View):
pass
class Post(grok.View):
def update(self, name, comment):
self.context.add_comment(name, comment)
def render(self):
return self.redirect(self.url(self.context))
</pre>
<p>Indexクラスは、単にcontextを表示するだけなので、なにもメソッドを持ちません。
Postクラスはフォームの入力を受けて、Guestbookのadd_commentを呼び出すビューです。
updateメソッドで実際の処理を行い、renderメソッドで元の画面にリダイレクトしています。</p>
</div>
<div class="section" id="id4">
<h3><a class="toc-backref" href="#id19">テンプレート</a></h3>
<p>ビュークラスは、HTMLの生成は受け持っていません。
Postクラスではrenderメソッドをオーバーライドしているため、レスポンスボディの生成されないため、IndexクラスのHTMLテンプレートだけが必要です。</p>
<p>five.grokではgrok.Viewを継承している場合、 {モジュール名}_templates/{クラス名}.pt というテンプレートが自動で利用されます。
appモジュールの Indexクラスの場合は app_templates/index.pt となります。</p>
<p>テンプレートの内容は <a class="reference external" href="https://github.com/aodag/zope2-guestbook/blob/master/guestbook/app_templates/index.pt">https://github.com/aodag/zope2-guestbook/blob/master/guestbook/app_templates/index.pt</a> をみてください。</p>
</div>
<div class="section" id="configure-zcml">
<h3><a class="toc-backref" href="#id20">configure.zcml</a></h3>
<p>さて、アプリケーションの実装ができたので、これらをコンポーネント登録します。
Zope3スタイルでは上記のクラスを全部configure.zcmlで登録することになりますが、five.grokを使っているので、1行のディレクティブで済ませることができます。</p>
<p>src/guestbook/guestbook/configure.zcml:</p>
<pre class="literal-block">
<configure xmlns="http://namespaces.zope.org/five"
xmlns:grok="http://namespaces.zope.org/grok">
<include package="five.grok"/>
<grok:grok package="."/>
</configure>
</pre>
</div>
<div class="section" id="zmi">
<h3><a class="toc-backref" href="#id21">ZMIに追加する</a></h3>
<p>アプリケーションを実装して、コンポーネントへの登録もできました。
あとは、ZMIへの登録です。</p>
<p>ZMIはZope2固有の機能なので、それに従わなくてはなりません。</p>
<p>src/guestbook/guestbook/__init__.py:</p>
<pre class="literal-block">
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from . import app
def addGuestbook(context, id, title):
""" add new guestbook """
guestbook = app.Guestbook(id)
guestbook.title = title
context._setObject(id, guestbook)
return "OK"
manage_addGuestbookForm = PageTemplateFile('manage_addGuestbook', globals())
def initialize(registrar):
registrar.registerClass(
app.Guestbook,
constructors=(manage_addGuestbookForm,
addGuestbook))
</pre>
<p>アプリケーションクラスと、追加するためのフォームとアクションをregistrarで登録します。
後はこの関数を configure.zcml に追加します。</p>
<p>src/guestbook/guestbook/configure.zcml 最終形:</p>
<pre class="literal-block">
<configure xmlns="http://namespaces.zope.org/five"
xmlns:grok="http://namespaces.zope.org/grok">
<registerPackage package="." initialize=".initialize"/>
<include package="five.grok"/>
<grok:grok package="."/>
</configure>
</pre>
</div>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id22">ZMIからの追加</a></h3>
<p>ここまできたら Zope2 を再起動します。
ZMIからguestbookを追加すれば、Guestbookアプリケーションを利用できます。</p>
</div>
</div>
<div class="section" id="id6">
<h2><a class="toc-backref" href="#id23">まとめ</a></h2>
<p>めんどい</p>
</div>
<div class="section" id="id7">
<h2><a class="toc-backref" href="#id24">参考文献</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://docs.zope.org/zope2/zdgbook/">Zope Developer’s Guide</a></li>
<li><a class="reference external" href="http://docs.zope.org/zope2/zope2book/">Zope2 Book</a></li>
<li><a class="reference external" href="http://developer.plone.org/reference_manuals/active/five-grok/index.html">Zope Component Architecture basics with five.grok</a></li>
</ul>
</div>
PythonのいろんなWebフレームワークでゲストブックアプリケーション2013-10-30T00:00:00+01:00aodagtag:pelican.aodag.jp,2013-10-30:pythonnoironnawebhuremuwakudegesutobutsukuapurikeshiyon.html<!-- -*- coding:utf-8 -*- -->
<p>作りました。</p>
<p><a class="reference external" href="http://d.hatena.ne.jp/nullpobug/20131026/1382728022">動機となったtokibitoのエントリ</a></p>
<p>まあロジックもそれほどないし、 bottle+peewee と比べてpyramidで書いたらどうなるかってので、1つ書いたのが <a class="reference external" href="https://gist.github.com/aodag/7173552">こちら</a> 。</p>
<p>オリジナルをほぼそのまま使いつつ、モデル定義を SQLAlchemy に変更。viewの関数の引数調整やURLの登録とかやって完成。</p>
<p>その後、調子に乗って、Zope2とPloneでもやってみた。</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/aodag/zope2-guestbook">Zope2版</a></li>
<li><a class="reference external" href="https://github.com/aodag/plone-guestbook">Plone版</a></li>
</ul>
<p>Zope2版では、five.grokを使ってみた。
grokはZope Toolkitを使ったWebアプリケーションフレームワークで、規約ベースとなっています。
five.grokはこれをZope2上で使えるようにしたもの。
規約ベースなので、規約を知らないと、なぜこれだけで動くのかってのがわかりにくいはず。</p>
<p>app.py には Guestbookクラスが実装されていて、唯一のモデルクラスとなっています。
なので、同じapp.pyにある2つのビュークラスは自動でGuestbookクラス用のビューになる。
さらに app_templates にあるクラスと同名(厳密にはlowerした名前)のテンプレートも自動でビューが利用します。
Postクラス用のテンプレートがないけど、これは render メソッドをオーバーライドしているため。
あとはZope2の管理画面(ZMI)から扱えるように、なんか作法にのっとってファクトリを登録しているのがイマイチですが、これは黙って従うものとしましょう。</p>
<p>Plone版では、dexterityを使ってみました。(実際にdexterityで書いてみたのは初めて)
dexterityでは、コンテンツの実装はfive.grokを使うので、コーディング自体はあまり変わらず。
PloneのCMS内にコンテンツの情報を登録するところが主になっています。
Ploneのアドオンで登録するので、アドオンのメタデータと、コンテンツのメタデータなどをXMLで作る。結構な量があるので、自動生成なかったら相当面倒な気がします。(下手するとメタデータのテストも必要)</p>
<p>ちなみに、パーミッションなど、適当です。
ゲストブックなので、Viewパーミッションあれば書き込み可能。</p>
<p>ヽ(´_・ω・)_あとはなにで作ろうねぇ...</p>
Pythonライブラリメモ2013-10-20T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-10-20:pythonraiburarimemo.html<p>分野別メモ(一部、趣味嗜好による偏りがあります)</p>
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#id1" id="id12">日付</a></li>
<li><a class="reference internal" href="#web" id="id13">webプログラミング</a><ul>
<li><a class="reference internal" href="#id2" id="id14">データ処理</a></li>
<li><a class="reference internal" href="#id3" id="id15">クライアント</a></li>
<li><a class="reference internal" href="#wsgi" id="id16">WSGI/フレームワーク</a></li>
<li><a class="reference internal" href="#id4" id="id17">WSGI/ツール、ライブラリ</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id5" id="id18">ストレージ</a><ul>
<li><a class="reference internal" href="#rdb" id="id19">RDBドライバ</a></li>
<li><a class="reference internal" href="#orm" id="id20">ORM</a></li>
<li><a class="reference internal" href="#nosql" id="id21">NoSQL</a></li>
<li><a class="reference internal" href="#id6" id="id22">キャッシュ</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id7" id="id23">ドキュメンテーション</a></li>
<li><a class="reference internal" href="#id8" id="id24">テスト</a></li>
<li><a class="reference internal" href="#id9" id="id25">開発</a><ul>
<li><a class="reference internal" href="#id10" id="id26">パッケージング/配備</a></li>
<li><a class="reference internal" href="#id11" id="id27">構成管理</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id12">日付</a></h2>
<ul class="simple">
<li>python-dateutil</li>
<li>arrow</li>
<li>mxDateTime</li>
</ul>
</div>
<div class="section" id="web">
<h2><a class="toc-backref" href="#id13">webプログラミング</a></h2>
<div class="section" id="id2">
<h3><a class="toc-backref" href="#id14">データ処理</a></h3>
<ul class="simple">
<li>simplejson</li>
<li>lxml</li>
<li>pycrypto</li>
</ul>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id15">クライアント</a></h3>
<ul class="simple">
<li>requests</li>
<li>openid2rp</li>
<li>httplib2</li>
</ul>
</div>
<div class="section" id="wsgi">
<h3><a class="toc-backref" href="#id16">WSGI/フレームワーク</a></h3>
<ul class="simple">
<li>pyramid</li>
<li>flask</li>
<li>bottle</li>
<li>wheezy</li>
<li>web2py</li>
<li>turbogears2</li>
<li>pylons</li>
</ul>
</div>
<div class="section" id="id4">
<h3><a class="toc-backref" href="#id17">WSGI/ツール、ライブラリ</a></h3>
<ul class="simple">
<li>webob</li>
<li>pastedeploy</li>
<li>repoze.*</li>
<li>zope.*</li>
<li>jinja2</li>
<li>mako</li>
<li>chameleon</li>
<li>kajiki</li>
<li>deform</li>
<li>colander</li>
<li>formencode</li>
<li>tw2</li>
<li>formalchemy</li>
<li>webhelpers2</li>
</ul>
</div>
</div>
<div class="section" id="id5">
<h2><a class="toc-backref" href="#id18">ストレージ</a></h2>
<div class="section" id="rdb">
<h3><a class="toc-backref" href="#id19">RDBドライバ</a></h3>
<ul class="simple">
<li>pymysql</li>
<li>pcycopg2</li>
</ul>
</div>
<div class="section" id="orm">
<h3><a class="toc-backref" href="#id20">ORM</a></h3>
<ul class="simple">
<li>sqlalchemy</li>
<li>peewee</li>
</ul>
</div>
<div class="section" id="nosql">
<h3><a class="toc-backref" href="#id21">NoSQL</a></h3>
<ul class="simple">
<li>pyredis</li>
<li>pymongo</li>
</ul>
</div>
<div class="section" id="id6">
<h3><a class="toc-backref" href="#id22">キャッシュ</a></h3>
<ul class="simple">
<li>dogpile.cache</li>
<li>beaker</li>
</ul>
</div>
</div>
<div class="section" id="id7">
<h2><a class="toc-backref" href="#id23">ドキュメンテーション</a></h2>
<ul class="simple">
<li>docutils</li>
<li>sphinx</li>
</ul>
</div>
<div class="section" id="id8">
<h2><a class="toc-backref" href="#id24">テスト</a></h2>
<ul class="simple">
<li>webtest</li>
<li>pytest</li>
<li>testfixtures</li>
<li>mock</li>
<li>nose</li>
<li>nose2</li>
<li>zope.testing</li>
<li>zope.testrunner</li>
</ul>
</div>
<div class="section" id="id9">
<h2><a class="toc-backref" href="#id25">開発</a></h2>
<div class="section" id="id10">
<h3><a class="toc-backref" href="#id26">パッケージング/配備</a></h3>
<ul class="simple">
<li>setuptools</li>
<li>pip</li>
<li>vertualenv</li>
<li>distlib</li>
<li>wheel</li>
<li>bento</li>
<li>twine</li>
<li>devpi</li>
<li>zc.buildout</li>
<li>fabric</li>
<li>ansible</li>
<li>pramiko</li>
</ul>
</div>
<div class="section" id="id11">
<h3><a class="toc-backref" href="#id27">構成管理</a></h3>
<ul class="simple">
<li>mercurial</li>
<li>buildbot</li>
<li>trac</li>
<li>rhodecode</li>
</ul>
</div>
</div>
wheelを使って開発する2013-10-06T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-10-06:wheelwoshi-tsutekai-fa-suru.html<p>とりあえず、 <tt class="docutils literal">pyvenv</tt> で作った仮想環境に <tt class="docutils literal">pip</tt> がインストールされているというところから。</p>
<p><a class="reference external" href="http://www.pylonsproject.org/projects/pyramid/about">pyramid</a> の開発を例にします。</p>
<div class="section" id="id1">
<h2>wheelをインストールする</h2>
<p>wheelパッケージを使うために <tt class="docutils literal">wheel <span class="pre"><http://pypi.python.org/pypi/wheel></span></tt> をインストールします。</p>
<pre class="literal-block">
$ pip install wheel --use-wheel
</pre>
<p><tt class="docutils literal">pip</tt> は1.4以降でwheelパッケージをインストールできます。
しかし、 <tt class="docutils literal">setuptools</tt> は、wheelパッケージを作成できません。
<tt class="docutils literal">wheel</tt> をインストールすると、setup.pyで bdist_wheel というサブコマンドが使えるようになります。
また、 <tt class="docutils literal">pip</tt> でも <tt class="docutils literal">wheel</tt> サブコマンドが使えるようになります。</p>
</div>
<div class="section" id="id2">
<h2>wheelでライブラリをインストールする</h2>
<p>現在PyPIにはそれほどwheelパッケージはあげられていないため、sdistパッケージを使ってローカルでwheelパッケージを作成します。</p>
<p>pyramidとその依存ライブラリのwheelパッケージを作成する:</p>
<pre class="literal-block">
$ pip wheel pyramid --pre
</pre>
<p>wheelパッケージをインストールする:</p>
<pre class="literal-block">
$ pip install pyramid --use-wheel --find-links=wheelhouse --no-index
</pre>
</div>
<div class="section" id="id3">
<h2>開発環境</h2>
<p>pyramidでは <tt class="docutils literal">pcreate</tt> コマンドでプロジェクトを開始するときに必要なファイルなどを自動生成します。
今回はZODBを利用したプロジェクトとします。</p>
<pre class="literal-block">
$ pcreate -s zodb mywork
</pre>
<p>プロジェクトの依存ライブラリをwheelインストール</p>
<pre class="literal-block">
$ pip wheel mywork --find-links=wheelhouse --use-wheel --pre
$ pip install -e mywork --find-links=wheelhouse --use-wheel --pre --no-index
</pre>
<p>wheelしたときに開発対象のプロジェクトのwheelパッケージも作成されますが、そのあとのinstallで対象外になるので気にしないようにしましょう。</p>
<p>この時点で、 <tt class="docutils literal">pip</tt> 以外は wheelパッケージから展開されて、 <a class="reference external" href="http://www.python.org/dev/peps/pep-0426/">METADATA 2.0</a> をサポートした dist-info 形式でインストールされています。</p>
</div>
<div class="section" id="id4">
<h2>まとめ</h2>
<p>依存ライブラリの扱いはwheelで十分可能になっています。(オプション指定が多いですが)</p>
<p>が、 pserveコマンドが利用するPasteDeplyは egg(というかpkg_resources)に依存しているため、ツールなどを含めるとまだ完全な移行は難しい状態です。</p>
</div>
Python開発環境2013-10-05T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-10-05:pythonkai-fa-huan-jing.html<p>ansible化のためのメモ</p>
<p>とりあえずdebianで試してますが、たいていのUNIX系OSでは同じでしょう。</p>
<div class="section" id="id1">
<h2>Pythonインストール</h2>
<p>MacやLinuxであれば、ほぼ間違いなくデフォルトでPythonはインストールされています。
が、システムでばりばり利用されている上にCentOSなど古いバージョンのままだったり、Macのように不可思議なパッチがあてられていたり、
古いsetuptoolsが添付されていたりと、問題の火種になる要素がてんこもりです。</p>
<p><a class="reference external" href="http://www.python.org/download/">公式サイト</a> からダウンロードしてクリーンな環境を作成しましょう。</p>
<p>ダウンロード:</p>
<pre class="literal-block">
$ wget http://www.python.org/ftp/python/3.3.2/Python-3.3.2.tar.bz2
</pre>
<div class="note">
<p class="first admonition-title">Note</p>
<p class="last">2013/10/05時点では3.3.2が最新バージョンです。</p>
</div>
<div class="section" id="id3">
<h3>インストールに必要なライブラリ</h3>
<p>ビルドするためには、 <tt class="docutils literal"><span class="pre">build-essential</span></tt> が必要です。
そのままビルドまで進めると、以下のようにライブラリが足りないために作成されなかったモジュールが表示されるので、
必要なライブラリもインストールします。</p>
<pre class="literal-block">
_bz2 _curses _curses_panel
_dbm _gdbm _lzma
_sqlite3 _ssl _tkinter
readline zlib
</pre>
<ul class="simple">
<li>libbz2-dev</li>
<li>libncurses-dev</li>
<li>libdb-dev</li>
<li>libgdbm-dev</li>
<li>liblzma-dev</li>
<li>libsqlite-dev</li>
<li>libssl-dev</li>
<li>tk-dev</li>
<li>libreadline-dev</li>
<li>zlib1g-dev</li>
</ul>
<div class="note">
<p class="first admonition-title">Note</p>
<p class="last">恐らく、サーバー環境では <tt class="docutils literal">tkinter</tt> を使う必要はないでしょう。また、 <tt class="docutils literal">cursers</tt> もWebアプリケーションには必要なさそうです。
実際のところ必要になるとすれば、 ssl, zlib, bzip2 あたりです。また、Webセッションの保存にdbmが必要になるかもしれません。</p>
</div>
<pre class="literal-block">
$ sudo aptitude install build-essential
$ sudo aptitude install libbz2-dev libncurses-dev libdb-dev libgdbm-dev liblzma-dev libsqlite-dev libssl-dev tk-dev libreadline-dev zlib1g-dev
</pre>
</div>
<div class="section" id="id4">
<h3>ビルド、インストール</h3>
<p>インストール先ですが、 <tt class="docutils literal"><span class="pre">/opt/python-{version}</span></tt> に入れることにします。</p>
<pre class="literal-block">
$ tar xvf Python-3.3.5.tar.bz2
$ cd Python-3.3.5
$ ./configure --prefix=/opt/python-3.3.5
$ make
$ sudo make install
$ /opt/python-3.3.2/bin/python3 --version
Python 3.3.2
</pre>
</div>
</div>
<div class="section" id="id5">
<h2>プロジェクト用のPython仮想環境</h2>
<p>まずは <tt class="docutils literal">pyvenv</tt> で仮想環境を作ります。</p>
<pre class="literal-block">
$ /opt/python-3.3.2/bin/pyvenv env
</pre>
<p>続いて、 <tt class="docutils literal">setuptools</tt>, <tt class="docutils literal">pip</tt> とインストールします。</p>
<pre class="literal-block">
$ wget https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py
$ env/bin/python ez_setup.py
$ wget https://raw.github.com/pypa/pip/develop/contrib/get-pip.py
$ env/bin/python get-pip.py
</pre>
</div>
<div class="section" id="id6">
<h2>まとめ</h2>
<p>と、ここまでやって、 <tt class="docutils literal">ansible</tt> 自体がpython2じゃないと動かないっぽいことに気づいた。</p>
</div>
PythonでWebAppの開発に必要なN個のこと2013-09-10T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-10:pythondewebappnokai-fa-nibi-yao-nange-nokoto.html<p><a class="reference external" href="http://d.hatena.ne.jp/gfx/20130909/1378741015">元ネタ</a></p>
<p>あるプログラミング言語で実際にWebAppを開発できるようになるまで、何が必要だろうか。言語仕様の習得は終えているとしよう。おそらく、最低限以下のような知識が必要だと思われる。とりあえずPythonについて知っていることを書いた。</p>
<div class="section" id="id2">
<h2>パッケージマネージャ</h2>
<p>まずライブラリの管理。モジュールをインストールし、可能であればバージョンを固定し、適切にロードする機能が必要だ。</p>
<p>Pythonの場合は pip というPyPIクライアントでライブラリをインストールする。ライブラリパスの設定は virtualenv で行う。</p>
</div>
<div class="section" id="id3">
<h2>アプリケーションサーバー</h2>
<p>Webサーバへのインターフェイスとしては、WSGIという仕様がある。WSGIに準拠したツールキットとしてWebObやWerkzuegなどがあり、サーバーには標準ライブラリのwsgirefやwaitress、gunicornなどがある。
本番環境ではgunicornが人気だ。</p>
</div>
<div class="section" id="id4">
<h2>リクエストパラメーターの処理</h2>
<p>cgi.FieldStorage が標準ライブラリにあるが、WebObやWerkzeugのRequestクラスを使うべきである。</p>
</div>
<div class="section" id="id5">
<h2>ルーティング</h2>
<p>サーバにきたリクエストを適切なコントローラに振り分ける機能だ。
Werkzeugにも含まれているし、ルーティング単体のライブラリWebDispatchもある。</p>
</div>
<div class="section" id="id6">
<h2>データベース</h2>
<p>標準ライブラリのsqliteをはじめ、DB API2.0がドライバの標準インターフェイスとなっている。
ORMを使うのであれば、SQLAlchemy以外の選択肢は思いつかない。</p>
</div>
<div class="section" id="id7">
<h2>ビューのレンダリング</h2>
<p>HTMLテンプレートエンジンは数え切れないほどあるので、主義思想なりパフォーマンスなりで選択しよう。</p>
<ul class="simple">
<li>Jinja2</li>
<li>Mako</li>
<li>Chameleon</li>
<li>Kajiki</li>
</ul>
<p>JSONの取扱いは標準モジュールのjsonがあるが、バグフィックスなどが行われているsimplejsonをインストールするべきだ。</p>
</div>
<div class="section" id="id8">
<h2>テストフレームワーク</h2>
<p>標準のunittestや、nose, py.testがある。最近はpy.testの人気が高い</p>
<ul class="simple">
<li>unittest</li>
<li>nose</li>
<li>py.test</li>
</ul>
<p>WSGIアプリケーションのテストには webtest が便利である。</p>
<p>mockライブラリがpython3.3以降で標準ライブラリに含まれている。</p>
<p>testfixturesはより詳しい情報を表示するアサーションや、ファイルシステムのフィクスチャ、例外のテストコンテキストなど便利なユーティリティをまとめている。</p>
</div>
<div class="section" id="http">
<h2>HTTPクライアント</h2>
<p>標準ライブラリの httplib, urllib, などあるが、最近はrequestsが非常に使いやすいAPIを提供している。</p>
</div>
<div class="section" id="waf">
<h2>WAF</h2>
<p>Djangoという定番があるが、上記のツールを組み合わせると発狂するので、以下のフレームワークをおすすめする。
どれもwsgiに準拠したフレームワークである。</p>
<ul class="simple">
<li>pyramid</li>
<li>flask</li>
<li>bottle</li>
</ul>
<p>以上。URLは気力があったら追記するので、それまではぐぐってほしい。</p>
</div>
PyHack 夏山 2013 の成果2013-09-08T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-08:pyhack-xia-shan-2013-nocheng-guo.html<p>まあ簡単に書いときます</p>
<ul class="simple">
<li>jsonschemaでスキーマ定義して、alpacaでフォーム生成、python-jsonschemaでバリデーションとかやってみた</li>
<li>drone.ioでwheelビルドとかしてみた</li>
<li>「谷村有美」俺的ベスト10を作った(with usaturn)</li>
</ul>
<p>ということで、最大の成果が以下</p>
<ul class="simple">
<li>OH MY GOD!!</li>
<li>好きこそものの上手なれ</li>
<li>最後のKiss</li>
<li>恋に落ちた</li>
<li>一緒に暮らそう</li>
<li>愛情と友情</li>
<li>たいくつな午後</li>
<li>パレード・パレード</li>
<li>いちばん大好きだった</li>
<li>今が好き</li>
</ul>
<p>ちなみにほぼ全曲知ってる共通した話題として出ただけで、べ、別にファンとかじゃないんだからね!</p>
Windows環境構築メモ2013-09-08T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-08:windowshuan-jing-gou-zhu-memo.html<p>単なる備忘録です。</p>
<div class="section" id="id1">
<h2>インストールしたもの</h2>
<ul class="simple">
<li>putty</li>
<li>winscp</li>
<li>winmerge</li>
<li>sourcetree</li>
<li>pycharm</li>
<li>gnupack版 emacs</li>
<li>kaoriya版 gvim</li>
<li>mingw</li>
<li>msysgit</li>
<li>vs2013 express for Desktop</li>
<li>vs2013 express for Windows8</li>
<li>vs2013 express for Web</li>
<li>SQLServer express</li>
</ul>
</div>
SQLAlchemy2013-09-07T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-07:sqlalchemy.html<p>つーことで、PyCon APAC2013のCFP没ネタの2つ目。</p>
<div class="section" id="id1">
<h2>インストール</h2>
<p>特にめんどうなことなくpipでインストールできます。</p>
<pre class="literal-block">
$ pip install sqlalchemy
</pre>
</div>
<div class="section" id="id2">
<h2>モデル定義</h2>
<p>テーブル定義とクラス定義をして、それらをマッピングするのが、データマッパーの本来の方法ですが、sqlalchemy.ext.declarative を使うのが圧倒的に楽です。
これを使うとテーブル定義をクラス内で行い、自動でマッピングまで行ってくれます。
declarative_baseでベースクラスを定義して、それを継承してモデルを定義します。:</p>
<pre class="literal-block">
from sqlalchemy import (
Column,
Integer,
ForeignKey,
)
from sqlalchemy.orm import (
relationship,
scoped_session,
sessionmaker,
)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
DBSession = scoped_session(sessionmaker())
class BankAccount(Base):
__tablename__ = 'bankaccount'
query = DBSession.query_property()
id = Column(Integer, primary_key=True)
balance = Column(Integer, default=0)
bank_id = Column(Integer, ForeignKey('bank.id'))
bank = relationship('Bank', backref='accounts')
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
class Bank(Base):
__tablename__ = 'bankaccount'
query = DBSession.query_property()
id = Column(Integer, primary_key=True)
</pre>
<div class="section" id="id3">
<h3>データ作成とかクエリとか</h3>
<p>新規作成は作ったオブジェクトを sessionにaddします。:</p>
<pre class="literal-block">
bank = Bank()
DBSession.add(bank)
DBSession.commit()
</pre>
<p>DBSessionはaddされたオブジェクトを flush メソッドが呼ばれたときに INSERT します。
また、commitメソッドでDBにcommitします。このときflushが必要だったときに自動でflushを呼びます。</p>
<p>既にDBSession管理下のオブジェクトに新規オブジェクトを関連づけても、INSERTされます。:</p>
<pre class="literal-block">
bank.accounts.append(BankAccount())
DBSession.commit()
</pre>
<p>モデルを取得する場合は、DBSessionのqueryメソッドを使用します。
クエリには、filterメソッドで条件を追加できます。:</p>
<pre class="literal-block">
DBSession.query(BankAccount)
DBSession.query(BankAccount).filter(BankAccount.balance>=100)
</pre>
<p>queryメソッドはクエリを作成するだけで、実際に取得させるには、allやone, firstなどのメソッドを呼ぶか、 for文などでイテレータとして評価させます。:</p>
<pre class="literal-block">
DBSession.query(BankAccount).all()
DBSession.query(BankAccount).first()
DBSession.query(BankAccount).one()
for account in DBSession.query(BankAccount):
account.withdraw(199)
</pre>
<p>また、DBSession.query_propertyで、そのクラスのクエリを生成するプロパティを作成できます。(上記の例でも定義してあります。):</p>
<pre class="literal-block">
BankAccount.query.filter(BankAccount.balance>=50).all()
</pre>
</div>
</div>
<div class="section" id="id4">
<h2>プラクティス</h2>
<div class="section" id="id5">
<h3>テストとかの注意</h3>
<p>view内でcommitすると、テスト中でもデータベースが必要になります。
また、テスト後にrollbackしてもテストで作成したデータが消えないため他のテストに影響がでます。</p>
<p>SQLAlchemyは、SessionがUnitOfWorkパターンを用いて更新情報を管理しているので、モデルの状態変更だけを気にするようにしましょう。</p>
<p>commit,rolbackは、wsgiミドルウェアやpyramidのtweenなどの仕組みを活用します。
zope.sqlalchemyのZopeTransactionExtensionを使うと、repoze.tm2やpyramid_tmでトランザクション管理ができます。
SQLAlchemyだけでよい場合は以下のようなwsgiミドルウェアが利用できます。:</p>
<pre class="literal-block">
class SQLATransactionMiddleware(object):
def __init__(self, app, dbsession):
self.app = app
self.dbsession = dbsession
@wsgify
def __call__(self, request):
try:
request.get_response(self.app)
self.dbsession.commit()
except Exception as e:
self.dbsession.abort()
six.reraise()
finally:
self.dbsession.remove()
</pre>
</div>
<div class="section" id="primaryjoinsecondaryjoin">
<h3>primaryjoinやsecondaryjoinの条件を活用する</h3>
<p>relationshipではForeignKeyを自動で結合条件として認識しますが、primaryjoinやsecondaryjoinを使ってさらに条件を追加できます。
たとえば、BlogとBlogEntryのような関係があった場合に、publicフラグがTrueのものだけを必要とする場合を考えてみましょう。:</p>
<pre class="literal-block">
class Blog(Base):
__tablename__ = 'blogs'
id = Column(Integer, primary_key=True)
title = Column(UnicodeText)
entries = relationship('BlogEntry', backref="blog")
public_entries = relationship('BlogEntry', primaryjoin='and_(Blog.id==BlogEntry.blog_id, BlogEntry.published!=None)')
class BlogEntry(Base):
__tablename__ = 'blog_entries'
id = Column(Integer, primary_key=True)
title = Column(UnicodeText)
published = Column(DateTime)
blog_id = Column(Integer, ForeignKey('blogs.id'))
</pre>
<p>entriesにはprimaryjoinがないので、 BlogEntryのblog_idの外部キーを自動で条件に採用します。
public_entriesでは、primaryjoinで条件を指定しています。blog_idの条件およびpublishedが設定されているものが、public_entriesで取得されるようになります。</p>
</div>
<div class="section" id="id6">
<h3>1テーブル 1クラス という妄想を捨てる</h3>
<p>SQLAlchemyはデータマッパーなので、複数テーブル複数クラスなどのマッピングもできます。(Djang○ ORM とは違うのだよ)
declarative_baseを使っているとクラス内でテーブル構成を定義するのでついつい1テーブル1クラスという考えになってしまいがちです。
複数テーブルを結合した集計サマリーなどのクエリをviewに書くような原始的な開発はもうやめましょう。</p>
<pre class="literal-block">
class Customer(Base):
__tablename__ = 'customers'
id = Column(Integer, primary_key=True)
name = Column(UnicodeText)
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
amount = Column(Integer)
customer_id = Column(Integer, ForeignKey('customers.id'))
customer = relationship('Customer', backref="orders")
order_summary = select([func.sum(Order.__table__.c.amount).label('amount'),
Order.__table__.c.customer_id]
).group_by(
Order.__table__.c.customer_id
).alias()
class CustomerOrder(Base):
__table__ = Customer.__table__.join(order_summary)
</pre>
<p>CustomerOrder は __tablename__ ではなく __table__を使っています。
このクラスには、customersと、orderを集計した結果を結合したものをマッピングしています。
喜んでください!みなさんが大好きな宣言的な書き方ですよ!</p>
</div>
</div>
Pyramid Testing2013-09-05T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-05:pyramid-testing.html<p>さて、もう間近にせまった <a class="reference external" href="http://apac-2013.pycon.jp/">PyCon Apac 2013</a> ですが、
わたくし <a class="reference external" href="http://apac-2013.pycon.jp/ja/program/sessions.html#session-14-1110-rooma0715-ja1-ja">パッケージングの今と未来</a> にて登壇します。</p>
<p>ところで、これ以外にもCFPを提出していまして、没ネタが2つほどあります。</p>
<ul class="simple">
<li>sqlalchemyの錬金術</li>
<li>testing pyramid</li>
</ul>
<p>(´・ω・`)実際のとこ、pyramidの話したかったんですけどねぇ....</p>
<div class="section" id="pyramid">
<h2>Pyramidでのテスト</h2>
<p>まずはpylonsprojectのドキュメント <a class="reference external" href="http://docs.pylonsproject.org/en/latest/community/testing.html">Unit Testing Guidelines</a> があります。
これはテストのテクニックではなく作法ですが、テスト内容を明確にするというのが重要な点かなと思います。</p>
<p>Pyramidなところはそんなとこかもしれません。
どこぞのガラパゴスな進化をしたフレームワークと違い、モダンなテストツールをそのまま使えます。(Pythonのフレームワークなのだから当たり前ですけども)</p>
</div>
<div class="section" id="id2">
<h2>テストケース</h2>
<p>pytestやnoseなどはディスカバリー機能を持ってるので、 <tt class="docutils literal">test_*</tt> といった名前の関数でよいですが、伝統的な <tt class="docutils literal">unittest</tt> フレームワークでは、 <tt class="docutils literal">TestCase</tt> クラスを継承します。
フィクスチャとしてよく用いられるダミークラスは <tt class="docutils literal">pyramid.testing</tt> に用意されています。
たとえば、ビュー関数のテストをする場合は <tt class="docutils literal">pyramid.testing.DummyRequest</tt> で <tt class="docutils literal">request</tt> オブジェクトを作ってビュー関数に渡します。</p>
<pre class="literal-block">
def index(request):
return dict(message=request.params['message'])
</pre>
<pre class="literal-block">
from pyramid import testing
def test_index():
request = testing.DummyRequest(params=dict(message="Hello"))
result = index(request)
eq_(result, dict(message='Hello'))
</pre>
<p>ああ、まったく普通のテストですね。</p>
</div>
<div class="section" id="id3">
<h2>データベース関連のテスト</h2>
<p>とりあえず <a class="reference external" href="http://www.zodb.org/en/latest/">ZODB</a> 使う場合はなにも迷うことはありません。
オブジェクトなので、なにも気にせず単なるクラスとしてテストしましょう。
トランザクションや永続化のようなものは、Persistentクラスやtransactionが面倒を見てくれます。</p>
<p><a class="reference external" href="http://www.sqlalchemy.org/">SQLAlchemy</a> を使う場合もたいていは永続化層のことは考えずにすみます。
zope.sqlalchemyとtransactionによって制御されるので、アプリケーションコードで明示的なcommitなどはしないようにしましょう。
sqlalchemyのsessionから取り出されたオブジェクトは状態が更新された場合に自動でUPDATEを行います。
新規に作成された場合は自動でINSERTされず、sessionに参加させねばなりません。
方法は2通りで、 session.add(obj) で明示的に追加する方法と、既にsessionに参加しているオブジェクトと関連付けします。</p>
<p>pyramidはroute(URLパターンとかそういうやつ)に factory を設定でき、factoryの結果を requestが contextとして持っています。
最初のモデルのロードを factory で行えば、新規オブジェクトは context に追加するだけでよいので、ビュー内でsessionを使う必要はなくなります。</p>
<pre class="literal-block">
def load_site(request):
try:
return Site.query.filter(Site.name=='default').first()
except NoResultFound:
raise HTTPNotFound
config.add_route('top', '/', factory=load_site)
@view_config(route_name='top', method='POST')
def new_document(request):
# 入力チェックなど
document = Document(**request.params)
request.context.documents.append(document)
return HTTPFound(request.route_url('document', name=document.name)
</pre>
<p><cite>load_site</cite> のテストはデータベースが必要です。
しかし、 <cite>new_document</cite> のユニットテストでは、データベースは必要ありません。</p>
</div>
<div class="section" id="id4">
<h2>機能テスト</h2>
<p>pyramidはwsgiフレームワークなので、 <cite>WebTest</cite> を使ってテストできます。
wsgiミドルウェアを使う場合は、PasteDeployなどを使ってミドルウェア適用済のwsgiアプリをロードしてから、 <cite>TestApp</cite> を作成しますが、
pyramidは最近あまりwsgiミドルぅエアを使わない傾向にあります。
その場合は単純に、pyramid.config.Configurator.make_wsgi_app を呼ぶだけです。多くの場合、アプリケーションのmain関数を呼ぶだけでしょう。</p>
<pre class="literal-block">
def test_app():
app = your.app.main({}, **test_settings)
app = webtest.TestApp(app)
res = app.get('/')
res.form['name'] = 'test-document'
res.form['data'] = """\
This is First Document
----------------------------------
test
"""
res = res.form.submit()
eq_(res.location, 'http://example.com/test-document')
res = app.get(res.location)
# ...
</pre>
</div>
<div class="section" id="id5">
<h2>まとめ</h2>
<p>(´・ω・`)というわけで、pyramid特有となると、context のところくらいしかありませんでした。
餅は餅屋なので、テストツールが提示している書き方をすれば十分ですね。</p>
</div>
Pelicanはじめました2013-09-04T00:00:00+02:00aodagtag:pelican.aodag.jp,2013-09-04:pelicanhazimemashita.html<p><a class="reference external" href="http://d.hatena.ne.jp/aodag/">はてなダイアリー</a> , <a class="reference external" href="http://blog.aodag.jp">Blogger</a> そして <a class="reference external" href="http://aodag.posthaven.com/">Posterous -> Posthaven</a> と乗り継いでますが、
なんとなくpelican はじめてみました。</p>
<p>書く内容は多分あまり変わらないはずです。</p>