Skip to content

Commit

Permalink
docs: Add explanations about SQLAlchemy's scoped_session.
Browse files Browse the repository at this point in the history
  • Loading branch information
rbarrois committed Feb 6, 2015
1 parent 392db86 commit f83c602
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 4 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def get_version(*module_dir_components):
'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',
),
}
102 changes: 100 additions & 2 deletions docs/orms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -330,3 +329,102 @@ A (very) simple exemple:
<User: User 1>
>>> session.query(User).all()
[<User: User 1>]
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 <sqlalchemy.orm.scoping.scoped_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 <unittest.TestCase>` 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()

0 comments on commit f83c602

Please sign in to comment.