1. Éric Lemoine
  2. sqlalchemy


Mike Bayer  committed c2e610b

- rework scoped_session and sessionmaker, [ticket:2500]
- rewrite tons of session docs

  • Participants
  • Parent commits 70a6865
  • Branches default

Comments (0)

Files changed (7)

File doc/build/builder/builders.py

View file
 def autodoc_skip_member(app, what, name, obj, skip, options):
     if what == 'class' and skip and \
-        name in ('__init__', '__eq__', '__ne__', '__lt__', '__le__') and \
+        name in ('__init__', '__eq__', '__ne__', '__lt__', '__le__', '__call__') and \
         return False

File doc/build/orm/session.rst

View file
 :class:`.Session` is a regular Python class which can
 be directly instantiated. However, to standardize how sessions are configured
-and acquired, the :func:`.sessionmaker` function is normally
+and acquired, the :class:`.sessionmaker` class is normally
 used to create a top level :class:`.Session`
 configuration which can then be used throughout an application without the
 need to repeat the configurational arguments.
-The usage of :func:`.sessionmaker` is illustrated below:
+The usage of :class:`.sessionmaker` is illustrated below:
 .. sourcecode:: python+sql
-Above, the :func:`.sessionmaker` call creates a class for us,
-which we assign to the name ``Session``. This class is a subclass of the
-actual :class:`.Session` class, which when instantiated, will
-use the arguments we've given the function, in this case
-to use a particular :class:`.Engine` for connection resources.
+Above, the :class:`.sessionmaker` call creates a factory for us,
+which we assign to the name ``Session``.  This factory, when
+called, will create a new :class:`.Session` object using the configurational
+arguments we've given the factory.  In this case, as is typical,
+we've configured the factory to specify a particular :class:`.Engine` for
+connection resources.
-A typical setup will associate the :func:`.sessionmaker` with an :class:`.Engine`,
+A typical setup will associate the :class:`.sessionmaker` with an :class:`.Engine`,
 so that each :class:`.Session` generated will use this :class:`.Engine`
 to acquire connection resources.   This association can
 be set up as in the example above, using the ``bind`` argument.
-When you write your application, place the result of the
-:func:`.sessionmaker` call at the global level.   The resulting
-``Session`` class, configured for your application, should then
+When you write your application, place the
+:class:`.sessionmaker` factory at the global level.   This
+factory can then
 be used by the rest of the applcation as the source of new :class:`.Session`
+instances, keeping the configuration for how :class:`.Session` objects
+are constructed in one place.
-An extremely common step taken by applications, including virtually
-all web applications, is to further wrap the :func:`.sessionmaker`
-construct in a so-called "contextual" session, provided by the
-:func:`.scoped_session` construct.  This construct places the :func:`.sessionmaker`
-into a **registry** that maintains a single :class:`.Session` per
-application thread.   Information on using contextual sessions
-is at :ref:`unitofwork_contextual`.
+The :class:`.sessionmaker` factory can also be used in conjunction with
+other helpers, which are passed a user-defined :class:`.sessionmaker` that
+is then maintained by the helper.  Some of these helpers are discussed in the
+section :ref:`session_faq_whentocreate`.
 Adding Additional Configuration to an Existing sessionmaker()
-A common scenario is where the :func:`.sessionmaker` is invoked
+A common scenario is where the :class:`.sessionmaker` is invoked
 at module import time, however the generation of one or more :class:`.Engine`
-instances to be associated with the :func:`.sessionmaker` has not yet proceeded.
-For this use case, the :func:`.sessionmaker` construct offers the
+instances to be associated with the :class:`.sessionmaker` has not yet proceeded.
+For this use case, the :class:`.sessionmaker` construct offers the
 :meth:`.sessionmaker.configure` method, which will place additional configuration
-directives into an existing :func:`.sessionmaker` that will take place
+directives into an existing :class:`.sessionmaker` that will take place
 when the construct is invoked::
 source of connectivity, or a :class:`.Session` that should
 have other arguments such as ``expire_on_commit`` established differently from
 what most of the application wants, specific arguments can be passed to the
-:func:`.sessionmaker` construct's class itself.  These arguments will override whatever
+:class:`.sessionmaker` factory's :meth:`.sessionmaker.__call__` method.
+These arguments will override whatever
 configurations have already been placed, such as below, where a new :class:`.Session`
 is constructed against a specific :class:`.Connection`::
 It's helpful to know the states which an instance can have within a session:
