<p dir="auto"><strong>Migrated issue, originally created by 0xANDREW NA (<a href="https://github.com/0xANDREW">@0xANDREW</a>)</strong></p>
<p dir="auto">(SQLAlchemy 1.0.11)</p>
<p dir="auto">Column definition in model:</p>
<p dir="auto"><code class="notranslate">public_data = Column(MutableDict.as_mutable(JSON), nullable=False)</code></p>
<p dir="auto">Event listener in same model file:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def __listener(target, value, oldvalue, initiator):
    ... do some stuff

event.listen(User.public_data, 'set', __listener)"><pre class="notranslate"><code class="notranslate">def __listener(target, value, oldvalue, initiator):
    ... do some stuff

event.listen(User.public_data, 'set', __listener)
</code></pre></div>
<p dir="auto">Change that should trigger <code class="notranslate">set</code> event:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# this doesn't work
user.public_data['address'] = ''

# but this works
user.public_data = {}"><pre class="notranslate"><code class="notranslate"># this doesn't work
user.public_data['address'] = ''

# but this works
user.public_data = {}
</code></pre></div>
<p dir="auto">The event is never triggered when only a JSON attribute is modified. I stepped through the SQLAlchemy code and found that after the first line above is executed, the model's <code class="notranslate">changed()</code> method is called, which I assume should be responsible for the event firing. Am I doing something wrong or is this not supported?</p>
<p dir="auto"><strong>Migrated issue, originally created by zoomorph (<a href="https://github.com/zoomorph">@zoomorph</a>)</strong></p>
<p dir="auto">Listening for changes to a scalar attribute can be done via the 'set' event:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def validate_phone(target, value, oldvalue, initiator):
    &quot;&quot;&quot;Strip non-numeric characters from a phone number&quot;&quot;&quot;

    return re.sub(r'(?![0-9])', '', value)

# setup listener on UserContact.phone attribute, instructing
# it to use the return value
listen(UserContact.phone, 'set', validate_phone, retval=True)"><pre class="notranslate"><code class="notranslate">def validate_phone(target, value, oldvalue, initiator):
    """Strip non-numeric characters from a phone number"""

    return re.sub(r'(?![0-9])', '', value)

# setup listener on UserContact.phone attribute, instructing
# it to use the return value
listen(UserContact.phone, 'set', validate_phone, retval=True)
</code></pre></div>
<p dir="auto">When Mutable attributes are set, they also emit the 'set' event. However, they do not emit any events when they are merely changed.</p>
<p dir="auto">A new event such as 'changed' should be added to cover this case. It would work like so:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def favorites_changed(target, value, oldvalue, initiator):
    logger.info(&quot;Favorites was set or changed&quot;)
    # do something

# setup listeners on UserContact.favorites attribute (a MutableList for example)
listen(UserContact.favorites, 'set', favorites_changed)
listen(UserContact.favorites, 'changed', favorites_changed)

u = session.query(UserContact).first()
u.favorites = [1, 2, 3]  # emits 'set'
u.favorites.append(4)  # should emit 'changed'
u.favorites[-1] = 5  # should emit 'changed'"><pre class="notranslate"><code class="notranslate">def favorites_changed(target, value, oldvalue, initiator):
    logger.info("Favorites was set or changed")
    # do something

# setup listeners on UserContact.favorites attribute (a MutableList for example)
listen(UserContact.favorites, 'set', favorites_changed)
listen(UserContact.favorites, 'changed', favorites_changed)

u = session.query(UserContact).first()
u.favorites = [1, 2, 3]  # emits 'set'
u.favorites.append(4)  # should emit 'changed'
u.favorites[-1] = 5  # should emit 'changed'
</code></pre></div>
<p dir="auto">Related to issue <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="384632812" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/issues/3297" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/3297/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/issues/3297">#3297</a></p>