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)