File: containers.py

package info (click to toggle)
python-echo 0.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 368 kB
  • sloc: python: 2,421; makefile: 148
file content (219 lines) | stat: -rw-r--r-- 6,794 bytes parent folder | download | duplicates (2)
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
from . import CallbackProperty, HasCallbackProperties
from .callback_container import CallbackContainer

__all__ = ['CallbackList', 'CallbackDict',
           'ListCallbackProperty', 'DictCallbackProperty']


class ContainerMixin:

    def _prepare_add(self, value):
        if isinstance(value, list):
            value = CallbackList(self.notify_all, value)
        elif isinstance(value, dict):
            value = CallbackDict(self.notify_all, value)
        if isinstance(value, HasCallbackProperties):
            value.add_global_callback(self.notify_all)
        elif isinstance(value, (CallbackList, CallbackDict)):
            value.callback = self.notify_all
        return value

    def _cleanup_remove(self, value):
        if isinstance(value, HasCallbackProperties):
            value.remove_global_callback(self.notify_all)
        elif isinstance(value, (CallbackList, CallbackDict)):
            value.callbacks.remove(self.notify_all)


class CallbackList(list, ContainerMixin):
    """
    A list that calls a callback function when it is modified.

    The first argument should be the callback function (which takes no
    arguments), and subsequent arguments are as for `list`.
    """

    def __init__(self, callback, *args, **kwargs):
        super(CallbackList, self).__init__(*args, **kwargs)
        self.callbacks = CallbackContainer()
        self.callbacks.append(callback)
        for index, value in enumerate(self):
            super().__setitem__(index, self._prepare_add(value))

    def notify_all(self, *args, **kwargs):
        for callback in self.callbacks:
            callback(*args, **kwargs)

    def __repr__(self):
        return "<CallbackList with {0} elements>".format(len(self))

    def append(self, value):
        super(CallbackList, self).append(self._prepare_add(value))
        self.notify_all()

    def extend(self, iterable):
        iterable = [self._prepare_add(value) for value in iterable]
        super(CallbackList, self).extend(iterable)
        self.notify_all()

    def insert(self, index, value):
        super(CallbackList, self).insert(index, self._prepare_add(value))
        self.notify_all()

    def pop(self, index=-1):
        result = super(CallbackList, self).pop(index)
        self._cleanup_remove(result)
        self.notify_all()
        return result

    def remove(self, value):
        super(CallbackList, self).remove(value)
        self._cleanup_remove(value)
        self.notify_all()

    def reverse(self):
        super(CallbackList, self).reverse()
        self.notify_all()

    def sort(self, key=None, reverse=False):
        super(CallbackList, self).sort(key=key, reverse=reverse)
        self.notify_all()

    def __setitem__(self, slc, new_value):

        old_values = self[slc]
        if not isinstance(slc, slice):
            old_values = [old_values]

        for old_value in old_values:
            self._cleanup_remove(old_value)

        if isinstance(slc, slice):
            new_value = [self._prepare_add(value) for value in new_value]
        else:
            new_value = self._prepare_add(new_value)

        super(CallbackList, self).__setitem__(slc, new_value)
        self.notify_all()

    def clear(self):
        for item in self:
            self._cleanup_remove(item)
        super(CallbackList, self).clear()
        self.notify_all()


class CallbackDict(dict, ContainerMixin):
    """
    A dictionary that calls a callback function when it is modified.

    The first argument should be the callback function (which takes no
    arguments), and subsequent arguments are passed to `dict`.
    """

    def __init__(self, callback, *args, **kwargs):
        super(CallbackDict, self).__init__(*args, **kwargs)
        self.callbacks = CallbackContainer()
        self.callbacks.append(callback)
        for key, value in self.items():
            super().__setitem__(key, self._prepare_add(value))

    def notify_all(self, *args, **kwargs):
        for callback in self.callbacks:
            callback(*args, **kwargs)

    def clear(self):
        for value in self.values():
            self._cleanup_remove(value)
        super().clear()
        self.notify_all()

    def popitem(self):
        result = super().popitem()
        self._cleanup_remove(result)
        self.notify_all()
        return result

    def update(self, *args, **kwargs):
        values = {}
        values.update(*args, **kwargs)
        for key, value in values.items():
            values[key] = self._prepare_add(value)
        super().update(values)
        self.notify_all()

    def pop(self, *args, **kwargs):
        result = super().pop(*args, **kwargs)
        self._cleanup_remove(result)
        self.notify_all()
        return result

    def __setitem__(self, key, value):
        if key in self:
            self._cleanup_remove(self[key])
        super().__setitem__(key, self._prepare_add(value))
        self.notify_all()

    def __repr__(self):
        return f"<CallbackDict with {len(self)} elements>"


class dynamic_callback:

    function = None

    def __call__(self, *args, **kwargs):
        self.function(*args, **kwargs)


class ListCallbackProperty(CallbackProperty):
    """
    A list property that calls callbacks when its contents are modified
    """

    def _default_getter(self, instance, owner=None):
        if instance not in self._values:
            self._default_setter(instance, self._default or [])
        return super(ListCallbackProperty, self)._default_getter(instance, owner)

    def _default_setter(self, instance, value):

        if not isinstance(value, list):
            raise TypeError('callback property should be a list')

        dcb = dynamic_callback()

        wrapped_list = CallbackList(dcb, value)

        def callback(*args, **kwargs):
            self.notify(instance, wrapped_list, wrapped_list)

        dcb.function = callback

        super(ListCallbackProperty, self)._default_setter(instance, wrapped_list)


class DictCallbackProperty(CallbackProperty):
    """
    A dictionary property that calls callbacks when its contents are modified
    """
    def _default_getter(self, instance, owner=None):
        if instance not in self._values:
            self._default_setter(instance, self._default or {})
        return super()._default_getter(instance, owner)

    def _default_setter(self, instance, value):

        if not isinstance(value, dict):
            raise TypeError("Callback property should be a dictionary.")

        dcb = dynamic_callback()

        wrapped_dict = CallbackDict(dcb, value)

        def callback(*args, **kwargs):
            self.notify(instance, wrapped_dict, wrapped_dict)

        dcb.function = callback

        super()._default_setter(instance, wrapped_dict)