Basic Layout¶
The starter files generated by selecting the zodb backend in the
cookiecutter are very basic, but they provide a good orientation for the
high-level patterns common to most traversal-based (and
ZODB-based) Pyramid projects.
Application configuration 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. We use __init__.py both as a marker, indicating the directory in
which it's contained is a package, and to contain application configuration
code.
When you run the application using the pserve command using the
development.ini generated configuration file, the application
configuration points at a 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.
Open tutorial/__init__.py. It should already contain the following:
1from pyramid.config import Configurator
2from pyramid_zodbconn import get_connection
3from .models import appmaker
4
5
6def root_factory(request):
7 conn = get_connection(request)
8 return appmaker(conn.root())
9
10
11def main(global_config, **settings):
12 """ This function returns a Pyramid WSGI application.
13 """
14 settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
15 with Configurator(settings=settings) as config:
16 config.include('pyramid_chameleon')
17 config.include('pyramid_tm')
18 config.include('pyramid_retry')
19 config.include('pyramid_zodbconn')
20 config.set_root_factory(root_factory)
21 config.add_static_view('static', 'static', cache_max_age=3600)
22 config.scan()
23 return config.make_wsgi_app()
Lines 1-3. Perform some dependency imports.
Lines 6-8. Define a root factory for our Pyramid application.
Line 11.
__init__.pydefines a function namedmain.Line 14. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the
pyramid_tmlifecycle.Line 15. Construct a Configurator as a context manager with the settings keyword parsed by PasteDeploy.
Line 16. Include support for the Chameleon template rendering bindings, allowing us to use the
.pttemplates.Line 17. Include support for
pyramid_tm, allowing Pyramid requests to join the active transaction as provided by the transaction package.Line 18. Include support for
pyramid_retryto retry a request when transient exceptions occur.Line 19. Include support for
pyramid_zodbconn, providing integration between ZODB and a Pyramid application.Line 20. Set a root factory using our function named
root_factory.Line 21. Register a "static view", which answers requests whose URL paths start with
/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 second argument of this tag is the "path", which is a relative asset specification, so it finds the resources it should serve within thestaticdirectory inside thetutorialpackage. Alternatively the cookiecutter could have used an absolute asset specification as the path (tutorial:static).Line 22. Perform a scan. A scan will find configuration decoration, such as view configuration decorators (e.g.,
@view_config) in the source code of thetutorialpackage and will take actions based on these decorators. We don't pass any arguments toscan(), which implies that the scan should take place in the current package (in this case,tutorial). The cookiecutter could have equivalently saidconfig.scan('tutorial'), but it chose to omit the package name argument.Line 23. 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 zodb cookiecutter put the classes that implement our
resource objects, each of which also happens to be a domain model object.
Here is the source for models.py:
1from persistent.mapping import PersistentMapping
2
3
4class MyModel(PersistentMapping):
5 __parent__ = __name__ = None
6
7
8def appmaker(zodb_root):
9 if 'app_root' not in zodb_root:
10 app_root = MyModel()
11 zodb_root['app_root'] = app_root
12 return zodb_root['app_root']
Lines 4-5. The
MyModelresource class is implemented here. Instances of this class are capable of being persisted in ZODB because the class inherits from thepersistent.mapping.PersistentMappingclass. The__parent__and__name__are important parts of the traversal protocol. By default, set these toNoneto indicate that this is the root object.Lines 8-12.
appmakeris 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. It is used by theroot_factorywe've defined in our__init__.py.Bootstrapping is done 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 cookiecutter 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:
1from pyramid.view import view_config
2from .models import MyModel
3
4
5@view_config(context=MyModel, renderer='templates/mytemplate.pt')
6def my_view(request):
7 return {'project': 'myproj'}
Let's try to understand the components in this module:
Lines 1-2. Perform some dependency imports.
Line 5. 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 14 of__init__.pyis run).The
@view_configdecorator accepts a number of keyword arguments. We use two keyword arguments here:contextandrenderer.The
contextargument signifies that the decorated view callable should only be run when traversal finds thetutorial.models.MyModelresource to be the context of a request. In English, this means that when the URL/is visited, becauseMyModelis the root model, this view callable will be invoked.The
rendererargument names an asset specification oftemplates/mytemplate.pt. This asset specification points at a Chameleon template which lives in themytemplate.ptfile within thetemplatesdirectory of thetutorialpackage. And indeed if you look in thetemplatesdirectory of this package, you'll see amytemplate.pttemplate file, which renders the default home page of the generated project. This asset specification is relative (to the view.py's current package). Alternatively we could have used the absolute asset specificationtutorial:templates/mytemplate.pt, but chose to use the relative version.Since this call to
@view_configdoesn't pass anameargument, themy_viewfunction which it decorates represents the "default" view callable used when the context is of the typeMyModel.Lines 6-7. 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 thezodbcookiecutter that is given arequestand which returns a dictionary. Themytemplate.ptrenderer 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.ptasset specification to fill in certain values on the page.
Configuration in development.ini¶
The development.ini (in the tutorial project directory, as
opposed to the tutorial package directory) looks like this:
###
# app configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
retry.attempts = 3
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
[pshell]
setup = tutorial.pshell.setup
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
listen = localhost:6543
###
# logging configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[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
Note the existence of a [app:main] section which specifies our WSGI
application. Our ZODB database settings are specified as the
zodbconn.uri setting within this section. This value, and the other
values within this section, are passed as **settings to the main
function we defined in __init__.py when the server is started via
pserve.