Quick Tour of Pyramid¶
Pyramid lets you start small and finish big. This Quick Tour of Pyramid is for those who want to evaluate Pyramid, whether you are new to Python web frameworks, or a pro in a hurry. For more detailed treatment of each topic, give the Quick Tutorial for Pyramid a try.
Installation¶
Once you have a standard Python environment setup, getting started with Pyramid is a breeze. Unfortunately "standard" is not so simple in Python. For this Quick Tour, it means Python, a virtual environment (or virtualenv for Python 2.7), and setuptools.
As an example, for Python 3.3+ on Linux:
$ pyvenv env33 $ wget https://bootstrap.pypa.io/ez_setup.py -O - | env33/bin/python $ env33/bin/easy_install "pyramid==1.6.5"
For Windows:
# Use your browser to download: # https://bootstrap.pypa.io/ez_setup.py c:\> c:\Python33\python -m venv env33 c:\> env33\Scripts\python ez_setup.py c:\> env33\Scripts\easy_install "pyramid==1.6.5"
Of course Pyramid runs fine on Python 2.6+, as do the examples in this Quick Tour. We're just showing Python 3 a little love (Pyramid had production support for Python 3 in October 2011).
Note
Why easy_install
and not pip
? Some distributions upon which
Pyramid depends have optional C extensions for performance. pip
cannot
install some binary Python distributions. With easy_install
, Windows
users are able to obtain binary Python distributions, so they get the
benefit of the C extensions without needing a C compiler. Also there can
be issues when pip
and easy_install
are used side-by-side in the
same environment, so we chose to recommend easy_install
for the sake of
reducing the complexity of these instructions.
Hello World¶
Microframeworks have shown that learning starts best from a very small first step. Here's a tiny application in Pyramid:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
return Response('<h1>Hello World!</h1>')
if __name__ == '__main__':
config = Configurator()
config.add_route('hello', '/')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
|
This simple example is easy to run. Save this as app.py
and run it:
$ python ./app.py
Next open http://localhost:6543/ in a browser, and you will see the Hello
World!
message.
New to Python web programming? If so, some lines in the module merit explanation:
- Line 10.
if __name__ == '__main__':
is Python's way of saying "Start here when running from the command line". - Lines 11-13. Use Pyramid's configurator to connect view code to a particular URL route.
- Lines 6-7. Implement the view code that generates the response.
- Lines 14-16. Publish a WSGI app using an HTTP server.
As shown in this example, the configurator plays a central role in Pyramid development. Building an application from loosely-coupled parts via Application Configuration is a central idea in Pyramid, one that we will revisit regurlarly in this Quick Tour.
See also
See also: Quick Tutorial Hello World, Creating Your First Pyramid Application, and Todo List Application in One File.
Handling web requests and responses¶
Developing for the web means processing web requests. As this is a critical part of a web application, web developers need a robust, mature set of software for web requests.
Pyramid has always fit nicely into the existing world of Python web development
(virtual environments, packaging, scaffolding, one of the first to embrace
Python 3, etc.). Pyramid turned to the well-regarded WebOb Python
library for request and response handling. In our example above, Pyramid hands
hello_world
a request
that is based on WebOb.
Let's see some features of requests and responses in action:
def hello_world(request):
# Some parameters from a request such as /?name=lisa
url = request.url
name = request.params.get('name', 'No Name Provided')
body = 'URL %s with name: %s' % (url, name)
return Response(
content_type="text/plain",
body=body
)
In this Pyramid view, we get the URL being visited from request.url
. Also
if you visited http://localhost:6543/?name=alice in a browser, the name is
included in the body of the response:
URL http://localhost:6543/?name=alice with name: alice
Finally we set the response's content type, and return the Response.
See also
See also: Quick Tutorial Request and Response and Request and Response Objects.
Views¶
For the examples above, the hello_world
function is a "view". In Pyramid
views are the primary way to accept web requests and return responses.
So far our examples place everything in one file:
- the view function
- its registration with the configurator
- the route to map it to an URL
- the WSGI application launcher
Let's move the views out to their own views.py
module and change the
app.py
to scan that module, looking for decorators that set up the views.
First our revised app.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
config.add_route('home', '/')
config.add_route('hello', '/howdy')
config.add_route('redirect', '/goto')
config.add_route('exception', '/problem')
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
|
We added some more routes, but we also removed the view code. Our views and
their registrations (via decorators) are now in a module views.py
, which is
scanned via config.scan('views')
.
We now have a views.py
module that is focused on handling requests and
responses:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import cgi
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
from pyramid.view import view_config
# First view, available at http://localhost:6543/
@view_config(route_name='home')
def home_view(request):
return Response('<p>Visit <a href="/howdy?name=lisa">hello</a></p>')
# /howdy?name=alice which links to the next view
@view_config(route_name='hello')
def hello_view(request):
name = request.params.get('name', 'No Name')
body = '<p>Hi %s, this <a href="/goto">redirects</a></p>'
# cgi.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
return Response(body % cgi.escape(name))
# /goto which issues HTTP redirect to the last view
@view_config(route_name='redirect')
def redirect_view(request):
return HTTPFound(location="/problem")
# /problem which causes a site error
@view_config(route_name='exception')
def exception_view(request):
raise Exception()
|
We have four views, each leading to the other. If you start at
http://localhost:6543/, you get a response with a link to the next view. The
hello_view
(available at the URL /howdy
) has a link to the
redirect_view
, which issues a redirect to the final view.
Earlier we saw config.add_view
as one way to configure a view. This section
introduces @view_config
. Pyramid's configuration supports imperative
configuration, such as the config.add_view
in the previous example. You
can also use declarative configuration in which a Python
decorator is placed on the line above the view. Both approaches result
in the same final configuration, thus usually it is simply a matter of taste.
See also
See also: Quick Tutorial Views, Views, View Configuration, and Debugging View Configuration.
Routing¶
Writing web applications usually means sophisticated URL design. We just saw some Pyramid machinery for requests and views. Let's look at features that help with routing.
Above we saw the basics of routing URLs to views in Pyramid:
- Your project's "setup" code registers a route name to be used when matching part of the URL.
- Elsewhere a view is configured to be called for that route name.
Note
Why do this twice? Other Python web frameworks let you create a route and associate it with a view in one step. As illustrated in Routes need relative ordering, multiple routes might match the same URL pattern. Rather than provide ways to help guess, Pyramid lets you be explicit in ordering. Pyramid also gives facilities to avoid the problem.
What if we want part of the URL to be available as data in my view? We can use this route declaration, for example:
6 | config.add_route('hello', '/howdy/{first}/{last}')
|
With this, URLs such as /howdy/amy/smith
will assign amy
to first
and smith
to last
. We can then use this data in our view:
5 6 7 8 | @view_config(route_name='hello')
def hello_world(request):
body = '<h1>Hi %(first)s %(last)s!</h1>' % request.matchdict
return Response(body)
|
request.matchdict
contains values from the URL that match the "replacement
patterns" (the curly braces) in the route declaration. This information can
then be used in your view.
See also
See also: Quick Tutorial Routing, URL Dispatch, Debugging Route Matching, and Request Processing.
Templating¶
Ouch. We have been making our own Response
and filling the response body
with HTML. You usually won't embed an HTML string directly in Python, but
instead you will use a templating language.
Pyramid doesn't mandate a particular database system, form library, and so on. It encourages replaceability. This applies equally to templating, which is fortunate: developers have strong views about template languages. That said, the Pylons Project officially supports bindings for Chameleon, Jinja2, and Mako. In this step let's use Chameleon.
Let's add pyramid_chameleon
, a Pyramid add-on which enables
Chameleon as a renderer in our Pyramid application:
$ easy_install pyramid_chameleon
With the package installed, we can include the template bindings into our
configuration in app.py
:
6 7 8 | config.add_route('hello', '/howdy/{name}')
config.include('pyramid_chameleon')
config.scan('views')
|
Now lets change our views.py
file:
1 2 3 4 5 6 | from pyramid.view import view_config
@view_config(route_name='hello', renderer='hello_world.pt')
def hello_world(request):
return dict(name=request.matchdict['name'])
|
Ahh, that looks better. We have a view that is focused on Python code. Our
@view_config
decorator specifies a renderer that points to our
template file. Our view then simply returns data which is then supplied to our
template hello_world.pt
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Quick Glance</title>
</head>
<body>
<h1>Hello ${name}</h1>
</body>
</html>
Since our view returned dict(name=request.matchdict['name'])
, we can use
name
as a variable in our template via ${name}
.
See also
See also: Quick Tutorial Templating, Templates, Debugging Templates, and Available Add-On Template System Bindings.
Templating with Jinja2¶
We just said Pyramid doesn't prefer one templating language over another. Time
to prove it. Jinja2 is a popular templating system, modeled after Django's
templates. Let's add pyramid_jinja2
, a Pyramid add-on which enables
Jinja2 as a renderer in our Pyramid applications:
$ easy_install pyramid_jinja2
With the package installed, we can include the template bindings into our configuration:
6 7 8 | config.add_route('hello', '/howdy/{name}')
config.include('pyramid_jinja2')
config.scan('views')
|
The only change in our view is to point the renderer at the .jinja2
file:
4 5 6 | @view_config(route_name='hello', renderer='hello_world.jinja2')
def hello_world(request):
return dict(name=request.matchdict['name'])
|
Our Jinja2 template is very similar to our previous template:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello {{ name }}!</h1>
</body>
</html>
Pyramid's templating add-ons register a new kind of renderer into your
application. The renderer registration maps to different kinds of filename
extensions. In this case, changing the extension from .pt
to .jinja2
passed the view response through the pyramid_jinja2
renderer.
See also
See also: Quick Tutorial Jinja2, Jinja2 homepage, and pyramid_jinja2 Overview.
Static assets¶
Of course the Web is more than just markup. You need static assets: CSS, JS,
and images. Let's point our web app at a directory from which Pyramid will
serve some static assets. First let's make another call to the
configurator in app.py
:
6 7 8 | config.add_route('hello', '/howdy/{name}')
config.add_static_view(name='static', path='static')
config.include('pyramid_jinja2')
|
This tells our WSGI application to map requests under
http://localhost:6543/static/ to files and directories inside a static
directory alongside our Python module.
Next make a directory named static
, and place app.css
inside:
body {
margin: 2em;
font-family: sans-serif;
}
All we need to do now is point to it in the <head>
of our Jinja2 template,
hello_world.jinja2
:
4 5 6 | <title>Hello World</title>
<link rel="stylesheet" href="/static/app.css"/>
</head>
|
This link presumes that our CSS is at a URL starting with /static/
. What if
the site is later moved under /somesite/static/
? Or perhaps a web developer
changes the arrangement on disk? Pyramid provides a helper to allow flexibility
on URL generation:
4 5 6 | <title>Hello World</title>
<link rel="stylesheet" href="{{ request.static_url('__main__:static/app.css') }}"/>
</head>
|
By using request.static_url
to generate the full URL to the static assets,
you ensure that you stay in sync with the configuration and gain refactoring
flexibility later.
See also
See also: Quick Tutorial Static Assets, Static Assets, Preventing HTTP Caching, and Influencing HTTP Caching.
Returning JSON¶
Modern web apps are more than rendered HTML. Dynamic pages now use JavaScript to update the UI in the browser by requesting server data as JSON. Pyramid supports this with a JSON renderer:
9 10 11 | @view_config(route_name='hello_json', renderer='json')
def hello_json(request):
return [1, 2, 3]
|
This wires up a view that returns some data through the JSON renderer, which calls Python's JSON support to serialize the data into JSON, and sets the appropriate HTTP headers.
We also need to add a route to app.py
so that our app will know how to
respond to a request for hello.json
.
6 7 8 | config.add_route('hello', '/howdy/{name}')
config.add_route('hello_json', 'hello.json')
config.add_static_view(name='static', path='static')
|
See also
See also: Quick Tutorial JSON, Writing View Callables Which Use a Renderer, JSON Renderer, and Adding and Changing Renderers.
View classes¶
So far our views have been simple, free-standing functions. Many times your views are related. They may have different ways to look at or work on the same data, or they may be a REST API that handles multiple operations. Grouping these together as a view class makes sense and achieves the following goals.
- Group views
- Centralize some repetitive defaults
- Share some state and helpers
The following shows a "Hello World" example with three operations: view a form,
save a change, or press the delete button in our views.py
:
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # One route, at /howdy/amy, so don't repeat on each @view_config
@view_defaults(route_name='hello')
class HelloWorldViews:
def __init__(self, request):
self.request = request
# Our templates can now say {{ view.name }}
self.name = request.matchdict['name']
# Retrieving /howdy/amy the first time
@view_config(renderer='hello.jinja2')
def hello_view(self):
return dict()
# Posting to /howdy/amy via the "Edit" submit button
@view_config(request_param='form.edit', renderer='edit.jinja2')
def edit_view(self):
print('Edited')
return dict()
# Posting to /howdy/amy via the "Delete" submit button
@view_config(request_param='form.delete', renderer='delete.jinja2')
def delete_view(self):
print('Deleted')
return dict()
|
As you can see, the three views are logically grouped together. Specifically:
- The first view is returned when you go to
/howdy/amy
. This URL is mapped to thehello
route that we centrally set using the optional@view_defaults
. - The second view is returned when the form data contains a field with
form.edit
, such as clicking on<input type="submit" name="form.edit" value="Save">
. This rule is specified in the@view_config
for that view. - The third view is returned when clicking on a button such as
<input type="submit" name="form.delete" value="Delete">
.
Only one route is needed, stated in one place atop the view class. Also, the
assignment of name
is done in the __init__
function. Our templates can
then use {{ view.name }}
.
Pyramid view classes, combined with built-in and custom predicates, have much more to offer:
- All the same view configuration parameters as function views
- One route leading to multiple views, based on information in the request or
data such as
request_param
,request_method
,accept
,header
,xhr
,containment
, andcustom_predicates
See also
See also: Quick Tutorial View Classes, Quick Tutorial More View Classes, and Defining a View Callable as a Class.
Quick project startup with scaffolds¶
So far we have done all of our Quick Tour as a single Python file. No Python packages, no structure. Most Pyramid projects, though, aren't developed this way.
To ease the process of getting started, Pyramid provides scaffolds that
generate sample projects from templates in Pyramid and Pyramid add-ons.
Pyramid's pcreate
command can list the available scaffolds:
$ pcreate --list
Available scaffolds:
alchemy: Pyramid SQLAlchemy project using url dispatch
pyramid_jinja2_starter: Pyramid Jinja2 starter project
starter: Pyramid starter project
zodb: Pyramid ZODB project using traversal
The pyramid_jinja2
add-on gave us a scaffold that we can use. From the
parent directory of where we want our Python package to be generated, let's use
that scaffold to make our project:
$ pcreate --scaffold pyramid_jinja2_starter hello_world
We next use the normal Python command to set up our package for development:
$ cd hello_world
$ python ./setup.py develop
We are moving in the direction of a full-featured Pyramid project, with a proper setup for Python standards (packaging) and Pyramid configuration. This includes a new way of running your application:
$ pserve development.ini
Let's look at pserve
and configuration in more depth.
See also
See also: Quick Tutorial Scaffolds, Creating a Pyramid Project, and Creating Pyramid Scaffolds
Application running with pserve
¶
Prior to scaffolds, our project mixed a number of operational details into our code. Why should my main code care which HTTP server I want and what port number to run on?
pserve
is Pyramid's application runner, separating operational details from
your code. When you install Pyramid, a small command program called pserve
is written to your bin
directory. This program is an executable Python
module. It's very small, getting most of its brains via import.
You can run pserve
with --help
to see some of its options. Doing so
reveals that you can ask pserve
to watch your development files and reload
the server when they change:
$ pserve development.ini --reload
The pserve
command has a number of other options and operations. Most of
the work, though, comes from your project's wiring, as expressed in the
configuration file you supply to pserve
. Let's take a look at this
configuration file.
See also
See also: What Is This pserve Thing
Configuration with .ini
files¶
Earlier in Quick Tour we first met Pyramid's configuration system. At that
point we did all configuration in Python code. For example, the port number
chosen for our HTTP server was right there in Python code. Our scaffold has
moved this decision and more into the development.ini
file:
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/environment.html
###
[app:main]
use = egg:hello_world
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = true
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
host = 127.0.0.1
port = 6543
###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/logging.html
###
[loggers]
keys = root, hello_world
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_hello_world]
level = DEBUG
handlers =
qualname = hello_world
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
Let's take a quick high-level look. First the .ini
file is divided into
sections:
[app:main]
configures our WSGI app[server:main]
holds our WSGI server settings- Various sections afterwards configure our Python logging system
We have a few decisions made for us in this configuration:
- Choice of web server:
use = egg:hello_world
tellspserve
to use thewaitress
server. - Port number:
port = 6543
tellswaitress
to listen on port 6543. - WSGI app: What package has our WSGI application in it?
use = egg:hello_world
in the app section tells the configuration what application to load. - Easier development by automatic template reloading: In development mode,
you shouldn't have to restart the server when editing a Jinja2 template.
pyramid.reload_templates = true
sets this policy, which might be different in production.
Additionally the development.ini
generated by this scaffold wired up
Python's standard logging. We'll now see in the console, for example, a log on
every request that comes in, as well as traceback information.
Easier development with debugtoolbar
¶
As we introduce the basics, we also want to show how to be productive in
development and debugging. For example, we just discussed template reloading
and earlier we showed --reload
for application reloading.
pyramid_debugtoolbar
is a popular Pyramid add-on which makes several tools
available in your browser. Adding it to your project illustrates several points
about configuration.
The scaffold pyramid_jinja2_starter
is already configured to include the
add-on pyramid_debugtoolbar
in its setup.py
:
11 12 13 14 15 16 | requires = [
'pyramid',
'pyramid_jinja2',
'pyramid_debugtoolbar',
'waitress',
]
|
It was installed when you previously ran:
$ python ./setup.py develop
The pyramid_debugtoolbar
package is a Pyramid add-on, which means we need
to include its configuration into our web application. The pyramid_jinja2
add-on already took care of this for us in its __init__.py
:
16 | config.include('pyramid_jinja2')
|
And it uses the pyramid.includes
facility in our development.ini
:
15 16 | pyramid.includes =
pyramid_debugtoolbar
|
You'll now see a Pyramid logo on the right side of your browser window, which
when clicked opens a new window that provides introspective access to debugging
information. Even better, if your web application generates an error, you will
see a nice traceback on the screen. When you want to disable this toolbar,
there's no need to change code: you can remove it from pyramid.includes
in
the relevant .ini
configuration file.
See also
See also: Quick Tutorial pyramid_debugtoolbar and pyramid_debugtoolbar
Unit tests and nose
¶
Yikes! We got this far and we haven't yet discussed tests. This is particularly egregious, as Pyramid has had a deep commitment to full test coverage since before its release.
Our pyramid_jinja2_starter
scaffold generated a tests.py
module with
one unit test in it. To run it, let's install the handy nose
test runner by
editing setup.py
. While we're at it, we'll throw in the coverage
tool
which yells at us for code that isn't tested. Edit line 36 so it becomes the
following:
36 37 38 | tests_require={
'testing': ['nose', 'coverage'],
},
|
We changed setup.py
which means we need to rerun
python ./setup.py develop
. We can now run all our tests:
$ nosetests hello_world/tests.py
.
Name Stmts Miss Cover Missing
---------------------------------------------------
hello_world 11 8 27% 11-23
hello_world.models 5 1 80% 8
hello_world.tests 14 0 100%
hello_world.views 4 0 100%
---------------------------------------------------
TOTAL 34 9 74%
----------------------------------------------------------------------
Ran 1 test in 0.009s
OK
Our unit test passed. What did our test look like?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import unittest
from pyramid import testing
from pyramid.i18n import TranslationStringFactory
_ = TranslationStringFactory('hello_world')
class ViewTests(unittest.TestCase):
def setUp(self):
testing.setUp()
def tearDown(self):
testing.tearDown()
def test_my_view(self):
from hello_world.views import my_view
request = testing.DummyRequest()
response = my_view(request)
self.assertEqual(response['project'], 'hello_world')
|
Pyramid supplies helpers for test writing, which we use in the test setup and teardown. Our one test imports the view, makes a dummy request, and sees if the view returns what we expected.
See also
See also: Quick Tutorial Unit Testing, Quick Tutorial Functional Testing, and Unit, Integration, and Functional Testing
Logging¶
It's important to know what is going on inside our web application. In development we might need to collect some output. In production we might need to detect situations when other people use the site. We need logging.
Fortunately Pyramid uses the normal Python approach to logging. The scaffold
generated in your development.ini
has a number of lines that configure the
logging for you to some reasonable defaults. You then see messages sent by
Pyramid (for example, when a new request comes in).
Maybe you would like to log messages in your code? In your Python module, import and set up the logging:
3 4 | import logging
log = logging.getLogger(__name__)
|
You can now, in your code, log messages:
9 10 | def my_view(request):
log.debug('Some Message')
|
This will log Some Message
at a debug
log level to the
application-configured logger in your development.ini
. What controls that?
These emphasized sections in the configuration file:
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | [loggers]
keys = root, hello_world
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_hello_world]
level = DEBUG
handlers =
qualname = hello_world
|
Our application, a package named hello_world
, is set up as a logger and
configured to log messages at a DEBUG
or higher level. When you visit
http://localhost:6543, your console will now show:
2016-01-18 13:55:55,040 DEBUG [hello_world.views:10][waitress] Some Message
See also
See also: Quick Tutorial Logging and Logging.
Sessions¶
When people use your web application, they frequently perform a task that requires semi-permanent data to be saved. For example, a shopping cart. This is called a session.
Pyramid has basic built-in support for sessions. Third party packages such as
pyramid_redis_sessions
provide richer session support. Or you can create
your own custom sessioning engine. Let's take a look at the built-in
sessioning support. In our __init__.py
we first import
the kind of sessioning we want:
2 3 | from hello_world.resources import get_root
from pyramid.session import SignedCookieSessionFactory
|
Warning
As noted in the session docs, this example implementation is not intended for use in settings with security implications.
Now make a "factory" and pass it to the configurator's
session_factory
argument:
13 14 15 16 17 | settings.setdefault('jinja2.i18n.domain', 'hello_world')
my_session_factory = SignedCookieSessionFactory('itsaseekreet')
config = Configurator(root_factory=get_root, settings=settings,
session_factory=my_session_factory)
|
Pyramid's request object now has a session
attribute that we can
use in our view code in views.py
:
9 10 11 12 13 14 15 | def my_view(request):
log.debug('Some Message')
session = request.session
if 'counter' in session:
session['counter'] += 1
else:
session['counter'] = 0
|
We need to update our Jinja2 template to show counter increment in the session:
40 41 42 | <p class="lead">
{% trans %}Hello{% endtrans %} to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.6</span>.</p>
<p>Counter: {{ request.session.counter }}</p>
|
See also
See also: Quick Tutorial Sessions, Sessions, Flash Messages, pyramid.session, and pyramid_redis_sessions.
Databases¶
Web applications mean data. Data means databases. Frequently SQL databases. SQL databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM usually leads to the mega-quality SQLAlchemy, a Python package that greatly eases working with databases.
Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold!
$ pcreate --scaffold alchemy sqla_demo
$ cd sqla_demo
$ python setup.py develop
We now have a working sample SQLAlchemy application with all dependencies installed. The sample project provides a console script to initialize a SQLite database with tables. Let's run it, then start the application:
$ initialize_sqla_demo_db development.ini
$ pserve development.ini
The ORM eases the mapping of database structures into a programming language. SQLAlchemy uses "models" for this mapping. The scaffold generated a sample model:
21 22 23 24 25 26 27 | class MyModel(Base):
__tablename__ = 'models'
id = Column(Integer, primary_key=True)
name = Column(Text)
value = Column(Integer)
Index('my_index', MyModel.name, unique=True, mysql_length=255)
|
View code, which mediates the logic between web requests and the rest of the system, can then easily get at the data thanks to SQLAlchemy:
12 13 14 15 16 17 18 | @view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
try:
one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
except DBAPIError:
return Response(conn_err_msg, content_type='text/plain', status_int=500)
return {'one': one, 'project': 'sqla_demo'}
|
Forms¶
Developers have lots of opinions about web forms, thus there are many form libraries for Python. Pyramid doesn't directly bundle a form library, but Deform is a popular choice for forms, along with its related Colander schema system.
As an example, imagine we want a form that edits a wiki page. The form should have two fields on it, one of them a required title and the other a rich text editor for the body. With Deform we can express this as a Colander schema:
class WikiPage(colander.MappingSchema):
title = colander.SchemaNode(colander.String())
body = colander.SchemaNode(
colander.String(),
widget=deform.widget.RichTextWidget()
)
With this in place, we can render the HTML for a form, perhaps with form data from an existing page:
form = self.wiki_form.render()
We'd like to handle form submission, validation, and saving:
# Get the form data that was posted
controls = self.request.POST.items()
try:
# Validate and either raise a validation error
# or return deserialized data from widgets
appstruct = wiki_form.validate(controls)
except deform.ValidationFailure as e:
# Bail out and render form with errors
return dict(title=title, page=page, form=e.render())
# Change the content and redirect to the view
page['title'] = appstruct['title']
page['body'] = appstruct['body']
Deform and Colander provide a very flexible combination for forms, widgets, schemas, and validation. Recent versions of Deform also include a retail mode for gaining Deform features on custom forms.
Also the deform_bootstrap
Pyramid add-on restyles the stock Deform widgets
using attractive CSS from Twitter Bootstrap and more powerful widgets from
Chosen.
See also
See also: Quick Tutorial Forms, Deform, Colander, and deform_bootstrap.
Conclusion¶
This Quick Tour covered a little about a lot. We introduced a long list of concepts in Pyramid, many of which are expanded on more fully in the Pyramid developer docs.