test_loader.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. """Tests for traitlets.config.loader"""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. from __future__ import annotations
  5. import copy
  6. import os
  7. import pickle
  8. from itertools import chain
  9. from tempfile import mkstemp
  10. from unittest import TestCase
  11. import pytest
  12. from traitlets import Dict, Integer, List, Tuple, Unicode
  13. from traitlets.config import Configurable
  14. from traitlets.config.loader import (
  15. ArgParseConfigLoader,
  16. Config,
  17. JSONFileConfigLoader,
  18. KeyValueConfigLoader,
  19. KVArgParseConfigLoader,
  20. LazyConfigValue,
  21. PyFileConfigLoader,
  22. )
  23. pyfile = """
  24. c = get_config()
  25. c.a=10
  26. c.b=20
  27. c.Foo.Bar.value=10
  28. c.Foo.Bam.value=list(range(10))
  29. c.D.C.value='hi there'
  30. """
  31. json1file = """
  32. {
  33. "version": 1,
  34. "a": 10,
  35. "b": 20,
  36. "Foo": {
  37. "Bam": {
  38. "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
  39. },
  40. "Bar": {
  41. "value": 10
  42. }
  43. },
  44. "D": {
  45. "C": {
  46. "value": "hi there"
  47. }
  48. }
  49. }
  50. """
  51. # should not load
  52. json2file = """
  53. {
  54. "version": 2
  55. }
  56. """
  57. import logging
  58. log = logging.getLogger("devnull")
  59. log.setLevel(0)
  60. class TestFileCL(TestCase):
  61. def _check_conf(self, config):
  62. self.assertEqual(config.a, 10)
  63. self.assertEqual(config.b, 20)
  64. self.assertEqual(config.Foo.Bar.value, 10)
  65. self.assertEqual(config.Foo.Bam.value, list(range(10)))
  66. self.assertEqual(config.D.C.value, "hi there")
  67. def test_python(self):
  68. fd, fname = mkstemp(".py", prefix="μnïcø∂e")
  69. f = os.fdopen(fd, "w")
  70. f.write(pyfile)
  71. f.close()
  72. # Unlink the file
  73. cl = PyFileConfigLoader(fname, log=log)
  74. config = cl.load_config()
  75. self._check_conf(config)
  76. def test_json(self):
  77. fd, fname = mkstemp(".json", prefix="μnïcø∂e")
  78. f = os.fdopen(fd, "w")
  79. f.write(json1file)
  80. f.close()
  81. # Unlink the file
  82. cl = JSONFileConfigLoader(fname, log=log)
  83. config = cl.load_config()
  84. self._check_conf(config)
  85. def test_context_manager(self):
  86. fd, fname = mkstemp(".json", prefix="μnïcø∂e")
  87. f = os.fdopen(fd, "w")
  88. f.write("{}")
  89. f.close()
  90. cl = JSONFileConfigLoader(fname, log=log)
  91. value = "context_manager"
  92. with cl as c:
  93. c.MyAttr.value = value
  94. self.assertEqual(cl.config.MyAttr.value, value)
  95. # check that another loader does see the change
  96. _ = JSONFileConfigLoader(fname, log=log)
  97. self.assertEqual(cl.config.MyAttr.value, value)
  98. def test_json_context_bad_write(self):
  99. fd, fname = mkstemp(".json", prefix="μnïcø∂e")
  100. f = os.fdopen(fd, "w")
  101. f.write("{}")
  102. f.close()
  103. with JSONFileConfigLoader(fname, log=log) as config:
  104. config.A.b = 1
  105. with self.assertRaises(TypeError), JSONFileConfigLoader(fname, log=log) as config:
  106. config.A.cant_json = lambda x: x
  107. loader = JSONFileConfigLoader(fname, log=log)
  108. cfg = loader.load_config()
  109. assert cfg.A.b == 1
  110. assert "cant_json" not in cfg.A
  111. def test_collision(self):
  112. a = Config()
  113. b = Config()
  114. self.assertEqual(a.collisions(b), {})
  115. a.A.trait1 = 1
  116. b.A.trait2 = 2
  117. self.assertEqual(a.collisions(b), {})
  118. b.A.trait1 = 1
  119. self.assertEqual(a.collisions(b), {})
  120. b.A.trait1 = 0
  121. self.assertEqual(
  122. a.collisions(b),
  123. {
  124. "A": {
  125. "trait1": "1 ignored, using 0",
  126. }
  127. },
  128. )
  129. self.assertEqual(
  130. b.collisions(a),
  131. {
  132. "A": {
  133. "trait1": "0 ignored, using 1",
  134. }
  135. },
  136. )
  137. a.A.trait2 = 3
  138. self.assertEqual(
  139. b.collisions(a),
  140. {
  141. "A": {
  142. "trait1": "0 ignored, using 1",
  143. "trait2": "2 ignored, using 3",
  144. }
  145. },
  146. )
  147. def test_v2raise(self):
  148. fd, fname = mkstemp(".json", prefix="μnïcø∂e")
  149. f = os.fdopen(fd, "w")
  150. f.write(json2file)
  151. f.close()
  152. # Unlink the file
  153. cl = JSONFileConfigLoader(fname, log=log)
  154. with self.assertRaises(ValueError):
  155. cl.load_config()
  156. def _parse_int_or_str(v):
  157. try:
  158. return int(v)
  159. except Exception:
  160. return str(v)
  161. class MyLoader1(ArgParseConfigLoader):
  162. def _add_arguments(self, aliases=None, flags=None, classes=None):
  163. p = self.parser
  164. p.add_argument("-f", "--foo", dest="Global.foo", type=str)
  165. p.add_argument("-b", dest="MyClass.bar", type=int)
  166. p.add_argument("-n", dest="n", action="store_true")
  167. p.add_argument("Global.bam", type=str)
  168. p.add_argument("--list1", action="append", type=_parse_int_or_str)
  169. p.add_argument("--list2", nargs="+", type=int)
  170. class MyLoader2(ArgParseConfigLoader):
  171. def _add_arguments(self, aliases=None, flags=None, classes=None):
  172. subparsers = self.parser.add_subparsers(dest="subparser_name")
  173. subparser1 = subparsers.add_parser("1")
  174. subparser1.add_argument("-x", dest="Global.x")
  175. subparser2 = subparsers.add_parser("2")
  176. subparser2.add_argument("y")
  177. class TestArgParseCL(TestCase):
  178. def test_basic(self):
  179. cl = MyLoader1()
  180. config = cl.load_config("-f hi -b 10 -n wow".split())
  181. self.assertEqual(config.Global.foo, "hi")
  182. self.assertEqual(config.MyClass.bar, 10)
  183. self.assertEqual(config.n, True)
  184. self.assertEqual(config.Global.bam, "wow")
  185. config = cl.load_config(["wow"])
  186. self.assertEqual(list(config.keys()), ["Global"])
  187. self.assertEqual(list(config.Global.keys()), ["bam"])
  188. self.assertEqual(config.Global.bam, "wow")
  189. def test_add_arguments(self):
  190. cl = MyLoader2()
  191. config = cl.load_config("2 frobble".split())
  192. self.assertEqual(config.subparser_name, "2")
  193. self.assertEqual(config.y, "frobble")
  194. config = cl.load_config("1 -x frobble".split())
  195. self.assertEqual(config.subparser_name, "1")
  196. self.assertEqual(config.Global.x, "frobble")
  197. def test_argv(self):
  198. cl = MyLoader1(argv="-f hi -b 10 -n wow".split())
  199. config = cl.load_config()
  200. self.assertEqual(config.Global.foo, "hi")
  201. self.assertEqual(config.MyClass.bar, 10)
  202. self.assertEqual(config.n, True)
  203. self.assertEqual(config.Global.bam, "wow")
  204. def test_list_args(self):
  205. cl = MyLoader1()
  206. config = cl.load_config("--list1 1 wow --list2 1 2 3 --list1 B".split())
  207. self.assertEqual(list(config.Global.keys()), ["bam"])
  208. self.assertEqual(config.Global.bam, "wow")
  209. self.assertEqual(config.list1, [1, "B"])
  210. self.assertEqual(config.list2, [1, 2, 3])
  211. class C(Configurable):
  212. str_trait = Unicode(config=True)
  213. int_trait = Integer(config=True)
  214. list_trait = List(config=True)
  215. list_of_ints = List(Integer(), config=True)
  216. dict_trait = Dict(config=True)
  217. dict_of_ints = Dict(
  218. key_trait=Integer(),
  219. value_trait=Integer(),
  220. config=True,
  221. )
  222. dict_multi = Dict(
  223. key_trait=Unicode(),
  224. per_key_traits={
  225. "int": Integer(),
  226. "str": Unicode(),
  227. },
  228. config=True,
  229. )
  230. class TestKeyValueCL(TestCase):
  231. klass = KeyValueConfigLoader
  232. def test_eval(self):
  233. cl = self.klass(log=log)
  234. config = cl.load_config(
  235. '--C.str_trait=all --C.int_trait=5 --C.list_trait=["hello",5]'.split()
  236. )
  237. c = C(config=config)
  238. assert c.str_trait == "all"
  239. assert c.int_trait == 5
  240. assert c.list_trait == ["hello", 5]
  241. def test_basic(self):
  242. cl = self.klass(log=log)
  243. argv = ["--" + s[2:] for s in pyfile.split("\n") if s.startswith("c.")]
  244. config = cl.load_config(argv)
  245. assert config.a == "10"
  246. assert config.b == "20"
  247. assert config.Foo.Bar.value == "10"
  248. # non-literal expressions are not evaluated
  249. self.assertEqual(config.Foo.Bam.value, "list(range(10))")
  250. self.assertEqual(Unicode().from_string(config.D.C.value), "hi there")
  251. def test_expanduser(self):
  252. cl = self.klass(log=log)
  253. argv = ["--a=~/1/2/3", "--b=~", "--c=~/", '--d="~/"']
  254. config = cl.load_config(argv)
  255. u = Unicode()
  256. self.assertEqual(u.from_string(config.a), os.path.expanduser("~/1/2/3"))
  257. self.assertEqual(u.from_string(config.b), os.path.expanduser("~"))
  258. self.assertEqual(u.from_string(config.c), os.path.expanduser("~/"))
  259. self.assertEqual(u.from_string(config.d), "~/")
  260. def test_extra_args(self):
  261. cl = self.klass(log=log)
  262. config = cl.load_config(["--a=5", "b", "d", "--c=10"])
  263. self.assertEqual(cl.extra_args, ["b", "d"])
  264. assert config.a == "5"
  265. assert config.c == "10"
  266. config = cl.load_config(["--", "--a=5", "--c=10"])
  267. self.assertEqual(cl.extra_args, ["--a=5", "--c=10"])
  268. cl = self.klass(log=log)
  269. config = cl.load_config(["extra", "--a=2", "--c=1", "--", "-"])
  270. self.assertEqual(cl.extra_args, ["extra", "-"])
  271. def test_unicode_args(self):
  272. cl = self.klass(log=log)
  273. argv = ["--a=épsîlön"]
  274. config = cl.load_config(argv)
  275. print(config, cl.extra_args)
  276. self.assertEqual(config.a, "épsîlön")
  277. def test_list_append(self):
  278. cl = self.klass(log=log)
  279. argv = ["--C.list_trait", "x", "--C.list_trait", "y"]
  280. config = cl.load_config(argv)
  281. assert config.C.list_trait == ["x", "y"]
  282. c = C(config=config)
  283. assert c.list_trait == ["x", "y"]
  284. def test_list_single_item(self):
  285. cl = self.klass(log=log)
  286. argv = ["--C.list_trait", "x"]
  287. config = cl.load_config(argv)
  288. c = C(config=config)
  289. assert c.list_trait == ["x"]
  290. def test_dict(self):
  291. cl = self.klass(log=log)
  292. argv = ["--C.dict_trait", "x=5", "--C.dict_trait", "y=10"]
  293. config = cl.load_config(argv)
  294. c = C(config=config)
  295. assert c.dict_trait == {"x": "5", "y": "10"}
  296. def test_dict_key_traits(self):
  297. cl = self.klass(log=log)
  298. argv = ["--C.dict_of_ints", "1=2", "--C.dict_of_ints", "3=4"]
  299. config = cl.load_config(argv)
  300. c = C(config=config)
  301. assert c.dict_of_ints == {1: 2, 3: 4}
  302. class CBase(Configurable):
  303. a = List().tag(config=True)
  304. b = List(Integer()).tag(config=True, multiplicity="*")
  305. c = List().tag(config=True, multiplicity="append")
  306. adict = Dict().tag(config=True)
  307. class CSub(CBase):
  308. d = Tuple().tag(config=True)
  309. e = Tuple().tag(config=True, multiplicity="+")
  310. bdict = Dict().tag(config=True, multiplicity="*")
  311. class TestArgParseKVCL(TestKeyValueCL):
  312. klass = KVArgParseConfigLoader # type:ignore
  313. def test_no_cast_literals(self):
  314. cl = self.klass(log=log) # type:ignore
  315. # test ipython -c 1 doesn't cast to int
  316. argv = ["-c", "1"]
  317. config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
  318. assert config.IPython.command_to_run == "1"
  319. def test_int_literals(self):
  320. cl = self.klass(log=log) # type:ignore
  321. # test ipython -c 1 doesn't cast to int
  322. argv = ["-c", "1"]
  323. config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
  324. assert config.IPython.command_to_run == "1"
  325. def test_unicode_alias(self):
  326. cl = self.klass(log=log) # type:ignore
  327. argv = ["--a=épsîlön"]
  328. config = cl.load_config(argv, aliases=dict(a="A.a"))
  329. print(dict(config))
  330. print(cl.extra_args)
  331. print(cl.aliases)
  332. self.assertEqual(config.A.a, "épsîlön")
  333. def test_expanduser2(self):
  334. cl = self.klass(log=log) # type:ignore
  335. argv = ["-a", "~/1/2/3", "--b", "'~/1/2/3'"]
  336. config = cl.load_config(argv, aliases=dict(a="A.a", b="A.b"))
  337. class A(Configurable):
  338. a = Unicode(config=True)
  339. b = Unicode(config=True)
  340. a = A(config=config)
  341. self.assertEqual(a.a, os.path.expanduser("~/1/2/3"))
  342. self.assertEqual(a.b, "~/1/2/3")
  343. def test_eval(self):
  344. cl = self.klass(log=log) # type:ignore
  345. argv = ["-c", "a=5"]
  346. config = cl.load_config(argv, aliases=dict(c="A.c"))
  347. self.assertEqual(config.A.c, "a=5")
  348. def test_seq_traits(self):
  349. cl = self.klass(log=log, classes=(CBase, CSub)) # type:ignore
  350. aliases = {"a3": "CBase.c", "a5": "CSub.e"}
  351. argv = (
  352. "--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB "
  353. "--CSub.d 1 --CSub.d BBB --CSub.e 1 --CSub.e=bcd a b c "
  354. ).split()
  355. config = cl.load_config(argv, aliases=aliases)
  356. assert cl.extra_args == ["a", "b", "c"]
  357. assert config.CBase.a == ["A", "2"]
  358. assert config.CBase.b == [1, 3]
  359. self.assertEqual(config.CBase.c, ["AA", "BB"])
  360. assert config.CSub.d == ("1", "BBB")
  361. assert config.CSub.e == ("1", "bcd")
  362. def test_seq_traits_single_empty_string(self):
  363. cl = self.klass(log=log, classes=(CBase,)) # type:ignore
  364. aliases = {"seqopt": "CBase.c"}
  365. argv = ["--seqopt", ""]
  366. config = cl.load_config(argv, aliases=aliases)
  367. self.assertEqual(config.CBase.c, [""])
  368. def test_dict_traits(self):
  369. cl = self.klass(log=log, classes=(CBase, CSub)) # type:ignore
  370. aliases = {"D": "CBase.adict", "E": "CSub.bdict"}
  371. argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"]
  372. config = cl.load_config(argv, aliases=aliases)
  373. c = CSub(config=config)
  374. assert c.adict == {"k1": "v1", "k2": "2", "k3": "v 3"}
  375. assert c.bdict == {"k": "v", "22": "222"}
  376. def test_mixed_seq_positional(self):
  377. aliases = {"c": "Class.trait"}
  378. cl = self.klass(log=log, aliases=aliases) # type:ignore
  379. assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")]
  380. positionals = ["a", "b", "c"]
  381. # test with positionals at any index
  382. for idx in range(len(assignments) + 1):
  383. argv_parts = assignments[:]
  384. argv_parts[idx:idx] = (positionals,) # type:ignore
  385. argv = list(chain(*argv_parts))
  386. config = cl.load_config(argv)
  387. assert config.Class.trait == ["1", "2", "3", "4"]
  388. assert cl.extra_args == ["a", "b", "c"]
  389. def test_split_positional(self):
  390. """Splitting positionals across flags is no longer allowed in traitlets 5"""
  391. cl = self.klass(log=log) # type:ignore
  392. argv = ["a", "--Class.trait=5", "b"]
  393. with pytest.raises(SystemExit):
  394. cl.load_config(argv)
  395. class TestConfig(TestCase):
  396. def test_setget(self):
  397. c = Config()
  398. c.a = 10
  399. self.assertEqual(c.a, 10)
  400. self.assertEqual("b" in c, False)
  401. def test_auto_section(self):
  402. c = Config()
  403. self.assertNotIn("A", c)
  404. assert not c._has_section("A")
  405. A = c.A
  406. A.foo = "hi there"
  407. self.assertIn("A", c)
  408. assert c._has_section("A")
  409. self.assertEqual(c.A.foo, "hi there")
  410. del c.A
  411. self.assertEqual(c.A, Config())
  412. def test_merge_doesnt_exist(self):
  413. c1 = Config()
  414. c2 = Config()
  415. c2.bar = 10
  416. c2.Foo.bar = 10
  417. c1.merge(c2)
  418. self.assertEqual(c1.Foo.bar, 10)
  419. self.assertEqual(c1.bar, 10)
  420. c2.Bar.bar = 10
  421. c1.merge(c2)
  422. self.assertEqual(c1.Bar.bar, 10)
  423. def test_merge_exists(self):
  424. c1 = Config()
  425. c2 = Config()
  426. c1.Foo.bar = 10
  427. c1.Foo.bam = 30
  428. c2.Foo.bar = 20
  429. c2.Foo.wow = 40
  430. c1.merge(c2)
  431. self.assertEqual(c1.Foo.bam, 30)
  432. self.assertEqual(c1.Foo.bar, 20)
  433. self.assertEqual(c1.Foo.wow, 40)
  434. c2.Foo.Bam.bam = 10
  435. c1.merge(c2)
  436. self.assertEqual(c1.Foo.Bam.bam, 10)
  437. def test_deepcopy(self):
  438. c1 = Config()
  439. c1.Foo.bar = 10
  440. c1.Foo.bam = 30
  441. c1.a = "asdf"
  442. c1.b = range(10)
  443. c1.Test.logger = logging.Logger("test")
  444. c1.Test.get_logger = logging.getLogger("test")
  445. c2 = copy.deepcopy(c1)
  446. self.assertEqual(c1, c2)
  447. self.assertTrue(c1 is not c2)
  448. self.assertTrue(c1.Foo is not c2.Foo)
  449. self.assertTrue(c1.Test is not c2.Test)
  450. self.assertTrue(c1.Test.logger is c2.Test.logger)
  451. self.assertTrue(c1.Test.get_logger is c2.Test.get_logger)
  452. def test_builtin(self):
  453. c1 = Config()
  454. c1.format = "json"
  455. def test_fromdict(self):
  456. c1 = Config({"Foo": {"bar": 1}})
  457. self.assertEqual(c1.Foo.__class__, Config)
  458. self.assertEqual(c1.Foo.bar, 1)
  459. def test_fromdictmerge(self):
  460. c1 = Config()
  461. c2 = Config({"Foo": {"bar": 1}})
  462. c1.merge(c2)
  463. self.assertEqual(c1.Foo.__class__, Config)
  464. self.assertEqual(c1.Foo.bar, 1)
  465. def test_fromdictmerge2(self):
  466. c1 = Config({"Foo": {"baz": 2}})
  467. c2 = Config({"Foo": {"bar": 1}})
  468. c1.merge(c2)
  469. self.assertEqual(c1.Foo.__class__, Config)
  470. self.assertEqual(c1.Foo.bar, 1)
  471. self.assertEqual(c1.Foo.baz, 2)
  472. self.assertNotIn("baz", c2.Foo)
  473. def test_contains(self):
  474. c1 = Config({"Foo": {"baz": 2}})
  475. c2 = Config({"Foo": {"bar": 1}})
  476. self.assertIn("Foo", c1)
  477. self.assertIn("Foo.baz", c1)
  478. self.assertIn("Foo.bar", c2)
  479. self.assertNotIn("Foo.bar", c1)
  480. def test_pickle_config(self):
  481. cfg = Config()
  482. cfg.Foo.bar = 1
  483. pcfg = pickle.dumps(cfg)
  484. cfg2 = pickle.loads(pcfg)
  485. self.assertEqual(cfg2, cfg)
  486. def test_getattr_section(self):
  487. cfg = Config()
  488. self.assertNotIn("Foo", cfg)
  489. Foo = cfg.Foo
  490. assert isinstance(Foo, Config)
  491. self.assertIn("Foo", cfg)
  492. def test_getitem_section(self):
  493. cfg = Config()
  494. self.assertNotIn("Foo", cfg)
  495. Foo = cfg["Foo"]
  496. assert isinstance(Foo, Config)
  497. self.assertIn("Foo", cfg)
  498. def test_getattr_not_section(self):
  499. cfg = Config()
  500. self.assertNotIn("foo", cfg)
  501. foo = cfg.foo
  502. assert isinstance(foo, LazyConfigValue)
  503. self.assertIn("foo", cfg)
  504. def test_getattr_private_missing(self):
  505. cfg = Config()
  506. self.assertNotIn("_repr_html_", cfg)
  507. with self.assertRaises(AttributeError):
  508. _ = cfg._repr_html_
  509. self.assertNotIn("_repr_html_", cfg)
  510. self.assertEqual(len(cfg), 0)
  511. def test_lazy_config_repr(self):
  512. cfg = Config()
  513. cfg.Class.lazy.append(1)
  514. cfg_repr = repr(cfg)
  515. assert "<LazyConfigValue" in cfg_repr
  516. assert "extend" in cfg_repr
  517. assert " [1]}>" in cfg_repr
  518. assert "value=" not in cfg_repr
  519. cfg.Class.lazy.get_value([0])
  520. repr2 = repr(cfg)
  521. assert repr([0, 1]) in repr2
  522. assert "value=" in repr2
  523. def test_getitem_not_section(self):
  524. cfg = Config()
  525. self.assertNotIn("foo", cfg)
  526. foo = cfg["foo"]
  527. assert isinstance(foo, LazyConfigValue)
  528. self.assertIn("foo", cfg)
  529. def test_merge_no_copies(self):
  530. c = Config()
  531. c2 = Config()
  532. c2.Foo.trait = []
  533. c.merge(c2)
  534. c2.Foo.trait.append(1)
  535. self.assertIs(c.Foo, c2.Foo)
  536. self.assertEqual(c.Foo.trait, [1])
  537. self.assertEqual(c2.Foo.trait, [1])
  538. def test_merge_multi_lazy(self):
  539. """
  540. With multiple config files (systemwide and users), we want compounding.
  541. If systemwide overwrite and user append, we want both in the right
  542. order.
  543. """
  544. c1 = Config()
  545. c2 = Config()
  546. c1.Foo.trait = [1]
  547. c2.Foo.trait.append(2)
  548. c = Config()
  549. c.merge(c1)
  550. c.merge(c2)
  551. self.assertEqual(c.Foo.trait, [1, 2])
  552. def test_merge_multi_lazyII(self):
  553. """
  554. With multiple config files (systemwide and users), we want compounding.
  555. If both are lazy we still want a lazy config.
  556. """
  557. c1 = Config()
  558. c2 = Config()
  559. c1.Foo.trait.append(1)
  560. c2.Foo.trait.append(2)
  561. c = Config()
  562. c.merge(c1)
  563. c.merge(c2)
  564. self.assertEqual(c.Foo.trait._extend, [1, 2])
  565. def test_merge_multi_lazy_III(self):
  566. """
  567. With multiple config files (systemwide and users), we want compounding.
  568. Prepend should prepend in the right order.
  569. """
  570. c1 = Config()
  571. c2 = Config()
  572. c1.Foo.trait = [1]
  573. c2.Foo.trait.prepend([0])
  574. c = Config()
  575. c.merge(c1)
  576. c.merge(c2)
  577. self.assertEqual(c.Foo.trait, [0, 1])
  578. def test_merge_multi_lazy_IV(self):
  579. """
  580. With multiple config files (systemwide and users), we want compounding.
  581. Both prepending should be lazy
  582. """
  583. c1 = Config()
  584. c2 = Config()
  585. c1.Foo.trait.prepend([1])
  586. c2.Foo.trait.prepend([0])
  587. c = Config()
  588. c.merge(c1)
  589. c.merge(c2)
  590. self.assertEqual(c.Foo.trait._prepend, [0, 1])
  591. def test_merge_multi_lazy_update_I(self):
  592. """
  593. With multiple config files (systemwide and users), we want compounding.
  594. dict update should be in the right order.
  595. """
  596. c1 = Config()
  597. c2 = Config()
  598. c1.Foo.trait = {"a": 1, "z": 26}
  599. c2.Foo.trait.update({"a": 0, "b": 1})
  600. c = Config()
  601. c.merge(c1)
  602. c.merge(c2)
  603. self.assertEqual(c.Foo.trait, {"a": 0, "b": 1, "z": 26})
  604. def test_merge_multi_lazy_update_II(self):
  605. """
  606. With multiple config files (systemwide and users), we want compounding.
  607. Later dict overwrite laziness
  608. """
  609. c1 = Config()
  610. c2 = Config()
  611. c1.Foo.trait.update({"a": 0, "b": 1})
  612. c2.Foo.trait = {"a": 1, "z": 26}
  613. c = Config()
  614. c.merge(c1)
  615. c.merge(c2)
  616. self.assertEqual(c.Foo.trait, {"a": 1, "z": 26})
  617. def test_merge_multi_lazy_update_III(self):
  618. """
  619. With multiple config files (systemwide and users), we want compounding.
  620. Later dict overwrite laziness
  621. """
  622. c1 = Config()
  623. c2 = Config()
  624. c1.Foo.trait.update({"a": 0, "b": 1})
  625. c2.Foo.trait.update({"a": 1, "z": 26})
  626. c = Config()
  627. c.merge(c1)
  628. c.merge(c2)
  629. self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1})