Skip to content

Commit ca0a48d

Browse files
committed
Added AliasProperty and tests
1 parent 030941b commit ca0a48d

File tree

3 files changed

+136
-31
lines changed

3 files changed

+136
-31
lines changed

pydispatch/dispatch.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,12 @@ class Foo(Dispatcher):
8383
__initialized_subclasses = set()
8484
__skip_initialized = True
8585
def __new__(cls, *args, **kwargs):
86-
def iter_bases(_cls):
87-
if _cls is not object:
88-
yield _cls
89-
for b in _cls.__bases__:
90-
for _cls_ in iter_bases(b):
91-
yield _cls_
9286
skip_initialized = Dispatcher._Dispatcher__skip_initialized
9387
if not skip_initialized or cls not in Dispatcher._Dispatcher__initialized_subclasses:
9488
props = {}
9589
events = set()
96-
for _cls in iter_bases(cls):
97-
for attr in dir(_cls):
98-
prop = getattr(_cls, attr)
90+
for _cls in cls.__mro__:
91+
for attr, prop in _cls.__dict__.items():
9992
if attr not in props and isinstance(prop, Property):
10093
props[attr] = prop
10194
prop.name = attr

pydispatch/properties.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def on_foo_value(self, instance, value, **kwargs):
4141

4242
PY2 = sys.version_info < (3,)
4343

44-
__all__ = ['Property', 'ListProperty', 'DictProperty']
44+
__all__ = ['Property', 'AliasProperty', 'ListProperty', 'DictProperty']
45+
4546

4647
class Property(object):
4748
"""Defined on the class level to create an observable attribute
@@ -56,36 +57,44 @@ class Property(object):
5657
:class:`~pydispatch.dispatch.Dispatcher` instance.
5758
5859
"""
60+
5961
def __init__(self, default=None):
6062
self._name = ''
6163
self.default = default
6264
self.__storage = {}
6365
self.__weakrefs = InformativeWVDict(del_callback=self._on_weakref_fin)
66+
6467
@property
6568
def name(self):
6669
return self._name
70+
6771
@name.setter
6872
def name(self, value):
6973
if self._name != '':
7074
return
7175
self._name = value
76+
7277
def _add_instance(self, obj, default=None):
7378
if default is None:
7479
default = self.default
7580
self.__storage[id(obj)] = self.default
7681
self.__weakrefs[id(obj)] = obj
82+
7783
def _del_instance(self, obj):
7884
del self.__storage[id(obj)]
85+
7986
def _on_weakref_fin(self, obj_id):
8087
if obj_id in self.__storage:
8188
del self.__storage[obj_id]
89+
8290
def __get__(self, obj, objcls=None):
8391
if obj is None:
8492
return self
8593
obj_id = id(obj)
8694
if obj_id not in self.__storage:
8795
self._add_instance(obj)
8896
return self.__storage[obj_id]
97+
8998
def __set__(self, obj, value):
9099
obj_id = id(obj)
91100
if obj_id not in self.__storage:
@@ -95,6 +104,7 @@ def __set__(self, obj, value):
95104
return
96105
self.__storage[obj_id] = value
97106
self._on_change(obj, current, value)
107+
98108
def _on_change(self, obj, old, value, **kwargs):
99109
"""Called internally to emit changes from the instance object
100110
@@ -114,11 +124,58 @@ def _on_change(self, obj, old, value, **kwargs):
114124
"""
115125
kwargs['property'] = self
116126
obj.emit(self.name, obj, value, old=old, **kwargs)
127+
117128
def __repr__(self):
118129
return '<{}: {}>'.format(self.__class__, self)
130+
119131
def __str__(self):
120132
return self.name
121133

