# 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, "(? 0 if __name__ == '__main__': # for test_help_output: MyApp.launch_instance()