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

Reply via email to