-* *Transient* - an instance that's not in a session, and is not saved to the
+* **Transient** - an instance that's not in a session, and is not saved to the
   database; i.e. it has no database identity. The only relationship such an
   object has to the ORM is that its class has a ``mapper()`` associated with
-* *Pending* - when you :func:`~sqlalchemy.orm.session.Session.add` a transient
+* **Pending** - when you :func:`~sqlalchemy.orm.session.Session.add` a transient
   instance, it becomes pending. It still wasn't actually flushed to the
   database yet, but it will be when the next flush occurs.
-* *Persistent* - An instance which is present in the session and has a record
+* **Persistent** - An instance which is present in the session and has a record
   in the database. You get persistent instances by either flushing so that the
   pending instances become persistent, or by querying the database for
   existing instances (or moving persistent instances from other sessions into
   your local session).
-* *Detached* - an instance which has a record in the database, but is not in
+* **Detached** - an instance which has a record in the database, but is not in
   any session. There's nothing wrong with this, and you can use objects
   normally when they're detached, **except** they will not be able to issue
   any SQL in order to load collections or attributes which are not yet loaded,
   or were marked as "expired".
 Knowing these states is important, since the
-:class:`~sqlalchemy.orm.session.Session` tries to be strict about ambiguous
+:class:`.Session` tries to be strict about ambiguous
 operations (such as trying to save the same object to two different sessions
 at the same time).
 .. _session_faq:
+.. _session_faq_whentocreate:
 Session Frequently Asked Questions
-* When do I make a :func:`.sessionmaker` ?
+* When do I make a :class:`.sessionmaker` ?
     Just one time, somewhere in your application's global scope. It should be
     looked upon as part of your application's configuration. If your
     application has three .py files in a package, you could, for example,
-    place the :func:`.sessionmaker` line in your ``__init__.py`` file; from
+    place the :class:`.sessionmaker` line in your ``__init__.py`` file; from
     that point on your other modules say "from mypackage import Session". That
     way, everyone else just uses :class:`.Session()`,
     and the configuration of that session is controlled by that central point.
     If your application starts up, does imports, but does not know what
     database it's going to be connecting to, you can bind the
     :class:`.Session` at the "class" level to the
-    engine later on, using ``configure()``.
+    engine later on, using :meth:`.sessionmaker.configure`.
     In the examples in this section, we will frequently show the
