diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-09-10 20:40:42 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-09-10 20:40:42 -0400 |
| commit | 43b8346b3c14a5742080ff66ce03bb05e5891c50 (patch) | |
| tree | 0a9cb259fb792135e3266d294ea0ce5812aeeb94 /lib/sqlalchemy/orm | |
| parent | f2622c0c53e761759da89c784c5105d2a38dcab9 (diff) | |
| download | sqlalchemy-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__.py | 36 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 22 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/scoping.py | 51 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 204 |
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): |
