test_loader.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. # encoding: utf-8
  2. """Tests for traitlets.config.loader"""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import copy
  6. import logging
  7. import os
  8. import pickle
  9. import sys
  10. from tempfile import mkstemp
  11. from unittest import TestCase
  12. from pytest import skip
  13. from traitlets.config.loader import (
  14. Config,
  15. LazyConfigValue,
  16. PyFileConfigLoader,
  17. JSONFileConfigLoader,
  18. KeyValueConfigLoader,
  19. ArgParseConfigLoader,
  20. KVArgParseConfigLoader,
  21. ConfigError,
  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')
  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')
  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')
  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. cl2 = 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')
  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):
  106. with JSONFileConfigLoader(fname, log=log) as config:
  107. config.A.cant_json = lambda x: x
  108. loader = JSONFileConfigLoader(fname, log=log)
  109. cfg = loader.load_config()
  110. assert cfg.A.b == 1
  111. assert 'cant_json' not in cfg.A
  112. def test_collision(self):
  113. a = Config()
  114. b = Config()
  115. self.assertEqual(a.collisions(b), {})
  116. a.A.trait1 = 1
  117. b.A.trait2 = 2
  118. self.assertEqual(a.collisions(b), {})
  119. b.A.trait1 = 1
  120. self.assertEqual(a.collisions(b), {})
  121. b.A.trait1 = 0
  122. self.assertEqual(a.collisions(b), {
  123. 'A': {
  124. 'trait1': "1 ignored, using 0",
  125. }
  126. })
  127. self.assertEqual(b.collisions(a), {
  128. 'A': {
  129. 'trait1': "0 ignored, using 1",
  130. }
  131. })
  132. a.A.trait2 = 3
  133. self.assertEqual(b.collisions(a), {
  134. 'A': {
  135. 'trait1': "0 ignored, using 1",
  136. 'trait2': "2 ignored, using 3",
  137. }
  138. })
  139. def test_v2raise(self):
  140. fd, fname = mkstemp('.json')
  141. f = os.fdopen(fd, 'w')
  142. f.write(json2file)
  143. f.close()
  144. # Unlink the file
  145. cl = JSONFileConfigLoader(fname, log=log)
  146. with self.assertRaises(ValueError):
  147. cl.load_config()
  148. class MyLoader1(ArgParseConfigLoader):
  149. def _add_arguments(self, aliases=None, flags=None):
  150. p = self.parser
  151. p.add_argument('-f', '--foo', dest='Global.foo', type=str)
  152. p.add_argument('-b', dest='MyClass.bar', type=int)
  153. p.add_argument('-n', dest='n', action='store_true')
  154. p.add_argument('Global.bam', type=str)
  155. class MyLoader2(ArgParseConfigLoader):
  156. def _add_arguments(self, aliases=None, flags=None):
  157. subparsers = self.parser.add_subparsers(dest='subparser_name')
  158. subparser1 = subparsers.add_parser('1')
  159. subparser1.add_argument('-x',dest='Global.x')
  160. subparser2 = subparsers.add_parser('2')
  161. subparser2.add_argument('y')
  162. class TestArgParseCL(TestCase):
  163. def test_basic(self):
  164. cl = MyLoader1()
  165. config = cl.load_config('-f hi -b 10 -n wow'.split())
  166. self.assertEqual(config.Global.foo, 'hi')
  167. self.assertEqual(config.MyClass.bar, 10)
  168. self.assertEqual(config.n, True)
  169. self.assertEqual(config.Global.bam, 'wow')
  170. config = cl.load_config(['wow'])
  171. self.assertEqual(list(config.keys()), ['Global'])
  172. self.assertEqual(list(config.Global.keys()), ['bam'])
  173. self.assertEqual(config.Global.bam, 'wow')
  174. def test_add_arguments(self):
  175. cl = MyLoader2()
  176. config = cl.load_config('2 frobble'.split())
  177. self.assertEqual(config.subparser_name, '2')
  178. self.assertEqual(config.y, 'frobble')
  179. config = cl.load_config('1 -x frobble'.split())
  180. self.assertEqual(config.subparser_name, '1')
  181. self.assertEqual(config.Global.x, 'frobble')
  182. def test_argv(self):
  183. cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
  184. config = cl.load_config()
  185. self.assertEqual(config.Global.foo, 'hi')
  186. self.assertEqual(config.MyClass.bar, 10)
  187. self.assertEqual(config.n, True)
  188. self.assertEqual(config.Global.bam, 'wow')
  189. class TestKeyValueCL(TestCase):
  190. klass = KeyValueConfigLoader
  191. def test_eval(self):
  192. cl = self.klass(log=log)
  193. config = cl.load_config('--Class.str_trait=all --Class.int_trait=5 --Class.list_trait=["hello",5]'.split())
  194. self.assertEqual(config.Class.str_trait, 'all')
  195. self.assertEqual(config.Class.int_trait, 5)
  196. self.assertEqual(config.Class.list_trait, ["hello", 5])
  197. def test_basic(self):
  198. cl = self.klass(log=log)
  199. argv = [ '--' + s[2:] for s in pyfile.split('\n') if s.startswith('c.') ]
  200. print(argv)
  201. config = cl.load_config(argv)
  202. self.assertEqual(config.a, 10)
  203. self.assertEqual(config.b, 20)
  204. self.assertEqual(config.Foo.Bar.value, 10)
  205. # non-literal expressions are not evaluated
  206. self.assertEqual(config.Foo.Bam.value, 'list(range(10))')
  207. self.assertEqual(config.D.C.value, 'hi there')
  208. def test_expanduser(self):
  209. cl = self.klass(log=log)
  210. argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
  211. config = cl.load_config(argv)
  212. self.assertEqual(config.a, os.path.expanduser('~/1/2/3'))
  213. self.assertEqual(config.b, os.path.expanduser('~'))
  214. self.assertEqual(config.c, os.path.expanduser('~/'))
  215. self.assertEqual(config.d, '~/')
  216. def test_extra_args(self):
  217. cl = self.klass(log=log)
  218. config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
  219. self.assertEqual(cl.extra_args, ['b', 'd'])
  220. self.assertEqual(config.a, 5)
  221. self.assertEqual(config.c, 10)
  222. config = cl.load_config(['--', '--a=5', '--c=10'])
  223. self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
  224. def test_unicode_args(self):
  225. cl = self.klass(log=log)
  226. argv = [u'--a=épsîlön']
  227. config = cl.load_config(argv)
  228. self.assertEqual(config.a, u'épsîlön')
  229. def test_unicode_bytes_args(self):
  230. uarg = u'--a=é'
  231. try:
  232. barg = uarg.encode(sys.stdin.encoding)
  233. except (TypeError, UnicodeEncodeError):
  234. raise skip("sys.stdin.encoding can't handle 'é'")
  235. cl = self.klass(log=log)
  236. config = cl.load_config([barg])
  237. self.assertEqual(config.a, u'é')
  238. def test_unicode_alias(self):
  239. cl = self.klass(log=log)
  240. argv = [u'--a=épsîlön']
  241. config = cl.load_config(argv, aliases=dict(a='A.a'))
  242. self.assertEqual(config.A.a, u'épsîlön')
  243. class TestArgParseKVCL(TestKeyValueCL):
  244. klass = KVArgParseConfigLoader
  245. def test_expanduser2(self):
  246. cl = self.klass(log=log)
  247. argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"]
  248. config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
  249. self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3'))
  250. self.assertEqual(config.A.b, '~/1/2/3')
  251. def test_eval(self):
  252. cl = self.klass(log=log)
  253. argv = ['-c', 'a=5']
  254. config = cl.load_config(argv, aliases=dict(c='A.c'))
  255. self.assertEqual(config.A.c, u"a=5")
  256. class TestConfig(TestCase):
  257. def test_setget(self):
  258. c = Config()
  259. c.a = 10
  260. self.assertEqual(c.a, 10)
  261. self.assertEqual('b' in c, False)
  262. def test_auto_section(self):
  263. c = Config()
  264. self.assertNotIn('A', c)
  265. assert not c._has_section('A')
  266. A = c.A
  267. A.foo = 'hi there'
  268. self.assertIn('A', c)
  269. assert c._has_section('A')
  270. self.assertEqual(c.A.foo, 'hi there')
  271. del c.A
  272. self.assertEqual(c.A, Config())
  273. def test_merge_doesnt_exist(self):
  274. c1 = Config()
  275. c2 = Config()
  276. c2.bar = 10
  277. c2.Foo.bar = 10
  278. c1.merge(c2)
  279. self.assertEqual(c1.Foo.bar, 10)
  280. self.assertEqual(c1.bar, 10)
  281. c2.Bar.bar = 10
  282. c1.merge(c2)
  283. self.assertEqual(c1.Bar.bar, 10)
  284. def test_merge_exists(self):
  285. c1 = Config()
  286. c2 = Config()
  287. c1.Foo.bar = 10
  288. c1.Foo.bam = 30
  289. c2.Foo.bar = 20
  290. c2.Foo.wow = 40
  291. c1.merge(c2)
  292. self.assertEqual(c1.Foo.bam, 30)
  293. self.assertEqual(c1.Foo.bar, 20)
  294. self.assertEqual(c1.Foo.wow, 40)
  295. c2.Foo.Bam.bam = 10
  296. c1.merge(c2)
  297. self.assertEqual(c1.Foo.Bam.bam, 10)
  298. def test_deepcopy(self):
  299. c1 = Config()
  300. c1.Foo.bar = 10
  301. c1.Foo.bam = 30
  302. c1.a = 'asdf'
  303. c1.b = range(10)
  304. c1.Test.logger = logging.Logger('test')
  305. c1.Test.get_logger = logging.getLogger('test')
  306. c2 = copy.deepcopy(c1)
  307. self.assertEqual(c1, c2)
  308. self.assertTrue(c1 is not c2)
  309. self.assertTrue(c1.Foo is not c2.Foo)
  310. self.assertTrue(c1.Test is not c2.Test)
  311. self.assertTrue(c1.Test.logger is c2.Test.logger)
  312. self.assertTrue(c1.Test.get_logger is c2.Test.get_logger)
  313. def test_builtin(self):
  314. c1 = Config()
  315. c1.format = "json"
  316. def test_fromdict(self):
  317. c1 = Config({'Foo' : {'bar' : 1}})
  318. self.assertEqual(c1.Foo.__class__, Config)
  319. self.assertEqual(c1.Foo.bar, 1)
  320. def test_fromdictmerge(self):
  321. c1 = Config()
  322. c2 = Config({'Foo' : {'bar' : 1}})
  323. c1.merge(c2)
  324. self.assertEqual(c1.Foo.__class__, Config)
  325. self.assertEqual(c1.Foo.bar, 1)
  326. def test_fromdictmerge2(self):
  327. c1 = Config({'Foo' : {'baz' : 2}})
  328. c2 = Config({'Foo' : {'bar' : 1}})
  329. c1.merge(c2)
  330. self.assertEqual(c1.Foo.__class__, Config)
  331. self.assertEqual(c1.Foo.bar, 1)
  332. self.assertEqual(c1.Foo.baz, 2)
  333. self.assertNotIn('baz', c2.Foo)
  334. def test_contains(self):
  335. c1 = Config({'Foo' : {'baz' : 2}})
  336. c2 = Config({'Foo' : {'bar' : 1}})
  337. self.assertIn('Foo', c1)
  338. self.assertIn('Foo.baz', c1)
  339. self.assertIn('Foo.bar', c2)
  340. self.assertNotIn('Foo.bar', c1)
  341. def test_pickle_config(self):
  342. cfg = Config()
  343. cfg.Foo.bar = 1
  344. pcfg = pickle.dumps(cfg)
  345. cfg2 = pickle.loads(pcfg)
  346. self.assertEqual(cfg2, cfg)
  347. def test_getattr_section(self):
  348. cfg = Config()
  349. self.assertNotIn('Foo', cfg)
  350. Foo = cfg.Foo
  351. assert isinstance(Foo, Config)
  352. self.assertIn('Foo', cfg)
  353. def test_getitem_section(self):
  354. cfg = Config()
  355. self.assertNotIn('Foo', cfg)
  356. Foo = cfg['Foo']
  357. assert isinstance(Foo, Config)
  358. self.assertIn('Foo', cfg)
  359. def test_getattr_not_section(self):
  360. cfg = Config()
  361. self.assertNotIn('foo', cfg)
  362. foo = cfg.foo
  363. assert isinstance(foo, LazyConfigValue)
  364. self.assertIn('foo', cfg)
  365. def test_getattr_private_missing(self):
  366. cfg = Config()
  367. self.assertNotIn('_repr_html_', cfg)
  368. with self.assertRaises(AttributeError):
  369. _ = cfg._repr_html_
  370. self.assertNotIn('_repr_html_', cfg)
  371. self.assertEqual(len(cfg), 0)
  372. def test_getitem_not_section(self):
  373. cfg = Config()
  374. self.assertNotIn('foo', cfg)
  375. foo = cfg['foo']
  376. assert isinstance(foo, LazyConfigValue)
  377. self.assertIn('foo', cfg)
  378. def test_merge_no_copies(self):
  379. c = Config()
  380. c2 = Config()
  381. c2.Foo.trait = []
  382. c.merge(c2)
  383. c2.Foo.trait.append(1)
  384. self.assertIs(c.Foo, c2.Foo)
  385. self.assertEqual(c.Foo.trait, [1])
  386. self.assertEqual(c2.Foo.trait, [1])