Commits

Mike Bayer committed ceacec4

- scoped_session docs
- added remove() method to scoped_session

  • Participants
  • Parent commits 0e4e392

Comments (0)

Files changed (2)

File doc/build/content/session.txt

 
 ## Contextual/Thread-local Sessions {@name=contextual}
 
-[this section is TODO]
+A common need in applications, particularly those built around web frameworks, is the ability to "share" a `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 `Session` class generated by `sessionmaker()` to provide auto-contextualizing support.  This means that whenever you create a `Session` instance with its constructor, you get an *existing* `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 extension.
+
+### Creating a Thread-local Context {@name=creating}
+
+The `scoped_session()` function wraps around the `sessionmaker()` function, and produces an object which behaves the same as the `Session` subclass returned by `sessionmaker()`:
 
     {python}
+    from sqlalchemy.orm import scoped_session, sessionmaker
     Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
+    
+However, when you instantiate this `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 `sessionmaker()`:
 
+    {python}
+    >>> # call Session() the first time.  the new Session instance is created.
+    >>> sess = Session()
+    
+    >>> # later, in the same application thread, someone else calls Session()
+    >>> sess2 = Session()
+    
+    >>> # the two Session objects are *the same* object
+    >>> sess is sess2
+    True
+
+Since the `Session()` constructor now returns the same `Session` object every time within the current thread, the object returned by `scoped_session()` also implements most of the `Session` methods and properties at the "class" level, such that you don't even need to instantiate `Session()`:
+
+    {python}
+    # create some objects
+    u1 = User()
+    u2 = User()
+    
+    # save to the contextual session, without instantiating
+    Session.save(u1)
+    Session.save(u2)
+    
+    # view the "new" attribute
+    assert u1 in Session.new
+    
+    # flush changes (if not using autoflush)
+    Session.flush()
+    
+    # commit transaction (if using a transactional session)
+    Session.commit()
+
+To "dispose" of the `Session`, theres two general approaches.  One is to close out the current session, but to leave it assigned to the current context.  This allows the same object to be re-used on another operation.  This may be called from a current, instantiated `Session`:
+
+    {python}
+    sess.close()
+    
+Or, when using `scoped_session()`, the `close()` method may also be called as a classmethod on the `Session` "class":
+
+    {python}
+    Session.close()
+
+When the `Session` is closed, it remains attached, but clears all of its contents and releases any ongoing transactional resources, including rolling back any remaining transactional state.  The `Session` can then be used again.
+
+The other method is to remove the current session from the current context altogether.  This is accomplished using the classmethod `remove()`:
+
+    {python}
+    Session.remove()
+    
+After `remove()`  is called, the next call to `Session()` will create a *new* `Session` object which then becomes the contextual session.
+
+That, in a nutshell, is all there really is to it.  Now for all the extra things one should know.
+
+### Lifespan of a Contextual Session {@name=lifespan}
+
+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:
+
+    {diagram}
+    Web Server          Web Framework        User-defined Controller Call
+    --------------      --------------       ------------------------------
+    web request    -> 
+                        call controller ->   # call Session().  this establishes a new,
+                                             # contextual Session.
+                                             sess = Session()
+                                             
+                                             # load some objects, save some changes
+                                             objects = sess.query(MyClass).all()
+                                             
+                                             # some other code calls Session, its the 
+                                             # same contextual session as "sess"
+                                             sess2 = Session()
+                                             sess2.save(foo)
+                                             sess2.commit()
+                                             
+                                             # generate content to be returned
+                                             return generate_content()
+                        Session.remove() <-
+    web response   <-  
+
+Above, we illustrate a *typical* organization of duties, where the "Web Framework" layer has some integration built-in to manage the span of ORM sessions.  Upon the initial handling of an incoming web request, the framework passes control to a controller.  The controller then calls `Session()` when it wishes to work with the ORM; this method establishes the contextual Session which will remain until its removed.  Disparate parts of the controller code may all call `Session()` and will get the same session object.  Then, when the controller has completed and the reponse is to be sent to the web server, the framework **closes out** the current contextual session, above using the `remove()` method which removes the session from the context altogether.
+
+As an alternative, the "finalization" step can also call `Session.close()`, which will leave the same session object in place, may be used.  Which one is better ?  For a web framework which runs from a fixed pool of threads, it doesn't matter much.  For a framework which runs a **variable** number of threads, or which **creates and disposes** of a thread for each request, `remove()` is better, since it leaves no resources associated with the thread which might not exist.
+
+* Why close out the session at all ?  Why not just leave it going so the next request doesn't have to do as many queries ?
+
+    There are some cases where you may actually want to do this.  However, this is a special case where you are dealing with data which **does not change** very often, or you don't care about the "freshness" of the data.  In reality, a single thread of a web server may, on a slow day, sit around for many minutes or even hours without being accessed.  When it's next accessed, if data from the previous request still exists in the session, that data may be very stale indeed.  So its generally better to have an empty session at the start of a web request.
+
+### Associating Classes and Mappers with a Contextual Session {@name=associating}
+
+Another luxury we gain, when we've established a `Session()` that can be globally accessed, is the ability for mapped classes and objects to provide us with session-oriented functionality automatically.  When using the `scoped_session()` function, we access this feature using the `mapper` attribute on the object in place of the normal `sqlalchemy.orm.mapper` function:
+
+    {python}
+    # "contextual" mapper function
+    mapper = Session.mapper
+    
+    # use normally
+    mapper(User, users_table, properties={
+        relation(Address)
+    })
+    mapper(Address, addresses_table)
+
+When we use the contextual `mapper()` function, our `User` and `Address` now gain a new attribute `query`, which will create a `Query` object for us against the contextual session:
+
+    {python}
+    wendy = User.query.filter_by(name='wendy').one()
+    
+Additionally, new instances are saved into the contextual session automatically upon construction; there is no longer a need to call `save()`:
+
+    {python}
+    >>> newuser = User(name='ed')
+    >>> assert newuser in Session.new
+    True
+
+This functionality is an updated version of what used to be accomplished by the `assignmapper()` SQLAlchemy extension.
+    
 [Generated docstrings for scoped_session()](rel:docstrings_sqlalchemy.orm_modfunc_scoped_session)
 
 ## Partitioning Strategies

File lib/sqlalchemy/orm/scoping.py

                 return self.session_factory(**kwargs)
         else:
             return self.registry()
-
+    
+    def remove(self):
+        self.registry.clear()
+    remove = classmethod(remove)
+    
     def mapper(self, *args, **kwargs):
         """return a mapper() function which associates this ScopedSession with the Mapper."""