[sqlalchemy] SQLAlchemy's merge doesn't fill foreign keys with ids of new objects in relationship

2015-04-14 Thread Юрий Пайков


My question is when I have in a session a newly created object(doesn't have 
primary key yet, but will obtain it upon flush) and I merge to that session 
another object referring to the first one by relationship (*b* in the 
example) SQLAlchemy doesn't populate latter object with the primary key 
from the former. Instead it just generate next value from the sequence. Why 
is it the case ?

from sqlalchemy import create_enginefrom sqlalchemy.orm import sessionmaker



engine = 
create_engine(postgresql+psycopg2://psql_admin:psql_admin@localhost/fm)


from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from sqlalchemy import Integer, ForeignKey, VARCHAR, TEXT, Boolean, 
DateTimefrom sqlalchemy.orm import relationshipfrom sqlalchemy.sql.schema 
import Column
class B(Base):
__tablename__='B'
id_=Column(Integer, primary_key=True)
data = Column(VARCHAR(30))
class Rel(Base):
__tablename__='Rel'
id_a=Column(Integer, primary_key=True)
id_b=Column(Integer, ForeignKey('B.id_'), primary_key=True)
b = relationship(B)
rel_data=Column(VARCHAR(30))

Session = sessionmaker(bind=engine)   
session = Session()Base.metadata.create_all(engine, checkfirst=True)


first_b=B(id_=1, data='ololo')
session.add(first_b)
session.commit()

session.add(Rel(id_a=800,id_b=1, rel_data='first relation data'))

second_b=B(data='f')
session.add(second_b)
x=session.merge(Rel(id_a=800, rel_data=second, b=second_b))
session.commit()

Here I have an error

IntegrityError: (raised as a result of Query-invoked autoflush; consider 
using a session.no_autoflush block if this flush is occuring prematurely) 
(IntegrityError) duplicate key value violates unique constraint B_pkey 
DETAIL: Key (id_)=(1) already exists. 'INSERT INTO B (data) VALUES 
(%(data)s) RETURNING B.id_' {'data': 'f'}

-- 
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] polymorphic inheritance and unique constraints

2015-04-14 Thread Richard Gerd Kuesters | Pollux Automation
sorry, i mean i couldn't test it earlier, when i first asked the 
question :) it was not another co-worker, lol.


cheers,
richard.

On 04/13/2015 06:30 PM, Mike Bayer wrote:



On 4/13/15 4:59 PM, Richard Gerd Kuesters | Pollux Automation wrote:
well, this didn't work with upstream 1.0 - sorry, I was in another 
project and couldn't test it myself.



you're not doing the same thing this user was doing in any case...



Traceback (most recent call last):
  File database_test.py, line 46, in module
from plx.db.core import *
  File ../src/plx/db/core.py, line 901, in module
UniqueConstraint(ContainerInstance.batch_id, 
ContainerAggregation.container_descriptor_id,)
  File 
/home/richard/.pyenv/versions/vpak-pollux-2.7.9/lib/python2.7/site-packages/sqlalchemy/sql/schema.py, 
line 2464, in __init__
ColumnCollectionMixin.__init__(self, *columns, 
_autoattach=_autoattach)
  File 
/home/richard/.pyenv/versions/vpak-pollux-2.7.9/lib/python2.7/site-packages/sqlalchemy/sql/schema.py, 
line 2393, in __init__

self._check_attach()
  File 
/home/richard/.pyenv/versions/vpak-pollux-2.7.9/lib/python2.7/site-packages/sqlalchemy/sql/schema.py, 
line 2429, in _check_attach

table.description)
sqlalchemy.exc.ArgumentError: Column(s) 
'container_aggregation.fk_container_descriptor_id' are not part of 
table 'container_instance'.


I got sqlalchemy from git, today.

 sqlalchemy.__version__
'1.0.0'

container_aggretation is a subclass of container_instance. I'm not 
using concrete inheritance here, may this be the problem?


anything else, it's Python 2.7.9 + Linux + PostgreSQL 9.4.1.


cheers,
richard.

On 03/24/2015 08:49 PM, Michael Bayer wrote:

are these two separate constraints?  I just looked and it seems like they are 
distinct.

I just added a fix to 1.0 because someone was hacking around something similar 
to this.

The easiest way to get these for the moment is just to create the 
UniqueConstraint outside of the class definition.