-    :func:`.sessionmaker` being created right above the line where we actually
-    invoke :class:`~sqlalchemy.orm.session.Session()`. But that's just for
-    example's sake ! In reality, the :func:`.sessionmaker` would be somewhere
-    at the module level, and your individual
-    :class:`~sqlalchemy.orm.session.Session()` calls would be sprinkled all
-    throughout your app, such as in a web application within each controller
-    method.
+    :class:`.sessionmaker` being created right above the line where we actually
+    invoke :class:`.Session`. But that's just for
+    example's sake!  In reality, the :class:`.sessionmaker` would be somewhere
+    at the module level.   The calls to instantiate :class:`.Session`
+    would then be placed at the point in the application where database
+    conversations begin (see :ref:`session_faq_whentocreate`).
-* When do I make a :class:`.Session` ?
+* When do I construct a :class:`.Session`, when do I commit it, and when do I close it ?
-    You typically invoke :class:`.Session` when you first need to talk to your
-    database, and want to save some objects or load some existing ones. It
-    then remains in use for the lifespan of a particular database
-    conversation, which includes not just the initial loading of objects but
-    throughout the whole usage of those instances.
+    A :class:`.Session` is typically constructed at the beginning of a logical
+    operation where database access is potentially anticipated.
-    Objects become detached if their owning session is discarded. They are
-    still functional in the detached state if the user has ensured that their
-    state has not been expired before detachment, but they will not be able to
-    represent the current state of database data. Because of this, it's best
-    to consider persisted objects as an extension of the state of a particular
-    :class:`.Session`, and to keep that session around until all referenced
-    objects have been discarded.
+    The :class:`.Session`, whenever it is used to talk to the database,
+    begins a database transaction as soon as it starts communicating.
+    Assuming the ``autocommit`` flag is left at its recommended default
+    of ``False``, this transaction remains in progress until the :class:`.Session`
+    is rolled back, committed, or closed.   The :class:`.Session` will
+    begin a new transaction if it is used again, subsequent to the previous
+    transaction ending; from this it follows that the :class:`.Session`
+    is capable of having a lifespan across many transactions, though only
+    one at a time.   We refer to these two concepts as *transaction scope*
+    and *session scope*.
-    An exception to this is when objects are placed in caches or otherwise
-    shared among threads or processes, in which case their detached state can
-    be stored, transmitted, or shared. However, the state of detached objects
-    should still be transferred back into a new :class:`.Session` using
-    :meth:`.Session.add` or :meth:`.Session.merge` before working with the
-    object (or in the case of merge, its state) again.
+    The implication here is that the SQLAlchemy ORM is encouraging the
+    developer to establish these two scopes in his or her application,
+    including not only when the scopes begin and end, but also the
+    expanse of those scopes, for example should a single
+    :class:`.Session` instance be local to the execution flow within a
+    function or method, should it be a global object used by the
+    entire application, or somewhere in between these two.
-    It is also very common that a :class:`.Session` as well as its associated
-    objects are only referenced by a single thread.  Sharing objects between
-    threads is most safely accomplished by sharing their state among multiple
-    instances of those objects, each associated with a distinct
-    :class:`.Session` per thread, :meth:`.Session.merge` to transfer state
-    between threads.   This pattern is not a strict requirement by any means,
-    but it has the least chance of introducing concurrency issues.
+    The burden placed on the developer to determine this scope is one
+    area where the SQLAlchemy ORM necessarily has a strong opinion
+    about how the database should be used.  The unit-of-work pattern
+    is specifically one of accumulating changes over time and flushing
+    them periodically, keeping in-memory state in sync with what's
+    known to be present in a local transaction. This pattern is only
+    effective when meaningful transaction scopes are in place.
-    To help with the recommended :class:`.Session` -per-thread,
-    :class:`.Session` -per-set-of-objects patterns, the
-    :func:`.scoped_session` function is provided which produces a
-    thread-managed registry of :class:`.Session` objects. It is commonly used
-    in web applications so that a single global variable can be used to safely
-    represent transactional sessions with sets of objects, localized to a
-    single thread. More on this object is in :ref:`unitofwork_contextual`.
+    It's usually not very hard to determine the best points at which
+    to begin and end the scope of a :class:`.Session`, though the wide
+    variety of application architectures possible can introduce
+    challenging situations.
+    A common choice is to tear down the :class:`.Session` at the same
+    time the transaction ends, meaning the transaction and session scopes
+    are the same.  This is a great choice to start out with as it
+    removes the need to consider session scope as separate from transaction
+    scope.
+    While there's no one-size-fits-all recommendation for how transaction
+    scope should be determined, there are common patterns.   Especially
+    if one is writing a web application, the choice is pretty much established.
+    A web application is the easiest case because such an appication is already
+    constructed around a single, consistent scope - this is the **request**,
+    which represents an incoming request from a browser, the processing
+    of that request to formulate a response, and finally the delivery of that
+    response back to the client.    Integrating web applications with the
+    :class:`.Session` is then the straightforward task of linking the
+    scope of the :class:`.Session` to that of the request.  The :class:`.Session`
+    can be established as the request begins, or using a **lazy initialization**
+    pattern which establishes one as soon as it is needed.  The request
+    then proceeds, with some system in place where application logic can access
+    the current :class:`.Session` in a manner associated with how the actual
+    requset object is accessed.  As the request ends, the :class:`.Session`
+    is torn down as well, usually through the usage of event hooks provided
+    by the web framework.   The transaction used by the :class:`.Session`
+    may also be committed at this point, or alternatively the application may
+    opt for an explicit commit pattern, only committing for those requests
+    where one is warranted, but still always tearing down the :class:`.Session`
+    unconditionally at the end.
+    Most web frameworks include infrastructure to establish a single
+    :class:`.Session`, associated with the request, which is correctly
+    constructed and torn down corresponding
+    torn down at the end of a request.   Such infrastructure pieces
+    include products such as `Flask-SQLAlchemy <http://packages.python.org/Flask-SQLAlchemy/>`_,
+    for usage in conjunction with the Flask web framework,
+    and `Zope-SQLAlchemy <http://pypi.python.org/pypi/zope.sqlalchemy>`_,
+    for usage in conjunction with the Pyramid and Zope frameworks.
+    SQLAlchemy strongly recommends that these products be used as
+    available.
+    In those situations where integration libraries are not available,
+    SQLAlchemy includes its own "helper" class known as
+    :class:`.scoped_session`.  This object actually is used as the basis
+    of some integration packages, and provides both a quick way
+    to associate a :class:`.Session` with the current thread, as well as
+    patterns to associate :class:`.Session` objects with other kinds of
+    scopes.
+    As mentioned before, for non-web applications there is no one clear
+    pattern, as applications themselves don't have just one pattern
+    of architecture.   The best strategy is to attempt to demarcate
+    "operations", points at which a particular thread begins to perform
+    a series of operations for some period of time, which can be committed
+    at the end.   Some examples:
+    * A background daemon which spawns off child forks for example
+      would want to create a :class:`.Session` local to each child
+      process work with that :class:`.Session` through the life of the "job"
+      that the fork is handling, then tear it down when the job is completed.
+    * For a command-line script, the application would create a single, global
+      :class:`.Session` that is established when the program begins to do its
+      work, and commits it right as the program is completing its task.
+    * For a GUI interface-driven application, the scope of the :class:`.Session`
+      may best be within the scope of a user-generated event, such as a button
+      push.  Or, the scope may correspond to explicit user interaction, such as
+      the user "opening" a series of records, then "saving" them.
 * Is the Session a cache ?
         session = Session.object_session(someobject)
