Michael Bayer wrote:
actually, the semantics of import_() are much more equivalent to
hibernate's merge(), not update(). now update() makes more sense to me.
merge() sounds fine to me (better than import_() anyway). How would you do
something like Hibernate's update() with pre-0.2 SA?
since I desperately want this to just be done, i am considering just
throwing on very similar versions of load(), merge(), save(), update(),
saveOrUpdate(), delete(), as described in
http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html
, complete with the same exception cases and everything (i think).
Don't forget session.get()
It seems like these methods might belong to Query. I'm not sure what I think
about this, but it looks like we're heading in that direction. Here are a few
thoughts on that:
The following methods have the same semantics as they do in Hibernate:
Query.save(obj)
Query.update(obj)
Query.save_or_update(obj)
Query.merge(obj)
Query.delete(obj)
Rule: methods that load instances from persistent storage may be executed
immediately while methods that would change an object's persistent state are
executed on session.flush().
however now we have the whole issue of descending to child objects too,
which was previously nicely handled by the thread-local get_session()
method.
It's nicely handled because the thread-local pattern allows you treat the
session like a global variable.
This is my problem with SA's implementation of the thread-local pattern: the
mapper depends on objectstore.get_session() without no arguments. Note, it is
fine for the mapper to call objectstore.get_session(obj) (which will raise an
exception if the given object is not associated with a session).
hibernate does a lot of extra work that we now have to
implement, complete with all the cascade styles, etc:
http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-transitive
.... so this is basically a huge pain in the ass.
Hmm...I'm sorry. However, I do think we need to decide how far we are going
here. For example, are we going to implement HQL?
One thing I haven't made up my mind about is whether SA should automatically
associate new object instances with a session. I know there are people who want
this convenience. It should definitely not be done by the mapper itself. I
think we can stay flexible as long as we never require mapped classes to be
derived from a base class that we own. That is not to say we won't ever provide
a default implementation for this type of thing. In fact, I have attached a
patch that implements thread-local session orthogonally from mapper.
~ Daniel
from sqlalchemy.util import ScopedRegistry
class ScopedContext(object):
"""A simple wrapper for ScopedRegistry that provides a "current" property
"""
def __init__(self, scoped_registry):
self.registry = scoped_registry
def get_current(self):
return self.registry()
def set_current(self, session):
self.registry.set(session)
def del_current(self):
self.registry.clear()
current = property(get_current, set_current, del_current)
class ThreadLocalSessionContext(ScopedContext):
"""ScopedContext thread-local sessions
Usage:
engine = create_engine(...)
def session_factory():
return Session(bind_to=engine)
context = ThreadLocalSessionContext(session_factory)
context.current # get thread-local session
context.current = Session(bind_to=other_engine) # set t-l session
del context.current # discard the thread-local session (a new one will
# be created on the next call to context.current)
"""
def __init__(self, session_factory):
reg = ScopedRegistry(session_factory)
super(ThreadLocalSessionContext, self).__init__(reg)
def create_metaclass(session_context):
"""create a metaclass to be used by objects that wish to be bound to a
thread-local session upon instantiatoin.
The given session_context object must have a "current" property that
returns the current session.
Usage:
context = ThreadLocalSessionContext(...)
class MyClass(object):
__metaclass__ = create_metaclass(context)
...
"""
class DomainMeta(type):
def __init__(cls, name, bases, dct):
old_init = getattr(cls, "__init__")
def __init__(self, *args, **kwargs):
session_context.current.add(self)
old_init(self, *args, **kwargs)
setattr(cls, "__init__", __init__)
super(DomainMeta, cls).__init__(name, bases, dct)
return DomainMeta
def create_baseclass(session_context):
"""create a baseclass to be used by objects that wish to be bound to a
thread-local session upon instantiatoin.
The given session_context object must have a "current" property that
returns the current session.
Usage:
context = ThreadLocalSessionContext(...)
DomainBase = create_baseclass(context)
class MyClass(DomainBase):
...
"""
class DomainObject(object):
def __init__(self, *args, **kwargs):
session_context.current.add(self)
super(DomainObject, self).__init__(*args, **kwargs)
return DomainObject
def test():
from sqlalchemy import objectstore
def run_test(class_, context):
obj = class_()
assert context.current == objectstore.get_session(obj)
# keep a reference so the old session doesn't get gc'd
old_session = context.current
context.current = objectstore.Session()
assert context.current != objectstore.get_session(obj)
assert old_session == objectstore.get_session(obj)
del context.current
assert context.current != objectstore.get_session(obj)
assert old_session == objectstore.get_session(obj)
obj2 = class_()
assert context.current == objectstore.get_session(obj2)
# test create_metaclass
context = ThreadLocalSessionContext(objectstore.Session)
meta = create_metaclass(context)
class MyClass(object): __metaclass__ = meta
run_test(MyClass, context)
# test create_baseclass
context = ThreadLocalSessionContext(objectstore.Session)
base = create_baseclass(context)
class MyClass(base): pass
run_test(MyClass, context)
if __name__ == "__main__"
test()
print "All tests passed!"