Michael Bayer wrote:

we agree, that there should be some way to turn on this feature:

    u = User()    # 'u' is now automatically in the session
    session.flush()    # 'u' is now saved.

I think by default it should require a session.save(u) before the flush (that's 
exactly what Hibernate does). Now if someone happens to want that type of 
magic, then they can use one of the _explicit_ recipes to get that behavior 
(i.e. the threadlocal mod (see below) or the base class or __session__ provided 
by SessionContext).


question 1. heres the code inside of mapper.py which decorates the __init__() method of a class, and automatically adds the item to the session, if available:

    def __init__(self):
        sess = current_session()
        if sess is not None;
            sess.save(self)

Now, get rid of current_session. how does it work ? beacuse honestly, i dont know how you think it should work. no other method is coming to mind for me right now.

The mapper will have an optional session_context parameter. It will provide the 
exact same functionality as current_session(), but provides additional 
flexibility because each mapper can have it's own context.


Next question, which is related. while its nice to have a class associated with a particular sessioncontext, i simply disagree that the explicit class/sessioncontext association should be required. i think there should be an option for a session to be locateable, without the need for an explicit declaration on each individual class or requiring the classes descend from some common base class. if you think that feature should not be available, maybe thats why you want get rid of current_session(). but i want that feature available. how can that be implemented without a current_session() function ?

As mentioned above, mapper() will have a new session_context parameter. 
Something like this:

m = mapper(..., session_context=context)

The mapper will default to this context as a last resort if it can't find a session any 
other way. This is better than current_session() because it's explicit. And it's more 
flexible because there can be multiple context's per application. This would be 
especially nice when dealing with multiple databases in the same application, because 
each database could have its own separate session context (all the mappers for each 
database would be associated with the respective session context for that database). If 
you want, you can even create a "context_mapper" function that will 
automatically add the context to the mapper call. Something like this:

from sqlalchemy import mapper
def create_context_mapper(context):
   def context_mapper(*args, **kwargs):
       return mapper(session_context=context, *args, **kwargs)
   return context_mapper

# Usage
context = SessionContext()
mapper = create_context_mapper(context)

m = mapper(User, users)

u = User() # associated with contextual session
session.flush() # save u


With this approach it's not even necessary to reference the context for every 
new mapper instantiation. The threadlocal mod will create a single global 
SessionContext and then make this type of modification to SA's mapper() 
function. That's even 100% backward compatible.

I have attached a patch. Note that I did not remove the current_session() and 
other related functions from the orm.session module. However, none of these 
functions are used by the library any longer and they could be removed if you 
agree that they are no longer necessary. Let me know what you think.

~ Daniel
Index: lib/sqlalchemy/mods/threadlocal.py
===================================================================
--- lib/sqlalchemy/mods/threadlocal.py  (revision 1441)
+++ lib/sqlalchemy/mods/threadlocal.py  (working copy)
@@ -1,5 +1,6 @@
 from sqlalchemy import util, engine, mapper
-from sqlalchemy.orm import session, current_session
+from sqlalchemy.orm import session
+from sqlalchemy.ext.sessioncontext import SessionContext
 import sqlalchemy
 import sys, types
 
@@ -10,19 +11,19 @@
 from the pool.  this greatly helps functions that call multiple statements to 
be able to easily use just one connection
 without explicit "close" statements on result handles.
 
-on the Session side, the current_session() method will be modified to return a 
thread-local Session when no arguments
-are sent.  It will also install module-level methods within the objectstore 
module, such as flush(), delete(), etc.
-which call this method on the thread-local session returned by 
current_session().
+on the Session side, module-level methods will be installed within the 
objectstore module, such as flush(), delete(), etc.
+which call this method on the thread-local session.
 
