Skip to content

Commit

Permalink
Deprecate on_trait_change and magic change handlers, and add HasTrait…
Browse files Browse the repository at this point in the history
…s.observe
  • Loading branch information
SylvainCorlay committed Aug 4, 2015
1 parent c661f4b commit ac3be19
Showing 1 changed file with 88 additions and 25 deletions.
113 changes: 88 additions & 25 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,47 @@ def tag(self, **metadata):
# The HasTraits implementation
#-----------------------------------------------------------------------------

class _CbWrapper(object):


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, _CbWrapper):
return self.cb == other.cb
else:
return self.cb == other

def __call__(self, change):
# The wrapper is callabled
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['object'])

def _cb_wrapper(cb):
if isinstance(cb, _CbWrapper):
return cb
else:
return _CbWrapper(cb)



class MetaHasTraits(type):
"""A metaclass for HasTraits.
Expand Down Expand Up @@ -688,46 +729,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 instead",
DeprecationWarning, stacklevel=2)
callables.append(_cb_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, _CbWrapper):
# _CbWrappers 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, 'object': 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):
Expand All @@ -747,7 +783,7 @@ def _remove_notifiers(self, handler, name):
pass

def on_trait_change(self, handler, 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.
Expand All @@ -771,6 +807,33 @@ def on_trait_change(self, handler, 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)
self.observe(_cb_wrapper(handler), name=name, remove=remove)

def observe(self, handler, name=None, remove=False):
"""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 optional keys:
- object : the HasTraits instance
- old : the old value of the modified trait attribute
- new : the new value of the modified trait attribute
- name : the name ofthe 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.
remove : bool
If False (the default), then install the handler. If True
then unintall it.
"""
if remove:
names = parse_notifier_name(name)
for n in names:
Expand Down

0 comments on commit ac3be19

Please sign in to comment.