summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-09-10 20:40:42 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-09-10 20:40:42 -0400
commit43b8346b3c14a5742080ff66ce03bb05e5891c50 (patch)
tree0a9cb259fb792135e3266d294ea0ce5812aeeb94 /lib/sqlalchemy/orm
parentf2622c0c53e761759da89c784c5105d2a38dcab9 (diff)
downloadsqlalchemy-43b8346b3c14a5742080ff66ce03bb05e5891c50.tar.gz
- rework scoped_session and sessionmaker, [ticket:2500]
- rewrite tons of session docs
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/__init__.py36
-rw-r--r--lib/sqlalchemy/orm/events.py22
-rw-r--r--lib/sqlalchemy/orm/scoping.py51
-rw-r--r--lib/sqlalchemy/orm/session.py204
4 files changed, 172 insertions, 141 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index f31e8b023..8c98ee3e4 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -58,7 +58,7 @@ from .session import (
make_transient
)
from .scoping import (
- ScopedSession
+ scoped_session
)
from . import mapper as mapperlib
from . import strategies
@@ -131,40 +131,6 @@ __all__ = (
)
-def scoped_session(session_factory, scopefunc=None):
- """Provides thread-local or scoped management of :class:`.Session` objects.
-
- This is a front-end function to
- :class:`.ScopedSession`::
-
- Session = scoped_session(sessionmaker(autoflush=True))
-
- To instantiate a Session object which is part of the scoped context,
- instantiate normally::
-
- session = Session()
-
- Most session methods are available as classmethods from the scoped
- session::
-
- Session.commit()
- Session.close()
-
- See also: :ref:`unitofwork_contextual`.
-
- :param session_factory: a callable function that produces
- :class:`.Session` instances, such as :func:`sessionmaker`.
-
- :param scopefunc: Optional "scope" function which would be
- passed to the :class:`.ScopedRegistry`. If None, the
- :class:`.ThreadLocalRegistry` is used by default.
-
- :returns: a :class:`.ScopedSession` instance
-
-
- """
- return ScopedSession(session_factory, scopefunc=scopefunc)
-
def create_session(bind=None, **kwargs):
"""Create a new :class:`.Session`
with no automation enabled by default.
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 25211d146..67f6d6431 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -925,16 +925,24 @@ class SessionEvents(event.Events):
@classmethod
def _accept_with(cls, target):
- if isinstance(target, orm.ScopedSession):
- if not isinstance(target.session_factory, type) or \
- not issubclass(target.session_factory, orm.Session):
+ if isinstance(target, orm.scoped_session):
+
+ target = target.session_factory
+ if not isinstance(target, orm.sessionmaker) and \
+ (
+ not isinstance(target, type) or
+ not issubclass(target, orm.Session)
+ ):
raise exc.ArgumentError(
- "Session event listen on a ScopedSession "
+ "Session event listen on a scoped_session "
"requires that its creation callable "
- "is a Session subclass.")
- return target.session_factory
+ "is associated with the Session class.")
+
+
+ if isinstance(target, orm.sessionmaker):
+ return target.class_
elif isinstance(target, type):
- if issubclass(target, orm.ScopedSession):
+ if issubclass(target, orm.scoped_session):
return orm.Session
elif issubclass(target, orm.Session):
return target
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
index 649ab7b87..fff17ee14 100644
--- a/lib/sqlalchemy/orm/scoping.py
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -10,25 +10,32 @@ from . import class_mapper, exc as orm_exc
from .session import Session
-__all__ = ['ScopedSession']
+__all__ = ['scoped_session']
-class ScopedSession(object):
- """Provides thread-local management of Sessions.
+class scoped_session(object):
+ """Provides scoped management of :class:`.Session` objects.
- Typical invocation is via the :func:`.scoped_session`
- function::
-
- Session = scoped_session(sessionmaker())
-
- The internal registry is accessible,
- and by default is an instance of :class:`.ThreadLocalRegistry`.
-
- See also: :ref:`unitofwork_contextual`.
+ See :ref:`unitofwork_contextual` for a tutorial.
"""
def __init__(self, session_factory, scopefunc=None):
+ """Construct a new :class:`.scoped_session`.
+
+ :param session_factory: a factory to create new :class:`.Session`
+ instances. This is usually, but not necessarily, an instance
+ of :class:`.sessionmaker`.
+ :param scopefunc: optional function which defines
+ the current scope. If not passed, the :class:`.scoped_session`
+ object assumes "thread-local" scope, and will use
+ a Python ``threading.local()`` in order to maintain the current
+ :class:`.Session`. If passed, the function should return
+ a hashable token; this token will be used as the key in a
+ dictionary in order to store and retrieve the current
+ :class:`.Session`.
+
+ """
self.session_factory = session_factory
if scopefunc:
self.registry = ScopedRegistry(session_factory, scopefunc)
@@ -36,6 +43,7 @@ class ScopedSession(object):
self.registry = ThreadLocalRegistry(session_factory)
def __call__(self, **kwargs):
+ """Return the current :class:`.Session`."""
if kwargs:
scope = kwargs.pop('scope', False)
if scope is not None:
@@ -60,7 +68,11 @@ class ScopedSession(object):
self.registry.clear()
def configure(self, **kwargs):
- """reconfigure the sessionmaker used by this ScopedSession."""
+ """reconfigure the :class:`.sessionmaker` used by this :class:`.scoped_session`.
+
+ See :meth:`.sessionmaker.configure`.
+
+ """
if self.registry.has():
warn('At least one scoped session is already present. '
@@ -70,8 +82,8 @@ class ScopedSession(object):
self.session_factory.configure(**kwargs)
def query_property(self, query_cls=None):
- """return a class property which produces a `Query` object
- against the class when called.
+ """return a class property which produces a :class:`.Query` object
+ against the class and the current :class:`.Session` when called.
e.g.::
@@ -108,12 +120,15 @@ class ScopedSession(object):
return None
return query()
+ScopedSession = scoped_session
+"""Old name for backwards compatibility."""
+
def instrument(name):
def do(self, *args, **kwargs):
return getattr(self.registry(), name)(*args, **kwargs)
return do
for meth in Session.public_methods:
- setattr(ScopedSession, meth, instrument(meth))
+ setattr(scoped_session, meth, instrument(meth))
def makeprop(name):
def set(self, attr):
@@ -123,12 +138,12 @@ def makeprop(name):
return property(get, set)
for prop in ('bind', 'dirty', 'deleted', 'new', 'identity_map',
'is_active', 'autoflush', 'no_autoflush'):
- setattr(ScopedSession, prop, makeprop(prop))
+ setattr(scoped_session, prop, makeprop(prop))
def clslevel(name):
def do(cls, *args, **kwargs):
return getattr(Session, name)(*args, **kwargs)
return classmethod(do)
for prop in ('close_all', 'object_session', 'identity_key'):
- setattr(ScopedSession, prop, clslevel(prop))
+ setattr(scoped_session, prop, clslevel(prop))
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index c6c7efffb..96a6983f8 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -24,81 +24,37 @@ from .events import SessionEvents
statelib = util.importlater("sqlalchemy.orm", "state")
import sys
-__all__ = ['Session', 'SessionTransaction', 'SessionExtension']
+__all__ = ['Session', 'SessionTransaction', 'SessionExtension', 'sessionmaker']
-def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
- expire_on_commit=True, **kwargs):
- """Generate a custom-configured :class:`.Session` class.
+class _SessionClassMethods(object):
+ """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
- The returned object is a subclass of :class:`.Session`, which,
- when instantiated with no arguments, uses the keyword arguments
- configured here as its constructor arguments.
-
- It is intended that the :func:`.sessionmaker()` function be called
- within the global scope of an application, and the returned class
- be made available to the rest of the application as the single
- class used to instantiate sessions.
-
- e.g.::
-
- # global scope
- Session = sessionmaker(autoflush=False)
-
- # later, in a local scope, create and use a session:
- sess = Session()
-
- Any keyword arguments sent to the constructor itself will override the
- "configured" keywords::
-
- Session = sessionmaker()
-
- # bind an individual session to a connection
- sess = Session(bind=connection)
-
- The class also includes a special classmethod ``configure()``, which
- allows additional configurational options to take place after the custom
- ``Session`` class has been generated. This is useful particularly for
- defining the specific ``Engine`` (or engines) to which new instances of
- ``Session`` should be bound::
-
- Session = sessionmaker()
- Session.configure(bind=create_engine('sqlite:///foo.db'))
-
- sess = Session()
-
- For options, see the constructor options for :class:`.Session`.
-
- """
- kwargs['bind'] = bind
- kwargs['autoflush'] = autoflush
- kwargs['autocommit'] = autocommit
- kwargs['expire_on_commit'] = expire_on_commit
-
- if class_ is None:
- class_ = Session
+ @classmethod
+ def close_all(cls):
+ """Close *all* sessions in memory."""
- class Sess(object):
- def __init__(self, **local_kwargs):
- for k in kwargs:
- local_kwargs.setdefault(k, kwargs[k])
- super(Sess, self).__init__(**local_kwargs)
+ for sess in _sessions.values():
+ sess.close()
- @classmethod
- def configure(self, **new_kwargs):
- """(Re)configure the arguments for this sessionmaker.
+ @classmethod
+ def identity_key(cls, *args, **kwargs):
+ """Return an identity key.
- e.g.::
+ This is an alias of :func:`.util.identity_key`.
- Session = sessionmaker()
+ """
+ return orm_util.identity_key(*args, **kwargs)
- Session.configure(bind=create_engine('sqlite://'))
- """
- kwargs.update(new_kwargs)
+ @classmethod
+ def object_session(cls, instance):
+ """Return the :class:`.Session` to which an object belongs.
+ This is an alias of :func:`.object_session`.
- return type("SessionMaker", (Sess, class_), {})
+ """
+ return object_session(instance)
class SessionTransaction(object):
"""A :class:`.Session`-level transaction.
@@ -446,7 +402,7 @@ class SessionTransaction(object):
else:
self.rollback()
-class Session(object):
+class Session(_SessionClassMethods):
"""Manages persistence operations for ORM-mapped objects.
The Session's usage paradigm is described at :ref:`session_toplevel`.
@@ -922,13 +878,6 @@ class Session(object):
for transaction in self.transaction._iterate_parents():
transaction.close()
- @classmethod
- def close_all(cls):
- """Close *all* sessions in memory."""
-
- for sess in _sessions.values():
- sess.close()
-
def expunge_all(self):
"""Remove all object instances from this ``Session``.
@@ -1573,15 +1522,6 @@ class Session(object):
merged_state.manager.dispatch.load(merged_state, None)
return merged
- @classmethod
- def identity_key(cls, *args, **kwargs):
- return orm_util.identity_key(*args, **kwargs)
-
- @classmethod
- def object_session(cls, instance):
- """Return the ``Session`` to which an object belongs."""
-
- return object_session(instance)
def _validate_persistent(self, state):
if not self.identity_map.contains_state(state):
@@ -2071,6 +2011,108 @@ class Session(object):
return util.IdentitySet(self._new.values())
+class sessionmaker(_SessionClassMethods):
+ """A configurable :class:`.Session` factory.
+
+ The :class:`.sessionmaker` factory generates new
+ :class:`.Session` objects when called, creating them given
+ the configurational arguments established here.
+
+ e.g.::
+
+ # global scope
+ Session = sessionmaker(autoflush=False)
+
+ # later, in a local scope, create and use a session:
+ sess = Session()
+
+ Any keyword arguments sent to the constructor itself will override the
+ "configured" keywords::
+
+ Session = sessionmaker()
+
+ # bind an individual session to a connection
+ sess = Session(bind=connection)
+
+ The class also includes a method :meth:`.configure`, which can
+ be used to specify additional keyword arguments to the factory, which
+ will take effect for subsequent :class:`.Session` objects generated.
+ This is usually used to associate one or more :class:`.Engine` objects
+ with an existing :class:`.sessionmaker` factory before it is first
+ used::
+
+ Session = sessionmaker()
+ Session.configure(bind=create_engine('sqlite:///foo.db'))
+
+ sess = Session()
+
+ """
+
+ def __init__(self, bind=None, class_=Session, autoflush=True,
+ autocommit=False,
+ expire_on_commit=True, **kw):
+ """Construct a new :class:`.sessionmaker`.
+
+ All arguments here except for ``class_`` correspond to arguments
+ accepted by :class:`.Session` directly. See the
+ :meth:`.Session.__init__` docstring for more details on parameters.
+
+ :param bind: a :class:`.Engine` or other :class:`.Connectable` with
+ which newly created :class:`.Session` objects will be associated.
+ :param class_: class to use in order to create new :class:`.Session`
+ objects. Defaults to :class:`.Session`.
+ :param autoflush: The autoflush setting to use with newly created
+ :class:`.Session` objects.
+ :param autocommit: The autocommit setting to use with newly created
+ :class:`.Session` objects.
+ :param expire_on_commit=True: the expire_on_commit setting to use
+ with newly created :class:`.Session` objects.
+ :param \**kw: all other keyword arguments are passed to the constructor
+ of newly created :class:`.Session` objects.
+
+ """
+ kw['bind'] = bind
+ kw['autoflush'] = autoflush
+ kw['autocommit'] = autocommit
+ kw['expire_on_commit'] = expire_on_commit
+ self.kw = kw
+ # make our own subclass of the given class, so that
+ # events can be associated with it specifically.
+ self.class_ = type(class_.__name__, (class_,), {})
+
+ def __call__(self, **local_kw):
+ """Produce a new :class:`.Session` object using the configuration
+ established in this :class:`.sessionmaker`.
+
+ In Python, the ``__call__`` method is invoked on an object when
+ it is "called" in the same way as a function::
+
+ Session = sessionmaker()
+ session = Session() # invokes sessionmaker.__call__()
+
+ """
+ for k, v in self.kw.items():
+ local_kw.setdefault(k, v)
+ return self.class_(**local_kw)
+
+ def configure(self, **new_kw):
+ """(Re)configure the arguments for this sessionmaker.
+
+ e.g.::
+
+ Session = sessionmaker()
+
+ Session.configure(bind=create_engine('sqlite://'))
+ """
+ self.kw.update(new_kw)
+
+ def __repr__(self):
+ return "%s(class_=%r%s)" % (
+ self.__class__.__name__,
+ self.class_.__name__,
+ ", ".join("%s=%r" % (k, v) for k, v in self.kw.items())
+ )
+
_sessions = weakref.WeakValueDictionary()
def make_transient(instance):