-
+Note: this mod creates a global, thread-local session context named 
sqlalchemy.objectstore. All mappers created
+after this mod is installed will reference this global context unless a 
session_context param is passed to mapper().
 """
 
-class Objectstore(object):
+class Objectstore(SessionContext):
     def __getattr__(self, key):
-        return getattr(current_session(), key)
+        return getattr(self.current, key)
     def get_session(self):
-        return current_session()
-        
+        return self.current
+
 def monkeypatch_query_method(class_, name):
     def do(self, *args, **kwargs):
         query = class_.mapper.query()
@@ -31,10 +32,14 @@
 
 def monkeypatch_objectstore_method(class_, name):
     def do(self, *args, **kwargs):
-        session = current_session()
+        session = sqlalchemy.objectstore.current
         getattr(session, name)(self, *args, **kwargs)
     setattr(class_, name, do)
     
+def context_mapper(*args, **kwargs):
+    kwargs.setdefault("session_context", sqlalchemy.objectstore)
+    return mapper(*args, **kwargs)
+
 def assign_mapper(class_, *args, **kwargs):
     kwargs.setdefault("is_primary", True)
     if not isinstance(getattr(class_, '__init__'), types.MethodType):
@@ -42,7 +47,7 @@
              for key, value in kwargs.items():
                  setattr(self, key, value)
         class_.__init__ = __init__
-    m = mapper(class_, *args, **kwargs)
+    m = sqlalchemy.mapper(class_, *args, **kwargs) # note explicit use of 
sqlalchemy.mapper, which is context_mapper if this mod is installed
     class_.mapper = m
     for name in ['get', 'select', 'select_by', 'selectone', 'get_by']:
         monkeypatch_query_method(class_, name)
@@ -50,14 +55,15 @@
         monkeypatch_objectstore_method(class_, name)
     
 def install_plugin():
-    reg = util.ScopedRegistry(session.Session)
-    session.register_default_session(lambda *args, **kwargs: reg())
+    sqlalchemy.objectstore = objectstore = Objectstore(session.Session)
+    session.register_default_session(lambda *args, **kwargs: 
objectstore.current)
     engine.default_strategy = 'threadlocal'
-    sqlalchemy.objectstore = Objectstore()
+    sqlalchemy.mapper = context_mapper
     sqlalchemy.assign_mapper = assign_mapper
 
 def uninstall_plugin():
     session.register_default_session(lambda *args, **kwargs:None)
     engine.default_strategy = 'plain'
+    sqlalchemy.mapper = mapper
     
 install_plugin()
Index: lib/sqlalchemy/orm/mapper.py
===================================================================
--- lib/sqlalchemy/orm/mapper.py        (revision 1441)
+++ lib/sqlalchemy/orm/mapper.py        (working copy)
@@ -51,7 +51,8 @@
                 polymorphic_map=None,
                 polymorphic_identity=None,
                 concrete=False,
-                select_table=None):
+                select_table=None,
+                session_context=None):
 
         ext = MapperExtension()
         
@@ -64,6 +65,11 @@
             
         self.extension = ext
 
+        if session_context is None:
+            class session_context(object):
+                current = None
+        self.session_context = session_context
+
         self.class_ = class_
         self.entity_name = entity_name
         self.class_key = ClassKey(class_, entity_name)
@@ -335,8 +341,10 @@
             
             if kwargs.has_key('_sa_session'):
                 session = kwargs.pop('_sa_session')
+            elif hasattr(obj, '__session__'):
+                session = obj.__session__()
             else:
-                session = sessionlib.current_session(self)
+                session = self.session_context.current
             if session is not None:
                 session._register_new(self)
             if oldinit is not None:
Index: lib/sqlalchemy/orm/query.py
===================================================================
--- lib/sqlalchemy/orm/query.py (revision 1441)
+++ lib/sqlalchemy/orm/query.py (working copy)
@@ -27,7 +27,10 @@
         self._get_clause = self.mapper._get_clause
     def _get_session(self):
         if self._session is None:
-            return sessionlib.required_current_session()
+            s = self.mapper.session_context.current
+            if s is None:
+                raise exceptions.InvalidRequestError("No mapper-level Session 
context is established.  Create your mappers with a session_context parameter 
or use 'import sqlalchemy.mods.threadlocal' to establish a default thread-local 
context.")
+            return s
         else:
             return self._session
     table = property(lambda s:s.mapper.select_table)

Reply via email to