On 02/23/2016 06:11 PM, Piotr Dobrogost wrote:
On Tuesday, February 23, 2016 at 5:44:45 PM UTC+1, Mike Bayer wrote:



That the old TextValue objects need to be deleted I understand. What I
don't see
is what makes SA specifically blank-out primary key column
'text_value.text_id'?

the "blanking out" is what occurs if you do *not* delete the old TextValue. It means to be de-associated with the parent Text object which necessarily means setting TextValue.text_id to None, which in this mapping happens to also be part of the primary key. If the TextValue is instead to be deleted, then this step does not occur.


Asking differently; I would understand if I got some integrity errors
while SA
would be trying to insert new rows with the same keys without first deleting
old rows but I have no idea how SA "came up with the idea" to blank-out
primary key?

    In this specific example, marking the TextValue as deleted ends up
    gving
    you an UPDATE of TextValue, because the ORM converts an
    INSERT/DELETE of
    the same primary key into a single UPDATE.  But if your dictionary


Is this an implementation detail or official semantics?

this is how the ORM deals with the case of a DELETE of a row, then an INSERT of a row with that same identity. The unit of work currently has the limitation that within a single flush, all INSERT statements happen before all DELETE statements, so in this case it takes the presence of both commands and turns them into an UPDATE. however, it's still considered to be a delete + insert - if you're using the versioning extensions, the operation will still show up as these two at the session level.


 It's very
important for me
to keep identity of existing TextValue objects and only update values in
their 'value' column.

so this is what your example currently does, minus association proxy and the dict mapping:

# values = {'pl': u'org', 'en': u'org'}
session.add(
    Text(_values=[
          TextValue(lang_id='pl', value='org'),
          TextValue(lang_id='en', value='org')])

text = session.query(Text).one()

# text.values = {'pl': u'modified', 'en': u'modified'}
text._values.clear()
text._values.append(TextValue(lang_id='pl', value='modified'))
text._values.append(TextValue(lang_id='en', value='modified'))


now if it instead was written like this, we wouldn't get redundant TextValue objects:

values = {'pl': u'org', 'en': u'org'}
session.add(Text(values=values))
text = session.query(Text).one()
text.values['pl'] = 'modified'
text.values['en'] = 'modified'


it would even keep the same TextValue if you did it like this:

text.values.update({'pl': u'modified', 'en': u'modified'})

so currently, the assignment you're doing treats this as "remove the old, add the new". This is called a "bulk assignment" inside the collection mechanics. The associationproxy has a few hooks to modify this but none of them currently provide a direct link to not first do the "clear" of the current value, if the incoming value is not the same object. However, we can just override __set__ and make assignment do anything we want:

from sqlalchemy.ext.associationproxy import AssociationProxy


class DictUpdateProxy(AssociationProxy):
    def __set__(self, obj, values):
        proxy = self.__get__(obj, None)

        keys_to_remove = set(proxy).difference(values)
        for key in keys_to_remove:
            del proxy[key]
        proxy.update(values)


class Text(Base):
    __tablename__ = 'text'
    id = Column(Integer, primary_key=True)
_values = relationship('TextValue', collection_class=attribute_mapped_collection('lang_id'))

    values = DictUpdateProxy(
        '_values', 'value',
        creator=lambda k, v: TextValue(lang_id=k, value=v))


in that way you can control exactly what __set__() does and it will never remove a TextValue object where the same identity is coming in on assignment.









(The reason it's important is that I'm using sqlalchemy-continuum
extension for automatic
versioning). Is there some preferred way to make sure identity is intact
and modifications
are done through UPDATE and not DELETE/INSERT?
Also, you say that in this specific example there is no DELETE needed
yet there's an error.
Does it mean SA tries to make sure DELETEs would be possible if they
were needed even if
in this specific example they are not needed?


Regards,
Piotr

--
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 [email protected]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
Visit this group at https://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 [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to