commit | author | age
|
640d77
|
1 |
.. _wiki2_basic_layout: |
SP |
2 |
|
e26700
|
3 |
============ |
CM |
4 |
Basic Layout |
|
5 |
============ |
|
6 |
|
b29848
|
7 |
The starter files generated by the ``alchemy`` cookiecutter are very basic, but |
6772a2
|
8 |
they provide a good orientation for the high-level patterns common to most |
ee9676
|
9 |
:term:`URL dispatch`-based :app:`Pyramid` projects. |
e26700
|
10 |
|
CM |
11 |
|
34a913
|
12 |
Application configuration with ``__init__.py`` |
6772a2
|
13 |
---------------------------------------------- |
e26700
|
14 |
|
b2adfe
|
15 |
A directory on disk can be turned into a Python :term:`package` by containing |
CM |
16 |
an ``__init__.py`` file. Even if empty, this marks a directory as a Python |
414b67
|
17 |
package. We use ``__init__.py`` both as a marker, indicating the directory in |
MM |
18 |
which it's contained is a package, and to contain application configuration |
34a913
|
19 |
code. |
197808
|
20 |
|
4f87e5
|
21 |
Open ``tutorial/__init__.py``. It should already contain the following: |
e26700
|
22 |
|
8ec0b0
|
23 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
24 |
:linenos: |
|
25 |
:language: py |
0aed1c
|
26 |
|
4f87e5
|
27 |
Let's go over this piece-by-piece. First we need some imports to support later |
SP |
28 |
code: |
0aed1c
|
29 |
|
8ec0b0
|
30 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
31 |
:end-before: main |
7b89a7
|
32 |
:lineno-match: |
8ec0b0
|
33 |
:language: py |
0aed1c
|
34 |
|
6772a2
|
35 |
``__init__.py`` defines a function named ``main``. Here is the entirety of |
CM |
36 |
the ``main`` function we've defined in our ``__init__.py``: |
0aed1c
|
37 |
|
8ec0b0
|
38 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
39 |
:pyobject: main |
7b89a7
|
40 |
:lineno-match: |
MM |
41 |
:language: py |
0aed1c
|
42 |
|
6772a2
|
43 |
When you invoke the ``pserve development.ini`` command, the ``main`` function |
CM |
44 |
above is executed. It accepts some settings and returns a :term:`WSGI` |
e2919c
|
45 |
application. (See :ref:`startup_chapter` for more about ``pserve``.) |
6772a2
|
46 |
|
2f6864
|
47 |
Next in ``main``, construct a :term:`Configurator` object: |
0aed1c
|
48 |
|
8ec0b0
|
49 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
50 |
:lines: 7 |
7b89a7
|
51 |
:lineno-match: |
8ec0b0
|
52 |
:language: py |
0aed1c
|
53 |
|
4f87e5
|
54 |
``settings`` is passed to the ``Configurator`` as a keyword argument with the |
SP |
55 |
dictionary values passed as the ``**settings`` argument. This will be a |
6772a2
|
56 |
dictionary of settings parsed from the ``.ini`` file, which contains |
4f87e5
|
57 |
deployment-related values, such as ``pyramid.reload_templates``, |
414b67
|
58 |
``sqlalchemy.url``, and so on. |
0aed1c
|
59 |
|
414b67
|
60 |
Next include :term:`Jinja2` templating bindings so that we can use renderers |
MM |
61 |
with the ``.jinja2`` extension within our project. |
404b28
|
62 |
|
8ec0b0
|
63 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
64 |
:lines: 8 |
7b89a7
|
65 |
:lineno-match: |
8ec0b0
|
66 |
:language: py |
414b67
|
67 |
|
a01c73
|
68 |
Next include the package ``models`` using a dotted Python path. The exact |
4f87e5
|
69 |
setup of the models will be covered later. |
414b67
|
70 |
|
8ec0b0
|
71 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
72 |
:lines: 9 |
7b89a7
|
73 |
:lineno-match: |
8ec0b0
|
74 |
:language: py |
404b28
|
75 |
|
4f87e5
|
76 |
Next include the ``routes`` module using a dotted Python path. This module will |
SP |
77 |
be explained in the next section. |
0aed1c
|
78 |
|
8ec0b0
|
79 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
MM |
80 |
:lines: 10 |
7b89a7
|
81 |
:lineno-match: |
8ec0b0
|
82 |
:language: py |
da5ebc
|
83 |
|
MM |
84 |
.. note:: |
|
85 |
|
4f87e5
|
86 |
Pyramid's :meth:`pyramid.config.Configurator.include` method is the primary |
SP |
87 |
mechanism for extending the configurator and breaking your code into |
|
88 |
feature-focused modules. |
da5ebc
|
89 |
|
MM |
90 |
``main`` next calls the ``scan`` method of the configurator |
|
91 |
(:meth:`pyramid.config.Configurator.scan`), which will recursively scan our |
4f87e5
|
92 |
``tutorial`` package, looking for ``@view_config`` and other special |
SP |
93 |
decorators. When it finds a ``@view_config`` decorator, a view configuration |
|
94 |
will be registered, allowing one of our application URLs to be mapped to some |
|
95 |
code. |
da5ebc
|
96 |
|
MM |
97 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
|
98 |
:lines: 11 |
|
99 |
:lineno-match: |
|
100 |
:language: py |
|
101 |
|
|
102 |
Finally ``main`` is finished configuring things, so it uses the |
|
103 |
:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a |
|
104 |
:term:`WSGI` application: |
|
105 |
|
|
106 |
.. literalinclude:: src/basiclayout/tutorial/__init__.py |
|
107 |
:lines: 12 |
|
108 |
:lineno-match: |
|
109 |
:language: py |
|
110 |
|
|
111 |
|
|
112 |
Route declarations |
|
113 |
------------------ |
|
114 |
|
3caa9d
|
115 |
Open the ``tutorial/routes.py`` file. It should already contain the following: |
da5ebc
|
116 |
|
MM |
117 |
.. literalinclude:: src/basiclayout/tutorial/routes.py |
|
118 |
:linenos: |
|
119 |
:language: py |
|
120 |
|
4f87e5
|
121 |
On line 2, we call :meth:`pyramid.config.Configurator.add_static_view` with |
SP |
122 |
three arguments: ``static`` (the name), ``static`` (the path), and |
|
123 |
``cache_max_age`` (a keyword argument). |
0aed1c
|
124 |
|
6772a2
|
125 |
This registers a static resource view which will match any URL that starts |
b9b2ee
|
126 |
with the prefix ``/static`` (by virtue of the first argument to |
4f87e5
|
127 |
``add_static_view``). This will serve up static resources for us from within |
SP |
128 |
the ``static`` directory of our ``tutorial`` package, in this case via |
6772a2
|
129 |
``http://localhost:6543/static/`` and below (by virtue of the second argument |
f65e19
|
130 |
to ``add_static_view``). With this declaration, we're saying that any URL that |
6772a2
|
131 |
starts with ``/static`` should go to the static view; any remainder of its |
4f87e5
|
132 |
path (e.g., the ``/foo`` in ``/static/foo``) will be used to compose a path to |
6772a2
|
133 |
a static file resource, such as a CSS file. |
0aed1c
|
134 |
|
4f87e5
|
135 |
On line 3, the module registers a :term:`route configuration` via the |
SP |
136 |
:meth:`pyramid.config.Configurator.add_route` method that will be used when the |
|
137 |
URL is ``/``. Since this route has a ``pattern`` equaling ``/``, it is the |
|
138 |
route that will be matched when the URL ``/`` is visited, e.g., |
|
139 |
``http://localhost:6543/``. |
0aed1c
|
140 |
|
392cc1
|
141 |
|
SP |
142 |
View declarations via the ``views`` package |
|
143 |
------------------------------------------- |
e26700
|
144 |
|
e7b210
|
145 |
The main function of a web framework is mapping each URL pattern to code (a |
CM |
146 |
:term:`view callable`) that is executed when the requested URL matches the |
|
147 |
corresponding :term:`route`. Our application uses the |
|
148 |
:meth:`pyramid.view.view_config` decorator to perform this mapping. |
6772a2
|
149 |
|
4f87e5
|
150 |
Open ``tutorial/views/default.py`` in the ``views`` package. It should already |
SP |
151 |
contain the following: |
6772a2
|
152 |
|
8ec0b0
|
153 |
.. literalinclude:: src/basiclayout/tutorial/views/default.py |
MM |
154 |
:linenos: |
|
155 |
:language: py |
6772a2
|
156 |
|
e2919c
|
157 |
The important part here is that the ``@view_config`` decorator associates the |
4f87e5
|
158 |
function it decorates (``my_view``) with a :term:`view configuration`, |
e2919c
|
159 |
consisting of: |
6772a2
|
160 |
|
e2919c
|
161 |
* a ``route_name`` (``home``) |
392cc1
|
162 |
* a ``renderer``, which is a template from the ``templates`` subdirectory of |
SP |
163 |
the package. |
6772a2
|
164 |
|
e2919c
|
165 |
When the pattern associated with the ``home`` view is matched during a request, |
392cc1
|
166 |
``my_view()`` will be executed. ``my_view()`` returns a dictionary; the |
SP |
167 |
renderer will use the ``templates/mytemplate.jinja2`` template to create a |
|
168 |
response based on the values in the dictionary. |
e2919c
|
169 |
|
AR |
170 |
Note that ``my_view()`` accepts a single argument named ``request``. This is |
|
171 |
the standard call signature for a Pyramid :term:`view callable`. |
6772a2
|
172 |
|
CM |
173 |
Remember in our ``__init__.py`` when we executed the |
4f87e5
|
174 |
:meth:`pyramid.config.Configurator.scan` method ``config.scan()``? The purpose |
SP |
175 |
of calling the scan method was to find and process this ``@view_config`` |
|
176 |
decorator in order to create a view configuration within our application. |
|
177 |
Without being processed by ``scan``, the decorator effectively does nothing. |
|
178 |
``@view_config`` is inert without being detected via a :term:`scan`. |
e26700
|
179 |
|
b29848
|
180 |
The sample ``my_view()`` created by the cookiecutter uses a ``try:`` and |
392cc1
|
181 |
``except:`` clause to detect if there is a problem accessing the project |
SP |
182 |
database and provide an alternate error response. That response will include |
|
183 |
the text shown at the end of the file, which will be displayed in the browser |
|
184 |
to inform the user about possible actions to take to solve the problem. |
5fbaf7
|
185 |
|
1b994c
|
186 |
|
0409cf
|
187 |
Content models with the ``models`` package |
SP |
188 |
------------------------------------------ |
fea87a
|
189 |
|
4f87e5
|
190 |
In an SQLAlchemy-based application, a *model* object is an object composed by |
0409cf
|
191 |
querying the SQL database. The ``models`` package is where the ``alchemy`` |
b29848
|
192 |
cookiecutter put the classes that implement our models. |
5edd54
|
193 |
|
4f87e5
|
194 |
First, open ``tutorial/models/meta.py``, which should already contain the |
SP |
195 |
following: |
0409cf
|
196 |
|
8ec0b0
|
197 |
.. literalinclude:: src/basiclayout/tutorial/models/meta.py |
MM |
198 |
:linenos: |
|
199 |
:language: py |
0aed1c
|
200 |
|
7b89a7
|
201 |
``meta.py`` contains imports and support code for defining the models. We |
MM |
202 |
create a dictionary ``NAMING_CONVENTION`` as well for consistent naming of |
|
203 |
support objects like indices and constraints. |
0aed1c
|
204 |
|
8ec0b0
|
205 |
.. literalinclude:: src/basiclayout/tutorial/models/meta.py |
MM |
206 |
:end-before: metadata |
|
207 |
:linenos: |
|
208 |
:language: py |
0aed1c
|
209 |
|
0409cf
|
210 |
Next we create a ``metadata`` object from the class |
SP |
211 |
:class:`sqlalchemy.schema.MetaData`, using ``NAMING_CONVENTION`` as the value |
7b89a7
|
212 |
for the ``naming_convention`` argument. |
MM |
213 |
|
4f87e5
|
214 |
A ``MetaData`` object represents the table and other schema definitions for a |
SP |
215 |
single database. We also need to create a declarative ``Base`` object to use as |
|
216 |
a base class for our models. Our models will inherit from this ``Base``, which |
|
217 |
will attach the tables to the ``metadata`` we created, and define our |
7b89a7
|
218 |
application's database schema. |
a7a639
|
219 |
|
8ec0b0
|
220 |
.. literalinclude:: src/basiclayout/tutorial/models/meta.py |
7b89a7
|
221 |
:lines: 15-16 |
MM |
222 |
:lineno-match: |
8ec0b0
|
223 |
:language: py |
0aed1c
|
224 |
|
4f87e5
|
225 |
Next open ``tutorial/models/mymodel.py``, which should already contain the |
SP |
226 |
following: |
|
227 |
|
|
228 |
.. literalinclude:: src/basiclayout/tutorial/models/mymodel.py |
|
229 |
:linenos: |
|
230 |
:language: py |
|
231 |
|
|
232 |
Notice we've defined the ``models`` as a package to make it straightforward for |
|
233 |
defining models in separate modules. To give a simple example of a model class, |
|
234 |
we have defined one named ``MyModel`` in ``mymodel.py``: |
0409cf
|
235 |
|
8ec0b0
|
236 |
.. literalinclude:: src/basiclayout/tutorial/models/mymodel.py |
MM |
237 |
:pyobject: MyModel |
4f87e5
|
238 |
:lineno-match: |
8ec0b0
|
239 |
:language: py |
0aed1c
|
240 |
|
db51d2
|
241 |
Our example model does not require an ``__init__`` method because SQLAlchemy |
4f87e5
|
242 |
supplies for us a default constructor, if one is not already present, which |
0409cf
|
243 |
accepts keyword arguments of the same name as that of the mapped attributes. |
db51d2
|
244 |
|
ca51e1
|
245 |
.. note:: Example usage of MyModel: |
BJR |
246 |
|
|
247 |
.. code-block:: python |
|
248 |
|
|
249 |
johnny = MyModel(name="John Doe", value=10) |
|
250 |
|
db51d2
|
251 |
The ``MyModel`` class has a ``__tablename__`` attribute. This informs |
e7b210
|
252 |
SQLAlchemy which table to use to store the data representing instances of this |
CM |
253 |
class. |
0aed1c
|
254 |
|
d1cb34
|
255 |
Finally, open ``tutorial/models/__init__.py``, which should already |
7b89a7
|
256 |
contain the following: |
MM |
257 |
|
|
258 |
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py |
|
259 |
:linenos: |
|
260 |
:language: py |
|
261 |
|
|
262 |
Our ``models/__init__.py`` module defines the primary API we will use for |
ab68f1
|
263 |
configuring the database connections within our application, and it contains |
7b89a7
|
264 |
several functions we will cover below. |
MM |
265 |
|
4f87e5
|
266 |
As we mentioned above, the purpose of the ``models.meta.metadata`` object is to |
SP |
267 |
describe the schema of the database. This is done by defining models that |
|
268 |
inherit from the ``Base`` object attached to that ``metadata`` object. In |
|
269 |
Python, code is only executed if it is imported, and so to attach the |
|
270 |
``models`` table defined in ``mymodel.py`` to the ``metadata``, we must import |
|
271 |
it. If we skip this step, then later, when we run |
ab68f1
|
272 |
:meth:`sqlalchemy.schema.MetaData.create_all`, the table will not be created |
4f87e5
|
273 |
because the ``metadata`` object does not know about it! |
21d69f
|
274 |
|
ab68f1
|
275 |
Another important reason to import all of the models is that, when defining |
SP |
276 |
relationships between models, they must all exist in order for SQLAlchemy to |
|
277 |
find and build those internal mappings. This is why, after importing all the |
|
278 |
models, we explicitly execute the function |
7b89a7
|
279 |
:func:`sqlalchemy.orm.configure_mappers`, once we are sure all the models have |
MM |
280 |
been defined and before we start creating connections. |
|
281 |
|
ab68f1
|
282 |
Next we define several functions for connecting to our database. The first and |
SP |
283 |
lowest level is the ``get_engine`` function. This creates an :term:`SQLAlchemy` |
|
284 |
database engine using :func:`sqlalchemy.engine_from_config` from the |
|
285 |
``sqlalchemy.``-prefixed settings in the ``development.ini`` file's |
|
286 |
``[app:main]`` section. This setting is a URI (something like ``sqlite://``). |
7b89a7
|
287 |
|
MM |
288 |
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py |
|
289 |
:pyobject: get_engine |
|
290 |
:lineno-match: |
|
291 |
:language: py |
|
292 |
|
|
293 |
The function ``get_session_factory`` accepts an :term:`SQLAlchemy` database |
ab68f1
|
294 |
engine, and creates a ``session_factory`` from the :term:`SQLAlchemy` class |
SP |
295 |
:class:`sqlalchemy.orm.session.sessionmaker`. This ``session_factory`` is then |
|
296 |
used for creating sessions bound to the database engine. |
7b89a7
|
297 |
|
MM |
298 |
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py |
|
299 |
:pyobject: get_session_factory |
|
300 |
:lineno-match: |
|
301 |
:language: py |
|
302 |
|
|
303 |
The function ``get_tm_session`` registers a database session with a transaction |
|
304 |
manager, and returns a ``dbsession`` object. With the transaction manager, our |
ab68f1
|
305 |
application will automatically issue a transaction commit after every request, |
7b89a7
|
306 |
unless an exception is raised, in which case the transaction will be aborted. |
MM |
307 |
|
|
308 |
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py |
|
309 |
:pyobject: get_tm_session |
|
310 |
:lineno-match: |
|
311 |
:language: py |
|
312 |
|
|
313 |
Finally, we define an ``includeme`` function, which is a hook for use with |
|
314 |
:meth:`pyramid.config.Configurator.include` to activate code in a Pyramid |
ab68f1
|
315 |
application add-on. It is the code that is executed above when we ran |
7b89a7
|
316 |
``config.include('.models')`` in our application's ``main`` function. This |
4f87e5
|
317 |
function will take the settings from the application, create an engine, and |
SP |
318 |
define a ``request.dbsession`` property, which we can use to do work on behalf |
|
319 |
of an incoming request to our application. |
7b89a7
|
320 |
|
MM |
321 |
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py |
|
322 |
:pyobject: includeme |
|
323 |
:lineno-match: |
|
324 |
:language: py |
|
325 |
|
e7b210
|
326 |
That's about all there is to it regarding models, views, and initialization |
CM |
327 |
code in our stock application. |
2f6864
|
328 |
|
7b89a7
|
329 |
The ``Index`` import and the ``Index`` object creation in ``mymodel.py`` is |
MM |
330 |
not required for this tutorial, and will be removed in the next step. |