[sqlalchemy] Oracle 8i supports RETURNING clause (but sqla running 8i doesn't)

2010-08-14 Thread Kent
I'm connecting to a legacy database, some tables that I need to map in
sqla have no primary key.  I've created views for such tables that map
the ROWID as the primary key.  SQLAlchemy does a RETURNING clause when
I am connected to a more recent Oracle database, but not when
connected to 8i.

I've pasted the exact same code sqla compiled for newer oracle into an
8i session and it works fine:

{{{
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL show release
release 801070400
}}}
{{{
...(set up variables in sqlplus)...
SQL INSERT INTO artransarchive (customerid, companyid, orderid,
adjinvoice, transactioncodeid, status, paymenttypeid, postdate,
amount, sourcesiteid, artype) VALUES
(:customerid, :companyid, :orderid, :adjinvoice, :transactioncodeid, :status, 
:paymenttypeid, :postdate, :amount, :sourcesiteid, :artype)
RETURNING artransarchive.artransarchiveid INTO :ret_0
  2  ;

1 row created.

SQL print ret_0

RET_0
-
zbAADAAARwDAAZ

SQL show release
release 801070400
SQL
}}}

Are there other problems that restricted the use of RETURNING with
oracle 8i, or was it believed to not be supported?  (Note that I don't
believe Oracle 8.0 supports it... I read it was implemented in 8i)

Kent

-- 
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.



Re: [sqlalchemy] Oracle 8i supports RETURNING clause (but sqla running 8i doesn't)

2010-08-14 Thread Michael Bayer

On Aug 14, 2010, at 9:34 AM, Kent wrote:

 I'm connecting to a legacy database, some tables that I need to map in
 sqla have no primary key.  I've created views for such tables that map
 the ROWID as the primary key.  SQLAlchemy does a RETURNING clause when
 I am connected to a more recent Oracle database, but not when
 connected to 8i.
 
 I've pasted the exact same code sqla compiled for newer oracle into an
 8i session and it works fine:
 
 {{{
 Connected to:
 Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
 With the Partitioning option
 JServer Release 8.1.7.4.0 - Production
 
 SQL show release
 release 801070400
 }}}
 {{{
 ...(set up variables in sqlplus)...
 SQL INSERT INTO artransarchive (customerid, companyid, orderid,
 adjinvoice, transactioncodeid, status, paymenttypeid, postdate,
 amount, sourcesiteid, artype) VALUES
 (:customerid, :companyid, :orderid, :adjinvoice, :transactioncodeid, :status, 
 :paymenttypeid, :postdate, :amount, :sourcesiteid, :artype)
 RETURNING artransarchive.artransarchiveid INTO :ret_0
  2  ;
 
 1 row created.
 
 SQL print ret_0
 
 RET_0
 -
 zbAADAAARwDAAZ
 
 SQL show release
 release 801070400
 SQL
 }}}
 
 Are there other problems that restricted the use of RETURNING with
 oracle 8i, or was it believed to not be supported?  (Note that I don't
 believe Oracle 8.0 supports it... I read it was implemented in 8i)

I probably went off of this: 
http://www.lattimore.id.au/2006/04/06/oracle-returning-clause/ which says its 
as of 10g.If we can get confirmation somewhere of when RETURNING was 
introduced we can lower the auto-returning-on-insert threshhold; though its 
not clear that its really a better method to use with oracle, so perhaps here 
since you have some special use we would allow it to be enabled manually on any 
version.

though if you're mapping to a view, these are writeable views ?   i thought 
those were only in myth.

-- 
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.



Re: [sqlalchemy] Re: SQLite: Rolling back DDL requests

2010-08-14 Thread Michael Bayer

On Aug 13, 2010, at 10:22 PM, Peter Hansen wrote:

 On Jun 24, 1:23 pm, Michael Bayer mike...@zzzcomputing.com wrote:
 On Jun 24, 2010, at 12:35 PM, Torsten Landschoff wrote:
 That's what I thought but it does not cure my problem.
 e.raw_connect().isolation_levelis in fact None, but the rollback is not
 done anyway. :-(
 
 its passing it through.dont know what else we can do there
 
 I ran into this myself today and after struggling for a few hours I
 came across this thread.  I then ended up creating a test case to
 reproduce the problem and it suggests the problem stems from some
 behaviour of transactions or of the engine.base.Connection class.  I
 don't quite know what to make of it yet, but I think it shows that the
 effect of passing it through is being counteracted by something
 else.  I ran this on Python 2.6.5 with SA 0.6.3 on Windows.

Postgresql and MS-SQL support transactional DDL, and I use this feature all the 
time with SQLAlchemy which does nothing special to support them.   

In addition, isolation_level=None with pysqlite disables the DBAPIs entire 
transactional system.  SQLAlchemy relies upon this system to handle proper 
transactional behavior.  Per their documentation, some statements will fail if 
executed in a transaction - SQLAlchemy does not want to be involved in 
reorganizing how the DBAPI wants to approach things, emitting manual 'begin' 
and 'commit' strings, etc.

So I consider this a pysqlite bug, and they should offer a mode by which there 
is no implicit commit for CREATE TABLE.

See http://docs.python.org/library/sqlite3.html#controlling-transactions 




 
 '''Test showing unexpected behaviour with sqlalchemy and sqlite3 and
 isolation_level settings.  The test creates one table outside of
 a transaction (to detect potential problems with the test not
 executing
 properly) then creates a second table inside a transaction which it
 immediately rolls back.
 
 test01() fails basically as expected, since the sqlite3 DBAPI layer
 appears to need isolation_level==None to properly roll back DDL
 statements.

 
 test02() succeeds because isolation_level==None now.  This test and
 test01()
 both use a connection from engine.raw_connection(), which is a
 sqlalchemy.pool._ConnectionFairy() object.
 
 test03() tries again with isolation_level==None but using a
 transaction
 created from a connection returned by engine.connect(), which is a
 sqlalchemy.engine.base.Connection() object.  This test fails in spite
 of the isolation_level setting.
 '''
 
 import unittest
 from sqlalchemy import create_engine
 
 DBPATH = 'sqlite://'
 DDL = 'create table %s (id integer primary key)'
 
 class TestCase(unittest.TestCase):
def setup(self, **kwargs):
self.engine = create_engine(DBPATH, **kwargs)
self.engine.execute(DDL % 'foo')
 
def rollback_raw(self):
conn = self.engine.raw_connection()
try:
conn.execute('begin')
conn.execute(DDL % 'bar')
raise ValueError
except ValueError:
conn.rollback()
 
def rollback_txn(self):
conn = self.engine.connect()
try:
txn = conn.begin()
conn.execute(DDL % 'bar')
raise ValueError
except ValueError:
txn.rollback()
 
def get_table_names(self):
conn = self.engine.raw_connection()
return [x[0] for x in conn.execute('select name from
 sqlite_master')]
 
def test01(self):
'''use engine with default isolation_level'''
self.setup()
self.rollback_raw()
self.assertEqual(['foo'], self.get_table_names())
 
def test02(self):
'''use raw_connection with isolation_level None'''
self.setup(connect_args={'isolation_level': None})
self.rollback_raw()
self.assertEqual(['foo'], self.get_table_names())
 
def test03(self):
'''use transaction with isolation_level None'''
self.setup(connect_args={'isolation_level': None})
self.rollback_txn()
self.assertEqual(['foo'], self.get_table_names())
 
 if __name__ == '__main__':
unittest.main()
 
 
 --
 Peter Hansen
 Engenuity Corporation
 
 -- 
 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.



Re: [sqlalchemy] Oracle 8i supports RETURNING clause (but sqla running 8i doesn't)

2010-08-14 Thread Kent Bower
Not a myth, I'm using them (via sqla).  Simple views (just one table) 
oracle figures out writes to all alone.  For more complex views (joins 
of tables), you can use an INSTEAD OF trigger to write to the view 
(just pl/sql trigger where you tell Oracle what you want to happen when 
someone inserts/updates/deletes data in your view)... also using them, 
not a myth.


Anyway, here is documentation from oracle.com, so at least we know 8.1.7 
supported it:
http://download-west.oracle.com/docs/cd/A87860_01/doc/server.817/a85397/state11c.htm#2066261 



(I think they added fancier bulk returns and the like in later versions...)

Kent


On 8/14/2010 10:42 AM, Michael Bayer wrote:

On Aug 14, 2010, at 9:34 AM, Kent wrote:

   

I'm connecting to a legacy database, some tables that I need to map in
sqla have no primary key.  I've created views for such tables that map
the ROWID as the primary key.  SQLAlchemy does a RETURNING clause when
I am connected to a more recent Oracle database, but not when
connected to 8i.

I've pasted the exact same code sqla compiled for newer oracle into an
8i session and it works fine:

{{{
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.0 - Production
With the Partitioning option
JServer Release 8.1.7.4.0 - Production

SQL  show release
release 801070400
}}}
{{{
...(set up variables in sqlplus)...
SQL  INSERT INTO artransarchive (customerid, companyid, orderid,
adjinvoice, transactioncodeid, status, paymenttypeid, postdate,
amount, sourcesiteid, artype) VALUES
(:customerid, :companyid, :orderid, :adjinvoice, :transactioncodeid, :status, 
:paymenttypeid, :postdate, :amount, :sourcesiteid, :artype)
RETURNING artransarchive.artransarchiveid INTO :ret_0
  2  ;

1 row created.

SQL  print ret_0

RET_0
-
zbAADAAARwDAAZ

SQL  show release
release 801070400
SQL
}}}

Are there other problems that restricted the use of RETURNING with
oracle 8i, or was it believed to not be supported?  (Note that I don't
believe Oracle 8.0 supports it... I read it was implemented in 8i)
 

I probably went off of 
this:http://www.lattimore.id.au/2006/04/06/oracle-returning-clause/  which says its as of 
10g.If we can get confirmation somewhere of when RETURNING was introduced we can 
lower the auto-returning-on-insert threshhold; though its not clear that its 
really a better method to use with oracle, so perhaps here since you have some special 
use we would allow it to be enabled manually on any version.

though if you're mapping to a view, these are writeable views ?   i thought 
those were only in myth.

   


--
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.



[sqlalchemy] Re: SQLite: Rolling back DDL requests

2010-08-14 Thread Peter Hansen
On Aug 14, 11:07 am, Michael Bayer mike...@zzzcomputing.com wrote:
 In addition, isolation_level=None with pysqlite disables the DBAPIs
 entire transactional system.  SQLAlchemy relies upon this system to
 handle proper transactional behavior.

 So I consider this a pysqlite bug, and they should offer a mode
 by which there is no implicit commit for CREATE TABLE.

Michael, I don't doubt that you're right, but the puzzling thing (for
me and, I think, Torsen) is that (if you set echo='debug') you see a
BEGIN and ROLLBACK statement apparently being issued through the DBAPI
layer, and if there's an implicit COMMIT going on we don't actually
see it and don't understand why isolation_level=None isn't preventing
it as it apparently does in the other case (test02).

I suspect this is because with the logging on, we aren't actually
seeing the operations performed, but merely sqlalchemy's report of
what it is about to ask for.  If the DBAPI is doing something under
the covers, we don't know what and therefore can't find a workaround.

To be clear, in test02 I believe we're effectively telling sqlite3
BEGIN; CREATE TABLE ...; ROLLBACK, and in test03 (with the same
isolation_level setting) that's exactly what the logging reports is
happening, yet the behaviour is different.

(I was hoping to put some debugging in the DBAPI layer but
unfortunately that's a C extension so harder to deal with.  Maybe
there's also some feature in sqlite3 itself (not the Python module)
which can be configured for debug purposes to show what's really
happening here.)

--
Peter Hansen
Engenuity Corporation

-- 
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.



[sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Hipp
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). I could create another session and 
add 'new' to it, but that seems like a lot of boilerplate when all I wanted to 
do was get a bit of info from the returned object.


Can someone explain how this is supposed to be done?

Thanks,
Michael







--
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.



Re: [sqlalchemy] Re: SQLite: Rolling back DDL requests

2010-08-14 Thread Michael Bayer

On Aug 14, 2010, at 12:04 PM, Peter Hansen wrote:

 On Aug 14, 11:07 am, Michael Bayer mike...@zzzcomputing.com wrote:
 In addition, isolation_level=None with pysqlite disables the DBAPIs
 entire transactional system.  SQLAlchemy relies upon this system to
 handle proper transactional behavior.
 
 So I consider this a pysqlite bug, and they should offer a mode
 by which there is no implicit commit for CREATE TABLE.
 
 Michael, I don't doubt that you're right, but the puzzling thing (for
 me and, I think, Torsen) is that (if you set echo='debug') you see a
 BEGIN and ROLLBACK statement apparently being issued through the DBAPI
 layer,

The BEGIN is just a logging message emitted by SQLAlchemy.  We don't ever 
send that string, and DBAPI has no begin() - this is a critical aspect of DBAPI 
that confuses everyone - it doesn't support ad-hoc transaction creation.  There 
always a transaction going on as far as DBAPI is concerned, its just that the 
transaction might not matter if the connection has disabled transactions.

ROLLBACK is a message we emit when we call connection.rollback().

 and if there's an implicit COMMIT going on we don't actually
 see

according to sqlite3's docs, there is, we of course can't see it unless you got 
sqlite3 to log its interactions.

 it and don't understand why isolation_level=None isn't preventing
 it as it apparently does in the other case (test02).

test02 would appear to succeed because you are manipulating the transaction 
directly by emitting 'begin'.




-- 
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.



Re: [sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Bayer

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).

hehno, 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() 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.



Re: [sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Hipp

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).


hehno, 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 

Re: [sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Bayer

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).
 
 hehno, 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'
 

Re: [sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Hipp

On 8/14/2010 2:29 PM, Michael Bayer wrote:

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.


Thanks. Yes, I was beginning to suspect such. Makes more sense to manage the 
session and commit/rollback issues at the top of the call stack. I was trying 
too hard to not have to pass the session down in argument lists, but looks like 
I should.


Thanks,
Michael

--
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.



Re: [sqlalchemy] DetachedInstanceError

2010-08-14 Thread Michael Bayer

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

 On 8/14/2010 2:29 PM, Michael Bayer wrote:
 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.
 
 Thanks. Yes, I was beginning to suspect such. Makes more sense to manage the 
 session and commit/rollback issues at the top of the call stack. I was trying 
 too hard to not have to pass the session down in argument lists, but looks 
 like I should.

well, you can either call object_session() in the methods to get the current 
object's session, or set up a registry like scoped_session that you access 
globally - I'd try to avoid having to pass the session around too.


-- 
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.