class Foo(Base):
 # …

class Bar(Foo):
# …

UniqueConstraint(Bar.x, Foo.y)

that way all the columns are set up, should just work.



Richard Gerd Kuesters | Polluxrich...@pollux.com.br  wrote:


well, understanding better the docs for column conflicts, can i use a 
declared_attr in a unique constraint? if yes, my problem is solved :)


On 03/24/2015 10:33 AM, Michael Bayer wrote:

Richard Gerd Kuesters | Pollux
rich...@pollux.com.br
  wrote:



hi all!

i'm dealing with a little problem here. i have a parent table and its two 
inheritances. there is a value that both children have and must be unique along 
either types. is there a way to move this column to the parent and use a 
constraint in the child? my implementation is postgres 9.4+ with psycopg2 only.

if this is single table inheritance then the constraint would most ideally
be placed on the parent class.

if you’re trying to make this “magic” such that you can semantically keep
the unique constraints on the child classes, you’d need to build out a
conditional approach within @declared_attr. IMO I think this is an idealized
edge case that in the real world doesn’t matter much - just do what works
(put the col / constraint on the base).

the approach is described at

http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/declarative/inheritance.html#resolving-column-conflicts
.
You’d need to make this work for both the column and the constraint.




as a simple example (i'm just creating this example to simplify things), this 
works:

class MyParent(Base):

 foo_id = Column(Integer, Sequence('foo_id_seq'), primary_key=True)
 foo_name = Column(Unicode(64), nullable=False)
 foo_type = Column(Integer, nullable=False)

 __mapper_args__ = {
 polymorphic_on: foo_type,
 polymorphic_identity: 0
 }


class MyChild1(MyParent):

 foo_id = Column(Integer, ForeignKey(MyParent.foo_id), primary_key=True)
 bar_id = Column(Integer, ForeignKey(AnotherEntity.bar_id), nullable=False)
 child1_specific_name = Column(Unicode(5), nullable=False)
 child1_baz_stuff = Column(Boolean, default=False)

 __mapper_args__ = {
 polymorphic_identity: 1
 }

 __table_args__ = (
 UniqueConstraint(bar_id, child1_specific_name,),  # works, bar_id is 
in MyChild1
 )


class MyChild2(MyParent):

 foo_id = Column(Integer, ForeignKey(MyParent.foo_id), primary_key=True)
 bar_id = Column(Integer, ForeignKey(AnotherEntity.bar_id), nullable=False)
 child2_specific_code = Column(UUID, nullable=False)
 child2_baz_stuff = Column(Float, nullable=False)
 
 __mapper_args__ = {

 polymorphic_identity: 2
 }

 __table_args__ = (
 UniqueConstraint(bar_id, child2_specific_code,),  # works, bar_id is 
in MyChild2
 )


but i would like to do this, if possible:

class MyParent(Base):

 foo_id = Column(Integer, Sequence('foo_id_seq'), primary_key=True)
 foo_name = Column(Unicode(64), 

[sqlalchemy] evaluate strategy for a bulk delete seems to mishandle cases where column and attribute names differ

2015-04-14 Thread Steven Winfield
Hi, 

I think I've found a bug triggered by bulk deletes that use the (default) 
evaluate strategy.

For example:

from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Parent(Base):
__tablename__ = parent
id = Column(Integer, primary_key=True)

class Child(Base):
__tablename__ = child
_id_parent = Column(id_parent, Integer, ForeignKey(Parent.id), 
primary_key=True)
name = Column(Text, primary_key=True)
parent = relationship(Parent)

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.bind.echo = True

# Make a parent
p = Parent(id=1)
session.add(p)
session.commit()

# Add a child
c = Child(name=foo, parent=p)
session.add(c)
session.commit()
# c is still in the session

session.query(Child).filter(Child.parent == p).delete(evaluate)

...give the following traceback:

File user!winfis!test_bed.py, line 34, in : 
session.query(Child).filter(Child.parent == p).delete(evaluate) 
file:\user!winfis!test_bed.py:34:exception
File 
R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py,
 
line 2670, in delete : delete_op.exec_() 
file:\R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py:2670:exception
File 
R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py,
 
line 896, in exec_ : self._do_pre_synchronize() 
file:\R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py:896:exception
File 
R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py,
 
line 958, in _do_pre_synchronize : eval_condition(obj)] 
file:\R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py:958:exception
File 
R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py,
 
line 116, in evaluate : right_val = eval_right(obj) 
file:\R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py:116:exception
File 
R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py,
 
line 72, in : return lambda obj: get_corresponding_attr(obj) 
file:\R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py:72:exception
AttributeError: 'Child' object has no attribute 'id_parent'


...because the attribute lookup on Child is attempted using the column 
name, rather than the attribute name. The actual delete action works fine 
when I switch the strategy to False or fetch, or if there are no Child 
objects in the session (so no evaluation is invoked).


As you can see, this is v0.9.7, but I've not seen anything relevant in the 
changelog since then. 

Cheers,
Steve.

-- 
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] SQLAlchemy's merge doesn't fill foreign keys with ids of new objects in relationship

