attrs icon indicating copy to clipboard operation
attrs copied to clipboard

Attrs support for publish/subscribe

Open bdanofsky opened this issue 2 years ago • 5 comments

Attrs has nice support for validation and conversion. That same concept could/should be extended to post setattr() phase of attribute updates. This would allow for publish / subscribe systems on attribute updates.

In theory this should be a trivial feature enhancement to allow an additional hook.

bdanofsky avatar Feb 10 '23 15:02 bdanofsky

Are you sure it's necessary to run after and can't be just part of on_setattr?

hynek avatar Apr 05 '23 08:04 hynek

I think the answer is no but let me spell out my use case:

  1. User sets a new attribute value (call this attrs attribute abc)
  2. Publish is called when setattr is called on abc a. stack is inspected to determine if abc depends on other attrs attributes b. If abc depends on other attrs attributes then their relationship is stored as part of my publish/subscribe system. example: abc = 8 * xyz # where xyz is another attrs attribute. c. Lookup if another attrs attribute depends on abc if so then that attribute is updated. example: qrs = abc / 128 # when abc updates then qrs updates with new value.
    This is done doing exec('qrs = abc/128') Note in step C there is an implied getattr() on abc which requires the new value

if Publish is called before abc actually has a new value then all the dependencies (in my example qrs) will be assigned based on the old value.

I was able to work around this issue by using a custom setattr . FYI that workflow in attrs is a bit buggy or was when I implemented the code. That said if you feel this feature is not something you wish to support I understand.
It seems like an obvious feature though.

bdanofsky avatar Apr 05 '23 15:04 bdanofsky

I think the issue here is that on_setattr doesn't provide any mechanism to do something after a new value is set, only to intercept a value before it is set. There are many things one might want to do immediately after setting a value including:

  • fire a changed signal (publish/subscribe signal),
  • update dependent variables,
  • invalidate caches or set _dirty=True.

Instead of specific pub/sub support, would it make sense to add a post_setattr hook? So the semantics would be


def set_dirty(obj, attribute, value):
    obj._dirty=True

@define
class A:
    x : int = field(default=0, post_setattr=set_dirty)
    _dirty: bool = False

# Mostly equivalent to
x_attr = ...
psa_attrs = {'x':  (x_attr, set_dirty)}
class A:
    def __init__(x: int=0, dirty=False):
        self.x = x
        self._dirty=dirty

    def __setattr__(self, name, val):
        object.__setattr__(self, name, val)
        try:
            a, hook = psa_attrs[name]
        except KeyError:
            pass
        else:
            hook(self, a, val)

jamesmurphy-mc avatar Jun 06 '23 18:06 jamesmurphy-mc

I'd be very happy with a post_setattr feature.

bdanofsky avatar Jun 06 '23 20:06 bdanofsky