<p dir="auto"><strong>Migrated issue, originally created by Adrian (<a href="https://github.com/thiefmaster">@thiefmaster</a>)</strong></p>
<p dir="auto">I have an <code class="notranslate">association_proxy</code> 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 <code class="notranslate">foo.bar = set(foo.bar)</code> (in my real application the right side comes from a form) I get errors due to duplicate rows being inserted.</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="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()"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">sqlalchemy</span> <span class="pl-k">import</span> <span class="pl-c1">*</span>
<span class="pl-k">from</span> <span class="pl-s1">sqlalchemy</span>.<span class="pl-s1">ext</span>.<span class="pl-s1">associationproxy</span> <span class="pl-k">import</span> <span class="pl-s1">association_proxy</span>
<span class="pl-k">from</span> <span class="pl-s1">sqlalchemy</span>.<span class="pl-s1">ext</span>.<span class="pl-s1">declarative</span> <span class="pl-k">import</span> <span class="pl-s1">declarative_base</span>
<span class="pl-k">from</span> <span class="pl-s1">sqlalchemy</span>.<span class="pl-s1">orm</span> <span class="pl-k">import</span> <span class="pl-c1">*</span>


<span class="pl-v">Base</span> <span class="pl-c1">=</span> <span class="pl-en">declarative_base</span>()


<span class="pl-k">class</span> <span class="pl-v">A</span>(<span class="pl-v">Base</span>):
    <span class="pl-s1">__tablename__</span> <span class="pl-c1">=</span> <span class="pl-s">'test_a'</span>

    <span class="pl-s1">id</span> <span class="pl-c1">=</span> <span class="pl-v">Column</span>(<span class="pl-v">Integer</span>, <span class="pl-s1">primary_key</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)
    <span class="pl-s1">b_rel</span> <span class="pl-c1">=</span> <span class="pl-en">relationship</span>(<span class="pl-s">'B'</span>, <span class="pl-s1">collection_class</span><span class="pl-c1">=</span><span class="pl-s1">set</span>, <span class="pl-s1">cascade</span><span class="pl-c1">=</span><span class="pl-s">'all, delete-orphan'</span>)
    <span class="pl-s1">b</span> <span class="pl-c1">=</span> <span class="pl-en">association_proxy</span>(<span class="pl-s">'b_rel'</span>, <span class="pl-s">'value'</span>, <span class="pl-s1">creator</span><span class="pl-c1">=</span><span class="pl-k">lambda</span> <span class="pl-s1">x</span>: <span class="pl-v">B</span>(<span class="pl-s1">value</span><span class="pl-c1">=</span><span class="pl-s1">x</span>))


<span class="pl-k">class</span> <span class="pl-v">B</span>(<span class="pl-v">Base</span>):
    <span class="pl-s1">__tablename__</span> <span class="pl-c1">=</span> <span class="pl-s">'test_b'</span>
    <span class="pl-s1">__table_args__</span> <span class="pl-c1">=</span> <span class="pl-v">UniqueConstraint</span>(<span class="pl-s">'a_id'</span>, <span class="pl-s">'value'</span>),

    <span class="pl-s1">id</span> <span class="pl-c1">=</span> <span class="pl-v">Column</span>(<span class="pl-v">Integer</span>, <span class="pl-s1">primary_key</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)
    <span class="pl-s1">a_id</span> <span class="pl-c1">=</span> <span class="pl-v">Column</span>(<span class="pl-v">Integer</span>, <span class="pl-v">ForeignKey</span>(<span class="pl-s">'test_a.id'</span>), <span class="pl-s1">nullable</span><span class="pl-c1">=</span><span class="pl-c1">False</span>)
    <span class="pl-s1">value</span> <span class="pl-c1">=</span> <span class="pl-v">Column</span>(<span class="pl-v">String</span>)


<span class="pl-s1">e</span> <span class="pl-c1">=</span> <span class="pl-en">create_engine</span>(<span class="pl-s">'sqlite:///:memory:'</span>, <span class="pl-s1">echo</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)
<span class="pl-c"># e = create_engine('postgresql:///test', echo=True)</span>
<span class="pl-v">Base</span>.<span class="pl-s1">metadata</span>.<span class="pl-en">create_all</span>(<span class="pl-s1">e</span>)
<span class="pl-c"># e.execute('TRUNCATE test_a, test_b;')</span>
<span class="pl-s1">s</span> <span class="pl-c1">=</span> <span class="pl-v">Session</span>(<span class="pl-s1">e</span>)

<span class="pl-s1">a</span> <span class="pl-c1">=</span> <span class="pl-v">A</span>()
<span class="pl-s1">a</span>.<span class="pl-s1">b</span> <span class="pl-c1">=</span> {<span class="pl-s">'x'</span>, <span class="pl-s">'y'</span>, <span class="pl-s">'z'</span>}
<span class="pl-s1">s</span>.<span class="pl-en">add</span>(<span class="pl-s1">a</span>)
<span class="pl-s1">s</span>.<span class="pl-en">commit</span>()

<span class="pl-s1">print</span>
<span class="pl-k">print</span> <span class="pl-s">'adding existing element to set'</span>
<span class="pl-s1">a</span>.<span class="pl-s1">b</span>.<span class="pl-en">add</span>(<span class="pl-s">'x'</span>)
<span class="pl-s1">s</span>.<span class="pl-en">flush</span>()

<span class="pl-s1">print</span>
<span class="pl-k">print</span> <span class="pl-s">'assigning same items to set'</span>
<span class="pl-s1">a</span>.<span class="pl-s1">b</span> <span class="pl-c1">=</span> <span class="pl-en">set</span>(<span class="pl-s1">a</span>.<span class="pl-s1">b</span>)
<span class="pl-s1">s</span>.<span class="pl-en">flush</span>()</pre></div>
<p dir="auto">test case, from <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="384635415" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/issues/3583" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/3583/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/issues/3583">#3583</a>:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="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__ = &quot;test_a&quot;

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


class B(Base):
    __tablename__ = &quot;test_b&quot;
    __table_args__ = (UniqueConstraint(&quot;a_id&quot;, &quot;value&quot;),)

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


e = create_engine(&quot;sqlite:///:memory:&quot;, 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 = {&quot;x&quot;, &quot;y&quot;, &quot;z&quot;}
s.add(a)
s.commit()

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

print(&quot;\nassigning same items to set&quot;)
a.b = set(a.b)
s.flush()
"><pre class="notranslate"><code class="notranslate">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()

</code></pre></div>
<p dir="auto">since this issue was added the association proxy has been highly modified</p>
<p dir="auto"><strong>Migrated issue, originally created by Michael Bayer (<a href="https://github.com/zzzeek">@zzzeek</a>)</strong></p>
<p dir="auto">the "clear" here is clumsy and <a href="we">originally thought was</a> the ultimate cause of the original issue in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="384626979" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/issues/2637" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/2637/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/issues/2637">#2637</a> <a href="but">it's not</a>.   Might be nicer if we had a bulk replace built in for those collections, would emit fewer events.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="    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)"><pre class="notranslate"><code class="notranslate">    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)
</code></pre></div>