test_configurable.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. """Tests for traitlets.config.configurable"""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. import logging
  5. from unittest import TestCase
  6. from pytest import mark
  7. from .._warnings import expected_warnings
  8. from traitlets.config.application import Application
  9. from traitlets.config.configurable import Configurable, LoggingConfigurable, SingletonConfigurable
  10. from traitlets.config.loader import Config
  11. from traitlets.log import get_logger
  12. from traitlets.traitlets import (
  13. CaselessStrEnum,
  14. Dict,
  15. Enum,
  16. Float,
  17. FuzzyEnum,
  18. Integer,
  19. List,
  20. Set,
  21. Unicode,
  22. validate,
  23. )
  24. from traitlets.utils.warnings import _deprecations_shown
  25. class MyConfigurable(Configurable):
  26. a = Integer(1, help="The integer a.").tag(config=True)
  27. b = Float(1.0, help="The integer b.").tag(config=True)
  28. c = Unicode("no config")
  29. mc_help = """MyConfigurable(Configurable) options
  30. ------------------------------------
  31. --MyConfigurable.a=<Integer>
  32. The integer a.
  33. Default: 1
  34. --MyConfigurable.b=<Float>
  35. The integer b.
  36. Default: 1.0"""
  37. mc_help_inst = """MyConfigurable(Configurable) options
  38. ------------------------------------
  39. --MyConfigurable.a=<Integer>
  40. The integer a.
  41. Current: 5
  42. --MyConfigurable.b=<Float>
  43. The integer b.
  44. Current: 4.0"""
  45. # On Python 3, the Integer trait is a synonym for Int
  46. mc_help = mc_help.replace("<Integer>", "<Int>")
  47. mc_help_inst = mc_help_inst.replace("<Integer>", "<Int>")
  48. class Foo(Configurable):
  49. a = Integer(0, help="The integer a.").tag(config=True)
  50. b = Unicode("nope").tag(config=True)
  51. flist = List([]).tag(config=True)
  52. fdict = Dict().tag(config=True)
  53. class Bar(Foo):
  54. b = Unicode("gotit", help="The string b.").tag(config=False)
  55. c = Float(help="The string c.").tag(config=True)
  56. bset = Set([]).tag(config=True, multiplicity="+")
  57. bset_values = Set([2, 1, 5]).tag(config=True, multiplicity="+")
  58. bdict = Dict().tag(config=True, multiplicity="+")
  59. bdict_values = Dict({1: "a", "0": "b", 5: "c"}).tag(config=True, multiplicity="+")
  60. foo_help = """Foo(Configurable) options
  61. -------------------------
  62. --Foo.a=<Int>
  63. The integer a.
  64. Default: 0
  65. --Foo.b=<Unicode>
  66. Default: 'nope'
  67. --Foo.fdict=<key-1>=<value-1>...
  68. Default: {}
  69. --Foo.flist=<list-item-1>...
  70. Default: []"""
  71. bar_help = """Bar(Foo) options
  72. ----------------
  73. --Bar.a=<Int>
  74. The integer a.
  75. Default: 0
  76. --Bar.bdict <key-1>=<value-1>...
  77. Default: {}
  78. --Bar.bdict_values <key-1>=<value-1>...
  79. Default: {1: 'a', '0': 'b', 5: 'c'}
  80. --Bar.bset <set-item-1>...
  81. Default: set()
  82. --Bar.bset_values <set-item-1>...
  83. Default: {1, 2, 5}
  84. --Bar.c=<Float>
  85. The string c.
  86. Default: 0.0
  87. --Bar.fdict=<key-1>=<value-1>...
  88. Default: {}
  89. --Bar.flist=<list-item-1>...
  90. Default: []"""
  91. class TestConfigurable(TestCase):
  92. def test_default(self):
  93. c1 = Configurable()
  94. c2 = Configurable(config=c1.config)
  95. c3 = Configurable(config=c2.config)
  96. self.assertEqual(c1.config, c2.config)
  97. self.assertEqual(c2.config, c3.config)
  98. def test_custom(self):
  99. config = Config()
  100. config.foo = "foo"
  101. config.bar = "bar"
  102. c1 = Configurable(config=config)
  103. c2 = Configurable(config=c1.config)
  104. c3 = Configurable(config=c2.config)
  105. self.assertEqual(c1.config, config)
  106. self.assertEqual(c2.config, config)
  107. self.assertEqual(c3.config, config)
  108. # Test that copies are not made
  109. self.assertTrue(c1.config is config)
  110. self.assertTrue(c2.config is config)
  111. self.assertTrue(c3.config is config)
  112. self.assertTrue(c1.config is c2.config)
  113. self.assertTrue(c2.config is c3.config)
  114. def test_inheritance(self):
  115. config = Config()
  116. config.MyConfigurable.a = 2
  117. config.MyConfigurable.b = 2.0
  118. c1 = MyConfigurable(config=config)
  119. c2 = MyConfigurable(config=c1.config)
  120. self.assertEqual(c1.a, config.MyConfigurable.a)
  121. self.assertEqual(c1.b, config.MyConfigurable.b)
  122. self.assertEqual(c2.a, config.MyConfigurable.a)
  123. self.assertEqual(c2.b, config.MyConfigurable.b)
  124. def test_parent(self):
  125. config = Config()
  126. config.Foo.a = 10
  127. config.Foo.b = "wow"
  128. config.Bar.b = "later"
  129. config.Bar.c = 100.0
  130. f = Foo(config=config)
  131. with expected_warnings(["`b` not recognized"]):
  132. b = Bar(config=f.config)
  133. self.assertEqual(f.a, 10)
  134. self.assertEqual(f.b, "wow")
  135. self.assertEqual(b.b, "gotit")
  136. self.assertEqual(b.c, 100.0)
  137. def test_override1(self):
  138. config = Config()
  139. config.MyConfigurable.a = 2
  140. config.MyConfigurable.b = 2.0
  141. c = MyConfigurable(a=3, config=config)
  142. self.assertEqual(c.a, 3)
  143. self.assertEqual(c.b, config.MyConfigurable.b)
  144. self.assertEqual(c.c, "no config")
  145. def test_override2(self):
  146. config = Config()
  147. config.Foo.a = 1
  148. config.Bar.b = "or" # Up above b is config=False, so this won't do it.
  149. config.Bar.c = 10.0
  150. with expected_warnings(["`b` not recognized"]):
  151. c = Bar(config=config)
  152. self.assertEqual(c.a, config.Foo.a)
  153. self.assertEqual(c.b, "gotit")
  154. self.assertEqual(c.c, config.Bar.c)
  155. with expected_warnings(["`b` not recognized"]):
  156. c = Bar(a=2, b="and", c=20.0, config=config)
  157. self.assertEqual(c.a, 2)
  158. self.assertEqual(c.b, "and")
  159. self.assertEqual(c.c, 20.0)
  160. def test_help(self):
  161. self.assertEqual(MyConfigurable.class_get_help(), mc_help)
  162. self.assertEqual(Foo.class_get_help(), foo_help)
  163. self.assertEqual(Bar.class_get_help(), bar_help)
  164. def test_help_inst(self):
  165. inst = MyConfigurable(a=5, b=4)
  166. self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
  167. def test_generated_config_enum_comments(self):
  168. class MyConf(Configurable):
  169. an_enum = Enum("Choice1 choice2".split(), help="Many choices.").tag(config=True)
  170. help_str = "Many choices."
  171. enum_choices_str = "Choices: any of ['Choice1', 'choice2']"
  172. rst_choices_str = "MyConf.an_enum : any of ``'Choice1'``|``'choice2'``"
  173. or_none_str = "or None"
  174. cls_help = MyConf.class_get_help()
  175. self.assertIn(help_str, cls_help)
  176. self.assertIn(enum_choices_str, cls_help)
  177. self.assertNotIn(or_none_str, cls_help)
  178. cls_cfg = MyConf.class_config_section()
  179. self.assertIn(help_str, cls_cfg)
  180. self.assertIn(enum_choices_str, cls_cfg)
  181. self.assertNotIn(or_none_str, cls_help)
  182. # Check order of Help-msg <--> Choices sections
  183. self.assertGreater(cls_cfg.index(enum_choices_str), cls_cfg.index(help_str))
  184. rst_help = MyConf.class_config_rst_doc()
  185. self.assertIn(help_str, rst_help)
  186. self.assertIn(rst_choices_str, rst_help)
  187. self.assertNotIn(or_none_str, rst_help)
  188. class MyConf2(Configurable):
  189. an_enum = Enum(
  190. "Choice1 choice2".split(),
  191. allow_none=True,
  192. default_value="choice2",
  193. help="Many choices.",
  194. ).tag(config=True)
  195. defaults_str = "Default: 'choice2'"
  196. cls2_msg = MyConf2.class_get_help()
  197. self.assertIn(help_str, cls2_msg)
  198. self.assertIn(enum_choices_str, cls2_msg)
  199. self.assertIn(or_none_str, cls2_msg)
  200. self.assertIn(defaults_str, cls2_msg)
  201. # Check order of Default <--> Choices sections
  202. self.assertGreater(cls2_msg.index(defaults_str), cls2_msg.index(enum_choices_str))
  203. cls2_cfg = MyConf2.class_config_section()
  204. self.assertIn(help_str, cls2_cfg)
  205. self.assertIn(enum_choices_str, cls2_cfg)
  206. self.assertIn(or_none_str, cls2_cfg)
  207. self.assertIn(defaults_str, cls2_cfg)
  208. # Check order of Default <--> Choices sections
  209. self.assertGreater(cls2_cfg.index(defaults_str), cls2_cfg.index(enum_choices_str))
  210. def test_generated_config_strenum_comments(self):
  211. help_str = "Many choices."
  212. defaults_str = "Default: 'choice2'"
  213. or_none_str = "or None"
  214. class MyConf3(Configurable):
  215. an_enum = CaselessStrEnum(
  216. "Choice1 choice2".split(),
  217. allow_none=True,
  218. default_value="choice2",
  219. help="Many choices.",
  220. ).tag(config=True)
  221. enum_choices_str = "Choices: any of ['Choice1', 'choice2'] (case-insensitive)"
  222. cls3_msg = MyConf3.class_get_help()
  223. self.assertIn(help_str, cls3_msg)
  224. self.assertIn(enum_choices_str, cls3_msg)
  225. self.assertIn(or_none_str, cls3_msg)
  226. self.assertIn(defaults_str, cls3_msg)
  227. # Check order of Default <--> Choices sections
  228. self.assertGreater(cls3_msg.index(defaults_str), cls3_msg.index(enum_choices_str))
  229. cls3_cfg = MyConf3.class_config_section()
  230. self.assertIn(help_str, cls3_cfg)
  231. self.assertIn(enum_choices_str, cls3_cfg)
  232. self.assertIn(or_none_str, cls3_cfg)
  233. self.assertIn(defaults_str, cls3_cfg)
  234. # Check order of Default <--> Choices sections
  235. self.assertGreater(cls3_cfg.index(defaults_str), cls3_cfg.index(enum_choices_str))
  236. class MyConf4(Configurable):
  237. an_enum = FuzzyEnum(
  238. "Choice1 choice2".split(),
  239. allow_none=True,
  240. default_value="choice2",
  241. help="Many choices.",
  242. ).tag(config=True)
  243. enum_choices_str = "Choices: any case-insensitive prefix of ['Choice1', 'choice2']"
  244. cls4_msg = MyConf4.class_get_help()
  245. self.assertIn(help_str, cls4_msg)
  246. self.assertIn(enum_choices_str, cls4_msg)
  247. self.assertIn(or_none_str, cls4_msg)
  248. self.assertIn(defaults_str, cls4_msg)
  249. # Check order of Default <--> Choices sections
  250. self.assertGreater(cls4_msg.index(defaults_str), cls4_msg.index(enum_choices_str))
  251. cls4_cfg = MyConf4.class_config_section()
  252. self.assertIn(help_str, cls4_cfg)
  253. self.assertIn(enum_choices_str, cls4_cfg)
  254. self.assertIn(or_none_str, cls4_cfg)
  255. self.assertIn(defaults_str, cls4_cfg)
  256. # Check order of Default <--> Choices sections
  257. self.assertGreater(cls4_cfg.index(defaults_str), cls4_cfg.index(enum_choices_str))
  258. class TestSingletonConfigurable(TestCase):
  259. def test_instance(self):
  260. class Foo(SingletonConfigurable):
  261. pass
  262. self.assertEqual(Foo.initialized(), False)
  263. foo = Foo.instance()
  264. self.assertEqual(Foo.initialized(), True)
  265. self.assertEqual(foo, Foo.instance())
  266. self.assertEqual(SingletonConfigurable._instance, None)
  267. def test_inheritance(self):
  268. class Bar(SingletonConfigurable):
  269. pass
  270. class Bam(Bar):
  271. pass
  272. self.assertEqual(Bar.initialized(), False)
  273. self.assertEqual(Bam.initialized(), False)
  274. bam = Bam.instance()
  275. self.assertEqual(Bar.initialized(), True)
  276. self.assertEqual(Bam.initialized(), True)
  277. self.assertEqual(bam, Bam._instance)
  278. self.assertEqual(bam, Bar._instance)
  279. self.assertEqual(SingletonConfigurable._instance, None)
  280. class TestLoggingConfigurable(TestCase):
  281. def test_parent_logger(self):
  282. class Parent(LoggingConfigurable):
  283. pass
  284. class Child(LoggingConfigurable):
  285. pass
  286. log = get_logger().getChild("TestLoggingConfigurable")
  287. parent = Parent(log=log)
  288. child = Child(parent=parent)
  289. self.assertEqual(parent.log, log)
  290. self.assertEqual(child.log, log)
  291. parent = Parent()
  292. child = Child(parent=parent, log=log)
  293. self.assertEqual(parent.log, get_logger())
  294. self.assertEqual(child.log, log)
  295. def test_parent_not_logging_configurable(self):
  296. class Parent(Configurable):
  297. pass
  298. class Child(LoggingConfigurable):
  299. pass
  300. parent = Parent()
  301. child = Child(parent=parent)
  302. self.assertEqual(child.log, get_logger())
  303. class MyParent(Configurable):
  304. pass
  305. class MyParent2(MyParent):
  306. pass
  307. class TestParentConfigurable(TestCase):
  308. def test_parent_config(self):
  309. cfg = Config(
  310. {
  311. "MyParent": {
  312. "MyConfigurable": {
  313. "b": 2.0,
  314. }
  315. }
  316. }
  317. )
  318. parent = MyParent(config=cfg)
  319. myc = MyConfigurable(parent=parent)
  320. self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
  321. def test_parent_inheritance(self):
  322. cfg = Config(
  323. {
  324. "MyParent": {
  325. "MyConfigurable": {
  326. "b": 2.0,
  327. }
  328. }
  329. }
  330. )
  331. parent = MyParent2(config=cfg)
  332. myc = MyConfigurable(parent=parent)
  333. self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
  334. def test_multi_parent(self):
  335. cfg = Config(
  336. {
  337. "MyParent2": {
  338. "MyParent": {
  339. "MyConfigurable": {
  340. "b": 2.0,
  341. }
  342. },
  343. # this one shouldn't count
  344. "MyConfigurable": {
  345. "b": 3.0,
  346. },
  347. }
  348. }
  349. )
  350. parent2 = MyParent2(config=cfg)
  351. parent = MyParent(parent=parent2)
  352. myc = MyConfigurable(parent=parent)
  353. self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
  354. def test_parent_priority(self):
  355. cfg = Config(
  356. {
  357. "MyConfigurable": {
  358. "b": 2.0,
  359. },
  360. "MyParent": {
  361. "MyConfigurable": {
  362. "b": 3.0,
  363. }
  364. },
  365. "MyParent2": {
  366. "MyConfigurable": {
  367. "b": 4.0,
  368. }
  369. },
  370. }
  371. )
  372. parent = MyParent2(config=cfg)
  373. myc = MyConfigurable(parent=parent)
  374. self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
  375. def test_multi_parent_priority(self):
  376. cfg = Config(
  377. {
  378. "MyConfigurable": {
  379. "b": 2.0,
  380. },
  381. "MyParent": {
  382. "MyConfigurable": {
  383. "b": 3.0,
  384. },
  385. },
  386. "MyParent2": {
  387. "MyConfigurable": {
  388. "b": 4.0,
  389. },
  390. "MyParent": {
  391. "MyConfigurable": {
  392. "b": 5.0,
  393. },
  394. },
  395. },
  396. }
  397. )
  398. parent2 = MyParent2(config=cfg)
  399. parent = MyParent2(parent=parent2)
  400. myc = MyConfigurable(parent=parent)
  401. self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
  402. class Containers(Configurable):
  403. lis = List().tag(config=True)
  404. def _lis_default(self):
  405. return [-1]
  406. s = Set().tag(config=True)
  407. def _s_default(self):
  408. return {"a"}
  409. d = Dict().tag(config=True)
  410. def _d_default(self):
  411. return {"a": "b"}
  412. class TestConfigContainers(TestCase):
  413. def test_extend(self):
  414. c = Config()
  415. c.Containers.lis.extend(list(range(5)))
  416. obj = Containers(config=c)
  417. self.assertEqual(obj.lis, list(range(-1, 5)))
  418. def test_insert(self):
  419. c = Config()
  420. c.Containers.lis.insert(0, "a")
  421. c.Containers.lis.insert(1, "b")
  422. obj = Containers(config=c)
  423. self.assertEqual(obj.lis, ["a", "b", -1])
  424. def test_prepend(self):
  425. c = Config()
  426. c.Containers.lis.prepend([1, 2])
  427. c.Containers.lis.prepend([2, 3])
  428. obj = Containers(config=c)
  429. self.assertEqual(obj.lis, [2, 3, 1, 2, -1])
  430. def test_prepend_extend(self):
  431. c = Config()
  432. c.Containers.lis.prepend([1, 2])
  433. c.Containers.lis.extend([2, 3])
  434. obj = Containers(config=c)
  435. self.assertEqual(obj.lis, [1, 2, -1, 2, 3])
  436. def test_append_extend(self):
  437. c = Config()
  438. c.Containers.lis.append([1, 2])
  439. c.Containers.lis.extend([2, 3])
  440. obj = Containers(config=c)
  441. self.assertEqual(obj.lis, [-1, [1, 2], 2, 3])
  442. def test_extend_append(self):
  443. c = Config()
  444. c.Containers.lis.extend([2, 3])
  445. c.Containers.lis.append([1, 2])
  446. obj = Containers(config=c)
  447. self.assertEqual(obj.lis, [-1, 2, 3, [1, 2]])
  448. def test_insert_extend(self):
  449. c = Config()
  450. c.Containers.lis.insert(0, 1)
  451. c.Containers.lis.extend([2, 3])
  452. obj = Containers(config=c)
  453. self.assertEqual(obj.lis, [1, -1, 2, 3])
  454. def test_set_update(self):
  455. c = Config()
  456. c.Containers.s.update({0, 1, 2})
  457. c.Containers.s.update({3})
  458. obj = Containers(config=c)
  459. self.assertEqual(obj.s, {"a", 0, 1, 2, 3})
  460. def test_dict_update(self):
  461. c = Config()
  462. c.Containers.d.update({"c": "d"})
  463. c.Containers.d.update({"e": "f"})
  464. obj = Containers(config=c)
  465. self.assertEqual(obj.d, {"a": "b", "c": "d", "e": "f"})
  466. def test_update_twice(self):
  467. c = Config()
  468. c.MyConfigurable.a = 5
  469. m = MyConfigurable(config=c)
  470. self.assertEqual(m.a, 5)
  471. c2 = Config()
  472. c2.MyConfigurable.a = 10
  473. m.update_config(c2)
  474. self.assertEqual(m.a, 10)
  475. c2.MyConfigurable.a = 15
  476. m.update_config(c2)
  477. self.assertEqual(m.a, 15)
  478. def test_update_self(self):
  479. """update_config with same config object still triggers config_changed"""
  480. c = Config()
  481. c.MyConfigurable.a = 5
  482. m = MyConfigurable(config=c)
  483. self.assertEqual(m.a, 5)
  484. c.MyConfigurable.a = 10
  485. m.update_config(c)
  486. self.assertEqual(m.a, 10)
  487. def test_config_default(self):
  488. class SomeSingleton(SingletonConfigurable):
  489. pass
  490. class DefaultConfigurable(Configurable):
  491. a = Integer().tag(config=True)
  492. def _config_default(self):
  493. if SomeSingleton.initialized():
  494. return SomeSingleton.instance().config
  495. return Config()
  496. c = Config()
  497. c.DefaultConfigurable.a = 5
  498. d1 = DefaultConfigurable()
  499. self.assertEqual(d1.a, 0)
  500. single = SomeSingleton.instance(config=c)
  501. d2 = DefaultConfigurable()
  502. self.assertIs(d2.config, single.config)
  503. self.assertEqual(d2.a, 5)
  504. def test_config_default_deprecated(self):
  505. """Make sure configurables work even with the deprecations in traitlets"""
  506. class SomeSingleton(SingletonConfigurable):
  507. pass
  508. # reset deprecation limiter
  509. _deprecations_shown.clear()
  510. with expected_warnings([]):
  511. class DefaultConfigurable(Configurable):
  512. a = Integer(config=True)
  513. def _config_default(self):
  514. if SomeSingleton.initialized():
  515. return SomeSingleton.instance().config
  516. return Config()
  517. c = Config()
  518. c.DefaultConfigurable.a = 5
  519. d1 = DefaultConfigurable()
  520. self.assertEqual(d1.a, 0)
  521. single = SomeSingleton.instance(config=c)
  522. d2 = DefaultConfigurable()
  523. self.assertIs(d2.config, single.config)
  524. self.assertEqual(d2.a, 5)
  525. def test_kwarg_config_priority(self):
  526. # a, c set in kwargs
  527. # a, b set in config
  528. # verify that:
  529. # - kwargs are set before config
  530. # - kwargs have priority over config
  531. class A(Configurable):
  532. a = Unicode("default", config=True)
  533. b = Unicode("default", config=True)
  534. c = Unicode("default", config=True)
  535. c_during_config = Unicode("never")
  536. @validate("b")
  537. def _record_c(self, proposal):
  538. # setting b from config records c's value at the time
  539. self.c_during_config = self.c
  540. return proposal.value
  541. cfg = Config()
  542. cfg.A.a = "a-config"
  543. cfg.A.b = "b-config"
  544. obj = A(a="a-kwarg", c="c-kwarg", config=cfg)
  545. assert obj.a == "a-kwarg"
  546. assert obj.b == "b-config"
  547. assert obj.c == "c-kwarg"
  548. assert obj.c_during_config == "c-kwarg"
  549. class TestLogger(TestCase):
  550. class A(LoggingConfigurable):
  551. foo = Integer(config=True)
  552. bar = Integer(config=True)
  553. baz = Integer(config=True)
  554. @mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
  555. def test_warn_match(self):
  556. logger = logging.getLogger("test_warn_match")
  557. cfg = Config({"A": {"bat": 5}})
  558. with self.assertLogs(logger, logging.WARNING) as captured:
  559. TestLogger.A(config=cfg, log=logger)
  560. output = "\n".join(captured.output)
  561. self.assertIn("Did you mean one of: `bar, baz`?", output)
  562. self.assertIn("Config option `bat` not recognized by `A`.", output)
  563. cfg = Config({"A": {"fool": 5}})
  564. with self.assertLogs(logger, logging.WARNING) as captured:
  565. TestLogger.A(config=cfg, log=logger)
  566. output = "\n".join(captured.output)
  567. self.assertIn("Config option `fool` not recognized by `A`.", output)
  568. self.assertIn("Did you mean `foo`?", output)
  569. cfg = Config({"A": {"totally_wrong": 5}})
  570. with self.assertLogs(logger, logging.WARNING) as captured:
  571. TestLogger.A(config=cfg, log=logger)
  572. output = "\n".join(captured.output)
  573. self.assertIn("Config option `totally_wrong` not recognized by `A`.", output)
  574. self.assertNotIn("Did you mean", output)
  575. def test_logger_adapter(caplog, capsys):
  576. logger = logging.getLogger("Application")
  577. adapter = logging.LoggerAdapter(logger, {"key": "adapted"})
  578. app = Application(log=adapter, log_level=logging.INFO)
  579. app.log_format = "%(key)s %(message)s"
  580. app.log.info("test message")
  581. assert "adapted test message" in capsys.readouterr().err