pyramid入門

54
Pyramid 入門 aodag September 15, 2014

Upload: atsushi-odagiri

Post on 28-May-2015

4.596 views

Category:

Documents


8 download

TRANSCRIPT

Pyramid入門

aodag

September 15, 2014

お前誰よ

I aodagI 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-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

インストール

$ pyvenv.py .venv$ . .venv/bin/activate(.venv)$ pip install pyramid

I pyvenvI pip

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

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 などが必要

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_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

TODOLISTアプリケーションその 4

I シングルページアプリケーションにする

I リクエスト、レスポンスで JSONを取り扱う

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 Pyramidはとても簡単I 強力なライブラリを活用できる

I JSONアプリケーションのバックエンドとしても優秀I SQLAlchemyは、がんばって勉強してください

参考文献

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/