Basic Layout¶
The starter files generated by 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 is 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
3
4from .models import appmaker
5
6
7def root_factory(request):
8 conn = get_connection(request)
9 return appmaker(conn.root())
10
11
12def main(global_config, **settings):
13 """ This function returns a Pyramid WSGI application.
14 """
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.include('.routes')
21 config.set_root_factory(root_factory)
22 config.scan()
23 return config.make_wsgi_app()
Let's go over this piece-by-piece. First we need some imports to support later code.
1from pyramid.config import Configurator
2from pyramid_zodbconn import get_connection
3
4from .models import appmaker
5
6
Define a root factory for our Pyramid application.
It establishes a connection to ZODB database.
It returns an appmaker
, which we will describe in the next section Resources and models with models package.
7def root_factory(request):
8 conn = get_connection(request)
9 return appmaker(conn.root())
__init__.py
defines a function named main
.
Here is the entirety of the main
function that we have defined in our __init__.py
:
12def main(global_config, **settings):
13 """ This function returns a Pyramid WSGI application.
14 """
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.include('.routes')
21 config.set_root_factory(root_factory)
22 config.scan()
23 return config.make_wsgi_app()
When you invoke the pserve development.ini
command, the main
function above is executed.
It accepts some settings and returns a WSGI application.
See Startup for more about pserve
.
Next in main
, construct a Configurator object using a context manager.
See also Deployment settings.
15 with Configurator(settings=settings) as config:
settings
is passed to the Configurator
as a keyword argument with the dictionary values passed as the **settings
argument.
This will be a dictionary of settings parsed from the .ini
file, which contains
deployment-related values, such as pyramid.reload_templates
, zodbconn.uri
, and so on.
Next include support for the Chameleon template rendering bindings, allowing us to use the .pt
templates.
16 config.include('pyramid_chameleon')
Next include support for pyramid_tm
, allowing Pyramid requests to join the active transaction as provided by the transaction package.
17 config.include('pyramid_tm')
Next include support for pyramid_retry
to retry a request when transient exceptions occur.
18 config.include('pyramid_retry')
Next include support for pyramid_zodbconn
, providing integration between ZODB and a Pyramid application.
19 config.include('pyramid_zodbconn')
Next include routes from the .routes
module.
20 config.include('.routes')
Next set a root factory using our function named root_factory
.
21 config.set_root_factory(root_factory)
The included module contains the following function.
1def includeme(config):
2 config.add_static_view('static', 'static', cache_max_age=3600)
This registers a "static view" using the pyramid.config.Configurator.add_static_view()
method.
This view answers requests whose URL path starts with /static
.
This statement registers a view that will serve up static assets, such as CSS and image files.
In this case the URL will answer requests at http://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 method is the "path".
It is a relative asset specification.
It finds the resources it should serve within the static
directory inside the tutorial
package.
Alternatively the cookiecutter could have used an absolute asset specification as the path (tutorial:static
).
The third argument is an optional cache_max_age
which specifies the number of seconds the static asset will be HTTP-cached.
Back into our __init__.py
, next perform a scan.
22 config.scan()
A scan will find configuration decoration, such as view configuration decorators (e.g., @view_config
) in the source code of the tutorial
package.
It will take actions based on these decorators.
We don't pass any arguments to scan()
, which implies that the scan should take place in the current package (in this case, tutorial
).
The cookiecutter could have equivalently said config.scan('tutorial')
, but it chose to omit the package name argument.
Finally use the pyramid.config.Configurator.make_wsgi_app()
method to return a WSGI application.
23 return config.make_wsgi_app()
Resources and models with models
package¶
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.
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
MyModel
resource class is implemented here. Instances of this class are 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, these are set toNone
to indicate that this is the root object.Lines 8-12.
appmaker
is used to return the application root object. It is called on every request to the Pyramid application by virtue of theroot_factory
defined in our__init__.py
. It also performs bootstrapping by creating an application root (inside the ZODB root object) if one does not already exist.Bootstrapping is done by first seeing if the database has the persistent application root. If not, then we make an instance, store it, and commit the transaction.
We then return the application root object.
View declarations via the views
package¶
Our cookiecutter generated a default views
package on our behalf.
It contains a two views.
The first view is used to render the page shown when you visit the URL http://localhost:6543/
.
Open tutorial/views/default.py
in the views
package.
It should already contain the following:
1from pyramid.view import view_config
2
3from ..models import MyModel
4
5
6@view_config(context=MyModel, renderer='tutorial:templates/mytemplate.pt')
7def my_view(request):
8 return {'project': 'myproj'}
Let's try to understand the components in this module:
Lines 1-3. Perform some dependency imports.
Line 6. 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. Remember in our application's__init__.py
when we executed thepyramid.config.Configurator.scan()
methodconfig.scan()
? By calling the scan method, Pyramid's configurator will find and process this@view_config
decorator, and create a view configuration within our application. Without being processed byscan
, the decorator effectively does nothing.@view_config
is inert without being detected via a scan.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 callablemy_view
should only be run when traversal finds thetutorial.models.MyModel
resource as the context of a request. In English this means that when the URL/
is visited, and 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 will see amytemplate.pt
template file This template renders the default home page of the generated project. This asset specification is absolute to theviews
package. Alternatively we could have used the relative asset specification../templates/mytemplate.pt
.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 7-8. A view callable named
my_view
is defined, which is decorated in the step above. This view callable is a function generated by the cookiecutter. It is given a single argument,request
. This is the standard call signature for a Pyramid view callable. The function returns the dictionary{'project': 'myproj'}
. This dictionary is used by the template named by themytemplate.pt
asset specification to fill in certain values on the page.
Let us open tutorial/views/notfound.py
in the views
package to look at the second view.
1from pyramid.view import notfound_view_config
2
3
4@notfound_view_config(renderer='tutorial:templates/404.pt')
5def notfound_view(request):
6 request.response.status = 404
7 return {}
Without repeating ourselves, we will point out the differences between this view and the previous.
Line 4. The
notfound_view
function is decorated with@notfound_view_config
. This decorator registers a Not Found View usingpyramid.config.Configurator.add_notfound_view()
.The
renderer
argument names an asset specification oftutorial:templates/404.pt
.Lines 5-7. A view callable named
notfound_view
is defined, which is decorated in the step above. It sets the HTTP response status code to404
. The function returns an empty dictionary to the template404.pt
, which accepts no parameters anyway.
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.
When the server is started via pserve
, the values within this section are passed as **settings
to the main
function defined in __init__.py
.
Tests¶
The project contains a basic structure for a test suite using pytest
.
The structure is covered later in Adding Tests.