-
-
Notifications
You must be signed in to change notification settings - Fork 208
Deprecate on_trait_change and magic change handlers #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1786aaf
e5c6493
fb1c93c
f36276b
99a4efe
b278013
2d69059
815d3bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,8 +209,8 @@ def __init__(self, source, target): | |
| try: | ||
| setattr(target[0], target[1], getattr(source[0], source[1])) | ||
| finally: | ||
| source[0].on_trait_change(self._update_target, source[1]) | ||
| target[0].on_trait_change(self._update_source, target[1]) | ||
| source[0].observe(self._update_target, name=source[1]) | ||
| target[0].observe(self._update_source, name=target[1]) | ||
|
|
||
| @contextlib.contextmanager | ||
| def _busy_updating(self): | ||
|
|
@@ -220,21 +220,21 @@ def _busy_updating(self): | |
| finally: | ||
| self.updating = False | ||
|
|
||
| def _update_target(self, name, old, new): | ||
| def _update_target(self, change): | ||
| if self.updating: | ||
| return | ||
| with self._busy_updating(): | ||
| setattr(self.target[0], self.target[1], new) | ||
| setattr(self.target[0], self.target[1], change['new']) | ||
|
|
||
| def _update_source(self, name, old, new): | ||
| def _update_source(self, change): | ||
| if self.updating: | ||
| return | ||
| with self._busy_updating(): | ||
| setattr(self.source[0], self.source[1], new) | ||
| setattr(self.source[0], self.source[1], change['new']) | ||
|
|
||
| def unlink(self): | ||
| self.source[0].on_trait_change(self._update_target, self.source[1], remove=True) | ||
| self.target[0].on_trait_change(self._update_source, self.target[1], remove=True) | ||
| self.source[0].unobserve(self._update_target, name=self.source[1]) | ||
| self.target[0].unobserve(self._update_source, name=self.target[1]) | ||
| self.source, self.target = None, None | ||
|
|
||
|
|
||
|
|
@@ -261,7 +261,7 @@ def __init__(self, source, target): | |
| try: | ||
| setattr(target[0], target[1], getattr(source[0], source[1])) | ||
| finally: | ||
| self.source[0].on_trait_change(self._update, self.source[1]) | ||
| self.source[0].observe(self._update, name=self.source[1]) | ||
|
|
||
| @contextlib.contextmanager | ||
| def _busy_updating(self): | ||
|
|
@@ -271,14 +271,14 @@ def _busy_updating(self): | |
| finally: | ||
| self.updating = False | ||
|
|
||
| def _update(self, name, old, new): | ||
| def _update(self, change): | ||
| if self.updating: | ||
| return | ||
| with self._busy_updating(): | ||
| setattr(self.target[0], self.target[1], new) | ||
| setattr(self.target[0], self.target[1], change['new']) | ||
|
|
||
| def unlink(self): | ||
| self.source[0].on_trait_change(self._update, self.source[1], remove=True) | ||
| self.source[0].unobserve(self._update, name=self.source[1]) | ||
| self.source, self.target = None, None | ||
|
|
||
| dlink = directional_link | ||
|
|
@@ -552,6 +552,50 @@ def tag(self, **metadata): | |
| # The HasTraits implementation | ||
| #----------------------------------------------------------------------------- | ||
|
|
||
| class _CallbackWrapper(object): | ||
| """An object adapting a on_trait_change callback into an observe callback. | ||
|
|
||
| The comparison operator __eq__ is implemented to enable removal of wrapped | ||
| callbacks. | ||
| """ | ||
|
|
||
| def __init__(self, cb): | ||
| if callable(cb): | ||
| self.cb = cb | ||
| # Bound methods have an additional 'self' argument. | ||
| offset = -1 if isinstance(self.cb, types.MethodType) else 0 | ||
| self.nargs = len(getargspec(cb)[0]) + offset | ||
| if (self.nargs > 4): | ||
| raise TraitError('a trait changed callback must have 0-4 arguments.') | ||
| else: | ||
| raise TraitError('a trait changed callback must be callable.') | ||
|
|
||
| def __eq__(self, other): | ||
| # The wrapper is equal to the wrapped element | ||
| if isinstance(other, _CallbackWrapper): | ||
| return self.cb == other.cb | ||
| else: | ||
| return self.cb == other | ||
|
|
||
| def __call__(self, change): | ||
| # The wrapper is callable | ||
| if self.nargs == 0: | ||
| self.cb() | ||
| elif self.nargs == 1: | ||
| self.cb(change['name']) | ||
| elif self.nargs == 2: | ||
| self.cb(change['name'], change['new']) | ||
| elif self.nargs == 3: | ||
| self.cb(change['name'], change['old'], change['new']) | ||
| elif self.nargs == 4: | ||
| self.cb(change['name'], change['old'], change['new'], change['owner']) | ||
|
|
||
| def _callback_wrapper(cb): | ||
| if isinstance(cb, _CallbackWrapper): | ||
| return cb | ||
| else: | ||
| return _CallbackWrapper(cb) | ||
|
|
||
|
|
||
| class MetaHasTraits(type): | ||
| """A metaclass for HasTraits. | ||
|
|
@@ -593,6 +637,35 @@ class dict to the newly created class ``cls``. | |
| super(MetaHasTraits, cls).__init__(name, bases, classdict) | ||
|
|
||
|
|
||
| def observe(*names): | ||
| """ A decorator which can be used to observe members on a class. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| *names | ||
| The str names of the attributes to observe on the object. | ||
| """ | ||
| return ObserveHandler(names) | ||
|
|
||
|
|
||
| class ObserveHandler(BaseDescriptor): | ||
|
|
||
| def __init__(self, names=None): | ||
| if names is None: | ||
| self.names=[None] | ||
| else: | ||
| self.names = names | ||
|
|
||
| def __call__(self, func): | ||
| self.func = func | ||
| return self | ||
|
|
||
| def instance_init(self, inst): | ||
| setattr(inst, self.name, self.func) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the intent of this It seems like omitting the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum then you could not call the method directly right? |
||
| for name in self.names: | ||
| inst.observe(self.func, name=name) | ||
|
|
||
|
|
||
| class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): | ||
| """The base class for all classes that have traitlets. | ||
| """ | ||
|
|
@@ -693,46 +766,41 @@ def _notify_trait(self, name, old_value, new_value): | |
|
|
||
| # First dynamic ones | ||
| callables = [] | ||
| callables.extend(self._trait_notifiers.get(name,[])) | ||
| callables.extend(self._trait_notifiers.get('anytrait',[])) | ||
| callables.extend(self._trait_notifiers.get(name, [])) | ||
| callables.extend(self._trait_notifiers.get('anytrait', [])) | ||
|
|
||
| # Now static ones | ||
| try: | ||
| cb = getattr(self, '_%s_changed' % name) | ||
| except: | ||
| pass | ||
| else: | ||
| callables.append(cb) | ||
| warn("_[traitname]_changed change handlers are deprecated: use observe and unobserve instead", | ||
| DeprecationWarning, stacklevel=2) | ||
| callables.append(_callback_wrapper(cb)) | ||
|
|
||
| # Call them all now | ||
| for c in callables: | ||
| # Traits catches and logs errors here. I allow them to raise | ||
| if callable(c): | ||
| argspec = getargspec(c) | ||
|
|
||
| nargs = len(argspec[0]) | ||
| # Bound methods have an additional 'self' argument | ||
| # I don't know how to treat unbound methods, but they | ||
| # can't really be used for callbacks. | ||
| if isinstance(c, types.MethodType): | ||
| offset = -1 | ||
| # Bound methods have an additional 'self' argument. | ||
| offset = -1 if isinstance(c, types.MethodType) else 0 | ||
|
|
||
| if isinstance(c, _CallbackWrapper): | ||
| # _CallbackWrappers are not compatible with getargspec and have one argument | ||
| nargs = 1 | ||
| else: | ||
| offset = 0 | ||
| if nargs + offset == 0: | ||
| nargs = len(getargspec(c)[0]) + offset | ||
|
|
||
| if nargs == 0: | ||
| c() | ||
| elif nargs + offset == 1: | ||
| c(name) | ||
| elif nargs + offset == 2: | ||
| c(name, new_value) | ||
| elif nargs + offset == 3: | ||
| c(name, old_value, new_value) | ||
| elif nargs + offset == 4: | ||
| c(name, old_value, new_value, self) | ||
| elif nargs == 1: | ||
| c({'name': name, 'old': old_value, 'new': new_value, 'owner': self}) | ||
| else: | ||
| raise TraitError('a trait changed callback ' | ||
| 'must have 0-4 arguments.') | ||
| raise TraitError('an observe change callback ' | ||
| 'must have 0-1 arguments.') | ||
| else: | ||
| raise TraitError('a trait changed callback ' | ||
| raise TraitError('an observe change callback ' | ||
| 'must be callable.') | ||
|
|
||
| def _add_notifiers(self, handler, name): | ||
|
|
@@ -754,12 +822,8 @@ def _remove_notifiers(self, handler, name): | |
| except ValueError: | ||
| pass | ||
|
|
||
| def remove_all_notifiers(self): | ||
| """Remove all trait change handlers.""" | ||
| self._trait_notifiers = {} | ||
|
|
||
| def on_trait_change(self, handler=None, name=None, remove=False): | ||
| """Setup a handler to be called when a trait changes. | ||
| """DEPRECATED: Setup a handler to be called when a trait changes. | ||
|
|
||
| This is used to setup dynamic notifications of trait changes. | ||
|
|
||
|
|
@@ -769,8 +833,8 @@ def on_trait_change(self, handler=None, name=None, remove=False): | |
| _a_changed(self, name, old, new) (fewer arguments can be used, see | ||
| below). | ||
|
|
||
| If `remove` is True and `handler` is None, all handlers for the | ||
| specified name are uninstalled. | ||
| If `remove` is True and `handler` is not specified, all change | ||
| handlers for the specified name are uninstalled. | ||
|
|
||
| Parameters | ||
| ---------- | ||
|
|
@@ -786,14 +850,57 @@ def on_trait_change(self, handler=None, name=None, remove=False): | |
| If False (the default), then install the handler. If True | ||
| then unintall it. | ||
| """ | ||
| warn("on_trait_change is deprecated: use observe instead", | ||
| DeprecationWarning, stacklevel=2) | ||
| if remove: | ||
| names = parse_notifier_name(name) | ||
| for n in names: | ||
| self._remove_notifiers(handler, n) | ||
| self.unobserve(_callback_wrapper(handler), name=name) | ||
| else: | ||
| names = parse_notifier_name(name) | ||
| for n in names: | ||
| self._add_notifiers(handler, n) | ||
| self.observe(_callback_wrapper(handler), name=name) | ||
|
|
||
| def observe(self, handler, name=None): | ||
| """Setup a handler to be called when a trait changes. | ||
|
|
||
| This is used to setup dynamic notifications of trait changes. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| handler : callable | ||
| A callable that is called when a trait changes. Its | ||
| signature can be handler() or handler(change), where change is a | ||
| dictionary with the following keys: | ||
| - owner : the HasTraits instance | ||
| - old : the old value of the modified trait attribute | ||
| - new : the new value of the modified trait attribute | ||
| - name : the name of the modified trait attribute. | ||
| name : list, str, None | ||
| If None, the handler will apply to all traits. If a list | ||
| of str, handler will apply to all names in the list. If a | ||
| str, the handler will apply just to that name. | ||
| """ | ||
| names = parse_notifier_name(name) | ||
| for n in names: | ||
| self._add_notifiers(handler, n) | ||
|
|
||
| def unobserve(self, handler, name=None): | ||
| """Remove a trait change handler. | ||
|
|
||
| This is used to unregister handlers to trait change notificiations. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| handler : callable | ||
| The callable called when a trait attribute changes. | ||
| name : list, str, None | ||
| If None, all change handlers for the specified name are | ||
| uninstalled. | ||
| """ | ||
| names = parse_notifier_name(name) | ||
| for n in names: | ||
| self._remove_notifiers(handler, n) | ||
|
|
||
| def unobserve_all(self): | ||
| """Remove all trait change handlers.""" | ||
| self._trait_notifiers = {} | ||
|
|
||
| @classmethod | ||
| def class_trait_names(cls, **metadata): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that since this is being registered in the metaclass. The
ObserveHandleris preventing the function from being registered as a method at runtime like it normally would.instance_initshould instead read: