Pyramid 入門 
aodag 
September 15, 2014
お前誰よ 
I aodag 
I Atsushi ODAGiri 
I 所属 
I ビープラウド 
I Pylonsproject.jp
Pyramid 
I 特徴 
I Web アプリケーションフレームワーク 
I ドキュメント、テストがしっかりしてるコミュニティ 
I 長所 
I 押しつけがない 
I フレームワーク自体が拡張可能 
I コンポーネントで整理された実装とAPI 
I 短所 
I 押し付けがない 
I フルスタックでないので各種ライブラリの知識が必要 
I 柔軟性の高さは諸刃の剣でもある
使う理由 
I SQLAlchemy と一緒に 
I 認証方法や権限設定などがアプリケーションに絡んで複雑 
I 保存先がDB でない場合などフルスタックがいらない 
I アレがきらいだから
pyramid の歴史 
I repoze.bfg 0.1 2008-07-08 
I repoze.bfg 1.0 2009-07-05 
I repoze.bfg 1.3b1 2010-10-25 
I pyramid 1.0a1 2010-11-05 
I pyramid 1.0 2011-01-30 
I pyramid 1.1 2011-07-22 
I pyramid 1.2 2011-09-12 
I pyramid 1.3 2012-03-21 
I pyramid 1.4 2012-12-18 
I pyramid 1.5 2014-05-31
インストール 
$ pyvenv.py .venv 
$ . .venv/bin/activate 
(.venv)$ pip install pyramid 
I pyvenv 
I pip
Hello Pyramid 
from pyramid.config import Configurator 
from pyramid_view import view_config 
@view_config(route_name="hello") 
def hello(request): 
name = request.matchdict["name"] 
body = "Hello {name}".format(name=name) 
request.response.text = body 
return request.response 
def main(global_conf, **settings): 
config = Configurator(settings=settings) 
config.add_route("hello", "/hello/{name}") 
config.scan(".") 
return config.make_wsgi_app()
Hello Pyramid 
I config.add_route 
I Web アプリケーションで扱うURL パターンを登録する 
I config.scan 
I view_config などのvenusian デコレータのコールバックを実 
行する 
I view_config 
I scan のタイミングでview を登録する 
I request_method やroute_name などの条件を指定
TODOLIST アプリケーションその1 
I モデル 
I Task 
I name 
I 機能 
I タスク一覧 
I Task をすべて表示する 
I タスク完了のボタンを表示する 
I タスク追加のフォームを表示する 
I タスク追加 
I Task を作成して、タスク一覧に戻る 
I タスク終了(削除) 
I Task を削除して、タスク一覧に戻る
SQLAlchemy を使う 
pip install pyramid_tm pyramid_sqlalchemy 
config.include("pyramid_tm") 
config.include("pyramid_sqlalchemy") 
I pyramid_tm 
I transaction を利用した包括的なトランザクション管理 
I エラー発生時はロールバック 
I pyramid_sqlalchemy 
I zope.sqlalchemy 
I sqlalchemy.url
Route の登録 
I add_route 
# タスク一覧、タスク作成 
config.add_route("top", "/") 
# タスク削除 
config.add_route( 
"task_finish", 
"/tasks/{task_id}/finish")
ビュー:タスク一覧 
@view_config(route_name="top", 
request_method="GET", 
renderer="templates/index.mako") 
def index(request): 
tasks = Task.query.all() 
return dict(tasks=tasks) 
I route_name 
I request_method 
I renderer
ビュー:タスク作成 
@view_config(route_name="top", 
request_method="POST") 
def create_task(request): 
name = request.params["name"] 
task = Task(name=name) 
DBSession.add(task) 
location = request.route_url("top") 
return HTTPFound(location) 
I request.params 
I request.route_url 
I HTTPFound
ビュー:タスク削除 
@view_config(route_name="task_finish", 
request_method="POST", 
renderer="templates/index.mako") 
def delete_task(request): 
task_id = request.matchdict["task_id"] 
task = Task.query.filter(Task.id == task_id).first() 
if task is None: 
raise HTTPNotFound 
location = request.route_url("top") 
return HTTPFound(location) 
I request.matchdict
Pyramid で使えるテンプレート 
I Pyramid1.0 から1.4 までmako, chameleon サポートが 
Pyramid 自体に含まれていた 
I Pyramid1.5 以降で標準サポートはstring, json のみ 
I 標準サポート 
I json 
I string 
I ライブラリでサポート 
I pyramid_mako 
I pyramid_chameleon 
I pyramid_jinja2 
I など
pyramid_mako 
pip install pyramid_mako 
config.include("pyramid_mako") 
I pyramid_mako 
I mako テンプレートを使ったrenderer 
I include すると“.mako” 拡張子で指定したレンダラーでmako 
テンプレートを利用できる
テンプレート:タスク一覧 
<ul> 
%for task in tasks: 
<li> 
${task.name} 
<form action="${request.route_url('task_finish', 
task_id=task.id)}" 
method="post"> 
<button type="submit">Finish</button> 
</form> 
</li> 
%endfor 
</ul>
テンプレート:タスク作成フォーム 
<form action="${request.route_url('top')}" method="post"> 
<input type="text" name="name"> 
<button type="submit">Add</button> 
</form>
TODOLIST アプリケーションその2
TODOLIST アプリケーションその2 
I Route とTraversal とView を活用する 
I アプリケーションのロジックはリソースで行うようにする 
I CSS などを適用 
I deform でフォームを作る 
I Task をまとめるTodoList モデル 
I ビューであれこれやらないようにする
Pyramid のView が呼ばれるまで 
I パターンマッチによりroute を決定 
I route に設定されたfactory でresource を作成 
I matchdict にtraverse がある場合はresource をトラバース 
I 残りのURL を消費しきる 
I リソースが__getitem__を持っていない 
I 残りのURL が@@ で始まる(ビュー名) 
I トラバース結果がcontext となる 
I route, context, request method などの条件からview を決定 
I view を呼び出す
route とresource factory 
config.add_route( 
"task", 
"/todolists/{todolist_id}/tasks/{task_id}/*traverse", 
factory=".resources.task_factory") 
I factory
resource factory 
def task_factory(request): 
todolist_id = request.matchdict["todolist_id"] 
task_id = request.matchdict["task_id"] 
task = Task.query.filter( 
Task.id == task_id, 
Task.todolist_id == todolist_id).first() 
if taks is None: 
raise HTTPNotFound 
return TaskResource(task, request)
アダプター 
class TaskResource(object): 
def __init__(self, task, request): 
self.task = task 
self.request = request 
def finish(self): 
self.task.finish() 
@property 
def todolist_url(self): 
return self.request.route_url( 
"todolist", 
todolist_id=self.task.todolist_id) 
I task ラップする対象 
I request API 呼び出しのために必要
view 
@view_config( 
route_name="task", 
name="finish", 
context=".reources.TaskResource", 
request_method="POST") 
def task_finish(context, request): 
context.finish() 
return HTTPFound(location=context.todolist_url) 
I view はコンテキストにイベントを伝える(メソッドを呼び出 
す)だけ
ビューが呼び出されるまで 
I URL: /todolist/1/tasks/2/@@finish 
I Route: tasks にマッチ 
I route_name: tasks 
I todolist_id: 1 
I task_id: 2 
I travarse: @@finish 
I tasks route のfactory であるtask_factory が呼び出される 
I TaskResource インスタンスがリソースとして作成される 
I URL の残りが@@finish となりトラバーサル終了 
I ビュー名finish 
I task_finish が呼び出される
Deform/Colander でフォーム作成 
I Colander 
I スキーマ、バリデーションライブラリ 
I Deform 
I フォームライブラリ 
I Peppercorn 
I HTML フォームで構造化したデータを扱うためのパーサー
Deform/Colander/peppercorn の動作 
I appstruct, pstruct, cstruct 
I アプリケーションモデルなどをappstruct にして渡す 
I deform がappstruct をフォームウィジェットとともにHTML 
にする 
I ブラウザからsubmit されるとパラメータはpstruct で渡され 
てくる 
I peppercoron でpstruct をcstruct に変換 
I colander でcstruct をappstruct に変換
Colander スキーマ 
import colander as c 
import deform.widget as w 
class TodolistSchema(c.Schema): 
name = c.SchemaNode(c.String()) 
description = c.SchemaNode(c.String(), 
widget=w.RichTextWidget()) 
I Schema クラス 
I SchemaNode 
I widget 
I deform へのヒントとしてウィジェットを設定する 
I 抽象的なスキーマ情報にこういう詳細が入るのはちょっとや 
だ(´・ω・`)
pyramid_deform 
@view_config(....) 
class TodolistForm(FormView): 
schema = TodolistSchema() 
buttons = ('save',) 
@property 
def context(self): 
return self.request.context 
def save_success(self, values): 
todolist = self.context.add_todolist(**values) 
return HTTPFound(todolist.url) 
I FormView を継承してビューを実装する 
I schema でcolander スキーマを指定 
I buttons でボタンの名前(‘save’) を設定しておくと、対応する 
メソッド(save_success) がバリデーション後に呼び出される
フォームのデフォルト値 
class EditTodolistForm(FormView): 
schema = TodolistSchema() 
buttons = ('save',) 
@property 
def context(self): 
return self.request.context 
def apptsruct(self): 
return self.context.appstruct() 
def save_success(self, values): 
todolist = self.context 
todolist.update(**values) 
return HTTPFound(todolist.url)
appstruct 
class TodolistResource(object): 
... 
def appstruct(self): 
return dict(name=self.todolist.name, 
description=self.todolist.description) 
I ビューのメソッドで詳細に実装したくない 
I context に委譲 
I FormView はなぜかcontext を持ってないのでrequest 経由で 
取得 
I appstruct 
I deform はフォームの値をdict(appstruct) で受け取る 
I appstruct はdeform によってpeppercorn が解釈可能なパラ 
メータ(pstruct) をsubmit するフォームとなるようにレンダ 
リングされる
pyramid_deform のAPI はあまりきれいじゃない 
I 継承ベース 
I あまり大きく動作を変えられない 
I メソッドオーバーライドによる穴埋め 
I フォームバリデーション以外のデータを扱えない 
I たとえばDB アクセスして重複チェックなどした場合のエ 
ラーとかきれいに表示できない 
I あまり多くを望まないように 
I 単純なマスタデータ入力以上はできないと思ったほうがよい
deform の扱い 
form = deform.Form(TodolistSchema(), buttons=('save',)) 
controls = request.params.items() 
try: 
params = form.deserialize(controls) 
except ValidationFailure as e: 
return dict(form=e) 
I request.params.items() 
I peppercorn はパラメータの順番が重要 
I ValidationFailure 
I 入力チェックの例外オブジェクト 
I エラー情報を含んだフォームをレンダリングする
static_view 
I CSS, JS などを取り扱うにはstatic_view を使う 
add_static_view("static", "my.todolist:static") 
I “static” はURL で使う名前 
I “my.todolist:static” はファイルパスここではasset 
specification 記法を使っている 
I add_static_view で登録したasset はテンプレートなどで 
static_url でURL を利用する 
I static_url の例 
<link rel="stylesheet" 
href="${request.static_url( 
'deform:static/css/bootstramp.min.css')}"> 
<script 
src="${request.static_url( 
'my.todolist:static/js/app.js')}"></script>
Asset Specification 
I “{package}:{directory}” のような文字列でパッケージ以下 
のディレクトリを表す 
I my.todolist:static は 
os.path.join(os.path.dirname(my.todolist.__file__), “static”) 
で取得できるディレクトリ
ベーステンプレート 
<html> 
<head> 
<link rel="stylesheet" 
href="${request.static_url( 
'deform:static/css/bootstramp.min.css')}"> 
<%block name="extra_header"></%block> 
</head> 
<body> 
<div class="container"> 
${next.body()} 
</div> 
</body> 
</html>
テンプレート 
<%inherit file="base.mako"> 
<%block name="extra_header> 
%for reqt in css_links: 
<link rel="stylesheet" 
href="${request.static_url(reqt)}" 
type="text/css" /> 
%endfor 
... 
</%block>
Mako テンプレート 
I block タグ 
I あとから継承先のテンプレートで埋める場所 
I next.body() 
I 直接継承しているテンプレートの内容をレンダリングする 
I レイアウトなど多段に継承するときに必要 
I inherit タグ 
I 継承テンプレートを指定 
I %for, %if, %while など 
I Python の各種制御構文と同じ 
I インデントブロックじゃないので、%endfor などが必要
TODOLIST アプリケーションその3 
I User モデルを追加 
I 認証、権限を追加
security 
def includeme(config): 
secret = config.registry.settings['session.secret'] 
session_factory = SignedCookieSessionFactory( 
secret=secret) 
config.set_session_factory(session_factory) 
authentication_policy = SessionAuthenticationPolicy() 
authorization_policy = ACLAuthorizationPolicy() 
config.set_authentication_policy( 
authentication_policy) 
config.set_authorization_policy( 
authorization_policy) 
config.set_forbidden_view(forbidden_view)
pyramid のセキュリティ機構 
I authentication_policy 
I アクセスしているユーザーが誰なのか? を判定する方法 
I authorization_policy 
I アクセスするユーザーは何ができるのか? を判定する方法
permission 
@view_config(route_name="top", 
permission="todolist.view", 
renderer="templates/index.mako") 
def index(context, request): 
return dict(todolists=context) 
I view ごとにpermission を決める 
I authorization policy で与えられたpermission がview の 
permission を含んでいれば、そのview を利用可能 
I 適切なpermission を得られなかった場合はforbidden_view が 
呼び出される
ACLAuthorizationPolicy 
class TodoListResource(object): 
... 
def __acl__(self): 
return [(Allow, self.todolist.user.username, 
'todolist.view'), 
(Allow, self.todolist.user.username, 
'todolist.edit'), 
(Allow, self.todolist.user.username, 
'task.create'), 
] 
I context の__acl__ プロパティでpermission を決定する 
I __acl__は、pyramid1.5 以降はメソッドでもよい 
I この場合はtodolist の持ち主ならtodolist.view などの 
permission を与えられる
ログインビュー 
@view_config(route_name="login", 
renderer="templates/login.mako") 
class LoginView(FormView): 
schema = LoginSchema() 
buttons = ('login',) 
def login_success(self, values): 
user = authenticate(self.request, 
values["username"], 
values["password"]) 
if not user: 
return 
headers = security.remember( 
self.request, user.username) 
res = HTTPFound(self.request.route_url("top"), 
headers=headers) 
return res
pyramid.security API 
I security.remember 
I authentication_policy に対して、identity(ログインユーザーな 
ど) を記録する(ログイン) 
I security.forget 
I authentication_policy に対して、identity を消去する(ログア 
ウト)
User の判定 
def authenticate(request, username, password): 
user = User.query.filter( 
User.username == username).first() 
if not user: 
return 
if not user.validate_password(password): 
return 
return user
TODOLIST アプリケーションその4 
I シングルページアプリケーションにする 
I リクエスト、レスポンスでJSON を取り扱う
pyramid のjson 対応 
I request.json_body 
I リクエストから直接json パースしたオブジェクトを受け取 
れる 
I json レンダラー 
I ビューが返すdict をjson にダンプしてくれる 
I オブジェクトが__json__ メソッドを持っているとダンプ中 
に呼び出される 
I xhr プリディケーション 
I view_config でxhr=True としておくと、jquery などのajax リ 
クエストはそちらのビューが優先して呼び出される
リソースに__json__ メソッドを追加する 
class TodoListResource(object): 
... 
def __json__(self, request): 
create_task_url = self.request.route_url( 
"tasks", todolist_id=self.id) 
return dict( 
tasks=self.unfinished_tasks, 
description=self.description, 
create_task_url=create_task_url)
colander でバリデーション 
@view_config(route_name="tasks", 
permission="task.create", 
renderer="json", 
xhr=True) 
def create_task(context, request): 
schema = TaskSchema() 
values = request.json_body 
values = schema.deserialize(values) 
task = context.add_task(name=values["name"]) 
return dict(task=task)
結局リソースとは 
I ビューとモデルの緩衝材 
I アプリケーションモデル 
I appstruct, __json__, __acl__ などフレームワークへのイン 
ターフェイスを提供する 
I アダプターパターン 
I すべてをリソースでやろうとしないように 
I なんでもありになりがち 
I コンストラクタで受け取った以上のオブジェクトを扱わない 
こと
まとめ 
I Pyramid はとても簡単 
I 強力なライブラリを活用できる 
I JSON アプリケーションのバックエンドとしても優秀 
I SQLAlchemy は、がんばって勉強してください
参考文献 
I Defending Pyramid’s Design 
I http://docs.pylonsproject.org/projects/pyra-mid/ 
en/1.5-branch/designdefense.html 
I Pyramid Documentation 
I http://docs.pylonsproject.org/en/latest/docs/pyramid.html 
I Deform 
I http://deform.readthedocs.org/en/latest/ 
I Colander 
I http://colander.readthedocs.org/en/latest 
I pyramid_sqlalchemy 
I http://pyramid-sqlalchemy.readthedocs.org/en/latest/ 
I pyramid_tm 
I http://pyramid-tm.readthedocs.org/en/latest/

Pyramid入門

  • 1.
    Pyramid 入門 aodag September 15, 2014
  • 2.
    お前誰よ I aodag I Atsushi ODAGiri I 所属 I ビープラウド I Pylonsproject.jp
  • 3.
    Pyramid I 特徴 I Web アプリケーションフレームワーク I ドキュメント、テストがしっかりしてるコミュニティ I 長所 I 押しつけがない I フレームワーク自体が拡張可能 I コンポーネントで整理された実装とAPI I 短所 I 押し付けがない I フルスタックでないので各種ライブラリの知識が必要 I 柔軟性の高さは諸刃の剣でもある
  • 4.
    使う理由 I SQLAlchemyと一緒に I 認証方法や権限設定などがアプリケーションに絡んで複雑 I 保存先がDB でない場合などフルスタックがいらない I アレがきらいだから
  • 5.
    pyramid の歴史 Irepoze.bfg 0.1 2008-07-08 I repoze.bfg 1.0 2009-07-05 I repoze.bfg 1.3b1 2010-10-25 I pyramid 1.0a1 2010-11-05 I pyramid 1.0 2011-01-30 I pyramid 1.1 2011-07-22 I pyramid 1.2 2011-09-12 I pyramid 1.3 2012-03-21 I pyramid 1.4 2012-12-18 I pyramid 1.5 2014-05-31
  • 6.
    インストール $ pyvenv.py.venv $ . .venv/bin/activate (.venv)$ pip install pyramid I pyvenv I pip
  • 7.
    Hello Pyramid frompyramid.config import Configurator from pyramid_view import view_config @view_config(route_name="hello") def hello(request): name = request.matchdict["name"] body = "Hello {name}".format(name=name) request.response.text = body return request.response def main(global_conf, **settings): config = Configurator(settings=settings) config.add_route("hello", "/hello/{name}") config.scan(".") return config.make_wsgi_app()
  • 8.
    Hello Pyramid Iconfig.add_route I Web アプリケーションで扱うURL パターンを登録する I config.scan I view_config などのvenusian デコレータのコールバックを実 行する I view_config I scan のタイミングでview を登録する I request_method やroute_name などの条件を指定
  • 9.
    TODOLIST アプリケーションその1 Iモデル I Task I name I 機能 I タスク一覧 I Task をすべて表示する I タスク完了のボタンを表示する I タスク追加のフォームを表示する I タスク追加 I Task を作成して、タスク一覧に戻る I タスク終了(削除) I Task を削除して、タスク一覧に戻る
  • 10.
    SQLAlchemy を使う pipinstall pyramid_tm pyramid_sqlalchemy config.include("pyramid_tm") config.include("pyramid_sqlalchemy") I pyramid_tm I transaction を利用した包括的なトランザクション管理 I エラー発生時はロールバック I pyramid_sqlalchemy I zope.sqlalchemy I sqlalchemy.url
  • 11.
    Route の登録 Iadd_route # タスク一覧、タスク作成 config.add_route("top", "/") # タスク削除 config.add_route( "task_finish", "/tasks/{task_id}/finish")
  • 12.
    ビュー:タスク一覧 @view_config(route_name="top", request_method="GET", renderer="templates/index.mako") def index(request): tasks = Task.query.all() return dict(tasks=tasks) I route_name I request_method I renderer
  • 13.
    ビュー:タスク作成 @view_config(route_name="top", request_method="POST") def create_task(request): name = request.params["name"] task = Task(name=name) DBSession.add(task) location = request.route_url("top") return HTTPFound(location) I request.params I request.route_url I HTTPFound
  • 14.
    ビュー:タスク削除 @view_config(route_name="task_finish", request_method="POST", renderer="templates/index.mako") def delete_task(request): task_id = request.matchdict["task_id"] task = Task.query.filter(Task.id == task_id).first() if task is None: raise HTTPNotFound location = request.route_url("top") return HTTPFound(location) I request.matchdict
  • 15.
    Pyramid で使えるテンプレート IPyramid1.0 から1.4 までmako, chameleon サポートが Pyramid 自体に含まれていた I Pyramid1.5 以降で標準サポートはstring, json のみ I 標準サポート I json I string I ライブラリでサポート I pyramid_mako I pyramid_chameleon I pyramid_jinja2 I など
  • 16.
    pyramid_mako pip installpyramid_mako config.include("pyramid_mako") I pyramid_mako I mako テンプレートを使ったrenderer I include すると“.mako” 拡張子で指定したレンダラーでmako テンプレートを利用できる
  • 17.
    テンプレート:タスク一覧 <ul> %fortask in tasks: <li> ${task.name} <form action="${request.route_url('task_finish', task_id=task.id)}" method="post"> <button type="submit">Finish</button> </form> </li> %endfor </ul>
  • 18.
    テンプレート:タスク作成フォーム <form action="${request.route_url('top')}"method="post"> <input type="text" name="name"> <button type="submit">Add</button> </form>
  • 19.
  • 20.
    TODOLIST アプリケーションその2 IRoute とTraversal とView を活用する I アプリケーションのロジックはリソースで行うようにする I CSS などを適用 I deform でフォームを作る I Task をまとめるTodoList モデル I ビューであれこれやらないようにする
  • 21.
    Pyramid のView が呼ばれるまで I パターンマッチによりroute を決定 I route に設定されたfactory でresource を作成 I matchdict にtraverse がある場合はresource をトラバース I 残りのURL を消費しきる I リソースが__getitem__を持っていない I 残りのURL が@@ で始まる(ビュー名) I トラバース結果がcontext となる I route, context, request method などの条件からview を決定 I view を呼び出す
  • 22.
    route とresource factory config.add_route( "task", "/todolists/{todolist_id}/tasks/{task_id}/*traverse", factory=".resources.task_factory") I factory
  • 23.
    resource factory deftask_factory(request): todolist_id = request.matchdict["todolist_id"] task_id = request.matchdict["task_id"] task = Task.query.filter( Task.id == task_id, Task.todolist_id == todolist_id).first() if taks is None: raise HTTPNotFound return TaskResource(task, request)
  • 24.
    アダプター class TaskResource(object): def __init__(self, task, request): self.task = task self.request = request def finish(self): self.task.finish() @property def todolist_url(self): return self.request.route_url( "todolist", todolist_id=self.task.todolist_id) I task ラップする対象 I request API 呼び出しのために必要
  • 25.
    view @view_config( route_name="task", name="finish", context=".reources.TaskResource", request_method="POST") def task_finish(context, request): context.finish() return HTTPFound(location=context.todolist_url) I view はコンテキストにイベントを伝える(メソッドを呼び出 す)だけ
  • 26.
    ビューが呼び出されるまで I URL:/todolist/1/tasks/2/@@finish I Route: tasks にマッチ I route_name: tasks I todolist_id: 1 I task_id: 2 I travarse: @@finish I tasks route のfactory であるtask_factory が呼び出される I TaskResource インスタンスがリソースとして作成される I URL の残りが@@finish となりトラバーサル終了 I ビュー名finish I task_finish が呼び出される
  • 27.
    Deform/Colander でフォーム作成 IColander I スキーマ、バリデーションライブラリ I Deform I フォームライブラリ I Peppercorn I HTML フォームで構造化したデータを扱うためのパーサー
  • 28.
    Deform/Colander/peppercorn の動作 Iappstruct, pstruct, cstruct I アプリケーションモデルなどをappstruct にして渡す I deform がappstruct をフォームウィジェットとともにHTML にする I ブラウザからsubmit されるとパラメータはpstruct で渡され てくる I peppercoron でpstruct をcstruct に変換 I colander でcstruct をappstruct に変換
  • 29.
    Colander スキーマ importcolander as c import deform.widget as w class TodolistSchema(c.Schema): name = c.SchemaNode(c.String()) description = c.SchemaNode(c.String(), widget=w.RichTextWidget()) I Schema クラス I SchemaNode I widget I deform へのヒントとしてウィジェットを設定する I 抽象的なスキーマ情報にこういう詳細が入るのはちょっとや だ(´・ω・`)
  • 30.
    pyramid_deform @view_config(....) classTodolistForm(FormView): schema = TodolistSchema() buttons = ('save',) @property def context(self): return self.request.context def save_success(self, values): todolist = self.context.add_todolist(**values) return HTTPFound(todolist.url) I FormView を継承してビューを実装する I schema でcolander スキーマを指定 I buttons でボタンの名前(‘save’) を設定しておくと、対応する メソッド(save_success) がバリデーション後に呼び出される
  • 31.
    フォームのデフォルト値 class EditTodolistForm(FormView): schema = TodolistSchema() buttons = ('save',) @property def context(self): return self.request.context def apptsruct(self): return self.context.appstruct() def save_success(self, values): todolist = self.context todolist.update(**values) return HTTPFound(todolist.url)
  • 32.
    appstruct class TodolistResource(object): ... def appstruct(self): return dict(name=self.todolist.name, description=self.todolist.description) I ビューのメソッドで詳細に実装したくない I context に委譲 I FormView はなぜかcontext を持ってないのでrequest 経由で 取得 I appstruct I deform はフォームの値をdict(appstruct) で受け取る I appstruct はdeform によってpeppercorn が解釈可能なパラ メータ(pstruct) をsubmit するフォームとなるようにレンダ リングされる
  • 33.
    pyramid_deform のAPI はあまりきれいじゃない I 継承ベース I あまり大きく動作を変えられない I メソッドオーバーライドによる穴埋め I フォームバリデーション以外のデータを扱えない I たとえばDB アクセスして重複チェックなどした場合のエ ラーとかきれいに表示できない I あまり多くを望まないように I 単純なマスタデータ入力以上はできないと思ったほうがよい
  • 34.
    deform の扱い form= deform.Form(TodolistSchema(), buttons=('save',)) controls = request.params.items() try: params = form.deserialize(controls) except ValidationFailure as e: return dict(form=e) I request.params.items() I peppercorn はパラメータの順番が重要 I ValidationFailure I 入力チェックの例外オブジェクト I エラー情報を含んだフォームをレンダリングする
  • 35.
    static_view I CSS,JS などを取り扱うにはstatic_view を使う add_static_view("static", "my.todolist:static") I “static” はURL で使う名前 I “my.todolist:static” はファイルパスここではasset specification 記法を使っている I add_static_view で登録したasset はテンプレートなどで static_url でURL を利用する I static_url の例 <link rel="stylesheet" href="${request.static_url( 'deform:static/css/bootstramp.min.css')}"> <script src="${request.static_url( 'my.todolist:static/js/app.js')}"></script>
  • 36.
    Asset Specification I“{package}:{directory}” のような文字列でパッケージ以下 のディレクトリを表す I my.todolist:static は os.path.join(os.path.dirname(my.todolist.__file__), “static”) で取得できるディレクトリ
  • 37.
    ベーステンプレート <html> <head> <link rel="stylesheet" href="${request.static_url( 'deform:static/css/bootstramp.min.css')}"> <%block name="extra_header"></%block> </head> <body> <div class="container"> ${next.body()} </div> </body> </html>
  • 38.
    テンプレート <%inherit file="base.mako"> <%block name="extra_header> %for reqt in css_links: <link rel="stylesheet" href="${request.static_url(reqt)}" type="text/css" /> %endfor ... </%block>
  • 39.
    Mako テンプレート Iblock タグ I あとから継承先のテンプレートで埋める場所 I next.body() I 直接継承しているテンプレートの内容をレンダリングする I レイアウトなど多段に継承するときに必要 I inherit タグ I 継承テンプレートを指定 I %for, %if, %while など I Python の各種制御構文と同じ I インデントブロックじゃないので、%endfor などが必要
  • 40.
    TODOLIST アプリケーションその3 IUser モデルを追加 I 認証、権限を追加
  • 41.
    security def includeme(config): secret = config.registry.settings['session.secret'] session_factory = SignedCookieSessionFactory( secret=secret) config.set_session_factory(session_factory) authentication_policy = SessionAuthenticationPolicy() authorization_policy = ACLAuthorizationPolicy() config.set_authentication_policy( authentication_policy) config.set_authorization_policy( authorization_policy) config.set_forbidden_view(forbidden_view)
  • 42.
    pyramid のセキュリティ機構 Iauthentication_policy I アクセスしているユーザーが誰なのか? を判定する方法 I authorization_policy I アクセスするユーザーは何ができるのか? を判定する方法
  • 43.
    permission @view_config(route_name="top", permission="todolist.view", renderer="templates/index.mako") def index(context, request): return dict(todolists=context) I view ごとにpermission を決める I authorization policy で与えられたpermission がview の permission を含んでいれば、そのview を利用可能 I 適切なpermission を得られなかった場合はforbidden_view が 呼び出される
  • 44.
    ACLAuthorizationPolicy class TodoListResource(object): ... def __acl__(self): return [(Allow, self.todolist.user.username, 'todolist.view'), (Allow, self.todolist.user.username, 'todolist.edit'), (Allow, self.todolist.user.username, 'task.create'), ] I context の__acl__ プロパティでpermission を決定する I __acl__は、pyramid1.5 以降はメソッドでもよい I この場合はtodolist の持ち主ならtodolist.view などの permission を与えられる
  • 45.
    ログインビュー @view_config(route_name="login", renderer="templates/login.mako") class LoginView(FormView): schema = LoginSchema() buttons = ('login',) def login_success(self, values): user = authenticate(self.request, values["username"], values["password"]) if not user: return headers = security.remember( self.request, user.username) res = HTTPFound(self.request.route_url("top"), headers=headers) return res
  • 46.
    pyramid.security API Isecurity.remember I authentication_policy に対して、identity(ログインユーザーな ど) を記録する(ログイン) I security.forget I authentication_policy に対して、identity を消去する(ログア ウト)
  • 47.
    User の判定 defauthenticate(request, username, password): user = User.query.filter( User.username == username).first() if not user: return if not user.validate_password(password): return return user
  • 48.
    TODOLIST アプリケーションその4 Iシングルページアプリケーションにする I リクエスト、レスポンスでJSON を取り扱う
  • 49.
    pyramid のjson 対応 I request.json_body I リクエストから直接json パースしたオブジェクトを受け取 れる I json レンダラー I ビューが返すdict をjson にダンプしてくれる I オブジェクトが__json__ メソッドを持っているとダンプ中 に呼び出される I xhr プリディケーション I view_config でxhr=True としておくと、jquery などのajax リ クエストはそちらのビューが優先して呼び出される
  • 50.
    リソースに__json__ メソッドを追加する classTodoListResource(object): ... def __json__(self, request): create_task_url = self.request.route_url( "tasks", todolist_id=self.id) return dict( tasks=self.unfinished_tasks, description=self.description, create_task_url=create_task_url)
  • 51.
    colander でバリデーション @view_config(route_name="tasks", permission="task.create", renderer="json", xhr=True) def create_task(context, request): schema = TaskSchema() values = request.json_body values = schema.deserialize(values) task = context.add_task(name=values["name"]) return dict(task=task)
  • 52.
    結局リソースとは I ビューとモデルの緩衝材 I アプリケーションモデル I appstruct, __json__, __acl__ などフレームワークへのイン ターフェイスを提供する I アダプターパターン I すべてをリソースでやろうとしないように I なんでもありになりがち I コンストラクタで受け取った以上のオブジェクトを扱わない こと
  • 53.
    まとめ I Pyramidはとても簡単 I 強力なライブラリを活用できる I JSON アプリケーションのバックエンドとしても優秀 I SQLAlchemy は、がんばって勉強してください
  • 54.
    参考文献 I DefendingPyramid’s Design I http://docs.pylonsproject.org/projects/pyra-mid/ en/1.5-branch/designdefense.html I Pyramid Documentation I http://docs.pylonsproject.org/en/latest/docs/pyramid.html I Deform I http://deform.readthedocs.org/en/latest/ I Colander I http://colander.readthedocs.org/en/latest I pyramid_sqlalchemy I http://pyramid-sqlalchemy.readthedocs.org/en/latest/ I pyramid_tm I http://pyramid-tm.readthedocs.org/en/latest/