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.
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
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') 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 6 7 8 9 from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, )
__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 11 12 def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') 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
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://
):
engine = engine_from_config(settings, 'sqlalchemy.')
main
then initializes our SQLAlchemy session object, passing it the
engine:
DBSession.configure(bind=engine)
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.
Base.metadata.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.
Next, include Chameleon templating bindings so that we can use
renderers with the .pt
extension within our project.
config.include('pyramid_chameleon')
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
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
(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.
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
¶
The main function of a web framework is mapping each URL pattern to code (a
view callable) that is executed when the requested URL matches the
corresponding route. Our application uses the
pyramid.view.view_config()
decorator to perform this mapping.
Open tutorial/tutorial/views.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 29 30 31 32 33 34 35 from pyramid.response import Response from pyramid.view import view_config from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'tutorial'} conn_err_msg = """\ Pyramid is having a problem using your SQL database. The problem might be caused by one of the following things: 1. You may need to run the "initialize_tutorial_db" script to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """
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 thetemplates
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 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.
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:
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 from sqlalchemy import ( Column, Integer, Text, Index, ) 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) Index('my_index', MyModel.name, unique=True, mysql_length=255)
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 16 from sqlalchemy import ( Column, Integer, Text, Index, ) 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:
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 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:
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 class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) value = Column(Integer)
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:
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.