On Aug 14, 2010, at 3:00 PM, Michael Hipp wrote:

> Michael
> 
> Thanks for taking the time to formulate a very thorough answer. (Now if I can 
> make my understanding be as thorough.)
> 
> If you could suffer me one more question ... it appears there are two* ways 
> to handle this inside a method that may not know where it's called from.
> 
>  def alternative1(thing):
>    sess = Session()
>    sess.merge(thing)
>    thing.name = "Foo"
>    sess.commit()
>    sess.close()
> 
>  def alternative2(thing, sess=None):
>    if sess is None:
>      sess = Session()
>      sess.merge(thing)
>    thing.name = "Foo"
>    sess.commit()  # incomplete, must do sess.close()
> 
> Am I getting anywhere close? Can either one be said to be better?

If you're looking for that approach, it is usually:

from sqlalchemy.orm import object_session

def foo(thing):
    session = object_session(thing)
    if not session:
        local_sess = Session(expire_on_commit=False)
        local_sess.add(thing)   
    thing.name = 'foo'
    if not session:
        local_sess.commit()

What we've done above is, if the "thing" is already part of a session, we don't 
assume to know what the state of the transaction is - we don't commit it.   If 
it was detached, and we made our own session, then we committed it.

You can also make a decorator that does the same:

import decorator  # pypi package

@decorator
def force_a_session(fn, item):
    session = object_session(item)
    if not session:
        local_sess = Session(expire_on_commit=False)
        local_sess.add(item)
    try:
        try:
            return fn(item)
        finally:
            if not session:
                local_sess.commit()
    except:
        if not session:
            local_sess.rollback()
        raise

    
The approach above may be fine for your needs but I wouldn't encourage it.  The 
demarcation of transaction boundaries shouldn't be an ad-hoc thing IMO and 
granular functions shouldn't be deciding whether or not they are setting up a 
transaction.





> 
> Again, thanks.
> Michael
> 
> * For now, I'm taking it as an article of faith that I should stay away from 
> expire_on_commit at least until I better understand the implications.
> 
> 
> 
> On 8/14/2010 12:38 PM, Michael Bayer wrote:
>> 
>> On Aug 14, 2010, at 12:53 PM, Michael Hipp wrote:
>> 
>>> I'm obviously missing some key concept as regards the management of
>>> sessions. This seemingly simple usage fails:
>>> 
>>> 
>>> def get_new():
>>> sess = Session()
>>> new = Something() # new orm object
>>> sess.add(new)
>>> sess.commit()
>>> sess.close()
>>> return new
>>> 
>>> new = get_new() # request a new Something
>>> print new
>>> print new.id
>>> 
>>> Those last 2 print lines throw:
>>> 
>>> DetachedInstanceError: Instance <Something at 0x2873ed0> is not bound to
>>> a Session; attribute refresh operation cannot proceed
>>> 
>>> I seem to keep butting heads with the session needing to be a global
>>> eternal thing (opposite what the docs recommend).
>> 
>> heh....no, the session is completely ad hoc. What you're missing is that
>> the objects associated with the session should also in most situations
>> be treated as ad-hoc - they represent the state of data within a
>> particular transaction.
>> 
>> If you use them outside of a transaction, and not associated with a
>> session that would otherwise have the ability to associate them with a
>> transaction, they are considered to be "detached". "detached" is
>> described at:
>> 
>> http://www.sqlalchemy.org/docs/session.html#quickie-intro-to-object-states
>> 
>> Where you'll note that "expired" attributes cannot be loaded back from
>> the database.
>> 
>> Why are they expired ? Let's look at commit():
>> 
>> http://www.sqlalchemy.org/docs/session.html#committing
>> 
>> Second paragraph. "Another behavior of commit()
>> <http://www.sqlalchemy.org/docs/reference/orm/sessions.html#sqlalchemy.orm.session.Session.commit>
>> is that by default it expires the state of all instances present after
>> the commit is complete".
>> 
>> Why does it do this ? Well, when we have a detached object, and there's
>> no transaction going on (i.e. no connection that can query the DB), we
>> know nothing about what is in the database at that point, so all state
>> on the object is expired. After all, if it had id =12, but some other
>> transaction has deleted row 12, that object is invalid. Without a
>> transaction associated, it would be wrong for us to tell you otherwise.
>> Because we don't know.
>> 
>> Now lets assume you don't like this behavior, and your application is
>> just set of operations at a time and nobody else is updating your row
>> (assumptions SQLAlchemy has chosen not to make). Fine. Turn off
>> expire_on_commit. Then when you detach your objects, all their
>> attributes are still present, and you can access them freely.
>> 
>> So what if we made this the default. What kinds of complaints, which btw
>> we never get anymore, would we have then ? Well, we'd have (and we had,
>> all the time) this complaint:
>> 
>> sess1 = Session()
>> x1 = sess1.query(X).first()
>> x1.foo = 'bar'
>> sess1.commit()
>> 
>> sess2 = Session()
>> x2 = sess2.query(X).first()
>> x2.foo = 'bat'
>> sess2.commit()
>> 
>> # x1 is still present in the Session's identity map
>> x1 = sess1.query(X).first()
>> assert x1.foo == 'bat' # ugh SQLALCHEMY IS BROKEN !
>> 
>> so we default to the more "transaction hugging" behavior by default -
>> where the error message you get is at least very straightforward,
>> instead of a subtle effect like this one.
>> 
>>> 
>>> Can someone explain how this is supposed to be done?
>> 
>> When you work with mapped objects, you're working with your database. A
>> Session() should be in place and a transaction is in progress. Its only
>> if you want to store mapped objects in some kind of offline cache, or
>> pass them to other usage contexts, that you'd want to keep "detached"
>> objects around. And when you go to use a "detached" object, you put it
>> back into a context where it again is a proxy to some ongoing database
>> operation, i.e. put it in the session for the current operation - often
>> this transfer of state is done via merge(), so that if the destination
>> session already has the object in question present, it will reconcile
>> the incoming state with what it already has. The "load=False" setting of
>> merge() prevents the usage of a SELECT from loading existing state, if
>> you are working with long term immutable data and don't want the extra
>> SELECT emitted.
>> 
>> Alternatively, if you really want to pass around detached objects and
>> make use of their detached state, even though that state may be stale or
>> even deleted vs. what's in the database, you can disable
>> expire_on_commit - if you are making ad-hoc sessions for usage in single
>> functions, just pass it to that specific session constructor.
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To post to this group, send email to sqlalch...@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.
> 

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to