Issue 1:
Migrated issue, originally created by Adrian (@thiefmaster)

I have an association_proxy on a set-like relationship. Normal set operations work perfectly when it comes to not adding duplicates, but when I replace the whole set e.g. using foo.bar = set(foo.bar) (in my real application the right side comes from a form) I get errors due to duplicate rows being inserted.

from sqlalchemy import *
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *

Base = declarative_base()

class A(Base):
__tablename__ = 'test_a'

id = Column(Integer, primary_key=True)
b_rel = relationship('B', collection_class=set, cascade='all, delete-orphan')
b = association_proxy('b_rel', 'value', creator=lambda x: B(value=x))

class B(Base):
__tablename__ = 'test_b'
__table_args__ = UniqueConstraint('a_id', 'value'),

id = Column(Integer, primary_key=True)
a_id = Column(Integer, ForeignKey('test_a.id'), nullable=False)
value = Column(String)

e = create_engine('sqlite:///:memory:', echo=True)
# e = create_engine('postgresql:///test', echo=True)
Base.metadata.create_all(e)
# e.execute('TRUNCATE test_a, test_b;')
s = Session(e)

a = A()
a.b = {'x', 'y', 'z'}
s.add(a)
s.commit()

print
print 'adding existing element to set'
a.b.add('x')
s.flush()

print
print 'assigning same items to set'
a.b = set(a.b)
s.flush()

Issue 2:
test case, from #3583:

from sqlalchemy import *
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *

Base = declarative_base()

class A(Base):
__tablename__ = "test_a"

id = Column(Integer, primary_key=True)
b_rel = relationship(
"B", collection_class=set, cascade="all, delete-orphan"
)
b = association_proxy("b_rel", "value", creator=lambda x: B(value=x))

class B(Base):
__tablename__ = "test_b"
__table_args__ = (UniqueConstraint("a_id", "value"),)

id = Column(Integer, primary_key=True)
a_id = Column(Integer, ForeignKey("test_a.id"), nullable=False)
value = Column(String)

e = create_engine("sqlite:///:memory:", echo=True)
# e = create_engine('postgresql:///test', echo=True)
Base.metadata.create_all(e)
# e.execute('TRUNCATE test_a, test_b;')
s = Session(e)

a = A()
a.b = {"x", "y", "z"}
s.add(a)
s.commit()

print("\nadding existing element to set")
a.b.add("x")
s.flush()

print("\nassigning same items to set")
a.b = set(a.b)
s.flush()

since this issue was added the association proxy has been highly modified

Migrated issue, originally created by Michael Bayer (@zzzeek)

the "clear" here is clumsy and originally thought was the ultimate cause of the original issue in #2637 it's not. Might be nicer if we had a bulk replace built in for those collections, would emit fewer events.

def __set__(self, obj, values):
if self.owning_class is None:
self.owning_class = type(obj)

if self.scalar:
creator = self.creator and self.creator or self.target_class
target = getattr(obj, self.target_collection)
if target is None:
setattr(obj, self.target_collection, creator(values))
else:
self._scalar_set(target, values)
else:
proxy = self.__get__(obj, None)
if proxy is not values:
proxy.clear()
self._set(proxy, values)