File: _observer_change_notifier.py

package info (click to toggle)
python-traits 6.4.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,648 kB
  • sloc: python: 34,801; ansic: 4,266; makefile: 102
file content (183 lines) | stat: -rw-r--r-- 7,106 bytes parent folder | download
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
        )