init
This commit is contained in:
commit
38355d2442
9083 changed files with 1225834 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,767 @@
|
|||
# coding: utf-8
|
||||
"""
|
||||
Tests for traitlets.config.application.Application
|
||||
"""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
from pytest import mark
|
||||
|
||||
from traitlets import (
|
||||
Bool,
|
||||
Bytes,
|
||||
Dict,
|
||||
HasTraits,
|
||||
Integer,
|
||||
List,
|
||||
Set,
|
||||
Tuple,
|
||||
Unicode,
|
||||
)
|
||||
from traitlets.config.application import Application
|
||||
from traitlets.config.configurable import Configurable
|
||||
from traitlets.config.loader import Config
|
||||
from traitlets.tests.utils import (
|
||||
check_help_all_output,
|
||||
check_help_output,
|
||||
get_output_error_code,
|
||||
)
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
|
||||
class Foo(Configurable):
|
||||
|
||||
i = Integer(0, help="""
|
||||
The integer i.
|
||||
|
||||
Details about i.
|
||||
""").tag(config=True)
|
||||
j = Integer(1, help="The integer j.").tag(config=True)
|
||||
name = Unicode('Brian', help="First name.").tag(config=True)
|
||||
la = List([]).tag(config=True)
|
||||
li = List(Integer()).tag(config=True)
|
||||
fdict = Dict().tag(config=True, multiplicity='+')
|
||||
|
||||
|
||||
class Bar(Configurable):
|
||||
|
||||
b = Integer(0, help="The integer b.").tag(config=True)
|
||||
enabled = Bool(True, help="Enable bar.").tag(config=True)
|
||||
tb = Tuple(()).tag(config=True, multiplicity='*')
|
||||
aset = Set().tag(config=True, multiplicity='+')
|
||||
bdict = Dict().tag(config=True)
|
||||
idict = Dict(value_trait=Integer()).tag(config=True)
|
||||
key_dict = Dict(per_key_traits={'i': Integer(), 'b': Bytes()}).tag(config=True)
|
||||
|
||||
|
||||
class MyApp(Application):
|
||||
|
||||
name = Unicode('myapp')
|
||||
running = Bool(False, help="Is the app running?").tag(config=True)
|
||||
classes = List([Bar, Foo])
|
||||
config_file = Unicode('', help="Load this config file").tag(config=True)
|
||||
|
||||
warn_tpyo = Unicode("yes the name is wrong on purpose", config=True,
|
||||
help="Should print a warning if `MyApp.warn-typo=...` command is passed")
|
||||
|
||||
aliases = {}
|
||||
aliases.update(Application.aliases)
|
||||
aliases.update({
|
||||
('fooi', 'i') : 'Foo.i',
|
||||
('j', 'fooj') : ('Foo.j', "`j` terse help msg"),
|
||||
'name' : 'Foo.name',
|
||||
'la': 'Foo.la',
|
||||
'li': 'Foo.li',
|
||||
'tb': 'Bar.tb',
|
||||
'D': 'Bar.bdict',
|
||||
'enabled' : 'Bar.enabled',
|
||||
'enable' : 'Bar.enabled',
|
||||
'log-level' : 'Application.log_level',
|
||||
})
|
||||
|
||||
flags = {}
|
||||
flags.update(Application.flags)
|
||||
flags.update({('enable', 'e'):
|
||||
({'Bar': {'enabled' : True}},
|
||||
"Set Bar.enabled to True"),
|
||||
('d', '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)
|
||||
|
||||
|
||||
def class_to_names(classes):
|
||||
return [klass.__name__ for klass in classes]
|
||||
|
||||
|
||||
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_no_eval_cli_text(self):
|
||||
app = MyApp()
|
||||
app.initialize(['--Foo.name=1'])
|
||||
app.init_foo()
|
||||
assert app.foo.name == '1'
|
||||
|
||||
def test_basic(self):
|
||||
app = MyApp()
|
||||
self.assertEqual(app.name, 'myapp')
|
||||
self.assertEqual(app.running, False)
|
||||
self.assertEqual(app.classes, [MyApp, Bar, Foo])
|
||||
self.assertEqual(app.config_file, '')
|
||||
|
||||
def test_mro_discovery(self):
|
||||
app = MyApp()
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits()),
|
||||
['Application', 'MyApp', 'Bar', 'Foo'])
|
||||
self.assertSequenceEqual(class_to_names(app._classes_inc_parents()),
|
||||
['Configurable', 'LoggingConfigurable', 'SingletonConfigurable',
|
||||
'Application', 'MyApp', 'Bar', 'Foo'])
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Application])),
|
||||
['Application'])
|
||||
self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Application])),
|
||||
['Configurable', 'LoggingConfigurable', 'SingletonConfigurable',
|
||||
'Application'])
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])),
|
||||
['Foo'])
|
||||
self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])),
|
||||
['Configurable', 'Bar'])
|
||||
|
||||
class MyApp2(Application): # no defined `classes` attr
|
||||
pass
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])),
|
||||
['Foo'])
|
||||
self.assertSequenceEqual(class_to_names(app._classes_inc_parents([Bar])),
|
||||
['Configurable', 'Bar'])
|
||||
|
||||
|
||||
def test_config(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line([
|
||||
"--i=10",
|
||||
"--Foo.j=10",
|
||||
"--enable=False",
|
||||
"--log-level=50",
|
||||
])
|
||||
config = app.config
|
||||
print(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_seq_args(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line("--li 1 --li 3 --la 1 --tb AB 2 --Foo.la=ab --Bar.aset S1 --Bar.aset S2 --Bar.aset S1".split())
|
||||
assert app.extra_args == ["2"]
|
||||
config = app.config
|
||||
assert config.Foo.li == [1, 3]
|
||||
assert config.Foo.la == ["1", "ab"]
|
||||
assert config.Bar.tb == ("AB",)
|
||||
self.assertEqual(config.Bar.aset, {"S1", "S2"})
|
||||
app.init_foo()
|
||||
assert app.foo.li == [1, 3]
|
||||
assert app.foo.la == ['1', 'ab']
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.aset, {'S1', 'S2'})
|
||||
assert app.bar.tb == ('AB',)
|
||||
|
||||
def test_config_dict_args(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(
|
||||
"--Foo.fdict a=1 --Foo.fdict b=b --Foo.fdict c=3 "
|
||||
"--Bar.bdict k=1 -D=a=b -D 22=33 "
|
||||
"--Bar.idict k=1 --Bar.idict b=2 --Bar.idict c=3 "
|
||||
.split())
|
||||
fdict = {'a': '1', 'b': 'b', 'c': '3'}
|
||||
bdict = {'k': '1', 'a': 'b', '22': '33'}
|
||||
idict = {'k': 1, 'b': 2, 'c': 3}
|
||||
config = app.config
|
||||
assert config.Bar.idict == idict
|
||||
self.assertDictEqual(config.Foo.fdict, fdict)
|
||||
self.assertDictEqual(config.Bar.bdict, bdict)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.fdict, fdict)
|
||||
app.init_bar()
|
||||
assert app.bar.idict == idict
|
||||
self.assertEqual(app.bar.bdict, bdict)
|
||||
|
||||
def test_config_propagation(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--i=10","--Foo.j=10","--enable=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', 'some help')}
|
||||
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_cli_allow_none(self):
|
||||
class App(Application):
|
||||
aliases = {"opt": "App.opt"}
|
||||
opt = Unicode(allow_none=True, config=True)
|
||||
|
||||
app = App()
|
||||
app.parse_command_line(["--opt=None"])
|
||||
assert app.opt is None
|
||||
|
||||
def test_flags(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--disable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["-d"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--enable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["-e"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
|
||||
def test_flags_help_msg(self):
|
||||
app = MyApp()
|
||||
stdout = io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
app.print_flag_help()
|
||||
hmsg = stdout.getvalue()
|
||||
self.assertRegex(hmsg, "(?<!-)-e, --enable\\b")
|
||||
self.assertRegex(hmsg, "(?<!-)-d, --disable\\b")
|
||||
self.assertIn("Equivalent to: [--Bar.enabled=True]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Bar.enabled=False]", hmsg)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--fooi=5", "--fooj=10"])
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.i, 5)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.j, 10)
|
||||
|
||||
def test_aliases_help_msg(self):
|
||||
app = MyApp()
|
||||
stdout = io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
app.print_alias_help()
|
||||
hmsg = stdout.getvalue()
|
||||
self.assertRegex(hmsg, "(?<!-)-i, --fooi\\b")
|
||||
self.assertRegex(hmsg, "(?<!-)-j, --fooj\\b")
|
||||
self.assertIn("Equivalent to: [--Foo.i]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Foo.j]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Foo.name]", hmsg)
|
||||
|
||||
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', 'args', "--disable"])
|
||||
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'])
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(
|
||||
["--disable", "--la", "-", "-", "--Bar.b=1", "--", "-", "extra"]
|
||||
)
|
||||
self.assertEqual(app.extra_args, ["-", "-", "extra"])
|
||||
|
||||
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 NotInConfig(HasTraits):
|
||||
from_hidden = Unicode('x', help="""From hidden class
|
||||
|
||||
Details about from_hidden.
|
||||
""").tag(config=True)
|
||||
|
||||
class NoTraits(Foo, Bar, NotInConfig):
|
||||
pass
|
||||
|
||||
app = MyApp()
|
||||
app.classes.append(NoTraits)
|
||||
|
||||
conf_txt = app.generate_config_file()
|
||||
print(conf_txt)
|
||||
self.assertIn('The integer b.', conf_txt)
|
||||
self.assertIn('# Foo(Configurable)', conf_txt)
|
||||
self.assertNotIn('# Configurable', conf_txt)
|
||||
self.assertIn('# NoTraits(Foo, Bar)', conf_txt)
|
||||
|
||||
# inherited traits, parent in class list:
|
||||
self.assertIn('# c.NoTraits.i', conf_txt)
|
||||
self.assertIn('# c.NoTraits.j', conf_txt)
|
||||
self.assertIn('# c.NoTraits.n', conf_txt)
|
||||
self.assertIn('# See also: Foo.j', conf_txt)
|
||||
self.assertIn('# See also: Bar.b', conf_txt)
|
||||
self.assertEqual(conf_txt.count('Details about i.'), 1)
|
||||
|
||||
# inherited traits, parent not in class list:
|
||||
self.assertIn("# c.NoTraits.from_hidden", conf_txt)
|
||||
self.assertNotIn('# See also: NotInConfig.', conf_txt)
|
||||
self.assertEqual(conf_txt.count('Details about from_hidden.'), 1)
|
||||
self.assertNotIn("NotInConfig", 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_subcommands_instanciation(self):
|
||||
"""Try all ways to specify how to create sub-apps."""
|
||||
app = Root.instance()
|
||||
app.parse_command_line(['sub1'])
|
||||
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
## Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
|
||||
Root.clear_instance()
|
||||
Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails.
|
||||
app = Root.instance()
|
||||
|
||||
app.parse_command_line(['sub1', 'sub2'])
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
self.assertIsInstance(app.subapp.subapp, Sub2)
|
||||
## Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
self.assertIs(app.subapp.subapp.parent, app.subapp)
|
||||
|
||||
Root.clear_instance()
|
||||
Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails.
|
||||
app = Root.instance()
|
||||
|
||||
app.parse_command_line(['sub1', 'sub3'])
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
self.assertIsInstance(app.subapp.subapp, Sub3)
|
||||
self.assertTrue(app.subapp.subapp.flag) # Set by factory.
|
||||
## Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
self.assertIs(app.subapp.subapp.parent, app.subapp) # Set by factory.
|
||||
|
||||
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.assertEqual(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)
|
||||
|
||||
|
||||
def test_cli_multi_scalar(caplog):
|
||||
class App(Application):
|
||||
aliases = {"opt": "App.opt"}
|
||||
opt = Unicode(config=True)
|
||||
|
||||
app = App(log=logging.getLogger())
|
||||
with pytest.raises(SystemExit):
|
||||
app.parse_command_line(["--opt", "1", "--opt", "2"])
|
||||
record = caplog.get_records("call")[-1]
|
||||
message = record.message
|
||||
|
||||
assert "Error loading argument" in message
|
||||
assert "App.opt=['1', '2']" in message
|
||||
assert "opt only accepts one value" in message
|
||||
assert record.levelno == logging.CRITICAL
|
||||
|
||||
|
||||
class Root(Application):
|
||||
subcommands = {
|
||||
'sub1': ('traitlets.config.tests.test_application.Sub1', 'import string'),
|
||||
}
|
||||
|
||||
|
||||
class Sub3(Application):
|
||||
flag = Bool(False)
|
||||
|
||||
|
||||
class Sub2(Application):
|
||||
pass
|
||||
|
||||
|
||||
class Sub1(Application):
|
||||
subcommands = {
|
||||
'sub2': (Sub2, 'Application class'),
|
||||
'sub3': (lambda root: Sub3(parent=root, flag=True), 'factory'),
|
||||
}
|
||||
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
def test_help_all_output():
|
||||
check_help_all_output(__name__)
|
||||
|
||||
|
||||
def test_show_config_cli():
|
||||
out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config'])
|
||||
assert ec == 0
|
||||
assert 'show_config' not in out
|
||||
|
||||
|
||||
def test_show_config_json_cli():
|
||||
out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config-json'])
|
||||
assert ec == 0
|
||||
assert 'show_config' not in out
|
||||
|
||||
|
||||
def test_show_config(capsys):
|
||||
cfg = Config()
|
||||
cfg.MyApp.i = 5
|
||||
# don't show empty
|
||||
cfg.OtherApp
|
||||
|
||||
app = MyApp(config=cfg, show_config=True)
|
||||
app.start()
|
||||
out, err = capsys.readouterr()
|
||||
assert 'MyApp' in out
|
||||
assert 'i = 5' in out
|
||||
assert 'OtherApp' not in out
|
||||
|
||||
|
||||
def test_show_config_json(capsys):
|
||||
cfg = Config()
|
||||
cfg.MyApp.i = 5
|
||||
cfg.OtherApp
|
||||
|
||||
app = MyApp(config=cfg, show_config_json=True)
|
||||
app.start()
|
||||
out, err = capsys.readouterr()
|
||||
displayed = json.loads(out)
|
||||
assert Config(displayed) == cfg
|
||||
|
||||
|
||||
def test_deep_alias():
|
||||
from traitlets.config import Application, Configurable
|
||||
from traitlets import Int
|
||||
|
||||
class Foo(Configurable):
|
||||
val = Int(default_value=5).tag(config=True)
|
||||
|
||||
class Bar(Configurable):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.foo = Foo(parent=self)
|
||||
|
||||
class TestApp(Application):
|
||||
name = 'test'
|
||||
|
||||
aliases = {'val': 'Bar.Foo.val'}
|
||||
classes = [Foo, Bar]
|
||||
|
||||
def initialize(self, *args, **kwargs):
|
||||
super().initialize(*args, **kwargs)
|
||||
self.bar = Bar(parent=self)
|
||||
|
||||
app = TestApp()
|
||||
app.initialize(['--val=10'])
|
||||
assert app.bar.foo.val == 10
|
||||
assert len(list(app.emit_alias_help())) > 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# for test_help_output:
|
||||
MyApp.launch_instance()
|
||||
|
|
@ -0,0 +1,680 @@
|
|||
"""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.application import Application
|
||||
from traitlets.config.configurable import (
|
||||
Configurable,
|
||||
LoggingConfigurable,
|
||||
SingletonConfigurable,
|
||||
)
|
||||
from traitlets.log import get_logger
|
||||
from traitlets.traitlets import (
|
||||
Integer, Float, Unicode, List, Dict, Set, Enum, FuzzyEnum,
|
||||
CaselessStrEnum, _deprecations_shown, validate,
|
||||
)
|
||||
|
||||
from traitlets.config.loader import Config
|
||||
|
||||
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 = """MyConfigurable(Configurable) options
|
||||
------------------------------------
|
||||
--MyConfigurable.a=<Integer>
|
||||
The integer a.
|
||||
Default: 1
|
||||
--MyConfigurable.b=<Float>
|
||||
The integer b.
|
||||
Default: 1.0"""
|
||||
|
||||
mc_help_inst="""MyConfigurable(Configurable) options
|
||||
------------------------------------
|
||||
--MyConfigurable.a=<Integer>
|
||||
The integer a.
|
||||
Current: 5
|
||||
--MyConfigurable.b=<Float>
|
||||
The integer b.
|
||||
Current: 4.0"""
|
||||
|
||||
# On Python 3, the Integer trait is a synonym for Int
|
||||
mc_help = mc_help.replace("<Integer>", "<Int>")
|
||||
mc_help_inst = mc_help_inst.replace("<Integer>", "<Int>")
|
||||
|
||||
class Foo(Configurable):
|
||||
a = Integer(0, help="The integer a.").tag(config=True)
|
||||
b = Unicode('nope').tag(config=True)
|
||||
flist = List([]).tag(config=True)
|
||||
fdict = Dict().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)
|
||||
bset = Set([]).tag(config=True, multiplicity='+')
|
||||
bset_values = Set([2,1,5]).tag(config=True, multiplicity='+')
|
||||
bdict = Dict().tag(config=True, multiplicity='+')
|
||||
bdict_values = Dict({1:'a','0':'b',5:'c'}).tag(config=True, multiplicity='+')
|
||||
|
||||
foo_help="""Foo(Configurable) options
|
||||
-------------------------
|
||||
--Foo.a=<Int>
|
||||
The integer a.
|
||||
Default: 0
|
||||
--Foo.b=<Unicode>
|
||||
Default: 'nope'
|
||||
--Foo.fdict=<key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Foo.flist=<list-item-1>...
|
||||
Default: []"""
|
||||
|
||||
bar_help="""Bar(Foo) options
|
||||
----------------
|
||||
--Bar.a=<Int>
|
||||
The integer a.
|
||||
Default: 0
|
||||
--Bar.bdict <key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Bar.bdict_values <key-1>=<value-1>...
|
||||
Default: {1: 'a', '0': 'b', 5: 'c'}
|
||||
--Bar.bset <set-item-1>...
|
||||
Default: set()
|
||||
--Bar.bset_values <set-item-1>...
|
||||
Default: {1, 2, 5}
|
||||
--Bar.c=<Float>
|
||||
The string c.
|
||||
Default: 0.0
|
||||
--Bar.fdict=<key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Bar.flist=<list-item-1>...
|
||||
Default: []"""
|
||||
|
||||
|
||||
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)
|
||||
self.assertEqual(Foo.class_get_help(), foo_help)
|
||||
self.assertEqual(Bar.class_get_help(), bar_help)
|
||||
|
||||
def test_help_inst(self):
|
||||
inst = MyConfigurable(a=5, b=4)
|
||||
self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
|
||||
|
||||
def test_generated_config_enum_comments(self):
|
||||
class MyConf(Configurable):
|
||||
an_enum = Enum('Choice1 choice2'.split(),
|
||||
help="Many choices.").tag(config=True)
|
||||
|
||||
help_str = "Many choices."
|
||||
enum_choices_str = "Choices: any of ['Choice1', 'choice2']"
|
||||
rst_choices_str = "MyConf.an_enum : any of ``'Choice1'``|``'choice2'``"
|
||||
or_none_str = "or None"
|
||||
|
||||
cls_help = MyConf.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls_help)
|
||||
self.assertIn(enum_choices_str, cls_help)
|
||||
self.assertNotIn(or_none_str, cls_help)
|
||||
|
||||
cls_cfg = MyConf.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls_cfg)
|
||||
self.assertIn(enum_choices_str, cls_cfg)
|
||||
self.assertNotIn(or_none_str, cls_help)
|
||||
## Check order of Help-msg <--> Choices sections
|
||||
self.assertGreater(cls_cfg.index(enum_choices_str),
|
||||
cls_cfg.index(help_str))
|
||||
|
||||
rst_help = MyConf.class_config_rst_doc()
|
||||
|
||||
self.assertIn(help_str, rst_help)
|
||||
self.assertIn(rst_choices_str, rst_help)
|
||||
self.assertNotIn(or_none_str, rst_help)
|
||||
|
||||
class MyConf2(Configurable):
|
||||
an_enum = Enum('Choice1 choice2'.split(),
|
||||
allow_none=True,
|
||||
default_value='choice2',
|
||||
help="Many choices.").tag(config=True)
|
||||
|
||||
defaults_str = "Default: 'choice2'"
|
||||
|
||||
cls2_msg = MyConf2.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls2_msg)
|
||||
self.assertIn(enum_choices_str, cls2_msg)
|
||||
self.assertIn(or_none_str, cls2_msg)
|
||||
self.assertIn(defaults_str, cls2_msg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls2_msg.index(defaults_str),
|
||||
cls2_msg.index(enum_choices_str))
|
||||
|
||||
cls2_cfg = MyConf2.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls2_cfg)
|
||||
self.assertIn(enum_choices_str, cls2_cfg)
|
||||
self.assertIn(or_none_str, cls2_cfg)
|
||||
self.assertIn(defaults_str, cls2_cfg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls2_cfg.index(defaults_str),
|
||||
cls2_cfg.index(enum_choices_str))
|
||||
|
||||
def test_generated_config_strenum_comments(self):
|
||||
help_str = "Many choices."
|
||||
defaults_str = "Default: 'choice2'"
|
||||
or_none_str = "or None"
|
||||
|
||||
class MyConf3(Configurable):
|
||||
an_enum = CaselessStrEnum('Choice1 choice2'.split(),
|
||||
allow_none=True,
|
||||
default_value='choice2',
|
||||
help="Many choices.").tag(config=True)
|
||||
|
||||
enum_choices_str = ("Choices: any of ['Choice1', 'choice2'] "
|
||||
"(case-insensitive)")
|
||||
|
||||
cls3_msg = MyConf3.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls3_msg)
|
||||
self.assertIn(enum_choices_str, cls3_msg)
|
||||
self.assertIn(or_none_str, cls3_msg)
|
||||
self.assertIn(defaults_str, cls3_msg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls3_msg.index(defaults_str),
|
||||
cls3_msg.index(enum_choices_str))
|
||||
|
||||
cls3_cfg = MyConf3.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls3_cfg)
|
||||
self.assertIn(enum_choices_str, cls3_cfg)
|
||||
self.assertIn(or_none_str, cls3_cfg)
|
||||
self.assertIn(defaults_str, cls3_cfg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls3_cfg.index(defaults_str),
|
||||
cls3_cfg.index(enum_choices_str))
|
||||
|
||||
class MyConf4(Configurable):
|
||||
an_enum = FuzzyEnum('Choice1 choice2'.split(),
|
||||
allow_none=True,
|
||||
default_value='choice2',
|
||||
help="Many choices.").tag(config=True)
|
||||
|
||||
enum_choices_str = ("Choices: any case-insensitive prefix "
|
||||
"of ['Choice1', 'choice2']")
|
||||
|
||||
cls4_msg = MyConf4.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls4_msg)
|
||||
self.assertIn(enum_choices_str, cls4_msg)
|
||||
self.assertIn(or_none_str, cls4_msg)
|
||||
self.assertIn(defaults_str, cls4_msg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls4_msg.index(defaults_str),
|
||||
cls4_msg.index(enum_choices_str))
|
||||
|
||||
cls4_cfg = MyConf4.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls4_cfg)
|
||||
self.assertIn(enum_choices_str, cls4_cfg)
|
||||
self.assertIn(or_none_str, cls4_cfg)
|
||||
self.assertIn(defaults_str, cls4_cfg)
|
||||
## Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls4_cfg.index(defaults_str),
|
||||
cls4_cfg.index(enum_choices_str))
|
||||
|
||||
|
||||
|
||||
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()
|
||||
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 TestLoggingConfigurable(TestCase):
|
||||
|
||||
def test_parent_logger(self):
|
||||
class Parent(LoggingConfigurable): pass
|
||||
class Child(LoggingConfigurable): pass
|
||||
log = get_logger().getChild("TestLoggingConfigurable")
|
||||
|
||||
parent = Parent(log=log)
|
||||
child = Child(parent=parent)
|
||||
self.assertEqual(parent.log, log)
|
||||
self.assertEqual(child.log, log)
|
||||
|
||||
parent = Parent()
|
||||
child = Child(parent=parent, log=log)
|
||||
self.assertEqual(parent.log, get_logger())
|
||||
self.assertEqual(child.log, log)
|
||||
|
||||
def test_parent_not_logging_configurable(self):
|
||||
class Parent(Configurable): pass
|
||||
class Child(LoggingConfigurable): pass
|
||||
parent = Parent()
|
||||
child = Child(parent=parent)
|
||||
self.assertEqual(child.log, get_logger())
|
||||
|
||||
|
||||
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,
|
||||
},
|
||||
'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)
|
||||
|
||||
def test_kwarg_config_priority(self):
|
||||
# a, c set in kwargs
|
||||
# a, b set in config
|
||||
# verify that:
|
||||
# - kwargs are set before config
|
||||
# - kwargs have priority over config
|
||||
class A(Configurable):
|
||||
a = Unicode('default', config=True)
|
||||
b = Unicode('default', config=True)
|
||||
c = Unicode('default', config=True)
|
||||
c_during_config = Unicode('never')
|
||||
@validate('b')
|
||||
def _record_c(self, proposal):
|
||||
# setting b from config records c's value at the time
|
||||
self.c_during_config = self.c
|
||||
return proposal.value
|
||||
|
||||
cfg = Config()
|
||||
cfg.A.a = 'a-config'
|
||||
cfg.A.b = 'b-config'
|
||||
obj = A(a='a-kwarg', c='c-kwarg', config=cfg)
|
||||
assert obj.a == 'a-kwarg'
|
||||
assert obj.b == 'b-config'
|
||||
assert obj.c == 'c-kwarg'
|
||||
assert obj.c_during_config == 'c-kwarg'
|
||||
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
|
||||
def test_logger_adapter(self):
|
||||
logger = logging.getLogger("test_logger_adapter")
|
||||
adapter = logging.LoggerAdapter(logger, {"key": "adapted"})
|
||||
|
||||
with self.assertLogs(logger, logging.INFO) as captured:
|
||||
app = Application(log=adapter, log_level=logging.INFO)
|
||||
app.log_format = "%(key)s %(message)s"
|
||||
app.log.info("test message")
|
||||
|
||||
output = "\n".join(captured.output)
|
||||
assert "adapted test message" in output
|
||||
|
|
@ -0,0 +1,754 @@
|
|||
# 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 os
|
||||
import pickle
|
||||
from itertools import chain
|
||||
from tempfile import mkstemp
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
|
||||
from traitlets.config.loader import (
|
||||
Config,
|
||||
LazyConfigValue,
|
||||
PyFileConfigLoader,
|
||||
JSONFileConfigLoader,
|
||||
KeyValueConfigLoader,
|
||||
ArgParseConfigLoader,
|
||||
KVArgParseConfigLoader,
|
||||
)
|
||||
from traitlets import (
|
||||
List,
|
||||
Tuple,
|
||||
Dict,
|
||||
Unicode,
|
||||
Integer,
|
||||
)
|
||||
from traitlets.config import Configurable
|
||||
|
||||
|
||||
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', prefix='μnïcø∂e')
|
||||
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', prefix='μnïcø∂e')
|
||||
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', prefix='μnïcø∂e')
|
||||
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', prefix='μnïcø∂e')
|
||||
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', prefix='μnïcø∂e')
|
||||
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()
|
||||
|
||||
|
||||
def _parse_int_or_str(v):
|
||||
try:
|
||||
return int(v)
|
||||
except:
|
||||
return str(v)
|
||||
|
||||
|
||||
class MyLoader1(ArgParseConfigLoader):
|
||||
def _add_arguments(self, aliases=None, flags=None, classes=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)
|
||||
p.add_argument('--list1', action='append', type=_parse_int_or_str)
|
||||
p.add_argument('--list2', nargs='+', type=int)
|
||||
|
||||
|
||||
class MyLoader2(ArgParseConfigLoader):
|
||||
def _add_arguments(self, aliases=None, flags=None, classes=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')
|
||||
|
||||
def test_list_args(self):
|
||||
cl = MyLoader1()
|
||||
config = cl.load_config('--list1 1 wow --list2 1 2 3 --list1 B'.split())
|
||||
self.assertEqual(list(config.Global.keys()), ['bam'])
|
||||
self.assertEqual(config.Global.bam, 'wow')
|
||||
self.assertEqual(config.list1, [1, 'B'])
|
||||
self.assertEqual(config.list2, [1, 2, 3])
|
||||
|
||||
|
||||
class C(Configurable):
|
||||
str_trait = Unicode(config=True)
|
||||
int_trait = Integer(config=True)
|
||||
list_trait = List(config=True)
|
||||
list_of_ints = List(Integer(), config=True)
|
||||
dict_trait = Dict(config=True)
|
||||
dict_of_ints = Dict(
|
||||
key_trait=Integer(),
|
||||
value_trait=Integer(),
|
||||
config=True,
|
||||
)
|
||||
dict_multi = Dict(
|
||||
key_trait=Unicode(),
|
||||
per_key_traits={
|
||||
"int": Integer(),
|
||||
"str": Unicode(),
|
||||
},
|
||||
config=True,
|
||||
)
|
||||
|
||||
|
||||
class TestKeyValueCL(TestCase):
|
||||
klass = KeyValueConfigLoader
|
||||
|
||||
def test_eval(self):
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config('--C.str_trait=all --C.int_trait=5 --C.list_trait=["hello",5]'.split())
|
||||
c = C(config=config)
|
||||
assert c.str_trait == 'all'
|
||||
assert c.int_trait == 5
|
||||
assert c.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.') ]
|
||||
config = cl.load_config(argv)
|
||||
assert config.a == '10'
|
||||
assert config.b == '20'
|
||||
assert config.Foo.Bar.value == '10'
|
||||
# non-literal expressions are not evaluated
|
||||
self.assertEqual(config.Foo.Bam.value, 'list(range(10))')
|
||||
self.assertEqual(Unicode().from_string(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)
|
||||
u = Unicode()
|
||||
self.assertEqual(u.from_string(config.a), os.path.expanduser('~/1/2/3'))
|
||||
self.assertEqual(u.from_string(config.b), os.path.expanduser('~'))
|
||||
self.assertEqual(u.from_string(config.c), os.path.expanduser('~/'))
|
||||
self.assertEqual(u.from_string(config.d), '~/')
|
||||
|
||||
def test_extra_args(self):
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config(['--a=5', 'b', 'd', '--c=10'])
|
||||
self.assertEqual(cl.extra_args, ['b', 'd'])
|
||||
assert config.a == '5'
|
||||
assert config.c == '10'
|
||||
config = cl.load_config(['--', '--a=5', '--c=10'])
|
||||
self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
|
||||
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config(['extra', '--a=2', '--c=1', '--', '-'])
|
||||
self.assertEqual(cl.extra_args, ['extra', '-'])
|
||||
|
||||
def test_unicode_args(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ['--a=épsîlön']
|
||||
config = cl.load_config(argv)
|
||||
print(config, cl.extra_args)
|
||||
self.assertEqual(config.a, 'épsîlön')
|
||||
|
||||
def test_list_append(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.list_trait", "x", "--C.list_trait", "y"]
|
||||
config = cl.load_config(argv)
|
||||
assert config.C.list_trait == ["x", "y"]
|
||||
c = C(config=config)
|
||||
assert c.list_trait == ["x", "y"]
|
||||
|
||||
def test_list_single_item(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.list_trait", "x"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.list_trait == ["x"]
|
||||
|
||||
def test_dict(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.dict_trait", "x=5", "--C.dict_trait", "y=10"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.dict_trait == {"x": "5", "y": "10"}
|
||||
|
||||
def test_dict_key_traits(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.dict_of_ints", "1=2", "--C.dict_of_ints", "3=4"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.dict_of_ints == {1: 2, 3: 4}
|
||||
|
||||
|
||||
class CBase(Configurable):
|
||||
a = List().tag(config=True)
|
||||
b = List(Integer()).tag(config=True, multiplicity='*')
|
||||
c = List().tag(config=True, multiplicity='append')
|
||||
adict = Dict().tag(config=True)
|
||||
|
||||
|
||||
class CSub(CBase):
|
||||
d = Tuple().tag(config=True)
|
||||
e = Tuple().tag(config=True, multiplicity='+')
|
||||
bdict = Dict().tag(config=True, multiplicity='*')
|
||||
|
||||
|
||||
class TestArgParseKVCL(TestKeyValueCL):
|
||||
klass = KVArgParseConfigLoader
|
||||
|
||||
def test_no_cast_literals(self):
|
||||
cl = self.klass(log=log)
|
||||
# test ipython -c 1 doesn't cast to int
|
||||
argv = ["-c", "1"]
|
||||
config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
|
||||
assert config.IPython.command_to_run == "1"
|
||||
|
||||
def test_int_literals(self):
|
||||
cl = self.klass(log=log)
|
||||
# test ipython -c 1 doesn't cast to int
|
||||
argv = ["-c", "1"]
|
||||
config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
|
||||
assert config.IPython.command_to_run == "1"
|
||||
|
||||
def test_unicode_alias(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ['--a=épsîlön']
|
||||
config = cl.load_config(argv, aliases=dict(a='A.a'))
|
||||
print(dict(config))
|
||||
print(cl.extra_args)
|
||||
print(cl.aliases)
|
||||
self.assertEqual(config.A.a, 'épsîlön')
|
||||
|
||||
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'))
|
||||
|
||||
class A(Configurable):
|
||||
a = Unicode(config=True)
|
||||
b = Unicode(config=True)
|
||||
|
||||
a = A(config=config)
|
||||
self.assertEqual(a.a, os.path.expanduser('~/1/2/3'))
|
||||
self.assertEqual(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, "a=5")
|
||||
|
||||
def test_seq_traits(self):
|
||||
cl = self.klass(log=log, classes=(CBase, CSub))
|
||||
aliases = {'a3': 'CBase.c', 'a5': 'CSub.e'}
|
||||
argv = ("--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB "
|
||||
"--CSub.d 1 --CSub.d BBB --CSub.e 1 --CSub.e=bcd a b c ").split()
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
assert cl.extra_args == ["a", "b", "c"]
|
||||
assert config.CBase.a == ['A', '2']
|
||||
assert config.CBase.b == [1, 3]
|
||||
self.assertEqual(config.CBase.c, ['AA', 'BB'])
|
||||
|
||||
assert config.CSub.d == ('1', 'BBB')
|
||||
assert config.CSub.e == ('1', 'bcd')
|
||||
|
||||
def test_seq_traits_single_empty_string(self):
|
||||
cl = self.klass(log=log, classes=(CBase, ))
|
||||
aliases = {'seqopt': 'CBase.c'}
|
||||
argv = ['--seqopt', '']
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
self.assertEqual(config.CBase.c, [''])
|
||||
|
||||
def test_dict_traits(self):
|
||||
cl = self.klass(log=log, classes=(CBase, CSub))
|
||||
aliases = {'D': 'CBase.adict', 'E': 'CSub.bdict'}
|
||||
argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"]
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
c = CSub(config=config)
|
||||
assert c.adict == {'k1': 'v1', 'k2': '2', 'k3': 'v 3'}
|
||||
assert c.bdict == {'k': 'v', '22': '222'}
|
||||
|
||||
def test_mixed_seq_positional(self):
|
||||
aliases = {"c": "Class.trait"}
|
||||
cl = self.klass(log=log, aliases=aliases)
|
||||
assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")]
|
||||
positionals = ["a", "b", "c"]
|
||||
# test with positionals at any index
|
||||
for idx in range(len(assignments) + 1):
|
||||
argv_parts = assignments[:]
|
||||
argv_parts[idx:idx] = (positionals,)
|
||||
argv = list(chain(*argv_parts))
|
||||
|
||||
config = cl.load_config(argv)
|
||||
assert config.Class.trait == ["1", "2", "3", "4"]
|
||||
assert cl.extra_args == ["a", "b", "c"]
|
||||
|
||||
def test_split_positional(self):
|
||||
"""Splitting positionals across flags is no longer allowed in traitlets 5"""
|
||||
cl = self.klass(log=log)
|
||||
argv = ["a", "--Class.trait=5", "b"]
|
||||
with pytest.raises(SystemExit):
|
||||
cl.load_config(argv)
|
||||
|
||||
|
||||
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_lazy_config_repr(self):
|
||||
cfg = Config()
|
||||
cfg.Class.lazy.append(1)
|
||||
cfg_repr = repr(cfg)
|
||||
assert '<LazyConfigValue' in cfg_repr
|
||||
assert "extend" in cfg_repr
|
||||
assert " [1]}>" in cfg_repr
|
||||
assert 'value=' not in cfg_repr
|
||||
cfg.Class.lazy.get_value([0])
|
||||
repr2 = repr(cfg)
|
||||
assert repr([0,1]) in repr2
|
||||
assert 'value=' in repr2
|
||||
|
||||
|
||||
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])
|
||||
|
||||
|
||||
def test_merge_multi_lazy(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
If systemwide overwirte and user append, we want both in the right
|
||||
order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = [1]
|
||||
c2.Foo.trait.append(2)
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, [1,2] )
|
||||
|
||||
|
||||
|
||||
def test_merge_multi_lazyII(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
If both are lazy we still want a lazy config.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.append(1)
|
||||
c2.Foo.trait.append(2)
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._extend, [1,2] )
|
||||
|
||||
def test_merge_multi_lazy_III(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Prepend should prepend in the right order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = [1]
|
||||
c2.Foo.trait.prepend([0])
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, [0, 1] )
|
||||
|
||||
def test_merge_multi_lazy_IV(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Both prepending should be lazy
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.prepend([1])
|
||||
c2.Foo.trait.prepend([0])
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._prepend, [0, 1])
|
||||
|
||||
def test_merge_multi_lazy_update_I(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
dict update shoudl be in the right order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = {"a": 1, "z": 26}
|
||||
c2.Foo.trait.update({"a": 0, "b": 1})
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, {"a": 0, "b": 1, "z": 26})
|
||||
|
||||
def test_merge_multi_lazy_update_II(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Later dict overwrite lazyness
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.update({"a": 0, "b": 1})
|
||||
c2.Foo.trait = {"a": 1, "z": 26}
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, {"a": 1, "z": 26})
|
||||
|
||||
def test_merge_multi_lazy_update_III(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Later dict overwrite lazyness
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.update({"a": 0, "b": 1})
|
||||
c2.Foo.trait.update({"a": 1, "z": 26})
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1})
|
||||
Loading…
Add table
Add a link
Reference in a new issue