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?

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.

Reply via email to