Pyramid を試してみる

注意:このポストを書いた当時は出て数日で 1.0 どころではなかったので古い内容となってます。

などを参考にする事をお勧めします。


今の仕事で Flask でプロトタイプ(社内限定で公開)を書いてたけど、外にも公開したいと言われた。
作ったものはとりあえず動くレベルで外に公開するには諸々足らない。
Flask もエクステンションを組み合わせて作り上げれば出来るのは分かってるけど、時間がないので楽をしようと他の WAF にする事にした。


Django でも良かったんだけど、前から気になってたのと、SQLAlchemy, Jinja2 をほぼそのまま流用できるという理由で Pylons にした。
で、Github や Bitbucket で良い Pylons のサンプルが無いかと探していた時に、Pyramid というものを見つけた。
# ちょうどプロジェクトのページが出来て 2, 3 日しかたってなかった模様


やたらドキュメントが充実してるなと印象を持ってドキュメントをビルドしてみたら、repoze.bfg をリネームしただけ?と思ってた。
# 恥ずかしながら repoze.bfg はその時初めて知った。
で、昨日 @aodag さんが pyramid について色々ツイートされてて、それ経由で色々知った。
詳しくは Good night, Posterous 参照。
また、

との事なので、興味をもったので試してみる。

インストール&プロジェクト作成

チュートリアルをそのままやるのが普通なんだろうけど、折角なので簡単なアプリケーションを作ってみた。

$ pip install pyramid
$ pip install zope.sqlalchemy
$ pip install repoze.tm2 
$ paster create -t pyramid_routesalchemy microblog

SQLAlchemy を使ったサンプルとして作る。
paster のテンプレートには他にも色々ある。
routesalchemy と alchmey の違いは何なんだろう。
あと、Pylons みたいなクラスベースの view を作る事が出来るみたい。
# こっちはまた今度試す

設定ファイル

[DEFAULT]
debug = true

[app:sqlalchemy]
use = egg:microblog#app
reload_templates = true
debug_authorization = false
debug_notfound = false
debug_templates = true
default_locale_name = ja
db_string = sqlite:///%(here)s/microblog.db
db_echo = false

jinja2.directories = microblog:templates

[pipeline:main]
pipeline =
    egg:repoze.tm2#tm
    sqlalchemy

[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = 6543

モデルを作る

models.py

# -*- coding: utf-8 -*-
import datetime
from sqlalchemy import create_engine, Column, Integer, Unicode, Text, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker        
from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

class Microblog(Base):
    __tablename__ = 'microblog'
    id = Column(Integer, primary_key=True)
    title = Column(Unicode(255))
    text = Column(Text())
    created_at = Column(DateTime, default=datetime.datetime.now())

    def __init__(self, title, text):
        self.title = title
        self.text = text

    def __repr__(self):
        return "<Microblog('%s', '%s', '%s', '%s')>" % (self.id, self.title, self.text, self.created_at)

def initialize_sql(db_string, db_echo=False):
    engine = create_engine(db_string, echo=db_echo)
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)

sqlite3 で microblog というテーブルがあるという想定。

ルーティングの設定

__init__.py

# -*- coding: utf-8 -*-
from pyramid.configuration import Configurator
from paste.deploy.converters import asbool
from pyramid_jinja2 import renderer_factory
from microblog.models import initialize_sql

def app(global_config, **settings):
    """ This function returns a WSGI application.

    It is usually called by the PasteDeploy framework during
    ``paster serve``.
    """
    db_string = settings.get('db_string')
    if db_string is None:
        raise ValueError("No 'db_string' value in application configuration.")
    db_echo = settings.get('db_echo', 'false')
    initialize_sql(db_string, asbool(db_echo))
    config = Configurator(settings=settings)
    config.begin()

    config.add_renderer('.jinja2', renderer_factory)
    config.add_static_view('static', 'microblog:static')
    config.add_route('home', '/', view='microblog.views.index')
    config.add_route('add', '/add', view='microblog.views.add',
                     view_renderer='entries.jinja2')

    config.end()
    return config.make_wsgi_app()

Pylons でいうところの、environment.py にあたるのかな?

views を作る

views.py

# -*- coding: utf-8 -*-
import transaction
from pyramid.view import view_config
from pyramid.renderers import render
from pyramid.response import Response
from microblog.models import DBSession, Microblog

@view_config(renderer='.jinja2')
def index(request):
    dbsession = DBSession()
    entries = dbsession.query(Microblog).order_by(Microblog.id.desc()).all()
    result = render('entries.jinja2', {'entries': entries})
    DBSession.remove()

    return Response(result)
    
def add(request):
    requests = request.params
    title = requests['title'] 
    text = requests['text']
        
    dbsession = DBSession()
    model = Microblog(title=title, text=text)
    dbsession.add(model)
    dbsession.flush()
    transaction.commit()

    entries = dbsession.query(Microblog).order_by(Microblog.id.desc()).all()
    result = render('entries.jinja2', {'entries': entries})
    DBSession.remove()
    
    return Response(result)

/index でデータベースの内容を全て表示、/add で Post の内容を追加する。
/add の方にデコレータがないのは、__init__.py の方で設定できるみたい。

テンプレート

Flask のテンプレートを拝借。
layout.jinja2

<!doctype html>
<title>The Pyramid Web Application Development Framework</title>
<link rel="shortcut icon" href="{{request.application_url}}/static/favicon.ico" />
<link rel="stylesheet" href="{{request.application_url}}/static/style.css" type="text/css" media="screen" charset="utf-8" />
<body>
<div class="page">
  <h1>Pyramid</h1>
  {% block entries %}{% endblock %}
</div>
</body>
</html>

entries.jinja2

{% extends "layout.jinja2" %}
{% block entries %}
  <form action="/add" method="post">
    <dl>
      <dt>Title:</dt>
      <dd><input type="text" size="30" name="title"></dd>
      <dt>Text:</dt>
      <dd><textarea name="text" rows="5" cols="40"></textarea></dd>
      <dd><input type="submit" value="Share"></dd>
    </dl>
  </form>
  <ul class="entries">
  {% for entry in entries %}
    <li><h2>{{ entry.title }}</h2>{{ entry.text }}
  {% else %}
    <li><em>Unbelievable.  No entries here so far</em>
  {% endfor %}
  </ul>
{% endblock %}

色々作る過程ではまったけど、簡単なのは作れた。


1.0αだし、オフィシャルのドキュメント以外の情報がないので、仕事で使うにはまだ早いかな…。
# ドキュメントは充実している
Pylons 自体はメンテナンスモードに移行との事なので、新規に開発される事はなさそう。
これから色んな人が使って行って成熟していくまでは、Pylons を使うとしよう。
# マルチデータベースとか、テスト用のデータベース作成とかそこら辺
# 自分でゴリゴリ変更できるんだろうけど、そういうのはフレームワーク側でよろしくやって欲しい