File: _trait_added_observer.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 (253 lines) | stat: -rw-r--r-- 8,303 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# (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!

from traits.observation._has_traits_helpers import (
    iter_objects,
    object_has_named_trait,
)
from traits.observation._i_observer import IObserver
from traits.observation._observe import add_or_remove_notifiers
from traits.observation._observer_change_notifier import ObserverChangeNotifier
from traits.observation._observer_graph import ObserverGraph
from traits.observation._trait_change_event import trait_event_factory


@IObserver.register
class TraitAddedObserver:
    """ An observer for observing the trait_added event.

    This observer only offers the "maintainer". When this observer is used in
    an ObserverGraph, its subgraphs are the graphs to be hooked up when a new
    trait is added, provided that the trait satisfies a given criterion.
    The criterion should align with the root observer(s) in these subgraph(s).

    Parameters
    ----------
    match_func : callable(str, CTrait) -> bool
        A callable that receives the name of the trait added and the
        corresponding trait. The returned boolean indicates whether
        notifiers should be added/removed for the added trait.
        This callable is used for equality check and must be hashable.
    optional : boolean
        Whether to skip this observer if the trait_added trait cannot be
        found on the incoming object.
    """

    def __init__(self, match_func, optional):
        self.match_func = match_func
        self.optional = optional

    def __hash__(self):
        """ Return a hash of this object."""
        return hash((type(self).__name__, self.match_func, self.optional))

    def __eq__(self, other):
        """ Return true if this observer is equal to the given one."""
        return (
            type(self) is type(other)
            and self.match_func == other.match_func
            and self.optional == other.optional
        )

    @property
    def notify(self):
        """ A boolean for whether this observer will notify for changes.
        """
        return False

    def iter_observables(self, object):
        """ Yield observables for notifiers to be attached to or detached from.

        Parameters
        ----------
        object: object
            Object provided by the ``iter_objects`` methods from another
            observers or directly by the user.

        Yields
        ------
        IObservable

        Raises
        ------
        ValueError
            If the given object cannot be handled by this observer.
        """
        if not object_has_named_trait(object, "trait_added"):
            if self.optional:
                return
            raise ValueError(
                "Unable to observe 'trait_added' event on {!r}".format(object))
        yield object._trait("trait_added", 2)

    def iter_objects(self, object):
        """ Yield objects for the next observer following this observer, in an
        ObserverGraph.

        Parameters
        ----------
        object: object
            Object provided by the ``iter_objects`` methods from another
            observers or directly by the user.

        Yields
        ------
        value : object
        """
        # children graphs are used in the maintainer only.
        yield from ()

    # get_notifier is not implemented as notify is always false.

    def get_maintainer(self, graph, handler, target, dispatcher):
        """ Return a notifier for maintaining downstream observers when
        trait_added event happens.

        Parameters
        ----------
        graph : ObserverGraph
            Description for the *downstream* observers, i.e. excluding self.
        handler : callable
            User handler.
        target : object
            Object seen by the user as the owner of the observer.
        dispatcher : callable
            Callable for dispatching the handler.

        Returns
        -------
        notifier : ObserverChangeNotifier
        """
        return ObserverChangeNotifier(
            observer_handler=self.observer_change_handler,
            event_factory=trait_event_factory,
            prevent_event=self.prevent_event,
            graph=graph,
            handler=handler,
            target=target,
            dispatcher=dispatcher,
        )

    def prevent_event(self, event):
        """ Return true if the added trait should not be handled.

        Parameters
        ----------
        event : TraitChangeEvent
            Event triggered by add_trait.
        """
        object = event.object
        name = event.new
        trait = object.trait(name=name)
        return not self.match_func(name, trait)

    @staticmethod
    def observer_change_handler(event, graph, handler, target, dispatcher):
        """ Handler for maintaining observers.

        Parameters
        ----------
        event : TraitChangeEvent
            Event triggered by add_trait.
        graph : ObserverGraph
            Description for the *downstream* observers, i.e. excluding self.
        handler : callable
            User handler.
        target : object
            Object seen by the user as the owner of the observer.
        dispatcher : callable
            Callable for dispatching the handler.
        """
        new_graph = ObserverGraph(
            node=_RestrictedNamedTraitObserver(
                name=event.new,
                wrapped_observer=graph.node,
            ),
            children=graph.children
        )
        add_or_remove_notifiers(
            object=event.object,
            graph=new_graph,
            handler=handler,
            target=target,
            dispatcher=dispatcher,
            remove=False,
        )
        # There is no mirrored action to this.
        # ``remove_trait`` does not fire a change event.
        # see enthought/traits#1047

    def iter_extra_graphs(self, graph):
        """ Yield new ObserverGraph to be contributed by this observer.

        Parameters
        ----------
        graph : ObserverGraph
            The graph this observer is part of.

        Yields
        ------
        ObserverGraph
        """
        yield from ()


@IObserver.register
class _RestrictedNamedTraitObserver:
    """ An observer to support TraitAddedObserver in order to add
    notifiers for one specific named trait. The notifiers should be
    contributed by the original observer.

    Parameters
    ----------
    name : str
        Name of the trait to be observed.
    wrapped_observer : IObserver
        The observer from which notifers are obtained.
    """

    def __init__(self, name, wrapped_observer):
        self.name = name
        self._wrapped_observer = wrapped_observer

    def __hash__(self):
        return hash((type(self), self.name, self._wrapped_observer))

    def __eq__(self, other):
        return (
            type(self) is type(other)
            and self.name == other.name
            and self._wrapped_observer == other._wrapped_observer
        )

    @property
    def notify(self):
        """ A boolean for whether this observer will notify for changes. """
        return self._wrapped_observer.notify

    def iter_observables(self, object):
        """ Yield only the observable for the named trait."""
        yield object._trait(self.name, 2)

    def iter_objects(self, object):
        """ Yield only the value for the named trait."""
        yield from iter_objects(object, self.name)

    def get_notifier(self, handler, target, dispatcher):
        """ Return the notifier from the wrapped observer."""
        return self._wrapped_observer.get_notifier(handler, target, dispatcher)

    def get_maintainer(self, graph, handler, target, dispatcher):
        """ Return the maintainer from the wrapped observer."""
        return self._wrapped_observer.get_maintainer(
            graph, handler, target, dispatcher)

    def iter_extra_graphs(self, graph):
        yield from ()