-.. index::
-   single: thread safety; sessions
-   single: thread safety; Session
+* Is the session thread-safe?
-* Is the session thread-safe?
+    The :class:`.Session` is very much intended to be used in a
+    **non-concurrent** fashion, which usually means in only one thread at a
+    time.
     Nope. It has no thread synchronization of any kind built in, and
     particularly when you do a flush operation, it definitely is not open to
     threads do not collide.
     A multithreaded application is usually going to want to make usage of
-    :func:`.scoped_session` to transparently manage sessions per thread.
+    :class:`.scoped_session` to transparently manage sessions per thread.
     More on this at :ref:`unitofwork_contextual`.
 The "flush-on-Query" aspect of the behavior can be disabled by constructing
-:func:`.sessionmaker` with the flag ``autoflush=False``::
+:class:`.sessionmaker` with the flag ``autoflush=False``::
     Session = sessionmaker(autoflush=False)
 attribute access or by them being present in a
 :class:`~sqlalchemy.orm.query.Query` result set, they receive the most recent
 state. To disable this behavior, configure
-:func:`.sessionmaker` with ``expire_on_commit=False``.
+:class:`.sessionmaker` with ``expire_on_commit=False``.
 Normally, instances loaded into the :class:`~sqlalchemy.orm.session.Session`
 are never changed by subsequent queries; the assumption is that the current
 full flush, these collections are all empty, and all objects are again weakly
 referenced. To disable the weak referencing behavior and force all objects
 within the session to remain until explicitly expunged, configure
-:func:`.sessionmaker` with the ``weak_identity_map=False``
+:class:`.sessionmaker` with the ``weak_identity_map=False``
 .. _unitofwork_cascades:
 Contextual/Thread-local Sessions
