Basic Layout¶
The starter files generated by the pyramid_zodb
template are basic, but
they provide a good orientation for the high-level patterns common to most
traversal -based Pyramid (and ZODB based) projects.
The source code for this tutorial stage can be browsed via http://github.com/Pylons/pyramid/tree/1.0-branch/docs/tutorials/wiki/src/basiclayout/.
App Startup with __init__.py
¶
A directory on disk can be turned into a Python package by containing
an __init__.py
file. Even if empty, this marks a directory as a Python
package. Our application uses __init__.py
as both a package marker, as
well as to contain application configuration code.
When you run the application using the paster
command using the
development.ini
generated config file, the application configuration
points at an Setuptools entry point described as egg:tutorial
. In our
application, because the application’s setup.py
file says so, this entry
point happens to be the main
function within the file named
__init__.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pyramid.config import Configurator from repoze.zodbconn.finder import PersistentApplicationFinder from tutorial.models import appmaker def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ zodb_uri = settings.get('zodb_uri') if zodb_uri is None: raise ValueError("No 'zodb_uri' in application configuration.") finder = PersistentApplicationFinder(zodb_uri, appmaker) def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) config.add_static_view('static', 'tutorial:static') config.scan('tutorial') return config.make_wsgi_app()
- Lines 1-3. Perform some dependency imports.
- Line 8. Get the ZODB configuration from the
development.ini
file’s[app:main]
section represented by thesettings
dictionary passed to ourapp
function. This will be a URI (something likefile:///path/to/Data.fs
). - Line 12. We create a “finder” object using the
PersistentApplicationFinder
helper class, passing it the ZODB URI and the “appmaker” we’ve imported frommodels.py
. - Lines 13 - 14. We create a root factory which uses the finder to return a ZODB root object.
- Line 15. We construct a Configurator with a root
factory and the settings keywords parsed by PasteDeploy. The root
factory is named
get_root
. - Line 16. Register a ‘static view’ which answers requests which start
with with URL path
/static
using thepyramid.config.Configurator.add_static_view method()
. This statement registers a view that will serve up static assets, such as CSS and image files, for us, in this case, athttp://localhost:6543/static/
and below. The first argument is the “name”static
, which indicates that the URL path prefix of the view will be/static
. the The second argument of this tag is the “path”, which is an asset specification, so it finds the resources it should serve within thestatic
directory inside thetutorial
package. - Line 17. Perform a scan. A scan will find configuration
decoration, such as view configuration decorators
(e.g.
@view_config
) in the source code of thetutorial
package and will take actions based on these decorators. The argument toscan()
is the package name to scan, which istutorial
. - Line 18. Use the
pyramid.config.Configurator.make_wsgi_app()
method to return a WSGI application.
Resources and Models with models.py
¶
Pyramid uses the word resource to describe objects arranged
hierarchically in a resource tree. This tree is consulted by
traversal to map URLs to code. In this application, the resource
tree represents the site structure, but it also represents the
domain model of the application, because each resource is a node
stored persistently in a ZODB database. The models.py
file is
where the pyramid_zodb
Paster template put the classes that implement our
resource objects, each of which happens also to be a domain model object.
Here is the source for models.py
:
1 2 3 4 5 6 7 8 9 10 11 12 from persistent.mapping import PersistentMapping class MyModel(PersistentMapping): __parent__ = __name__ = None def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = MyModel() zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root']
Lines 3-4. The
MyModel
resource class is implemented here. Instances of this class will be capable of being persisted in ZODB because the class inherits from thepersistent.mapping.PersistentMapping
class. The__parent__
and__name__
are important parts of the traversal protocol. By default, have these asNone
indicating that this is the root object.Lines 6-12.
appmaker
is used to return the application root object. It is called on every request to the Pyramid application. It also performs bootstrapping by creating an application root (inside the ZODB root object) if one does not already exist.We do so by first seeing if the database has the persistent application root. If not, we make an instance, store it, and commit the transaction. We then return the application root object.
Views With views.py
¶
Our paster template generated a default views.py
on our behalf. It
contains a single view, which is used to render the page shown when you visit
the URL http://localhost:6543/
.
Here is the source for views.py
:
1 2 3 4 5 6 7 from pyramid.view import view_config from tutorial.models import MyModel @view_config(context=MyModel, renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'}
Let’s try to understand the components in this module:
Lines 1-2. Perform some dependency imports.
Line 4. Use the
pyramid.view.view_config()
configuration decoration to perform a view configuration registration. This view configuration registration will be activated when the application is started. It will be activated by virtue of it being found as the result of a scan (when Line 17 of__init__.py
is run).The
@view_config
decorator accepts a number of keyword arguments. We use two keyword arguments here:context
andrenderer
.The
context
argument signifies that the decorated view callable should only be run when traversal finds thetutorial.models.MyModel
resource to be the context of a request. In English, this means that when the URL/
is visited, becauseMyModel
is the root model, this view callable will be invoked.The
renderer
argument names an asset specification oftutorial:templates/mytemplate.pt
. This asset specification points at a Chameleon template which lives in themytemplate.pt
file within thetemplates
directory of thetutorial
package. And indeed if you look in thetemplates
directory of this package, you’ll see amytemplate.pt
template file, which renders the default home page of the generated project.Since this call to
@view_config
doesn’t pass aname
argument, themy_view
function which it decorates represents the “default” view callable used when the context is of the typeMyModel
.Lines 5-6. We define a view callable named
my_view
, which we decorated in the step above. This view callable is a function we write generated by thepyramid_zodb
template that is given arequest
and which returns a dictionary. Themytemplate.pt
renderer named by the asset specification in the step above will convert this dictionary to a response on our behalf.The function returns the dictionary
{'project':'tutorial'}
. This dictionary is used by the template named by themytemplate.pt
asset specification to fill in certain values on the page.
The WSGI Pipeline in development.ini
¶
The development.ini
(in the tutorial project directory, as
opposed to the tutorial package directory) looks like this:
[app:tutorial]
use = egg:tutorial
reload_templates = true
debug_authorization = false
debug_notfound = false
debug_routematch = false
debug_templates = true
default_locale_name = en
zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
[pipeline:main]
pipeline =
egg:WebError#evalerror
egg:repoze.zodbconn#closer
egg:repoze.retry#retry
tm
tutorial
[filter:tm]
use = egg:repoze.tm2#tm
commit_veto = repoze.tm:default_commit_veto
[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
# End logging configuration
Note the existence of a [pipeline:main]
section which specifies our WSGI
pipeline. This “pipeline” will be served up as our WSGI application. As far
as the WSGI server is concerned the pipeline is our application. Simpler
configurations don’t use a pipeline: instead they expose a single WSGI
application as “main”. Our setup is more complicated, so we use a pipeline
composed of middleware.
The egg:WebError#evalerror
middleware is at the “top” of the pipeline.
This is middleware which displays debuggable errors in the browser while
you’re developing (not recommended for deployment).
The egg:repoze.zodbconn#closer
middleware is in the middle of the
pipeline. This is a piece of middleware which closes the ZODB connection
opened by the PersistentApplicationFinder
at the end of the request.
The egg:repoze.retry#retry
middleware catches ConflictError
exceptions from ZODB and retries the request up to three times (ZODB is an
optimistic concurrency database that relies on application-level transaction
retries when a conflict occurs).
The tm
middleware is the last piece of middleware in the pipeline. This
commits a transaction near the end of the request unless there’s an exception
raised or the HTTP response code is an error code. The tm
refers to the
[filter:tm]
section beneath the pipeline declaration, which configures
the transaction manager.
The final line in the [pipeline:main]
section is tutorial
, which
refers to the [app:tutorial]
section above it. The [app:tutorial]
section is the section which actually defines our application settings. The
values within this section are passed as **settings
to the main
function we defined in __init__.py
when the server is started via
paster serve
.