134+
135+
class AliasProperty(Property):
136+
"""Property with a getter method and optional setter method. Behaves similar to Pythons builtin properties.
137+
138+
Args:
139+
getter : method used to provide the property value
140+
setter (Optional): method used to set the property value. If this method returns False change events will
141+
not be emitted.
142+
"""
143+
144+
def __init__(self, getter, setter=None, bind=None):
145+
super().__init__()
146+
self.__getter = getter
147+
self.__setter = setter
148+
self.__bindings = dict((prop, self._on_change) for prop in bind) if bind is not None else {}
149+
150+
def _on_change(self, obj, *args, **kwargs):
151+
property = kwargs.get('property', None)
152+
if property is None:
153+
return super()._on_change(obj, *args, **kwargs)
154+
old = super().__get__(obj)
155+
value = self.__get__(obj)
156+
if old != value:
157+
super()._on_change(obj, old, value)
158+
159+
def _add_instance(self, obj, default=None):
160+
super()._add_instance(obj, default)
161+
obj.bind(**self.__bindings)
162+
163+
def __get__(self, obj, objcls=None):
164+
if obj is None:
165+
return self
166+
value = self._Property__storage[id(obj)] = self.__getter(obj)
167+
return value
168+
169+
def __set__(self, obj, value):
170+
current = self.__getter(obj)
171+
if current == value:
172+
return
173+
if self.__setter is None:
174+
raise AttributeError("can't set attribute")
175+
if self.__setter(obj, value) is None:
176+
super().__set__(obj, value)
177+
178+
122179
class ListProperty(Property):
123180
"""Property with a :class:`list` type value
124181
@@ -134,18 +191,22 @@ class ListProperty(Property):
134191
Changes to the contents of the list are able to be observed through
135192
:class:`ObservableList`.
136193
"""
194+
137195
def __init__(self, default=None, copy_on_change=False):
138196
if default is None:
139197
default = []
140198
self.copy_on_change = copy_on_change
141199
super(ListProperty, self).__init__(default)
200+
142201
def _add_instance(self, obj):
143202
default = self.default[:]
144203
default = ObservableList(default, obj=obj, property=self)
145204
super(ListProperty, self)._add_instance(obj, default)
205+
146206
def __set__(self, obj, value):
147207
value = ObservableList(value, obj=obj, property=self)
148208
super(ListProperty, self).__set__(obj, value)
209+
149210
def __get__(self, obj, objcls=None):
150211
if obj is None:
151212
return self
@@ -155,6 +216,7 @@ def __get__(self, obj, objcls=None):
155216
self._Property__storage[id(obj)] = value
156217
return value
157218

219+
158220
class DictProperty(Property):
159221
"""Property with a :class:`dict` type value
160222
@@ -170,18 +232,22 @@ class DictProperty(Property):
170232
Changes to the contents of the dict are able to be observed through
171233
:class:`ObservableDict`.
172234
"""
235+
173236
def __init__(self, default=None, copy_on_change=False):
174237
if default is None:
175238
default = {}
176239
self.copy_on_change = copy_on_change
177240
super(DictProperty, self).__init__(default)
241+
178242
def _add_instance(self, obj):
179243
default = self.default.copy()
180244
default = ObservableDict(default, obj=obj, property=self)
181245
super(DictProperty, self)._add_instance(obj, default)
246+
182247
def __set__(self, obj, value):
183248
value = ObservableDict(value, obj=obj, property=self)
184249
super(DictProperty, self).__set__(obj, value)
250+
185251
def __get__(self, obj, objcls=None):
186252
if obj is None:
187253
return self
@@ -191,6 +257,7 @@ def __get__(self, obj, objcls=None):
191257
self._Property__storage[id(obj)] = value
192258
return value
193259

260+
194261
class Observable(object):
195262
"""Mixin used by :class:`ObservableList` and :class:`ObservableDict`
196263
to emit changes and build other observables
@@ -202,19 +269,22 @@ class Observable(object):
202269
copied and replaced by another :class:`ObservableDict`. This allows nested
203270
containers to be observed and their changes to be tracked.
204271
"""
272+
205273
def _build_observable(self, item):
206274
if isinstance(item, list):
207275
item = ObservableList(item, parent=self)
208276
elif isinstance(item, dict):
209277
item = ObservableDict(item, parent=self)
210278
return item
279+
211280
def _get_copy_or_none(self):
212281
p = self.parent_observable
213282
if p is not None:
214283
return p._get_copy_or_none()
215284
if not self.copy_on_change:
216285
return None
217286
return self._deepcopy()
287+
218288
def _deepcopy(self):
219289
o = self.copy()
220290
if isinstance(self, list):
@@ -225,6 +295,7 @@ def _deepcopy(self):
225295
if isinstance(item, Observable):
226296
o[key] = item._deepcopy()
227297
return o
298+
228299
def _emit_change(self, **kwargs):
229300
if not self._init_complete:
230301
return
@@ -235,12 +306,14 @@ def _emit_change(self, **kwargs):
235306
return
236307
self.property._on_change(self.obj, old, self, **kwargs)
237308

