From d6243ac1e7724cce26a738de5b86f187ef444e77 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 7 Feb 2016 15:29:17 -0600 Subject: update definingmodels chapter of wiki2 tutorial --- docs/tutorials/wiki2/definingmodels.rst | 32 +++++----- docs/tutorials/wiki2/installation.rst | 7 +++ docs/tutorials/wiki2/src/models/MANIFEST.in | 2 +- docs/tutorials/wiki2/src/models/production.ini | 2 - .../wiki2/src/models/tutorial/__init__.py | 2 +- .../wiki2/src/models/tutorial/models/__init__.py | 71 +++++++++++++++++++++- .../wiki2/src/models/tutorial/models/meta.py | 33 ---------- .../wiki2/src/models/tutorial/models/mymodel.py | 3 +- .../src/models/tutorial/scripts/initializedb.py | 27 ++++---- .../wiki2/src/models/tutorial/templates/404.jinja2 | 8 +++ docs/tutorials/wiki2/src/models/tutorial/tests.py | 18 +++--- .../wiki2/src/models/tutorial/views/default.py | 4 +- .../wiki2/src/models/tutorial/views/errors.py | 5 ++ 13 files changed, 133 insertions(+), 81 deletions(-) create mode 100644 docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 create mode 100644 docs/tutorials/wiki2/src/models/tutorial/views/errors.py (limited to 'docs') diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 9b517994f..b90bf77e6 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -3,7 +3,7 @@ Defining the Domain Model ========================= The first change we'll make to our stock ``pcreate``-generated application will -be to define a :term:`domain model` constructor representing a wiki page. +be to define a wiki page :term:`domain model`. We'll do this inside our ``mymodel.py`` file. @@ -12,12 +12,12 @@ Edit ``mymodel.py`` .. note:: - There is nothing special about the filename ``mymodel.py``. A - project may have many models throughout its codebase in arbitrarily named - modules. Modules implementing models often have ``model`` in their - names or they may live in a Python subpackage of your application package - named ``models`` (as we've done in this tutorial), but this is only a - convention and not a requirement. + There is nothing special about the filename ``mymodel.py`` except that it + is a Python module. A project may have many models throughout its codebase + in arbitrarily named modules. Modules implementing models often have + ``model`` in their names or they may live in a Python subpackage of your + application package named ``models`` (as we've done in this tutorial), but + this is only a convention and not a requirement. Open the ``tutorial/models/mymodel.py`` file and edit it to look like the following: @@ -25,7 +25,7 @@ the following: .. literalinclude:: src/models/tutorial/models/mymodel.py :linenos: :language: py - :emphasize-lines: 9-11,13,14 + :emphasize-lines: 10-12,14-15 The highlighted lines are the ones that need to be changed, as well as removing lines that reference ``Index``. @@ -34,7 +34,7 @@ The first thing we've done is remove the stock ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a sample and we're not going to use it. -Then we added a ``Page`` class. Because this is an SQLAlchemy application, +Then we added a ``Page`` class. Because this is a SQLAlchemy application, this class inherits from an instance of :func:`sqlalchemy.ext.declarative.declarative_base`. @@ -43,7 +43,7 @@ this class inherits from an instance of :linenos: :language: python -As you can see, our ``Page`` class has a class level attribute +As you can see, our ``Page`` class has a class-level attribute ``__tablename__`` which equals the string ``'pages'``. This means that SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our ``Page`` class will also have class-level attributes named ``id``, ``name``, @@ -58,7 +58,7 @@ Edit ``models/__init__.py`` --------------------------- Since we are using a package for our models, we also need to update our -``__init__.py`` file. +``__init__.py`` file to ensure that the model is attached to the metadata. Open the ``tutorial/models/__init__.py`` file and edit it to look like the following: @@ -66,7 +66,7 @@ the following: .. literalinclude:: src/models/tutorial/models/__init__.py :linenos: :language: py - :emphasize-lines: 4 + :emphasize-lines: 8 Here we need to align our import with the name of the model ``Page``. @@ -83,7 +83,7 @@ Since we've changed our model, we need to make changes to our ``initializedb.py`` script. In particular, we'll replace our import of ``MyModel`` with one of ``Page`` and we'll change the very end of the script to create a ``Page`` rather than a ``MyModel`` and add it to our -``DBSession``. +``dbsession``. Open ``tutorial/scripts/initializedb.py`` and edit it to look like the following: @@ -91,11 +91,9 @@ the following: .. literalinclude:: src/models/tutorial/scripts/initializedb.py :linenos: :language: python - :emphasize-lines: 16,31,41 + :emphasize-lines: 18,44-45 -Only the highlighted lines need to be changed, as well as removing the lines -referencing ``pyramid.scripts.common`` and ``options`` under the ``main`` -function. +Only the highlighted lines need to be changed. Installing the project and re-initializing the database diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 70d0444b7..5d6d8e56b 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -298,6 +298,13 @@ Initializing the database We need to use the ``initialize_tutorial_db`` :term:`console script` to initialize our database. +.. note:: + + The ``initialize_tutorial_db`` command is not performing a migration but + rather simply creating missing tables and adding some dummy data. If you + already have a database, you should delete it before running + ``initialize_tutorial_db`` again. + Type the following command, making sure you are still in the ``tutorial`` directory (the directory with a ``development.ini`` in it): diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in index 81beba1b1..42cd299b5 100644 --- a/docs/tutorials/wiki2/src/models/MANIFEST.in +++ b/docs/tutorials/wiki2/src/models/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index 97acfbd7d..cb1db3211 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -11,8 +11,6 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 7994bbfa8..17763812a 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -6,7 +6,7 @@ def main(global_config, **settings): """ config = Configurator(settings=settings) config.include('pyramid_jinja2') - config.include('.models.meta') + config.include('.models') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py index 7b1c62867..4810c357a 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py @@ -1,7 +1,72 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import configure_mappers -# import all models classes here for sqlalchemy mappers -# to pick up +import zope.sqlalchemy + +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines from .mymodel import Page # flake8: noqa -# run configure mappers to ensure we avoid any race conditions +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker() + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('tutorial.models')``. + + """ + settings = config.get_settings() + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + session_factory = get_session_factory(get_engine(settings)) + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py index 80ececd8c..fc3e8f1dd 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py @@ -1,8 +1,5 @@ -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 # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more @@ -17,33 +14,3 @@ NAMING_CONVENTION = { 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 diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py index 45571d78e..b23d0c0d2 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py @@ -1,10 +1,11 @@ -from .meta import Base from sqlalchemy import ( Column, Integer, Text, ) +from .meta import Base + class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py index 4aac4a848..601a6e73f 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py @@ -7,13 +7,15 @@ from pyramid.paster import ( setup_logging, ) -from ..models.meta import ( - Base, - get_session, +from pyramid.scripts.common import parse_vars + +from ..models.meta import Base +from ..models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) -from ..models.mymodel import Page +from ..models import Page def usage(argv): @@ -27,16 +29,17 @@ def main(argv=sys.argv): if len(argv) < 2: usage(argv) config_uri = argv[1] + options = parse_vars(argv[2:]) setup_logging(config_uri) - settings = get_appsettings(config_uri) + 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) + session_factory = get_session_factory(engine) + with transaction.manager: - model = Page(name='FrontPage', data='This is the front page') - dbsession.add(model) + dbsession = get_tm_session(session_factory, transaction.manager) + + page = Page(name='FrontPage', data='This is the front page') + dbsession.add(page) diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 new file mode 100644 index 000000000..1917f83c7 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +
+

