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!"