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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
.. _wiki_basic_layout:
============
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 :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
Application configuration with ``__init__.py``
----------------------------------------------
A directory on disk can be turned into a Python :term:`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 :term:`Setuptools` :term:`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:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:linenos:
:language: py
Let's go over this piece-by-piece.
First we need some imports to support later code.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:end-before: root_factory
:lineno-match:
:language: py
Define a :term:`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 :ref:`wiki-resources-and-models`.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:pyobject: root_factory
:lineno-match:
:language: py
``__init__.py`` defines a function named ``main``.
Here is the entirety of the ``main`` function that we have defined in our ``__init__.py``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:pyobject: main
:lineno-match:
:language: py
When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed.
It accepts some settings and returns a :term:`WSGI` application.
See :ref:`startup_chapter` for more about ``pserve``.
Next in ``main``, construct a :term:`Configurator` object using a context manager.
See also :term:`Deployment settings`.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 15
:lineno-match:
:language: py
``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 :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 16
:lineno-match:
:language: py
Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 17
:lineno-match:
:language: py
Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 18
:lineno-match:
:language: py
Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 19
:lineno-match:
:language: py
Next include routes from the ``.routes`` module.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 20
:lineno-match:
:language: py
Next set a root factory using our function named ``root_factory``.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 21
:lineno-match:
:language: py
The included module contains the following function.
.. literalinclude:: src/basiclayout/tutorial/routes.py
:linenos:
:language: py
This registers a "static view" using the :meth:`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 :term:`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 :term:`scan`.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 22
:lineno-match:
:language: py
A scan will find :term:`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 :meth:`~pyramid.config.Configurator.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 :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 23
:lineno-match:
:language: py
.. _wiki-resources-and-models:
Resources and models with ``models`` package
--------------------------------------------
:app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`.
This tree is consulted by :term:`traversal` to map URLs to code.
In this application, the resource tree represents the site structure, but it *also* represents the :term:`domain model` of the application.
Each resource is a node stored persistently in a :term:`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``:
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:linenos:
:language: python
#. *Lines 4-5*.
The ``MyModel`` :term:`resource` class is implemented here.
Instances of this class are capable of being persisted in :term:`ZODB` because the class inherits from the :class:`persistent.mapping.PersistentMapping` class.
The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol.
By default, these are set to ``None`` to indicate that this is the :term:`root` object.
#. *Lines 8-12*.
``appmaker`` is used to return the *application root* object.
It is called on *every request* to the :app:`Pyramid` application by virtue of the ``root_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:
.. literalinclude:: src/basiclayout/tutorial/views/default.py
:linenos:
:language: python
Let's try to understand the components in this module:
#. *Lines 1-3*.
Perform some dependency imports.
#. *Line 6*.
Use the :func:`pyramid.view.view_config` :term:`configuration decoration` to perform a :term:`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 the :meth:`pyramid.config.Configurator.scan` method ``config.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 by ``scan``, the decorator effectively does nothing.
``@view_config`` is inert without being detected via a :term:`scan`.
The ``@view_config`` decorator accepts a number of keyword arguments.
We use two keyword arguments here: ``context`` and ``renderer``.
The ``context`` argument signifies that the decorated view callable ``my_view`` should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` as the :term:`context` of a request.
In English this means that when the URL ``/`` is visited, and because ``MyModel`` is the root model, this view callable will be invoked.
The ``renderer`` argument names an :term:`asset specification` of ``tutorial:templates/mytemplate.pt``.
This asset specification points at a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package.
And indeed if you look in the ``templates`` directory of this package, you will see a ``mytemplate.pt`` template file
This template renders the default home page of the generated project.
This asset specification is *absolute* to the ``views`` package.
Alternatively we could have used the relative asset specification ``../templates/mytemplate.pt``.
Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view callable used when the context is of the type ``MyModel``.
#. *Lines 7-8*.
A :term:`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 :term:`view callable`.
The function returns the dictionary ``{'project': 'myproj'}``.
This dictionary is used by the template named by the ``mytemplate.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.
.. literalinclude:: src/basiclayout/tutorial/views/notfound.py
:linenos:
:language: python
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 :term:`Not Found View` using :meth:`pyramid.config.Configurator.add_notfound_view`.
The ``renderer`` argument names an :term:`asset specification` of ``tutorial:templates/404.pt``.
#. *Lines 5-7*.
A :term:`view callable` named ``notfound_view`` is defined, which is decorated in the step above.
It sets the HTTP response status code to ``404``.
The function returns an empty dictionary to the template ``404.pt``, which accepts no parameters anyway.
Configuration in ``development.ini``
------------------------------------
The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as opposed to the ``tutorial`` :term:`package` directory) looks like this:
.. literalinclude:: src/basiclayout/development.ini
:language: ini
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 :ref:`wiki_adding_tests`.
|