test_configurable.py 22 KB

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