Download - Pyramid入門
Pyramid
I 特徴
I WebアプリケーションフレームワークI ドキュメント、テストがしっかりしてるコミュニティ
I 長所
I 押しつけがない
I フレームワーク自体が拡張可能
I コンポーネントで整理された実装と API
I 短所
I 押し付けがない
I フルスタックでないので各種ライブラリの知識が必要
I 柔軟性の高さは諸刃の剣でもある
pyramidの歴史
I repoze.bfg 0.1 2008-07-08I repoze.bfg 1.0 2009-07-05I repoze.bfg 1.3b1 2010-10-25I pyramid 1.0a1 2010-11-05I pyramid 1.0 2011-01-30I pyramid 1.1 2011-07-22I pyramid 1.2 2011-09-12I pyramid 1.3 2012-03-21I pyramid 1.4 2012-12-18I pyramid 1.5 2014-05-31
Hello Pyramid
from pyramid.config import Configuratorfrom 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 = bodyreturn 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_routeI Webアプリケーションで扱う URLパターンを登録する
I config.scanI view_config などの venusianデコレータのコールバックを実行する
I view_configI scanのタイミングで viewを登録するI request_methodや route_nameなどの条件を指定
TODOLISTアプリケーションその1
I モデル
I TaskI 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_tmI transaction を利用した包括的なトランザクション管理I エラー発生時はロールバック
I pyramid_sqlalchemyI zope.sqlalchemyI 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_nameI request_methodI 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.paramsI request.route_urlI 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 HTTPNotFoundlocation = 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 jsonI string
I ライブラリでサポート
I pyramid_makoI pyramid_chameleonI pyramid_jinja2I など
pyramid_mako
pip install pyramid_mako
config.include("pyramid_mako")
I pyramid_makoI makoテンプレートを使った rendererI 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
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 = taskself.request = request
def finish(self):self.task.finish()
@propertydef 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/@@finishI Route: tasks にマッチ
I route_name: tasksI todolist_id: 1I task_id: 2I travarse: @@finish
I tasks routeの factoryである task_factory が呼び出されるI TaskResourceインスタンスがリソースとして作成されるI URLの残りが @@finish となりトラバーサル終了I ビュー名 finishI task_finish が呼び出される
Deform/Colanderでフォーム作成
I ColanderI スキーマ、バリデーションライブラリ
I DeformI フォームライブラリ
I PeppercornI HTMLフォームで構造化したデータを扱うためのパーサー
Deform/Colander/peppercornの動作
I appstruct, pstruct, cstructI アプリケーションモデルなどを appstruct にして渡すI deformが appstructをフォームウィジェットとともに HTMLにする
I ブラウザから submitされるとパラメータは pstructで渡されてくる
I peppercoronで pstructを cstructに変換I colanderで cstructを appstructに変換
Colander スキーマ
import colander as cimport deform.widget as w
class TodolistSchema(c.Schema):name = c.SchemaNode(c.String())description = c.SchemaNode(c.String(),
widget=w.RichTextWidget())
I SchemaクラスI SchemaNodeI widget
I deformへのヒントとしてウィジェットを設定するI 抽象的なスキーマ情報にこういう詳細が入るのはちょっとや
だ (´・ω・`)
pyramid_deform
@view_config(....)class TodolistForm(FormView):
schema = TodolistSchema()buttons = ('save',)@propertydef 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',)
@propertydef context(self):
return self.request.context
def apptsruct(self):return self.context.appstruct()
def save_success(self, values):todolist = self.contexttodolist.update(**values)return HTTPFound(todolist.url)
appstructclass TodolistResource(object):
...
def appstruct(self):return dict(name=self.todolist.name,
description=self.todolist.description)
I ビューのメソッドで詳細に実装したくない
I contextに委譲I FormViewはなぜか contextを持ってないので request経由で取得
I appstructI 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 ValidationFailureI 入力チェックの例外オブジェクト
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')}"><scriptsrc="${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 などが必要
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_policyI アクセスしているユーザーが誰なのか? を判定する方法
I authorization_policyI アクセスするユーザーは何ができるのか? を判定する方法
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.rememberI authentication_policy に対して、identity(ログインユーザーなど)を記録する (ログイン)
I security.forgetI authentication_policy に対して、identityを消去する (ログアウト)
Userの判定
def authenticate(request, username, password):user = User.query.filter(
User.username == username).first()if not user:
returnif not user.validate_password(password):
returnreturn user
pyramidの json対応
I request.json_bodyI リクエストから直接 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_bodyvalues = 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 Defending Pyramid’s DesignI http://docs.pylonsproject.org/projects/pyra-
mid/en/1.5-branch/designdefense.htmlI Pyramid Documentation
I http://docs.pylonsproject.org/en/latest/docs/pyramid.htmlI Deform
I http://deform.readthedocs.org/en/latest/I Colander
I http://colander.readthedocs.org/en/latestI pyramid_sqlalchemy
I http://pyramid-sqlalchemy.readthedocs.org/en/latest/I pyramid_tm
I http://pyramid-tm.readthedocs.org/en/latest/