summaryrefslogtreecommitdiffstats
path: root/contrib/python/traitlets/py2
diff options
context:
space:
mode:
authormaxim-yurchuk <[email protected]>2024-10-09 12:29:46 +0300
committermaxim-yurchuk <[email protected]>2024-10-09 13:14:22 +0300
commit9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch)
treea8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/traitlets/py2
parenta44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff)
publishFullContrib: true for ydb
<HIDDEN_URL> commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/traitlets/py2')
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/tests/__init__.py0
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/tests/test_application.py421
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py459
-rw-r--r--contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py453
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/tests/__init__.py0
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py14
-rw-r--r--contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py30
7 files changed, 1377 insertions, 0 deletions
diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/__init__.py b/contrib/python/traitlets/py2/traitlets/config/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/tests/__init__.py
diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py
new file mode 100644
index 00000000000..39bd9b786e8
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_application.py
@@ -0,0 +1,421 @@
+# coding: utf-8
+"""
+Tests for traitlets.config.application.Application
+"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import json
+import logging
+import os
+from io import StringIO
+from unittest import TestCase
+
+try:
+ from unittest import mock
+except ImportError:
+ import mock
+
+pjoin = os.path.join
+
+from pytest import mark
+
+from traitlets.config.configurable import Configurable
+from traitlets.config.loader import Config
+from traitlets.tests.utils import check_help_output, check_help_all_output
+
+from traitlets.config.application import (
+ Application
+)
+
+from ipython_genutils.tempdir import TemporaryDirectory
+from traitlets.traitlets import (
+ Bool, Unicode, Integer, List, Dict
+)
+
+
+class Foo(Configurable):
+
+ i = Integer(0, help="The integer i.").tag(config=True)
+ j = Integer(1, help="The integer j.").tag(config=True)
+ name = Unicode(u'Brian', help="First name.").tag(config=True)
+
+
+class Bar(Configurable):
+
+ b = Integer(0, help="The integer b.").tag(config=True)
+ enabled = Bool(True, help="Enable bar.").tag(config=True)
+
+
+class MyApp(Application):
+
+ name = Unicode(u'myapp')
+ running = Bool(False, help="Is the app running?").tag(config=True)
+ classes = List([Bar, Foo])
+ config_file = Unicode(u'', help="Load this config file").tag(config=True)
+
+ warn_tpyo = Unicode(u"yes the name is wrong on purpose", config=True,
+ help="Should print a warning if `MyApp.warn-typo=...` command is passed")
+
+ aliases = Dict({
+ 'i' : 'Foo.i',
+ 'j' : 'Foo.j',
+ 'name' : 'Foo.name',
+ 'enabled' : 'Bar.enabled',
+ 'log-level' : 'Application.log_level',
+ })
+
+ flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"),
+ disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False"),
+ crit=({'Application' : {'log_level' : logging.CRITICAL}},
+ "set level=CRITICAL"),
+ ))
+
+ def init_foo(self):
+ self.foo = Foo(parent=self)
+
+ def init_bar(self):
+ self.bar = Bar(parent=self)
+
+
+class TestApplication(TestCase):
+
+ def test_log(self):
+ stream = StringIO()
+ app = MyApp(log_level=logging.INFO)
+ handler = logging.StreamHandler(stream)
+ # trigger reconstruction of the log formatter
+ app.log.handlers = [handler]
+ app.log_format = "%(message)s"
+ app.log_datefmt = "%Y-%m-%d %H:%M"
+ app.log.info("hello")
+ assert "hello" in stream.getvalue()
+
+ def test_basic(self):
+ app = MyApp()
+ self.assertEqual(app.name, u'myapp')
+ self.assertEqual(app.running, False)
+ self.assertEqual(app.classes, [MyApp,Bar,Foo])
+ self.assertEqual(app.config_file, u'')
+
+ def test_config(self):
+ app = MyApp()
+ app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"])
+ config = app.config
+ self.assertEqual(config.Foo.i, 10)
+ self.assertEqual(config.Foo.j, 10)
+ self.assertEqual(config.Bar.enabled, False)
+ self.assertEqual(config.MyApp.log_level,50)
+
+ def test_config_propagation(self):
+ app = MyApp()
+ app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"])
+ app.init_foo()
+ app.init_bar()
+ self.assertEqual(app.foo.i, 10)
+ self.assertEqual(app.foo.j, 10)
+ self.assertEqual(app.bar.enabled, False)
+
+ def test_cli_priority(self):
+ """Test that loading config files does not override CLI options"""
+ name = 'config.py'
+ class TestApp(Application):
+ value = Unicode().tag(config=True)
+ config_file_loaded = Bool().tag(config=True)
+ aliases = {'v': 'TestApp.value'}
+ app = TestApp()
+ with TemporaryDirectory() as td:
+ config_file = pjoin(td, name)
+ with open(config_file, 'w') as f:
+ f.writelines([
+ "c.TestApp.value = 'config file'\n",
+ "c.TestApp.config_file_loaded = True\n"
+ ])
+
+ app.parse_command_line(['--v=cli'])
+ assert 'value' in app.config.TestApp
+ assert app.config.TestApp.value == 'cli'
+ assert app.value == 'cli'
+
+ app.load_config_file(name, path=[td])
+ assert app.config_file_loaded
+ assert app.config.TestApp.value == 'cli'
+ assert app.value == 'cli'
+
+ def test_ipython_cli_priority(self):
+ # this test is almost entirely redundant with above,
+ # but we can keep it around in case of subtle issues creeping into
+ # the exact sequence IPython follows.
+ name = 'config.py'
+ class TestApp(Application):
+ value = Unicode().tag(config=True)
+ config_file_loaded = Bool().tag(config=True)
+ aliases = {'v': 'TestApp.value'}
+ app = TestApp()
+ with TemporaryDirectory() as td:
+ config_file = pjoin(td, name)
+ with open(config_file, 'w') as f:
+ f.writelines([
+ "c.TestApp.value = 'config file'\n",
+ "c.TestApp.config_file_loaded = True\n"
+ ])
+ # follow IPython's config-loading sequence to ensure CLI priority is preserved
+ app.parse_command_line(['--v=cli'])
+ # this is where IPython makes a mistake:
+ # it assumes app.config will not be modified,
+ # and storing a reference is storing a copy
+ cli_config = app.config
+ assert 'value' in app.config.TestApp
+ assert app.config.TestApp.value == 'cli'
+ assert app.value == 'cli'
+ app.load_config_file(name, path=[td])
+ assert app.config_file_loaded
+ # enforce cl-opts override config file opts:
+ # this is where IPython makes a mistake: it assumes
+ # that cl_config is a different object, but it isn't.
+ app.update_config(cli_config)
+ assert app.config.TestApp.value == 'cli'
+ assert app.value == 'cli'
+
+ def test_flags(self):
+ app = MyApp()
+ app.parse_command_line(["--disable"])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, False)
+ app.parse_command_line(["--enable"])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, True)
+
+ def test_aliases(self):
+ app = MyApp()
+ app.parse_command_line(["--i=5", "--j=10"])
+ app.init_foo()
+ self.assertEqual(app.foo.i, 5)
+ app.init_foo()
+ self.assertEqual(app.foo.j, 10)
+
+ def test_flag_clobber(self):
+ """test that setting flags doesn't clobber existing settings"""
+ app = MyApp()
+ app.parse_command_line(["--Bar.b=5", "--disable"])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, False)
+ self.assertEqual(app.bar.b, 5)
+ app.parse_command_line(["--enable", "--Bar.b=10"])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, True)
+ self.assertEqual(app.bar.b, 10)
+
+ def test_warn_autocorrect(self):
+ stream = StringIO()
+ app = MyApp(log_level=logging.INFO)
+ app.log.handlers = [logging.StreamHandler(stream)]
+
+ cfg = Config()
+ cfg.MyApp.warn_typo = "WOOOO"
+ app.config = cfg
+
+ self.assertIn("warn_typo", stream.getvalue())
+ self.assertIn("warn_tpyo", stream.getvalue())
+
+
+ def test_flatten_flags(self):
+ cfg = Config()
+ cfg.MyApp.log_level = logging.WARN
+ app = MyApp()
+ app.update_config(cfg)
+ self.assertEqual(app.log_level, logging.WARN)
+ self.assertEqual(app.config.MyApp.log_level, logging.WARN)
+ app.initialize(["--crit"])
+ self.assertEqual(app.log_level, logging.CRITICAL)
+ # this would be app.config.Application.log_level if it failed:
+ self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL)
+
+ def test_flatten_aliases(self):
+ cfg = Config()
+ cfg.MyApp.log_level = logging.WARN
+ app = MyApp()
+ app.update_config(cfg)
+ self.assertEqual(app.log_level, logging.WARN)
+ self.assertEqual(app.config.MyApp.log_level, logging.WARN)
+ app.initialize(["--log-level", "CRITICAL"])
+ self.assertEqual(app.log_level, logging.CRITICAL)
+ # this would be app.config.Application.log_level if it failed:
+ self.assertEqual(app.config.MyApp.log_level, "CRITICAL")
+
+ def test_extra_args(self):
+ app = MyApp()
+ app.parse_command_line(["--Bar.b=5", 'extra', "--disable", 'args'])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, False)
+ self.assertEqual(app.bar.b, 5)
+ self.assertEqual(app.extra_args, ['extra', 'args'])
+ app = MyApp()
+ app.parse_command_line(["--Bar.b=5", '--', 'extra', "--disable", 'args'])
+ app.init_bar()
+ self.assertEqual(app.bar.enabled, True)
+ self.assertEqual(app.bar.b, 5)
+ self.assertEqual(app.extra_args, ['extra', '--disable', 'args'])
+
+ def test_unicode_argv(self):
+ app = MyApp()
+ app.parse_command_line(['ünîcødé'])
+
+ def test_document_config_option(self):
+ app = MyApp()
+ app.document_config_options()
+
+ def test_generate_config_file(self):
+ app = MyApp()
+ assert 'The integer b.' in app.generate_config_file()
+
+ def test_generate_config_file_classes_to_include(self):
+ class NoTraits(Foo, Bar):
+ pass
+
+ app = MyApp()
+ app.classes.append(NoTraits)
+ conf_txt = app.generate_config_file()
+ self.assertIn('The integer b.', conf_txt)
+ self.assertIn('# Bar(Configurable)', conf_txt)
+ self.assertIn('# Foo(Configurable)', conf_txt)
+ self.assertNotIn('# Configurable', conf_txt)
+ self.assertIn('# NoTraits(Foo,Bar)', conf_txt)
+
+ def test_multi_file(self):
+ app = MyApp()
+ app.log = logging.getLogger()
+ name = 'config.py'
+ with TemporaryDirectory('_1') as td1:
+ with open(pjoin(td1, name), 'w') as f1:
+ f1.write("get_config().MyApp.Bar.b = 1")
+ with TemporaryDirectory('_2') as td2:
+ with open(pjoin(td2, name), 'w') as f2:
+ f2.write("get_config().MyApp.Bar.b = 2")
+ app.load_config_file(name, path=[td2, td1])
+ app.init_bar()
+ self.assertEqual(app.bar.b, 2)
+ app.load_config_file(name, path=[td1, td2])
+ app.init_bar()
+ self.assertEqual(app.bar.b, 1)
+
+ @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs')
+ def test_log_collisions(self):
+ app = MyApp()
+ app.log = logging.getLogger()
+ app.log.setLevel(logging.INFO)
+ name = 'config'
+ with TemporaryDirectory('_1') as td:
+ with open(pjoin(td, name + '.py'), 'w') as f:
+ f.write("get_config().Bar.b = 1")
+ with open(pjoin(td, name + '.json'), 'w') as f:
+ json.dump({
+ 'Bar': {
+ 'b': 2
+ }
+ }, f)
+ with self.assertLogs(app.log, logging.WARNING) as captured:
+ app.load_config_file(name, path=[td])
+ app.init_bar()
+ assert app.bar.b == 2
+ output = '\n'.join(captured.output)
+ assert 'Collision' in output
+ assert '1 ignored, using 2' in output
+ assert pjoin(td, name + '.py') in output
+ assert pjoin(td, name + '.json') in output
+
+ @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs')
+ def test_log_bad_config(self):
+ 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 self.assertLogs(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])
+
+ def test_loaded_config_files(self):
+ app = MyApp()
+ app.log = logging.getLogger()
+ name = 'config.py'
+ with TemporaryDirectory('_1') as td1:
+ config_file = pjoin(td1, name)
+ with open(config_file, 'w') as f:
+ f.writelines([
+ "c.MyApp.running = True\n"
+ ])
+
+ app.load_config_file(name, path=[td1])
+ self.assertEqual(len(app.loaded_config_files), 1)
+ self.assertEquals(app.loaded_config_files[0], config_file)
+
+ app.start()
+ self.assertEqual(app.running, True)
+
+ # emulate an app that allows dynamic updates and update config file
+ with open(config_file, 'w') as f:
+ f.writelines([
+ "c.MyApp.running = False\n"
+ ])
+
+ # reload and verify update, and that loaded_configs was not increased
+ app.load_config_file(name, path=[td1])
+ self.assertEqual(len(app.loaded_config_files), 1)
+ self.assertEqual(app.running, False)
+
+ # Attempt to update, ensure error...
+ with self.assertRaises(AttributeError):
+ app.loaded_config_files = "/foo"
+
+ # ensure it can't be udpated via append
+ app.loaded_config_files.append("/bar")
+ self.assertEqual(len(app.loaded_config_files), 1)
+
+ # repeat to ensure no unexpected changes occurred
+ app.load_config_file(name, path=[td1])
+ self.assertEqual(len(app.loaded_config_files), 1)
+ self.assertEqual(app.running, False)
+
+
+class DeprecatedApp(Application):
+ override_called = False
+ parent_called = False
+ def _config_changed(self, name, old, new):
+ self.override_called = True
+ def _capture(*args):
+ self.parent_called = True
+ with mock.patch.object(self.log, 'debug', _capture):
+ super(DeprecatedApp, self)._config_changed(name, old, new)
+
+
+def test_deprecated_notifier():
+ app = DeprecatedApp()
+ assert not app.override_called
+ assert not app.parent_called
+ app.config = Config({'A': {'b': 'c'}})
+ assert app.override_called
+ assert app.parent_called
+
+
+def test_help_output():
+ check_help_output(__name__)
+ check_help_all_output(__name__)
+
+if __name__ == '__main__':
+ # for test_help_output:
+ MyApp.launch_instance() \ No newline at end of file
diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py
new file mode 100644
index 00000000000..9fbdb7209dc
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_configurable.py
@@ -0,0 +1,459 @@
+# encoding: utf-8
+"""Tests for traitlets.config.configurable"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import logging
+from unittest import TestCase
+
+from pytest import mark
+
+from traitlets.config.configurable import (
+ Configurable,
+ LoggingConfigurable,
+ SingletonConfigurable,
+)
+
+from traitlets.traitlets import (
+ Integer, Float, Unicode, List, Dict, Set,
+ _deprecations_shown,
+)
+
+from traitlets.config.loader import Config
+from six import PY3
+
+from ...tests._warnings import expected_warnings
+
+class MyConfigurable(Configurable):
+ a = Integer(1, help="The integer a.").tag(config=True)
+ b = Float(1.0, help="The integer b.").tag(config=True)
+ c = Unicode('no config')
+
+
+mc_help=u"""MyConfigurable options
+----------------------
+--MyConfigurable.a=<Integer>
+ Default: 1
+ The integer a.
+--MyConfigurable.b=<Float>
+ Default: 1.0
+ The integer b."""
+
+mc_help_inst=u"""MyConfigurable options
+----------------------
+--MyConfigurable.a=<Integer>
+ Current: 5
+ The integer a.
+--MyConfigurable.b=<Float>
+ Current: 4.0
+ The integer b."""
+
+# On Python 3, the Integer trait is a synonym for Int
+if PY3:
+ mc_help = mc_help.replace(u"<Integer>", u"<Int>")
+ mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>")
+
+class Foo(Configurable):
+ a = Integer(0, help="The integer a.").tag(config=True)
+ b = Unicode('nope').tag(config=True)
+
+
+class Bar(Foo):
+ b = Unicode('gotit', help="The string b.").tag(config=False)
+ c = Float(help="The string c.").tag(config=True)
+
+
+class TestConfigurable(TestCase):
+
+ def test_default(self):
+ c1 = Configurable()
+ c2 = Configurable(config=c1.config)
+ c3 = Configurable(config=c2.config)
+ self.assertEqual(c1.config, c2.config)
+ self.assertEqual(c2.config, c3.config)
+
+ def test_custom(self):
+ config = Config()
+ config.foo = 'foo'
+ config.bar = 'bar'
+ c1 = Configurable(config=config)
+ c2 = Configurable(config=c1.config)
+ c3 = Configurable(config=c2.config)
+ self.assertEqual(c1.config, config)
+ self.assertEqual(c2.config, config)
+ self.assertEqual(c3.config, config)
+ # Test that copies are not made
+ self.assertTrue(c1.config is config)
+ self.assertTrue(c2.config is config)
+ self.assertTrue(c3.config is config)
+ self.assertTrue(c1.config is c2.config)
+ self.assertTrue(c2.config is c3.config)
+
+ def test_inheritance(self):
+ config = Config()
+ config.MyConfigurable.a = 2
+ config.MyConfigurable.b = 2.0
+ c1 = MyConfigurable(config=config)
+ c2 = MyConfigurable(config=c1.config)
+ self.assertEqual(c1.a, config.MyConfigurable.a)
+ self.assertEqual(c1.b, config.MyConfigurable.b)
+ self.assertEqual(c2.a, config.MyConfigurable.a)
+ self.assertEqual(c2.b, config.MyConfigurable.b)
+
+ def test_parent(self):
+ config = Config()
+ config.Foo.a = 10
+ config.Foo.b = "wow"
+ config.Bar.b = 'later'
+ config.Bar.c = 100.0
+ f = Foo(config=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')
+ self.assertEqual(b.c, 100.0)
+
+ def test_override1(self):
+ config = Config()
+ config.MyConfigurable.a = 2
+ config.MyConfigurable.b = 2.0
+ c = MyConfigurable(a=3, config=config)
+ self.assertEqual(c.a, 3)
+ self.assertEqual(c.b, config.MyConfigurable.b)
+ self.assertEqual(c.c, 'no config')
+
+ def test_override2(self):
+ config = Config()
+ 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
+ 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)
+ 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)
+
+ def test_help(self):
+ self.assertEqual(MyConfigurable.class_get_help(), mc_help)
+
+ def test_help_inst(self):
+ inst = MyConfigurable(a=5, b=4)
+ self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
+
+
+class TestSingletonConfigurable(TestCase):
+
+ def test_instance(self):
+ class Foo(SingletonConfigurable): pass
+ self.assertEqual(Foo.initialized(), False)
+ foo = Foo.instance()
+ self.assertEqual(Foo.initialized(), True)
+ self.assertEqual(foo, Foo.instance())
+ self.assertEqual(SingletonConfigurable._instance, None)
+
+ def test_inheritance(self):
+ class Bar(SingletonConfigurable): pass
+ class Bam(Bar): pass
+ self.assertEqual(Bar.initialized(), False)
+ self.assertEqual(Bam.initialized(), False)
+ bam = Bam.instance()
+ bam == Bar.instance()
+ self.assertEqual(Bar.initialized(), True)
+ self.assertEqual(Bam.initialized(), True)
+ self.assertEqual(bam, Bam._instance)
+ self.assertEqual(bam, Bar._instance)
+ self.assertEqual(SingletonConfigurable._instance, None)
+
+
+class MyParent(Configurable):
+ pass
+
+class MyParent2(MyParent):
+ pass
+
+class TestParentConfigurable(TestCase):
+
+ def test_parent_config(self):
+ cfg = Config({
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 2.0,
+ }
+ }
+ })
+ parent = MyParent(config=cfg)
+ myc = MyConfigurable(parent=parent)
+ self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
+
+ def test_parent_inheritance(self):
+ cfg = Config({
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 2.0,
+ }
+ }
+ })
+ parent = MyParent2(config=cfg)
+ myc = MyConfigurable(parent=parent)
+ self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
+
+ def test_multi_parent(self):
+ cfg = Config({
+ 'MyParent2' : {
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 2.0,
+ }
+ },
+ # this one shouldn't count
+ 'MyConfigurable' : {
+ 'b' : 3.0,
+ },
+ }
+ })
+ parent2 = MyParent2(config=cfg)
+ parent = MyParent(parent=parent2)
+ myc = MyConfigurable(parent=parent)
+ self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
+
+ def test_parent_priority(self):
+ cfg = Config({
+ 'MyConfigurable' : {
+ 'b' : 2.0,
+ },
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 3.0,
+ }
+ },
+ 'MyParent2' : {
+ 'MyConfigurable' : {
+ 'b' : 4.0,
+ }
+ }
+ })
+ parent = MyParent2(config=cfg)
+ myc = MyConfigurable(parent=parent)
+ self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
+
+ def test_multi_parent_priority(self):
+ cfg = Config({
+ 'MyConfigurable' : {
+ 'b' : 2.0,
+ },
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 3.0,
+ }
+ },
+ 'MyParent2' : {
+ 'MyConfigurable' : {
+ 'b' : 4.0,
+ }
+ },
+ 'MyParent2' : {
+ 'MyParent' : {
+ 'MyConfigurable' : {
+ 'b' : 5.0,
+ }
+ }
+ }
+ })
+ parent2 = MyParent2(config=cfg)
+ parent = MyParent2(parent=parent2)
+ myc = MyConfigurable(parent=parent)
+ self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
+
+class Containers(Configurable):
+ lis = List().tag(config=True)
+ def _lis_default(self):
+ return [-1]
+
+ s = Set().tag(config=True)
+ def _s_default(self):
+ return {'a'}
+
+ d = Dict().tag(config=True)
+ def _d_default(self):
+ return {'a' : 'b'}
+
+class TestConfigContainers(TestCase):
+ def test_extend(self):
+ c = Config()
+ c.Containers.lis.extend(list(range(5)))
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, list(range(-1,5)))
+
+ def test_insert(self):
+ c = Config()
+ c.Containers.lis.insert(0, 'a')
+ c.Containers.lis.insert(1, 'b')
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, ['a', 'b', -1])
+
+ def test_prepend(self):
+ c = Config()
+ c.Containers.lis.prepend([1,2])
+ c.Containers.lis.prepend([2,3])
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, [2,3,1,2,-1])
+
+ def test_prepend_extend(self):
+ c = Config()
+ c.Containers.lis.prepend([1,2])
+ c.Containers.lis.extend([2,3])
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, [1,2,-1,2,3])
+
+ def test_append_extend(self):
+ c = Config()
+ c.Containers.lis.append([1,2])
+ c.Containers.lis.extend([2,3])
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, [-1,[1,2],2,3])
+
+ def test_extend_append(self):
+ c = Config()
+ c.Containers.lis.extend([2,3])
+ c.Containers.lis.append([1,2])
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, [-1,2,3,[1,2]])
+
+ def test_insert_extend(self):
+ c = Config()
+ c.Containers.lis.insert(0, 1)
+ c.Containers.lis.extend([2,3])
+ obj = Containers(config=c)
+ self.assertEqual(obj.lis, [1,-1,2,3])
+
+ def test_set_update(self):
+ c = Config()
+ c.Containers.s.update({0,1,2})
+ c.Containers.s.update({3})
+ obj = Containers(config=c)
+ self.assertEqual(obj.s, {'a', 0, 1, 2, 3})
+
+ def test_dict_update(self):
+ c = Config()
+ c.Containers.d.update({'c' : 'd'})
+ c.Containers.d.update({'e' : 'f'})
+ obj = Containers(config=c)
+ self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'})
+
+ def test_update_twice(self):
+ c = Config()
+ c.MyConfigurable.a = 5
+ m = MyConfigurable(config=c)
+ self.assertEqual(m.a, 5)
+
+ c2 = Config()
+ c2.MyConfigurable.a = 10
+ m.update_config(c2)
+ self.assertEqual(m.a, 10)
+
+ c2.MyConfigurable.a = 15
+ m.update_config(c2)
+ self.assertEqual(m.a, 15)
+
+ def test_update_self(self):
+ """update_config with same config object still triggers config_changed"""
+ c = Config()
+ c.MyConfigurable.a = 5
+ m = MyConfigurable(config=c)
+ self.assertEqual(m.a, 5)
+ c.MyConfigurable.a = 10
+ m.update_config(c)
+ self.assertEqual(m.a, 10)
+
+ def test_config_default(self):
+ class SomeSingleton(SingletonConfigurable):
+ pass
+
+ class DefaultConfigurable(Configurable):
+ a = Integer().tag(config=True)
+ def _config_default(self):
+ if SomeSingleton.initialized():
+ return SomeSingleton.instance().config
+ return Config()
+
+ c = Config()
+ c.DefaultConfigurable.a = 5
+
+ d1 = DefaultConfigurable()
+ self.assertEqual(d1.a, 0)
+
+ single = SomeSingleton.instance(config=c)
+
+ d2 = DefaultConfigurable()
+ self.assertIs(d2.config, single.config)
+ self.assertEqual(d2.a, 5)
+
+ def test_config_default_deprecated(self):
+ """Make sure configurables work even with the deprecations in traitlets"""
+ class SomeSingleton(SingletonConfigurable):
+ pass
+
+ # reset deprecation limiter
+ _deprecations_shown.clear()
+ with expected_warnings([]):
+ class DefaultConfigurable(Configurable):
+ a = Integer(config=True)
+ def _config_default(self):
+ if SomeSingleton.initialized():
+ return SomeSingleton.instance().config
+ return Config()
+
+ c = Config()
+ c.DefaultConfigurable.a = 5
+
+ d1 = DefaultConfigurable()
+ self.assertEqual(d1.a, 0)
+
+ single = SomeSingleton.instance(config=c)
+
+ d2 = DefaultConfigurable()
+ self.assertIs(d2.config, single.config)
+ self.assertEqual(d2.a, 5)
+
+
+class TestLogger(TestCase):
+
+ class A(LoggingConfigurable):
+ foo = Integer(config=True)
+ bar = Integer(config=True)
+ baz = Integer(config=True)
+
+ @mark.skipif(not hasattr(TestCase, 'assertLogs'), reason='requires TestCase.assertLogs')
+ def test_warn_match(self):
+ logger = logging.getLogger('test_warn_match')
+ cfg = Config({'A': {'bat': 5}})
+ with self.assertLogs(logger, logging.WARNING) as captured:
+ a = TestLogger.A(config=cfg, log=logger)
+
+ output = '\n'.join(captured.output)
+ self.assertIn('Did you mean one of: `bar, baz`?', output)
+ self.assertIn('Config option `bat` not recognized by `A`.', output)
+
+ cfg = Config({'A': {'fool': 5}})
+ with self.assertLogs(logger, logging.WARNING) as captured:
+ a = TestLogger.A(config=cfg, log=logger)
+
+ output = '\n'.join(captured.output)
+ self.assertIn('Config option `fool` not recognized by `A`.', output)
+ self.assertIn('Did you mean `foo`?', output)
+
+ cfg = Config({'A': {'totally_wrong': 5}})
+ with self.assertLogs(logger, logging.WARNING) as captured:
+ a = TestLogger.A(config=cfg, log=logger)
+
+ output = '\n'.join(captured.output)
+ self.assertIn('Config option `totally_wrong` not recognized by `A`.', output)
+ self.assertNotIn('Did you mean', output)
+
diff --git a/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py b/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py
new file mode 100644
index 00000000000..50c8659f3c6
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/config/tests/test_loader.py
@@ -0,0 +1,453 @@
+# encoding: utf-8
+"""Tests for traitlets.config.loader"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import copy
+import logging
+import os
+import pickle
+import sys
+from tempfile import mkstemp
+from unittest import TestCase
+
+from pytest import skip
+
+from traitlets.config.loader import (
+ Config,
+ LazyConfigValue,
+ PyFileConfigLoader,
+ JSONFileConfigLoader,
+ KeyValueConfigLoader,
+ ArgParseConfigLoader,
+ KVArgParseConfigLoader,
+ ConfigError,
+)
+
+
+pyfile = """
+c = get_config()
+c.a=10
+c.b=20
+c.Foo.Bar.value=10
+c.Foo.Bam.value=list(range(10))
+c.D.C.value='hi there'
+"""
+
+json1file = """
+{
+ "version": 1,
+ "a": 10,
+ "b": 20,
+ "Foo": {
+ "Bam": {
+ "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+ },
+ "Bar": {
+ "value": 10
+ }
+ },
+ "D": {
+ "C": {
+ "value": "hi there"
+ }
+ }
+}
+"""
+
+# should not load
+json2file = """
+{
+ "version": 2
+}
+"""
+
+import logging
+log = logging.getLogger('devnull')
+log.setLevel(0)
+
+class TestFileCL(TestCase):
+
+ def _check_conf(self, config):
+ self.assertEqual(config.a, 10)
+ self.assertEqual(config.b, 20)
+ self.assertEqual(config.Foo.Bar.value, 10)
+ self.assertEqual(config.Foo.Bam.value, list(range(10)))
+ self.assertEqual(config.D.C.value, 'hi there')
+
+ def test_python(self):
+ fd, fname = mkstemp('.py')
+ f = os.fdopen(fd, 'w')
+ f.write(pyfile)
+ f.close()
+ # Unlink the file
+ cl = PyFileConfigLoader(fname, log=log)
+ config = cl.load_config()
+ self._check_conf(config)
+
+ def test_json(self):
+ fd, fname = mkstemp('.json')
+ f = os.fdopen(fd, 'w')
+ f.write(json1file)
+ f.close()
+ # Unlink the file
+ 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()
+ self.assertEqual(a.collisions(b), {})
+ a.A.trait1 = 1
+ b.A.trait2 = 2
+ self.assertEqual(a.collisions(b), {})
+ b.A.trait1 = 1
+ self.assertEqual(a.collisions(b), {})
+ b.A.trait1 = 0
+ self.assertEqual(a.collisions(b), {
+ 'A': {
+ 'trait1': "1 ignored, using 0",
+ }
+ })
+ self.assertEqual(b.collisions(a), {
+ 'A': {
+ 'trait1': "0 ignored, using 1",
+ }
+ })
+ a.A.trait2 = 3
+ self.assertEqual(b.collisions(a), {
+ 'A': {
+ 'trait1': "0 ignored, using 1",
+ 'trait2': "2 ignored, using 3",
+ }
+ })
+
+ def test_v2raise(self):
+ fd, fname = mkstemp('.json')
+ f = os.fdopen(fd, 'w')
+ f.write(json2file)
+ f.close()
+ # Unlink the file
+ cl = JSONFileConfigLoader(fname, log=log)
+ with self.assertRaises(ValueError):
+ cl.load_config()
+
+
+class MyLoader1(ArgParseConfigLoader):
+ def _add_arguments(self, aliases=None, flags=None):
+ p = self.parser
+ p.add_argument('-f', '--foo', dest='Global.foo', type=str)
+ p.add_argument('-b', dest='MyClass.bar', type=int)
+ p.add_argument('-n', dest='n', action='store_true')
+ p.add_argument('Global.bam', type=str)
+
+class MyLoader2(ArgParseConfigLoader):
+ def _add_arguments(self, aliases=None, flags=None):
+ subparsers = self.parser.add_subparsers(dest='subparser_name')
+ subparser1 = subparsers.add_parser('1')
+ subparser1.add_argument('-x',dest='Global.x')
+ subparser2 = subparsers.add_parser('2')
+ subparser2.add_argument('y')
+
+class TestArgParseCL(TestCase):
+
+ def test_basic(self):
+ cl = MyLoader1()
+ config = cl.load_config('-f hi -b 10 -n wow'.split())
+ self.assertEqual(config.Global.foo, 'hi')
+ self.assertEqual(config.MyClass.bar, 10)
+ self.assertEqual(config.n, True)
+ self.assertEqual(config.Global.bam, 'wow')
+ config = cl.load_config(['wow'])
+ self.assertEqual(list(config.keys()), ['Global'])
+ self.assertEqual(list(config.Global.keys()), ['bam'])
+ self.assertEqual(config.Global.bam, 'wow')
+
+ def test_add_arguments(self):
+ cl = MyLoader2()
+ config = cl.load_config('2 frobble'.split())
+ self.assertEqual(config.subparser_name, '2')
+ self.assertEqual(config.y, 'frobble')
+ config = cl.load_config('1 -x frobble'.split())
+ self.assertEqual(config.subparser_name, '1')
+ self.assertEqual(config.Global.x, 'frobble')
+
+ def test_argv(self):
+ cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
+ config = cl.load_config()
+ self.assertEqual(config.Global.foo, 'hi')
+ self.assertEqual(config.MyClass.bar, 10)
+ self.assertEqual(config.n, True)
+ self.assertEqual(config.Global.bam, 'wow')
+
+
+class TestKeyValueCL(TestCase):
+ klass = KeyValueConfigLoader
+
+ def test_eval(self):
+ cl = self.klass(log=log)
+ config = cl.load_config('--Class.str_trait=all --Class.int_trait=5 --Class.list_trait=["hello",5]'.split())
+ self.assertEqual(config.Class.str_trait, 'all')
+ self.assertEqual(config.Class.int_trait, 5)
+ self.assertEqual(config.Class.list_trait, ["hello", 5])
+
+ def test_basic(self):
+ cl = self.klass(log=log)
+ argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ]
+ print(argv)
+ config = cl.load_config(argv)
+ self.assertEqual(config.a, 10)
+ self.assertEqual(config.b, 20)
+ self.assertEqual(config.Foo.Bar.value, 10)
+ # non-literal expressions are not evaluated
+ self.assertEqual(config.Foo.Bam.value, 'list(range(10))')
+ self.assertEqual(config.D.C.value, 'hi there')
+
+ def test_expanduser(self):
+ cl = self.klass(log=log)
+ argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
+ config = cl.load_config(argv)
+ self.assertEqual(config.a, os.path.expanduser('~/1/2/3'))
+ self.assertEqual(config.b, os.path.expanduser('~'))
+ self.assertEqual(config.c, os.path.expanduser('~/'))
+ self.assertEqual(config.d, '~/')
+
+ def test_extra_args(self):
+ cl = self.klass(log=log)
+ config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
+ self.assertEqual(cl.extra_args, ['b', 'd'])
+ self.assertEqual(config.a, 5)
+ self.assertEqual(config.c, 10)
+ config = cl.load_config(['--', '--a=5', '--c=10'])
+ self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
+
+ def test_unicode_args(self):
+ cl = self.klass(log=log)
+ argv = [u'--a=épsîlön']
+ config = cl.load_config(argv)
+ self.assertEqual(config.a, u'épsîlön')
+
+ def test_unicode_bytes_args(self):
+ uarg = u'--a=é'
+ try:
+ barg = uarg.encode(sys.stdin.encoding)
+ except (TypeError, UnicodeEncodeError):
+ raise skip("sys.stdin.encoding can't handle 'é'")
+
+ cl = self.klass(log=log)
+ config = cl.load_config([barg])
+ self.assertEqual(config.a, u'é')
+
+ def test_unicode_alias(self):
+ cl = self.klass(log=log)
+ argv = [u'--a=épsîlön']
+ config = cl.load_config(argv, aliases=dict(a='A.a'))
+ self.assertEqual(config.A.a, u'épsîlön')
+
+
+class TestArgParseKVCL(TestKeyValueCL):
+ klass = KVArgParseConfigLoader
+
+ def test_expanduser2(self):
+ cl = self.klass(log=log)
+ argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"]
+ config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
+ self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3'))
+ self.assertEqual(config.A.b, '~/1/2/3')
+
+ def test_eval(self):
+ cl = self.klass(log=log)
+ argv = ['-c', 'a=5']
+ config = cl.load_config(argv, aliases=dict(c='A.c'))
+ self.assertEqual(config.A.c, u"a=5")
+
+
+class TestConfig(TestCase):
+
+ def test_setget(self):
+ c = Config()
+ c.a = 10
+ self.assertEqual(c.a, 10)
+ self.assertEqual('b' in c, False)
+
+ def test_auto_section(self):
+ c = Config()
+ self.assertNotIn('A', c)
+ assert not c._has_section('A')
+ A = c.A
+ A.foo = 'hi there'
+ self.assertIn('A', c)
+ assert c._has_section('A')
+ self.assertEqual(c.A.foo, 'hi there')
+ del c.A
+ self.assertEqual(c.A, Config())
+
+ def test_merge_doesnt_exist(self):
+ c1 = Config()
+ c2 = Config()
+ c2.bar = 10
+ c2.Foo.bar = 10
+ c1.merge(c2)
+ self.assertEqual(c1.Foo.bar, 10)
+ self.assertEqual(c1.bar, 10)
+ c2.Bar.bar = 10
+ c1.merge(c2)
+ self.assertEqual(c1.Bar.bar, 10)
+
+ def test_merge_exists(self):
+ c1 = Config()
+ c2 = Config()
+ c1.Foo.bar = 10
+ c1.Foo.bam = 30
+ c2.Foo.bar = 20
+ c2.Foo.wow = 40
+ c1.merge(c2)
+ self.assertEqual(c1.Foo.bam, 30)
+ self.assertEqual(c1.Foo.bar, 20)
+ self.assertEqual(c1.Foo.wow, 40)
+ c2.Foo.Bam.bam = 10
+ c1.merge(c2)
+ self.assertEqual(c1.Foo.Bam.bam, 10)
+
+ def test_deepcopy(self):
+ c1 = Config()
+ c1.Foo.bar = 10
+ c1.Foo.bam = 30
+ c1.a = 'asdf'
+ c1.b = range(10)
+ c1.Test.logger = logging.Logger('test')
+ c1.Test.get_logger = logging.getLogger('test')
+ c2 = copy.deepcopy(c1)
+ self.assertEqual(c1, c2)
+ self.assertTrue(c1 is not c2)
+ self.assertTrue(c1.Foo is not c2.Foo)
+ self.assertTrue(c1.Test is not c2.Test)
+ self.assertTrue(c1.Test.logger is c2.Test.logger)
+ self.assertTrue(c1.Test.get_logger is c2.Test.get_logger)
+
+ def test_builtin(self):
+ c1 = Config()
+ c1.format = "json"
+
+ def test_fromdict(self):
+ c1 = Config({'Foo' : {'bar' : 1}})
+ self.assertEqual(c1.Foo.__class__, Config)
+ self.assertEqual(c1.Foo.bar, 1)
+
+ def test_fromdictmerge(self):
+ c1 = Config()
+ c2 = Config({'Foo' : {'bar' : 1}})
+ c1.merge(c2)
+ self.assertEqual(c1.Foo.__class__, Config)
+ self.assertEqual(c1.Foo.bar, 1)
+
+ def test_fromdictmerge2(self):
+ c1 = Config({'Foo' : {'baz' : 2}})
+ c2 = Config({'Foo' : {'bar' : 1}})
+ c1.merge(c2)
+ self.assertEqual(c1.Foo.__class__, Config)
+ self.assertEqual(c1.Foo.bar, 1)
+ self.assertEqual(c1.Foo.baz, 2)
+ self.assertNotIn('baz', c2.Foo)
+
+ def test_contains(self):
+ c1 = Config({'Foo' : {'baz' : 2}})
+ c2 = Config({'Foo' : {'bar' : 1}})
+ self.assertIn('Foo', c1)
+ self.assertIn('Foo.baz', c1)
+ self.assertIn('Foo.bar', c2)
+ self.assertNotIn('Foo.bar', c1)
+
+ def test_pickle_config(self):
+ cfg = Config()
+ cfg.Foo.bar = 1
+ pcfg = pickle.dumps(cfg)
+ cfg2 = pickle.loads(pcfg)
+ self.assertEqual(cfg2, cfg)
+
+ def test_getattr_section(self):
+ cfg = Config()
+ self.assertNotIn('Foo', cfg)
+ Foo = cfg.Foo
+ assert isinstance(Foo, Config)
+ self.assertIn('Foo', cfg)
+
+ def test_getitem_section(self):
+ cfg = Config()
+ self.assertNotIn('Foo', cfg)
+ Foo = cfg['Foo']
+ assert isinstance(Foo, Config)
+ self.assertIn('Foo', cfg)
+
+ def test_getattr_not_section(self):
+ cfg = Config()
+ self.assertNotIn('foo', cfg)
+ foo = cfg.foo
+ assert isinstance(foo, LazyConfigValue)
+ self.assertIn('foo', cfg)
+
+ def test_getattr_private_missing(self):
+ cfg = Config()
+ self.assertNotIn('_repr_html_', cfg)
+ with self.assertRaises(AttributeError):
+ _ = cfg._repr_html_
+ self.assertNotIn('_repr_html_', cfg)
+ self.assertEqual(len(cfg), 0)
+
+ def test_getitem_not_section(self):
+ cfg = Config()
+ self.assertNotIn('foo', cfg)
+ foo = cfg['foo']
+ assert isinstance(foo, LazyConfigValue)
+ self.assertIn('foo', cfg)
+
+ def test_merge_no_copies(self):
+ c = Config()
+ c2 = Config()
+ c2.Foo.trait = []
+ c.merge(c2)
+ c2.Foo.trait.append(1)
+ self.assertIs(c.Foo, c2.Foo)
+ self.assertEqual(c.Foo.trait, [1])
+ self.assertEqual(c2.Foo.trait, [1])
+
diff --git a/contrib/python/traitlets/py2/traitlets/utils/tests/__init__.py b/contrib/python/traitlets/py2/traitlets/utils/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/tests/__init__.py
diff --git a/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py b/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py
new file mode 100644
index 00000000000..ef5c3c1384f
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/tests/test_bunch.py
@@ -0,0 +1,14 @@
+from ..bunch import Bunch
+
+def test_bunch():
+ b = Bunch(x=5, y=10)
+ assert 'y' in b
+ assert 'x' in b
+ assert b.x == 5
+ b['a'] = 'hi'
+ assert b.a == 'hi'
+
+def test_bunch_dir():
+ b = Bunch(x=5, y=10)
+ assert 'x' in dir(b)
+ assert 'keys' in dir(b)
diff --git a/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py b/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py
new file mode 100644
index 00000000000..c86459ff423
--- /dev/null
+++ b/contrib/python/traitlets/py2/traitlets/utils/tests/test_importstring.py
@@ -0,0 +1,30 @@
+# encoding: utf-8
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+#
+# Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
+# also under the terms of the Modified BSD License.
+"""Tests for traitlets.utils.importstring."""
+
+import os
+from unittest import TestCase
+
+from ..importstring import import_item
+
+
+class TestImportItem(TestCase):
+
+ def test_import_unicode(self):
+ self.assertIs(os, import_item(u'os'))
+ self.assertIs(os.path, import_item(u'os.path'))
+ self.assertIs(os.path.join, import_item(u'os.path.join'))
+
+ def test_bad_input(self):
+ class NotAString(object):
+ pass
+ msg = (
+ "import_item accepts strings, "
+ "not '%s'." % NotAString
+ )
+ with self.assertRaisesRegexp(TypeError, msg):
+ import_item(NotAString())