OdinsGenre
2018-09-01 a01c73ac2543ea09d87365f17566f1f546471b23
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.