1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import types
import weakref
from traits.observation.exceptions import NotifierNotFound
class ObserverChangeNotifier:
""" Wrapper for maintaining notifiers in an ObserverGraph
when an upstream object changes.
An instance of ``ObserverChangeNotifier`` is a callable to be contributed
to an instance of ``IObserverable``, e.g. ``CTrait``, ``TraitList`` etc.,
such that it will be called when an observerable emits notificaitons for
changes.
For example, suppose changes are observed on an extended attribute path,
e.g. ``foo.bar.baz``, where ``foo`` and ``bar`` are both instances of
``HasTraits``. There will be an instance of ``ObserverChangeNotifier`` on
the ``CTrait`` for ``foo``. When ``foo`` changes, the notifier will be
called for maintaining the observers for ``bar.baz``. Similarly, there
will be an instance of ``ObserverChangeNotifier`` on the ``CTrait`` for
``bar`` for maintaining the observers for ``baz``, when ``bar`` changes.
"""
def __init__(
self, *, observer_handler, event_factory, prevent_event,
graph, handler, target, dispatcher):
"""
Parameters
----------
observer_handler : callable(event, graph, handler, target, dispatcher)
The handler for maintaining observers and notifiers.
``event`` is the object created by the ``event_factory`` and is
associated with the change event that triggers this notifier.
The rest of the arguments are provided by the arguments to this
notifier.
event_factory : callable(*args, **kwargs) -> object
A factory function for creating the event object to be sent to
the observer_handler. The call signature must be compatible with
the call signature expected by the observable this notifier is
used with. e.g. for CTrait, the call signature will be
``(object, name, old, new)``.
prevent_event : callable(event) -> boolean
A callable for controlling whether the observer_handler should be
invoked. It receives the event created by the event factory and
returns true if the event should be prevented, false if the event
should be fired.
graph : ObserverGraph
An object describing what traits are being observed on an instance
of ``HasTraits``, e.g. observe mutations on a list referenced by
a specific named trait.
handler : callable(event)
The user handler being maintained when a container object changes.
A weak reference is created for the handler if it is an instance
method. If the instance method handler is garbage collected, this
notifier will be silenced.
target : object
An object for defining the context of the user's handler notifier.
A weak reference is created for the target. If the target is
garbage collected, this notifier will be silenced.
This is typically an instance of HasTraits acting as a container
for an observable on which this notifier is attached to. It would
be seen by the user as the "owner" of the observer.
dispatcher : callable(function, event)
Callable for dispatching the user's handler.
"""
self.observer_handler = observer_handler
self.event_factory = event_factory
self.prevent_event = prevent_event
self.graph = graph
self.target = weakref.ref(target)
if isinstance(handler, types.MethodType):
self.handler = weakref.WeakMethod(handler)
else:
def _return_handler():
return handler
self.handler = _return_handler
self.dispatcher = dispatcher
def add_to(self, observable):
""" Add this notifier to the observable.
Parameters
----------
observable : IObservable
"""
notifiers = observable._notifiers(True)
notifiers.append(self)
def remove_from(self, observable):
""" Remove a notifier equivalent to this one from the observable.
Parameters
----------
observable : IObservable
Raises
------
NotifierNotFound
If the notifier cannot be found.
"""
notifiers = observable._notifiers(True)
for notifier in notifiers[:]:
if self.equals(notifier):
notifiers.remove(notifier)
break
else:
raise NotifierNotFound("Notifier not found.")
def __call__(self, *args, **kwargs):
""" Called by the observable this notifier is attached to.
This exercises the observer_handler for maintaining observers and
notifiers on changed objects.
Note that any unexpected exceptions will be raised, as the
``observer_handler`` is not provided by users of traits, but is
a callable maintained in traits.
"""
target = self.target()
if target is None:
return
handler = self.handler()
if handler is None:
return
event = self.event_factory(*args, **kwargs)
if self.prevent_event(event):
return
self.observer_handler(
event=event,
graph=self.graph,
target=target,
handler=handler,
dispatcher=self.dispatcher,
)
def equals(self, other):
""" Return true if other is a notifier equivalent to this one.
Parameters
----------
other : any
Returns
-------
boolean
"""
return (
type(self) is type(other)
# observer_handler contains the logic for maintaining notifiers
# in the downstream graph.
and self.observer_handler is other.observer_handler
# graph is an input for observer_handler.
# Unequal graphs should not interfere each other.
and self.graph == other.graph
# user handler is an input for observer_handler.
# different user handlers should not interfere each other.
and self.handler() == other.handler()
# target is an input for observer_handler.
# it goes together with the user's handler
and self.target() is other.target()
# dispatcher is an input for observer_handler.
# different dispatchers should not interfere each other.
and self.dispatcher == other.dispatcher
)
|