Pyramid katas¶
前提¶
- Python 3.4.3
環境作成¶
プロジェクト用の環境とpypaツールの準備
UNIX系
pyvenv .venv
. .venv/bin/activate
pip install -U pip
pip install -U setuptools
windows (powershell)
c:\python34\python c:\python34\tools\scripts\pyvenv.py .venv
.venv\scripts\activate.ps1
python -m pip install -U pip
python -m pip install -U setuptools
共通:
pip install wheel
pip wheel wheel
WSGI¶
waitressをインストール:
pip wheel -f wheelhouse waitress
pip install -f wheelhouse waitress
インストール確認:
waitress-serve -h
wsgiアプリケーション
def application(environ, start_response):
start_response("200 OK",
[('Content-type', 'text/plain')])
return [b"Hello, world!"]
Webアプリケーションを実行:
waitress-serve wsgiapp.application
webob¶
webobをインストール:
pip wheel webob -f wheelhouse
pip install webob -f wheelhouse
webobを使ったwsgiアプリケーション
from webob import Request, Response
def application(environ, start_response):
request = Request(environ)
response = Response(request=request)
response.text = "Hello, world!"
return response(environ, start_response)
webob.dec.wsgifyを使ったwsgiアプリケーション
from webob.dec import wsgify
@wsgify
def application(request):
request.response.text = "Hello, world!"
return request.response
pyramid¶
pyramidをインストール:
pip wheel -f wheelhouse pyramid
pip install -f wheelhouse pyramid
pyramidの最小アプリケーション
from pyramid.config import Configurator
def index(request):
request.response.text = "Hello, world!"
return request.response
config = Configurator()
config.add_view(index)
application = config.make_wsgi_app()
pyramidアプリケーションのモジュール化¶
- myapp1
- __init__.py
- wsgi.py
- views.py
__init__.py: アプリケーションのエントリポイント
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.scan()
return config.make_wsgi_app()
views.py: webアプリケーションビュー
from pyramid.view import view_config
@view_config()
def index(request):
request.response.text = "Hello, world!"
return request.response
wsgi.py: wsgiアプリケーションの生成
from . import main
settings = {
}
application = main({}, **settings)
URLディスパッチ¶
- myapp2
- __init__.py
- wsgi.py
- views.py
__init__.py: add_routeでURLパターン登録
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.add_route('top', '/')
config.add_route('user', '/users/{username}')
config.scan()
return config.make_wsgi_app()
views.py: view_configによるroute割り当て
from pyramid.view import view_config
@view_config(route_name="top")
def index(request):
request.response.text = "Hello, world!"
return request.response
@view_config(route_name="user")
def user(request):
username = request.matchdict["username"]
request.response.text = "Hello, {username}!".format(username=username)
return request.response
wsgi.pyはmyapp1と同じ
HTMLテンプレート¶
pyramid_jinja2をインストール:
pip wheel pyramid_jinja2 -f wheelhouse
pip install pyramid_jinja2 -f wheelhouse
- myapp3
- __init__.py
- wsgi.py
- views.py
- templates
- index.jinja2
- user.jinja2
__init__.py: pyramid_jinja2 を include
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.include("pyramid_jinja2")
config.add_route('top', '/')
config.add_route('user', '/users/{username}')
config.scan()
return config.make_wsgi_app()
views.py: view_configのrendererでテンプレートを指定
from pyramid.view import view_config
@view_config(route_name="top",
renderer='templates/index.jinja2')
def index(request):
return dict()
@view_config(route_name="user",
renderer='templates/user.jinja2')
def user(request):
username = request.matchdict["username"]
return dict(username=username)
ビューから渡された値(username) をテンプレート内で使用
Hello, {{ username }}!
データベースアクセス¶
pyramid_sqlalchemyとpyramid_tmのインストール:
pip wheel -f wheelhouse pyramid_sqlalchemy pyramid_tm
pip install -f wheelhouse pyramid_sqlalchemy pyramid_tm
- myapp4
- __init__.py
- wsgi.py
- models.py
- views.py
- templates
- index.jinja2
- user.jinja2
__init__.py: pyramid_sqlalchemy, pyramid_tm を include
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.include("pyramid_tm")
config.include("pyramid_sqlalchemy")
config.include("pyramid_jinja2")
config.add_route('top', '/')
config.add_route('user', '/users/{username}')
config.scan()
return config.make_wsgi_app()
wsgi.py: sqlalchemy.url にデータベース接続を設定
import os
from . import main
here = os.getcwd()
settings = {
'sqlalchemy.url': 'sqlite:///{here}/myapp4.sqlite'.format(here=here),
'sqlalchemy.echo': True,
}
application = main({}, **settings)
models.py: モデル定義
from sqlalchemy import (
Column,
Integer,
Unicode,
)
from pyramid_sqlalchemy import (
BaseObject,
Session,
)
class User(BaseObject):
__tablename__ = 'users'
query = Session.query_property()
id = Column(Integer, primary_key=True)
username = Column(Unicode(255), unique=True)
views.py: User.query でデータベースからモデルを取得
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPNotFound
from .models import User
@view_config(route_name="top",
renderer='templates/index.jinja2')
def index(request):
return dict()
@view_config(route_name="user",
renderer='templates/user.jinja2')
def user(request):
username = request.matchdict["username"]
user = User.query.filter(User.username == username).first()
if user is None:
raise HTTPNotFound
return dict(user=user)
user.jinja2: ビューから渡されたuserのプロパティアクセス
Hello, {{ user.username }}!
データベース、テーブル作成:
>>> from myapp4 import main
>>> import os
>>> here = os.getcwd()
>>> main({}, **{'sqlalchemy.url': 'sqlite:///{here}/myapp4.sqlite'.format(here=here)}
>>> from myapp4 import models
>>> models.BaseObject.metadata.create_all()
データ投入:
>>> user = models.User(username="aodag")
>>> models.Session.add(user)
>>> models.Session.flush()
>>> import transaction
>>> transaction.commit()
>>> models.User.query.all()
データ作成とフォーム¶
pyramid_deform colanderalchemy のインストール:
pip wheel -f wheelhouse "deform>=2.0dev" pyramid_deform colanderalchemy
- myapp5
- __init__.py
- wsgi.py
- models.py
- views.py
- templates
- index.jinja2
- new_user.jinja2
- users.jinja2
- user.jinja2
__init__.py: pyramid_deform を include
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.include("pyramid_tm")
config.include("pyramid_sqlalchemy")
config.include("pyramid_jinja2")
config.include("pyramid_deform")
config.add_route('top', '/')
config.add_route('users', '/users')
config.add_route('new_user', '/new_user')
config.add_route('user', '/users/{username}')
config.scan()
return config.make_wsgi_app()
views.py: FormViewを使ってdeformを使うビューを定義
from pyramid.view import view_config
from pyramid.httpexceptions import (
HTTPNotFound,
HTTPFound,
)
from pyramid_deform import FormView
from colanderalchemy import SQLAlchemySchemaNode
from .models import User
@view_config(route_name="top",
renderer='templates/index.jinja2')
def index(request):
return dict()
@view_config(route_name="user",
renderer='templates/user.jinja2')
def user(request):
username = request.matchdict["username"]
user = User.query.filter(User.username == username).first()
if user is None:
raise HTTPNotFound
return dict(user=user)
@view_config(route_name="users",
renderer="templates/users.jinja2")
def users(request):
users = User.query.all()
return dict(users=users)
@view_config(route_name="new_user",
renderer="templates/new_user.jinja2")
class NewUserForm(FormView):
schema = SQLAlchemySchemaNode(User,
excludes=['id'])
buttons = ('add',)
def add_success(self, values):
user = User(**values)
user.query.session.add(user)
return HTTPFound(self.request.route_url('users'))
new_user.jinja2: フォームの表示
{{ form|safe }}
レイアウトとスタイル¶
pyramid_layout のインストール:
pip wheel -f wheelhouse pyramid_layout
pip install -f wheelhouse pyramid_layout
- myapp6
- __init__.py
- wsgi.py
- models.py
- views.py
- layouts.py
- templates
- base.jinja2
- index.jinja2
- new_user.jinja2
- users.jinja2
- user.jinja2
__init__.py: pyramid_layout を include
from pyramid.config import Configurator
def main(global_conf, **settings):
config = Configurator(
settings=settings)
config.include("pyramid_tm")
config.include("pyramid_sqlalchemy")
config.include("pyramid_jinja2")
config.include("pyramid_deform")
config.include("pyramid_layout")
config.add_route('top', '/')
config.add_route('users', '/users')
config.add_route('new_user', '/new_user')
config.add_route('user', '/users/{username}')
config.scan()
return config.make_wsgi_app()
layouts.py: BaseLayoutを定義
from pyramid_layout.layout import layout_config
@layout_config(template="templates/base.jinja2")
class BaseLayout(object):
def __init__(self, context, request):
self.context = context
self.request = request
BaseLayoutで使うレイアウト
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet"
href="{{ request.static_url('deform:static/css/bootstrap.min.css') }}">
{% if css_links %}
{% for css in css_links %}
<link rel="stylesheet"
href="{{ request.static_url(css) }}">
{% endfor %}
{% endif %}
<script src="{{ request.static_url('deform:static/scripts/jquery-2.0.3.min.js') }}"></script>
<script src="{{ request.static_url('deform:static/scripts/bootstrap.min.js') }}"></script>
{% if js_links %}
{% for js in js_links %}
<script src="{{ request.static_url(js) }}" ></script>
{% endfor %}
{% endif %}
</head>
<body>
<div class="container">
{% block main_contents %}{% endblock %}
</div>
</body>
</html>
レイアウトで提供されるテンプレートを継承する(その他のテンプレートでも同様)
{% extends main_template %}
{% block main_contents %}
Hello, world!
{% endblock %}
スキーママイグレーション¶
- myapp7
- __init__.py
- wsgi.py
- models.py
- views.py
- layouts.py
- templates
- base.jinja2
- index.jinja2
- new_user.jinja2
- users.jinja2
- user.jinja2
alembicのインストール:
pip wheel -f wheelhouse alembic
pip install -f wheelhouse alembic
マイグレーションの初期化:
alembic init alembic
alembic.ini マイグレーション設定 sqlalchemy.url に接続文字列を設定する
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = sqlite:///%(here)s/myapp7.sqlite
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
alembic/env.py: target_schemaにアプリケーションモデルのmetadataを設定
from myapp7 import models
target_metadata = models.BaseObject.metadata
初期のスキーマリビジョンを作成:
alembic revision --autogenerate -m "first models"
スキーマをデータベースに反映:
alembic upgrade head
alembic history
models.py: モデルに項目(birthday) 追加
from sqlalchemy import (
Column,
Integer,
Date,
Unicode,
)
from pyramid_sqlalchemy import (
BaseObject,
Session,
)
class User(BaseObject):
__tablename__ = 'users'
query = Session.query_property()
id = Column(Integer, primary_key=True)
username = Column(Unicode(255), unique=True)
birthday = Column(Date)
追加した項目分のリビジョンを作成:
alembic revision --autogenerate -m "user birthday"
追加したリビジョンをデータベースに反映:
alembic upgrade head
alembic history