Pyramid Alchemy scaffold

+

404 Page Not Found

+
+{% endblock content %} diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index b947e3bb1..c54945c28 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -13,22 +13,22 @@ class BaseTest(unittest.TestCase): self.config = testing.setUp(settings={ 'sqlalchemy.url': 'sqlite:///:memory:' }) - self.config.include('.models.meta') + self.config.include('.models') settings = self.config.get_settings() - from .models.meta import ( - get_session, + from .models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) self.engine = get_engine(settings) - dbmaker = get_dbmaker(self.engine) + session_factory = get_session_factory(self.engine) - self.session = get_session(transaction.manager, dbmaker) + self.session = get_tm_session(session_factory, transaction.manager) def init_database(self): - from .models.meta import Base + from .models import Base Base.metadata.create_all(self.engine) def tearDown(self): @@ -36,7 +36,7 @@ class BaseTest(unittest.TestCase): testing.tearDown() transaction.abort() - Base.metadata.create_all(self.engine) + Base.metadata.drop_all(self.engine) class TestMyViewSuccessCondition(BaseTest): @@ -45,7 +45,7 @@ class TestMyViewSuccessCondition(BaseTest): super(TestMyViewSuccessCondition, self).setUp() self.init_database() - from .models.mymodel import MyModel + from .models import MyModel model = MyModel(name='one', value=55) self.session.add(model) diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py index 13ad8793c..ad0c728d7 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py @@ -3,7 +3,7 @@ from pyramid.view import view_config from sqlalchemy.exc import DBAPIError -from ..models.mymodel import MyModel +from ..models import MyModel @view_config(route_name='home', renderer='../templates/mytemplate.jinja2') @@ -12,7 +12,7 @@ def my_view(request): query = request.dbsession.query(MyModel) one = query.filter(MyModel.name == 'one').first() except DBAPIError: - return Response(db_err_msg, content_type='text/plain', status_int=500) + return Response(db_err_msg, content_type='text/plain', status=500) return {'one': one, 'project': 'tutorial'} diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/errors.py b/docs/tutorials/wiki2/src/models/tutorial/views/errors.py new file mode 100644 index 000000000..a4b8201f1 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/views/errors.py @@ -0,0 +1,5 @@ +from pyramid.view import notfound_view_config + +@notfound_view_config(renderer='../templates/404.jinja2') +def notfound_view(request): + return {} -- cgit v1.2.3