2015-04-14 Thread Юрий Пайков
Oh, it seems that merge() actually does populate the b_id if that B is 
non-conflicting... Seems I have another problem . 
Thank you anyway, Michael

вторник, 14 апреля 2015 г., 20:08:04 UTC+5 пользователь Michael Bayer 
написал:

  

 that's not what I see happening here.   I see very simply that the B.id_ 
 column is a SERIAL so is linked to a sequence, however you are inserting a 
 row with a hardcoded 1 for a primary key; so the second B object, which 
 relies on the sequence, fails due to an identity conflict.

 So let's repair the test case first, and that first B.id we'll set to 10 
 so that it doesn't conflict.

 Now we get the error you probably intended to send:

 SELECT Rel.id_a AS Rel_id_a, Rel.id_b AS Rel_id_b, Rel.rel_data 
 AS Rel_rel_data 
 FROM Rel 
 WHERE Rel.id_a = %(param_1)s AND Rel.id_b = %(param_2)s
 2015-04-14 11:05:11,850 INFO sqlalchemy.engine.base.Engine {'param_1': 
 800, 'param_2': symbol('NEVER_SET')}

 where this is, the merge() is proceeding to attempt to locate the object 
 by primary key but the PK is not filled in.  This is the expected 
 behavior.   The primary key of an object is never auto-populated until it 
 is flushed.   So here, if you are passing in a transient object, you need 
 to set the PK yourself:

 second_b = B(data='f')
 session.add(second_b)
 session.flush()
 x = session.merge(Rel(id_a=800, rel_data=second, id_b=second_b.id_))



 

-- 
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] evaluate strategy for a bulk delete seems to mishandle cases where column and attribute names differ

2015-04-14 Thread Steven Winfield
Fantastic, thanks!

On Tuesday, April 14, 2015 at 4:00:01 PM UTC+1, Michael Bayer wrote:

  

 On 4/14/15 6:38 AM, Steven Winfield wrote:
  
  Hi, 

  I think I've found a bug triggered by bulk deletes that use the 
 (default) evaluate strategy.
  

 a bug is created at 
 https://bitbucket.org/zzzeek/sqlalchemy/issue/3365/evaluator-cant-locate-orm-entity-when
   
 and for now you need to compare using the columns, not the relationship, 
 e.g. Child._id_parent == parent.id, if you want to use evaluate there.


  
  For example: 

  from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import sessionmaker, relationship

  Base = declarative_base()

  class Parent(Base):
 __tablename__ = parent
 id = Column(Integer, primary_key=True)

  class Child(Base):
 __tablename__ = child
 _id_parent = Column(id_parent, Integer, ForeignKey(Parent.id), 
 primary_key=True)
 name = Column(Text, primary_key=True)
 parent = relationship(Parent)

  engine = create_engine('sqlite://')
 Base.metadata.create_all(engine)
 Session = sessionmaker(bind=engine)
 session = Session()
 session.bind.echo = True

  # Make a parent
 p = Parent(id=1)
 session.add(p)
 session.commit()

  # Add a child
 c = Child(name=foo, parent=p)
 session.add(c)
 session.commit()
 # c is still in the session

  session.query(Child).filter(Child.parent == p).delete(evaluate)
  
  ...give the following traceback:

  File user!winfis!test_bed.py, line 34, in : 
 session.query(Child).filter(Child.parent == p).delete(evaluate) 
 File 
 R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\query.py,
  
 line 2670, in delete : delete_op.exec_() 
 File 
 R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py,
  
 line 896, in exec_ : self._do_pre_synchronize() 
 File 
 R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\persistence.py,
  
 line 958, in _do_pre_synchronize : eval_condition(obj)] 
 File 
 R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py,
  
 line 116, in evaluate : right_val = eval_right(obj) 
 File 
 R:\sw\external\20150407-0\python27\lib\site-packages\sqlalchemy-0.9.7-py2.7-win32.egg\sqlalchemy\orm\evaluator.py,
  
 line 72, in : return lambda obj: get_corresponding_attr(obj) 
 AttributeError: 'Child' object has no attribute 'id_parent'


  ...because the attribute lookup on Child is attempted using the column 
 name, rather than the attribute name. The actual delete action works fine 
 when I switch the strategy to False or fetch, or if there are no Child 
 objects in the session (so no evaluation is invoked).


  As you can see, this is v0.9.7, but I've not seen anything relevant in 
 the changelog since then. 

  Cheers,
 Steve.
  -- 
 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+...@googlegroups.com javascript:.
 To post to this group, send email to sqlal...@googlegroups.com 
 javascript:.
 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] SQLAlchemy's merge doesn't fill foreign keys with ids of new objects in relationship

