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 url dispatch -based Pyramid projects.

The source code for this tutorial stage can be browsed at http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/basiclayout/.

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 it’s contained within is a package, and to contain configuration code.

Open tutorial/tutorial/__init__.py. It should already contain the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from pyramid.config import Configurator
from sqlalchemy import engine_from_config

from .models import DBSession

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.scan()
    return config.make_wsgi_app()

Let’s go over this piece-by-piece. First, we need some imports to support later code:

1
2
3
4
5
from pyramid.config import Configurator
from sqlalchemy import engine_from_config

from .models import DBSession

__init__.py defines a function named main. Here is the entirety of the main function we’ve defined in our __init__.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.scan()
    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.)

The main function first creates a SQLAlchemy database engine using 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://):

1
    engine = engine_from_config(settings, 'sqlalchemy.')

main then initializes our SQL database using SQLAlchemy, passing it the engine:

    DBSession.configure(bind=engine)

The next step of main is to construct a Configurator object:

    config = Configurator(settings=settings)

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.

main now calls pyramid.config.Configurator.add_static_view() with two arguments: static (the name), and static (the path):

    config.add_static_view('static', 'static', cache_max_age=3600)

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 route configuration via the pyramid.config.Configurator.add_route() method that will be used when the URL is /:

    config.add_route('home', '/')

Since this route has a pattern equalling / it is the route that will be matched when the URL / is visted, e.g. http://localhost:6543/.

main next calls the scan method of the configurator, 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.

    config.scan()

Finally, main is finished configuring things, so it uses the pyramid.config.Configurator.make_wsgi_app() method to return a WSGI application:

    return config.make_wsgi_app()

View Declarations via views.py

Mapping a route to code that will be executed when a match for the route’s pattern occurs is done by registering a view configuration. Our application uses the pyramid.view.view_config() decorator to map view callables to each route, thereby mapping URL patterns to code.

Open tutorial/tutorial/views.py. It should already contain the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pyramid.view import view_config

from .models import (
    DBSession,
    MyModel,
    )

@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
    one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
    return {'one':one, 'project':'tutorial'}

The important part here is that the @view_config decorator associates the function it decorates (my_view) with a 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 view callable.

Remember in our __init__.py when we executed the pyramid.config.Configurator.scan() method, i.e. 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 scan.

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:

 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
from sqlalchemy import (
    Column,
    Integer,
    Text,
    )

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    value = Column(Integer)

    def __init__(self, name, value):
        self.name = name
        self.value = value

Let’s examine this in detail. First, we need some imports to support later code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from sqlalchemy import (
    Column,
    Integer,
    Text,
    )

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

from zope.sqlalchemy import ZopeTransactionExtension

Next we set up a SQLAlchemy “DBSession” object:

1
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))

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 datbase 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:

Base = declarative_base()

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:

1
2
3
4
5
6
7
8
9
class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    value = Column(Integer)

    def __init__(self, name, value):
        self.name = name
        self.value = value

Our example model has an __init__ that takes a two arguments (name, and value). It stores these values as self.name and self.value within the __init__ function itself. The MyModel class also has a __tablename__ attribute. This informs SQLAlchemy which table to use to store the data representing instances of this class.

That’s about all there is to it to models, views, and initialization code in our stock application.