So session.merge() does not automatically do .add()?

3,087 views
Skip to first unread message

Bao Niu

unread,
Feb 19, 2014, 6:08:39 AM2/19/14
to sqlal...@googlegroups.com
I thought I understood the behaviour of session.merge() from reading the documentation, but I'm wrong. Here is an example. I have codes below,

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")
This class has a corresponding table 'user' which has already been populated with one row, with its primary key equal to 1.
Now I run the following instructions from python -i:
>>> u = User(id=1)
>>> session.merge(u)
>>> session.commit()
I expected that there should be a sqlalchemy.orm.exc.FlushError, because both object u (which has been merged) and the row which has been already in the table have their primary key equal to 1. However, nothing happened. Sqlalchemy just ignored that merged u!
If I do these instructions:
>>> session.add(u)
>>> session.commit()
Then I got a sqlalchemy.orm.exc.FlushError, which was expected.

So, session.merge() does not include .add()? From the documentation it seems that it does.

Michael Bayer

unread,
Feb 19, 2014, 9:47:18 AM2/19/14
to sqlal...@googlegroups.com
So first is that the object you pass to merge() is *never* itself added to the session.  The object you pass in is unmodified.   The return value of merge() is the object that is actually in the Session.  This object will either have been newly created, in which case it was added using add(), or it was loaded from the database.  But that object is never the one you passed in.

So in your test if primary key “1” already exists, then merge() returned the User object it had for that primary key.
signature.asc

Bao Niu

unread,
Feb 19, 2014, 10:01:49 AM2/19/14
to sqlal...@googlegroups.com

Thanks for replying. Isn't it the case when I just provide a primary key for the source object, the attribute shoul be marked expired on the target instance? In my example I only give the object u a primary key value, so the newly created object should be an object that only has a primary key value and "wipe out" other attributes originally for that row in the table. However the result is that row stays unchanged after all my instructions. Is there something I missed here?

Michael Bayer

unread,
Feb 19, 2014, 10:21:13 AM2/19/14
to sqlal...@googlegroups.com
On Feb 19, 2014, at 10:01 AM, Bao Niu <niub...@gmail.com> wrote:

Thanks for replying. Isn't it the case when I just provide a primary key for the source object, the attribute shoul be marked expired on the target instance?


no because the target instance a product of either a. essentially loaded by session.query(User).get(1), so not expired or b. primary key identity 1 doesn’t exist in the database yet in which case the new object is only pending.


In my example I only give the object u a primary key value, so the newly created object should be an object that only has a primary key value and "wipe out" other attributes originally for that row in the table.

attributes that are not specified in the source object are not copied.  this allows one to set a subset of attributes on the target object.




On Feb 19, 2014 6:47 AM, "Michael Bayer" <mik...@zzzcomputing.com> wrote:

On Feb 19, 2014, at 6:08 AM, Bao Niu <niub...@gmail.com> wrote:

I thought I understood the behaviour of session.merge() from reading the documentation, but I'm wrong. Here is an example. I have codes below,

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")
This class has a corresponding table 'user' which has already been populated with one row, with its primary key equal to 1.
Now I run the following instructions from python -i:
>>> u = User(id=1)
>>> session.merge(u)
>>> session.commit()
I expected that there should be a sqlalchemy.orm.exc.FlushError, because both object u (which has been merged) and the row which has been already in the table have their primary key equal to 1. However, nothing happened. Sqlalchemy just ignored that merged u!
If I do these instructions:
>>> session.add(u)
>>> session.commit()
Then I got a sqlalchemy.orm.exc.FlushError, which was expected.

So, session.merge() does not include .add()? From the documentation it seems that it does.

So first is that the object you pass to merge() is *never* itself added to the session.  The object you pass in is unmodified.   The return value of merge() is the object that is actually in the Session.  This object will either have been newly created, in which case it was added using add(), or it was loaded from the database.  But that object is never the one you passed in.

So in your test if primary key “1” already exists, then merge() returned the User object it had for that primary key.

--
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.
To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.

signature.asc

Bao Niu

unread,
Feb 19, 2014, 11:57:06 PM2/19/14
to sqlal...@googlegroups.com
Please allow me to clarify here. So if attributes on source object u are different from attributes loaded from database, database will always prevail? I thought the source object u should represent newer version of the data, shouldn't it?

Michael Bayer

unread,
Feb 20, 2014, 12:08:58 AM2/20/14
to sqlal...@googlegroups.com

On Feb 19, 2014, at 11:57 PM, Bao Niu <niub...@gmail.com> wrote:

> Please allow me to clarify here. So if attributes on source object u are different from attributes loaded from database, database will always prevail? I thought the source object u should represent newer version of the data, shouldn't it?


All attributes that are present on your source object are copied to the database object. All attributes that are not present, are not. There is a difference between an attribute that is “None” and an attribute that is missing. In python, this corresponds to whether or not you’d see the attribute actually present in the object’s __dict__.

If I make an object like this:

obj = User(id=1)

the only attribute that object has is, “id”. All other attributes are non-present. If my user table has a column like “name”, it is only “present” if I say this:

obj = User(id=1, name=‘some name’)

or if I want it to be “None”:

obj = User(id=1, name=None)

The merge process doesn't do anything with attributes that are missing. You can quickly see which attributes are present and which are not by just looking at the __dict__ of your object:

print obj.__dict__

If you’d like to make an object that has all attributes populated with None, assuming you’re using declarative constructors you can do something like this:

from sqlalchemy import inspect
obj = User(id=1, **dict((attr.key, None) for attr in inspect(User).attrs if not attr.uselist and attr.key != ‘id’))

Though that doesn’t account for collections, which to initialize as blank typically accept a collection of a certain type. That line is kind of awkward, and what should be used instead sort of depends on what you’re trying to do.





signature.asc
Reply all
Reply to author
Forward
0 new messages