2015-04-14 Thread Mike Bayer



On 4/14/15 3:55 AM, Юрий Пайков wrote:


My question is when I have in a session a newly created object(doesn't 
have primary key yet, but will obtain it upon flush) and I merge to 
that session another object referring to the first one by relationship 
(/b/ in the example) SQLAlchemy doesn't populate latter object with 
the primary key from the former. Instead it just generate next value 
from the sequence. Why is it the case ?




that's not what I see happening here.   I see very simply that the B.id_ 
column is a SERIAL so is linked to a sequence, however you are inserting 
a row with a hardcoded 1 for a primary key; so the second B object, 
which relies on the sequence, fails due to an identity conflict.


So let's repair the test case first, and that first B.id we'll set to 
10 so that it doesn't conflict.


Now we get the error you probably intended to send:

SELECT Rel.id_a AS Rel_id_a, Rel.id_b AS Rel_id_b, 
Rel.rel_data AS Rel_rel_data

FROM Rel
WHERE Rel.id_a = %(param_1)s AND Rel.id_b = %(param_2)s
2015-04-14 11:05:11,850 INFO sqlalchemy.engine.base.Engine {'param_1': 
800, 'param_2': symbol('NEVER_SET')}


where this is, the merge() is proceeding to attempt to locate the object 
by primary key but the PK is not filled in.  This is the expected 
behavior.   The primary key of an object is never auto-populated until 
it is flushed.   So here, if you are passing in a transient object, you 
need to set the PK yourself:


second_b = B(data='f')
session.add(second_b)
session.flush()
x = session.merge(Rel(id_a=800, rel_data=second, id_b=second_b.id_))





|fromsqlalchemy importcreate_engine fromsqlalchemy.orm 
importsessionmaker engine 
=create_engine(postgresql+psycopg2://psql_admin:psql_admin@localhost/fm)fromsqlalchemy.ext.declarative 
importdeclarative_base Base=declarative_base()fromsqlalchemy 
importInteger,ForeignKey,VARCHAR,TEXT,Boolean,DateTimefromsqlalchemy.orm 
importrelationship fromsqlalchemy.sql.schema 
importColumnclassB(Base):__tablename__='B'id_=Column(Integer,primary_key=True)data 
=Column(VARCHAR(30))classRel(Base):__tablename__='Rel'id_a=Column(Integer,primary_key=True)id_b=Column(Integer,ForeignKey('B.id_'),primary_key=True)b 
=relationship(B)rel_data=Column(VARCHAR(30))Session=sessionmaker(bind=engine)session 
=Session()Base.metadata.create_all(engine,checkfirst=True)first_b=B(id_=1,data='ololo')session.add(first_b)session.commit()session.add(Rel(id_a=800,id_b=1,rel_data='first 
relation 
data'))second_b=B(data='f')session.add(second_b)x=session.merge(Rel(id_a=800,rel_data=second,b=second_b))session.commit()|


Here I have an error

IntegrityError: (raised as a result of Query-invoked autoflush;
consider using a session.no_autoflush block if this flush is
occuring prematurely) (IntegrityError) duplicate key value
violates unique constraint B_pkey DETAIL: Key (id_)=(1) already
exists. 'INSERT INTO B (data) VALUES (%(data)s) RETURNING
B.id_' {'data': 'f'}

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