-A common need in applications, particularly those built around web frameworks,
-is the ability to "share" a :class:`~sqlalchemy.orm.session.Session` object
-among disparate parts of an application, without needing to pass the object
-explicitly to all method and function calls. What you're really looking for is
-some kind of "global" session object, or at least "global" to all the parts of
-an application which are tasked with servicing the current request. For this
-pattern, SQLAlchemy provides the ability to enhance the
-:class:`~sqlalchemy.orm.session.Session` class generated by
-:func:`.sessionmaker` to provide auto-contextualizing support.
-This means that whenever you create a :class:`~sqlalchemy.orm.session.Session`
-instance with its constructor, you get an *existing*
-:class:`~sqlalchemy.orm.session.Session` object which is bound to some
-"context". By default, this context is the current thread. This feature is
-what previously was accomplished using the ``sessioncontext`` SQLAlchemy
+Recall from the section :ref:`session_faq_whentocreate`, the concept of
+"session scopes" was introduced, with an emphasis on web applications
+and the practice of linking the scope of a :class:`.Session` with that
+of a web request.   Most modern web frameworks include integration tools
+so that the scope of the :class:`.Session` can be managed automatically,
+and these tools should be used as they are available.
-Creating a Thread-local Context
+SQLAlchemy includes its own helper object, which helps with the establishment
+of user-defined :class:`.Session` scopes.  It is also used by third-party
+integration systems to help construct their integration schemes.
-The :func:`~sqlalchemy.orm.scoped_session` function wraps around the
-:func:`.sessionmaker` function, and produces an object which
-behaves the same as the :class:`~sqlalchemy.orm.session.Session` subclass
-returned by :func:`.sessionmaker`::
+The object is the :class:`.scoped_session` object, and it represents a
+**registry** of :class:`.Session` objects.  If you're not familiar with the
+registry pattern, a good introduction can be found in `Patterns of Enterprise
+Architecture <http://martinfowler.com/eaaCatalog/registry.html>`_.
-    from sqlalchemy.orm import scoped_session, sessionmaker
-    Session = scoped_session(sessionmaker())
+.. note::
-However, when you instantiate this :class:`~sqlalchemy.orm.session.Session`
-"class", in reality the object is pulled from a threadlocal variable, or if it
-doesn't exist yet, it's created using the underlying class generated by
+   The :class:`.scoped_session` object is a very popular and useful object
+   used by many SQLAlchemy applications.  However, it is important to note
+   that it presents **only one approach** to the issue of :class:`.Session`
+   management.  If you're new to SQLAlchemy, and especially if the
+   term "thread-local variable" seems strange to you, we recommend that
+   if possible you familiarize first with an off-the-shelf integration
+   system such as `Flask-SQLAlchemy <http://packages.python.org/Flask-SQLAlchemy/>`_
+   or `zope.sqlalchemy <http://pypi.python.org/pypi/zope.sqlalchemy>`_.
-    >>> # call Session() the first time.  the new Session instance is created.
-    >>> session = Session()
+A :class:`.scoped_session` is constructed by calling it, passing it a
+**factory** which can create new :class:`.Session` objects.   A factory
+is just something that produces a new object when called, and in the
+case of :class:`.Session`, the most common factory is the :class:`.sessionmaker`,
+introduced earlier in this section.  Below we illustrate this usage::
-    >>> # later, in the same application thread, someone else calls Session()
-    >>> session2 = Session()
+    >>> from sqlalchemy.orm import scoped_session
+    >>> from sqlalchemy.orm import sessionmaker
-    >>> # the two Session objects are *the same* object
-    >>> session is session2
+    >>> session_factory = sessionmaker(bind=some_engine)
+    >>> Session = scoped_session(session_factory)
+The :class:`.scoped_session` object we've created will now call upon the
+:class:`.sessionmaker` when we "call" the registry::
+    >>> some_session = Session()
+Above, ``some_session`` is an instance of :class:`.Session`, which we
+can now use to talk to the database.   This same :class:`.Session` is also
+present within the :class:`.scoped_session` registry we've created.   If
+we call upon the registry a second time, we get back the **same** :class:`.Session`::
+    >>> some_other_session = Session()
+    >>> some_session is some_other_session
-Since the :class:`~sqlalchemy.orm.session.Session()` constructor now returns
-the same :class:`~sqlalchemy.orm.session.Session` object every time within the
-current thread, the object returned by :func:`~sqlalchemy.orm.scoped_session`
-also implements most of the :class:`~sqlalchemy.orm.session.Session` methods
-and properties at the "class" level, such that you don't even need to
-instantiate :class:`~sqlalchemy.orm.session.Session()`::
+This pattern allows disparate sections of the application to call upon a global
+:class:`.scoped_session`, so that all those areas may share the same session
+without the need to pass it explicitly.   The :class:`.Session` we've established
+in our registry will remain, until we explicitly tell our regsitry to dispose of it,
+by calling :meth:`.scoped_session.remove`::
-    # create some objects
-    u1 = User()
-    u2 = User()
+    >>> Session.remove()
-    # save to the contextual session, without instantiating
-    Session.add(u1)
-    Session.add(u2)
+The :meth:`.scoped_session.remove` method first calls :meth:`.Session.close` on
+the current :class:`.Session`, which has the effect of releasing any connection/transactional
+resources owned by the :class:`.Session` first, then discarding the :class:`.Session`
+itself.  "Releasing" here means that any pending transaction will be rolled back
+using ``connection.rollback()``.
-    # view the "new" attribute
-    assert u1 in Session.new
+At this point, the :class:`.scoped_session` object is "empty", and will create
+a **new** :class:`.Session` when called again.  As illustrated below, this
+is not the same :class:`.Session` we had before::
-    # commit changes
-    Session.commit()
+    >>> new_session = Session()
+    >>> new_session is some_session
+    False
-The contextual session may be disposed of by calling ``Session.remove()``::
+The above series of steps illustrates the idea of the "registry" pattern in a
+nutshell.  With that basic idea in hand, we can discuss some of the details
+of how this pattern proceeds.
-    # remove current contextual session
-    Session.remove()
+Implicit Method Access
-After ``remove()`` is called, the next operation with the contextual session
-will start a new :class:`~sqlalchemy.orm.session.Session` for the current
+The job of the :class:`.scoped_session` is simple; hold onto a :class:`.Session`
+for all who ask for it.  As a means of producing more transparent access to this
+:class:`.Session`, the :class:`.scoped_session` also includes **proxy behavior**,
+meaning that the registry itself can be treated just like a :class:`.Session`
+directly; when methods are called on this object, they are **proxied** to the
+underlying :class:`.Session` being maintained by the registry::
+    Session = scoped_session(some_factory)
+    # equivalent to:
+    #
+    # session = Session()
+    # print session.query(MyClass).all()
+    #
+    print Session.query(MyClass).all()
+The above code accomplishes the same task as that of acquiring the current
+:class:`.Session` by calling upon the registry, then using that :class:`.Session`.
+Thread-Local Scope
+Users who are familiar with multithreaded programming will note that representing
+anything as a global variable is usually a bad idea, as it implies that the
+global object will be accessed by many threads concurrently.   The :class:`.Session`
+object is entirely designed to be used in a **non-concurrent** fashion, which
+in terms of multithreading means "only in one thread at a time".   So our
+above example of :class:`.scoped_session` usage, where the same :class:`.Session`
+object is maintained across multiple calls, suggests that some process needs
+to be in place such that mutltiple calls across many threads don't actually get
+a handle to the same session.   We call this notion **thread local storage**,
+which means, a special object is used that will maintain a distinct object
+per each application thread.   Python provides this via the
+`threading.local() <http://docs.python.org/library/threading.html#threading.local>`_
+construct.  The :class:`.scoped_session` object by default uses this object
+as storage, so that a single :class:`.Session` is maintained for all who call
+upon the :class:`.scoped_session` registry, but only within the scope of a single
+thread.   Callers who call upon the registry in a different thread get a
+:class:`.Session` instance that is local to that other thread.
+Using this technique, the :class:`.scoped_session` provides a quick and relatively
+simple (if one is familiar with thread-local storage) way of providing
+a single, global object in an application that is safe to be called upon
+from multiple threads.
+The :meth:`.scoped_session.remove` method, as always, removes the current
+:class:`.Session` associated with the thread, if any.  However, one advantage of the
+``threading.local()`` object is that if the application thread itself ends, the
+"storage" for that thread is also garbage collected.  So it is in fact "safe" to
+use thread local scope with an application that spawns and tears down threads,
+without the need to call :meth:`.scoped_session.remove`.  However, the scope
+of transactions themselves, i.e. ending them via :meth:`.Session.commit` or
+:meth:`.Session.rollback`, will usually still be something that must be explicitly
+arranged for at the appropriate time, unless the application actually ties the
+lifespan of a thread to the lifespan of a transaction.
 .. _session_lifespan:
