MutableSet isn't change aware on bitwise operators

Issue #3853 resolved
Daniel Kolsoi created an issue

Python sets support common set operations as bitwise operators such as &=, |=, ^=, -=. The MutableSet class seems to fully support these operations as full methods (.intersection, .union, ...) but not bitwise operators.

This is unexpected since the bitwise operators do in fact modify the underlying set(due to inheriting from set), but since the .changed() method is not called by MutableSet, sqlalchemy is never notified that the data has been modified. This leads to the possibility of having a mutable set column which is modified locally but does not get updated on commit or flush.

For example,

from sqlalchemy.ext.mutable import MutableSet
from sqlalchemy.types import TypeDecorator, UnicodeText

class _JSONEncodedSet(TypeDecorator):
    impl = UnicodeText

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = json.dumps(list(value))
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value = set(json.loads(value))
        return value

JSONEncodedSet = MutableSet.as_mutable(_JSONEncodedSet)

from sqlalchemy.ext.declarative import declarative_base

BaseModel = declarative_base()

class MyModel(BaseModel):
    json_set = Column(JSONEncodedSet)

from sqlalchemy.orm import scoped_session, sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension

session = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))

session.add(MyModel(json_set={1, 2, 3}))
session.flush()

model = session.query(MyModel).first()
model.json_set &= {1, 3}

session.flush()

del model

model = session.query(MyModel).first()

assert model.json_set == {1, 3}  # AssertionError

Similarly, MutableList doesn't seem to support += [1]

Tested in SqlAlchemy 1.1.3, Sqlite but doesn't seem to be DB specific.

Comments (7)

  1. Mike Bayer repo owner

    Implement in-place mutation operators for MutableSet, MutableList

    Implemented in-place mutation operators __ior__, __iand__, __ixor__ and __isub__ for :class:.mutable.MutableSet and __iadd__ for :class:.mutable.MutableList so that change events are fired off when these mutator methods are used to alter the collection.

    Change-Id: Ib357a96d3b06c5deb6b53eb304a8b9f1dc9e9ede Fixes: #3853

    → <<cset e8ad3988621a>>

  2. Log in to comment