Hello community, here is the log from the commit of package python3-traitlets for openSUSE:Factory checked in at 2016-03-16 10:36:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-traitlets (Old) and /work/SRC/openSUSE:Factory/.python3-traitlets.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-traitlets" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-traitlets/python3-traitlets.changes 2016-01-20 09:55:10.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python3-traitlets.new/python3-traitlets.changes 2016-03-16 10:36:35.000000000 +0100 @@ -1,0 +2,19 @@ +Tue Mar 15 04:32:29 UTC 2016 - a...@gmx.de + +- update to version 4.2.1: + * demote super().__init__ argument warning to deprecation + +- changes from version 4.2.0: + * JSONFileConfigLoader can be used as a context manager for updating + configuration. + * If a value in config does not map onto a configurable trait, a + message is displayed that the value will have no effect. + * Unused arguments are passed to super() in HasTraits.__init__, + improving support for multiple inheritance. + * Various bugfixes and improvements in the new API introduced in + 4.1. + * Application subclasses may specify raise_config_file_errors = True + to exit on failure to load config files, instead of the default of + logging the failures. + +------------------------------------------------------------------- Old: ---- traitlets-4.1.0.tar.gz New: ---- traitlets-4.2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-traitlets.spec ++++++ --- /var/tmp/diff_new_pack.0XF8OL/_old 2016-03-16 10:36:36.000000000 +0100 +++ /var/tmp/diff_new_pack.0XF8OL/_new 2016-03-16 10:36:36.000000000 +0100 @@ -17,7 +17,7 @@ Name: python3-traitlets -Version: 4.1.0 +Version: 4.2.1 Release: 0 Summary: Traitlets Python config system License: BSD-3-Clause ++++++ traitlets-4.1.0.tar.gz -> traitlets-4.2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/MANIFEST.in new/traitlets-4.2.1/MANIFEST.in --- old/traitlets-4.1.0/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/MANIFEST.in 2015-11-27 16:12:53.000000000 +0100 @@ -0,0 +1,22 @@ +include CONTRIBUTING.md +include COPYING.md +include README.md + +# Documentation +graft docs +exclude docs/\#* + +# Examples +graft examples + +# docs subdirs we want to skip +prune docs/build +prune docs/gh-pages +prune docs/dist + +# Patterns to exclude from any directory +global-exclude *~ +global-exclude *.pyc +global-exclude *.pyo +global-exclude .git +global-exclude .ipynb_checkpoints diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/PKG-INFO new/traitlets-4.2.1/PKG-INFO --- old/traitlets-4.1.0/PKG-INFO 2016-01-15 18:00:30.000000000 +0100 +++ new/traitlets-4.2.1/PKG-INFO 2016-03-14 20:38:32.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: traitlets -Version: 4.1.0 +Version: 4.2.1 Summary: Traitlets Python config system Home-page: http://ipython.org Author: IPython Development Team diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/docs/source/changelog.rst new/traitlets-4.2.1/docs/source/changelog.rst --- old/traitlets-4.1.0/docs/source/changelog.rst 2016-01-15 17:57:59.000000000 +0100 +++ new/traitlets-4.2.1/docs/source/changelog.rst 2016-03-14 16:51:29.000000000 +0100 @@ -1,6 +1,22 @@ Changes in Traitlets ==================== +4.2 +--- + +`4.2 on GitHub <https://github.com/ipython/traitlets/milestones/4.2>`__ + +- :class:`JSONFileConfigLoader` can be used as a context manager for updating configuration. +- If a value in config does not map onto a configurable trait, + a message is displayed that the value will have no effect. +- Unused arguments are passed to ``super()`` in ``HasTraits.__init__``, + improving support for multiple inheritance. +- Various bugfixes and improvements in the new API introduced in 4.1. +- Application subclasses may specify ``raise_config_file_errors = True`` + to exit on failure to load config files, + instead of the default of logging the failures. + + 4.1 --- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/setup.cfg new/traitlets-4.2.1/setup.cfg --- old/traitlets-4.1.0/setup.cfg 2015-04-09 02:27:21.000000000 +0200 +++ new/traitlets-4.2.1/setup.cfg 2016-03-14 20:38:32.000000000 +0100 @@ -1,2 +1,8 @@ [bdist_wheel] -universal=1 +universal = 1 + +[egg_info] +tag_build = +tag_svn_revision = 0 +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/_version.py new/traitlets-4.2.1/traitlets/_version.py --- old/traitlets-4.1.0/traitlets/_version.py 2016-01-15 18:00:02.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/_version.py 2016-03-14 20:37:55.000000000 +0100 @@ -1,2 +1,2 @@ -version_info = (4, 1, 0) +version_info = (4, 2, 1) __version__ = '.'.join(map(str, version_info)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/application.py new/traitlets-4.2.1/traitlets/config/application.py --- old/traitlets-4.1.0/traitlets/config/application.py 2016-01-11 16:09:05.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/config/application.py 2016-03-14 16:37:50.000000000 +0100 @@ -21,7 +21,7 @@ ) from traitlets.traitlets import ( - Unicode, List, Enum, Dict, Instance, TraitError, observe, observe_compat, default, + Bool, Unicode, List, Enum, Dict, Instance, TraitError, observe, observe_compat, default, ) from ipython_genutils.importstring import import_item from ipython_genutils.text import indent, wrap_paragraphs, dedent @@ -149,6 +149,9 @@ # the argv used to initialize the application argv = List() + # Whether failing to load config files should prevent startup + raise_config_file_errors = Bool(False) + # The log level for the application log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'), default_value=logging.WARN, @@ -511,7 +514,7 @@ self.extra_args = loader.extra_args @classmethod - def _load_config_files(cls, basefilename, path=None, log=None): + def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file_errors=False): """Load config files (py,json) by filename and path. yield each config object in turn. @@ -536,6 +539,8 @@ # unlikely event that the error raised before filefind finished filename = loader.full_filename or basefilename # problem while running the file + if raise_config_file_errors: + raise if log: log.error("Exception while loading config file %s", filename, exc_info=True) @@ -553,7 +558,9 @@ """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) loaded = [] - for config in self._load_config_files(filename, path=path, log=self.log): + for config in self._load_config_files(filename, path=path, log=self.log, + raise_config_file_errors=self.raise_config_file_errors, + ): loaded.append(config) self.update_config(config) if len(loaded) > 1: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/configurable.py new/traitlets-4.2.1/traitlets/config/configurable.py --- old/traitlets-4.1.0/traitlets/config/configurable.py 2016-01-13 11:14:04.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/config/configurable.py 2016-02-17 12:48:39.000000000 +0100 @@ -7,8 +7,9 @@ from __future__ import print_function from copy import deepcopy +import warnings -from .loader import Config, LazyConfigValue +from .loader import Config, LazyConfigValue, _is_section_key from traitlets.traitlets import HasTraits, Instance, observe, observe_compat, default from ipython_genutils.text import indent, dedent, wrap_paragraphs from ipython_genutils.py3compat import iteritems @@ -151,15 +152,21 @@ # config object. If we don't, a mutable config_value will be # shared by all instances, effectively making it a class attribute. setattr(self, name, deepcopy(config_value)) - elif isinstance(self, LoggingConfigurable): + elif not _is_section_key(name) and not isinstance(config_value, Config): from difflib import get_close_matches + if isinstance(self, LoggingConfigurable): + warn = self.log.warning + else: + warn = lambda msg: warnings.warn(msg, stacklevel=9) matches = get_close_matches(name, traits) + msg = u"Config option `{option}` not recognized by `{klass}`.".format( + option=name, klass=self.__class__.__name__) + if len(matches) == 1: - self.log.warning(u"Config option `{option}` not recognized by `{klass}`, do you mean : `{matches}`" - .format(option=name, klass=type(self).__name__, matches=matches[0])) + msg += u" Did you mean `{matches}`?".format(matches=matches[0]) elif len(matches) >= 1: - 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))) + msg +=" Did you mean one of: `{matches}`?".format(matches=', '.join(sorted(matches))) + warn(msg) @observe('config') @observe_compat @@ -180,19 +187,13 @@ 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""" - # Save a copy of the old config - oldconfig = deepcopy(self.config) - # merge new config + """Update config and load the new values""" + # load config + self._load_config(config) + # merge it into self.config self.config.merge(config) - # unconditionally notify trait change, which triggers load of new config - self.notify_change({ - 'name': 'config', - 'old': oldconfig, - 'new': self.config, - 'owner': self, - 'type': 'change', - }) + # TODO: trigger change event if/when dict-update change events take place + # DO NOT trigger full trait-change @classmethod def class_get_help(cls, inst=None): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/loader.py new/traitlets-4.2.1/traitlets/config/loader.py --- old/traitlets-4.1.0/traitlets/config/loader.py 2015-07-11 05:20:55.000000000 +0200 +++ new/traitlets-4.2.1/traitlets/config/loader.py 2016-02-17 12:48:39.000000000 +0100 @@ -385,7 +385,16 @@ self.full_filename = filefind(self.filename, self.path) class JSONFileConfigLoader(FileConfigLoader): - """A JSON file loader for config""" + """A JSON file loader for config + + Can also act as a context manager that rewrite the configuration file to disk on exit. + + Example:: + + with JSONFileConfigLoader('myapp.json','/home/jupyter/configurations/') as c: + c.MyNewConfigurable.new_value = 'Updated' + + """ def load_config(self): """Load the config from a file and return it as a Config object.""" @@ -414,6 +423,23 @@ else: raise ValueError('Unknown version of JSON config file: {version}'.format(version=version)) + def __enter__(self): + self.load_config() + return self.config + + def __exit__(self, exc_type, exc_value, traceback): + """ + Exit the context manager but do not handle any errors. + + In case of any error, we do not want to write the potentially broken + configuration to disk. + """ + self.config.version = 1 + json_config = json.dumps(self.config, indent=2) + with open(self.full_filename, 'w') as f: + f.write(json_config) + + class PyFileConfigLoader(FileConfigLoader): """A config loader for pure python files. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/tests/test_application.py new/traitlets-4.2.1/traitlets/config/tests/test_application.py --- old/traitlets-4.1.0/traitlets/config/tests/test_application.py 2016-01-11 16:09:05.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/config/tests/test_application.py 2016-03-14 16:37:50.000000000 +0100 @@ -18,6 +18,7 @@ pjoin = os.path.join +from nose import SkipTest import nose.tools as nt from traitlets.config.configurable import Configurable @@ -223,6 +224,31 @@ app.load_config_file(name, path=[td1, td2]) app.init_bar() self.assertEqual(app.bar.b, 1) + + def test_log_bad_config(self): + if not hasattr(nt, 'assert_logs'): + raise SkipTest("Test requires nose.tests.assert_logs") + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with nt.assert_logs(app.log, logging.ERROR) as captured: + app.load_config_file(name, path=[td]) + output = '\n'.join(captured.output) + self.assertIn('SyntaxError', output) + + def test_raise_on_bad_config(self): + app = MyApp() + app.raise_config_file_errors = True + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory() as td: + with open(pjoin(td, name), 'w') as f: + f.write("syntax error()") + with self.assertRaises(SyntaxError): + app.load_config_file(name, path=[td]) class DeprecatedApp(Application): @@ -235,6 +261,7 @@ with mock.patch.object(self.log, 'debug', _capture): super(DeprecatedApp, self)._config_changed(name, old, new) + def test_deprecated_notifier(): app = DeprecatedApp() nt.assert_false(app.override_called) @@ -242,4 +269,4 @@ app.config = Config({'A': {'b': 'c'}}) nt.assert_true(app.override_called) nt.assert_true(app.parent_called) - + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/tests/test_configurable.py new/traitlets-4.2.1/traitlets/config/tests/test_configurable.py --- old/traitlets-4.1.0/traitlets/config/tests/test_configurable.py 2016-01-13 11:14:04.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/config/tests/test_configurable.py 2016-03-10 09:53:45.000000000 +0100 @@ -4,11 +4,16 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import logging from unittest import TestCase +import nose.tools as nt +from nose import SkipTest + from traitlets.config.configurable import ( - Configurable, - SingletonConfigurable + Configurable, + LoggingConfigurable, + SingletonConfigurable, ) from traitlets.traitlets import ( @@ -103,7 +108,8 @@ config.Bar.b = 'later' config.Bar.c = 100.0 f = Foo(config=config) - b = Bar(config=f.config) + with expected_warnings(['`b` not recognized']): + b = Bar(config=f.config) self.assertEqual(f.a, 10) self.assertEqual(f.b, 'wow') self.assertEqual(b.b, 'gotit') @@ -123,11 +129,13 @@ config.Foo.a = 1 config.Bar.b = 'or' # Up above b is config=False, so this won't do it. config.Bar.c = 10.0 - c = Bar(config=config) + with expected_warnings(['`b` not recognized']): + c = Bar(config=config) self.assertEqual(c.a, config.Foo.a) self.assertEqual(c.b, 'gotit') self.assertEqual(c.c, config.Bar.c) - c = Bar(a=2, b='and', c=20.0, config=config) + with expected_warnings(['`b` not recognized']): + c = Bar(a=2, b='and', c=20.0, config=config) self.assertEqual(c.a, 2) self.assertEqual(c.b, 'and') self.assertEqual(c.c, 20.0) @@ -411,4 +419,39 @@ d2 = DefaultConfigurable() self.assertIs(d2.config, single.config) self.assertEqual(d2.a, 5) - + + +def test_warn_match(): + if not hasattr(nt, 'assert_logs'): + raise SkipTest("Test requires nose.tests.assert_logs") + class A(LoggingConfigurable): + foo = Integer(config=True) + bar = Integer(config=True) + baz = Integer(config=True) + + logger = logging.getLogger('test_warn_match') + + cfg = Config({'A': {'bat': 5}}) + with nt.assert_logs(logger, logging.WARNING) as captured: + a = A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + nt.assert_in('Did you mean one of: `bar, baz`?', output) + nt.assert_in('Config option `bat` not recognized by `A`.', output) + + cfg = Config({'A': {'fool': 5}}) + with nt.assert_logs(logger, logging.WARNING) as captured: + a = A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + nt.assert_in('Config option `fool` not recognized by `A`.', output) + nt.assert_in('Did you mean `foo`?', output) + + cfg = Config({'A': {'totally_wrong': 5}}) + with nt.assert_logs(logger, logging.WARNING) as captured: + a = A(config=cfg, log=logger) + + output = '\n'.join(captured.output) + nt.assert_in('Config option `totally_wrong` not recognized by `A`.', output) + nt.assert_not_in('Did you mean', output) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/config/tests/test_loader.py new/traitlets-4.2.1/traitlets/config/tests/test_loader.py --- old/traitlets-4.1.0/traitlets/config/tests/test_loader.py 2015-07-11 05:20:55.000000000 +0200 +++ new/traitlets-4.2.1/traitlets/config/tests/test_loader.py 2016-02-17 12:48:39.000000000 +0100 @@ -99,7 +99,45 @@ cl = JSONFileConfigLoader(fname, log=log) config = cl.load_config() self._check_conf(config) - + + def test_context_manager(self): + + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + cl = JSONFileConfigLoader(fname, log=log) + + value = 'context_manager' + + with cl as c: + c.MyAttr.value = value + + self.assertEqual(cl.config.MyAttr.value, value) + + # check that another loader does see the change + cl2 = JSONFileConfigLoader(fname, log=log) + self.assertEqual(cl.config.MyAttr.value, value) + + def test_json_context_bad_write(self): + fd, fname = mkstemp('.json') + f = os.fdopen(fd, 'w') + f.write('{}') + f.close() + + with JSONFileConfigLoader(fname, log=log) as config: + config.A.b = 1 + + with self.assertRaises(TypeError): + with JSONFileConfigLoader(fname, log=log) as config: + config.A.cant_json = lambda x: x + + loader = JSONFileConfigLoader(fname, log=log) + cfg = loader.load_config() + assert cfg.A.b == 1 + assert 'cant_json' not in cfg.A + def test_collision(self): a = Config() b = Config() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/tests/test_traitlets.py new/traitlets-4.2.1/traitlets/tests/test_traitlets.py --- old/traitlets-4.1.0/traitlets/tests/test_traitlets.py 2016-01-11 16:09:05.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/tests/test_traitlets.py 2016-02-17 16:36:08.000000000 +0100 @@ -22,7 +22,7 @@ Union, All, Undefined, Type, This, Instance, TCPAddress, List, Tuple, ObjectName, DottedObjectName, CRegExp, link, directional_link, ForwardDeclaredType, ForwardDeclaredInstance, validate, observe, default, - observe_compat, + observe_compat, BaseDescriptor, HasDescriptors, ) from ipython_genutils import py3compat from ipython_genutils.testing.decorators import skipif @@ -266,6 +266,25 @@ self.assertEqual(B.tt.this_class, B) self.assertEqual(B.ttt.this_class, B) +class TestHasDescriptors(TestCase): + + def test_setup_instance(self): + + class FooDescriptor(BaseDescriptor): + + def instance_init(self, inst): + foo = inst.foo # instance should have the attr + + class HasFooDescriptors(HasDescriptors): + + fd = FooDescriptor() + + def setup_instance(self, *args, **kwargs): + self.foo = kwargs.get('foo', None) + super(HasFooDescriptors, self).setup_instance(*args, **kwargs) + + hfd = HasFooDescriptors(foo='bar') + class TestHasTraitsNotify(TestCase): def setUp(self): @@ -1025,7 +1044,7 @@ tree = Tree( value='foo', - leaves=[Tree('bar'), Tree('buzz')] + leaves=[Tree(value='bar'), Tree(value='buzz')] ) with self.assertRaises(TraitError): @@ -2163,3 +2182,33 @@ obj.trait = 5 nt.assert_true(obj.child_called) nt.assert_true(obj.parent_called) + +def test_super_args(): + class SuperRecorder(object): + def __init__(self, *args, **kwargs): + self.super_args = args + self.super_kwargs = kwargs + + class SuperHasTraits(HasTraits, SuperRecorder): + i = Integer() + + obj = SuperHasTraits('a1', 'a2', b=10, i=5, c='x') + nt.assert_equal(obj.i, 5) + assert not hasattr(obj, 'b') + assert not hasattr(obj, 'c') + nt.assert_equal(obj.super_args, ('a1', 'a2')) + nt.assert_equal(obj.super_kwargs, {'b': 10, 'c': 'x'}) + +def test_super_bad_args(): + class SuperHasTraits(HasTraits): + a = Integer() + + if sys.version_info < (3,): + # Legacy Python, object.__init__ warns itself, instead of raising + w = ['object.__init__'] + else: + w = ["Passing unrecoginized arguments"] + with expected_warnings(w): + obj = SuperHasTraits(a=1, b=2) + nt.assert_equal(obj.a, 1) + assert not hasattr(obj, 'b') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets/traitlets.py new/traitlets-4.2.1/traitlets/traitlets.py --- old/traitlets-4.1.0/traitlets/traitlets.py 2016-01-15 17:57:59.000000000 +0100 +++ new/traitlets-4.2.1/traitlets/traitlets.py 2016-03-14 20:37:18.000000000 +0100 @@ -107,7 +107,7 @@ try: fname = inspect.getsourcefile(method) or "<unknown>" lineno = inspect.getsourcelines(method)[1] or 0 - except TypeError as e: + except (IOError, TypeError) as e: # Failed to inspect for some reason warn(warn_msg + ('\n(inspection failed) %s' % e), DeprecationWarning) else: @@ -460,6 +460,7 @@ This looks for: + * default generators registered with the @default descriptor. * obj._{name}_default() on the class with the traitlet, or a subclass that obj belongs to. * trait.make_dynamic_default, which is defined by Instance @@ -486,17 +487,16 @@ return getattr(self, 'make_dynamic_default', None) def instance_init(self, obj): - obj._cross_validation_lock = True # If no dynamic initialiser is present, and the trait implementation or # use provides a static default, transfer that to obj._trait_values. - if (self._dynamic_default_callable(obj) is None) \ - and (self.default_value is not Undefined): - v = self._validate(obj, self.default_value) - if self.name is not None: - obj._trait_values[self.name] = v - obj._cross_validation_lock = False + with obj.cross_validation_lock: + if (self._dynamic_default_callable(obj) is None) \ + and (self.default_value is not Undefined): + v = self._validate(obj, self.default_value) + if self.name is not None: + obj._trait_values[self.name] = v - def get(self, obj, cls): + def get(self, obj, cls=None): try: value = obj._trait_values[self.name] except KeyError: @@ -800,7 +800,7 @@ the registered cross validator could potentially make changes to attributes of the ``HasTraits`` instance. However, we recommend not to do so. The reason is that the cross-validation of attributes may run in arbitrary order when - exitting the ``hold_trait_modification` context, and such changes may not + exiting the ``hold_trait_notifications` context, and such changes may not commute. """ return ValidateHandler(names) @@ -899,18 +899,22 @@ """The base class for all classes that have descriptors. """ - def __new__(cls, *args, **kw): + def __new__(cls, *args, **kwargs): # This is needed because object.__new__ only accepts # the cls argument. new_meth = super(HasDescriptors, cls).__new__ if new_meth is object.__new__: inst = new_meth(cls) else: - inst = new_meth(cls, **kw) - inst.setup_instance() + inst = new_meth(cls, *args, **kwargs) + inst.setup_instance(*args, **kwargs) return inst - def setup_instance(self): + def setup_instance(self, *args, **kwargs): + """ + This is called **before** self.__init__ is called. + """ + self._cross_validation_lock = False cls = self.__class__ for key in dir(cls): # Some descriptors raise AttributeError like zope.interface's @@ -927,20 +931,43 @@ class HasTraits(py3compat.with_metaclass(MetaHasTraits, HasDescriptors)): - def setup_instance(self): + def setup_instance(self, *args, **kwargs): self._trait_values = {} self._trait_notifiers = {} self._trait_validators = {} - super(HasTraits, self).setup_instance() + super(HasTraits, self).setup_instance(*args, **kwargs) - def __init__(self, *args, **kw): + def __init__(self, *args, **kwargs): # Allow trait values to be set using keyword arguments. # We need to use setattr for this to trigger validation and # notifications. - self._cross_validation_lock = False + super_args = args + super_kwargs = {} with self.hold_trait_notifications(): - for key, value in iteritems(kw): - setattr(self, key, value) + for key, value in iteritems(kwargs): + if self.has_trait(key): + setattr(self, key, value) + else: + # passthrough args that don't set traits to super + super_kwargs[key] = value + try: + super(HasTraits, self).__init__(*super_args, **super_kwargs) + except TypeError as e: + arg_s_list = [ repr(arg) for arg in super_args ] + for k, v in super_kwargs.items(): + arg_s_list.append("%s=%r" % (k, v)) + arg_s = ', '.join(arg_s_list) + warn( + "Passing unrecoginized arguments to super({classname}).__init__({arg_s}).\n" + "{error}\n" + "This error will be raised in a future release of traitlets." + .format( + arg_s=arg_s, classname=self.__class__.__name__, + error=e, + ), + DeprecationWarning, + stacklevel=2, + ) def __getstate__(self): d = self.__dict__.copy() @@ -968,6 +995,23 @@ if isinstance(value, EventHandler): value.instance_init(self) + @property + @contextlib.contextmanager + def cross_validation_lock(self): + """ + A contextmanager for running a block with our cross validation lock set + to True. + + At the end of the block, the lock's value is restored to its value + prior to entering the block. + """ + original_value = self._cross_validation_lock + try: + self._cross_validation_lock = True + yield + finally: + self._cross_validation_lock = original_value + @contextlib.contextmanager def hold_trait_notifications(self): """Context manager for bundling trait change notifications and cross @@ -1010,7 +1054,7 @@ for name in list(cache.keys()): trait = getattr(self.__class__, name) value = trait._cross_validate(self, getattr(self, name)) - setattr(self, name, value) + self.set_trait(name, value) except TraitError as e: # Roll back in case of TraitError during final cross validation. self.notify_change = lambda x: None @@ -1019,7 +1063,7 @@ # TODO: Separate in a rollback function per notification type. if change['type'] == 'change': if change['old'] is not Undefined: - setattr(self, name, change['old']) + self.set_trait(name, change['old']) else: self._trait_values.pop(name) cache = {} @@ -1077,7 +1121,7 @@ if isinstance(c, _CallbackWrapper): c = c.__call__ - elif isinstance(c, EventHandler): + elif isinstance(c, EventHandler) and c.name is not None: c = getattr(self, c.name) c(change) @@ -1150,8 +1194,8 @@ ---------- handler : callable A callable that is called when a trait changes. Its - signature can be ``handler()`` or ``handler(change)``, where change - is a dictionary. The change dictionary at least holds a 'type' key. + signature should be ``handler(change)``, where ``change```is a + dictionary. The change dictionary at least holds a 'type' key. * ``type``: the type of notification. Other keys may be passed depending on the value of 'type'. In the case where type is 'change', we also have the following keys: @@ -1224,10 +1268,6 @@ The names of the traits that should be cross-validated """ for name in names: - if name in self._trait_validators: - raise TraitError("A cross-validator for the trait" - " '%s' already exists" % name) - magic_name = '_%s_validate' % name if hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) @@ -1350,11 +1390,12 @@ def set_trait(self, name, value): """Forcibly sets trait attribute, including read-only attributes.""" + cls = self.__class__ if not self.has_trait(name): - raise TraitError("Class %s does not have a trait named %s" % - (self.__class__.__name__, name)) + raise TraitError("Class %s does not have a trait" + "named %s" % (cls.__name__, name)) else: - self.traits()[name].set(self, value) + getattr(cls, name).set(self, value) #----------------------------------------------------------------------------- # Actual TraitTypes implementations/subclasses @@ -1662,8 +1703,7 @@ super(Union, self).instance_init(obj) def validate(self, obj, value): - obj._cross_validation_lock = True - try: + with obj.cross_validation_lock: for trait_type in self.trait_types: try: v = trait_type._validate(obj, value) @@ -1671,17 +1711,22 @@ return v except TraitError: continue - finally: - obj._cross_validation_lock = False self.error(obj, value) - def __or__(self, other): if isinstance(other, Union): return Union(self.trait_types + other.trait_types) else: return Union(self.trait_types + [other]) + def make_dynamic_default(self): + for trait_type in self.trait_types: + if trait_type.default_value != Undefined: + return trait_type.default_value + elif hasattr(trait_type, 'make_dynamic_default'): + return trait_type.make_dynamic_default() + + #----------------------------------------------------------------------------- # Basic TraitTypes implementations/subclasses #----------------------------------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets.egg-info/PKG-INFO new/traitlets-4.2.1/traitlets.egg-info/PKG-INFO --- old/traitlets-4.1.0/traitlets.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/traitlets.egg-info/PKG-INFO 2016-03-14 20:38:32.000000000 +0100 @@ -0,0 +1,21 @@ +Metadata-Version: 1.1 +Name: traitlets +Version: 4.2.1 +Summary: Traitlets Python config system +Home-page: http://ipython.org +Author: IPython Development Team +Author-email: ipython-...@scipy.org +License: BSD +Description: A configuration system for Python applications. +Keywords: Interactive,Interpreter,Shell,Web +Platform: Linux +Platform: Mac OS X +Platform: Windows +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets.egg-info/SOURCES.txt new/traitlets-4.2.1/traitlets.egg-info/SOURCES.txt --- old/traitlets-4.1.0/traitlets.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/traitlets.egg-info/SOURCES.txt 2016-03-14 20:38:32.000000000 +0100 @@ -0,0 +1,46 @@ +CONTRIBUTING.md +COPYING.md +MANIFEST.in +README.md +setup.cfg +setup.py +docs/Makefile +docs/make.bat +docs/requirements.txt +docs/source/changelog.rst +docs/source/conf.py +docs/source/config.rst +docs/source/defining_traits.rst +docs/source/index.rst +docs/source/migration.rst +docs/source/trait_types.rst +docs/source/using_traitlets.rst +examples/myapp.py +traitlets/__init__.py +traitlets/_version.py +traitlets/log.py +traitlets/traitlets.py +traitlets.egg-info/PKG-INFO +traitlets.egg-info/SOURCES.txt +traitlets.egg-info/dependency_links.txt +traitlets.egg-info/requires.txt +traitlets.egg-info/top_level.txt +traitlets/config/__init__.py +traitlets/config/application.py +traitlets/config/configurable.py +traitlets/config/loader.py +traitlets/config/manager.py +traitlets/config/tests/__init__.py +traitlets/config/tests/test_application.py +traitlets/config/tests/test_configurable.py +traitlets/config/tests/test_loader.py +traitlets/tests/__init__.py +traitlets/tests/_warnings.py +traitlets/tests/test_traitlets.py +traitlets/tests/utils.py +traitlets/utils/__init__.py +traitlets/utils/getargspec.py +traitlets/utils/importstring.py +traitlets/utils/sentinel.py +traitlets/utils/tests/__init__.py +traitlets/utils/tests/test_importstring.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets.egg-info/dependency_links.txt new/traitlets-4.2.1/traitlets.egg-info/dependency_links.txt --- old/traitlets-4.1.0/traitlets.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/traitlets.egg-info/dependency_links.txt 2016-03-14 20:38:32.000000000 +0100 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets.egg-info/requires.txt new/traitlets-4.2.1/traitlets.egg-info/requires.txt --- old/traitlets-4.1.0/traitlets.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/traitlets.egg-info/requires.txt 2016-03-14 20:38:32.000000000 +0100 @@ -0,0 +1,2 @@ +ipython_genutils +decorator diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/traitlets-4.1.0/traitlets.egg-info/top_level.txt new/traitlets-4.2.1/traitlets.egg-info/top_level.txt --- old/traitlets-4.1.0/traitlets.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/traitlets-4.2.1/traitlets.egg-info/top_level.txt 2016-03-14 20:38:32.000000000 +0100 @@ -0,0 +1 @@ +traitlets