-Lifespan of a Contextual Session
+Using Thread-Local Scope with Web Applications
-A (really, really) common question is when does the contextual session get
-created, when does it get disposed ? We'll consider a typical lifespan as used
-in a web application::
+As discussed in the section :ref:`session_faq_whentocreate`, a web application
+is architected around the concept of a **web request**, and integrating
+such an application with the :class:`.Session` usually implies that the :class:`.Session`
+will be associated with that request.  As it turns out, most Python web frameworks,
+with notable exceptions such as the asynchronous frameworks Twisted and
+Tornado, use threads in a simple way, such that a particular web request is received,
+processed, and completed within the scope of a single *worker thread*.  When
+the request ends, the worker thread is released to a pool of workers where it
+is available to handle another request.
-    Web Server          Web Framework        User-defined Controller Call
+This simple correspondence of web request and thread means that to associate a
+:class:`.Session` with a thread implies it is also associated with the web request
+running within that thread, and vice versa, provided that the :class:`.Session` is
+created only after the web request begins and torn down just before the web request ends.
+So it is a very common practice to use :class:`.scoped_session` as a quick way
+to integrate the :class:`.Session` with a web application.  The sequence
+diagram below illustrates this flow::
+    Web Server          Web Framework        SQLAlchemy ORM Code
     --------------      --------------       ------------------------------
