Skip to content

Commit

Permalink
Merge pull request #2112 from stevepiercy/feature/alchemy-scaffold-up…
Browse files Browse the repository at this point in the history
…date

Finish work on basiclayout.rst and its src at wiki2/src/basiclayout/
  • Loading branch information
stevepiercy committed Nov 12, 2015
2 parents 2aba031 + 0409cf1 commit 16a490f
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 147 deletions.
147 changes: 77 additions & 70 deletions docs/tutorials/wiki2/basiclayout.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,22 @@ Finally ``main`` is finished configuring things, so it uses the
:term:`WSGI` application:

.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 21
:lines: 13
:language: py

View declarations via ``views.py``
----------------------------------

View declarations via the ``views`` package
-------------------------------------------

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:
Open ``tutorial/tutorial/views/default.py`` in the ``views`` package. It
should already contain the following:

.. literalinclude:: src/basiclayout/tutorial/views.py
.. literalinclude:: src/basiclayout/tutorial/views/default.py
:linenos:
:language: py

Expand All @@ -135,13 +137,13 @@ 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.
* 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.
``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
renderer will use the ``templates/mytemplate.jinja2`` 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`.
Expand All @@ -154,100 +156,105 @@ 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.
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``
---------------------------------
Content models with the ``models`` package
------------------------------------------

.. START moved from Application configuration with ``__init__.py``. This
section is a WIP, and needs to be updated using the new models package.
In a SQLAlchemy-based application, a *model* object is an object composed by
querying the SQL database. The ``models`` package is where the ``alchemy``
scaffold put the classes that implement our models.

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://``):
First, open ``tutorial/tutorial/models/__init__.py``, which should already
contain the following:

.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 13
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:linenos:
:language: py

``main`` then initializes our SQLAlchemy session object, passing it the
engine:
Our ``__init__.py`` will perform some imports to support later code, then calls
the function :func:`sqlalchemy.orm.configure_mappers`.

.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 14
Next open ``tutorial/tutorial/models/meta.py``, which should already contain
the following:

.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:linenos:
: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.
``meta.py`` contains imports that are used to support later code. We create a
dictionary ``NAMING_CONVENTION`` as well.

.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 15
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:end-before: metadata
:linenos:
:language: py

.. END moved from Application configuration with ``__init__.py``
Next we create a ``metadata`` object from the class
:class:`sqlalchemy.schema.MetaData`, using ``NAMING_CONVENTION`` as the value
for the ``naming_convention`` argument. We also need to create a declarative
``Base`` object to use as a base class for our model. Then our model classes
will inherit from the ``Base`` class so they can be associated with our
particular database connection.

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.
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:lines: 15-16
:lineno-start: 15
:linenos:
:language: py

Open ``tutorial/tutorial/models.py``. It should already contain the following:
Next we define several functions, the first of which is ``includeme``, which
configures various database settings by calling subsequently defined functions.

.. literalinclude:: src/basiclayout/tutorial/models.py
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:pyobject: includeme
:linenos:
:language: py

Let's examine this in detail. First, we need some imports to support later code:
The function ``get_session`` registers a database session with a transaction
manager, and returns a ``dbsession`` object. With the transaction manager, our
application will automatically issue a transaction commit after every request
unless an exception is raised, in which case the transaction will be aborted.

.. literalinclude:: src/basiclayout/tutorial/models.py
:end-before: DBSession
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:pyobject: get_session
:linenos:
:language: py

Next we set up a SQLAlchemy ``DBSession`` object:
The ``get_engine`` function creates an :term:`SQLAlchemy` database engine using
:func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.``-prefixed
settings in the ``development.ini`` file's ``[app:main]`` section, which is a
URI, something like ``sqlite://``.

.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 17
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:pyobject: get_engine
:linenos:
: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:
The function ``get_dbmaker`` accepts an :term:`SQLAlchemy` database engine,
and creates a database session object ``dbmaker`` from the :term:`SQLAlchemy`
class :class:`sqlalchemy.orm.session.sessionmaker`, which is then used for
creating a session with the database engine.

.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 17
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:pyobject: get_dbmaker
:linenos:
: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``:
To give a simple example of a model class, we define one named ``MyModel``:

.. literalinclude:: src/basiclayout/tutorial/models.py
.. literalinclude:: src/basiclayout/tutorial/models/mymodel.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.
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:

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/wiki2/src/basiclayout/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

requires = [
'pyramid',
'pyramid_chameleon',
'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
Expand Down
27 changes: 0 additions & 27 deletions docs/tutorials/wiki2/src/basiclayout/tutorial/models.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from sqlalchemy.orm import configure_mappers
# import all models classes here for sqlalchemy mappers
# to pick up
from .mymodel import MyModel # flake8: noqa

# run configure mappers to ensure we avoid any race conditions
configure_mappers()
46 changes: 46 additions & 0 deletions docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from sqlalchemy import engine_from_config
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import MetaData
import zope.sqlalchemy

NAMING_CONVENTION = {
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
}

metadata = MetaData(naming_convention=NAMING_CONVENTION)
Base = declarative_base(metadata=metadata)


def includeme(config):
settings = config.get_settings()
dbmaker = get_dbmaker(get_engine(settings))

config.add_request_method(
lambda r: get_session(r.tm, dbmaker),
'dbsession',
reify=True
)

config.include('pyramid_tm')


def get_session(transaction_manager, dbmaker):
dbsession = dbmaker()
zope.sqlalchemy.register(dbsession,
transaction_manager=transaction_manager)
return dbsession


def get_engine(settings, prefix='sqlalchemy.'):
return engine_from_config(settings, prefix)


def get_dbmaker(engine):
dbmaker = sessionmaker()
dbmaker.configure(bind=engine)
return dbmaker
17 changes: 17 additions & 0 deletions docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .meta import Base
from sqlalchemy import (
Column,
Index,
Integer,
Text,
)


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


Index('my_index', MyModel.name, unique=True, mysql_length=255)
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,44 @@
import sys
import transaction

from sqlalchemy import engine_from_config

from pyramid.paster import (
get_appsettings,
setup_logging,
)

from ..models import (
DBSession,
MyModel,
from pyramid.scripts.common import parse_vars

from ..models.meta import (
Base,
get_session,
get_engine,
get_dbmaker,
)
from ..models.mymodel import MyModel


def usage(argv):
cmd = os.path.basename(argv[0])
print('usage: %s <config_uri>\n'
print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)


def main(argv=sys.argv):
if len(argv) != 2:
if len(argv) < 2:
usage(argv)
config_uri = argv[1]
options = parse_vars(argv[2:])
setup_logging(config_uri)
settings = get_appsettings(config_uri)
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
settings = get_appsettings(config_uri, options=options)

engine = get_engine(settings)
dbmaker = get_dbmaker(engine)

dbsession = get_session(transaction.manager, dbmaker)

Base.metadata.create_all(engine)

with transaction.manager:
model = MyModel(name='one', value=1)
DBSession.add(model)
dbsession.add(model)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 16a490f

Please sign in to comment.