[sqlalchemy] Changing a value linked with a one-to-one relationship

2013-06-04 Thread Etienne Rouxel
Hello

I would like to change a value in a one-to-one relationship but I cannot 
because of some actions that SQLAlchemy try to do, and I don't know why.
Here is my simplified code :

# -*- coding: utf-8 -*-
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

_taxon_table = Table('taxon', Base.metadata,
Column('id', Integer, primary_key=True),
schema='botany'
)

_foliagetype_table = Table('foliagetype', Base.metadata,
Column('id', Integer, primary_key=True),
schema='botany'
)

_plant_table = Table('plant', Base.metadata,
Column('id', Integer, primary_key=True),
Column('taxon_id', Integer),
Column('foliagetype_id', Integer),
ForeignKeyConstraint(['taxon_id'], ['botany.taxon.id']),
ForeignKeyConstraint(['foliagetype_id'], ['botany.foliagetype.id']),
schema='botany'
)

class Taxon(Base):
__table__ = _taxon_table

class Foliagetype(Base):
__table__ = _foliagetype_table

class Plant(Base):
__table__ = _plant_table

taxon = relationship('Taxon',
backref=backref('plant',
uselist=False))

foliagetype = relationship('Foliagetype',
backref=backref('plants'))

if __name__ == '__main__':

engine = create_engine('postgresql://xxx@localhost:5432/xxx')
Session = sessionmaker(bind=engine)
session = Session()

taxon_old = session.query(Taxon).get(-2147483634)
taxon_new = session.query(Taxon).get(-2147483645)

foliagetype_old = session.query(Foliagetype).get(-2147483646)
foliagetype_new = session.query(Foliagetype).get(-2147483645)

plant = session.query(Plant).get(-2147483643)

print -- Change foliagetype --
print plant.foliagetype is foliagetype_old  # True
plant.foliagetype = foliagetype_new
print plant.foliagetype is foliagetype_new  # True

print -- Change taxon --
print plant.taxon is taxon_old  # True
plant.taxon = taxon_new  # triggers an IntegrityError
print plant.taxon is taxon_new



So a plant must have one and exactly one foliagetype and same with the the 
taxon : a plant must have one and exactly one taxon. The difference 
however, is that a foliagetype can be linked to several plants while a 
taxon can only be linked to a single plant.

In my code above the change of foliagetype leads SQLAlchemy to output the 
expected SQL :

UPDATE botany.plant SET foliagetype_id= -2147483645 WHERE botany.plant.id = 
 -2147483643


However, concerning the change of taxon, I don't understand what SQLAlchemy 
is doing. It first output:

SELECT botany.plant.id AS botany_plant_id, botany.plant.taxon_id AS 
botany_plant_taxon_id, botany.plant.foliagetype_id AS 
botany_plant_foliagetype_id 
FROM botany.plant 
WHERE  -2147483634 = botany.plant.taxon_id


and then:

UPDATE botany.plant SET taxon_id=NULL WHERE botany.plant.id =  -2147483643


which fails because NULL is not allowed for taxon_id. So, why do not 
SQLAlchemy just output this instead?

UPDATE botany.plant SET taxon_id= -2147483645 WHERE botany.plant.id = 
 -2147483643


-- 
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?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.




Re: [sqlalchemy] Changing a value linked with a one-to-one relationship

2013-06-04 Thread Michael Bayer

On Jun 4, 2013, at 10:38 AM, Etienne Rouxel rouxel.etie...@gmail.com wrote:

 
 and then:
 
 UPDATE botany.plant SET taxon_id=NULL WHERE botany.plant.id =  -2147483643
 
 which fails because NULL is not allowed for taxon_id. So, why do not 
 SQLAlchemy just output this instead?
 
 UPDATE botany.plant SET taxon_id= -2147483645 WHERE botany.plant.id =  
 -2147483643

if you look in your stack trace, you'll see the word autoflush (I adapted 
your excerpt into a full test case to reproduce):

Traceback (most recent call last):
  File test.py, line 74, in module
plant.taxon = taxon_new  # triggers an IntegrityError
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py, line 
303, in __set__

...

 File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py, line 
824, in fire_replace_event
value = fn(state, value, previous, initiator or self)
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py, line 
1131, in emit_backref_from_scalar_set_event
passive=PASSIVE_NO_FETCH)
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py, line 
638, in append

... 

 File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/strategies.py, line 
585, in _emit_lazyload
result = q.all()
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py, line 2219, 
in all
return list(self)
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py, line 2330, 
in __iter__
self.session._autoflush()
  File /Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py, line 
1139, in _autoflush
self.flush()


you can see that the ORM is not getting as far as taxon_new being fully 
assigned.   The assignment is resulting in the backref firing off and leading 
to an assignment on the Taxon.plant side.The assignment then tries to load 
the existing value for Taxon.plant, which results in an autoflush, which 
results in the integrity error.

There seems to be some unfortunate effect going on, as the issue of backrefs 
loading collections like this was long ago resolved to defer that activity, but 
it appears that when uselist=False is present, this logic no longer takes 
place.  Ticket http://www.sqlalchemy.org/trac/ticket/2741 has been added to 
look into this as this should not be happening.

Anytime a stack trace has autoflush in it where you don't want autoflush to 
occur, defer autoflush for those operations like this:

with session.no_autoflush:
plant.taxon = taxon_new
session.flush()  # optional, emits the UPDATE you're looking for sooner rather 
than later
print plant.taxon is taxon_new




-- 
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?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.




Re: [sqlalchemy] Changing a value linked with a one-to-one relationship

2013-06-04 Thread Michael Bayer

On Jun 4, 2013, at 11:15 AM, Michael Bayer mike...@zzzcomputing.com wrote:

 There seems to be some unfortunate effect going on, as the issue of backrefs 
 loading collections like this was long ago resolved to defer that activity, 
 but it appears that when uselist=False is present, this logic no longer takes 
 place.  Ticket http://www.sqlalchemy.org/trac/ticket/2741 has been added to 
 look into this as this should not be happening.
 

OK I've looked into it, and it actually should be happening, because the 
difference between uselist=True and uselist=False is that when setting a 
uselist=False, the *old* value must be removed, so we have to load the old 
value in order to reflect that in the history.

So when we say:

plant.taxon = taxon_new

SQLAlchemy must load taxon.plant on taxon_new to get the previous plant 
value that's present, if any, so that it can be de-associated with taxon_new as 
taxon.plant is uselist=False.   This can't be determined with taxon_new alone 
- the database must be scanned for taxon_new's identity in the plant table.   
 So by default this will also autoflush, so here you just want to temporarily 
disable autoflush.



-- 
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?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.