-    web request    ->
-                        call controller ->   # call Session().  this establishes a new,
-                                             # contextual Session.
-                                             session = Session()
+    startup        ->   Web framework        # Session registry is established
+                        initializes          Session = scoped_session(sessionmaker())
-                                             # load some objects, save some changes
-                                             objects = session.query(MyClass).all()
+    incoming
+    web request    ->   web request     ->   # The registry is *optionally*
+                        starts               # called upon explicitly to create
+                                             # a Session local to the thread and/or request
+                                             Session()
-                                             # some other code calls Session, it's the
-                                             # same contextual session as "sess"
-                                             session2 = Session()
-                                             session2.add(foo)
-                                             session2.commit()
+                                             # the Session registry can otherwise
+                                             # be used at any time, creating the
+                                             # request-local Session() if not present,
+                                             # or returning the existing one
+                                             Session.query(MyClass) # ...
-                                             # generate content to be returned
-                                             return generate_content()
-                        Session.remove() <-
-    web response   <-
+                                             Session.add(some_object) # ...
-The above example illustrates an explicit call to :meth:`.ScopedSession.remove`. This
-has the effect such that each web request starts fresh with a brand new
-session, and is the most definitive approach to closing out a request.
+                                             # if data was modified, commit the
+                                             # transaction
+                                             Session.commit()
-It's not strictly necessary to remove the session at the end of the request -
-other options include calling :meth:`.Session.close`, :meth:`.Session.rollback`,
-:meth:`.Session.commit` at the end so that the existing session returns
-its connections to the pool and removes any existing transactional context.
-Doing nothing is an option too, if individual controller methods take responsibility
-for ensuring that no transactions remain open after a request ends.
+                        web request ends  -> # the registry is instructed to
+                                             # remove the Session
+                                             Session.remove()
+                        sends output      <-
+    outgoing web    <-
+    response
+Using the above flow, the process of integrating the :class:`.Session` with the
+web application has exactly two requirements:
+1. Create a single :class:`.scoped_session` registry when the web application
+   first starts, ensuring that this object is accessible by the rest of the
+   application.
+2. Ensure that :meth:`.scoped_session.remove` is called when the web request ends,
+   usually by integrating with the web framework's event system to establish
+   an "on request end" event.
+As noted earlier, the above pattern is **just one potential way** to integrate a :class:`.Session`
+with a web framework, one which in particular makes the significant assumption
+that the **web framework associates web requests with application threads**.  It is
+however **strongly recommended that the integration tools provided with the web framework
+itself be used, if available**, instead of :class:`.scoped_session`.
+In particular, while using a thread local can be convenient, it is preferable that the :class:`.Session` be
+associated **directly with the request**, rather than with
+the current thread.   The next section on custom scopes details a more advanced configuration
+which can combine the usage of :class:`.scoped_session` with direct request based scope, or
+any kind of scope.
+Using Custom Created Scopes
+The :class:`.scoped_session` object's default behavior of "thread local" scope is only
+one of many options on how to "scope" a :class:`.Session`.   A custom scope can be defined
+based on any existing system of getting at "the current thing we are working with".
+Suppose a web framework defines a library function ``get_current_request()``.  An application
+build on this framework can call this function at any time, and the result will be
+some kind of ``Request`` object that represents the current request being processed.
+If the ``Request`` object is hashable, then this function can be easily integrated with
+:class:`.scoped_session` to associate the :class:`.Session` with the request.  Below we illustrate
+this in conjunction with a hypothetical event marker provided by the web framework
+``on_request_end``, which allows code to be invoked whenever a request ends::
+    from my_web_framework import get_current_request, on_request_end
+    from sqlalchemy.orm import scoped_session, sessionmaker
+    Session = scoped_session(sessionmaker(bind=some_engine), scopefunc=get_current_request)
+    @on_request_end
+    def remove_session(req):
+        Session.remove()
+Above, we instantiate :class:`.scoped_session` in the usual way, except that we pass
+our request-returning function as the "scopefunc".  This instructs :class:`.scoped_session`
+to use this function to generate a dictionary key whenever the registry is called upon
+to return the current :class:`.Session`.   In this case it is particularly important
+that we ensure a reliable "remove" system is implemented, as this dictionary is not
+otherwise self-managed.
 Contextual Session API
