From aa6d882c62779ab79d3d0536d5b97517b6d5704e Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 17 Dec 2015 13:20:34 +0100 Subject: [PATCH 1/3] use observe in traitlets.config instead of deprecated APIs --- traitlets/config/application.py | 25 ++++++++++++++----------- traitlets/config/configurable.py | 10 +++++----- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/traitlets/config/application.py b/traitlets/config/application.py index bf3520f0..2b58a877 100644 --- a/traitlets/config/application.py +++ b/traitlets/config/application.py @@ -22,7 +22,7 @@ ) from traitlets.traitlets import ( - Unicode, List, Enum, Dict, Instance, TraitError + Unicode, List, Enum, Dict, Instance, TraitError, observe, default ) from ipython_genutils.importstring import import_item from ipython_genutils.text import indent, wrap_paragraphs, dedent @@ -177,7 +177,7 @@ def _log_format_changed(self, name, old, new): _log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt) _log_handler.setFormatter(_log_formatter) - + @default('log') def _log_default(self): """Start logging for this application. @@ -215,12 +215,14 @@ def _log_default(self): # this must be a dict of two-tuples, the first element being the Config/dict # and the second being the help string for the flag flags = Dict() - def _flags_changed(self, name, old, new): + @observe('flags') + def _flags_changed(self, change): """ensure flags dict is valid""" - for key,value in iteritems(new): - assert len(value) == 2, "Bad flag: %r:%s"%(key,value) - assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value) - assert isinstance(value[1], string_types), "Bad flag: %r:%s"%(key,value) + new = change['new'] + for key, value in new.items(): + assert len(value) == 2, "Bad flag: %r:%s" % (key, value) + assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s" % (key, value) + assert isinstance(value[1], string_types), "Bad flag: %r:%s" % (key, value) # subcommands for launching other applications @@ -242,11 +244,12 @@ def __init__(self, **kwargs): # options and config files. if self.__class__ not in self.classes: self.classes.insert(0, self.__class__) - - def _config_changed(self, name, old, new): - SingletonConfigurable._config_changed(self, name, old, new) + + @observe('config') + def _config_changed(self, change): + super(Application, self)._config_changed(change) self.log.debug('Config changed:') - self.log.debug(repr(new)) + self.log.debug(repr(change['new'])) @catch_config_error def initialize(self, argv=None): diff --git a/traitlets/config/configurable.py b/traitlets/config/configurable.py index eed9a816..a9ff2396 100644 --- a/traitlets/config/configurable.py +++ b/traitlets/config/configurable.py @@ -6,11 +6,10 @@ from __future__ import print_function -import logging from copy import deepcopy from .loader import Config, LazyConfigValue -from traitlets.traitlets import HasTraits, Instance +from traitlets.traitlets import HasTraits, Instance, observe, default from ipython_genutils.text import indent, dedent, wrap_paragraphs from ipython_genutils.py3compat import iteritems @@ -162,8 +161,8 @@ def _load_config(self, cfg, section_names=None, traits=None): self.log.warning(u"Config option `{option}` not recognized by `{klass}`, do you mean one of : `{matches}`" .format(option=name, klass=type(self).__name__, matches=' ,'.join(matches))) - - def _config_changed(self, name, old, new): + @observe('config') + def _config_changed(self, change): """Update all the class traits having ``config=True`` in metadata. For any class trait with a ``config`` metadata attribute that is @@ -177,7 +176,7 @@ def _config_changed(self, name, old, new): # classes that are Configurable subclasses. This starts with Configurable # and works down the mro loading the config for each section. section_names = self.section_names() - self._load_config(new, traits=traits, section_names=section_names) + self._load_config(change['new'], traits=traits, section_names=section_names) def update_config(self, config): """Update config and trigger reload of config via trait events""" @@ -329,6 +328,7 @@ class LoggingConfigurable(Configurable): """ log = Instance('logging.Logger') + @default('log') def _log_default(self): from traitlets import log return log.get_logger() From 6cc80a1d214541df6c635e92beb8d0e386fa43e8 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 17 Dec 2015 13:25:22 +0100 Subject: [PATCH 2/3] Deprecations about magic method names point to method definitions use warn_explicit to point directly to method definitions, rather than event-triggering code, which is not informative. --- traitlets/traitlets.py | 50 +++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 010d94d6..cc54bae3 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -50,7 +50,7 @@ ClassTypes = (ClassType, type) except: ClassTypes = (type,) -from warnings import warn +from warnings import warn, warn_explicit from ipython_genutils import py3compat from ipython_genutils.py3compat import iteritems, string_types @@ -90,6 +90,29 @@ class TraitError(Exception): #----------------------------------------------------------------------------- +def _deprecated_method(method, cls, method_name, msg): + """Show deprecation warning about a magic method definition. + + Uses warn_explicit to bind warning to method definition instead of triggering code, + which isn't relevant. + """ + warn_msg = "{classname}.{method_name} is deprecated: {msg}".format( + classname=cls.__name__, method_name=method_name, msg=msg + ) + + for parent in inspect.getmro(cls): + if method_name in parent.__dict__: + cls = parent + break + try: + fname = inspect.getsourcefile(method) or "" + lineno = inspect.getsourcelines(method)[1] or 0 + except TypeError as e: + # Failed to inspect for some reason + warn(warn_msg + ('\n(inspection failed)' % e), DeprecationWarning) + else: + warn_explicit(warn_msg, DeprecationWarning, fname, lineno) + def class_of(object): """ Returns a string containing the class name of an object with the correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', @@ -387,7 +410,7 @@ def __init__(self, default_value=Undefined, allow_none=None, read_only=None, hel ) if len(metadata) > 0: - warn("metadata %s was set from the constructor. Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')"%(metadata,), + warn("metadata %s was set from the constructor. Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')" % (metadata,), DeprecationWarning, stacklevel=2) if len(self.metadata) > 0: self.metadata = self.metadata.copy() @@ -451,9 +474,9 @@ def _dynamic_default_callable(self, obj): meth_name = '_%s_default' % self.name for cls in mro[:mro.index(self.this_class) + 1]: if meth_name in cls.__dict__: - warn("_[traitname]_default handlers are deprecated: use default" - " decorator instead", DeprecationWarning, stacklevel=2) - return getattr(obj, meth_name) + method = getattr(obj, meth_name) + _deprecated_method(method, cls, meth_name, "use @default decorator instead.") + return method return getattr(self, 'make_dynamic_default', None) @@ -538,9 +561,10 @@ def _cross_validate(self, obj, value): proposal = {'trait': self, 'value': value, 'owner': obj} value = obj._trait_validators[self.name](obj, proposal) elif hasattr(obj, '_%s_validate' % self.name): - warn("_[traitname]_validate handlers are deprecated: use validate" - " decorator instead", DeprecationWarning, stacklevel=2) - cross_validate = getattr(obj, '_%s_validate' % self.name) + meth_name = '_%s_validate' % self.name + cross_validate = getattr(obj, meth_name) + _deprecated_method(cross_validate, obj.__class__, meth_name, + "use @validate decorator instead.") value = cross_validate(value, self) return value @@ -995,9 +1019,9 @@ def notify_change(self, change): if hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) if not isinstance(class_value, ObserveHandler): - warn("_[traitname]_changed handlers are deprecated: use observe" - " and unobserve instead", DeprecationWarning, stacklevel=2) - cb = getattr(self, '_%s_changed' % name) + _deprecated_method(class_value, self.__class__, magic_name, + "use @observe and @unobserve instead.") + cb = getattr(self, magic_name) # Only append the magic method if it was not manually registered if cb not in callables: callables.append(_callback_wrapper(cb)) @@ -1174,8 +1198,8 @@ def _register_validator(self, handler, names): if hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) if not isinstance(class_value, ValidateHandler): - warn("_[traitname]_validate handlers are deprecated: use validate" - " decorator instead", DeprecationWarning, stacklevel=2) + _deprecated_method(class_value, self.__class, magic_name, + "use @validate decorator instead.") for name in names: self._trait_validators[name] = handler From d81184fb76851ce60732936b5729fec489bb584e Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 17 Dec 2015 14:28:38 +0100 Subject: [PATCH 3/3] handle super() offset to set correct stack level on metadata in `__init__` (gross?) --- traitlets/traitlets.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index cc54bae3..3a25dd27 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -410,8 +410,15 @@ def __init__(self, default_value=Undefined, allow_none=None, read_only=None, hel ) if len(metadata) > 0: + stacklevel = 1 + f = inspect.currentframe() + # count supers to determine stacklevel for warning + while f.f_code.co_name == '__init__': + stacklevel += 1 + f = f.f_back + warn("metadata %s was set from the constructor. Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')" % (metadata,), - DeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=stacklevel) if len(self.metadata) > 0: self.metadata = self.metadata.copy() self.metadata.update(metadata)