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
|
============
Basic Layout
============
The starter files generated by the ``alchemy`` scaffold are very basic, but
they provide a good orientation for the high-level patterns common to most
:term:`URL dispatch`-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's contained is a package, and to contain application configuration
code.
Open ``tutorial/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: main
:linenos:
:language: py
``__init__.py`` defines a function named ``main``. Here is the entirety of
the ``main`` function we've defined in our ``__init__.py``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:pyobject: main
:linenos:
: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``.)
The main function first creates a :term:`SQLAlchemy` database engine using
:func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.`` prefixed
settings in the ``development.ini`` file's ``[app:main]`` section.
This will be a URI (something like ``sqlite://``):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 13
:language: py
``main`` then initializes our SQLAlchemy session object, passing it the
engine:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 14
:language: py
``main`` subsequently initializes our SQLAlchemy declarative ``Base`` object,
assigning the engine we created to the ``bind`` attribute of it's
``metadata`` object. This allows table definitions done imperatively
(instead of declaratively, via a class statement) to work. We won't use any
such tables in our application, but if you add one later, long after you've
forgotten about this tutorial, you won't be left scratching your head when it
doesn't work.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 15
:language: py
The next step of ``main`` is to construct a :term:`Configurator` object:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 16
: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``,
``db_string``, etc.
Next, include :term:`Chameleon` templating bindings so that we can use
renderers with the ``.pt`` extension within our project.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 17
:language: py
``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
two arguments: ``static`` (the name), and ``static`` (the path):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 18
:language: py
This registers a static resource view which will match any URL that starts
with the prefix ``/static`` (by virtue of the first argument to
``add_static_view``). This will serve up static resources for us from within
the ``static`` directory of our ``tutorial`` package, in this case, via
``http://localhost:6543/static/`` and below (by virtue of the second argument
to ``add_static_view``). With this declaration, we're saying that any URL that
starts with ``/static`` should go to the static view; any remainder of its
path (e.g. the ``/foo`` in ``/static/foo``) will be used to compose a path to
a static file resource, such as a CSS file.
Using the configurator ``main`` also registers a :term:`route configuration`
via the :meth:`pyramid.config.Configurator.add_route` method that will be
used when the URL is ``/``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 19
:language: py
Since this route has a ``pattern`` equaling ``/`` it is the route that will
be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``.
``main`` next calls the ``scan`` method of the configurator
(:meth:`pyramid.config.Configurator.scan`), which will recursively scan our
``tutorial`` package, looking for ``@view_config`` (and
other special) decorators. When it finds a ``@view_config`` decorator, a
view configuration will be registered, which will allow one of our
application URLs to be mapped to some code.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 20
:language: py
Finally, ``main`` is finished configuring things, so it uses the
:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a
:term:`WSGI` application:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 21
:language: py
View declarations via ``views.py``
----------------------------------
The main function of a web framework is mapping each URL pattern to code (a
:term:`view callable`) that is executed when the requested URL matches the
corresponding :term:`route`. Our application uses the
:meth:`pyramid.view.view_config` decorator to perform this mapping.
Open ``tutorial/tutorial/views.py``. It should already contain the following:
.. literalinclude:: src/basiclayout/tutorial/views.py
:linenos:
:language: py
The important part here is that the ``@view_config`` decorator associates the
function it decorates (``my_view``) with a :term:`view configuration`,
consisting of:
* a ``route_name`` (``home``)
* a ``renderer``, which is a template from the ``templates`` subdirectory
of the package.
When the pattern associated with the ``home`` view is matched during a request,
``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
renderer will use the ``templates/mytemplate.pt`` template to create a response
based on the values in the dictionary.
Note that ``my_view()`` accepts a single argument named ``request``. This is
the standard call signature for a Pyramid :term:`view callable`.
Remember in our ``__init__.py`` when we executed the
:meth:`pyramid.config.Configurator.scan` method ``config.scan()``? The
purpose of calling the scan method was to find and process this
``@view_config`` decorator in order to 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 sample ``my_view()`` created by the scaffold uses a ``try:`` and ``except:``
clause to detect if there is a problem accessing the project database and
provide an alternate error response. That response will include the text
shown at the end of the file, which will be displayed in the browser to
inform the user about possible actions to take to solve the problem.
Content Models with ``models.py``
---------------------------------
In a SQLAlchemy-based application, a *model* object is an object composed by
querying the SQL database. The ``models.py`` file is where the ``alchemy``
scaffold put the classes that implement our models.
Open ``tutorial/tutorial/models.py``. It should already contain the following:
.. literalinclude:: src/basiclayout/tutorial/models.py
:linenos:
:language: py
Let's examine this in detail. First, we need some imports to support later code:
.. literalinclude:: src/basiclayout/tutorial/models.py
:end-before: DBSession
:linenos:
:language: py
Next we set up a SQLAlchemy ``DBSession`` object:
.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 17
:language: py
``scoped_session`` and ``sessionmaker`` are standard SQLAlchemy helpers.
``scoped_session`` allows us to access our database connection globally.
``sessionmaker`` creates a database session object. We pass to
``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension
option in order to allow the system to automatically manage database
transactions. With ``ZopeTransactionExtension`` activated, our application
will automatically issue a transaction commit after every request unless an
exception is raised, in which case the transaction will be aborted.
We also need to create a declarative ``Base`` object to use as a
base class for our model:
.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 18
:language: py
Our model classes will inherit from this ``Base`` class so they can be
associated with our particular database connection.
To give a simple example of a model class, we define one named ``MyModel``:
.. literalinclude:: src/basiclayout/tutorial/models.py
:pyobject: MyModel
:linenos:
:language: py
Our example model does not require an ``__init__`` method because SQLAlchemy
supplies for us a default constructor if one is not already present,
which accepts keyword arguments of the same name as that of the mapped attributes.
.. note:: Example usage of MyModel:
.. code-block:: python
johnny = MyModel(name="John Doe", value=10)
The ``MyModel`` class has a ``__tablename__`` attribute. This informs
SQLAlchemy which table to use to store the data representing instances of this
class.
The Index import and the Index object creation is not required for this
tutorial, and will be removed in the next step.
That's about all there is to it regarding models, views, and initialization
code in our stock application.
|