-.. autofunction:: sqlalchemy.orm.scoped_session
-.. autoclass:: sqlalchemy.orm.scoping.ScopedSession
+.. autoclass:: sqlalchemy.orm.scoping.scoped_session
 .. autoclass:: sqlalchemy.util.ScopedRegistry
 The above :class:`.Session` class is plugged in using the ``class_``
-argument to :func:`.sessionmaker`::
+argument to :class:`.sessionmaker`::
     Session = sessionmaker(class_=RoutingSession)
 Session and sessionmaker()
-.. autofunction:: sessionmaker
+.. autoclass:: sessionmaker
+    :members:
+    :show-inheritance:
+    :inherited-members:
 .. autoclass:: sqlalchemy.orm.session.Session
+   :show-inheritance:
+   :inherited-members:
 .. autoclass:: sqlalchemy.orm.session.SessionTransaction

File lib/sqlalchemy/orm/__init__.py

View file
 from .scoping import (
-    ScopedSession
+    scoped_session
 from . import mapper as mapperlib
 from . import strategies
-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.

File lib/sqlalchemy/orm/events.py

View file
     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

File lib/sqlalchemy/orm/scoping.py

View file
 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)
             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:
     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. '
     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.
                     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):
     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))

File lib/sqlalchemy/orm/session.py

View file
 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.
+    @classmethod
+    def close_all(cls):
+        """Close *all* sessions in memory."""
-    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.
+        for sess in _sessions.values():
+            sess.close()
-    e.g.::
+    @classmethod
+    def identity_key(cls, *args, **kwargs):
+        """Return an identity key.
-        # global scope
-        Session = sessionmaker(autoflush=False)
+        This is an alias of :func:`.util.identity_key`.
-        # later, in a local scope, create and use a session:
-        sess = Session()
+        """
+        return orm_util.identity_key(*args, **kwargs)
-    Any keyword arguments sent to the constructor itself will override the
-    "configured" keywords::
+    @classmethod
+    def object_session(cls, instance):
+        """Return the :class:`.Session` to which an object belongs.
-        Session = sessionmaker()
+        This is an alias of :func:`.object_session`.
-        # 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
-    class Sess(object):
-        def __init__(self, **local_kwargs):
-            for k in kwargs:
-                local_kwargs.setdefault(k, kwargs[k])
-            super(Sess, self).__init__(**local_kwargs)
-        @classmethod
-        def configure(self, **new_kwargs):
-            """(Re)configure the arguments for this sessionmaker.
-            e.g.::
-                Session = sessionmaker()
-                Session.configure(bind=create_engine('sqlite://'))
-            """
-            kwargs.update(new_kwargs)
-    return type("SessionMaker", (Sess, class_), {})
+        return object_session(instance)
 class SessionTransaction(object):
     """A :class:`.Session`-level transaction.
-class Session(object):
+class Session(_SessionClassMethods):
     """Manages persistence operations for ORM-mapped objects.
     The Session's usage paradigm is described at :ref:`session_toplevel`.
             for transaction in self.transaction._iterate_parents():
-    @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``.
             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):
         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):

File test/orm/test_events.py

View file
-            "Session event listen on a ScopedSession "
-            "requires that its creation callable is a Session subclass.",
+            "Session event listen on a scoped_session requires that its "
+            "creation callable is associated with the Session class.",
             event.listen, scope, "before_flush", my_listener_one
-            "Session event listen on a ScopedSession "
-            "requires that its creation callable is a Session subclass.",
+            "Session event listen on a scoped_session requires that its "
+            "creation callable is associated with the Session class.",
             event.listen, scope, "before_flush", my_listener_one