From f83c602874698427bdc141accd8fc14a9749d6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Feb 2015 23:19:06 +0100 Subject: docs: Add explanations about SQLAlchemy's scoped_session. --- docs/conf.py | 4 +-- docs/orms.rst | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/conf.py b/docs/conf.py index 4f76d45..ce6730b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -247,7 +247,7 @@ intersphinx_mapping = { 'http://docs.djangoproject.com/en/dev/_objects/', ), 'sqlalchemy': ( - 'http://docs.sqlalchemy.org/en/rel_0_8/', - 'http://docs.sqlalchemy.org/en/rel_0_8/objects.inv', + 'http://docs.sqlalchemy.org/en/rel_0_9/', + 'http://docs.sqlalchemy.org/en/rel_0_9/objects.inv', ), } diff --git a/docs/orms.rst b/docs/orms.rst index e32eafa..ab813a2 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -298,9 +298,8 @@ A (very) simple exemple: from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker - session = scoped_session(sessionmaker()) engine = create_engine('sqlite://') - session.configure(bind=engine) + session = scoped_session(sessionmaker(bind=engine)) Base = declarative_base() @@ -330,3 +329,102 @@ A (very) simple exemple: >>> session.query(User).all() [] + + +Managing sessions +""""""""""""""""" + +Since `SQLAlchemy`_ is a general purpose library, +there is no "global" session management system. + +The most common pattern when working with unit tests and ``factory_boy`` +is to use `SQLAlchemy`_'s :class:`sqlalchemy.orm.scoping.scoped_session`: + +* The test runner configures some project-wide :class:`~sqlalchemy.orm.scoping.scoped_session` +* Each :class:`~SQLAlchemyModelFactory` subclass uses this + :class:`~sqlalchemy.orm.scoping.scoped_session` as its :attr:`~SQLAlchemyOptions.sqlalchemy_session` +* The :meth:`~unittest.TestCase.tearDown` method of tests calls + :meth:`Session.remove ` + to reset the session. + + +Here is an example layout: + +- A global (test-only?) file holds the :class:`~sqlalchemy.orm.scoping.scoped_session`: + +.. code-block:: python + + # myprojet/test/common.py + + from sqlalchemy import orm + Session = orm.scoped_session(orm.sessionmaker()) + + +- All factory access it: + +.. code-block:: python + + # myproject/factories.py + + import factory + import factory.alchemy + + from . import models + from .test import common + + class UserFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = models.User + + # Use the not-so-global scoped_session + # Warning: DO NOT USE common.Session()! + sqlalchemy_session = common.Session + + name = factory.Sequence(lambda n: "User %d" % n) + + +- The test runner configures the :class:`~sqlalchemy.orm.scoping.scoped_session` when it starts: + +.. code-block:: python + + # myproject/test/runtests.py + + import sqlalchemy + + from . import common + + def runtests(): + engine = sqlalchemy.create_engine('sqlite://') + + # It's a scoped_session, we can configure it later + common.Session.configure(engine=engine) + + run_the_tests + + +- :class:`test cases ` use this ``scoped_session``, + and clear it after each test: + +.. code-block:: python + + # myproject/test/test_stuff.py + + import unittest + + from . import common + + class MyTest(unittest.TestCase): + + def setUp(self): + # Prepare a new, clean session + self.session = common.Session() + + def test_something(self): + u = factories.UserFactory() + self.assertEqual([u], self.session.query(User).all()) + + def tearDown(self): + # Rollback the session => no changes to the database + self.session.rollback() + # Remove it, so that the next test gets a new Session() + common.Session.remove() -- cgit v1.2.3