Source code for napari.utils.events.containers._evented_dict

"""MutableMapping that emits events when altered."""
from typing import Mapping, Sequence, Type, Union

from ..event import EmitterGroup, Event
from ..types import SupportsEvents
from ._dict import _K, _T, TypedMutableMapping


[docs]class EventedDict(TypedMutableMapping[_K, _T]): """Mutable dictionary that emits events when altered. This class is designed to behave exactly like builting ``dict``, but will emit events before and after all mutations (addition, removal, and changing). Parameters ---------- data : Mapping, optional Dictionary to initialize the class with. basetype : type of sequence of types, optional Type of the element in the dictionary. Events ------ changed (key: K, old_value: T, value: T) emitted when ``key`` is set from ``old_value`` to ``value`` adding (key: K) emitted before an item is added to the dictionary with ``key`` added (key: K, value: T) emitted after ``value`` was added to the dictionary with ``key`` removing (key: K) emitted before ``key`` is removed from the dictionary removed (key: K, value: T) emitted after ``key`` was removed from the dictionary updated (key, K, value: T) emitted after ``value`` of ``key`` was changed. Only implemented by subclasses to give them an option to trigger some update after ``value`` was changed and this class did not register it. This can be useful if the ``basetype`` is not an evented object. """ events: EmitterGroup def __init__( self, data: Mapping[_K, _T] = None, basetype: Union[Type[_T], Sequence[Type[_T]]] = (), ): _events = { "changing": None, "changed": None, "adding": None, "added": None, "removing": None, "removed": None, "updated": None, } # For inheritance: If the mro already provides an EmitterGroup, add... if hasattr(self, "events") and isinstance(self.events, EmitterGroup): self.events.add(**_events) else: # otherwise create a new one self.events = EmitterGroup(source=self, **_events) super().__init__(data, basetype) def __setitem__(self, key: _K, value: _T): old = self._dict.get(key, None) if value is old or value == old: return if old is None: self.events.adding(key=key) super().__setitem__(key, value) self.events.added(key=key, value=value) self._connect_child_emitters(value) else: super().__setitem__(key, value) self.events.changed(key=key, old_value=old, value=value) def __delitem__(self, key: _K): self.events.removing(key=key) self._disconnect_child_emitters(self[key]) item = self._dict.pop(key) self.events.removed(key=key, value=item) def _reemit_child_event(self, event: Event): """An item in the dict emitted an event. Re-emit with key""" if not hasattr(event, "key"): setattr(event, "key", self.key(event.source)) # re-emit with this object's EventEmitter of the same type if present # otherwise just emit with the EmitterGroup itself getattr(self.events, event.type, self.events)(event) def _disconnect_child_emitters(self, child: _T): """Disconnect all events from the child from the re-emitter.""" if isinstance(child, SupportsEvents): child.events.disconnect(self._reemit_child_event) def _connect_child_emitters(self, child: _T): """Connect all events from the child to be re-emitted.""" if isinstance(child, SupportsEvents): # make sure the event source has been set on the child if child.events.source is None: child.events.source = child child.events.connect(self._reemit_child_event)
[docs] def key(self, value: _T): """Return first instance of value.""" for k, v in self._dict.items(): if v is value or v == value: return k