309+
238310
class ObservableList(list, Observable):
239311
"""A :class:`list` subclass that tracks changes to its contents
240312
241313
Note:
242314
This class is for internal use and not intended to be used directly
243315
"""
316+
244317
def __init__(self, initlist=None, **kwargs):
245318
self._init_complete = False
246319
super(ObservableList, self).__init__()
@@ -254,20 +327,24 @@ def __init__(self, initlist=None, **kwargs):
254327
if initlist is not None:
255328
self.extend(initlist)
256329
self._init_complete = True
330+
257331
def __setitem__(self, key, item):
258332
old = self._get_copy_or_none()
259333
item = self._build_observable(item)
260334
super(ObservableList, self).__setitem__(key, item)
261335
self._emit_change(keys=[key], old=old)
336+
262337
def __delitem__(self, key):
263338
old = self._get_copy_or_none()
264339
super(ObservableList, self).__delitem__(key)
265340
self._emit_change(old=old)
341+
266342
if PY2:
267343
def __setslice__(self, *args):
268344
old = self._get_copy_or_none()
269345
super(ObservableList, self).__setslice__(*args)
270346
self._emit_change(old=old)
347+
271348
def __delslice__(self, *args):
272349
old = self._get_copy_or_none()
273350
super(ObservableList, self).__delslice__(*args)
@@ -280,15 +357,18 @@ def clear(self):
280357
if not hasattr(list, 'copy'):
281358
def copy(self):
282359
return self[:]
360+
283361
def __iadd__(self, other):
284362
other = self._build_observable(other)
285363
self.extend(other)
286364
return self
365+
287366
def append(self, item):
288367
old = self._get_copy_or_none()
289368
item = self._build_observable(item)
290369
super(ObservableList, self).append(item)
291370
self._emit_change(old=old)
371+
292372
def extend(self, other):
293373
old = self._get_copy_or_none()
294374
init = self._init_complete
@@ -298,17 +378,20 @@ def extend(self, other):
298378
if init:
299379
self._init_complete = True
300380
self._emit_change(old=old)
381+
301382
def remove(self, *args):
302383
old = self._get_copy_or_none()
303384
super(ObservableList, self).remove(*args)
304385
self._emit_change(old=old)
305386

387+
306388
class ObservableDict(dict, Observable):
307389
"""A :class:`dict` subclass that tracks changes to its contents
308390
309391
Note:
310392
This class is for internal use and not intended to be used directly
311393
"""
394+
312395
def __init__(self, initdict=None, **kwargs):
313396
self._init_complete = False
314397
super(ObservableDict, self).__init__()
@@ -322,15 +405,18 @@ def __init__(self, initdict=None, **kwargs):
322405
if initdict is not None:
323406
self.update(initdict)
324407
self._init_complete = True
408+
325409
def __setitem__(self, key, item):
326410
old = self._get_copy_or_none()
327411
item = self._build_observable(item)
328412
super(ObservableDict, self).__setitem__(key, item)
329413
self._emit_change(keys=[key], old=old)
414+
330415
def __delitem__(self, key):
331416
old = self._get_copy_or_none()
332417
super(ObservableDict, self).__delitem__(key)
333418
self._emit_change(old=old)
419+
334420
def update(self, other):
335421
old = self._get_copy_or_none()
336422
init = self._init_complete
@@ -344,15 +430,20 @@ def update(self, other):
344430
if init:
345431
self._init_complete = True
346432
self._emit_change(keys=list(keys), old=old)
433+
347434
def clear(self):
348435
old = self._get_copy_or_none()
349436
super(ObservableDict, self).clear()
350437
self._emit_change(old=old)
438+
351439
def pop(self, *args):
352440
old = self._get_copy_or_none()
353441
super(ObservableDict, self).pop(*args)
354442
self._emit_change(old=old)
443+
355444
def setdefault(self, *args):
356445
old = self._get_copy_or_none()
357446
super(ObservableDict, self).setdefault(*args)
358447
self._emit_change(old=old)
448+
449+

0 commit comments

Comments
 (0)