stefanofontanelli / ColanderAlchemy

ColanderAlchemy helps you autogenerating Colander schemas based on SQLAlchemy mapped objects.
Other
65 stars 32 forks source link

Can not update the object that was received with objectify method. #92

Open uralbash opened 8 years ago

uralbash commented 8 years ago

I can not save the object that was received with objectify method. For example I save user with relation to address.


from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = 'address'

    id = Column(Integer, primary_key=True)
    email_address = Column(String(50), nullable=False)
    user_id = Column(Integer, ForeignKey('user.id'), nullable=False)

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources
engine = create_engine('sqlite:///:memory:')
User.__table__.create(engine)
Address.__table__.create(engine)

# create a configured "Session" class
Session = sessionmaker(bind=engine)

# create a Session
session = Session()
u1 = User(name='ed', addresses=[Address(email_address='ed@ed.com')])
session.add(u1)
session.commit()

Now I get dict from user oject:


from colanderalchemy import SQLAlchemySchemaNode

schema = SQLAlchemySchemaNode(User)
u1_serialize = schema.dictify(u1)

Create object from that dict and add it to dbsession:

ui_deserialize = schema.objectify(u1_serialize, u1)
session.add(ui_deserialize)

But it raises error when I commit session.

session.commit()

""" *** sqlalchemy.orm.exc.FlushError: New instance <Address at 0x7fac02cad1d0>
with identity key (<class '__main__.Address'>, (1,))
conflicts with persistent instance <Address at 0x7fac02cadf60>
"""

It turns out that I can only create objects in such a way, not update. This is a very limited use of this function.

tisdall commented 8 years ago

I'm no longer working on anything related to this any more, unfortunately.

A quick look at the code looks like it's supposed to recursively update the attributes but the recursive calls to set the context like on the initial call. There's also a variable called cls that's not being used that I suspect is what's supposed to be passed in as the context.

so perhaps it should be:

                    cls = prop.mapper.class_
                    if prop.uselist:
                        # Sequence of objects
                        value = [self[attr].children[0].objectify(obj, cls)  # added context here
                                 for obj in dict_[attr]]
                    else:
                        # Single object
                        value = self[attr].objectify(dict_[attr], cls)  # added context here

Let me know if that fixes it. And if it does, it'd be great if you could produce a PR with some tests.

tisdall commented 8 years ago

@uralbash - Did that solve your issue?

uralbash commented 8 years ago

@tisdall Yes thank you. I'll try to write tests when the time appears.

tisdall commented 8 years ago

@uralbash Are you able to put together a PR with tests for this?