[sqlalchemy] 0.9.7: Intermittently find a dead weakref to a listens_for function while checking _stored_in_collection of a registry

2015-04-02 Thread Evan James
Hi folks,

While running my test suite, I hit an issue with the following stack trace:

ERROR at setup of test_track_before_delete
 

request = SubRequest 'database_session' for Function 
'test_track_before_delete', engine = Engine(sqlite:///test.db) 


 

@pytest.fixture 

def database_session(request, engine): 

connection = engine.connect() 

trans = connection.begin() 

 

meta.Session = scoped_session(sessionmaker(autocommit=False, 
autoflush=True, bind=connection)) 

register_session_listeners(meta.Session) 

 

meta.Session.begin_nested() 

   @event.listens_for(meta.Session, after_transaction_end) 

def restart_savepoint(session, transaction): 

if transaction.nested and not transaction._parent.nested: 


 

automated_tests/conftest.py:52:  

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/api.
py:94: in decorate 

listen(target, identifier, fn, *args, **kw) 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/api.
py:63: in listen 

_event_key(target, identifier, fn).listen(*args, **kw) 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/
registry.py:187: in listen 

self.dispatch_target.dispatch._listen(self, *args, **kw) 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/base.
py:184: in _listen 

event_key.base_listen(propagate=propagate, insert=insert, named=named) 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/
registry.py:226: in base_listen 

for_modify(target.dispatch).append(self, propagate) 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/attr.
py:118: in append 

registry._stored_in_collection(event_key, self) 

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

 

event_key = sqlalchemy.event.registry._EventKey object at 0x111c6b3d0, 
owner = sqlalchemy.event.attr._DispatchDescriptor object at 0x10eac8590 


 

def _stored_in_collection(event_key, owner): 

key = event_key._key 

 

dispatch_reg = _key_to_collection[key] 

 

owner_ref = owner.ref 

listen_ref = weakref.ref(event_key._listen_fn) 

 

if owner_ref in dispatch_reg: 

   assert dispatch_reg[owner_ref] == listen_ref 

E   assert weakref at 0x111cc25d0; dead == weakref at 0x111cc2af8
; to 'function' at 0x111c7f668 (restart_savepoint) 


 

../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/
registry.py:74: AssertionError


While running the @event.listens_for decorator in the setup of a test 
fixture, SQLAlchemy throws an assertion because the ref it has in the 
dispatch registry isn't identical to the ref to the listener function. 
 We're on SQLAlchemy 0.9.7, pytest 2.6.4.  Note that the test suite is 
setting up nested transactions on SQLite; we are using the recommended 
workaround from SQLAlchemy documentation to take control of transactional 
integration from pysqlite.


Since there's an assertion in the code in _stored_in_collection(), I assume 
that there's some expectation that the refs might not match; am I doing 
something wrong in my setup which this assertion is meant to catch?  I've 
only seen this error once (while tracking down a different nondeterministic 
error in my own app's code), so I can't provide much more information than 
this, but the portion of test fixture code seen above in the stack trace is 
basically the entire reproduction case.  This fixture runs before every 
test in my suite, but I've only seen an error once across a large number of 
runs, so the error is *very* intermittent.  Because of that, I'm not 
worried too much about the error itself, but I thought I should post it 
here in case it's a symptom of something I should be worrying about.


Thanks,

Evan James

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.


Re: [sqlalchemy] 0.9.7: Intermittently find a dead weakref to a listens_for function while checking _stored_in_collection of a registry

2015-04-02 Thread Mike Bayer


On 4/2/15 4:28 PM, Evan James wrote:
 Hi folks,

 While running my test suite, I hit an issue with the following stack
 trace:

 |
 ERROR at setup of test_track_before_delete
  

 request
 =SubRequest'database_session'forFunction'test_track_before_delete',engine
 =Engine(sqlite:///test.db)


  

 @pytest.fixture

 defdatabase_session(request,engine):

 connection =engine.connect()

 trans =connection.begin()

  


 meta.Session=scoped_session(sessionmaker(autocommit=False,autoflush=True,bind=connection))

 register_session_listeners(meta.Session)

  

 meta.Session.begin_nested()

   @event.listens_for(meta.Session,after_transaction_end)

 defrestart_savepoint(session,transaction):

 iftransaction.nested andnottransaction._parent.nested:


  

 automated_tests/conftest.py:52: 

 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/api.py:94:indecorate


 listen(target,identifier,fn,*args,**kw)

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/api.py:63:inlisten


 _event_key(target,identifier,fn).listen(*args,**kw)

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/registry.py:187:inlisten


 self.dispatch_target.dispatch._listen(self,*args,**kw)

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/base.py:184:in_listen


 event_key.base_listen(propagate=propagate,insert=insert,named=named)

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/registry.py:226:inbase_listen


 for_modify(target.dispatch).append(self,propagate)

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/attr.py:118:inappend


 registry._stored_in_collection(event_key,self)

 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

  

 event_key =sqlalchemy.event.registry._EventKey objectat
 0x111c6b3d0,owner =sqlalchemy.event.attr._DispatchDescriptor
 objectat 0x10eac8590


  

 def_stored_in_collection(event_key,owner):

 key =event_key._key

  

 dispatch_reg =_key_to_collection[key]

  

 owner_ref =owner.ref

 listen_ref =weakref.ref(event_key._listen_fn)

  

 ifowner_ref indispatch_reg:

   assertdispatch_reg[owner_ref]==listen_ref

 E   assertweakref at 0x111cc25d0;dead==weakref at
 0x111cc2af8;to 'function'at 0x111c7f668(restart_savepoint)


  

 ../../../.virtualenvs/ERP/lib/python2.7/site-packages/sqlalchemy/event/registry.py:74:AssertionError
 |


 While running the @event.listens_for decorator in the setup of a test
 fixture, SQLAlchemy throws an assertion because the ref it has in the
 dispatch registry isn't identical to the ref to the listener function.
  We're on SQLAlchemy 0.9.7, pytest 2.6.4.  Note that the test suite is
 setting up nested transactions on SQLite; we are using the recommended
 workaround from SQLAlchemy documentation to take control of
 transactional integration from pysqlite.


 Since there's an assertion in the code in _stored_in_collection(), I
 assume that there's some expectation that the refs might not match; am
 I doing something wrong in my setup which this assertion is meant to
 catch?  I've only seen this error once (while tracking down a
 different nondeterministic error in my own app's code), so I can't
 provide much more information than this, but the portion of test
 fixture code seen above in the stack trace is basically the entire
 reproduction case.  This fixture runs before every test in my suite,
 but I've only seen an error once across a large number of runs, so the
 error is *very* intermittent.  Because of that, I'm not worried too
 much about the error itself, but I thought I should post it here in
 case it's a symptom of something I should be worrying about.

this was issue https://bitbucket.org/zzzeek/sqlalchemy/issue/3199 and
has been fixed as of 0.9.8.






 Thanks,

 Evan James

 -- 
 You received this message because you are subscribed to the Google
 Groups sqlalchemy group.
 To unsubscribe from this group and stop receiving emails from it, send
 an email to sqlalchemy+unsubscr...@googlegroups.com
 mailto:sqlalchemy+unsubscr...@googlegroups.com.
 To post to this group, send email to sqlalchemy@googlegroups.com
 mailto:sqlalchemy@googlegroups.com.
 Visit this group at http://groups.google.com/group/sqlalchemy.
 For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at 

Re: [sqlalchemy] Inherited class column override

2015-04-02 Thread Pierre B
Seems like exactly what I need! Many thanks for all your help, I will try 
that out right away :)

On Wednesday, April 1, 2015 at 7:04:51 PM UTC+2, Michael Bayer wrote:



 On 4/1/15 10:28 AM, Pierre B wrote:
  
  Here's a simple visual of the schema
  

 OK, so that's called a polymorphic foreign key.  SQLAlchemy doesn't have 
 first class support for this concept because it's relationally incorrect, 
 but there is an example at 
 http://docs.sqlalchemy.org/en/rel_0_9/_modules/examples/generic_associations/generic_fk.html
  
 which shows one way to produce this effect.The key aspects to this in 
 reference to your model attempts are that there are no ForeignKey objects; 
 objects like Column and ForeignKey are schema-level objects, and if you 
 construct one, that implies it exists in the schema.  That's why you can't 
 make two Column objects with the same name pointing to the same table, and 
 this is what I'm referring to when I say that the schema has to be 
 considered when building out these declarations.

 The techniques to make the relationship here involve using the 
 primaryjoin argument to establish how the tables join directly, as well 
 as the foreign() annotation and/or foreign_keys argument which you can 
 see used in the example; that is, how the tables join is constructed using 
 all ORM constructs and not schema-level constructs.



   
  https://i.imgur.com/TaC2V6b.png

 There are no foreign key constraints in the database schema, id1 and id2 
 are just stored there, a type column is used to retrieve records e.g type 
 equals B for an association between RightB and ReftB and equals A between 
 RightA and LeftA. That is why I'm trying to set a default value for the 
 type column so I don't have to deal with that junction table when inserting 
 records.


 On Wednesday, April 1, 2015 at 4:11:03 PM UTC+2, Michael Bayer wrote: 

  

 On 4/1/15 4:55 AM, Pierre B wrote:
  
 Unfortunately I'm inheriting the relational model from an old application. 
 I have dozens of tables using a single junction table for associations. 
 I can not completely redesign my relational model because it needs to be 
 compatible with the old application.
  
 I was asking no such thing.  I only ask that you consider the relational 
 model when building *new* elements of the application.   If these models 
 are in fact mapping to an existing schema, I find it surprising that your 
 existing database schema includes *two* foreign key constraints present on 
 each of people4l2.id1 and people4l2.id2, constraining each column to both 
 left1.id/left2.id and right1.id/right2.id.




   At this point, I think my best option is setting up table inheritance 
 at the database level (database is Postgresql) and migrating records into 
 children tables. Minimal code refactoring would be involved in the old 
 application and it would be possible to use the association object pattern.

 On Tuesday, March 31, 2015 at 8:05:19 PM UTC+2, Michael Bayer wrote: 



 Pierre B rocambol...@gmail.com wrote: 

  I tried using the association object pattern before but can't get it to 
 work because I use the same id1 and id2 columns for all foreign keys and 
 I'm not able to override them in the sub-classes (conflicts with existing 
 column error). 
  class MyClass(HasSomeAttribute, db.Model): 
 __tablename__ = 'people4l2' 
 id = db.Column(db.Integer, primary_key=True) 
  
  class MySubClass1(MyClass): 
 right1_id = db.Column('id2', db.Integer, ForeignKey('right1.id')) 
 left1_id = db.Column('id1', db.Integer, ForeignKey('left1.id')) 
  
  class MySubClass2(MyClass): 
 right2_id = db.Column('id2', db.Integer, ForeignKey('right2.id')) 
 left2_id = db.Column('id1', db.Integer, ForeignKey('left2.id’)) 

 That’s because you do not have a __tablename__ for these subclasses, so 
 when 
 you put a column on the subclass, that is physically a column on the 
 ‘people4l2’ table; the names cannot be conflicting. Also, it is not 
 possible 
 to have a column named “people4l2.id2” which is in some cases a foreign 
 key 
 to “right1.id” and in other cases to “right2.id”. 

 This probably all seems very complicated if you only think of it in terms 
 of 
 a Python object model. That’s why it is essential that you design your 
 database schema in terms of database tables, and how those tables will 
 work 
 within a purely relational model, without Python being involved, first. 

 For simple cases, the design of the relational model and the object model 
 are so similar that this explicit step isn’t necessary, but once the goals 
 become a little bit divergent between relational and object model, that’s 
 when the relational model has to be developed separately, up front. This 
 is 
 the essence of how SQLAlchemy works, which becomes apparent the moment you 
 get into models like these which are typically impossible on most other 
 ORMs, since most ORMs do not consider design of the relational model as 
 separate from the 

Re: [sqlalchemy] PostgreSQL UNION with ORDER or LIMIT parenthesis patch

2015-04-02 Thread Mike Bayer
haha it's not resolved :)  UNIONs are really tough, as are SQLite's
choice of quirks in this area. 

On 4/1/15 11:27 PM, Charles Leifer wrote:
 Funnily enough I ran into this particular question regarding Peewee
 ORM and, googling Postgresql UNION parentheses, I found this post. I
 ran into the same thing where PG likes the parentheses, but SQLite
 doesn't. I know this is a very old post, but out of curiosity, how did
 you end up resolving this?

 On Thursday, July 5, 2012 at 4:35:51 PM UTC-5, Michael Bayer wrote:


 On Jul 5, 2012, at 4:57 PM, Sergey Shepelev wrote:

 Problem: sa.union_all(*qs).order_by('y')  will generate invalid
 SQL like this:

 SELECT ... ORDER BY x LIMIT 10 UNION ALL SELECT ... ORDER BY x
 LIMIT 10 ORDER BY y

 Order in inner queries could be required for LIMIT or DISTINCT ON
 (field).

 Solution: sqlalchemy should put all inner queries that contain
 ORDER BY or LIMIT clauses into parenthesis, like this:

 SELECT ... UNION ALL (SELECT ... ORDER BY x LIMIT 10) ORDER BY y


 Test:
 def test_with_order(self):
 q = sa.select([2]).order_by('1')
 union = sa.union(q, q).limit(3)
 db.postgresql.execute(union)
 self.assertEqual(str(union), (SELECT 2 ORDER BY 1) UNION
 (SELECT 2 ORDER BY 1) LIMIT 3)

 Monkey patch at https://gist.github.com/3056292
 https://gist.github.com/3056292

 This is tricky because the SQL expression language doesn't like to
 make guesses like that, that is, decides about the structure of
 the query based on things inside of select() constructs.  The ORM
 does that kind of thing more acceptably, but it isn't covering
 this case either at the moment.

 The above SQL is not accepted on some backends - that is, it fails
 on SQLite at least.   Odd structures like that tend to not work on
 several of the more quirky backends, common culprits are Oracle,
 Firebird, SQL Server, older MySQL versions.  

 So to make this magic here is to make the select() construct
 behave differently on the PG backend, or maybe just not on the
  SQLite backend, but this is tricky to decide without knowing the
 true compatibility here and what's going to break for whom if we
 make a change like that - the SQL expression language tends to
 want you to manually apply structural conventions right now (such
 as, if you say select().select(), there's no alias generated
 automatically, things like that).

 I would have expected alias() to work here, but it doesn't.  so
 it's on deck to work on why that is at some point.

 The point is right now you can force the parens using
 self_group(), and I'd be more comfortable if you stuck with that
 for this particular case right now, though I may change my mind
 about handling this case in the future:

 q = select([2]).limit(5).order_by('1').self_group()
 union = union(q, q).limit(3)
 print union

 i put up http://www.sqlalchemy.org/trac/ticket/2528
 http://www.sqlalchemy.org/trac/ticket/2528, where the goal is to
 test the compatibility of this kind of query across the most
 common backends and see what the best approach might be.   I'd
 probably go as far as alias() rending parenthesis, though (that
 is, self_group() will likely remain exactly what you want here).








 -- 
 You received this message because you are subscribed to the Google
 Groups sqlalchemy group.
 To unsubscribe from this group and stop receiving emails from it, send
 an email to sqlalchemy+unsubscr...@googlegroups.com
 mailto:sqlalchemy+unsubscr...@googlegroups.com.
 To post to this group, send email to sqlalchemy@googlegroups.com
 mailto:sqlalchemy@googlegroups.com.
 Visit this group at http://groups.google.com/group/sqlalchemy.
 For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.


Re: [sqlalchemy] PostgreSQL UNION with ORDER or LIMIT parenthesis patch

2015-04-02 Thread Jonathan Vanasco
I wish I saw this earlier.  This thread basically describes a situation 
I've had, and explains a few of the weird hacks I've had to use.

If you need a workaround, I've been getting around this by using a union on 
2 subqueries, querying the union columns, and using plaintext sql to order 
(the sql ordering works in = 0.9.9 and = 1.0.0b4)

q_a = s.query(Foo).subquery(name='select_a')
q_b = s.query(Foo).subquery(name='select_b')
q_union = sqlalchemy.union(q_a.select(), 
q_b.select()).alias('select_ab')
q = s.query(q_union.c.field).order_by( sqlalchemy.desc('plaintext'))


-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.


Re: [sqlalchemy] Nested transaction rollback does not undo changes to instances

2015-04-02 Thread Mike Bayer


On 4/2/15 10:01 AM, Chris Wilson wrote:
 Dear SQLAlchemy developers,

 I think I've found a problem with SQLAlchemy not rolling back changes
 to instances that are committed in an inner nested transaction, when
 the outer nested transaction rolls back.

 The manual says:

 When begin_nested()
 
 http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.begin_nested
 is called, a flush()
 
 http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.flush
 is unconditionally issued (regardless of the autoflush setting).
 This is so that when a rollback()
 
 http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.rollback
 occurs, the full state of the session is expired, thus causing all
 subsequent attribute/instance access to reference the full state
 of the Session
 
 http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session
 right before begin_nested()
 
 http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.begin_nestedwas
 called.


 So I think that if we make a change to an instance, inside a
 transaction, and then rollback, we should see the old values in any
 instances, as if the transaction never happened. And indeed this
 appears to work for simple cases:

 from sqlalchemy import Column, Integer, Boolean
 from sqlalchemy import create_engine
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import sessionmaker

 Base = declarative_base()
 class Test(Base):
 __tablename__ = 'test'
 id = Column(Integer, primary_key=True)
 value = Column(Boolean)
 
 engine = create_engine('sqlite:///sqlalchemy_example.db')

 Base.metadata.create_all(engine)

 DBSession = sessionmaker(bind=engine)
 session = DBSession(autocommit=True)

 session.begin()
 test_data = Test(value=False)
 session.add(test_data)
 session.commit()
 print test_data.value, in the beginning

 try:
 with session.begin():
 # with session.begin_nested():
 test_data.value = True
 print test_data.value, before rollback
 raise ValueError(force a rollback)
 except ValueError as e:
 print ValueError caught: {}.format(e)
 print test_data.value, after rollback


 Which, as expected, resets value from True to False during rollback():

 False in the beginning

 True before rollback

 ValueError caught: force a rollback

 False after rollback


 Note: this example doesn't work with my SQLite install because it
 doesn't seem to support savepoints properly, even though it's
 documented to do so. I used postgresql to actually run these tests,
 and created the table as follows:

 CREATE TABLE test (id SERIAL NOT NULL PRIMARY KEY, value BOOLEAN);
 GRANT ALL ON TABLE test TO standard;
 GRANT ALL ON TABLE test_id_seq TO standard;


 It even works if you rollback a nested transaction:

 try:
 with session.begin():
 try:
 with session.begin_nested():
 test_data.value = True
 print test_data.value, after nested commit,
 before nested rollback
 raise ValueError(force a rollback)
 except ValueError as e:
 print ValueError caught: {}.format(e)
 
 print test_data.value, after nested rollback
 assert not test_data.value, should have been rolled back
 
 assert session.transaction is not None
 raise ValueError(force a rollback)
 except ValueError as e:
 print ValueError caught: {}.format(e)
 print test_data.value, after outer rollback


 However, it does NOT work if you roll back a nested transaction that
 has a committed nested transaction inside it (differences highlighted):

 try:
 with session.begin():
 try:
 with session.begin_nested():
 with session.begin_nested():
 test_data.value = True
 print test_data.value, after nested commit,
 before nested rollback
 raise ValueError(force a rollback)
 except ValueError as e:
 print ValueError caught: {}.format(e)
 
 print test_data.value, after nested rollback
 # assert not test_data.value, should have been rolled
 back; this assertion fails if enabled
 
 assert session.transaction is not None
 raise ValueError(force a rollback)
 except ValueError as e:
 print ValueError caught: {}.format(e)

 print test_data.value, after outer rollback
 assert session.transaction is None


 Which outputs:

 False in the beginning

   

[sqlalchemy] Nested transaction rollback does not undo changes to instances

2015-04-02 Thread Chris Wilson
Dear SQLAlchemy developers,

I think I've found a problem with SQLAlchemy not rolling back changes to 
instances that are committed in an inner nested transaction, when the outer 
nested transaction rolls back.

The manual says:

When begin_nested() 
http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.begin_nested
 
is called, a flush() 
http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.flush
 
is unconditionally issued (regardless of the autoflush setting). This is so 
that when a rollback() 
http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.rollback
 
occurs, the full state of the session is expired, thus causing all 
subsequent attribute/instance access to reference the full state of the 
Session 
http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session
 
right before begin_nested() 
http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.begin_nestedwas
 
called.


So I think that if we make a change to an instance, inside a transaction, 
and then rollback, we should see the old values in any instances, as if the 
transaction never happened. And indeed this appears to work for simple 
cases:

from sqlalchemy import Column, Integer, Boolean
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
class Test(Base):
__tablename__ = 'test'
id = Column(Integer, primary_key=True)
value = Column(Boolean)

engine = create_engine('sqlite:///sqlalchemy_example.db')

Base.metadata.create_all(engine)

DBSession = sessionmaker(bind=engine)
session = DBSession(autocommit=True)

session.begin()
test_data = Test(value=False)
session.add(test_data)
session.commit()
print test_data.value, in the beginning

try:
with session.begin():
# with session.begin_nested():
test_data.value = True
print test_data.value, before rollback
raise ValueError(force a rollback)
except ValueError as e:
print ValueError caught: {}.format(e)
print test_data.value, after rollback


Which, as expected, resets value from True to False during rollback():

False in the beginning

True before rollback

ValueError caught: force a rollback

False after rollback


Note: this example doesn't work with my SQLite install because it doesn't 
seem to support savepoints properly, even though it's documented to do so. 
I used postgresql to actually run these tests, and created the table as 
follows:

CREATE TABLE test (id SERIAL NOT NULL PRIMARY KEY, value BOOLEAN);
GRANT ALL ON TABLE test TO standard;
GRANT ALL ON TABLE test_id_seq TO standard;


It even works if you rollback a nested transaction:

try:
with session.begin():
try:
with session.begin_nested():
test_data.value = True
print test_data.value, after nested commit, before nested 
rollback
raise ValueError(force a rollback)
except ValueError as e:
print ValueError caught: {}.format(e)

print test_data.value, after nested rollback
assert not test_data.value, should have been rolled back

assert session.transaction is not None
raise ValueError(force a rollback)
except ValueError as e:
print ValueError caught: {}.format(e)
print test_data.value, after outer rollback


However, it does NOT work if you roll back a nested transaction that has a 
committed nested transaction inside it (differences highlighted):

try:
with session.begin():
try:
with session.begin_nested():
with session.begin_nested():
test_data.value = True
print test_data.value, after nested commit, before nested 
rollback
raise ValueError(force a rollback)
except ValueError as e:
print ValueError caught: {}.format(e)

print test_data.value, after nested rollback
# assert not test_data.value, should have been rolled back; this 
assertion fails if enabled

assert session.transaction is not None
raise ValueError(force a rollback)
except ValueError as e:
print ValueError caught: {}.format(e)

print test_data.value, after outer rollback
assert session.transaction is None


Which outputs:

False in the beginning

True after nested commit, before nested rollback

ValueError caught: force a rollback

True after nested rollback

ValueError caught: force a rollback

False after outer rollback


You can see that after the nested transaction rollback, the value on the 
instance has not been reset to False, but the outer (final) rollback 
successfully resets it.

We spotted this because we run tests in transactions (currently nested 
transactions, although now I have to change it to work around this issue), 

Re: [sqlalchemy] Nested transaction rollback does not undo changes to instances

2015-04-02 Thread Mike Bayer


On 4/2/15 11:54 AM, Mike Bayer wrote:

 it's a bug, and I've captured the origin, diagnosis and probable
 solution here:
 https://bitbucket.org/zzzeek/sqlalchemy/issue/3352/nested-begin_nested-blocks-dont-track

this issue is fixed for 0.9.10 and 1.0.0b5, you can test now using
either latest master or the rel_0_9 branch.


-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.