##// END OF EJS Templates
copy values in Config.merge
MinRK -
Show More
@@ -1,802 +1,802
1 1 """A simple configuration system.
2 2
3 3 Inheritance diagram:
4 4
5 5 .. inheritance-diagram:: IPython.config.loader
6 6 :parts: 3
7 7
8 8 Authors
9 9 -------
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min RK
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 import argparse
27 27 import copy
28 28 import os
29 29 import re
30 30 import sys
31 31
32 32 from IPython.utils.path import filefind, get_ipython_dir
33 33 from IPython.utils import py3compat, warn
34 34 from IPython.utils.encoding import DEFAULT_ENCODING
35 35 from IPython.utils.py3compat import unicode_type, iteritems
36 36 from IPython.utils.traitlets import HasTraits, List, Any, TraitError
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Exceptions
40 40 #-----------------------------------------------------------------------------
41 41
42 42
43 43 class ConfigError(Exception):
44 44 pass
45 45
46 46 class ConfigLoaderError(ConfigError):
47 47 pass
48 48
49 49 class ConfigFileNotFound(ConfigError):
50 50 pass
51 51
52 52 class ArgumentError(ConfigLoaderError):
53 53 pass
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Argparse fix
57 57 #-----------------------------------------------------------------------------
58 58
59 59 # Unfortunately argparse by default prints help messages to stderr instead of
60 60 # stdout. This makes it annoying to capture long help screens at the command
61 61 # line, since one must know how to pipe stderr, which many users don't know how
62 62 # to do. So we override the print_help method with one that defaults to
63 63 # stdout and use our class instead.
64 64
65 65 class ArgumentParser(argparse.ArgumentParser):
66 66 """Simple argparse subclass that prints help to stdout by default."""
67 67
68 68 def print_help(self, file=None):
69 69 if file is None:
70 70 file = sys.stdout
71 71 return super(ArgumentParser, self).print_help(file)
72 72
73 73 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # Config class for holding config information
77 77 #-----------------------------------------------------------------------------
78 78
79 79 class LazyConfigValue(HasTraits):
80 80 """Proxy object for exposing methods on configurable containers
81 81
82 82 Exposes:
83 83
84 84 - append, extend, insert on lists
85 85 - update on dicts
86 86 - update, add on sets
87 87 """
88 88
89 89 _value = None
90 90
91 91 # list methods
92 92 _extend = List()
93 93 _prepend = List()
94 94
95 95 def append(self, obj):
96 96 self._extend.append(obj)
97 97
98 98 def extend(self, other):
99 99 self._extend.extend(other)
100 100
101 101 def prepend(self, other):
102 102 """like list.extend, but for the front"""
103 103 self._prepend[:0] = other
104 104
105 105 _inserts = List()
106 106 def insert(self, index, other):
107 107 if not isinstance(index, int):
108 108 raise TypeError("An integer is required")
109 109 self._inserts.append((index, other))
110 110
111 111 # dict methods
112 112 # update is used for both dict and set
113 113 _update = Any()
114 114 def update(self, other):
115 115 if self._update is None:
116 116 if isinstance(other, dict):
117 117 self._update = {}
118 118 else:
119 119 self._update = set()
120 120 self._update.update(other)
121 121
122 122 # set methods
123 123 def add(self, obj):
124 124 self.update({obj})
125 125
126 126 def get_value(self, initial):
127 127 """construct the value from the initial one
128 128
129 129 after applying any insert / extend / update changes
130 130 """
131 131 if self._value is not None:
132 132 return self._value
133 133 value = copy.deepcopy(initial)
134 134 if isinstance(value, list):
135 135 for idx, obj in self._inserts:
136 136 value.insert(idx, obj)
137 137 value[:0] = self._prepend
138 138 value.extend(self._extend)
139 139
140 140 elif isinstance(value, dict):
141 141 if self._update:
142 142 value.update(self._update)
143 143 elif isinstance(value, set):
144 144 if self._update:
145 145 value.update(self._update)
146 146 self._value = value
147 147 return value
148 148
149 149 def to_dict(self):
150 150 """return JSONable dict form of my data
151 151
152 152 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
153 153 """
154 154 d = {}
155 155 if self._update:
156 156 d['update'] = self._update
157 157 if self._extend:
158 158 d['extend'] = self._extend
159 159 if self._prepend:
160 160 d['prepend'] = self._prepend
161 161 elif self._inserts:
162 162 d['inserts'] = self._inserts
163 163 return d
164 164
165 165
166 166 def _is_section_key(key):
167 167 """Is a Config key a section name (does it start with a capital)?"""
168 168 if key and key[0].upper()==key[0] and not key.startswith('_'):
169 169 return True
170 170 else:
171 171 return False
172 172
173 173
174 174 class Config(dict):
175 175 """An attribute based dict that can do smart merges."""
176 176
177 177 def __init__(self, *args, **kwds):
178 178 dict.__init__(self, *args, **kwds)
179 179 self._ensure_subconfig()
180 180
181 181 def _ensure_subconfig(self):
182 182 """ensure that sub-dicts that should be Config objects are
183 183
184 184 casts dicts that are under section keys to Config objects,
185 185 which is necessary for constructing Config objects from dict literals.
186 186 """
187 187 for key in self:
188 188 obj = self[key]
189 189 if _is_section_key(key) \
190 190 and isinstance(obj, dict) \
191 191 and not isinstance(obj, Config):
192 192 setattr(self, key, Config(obj))
193 193
194 194 def _merge(self, other):
195 195 """deprecated alias, use Config.merge()"""
196 196 self.merge(other)
197 197
198 198 def merge(self, other):
199 199 """merge another config object into this one"""
200 200 to_update = {}
201 201 for k, v in iteritems(other):
202 202 if k not in self:
203 to_update[k] = v
203 to_update[k] = copy.deepcopy(v)
204 204 else: # I have this key
205 205 if isinstance(v, Config) and isinstance(self[k], Config):
206 206 # Recursively merge common sub Configs
207 207 self[k].merge(v)
208 208 else:
209 209 # Plain updates for non-Configs
210 to_update[k] = v
210 to_update[k] = copy.deepcopy(v)
211 211
212 212 self.update(to_update)
213 213
214 214 def __contains__(self, key):
215 215 # allow nested contains of the form `"Section.key" in config`
216 216 if '.' in key:
217 217 first, remainder = key.split('.', 1)
218 218 if first not in self:
219 219 return False
220 220 return remainder in self[first]
221 221
222 222 return super(Config, self).__contains__(key)
223 223
224 224 # .has_key is deprecated for dictionaries.
225 225 has_key = __contains__
226 226
227 227 def _has_section(self, key):
228 228 return _is_section_key(key) and key in self
229 229
230 230 def copy(self):
231 231 return type(self)(dict.copy(self))
232 232
233 233 def __copy__(self):
234 234 return self.copy()
235 235
236 236 def __deepcopy__(self, memo):
237 237 import copy
238 238 return type(self)(copy.deepcopy(list(self.items())))
239 239
240 240 def __getitem__(self, key):
241 241 try:
242 242 return dict.__getitem__(self, key)
243 243 except KeyError:
244 244 if _is_section_key(key):
245 245 c = Config()
246 246 dict.__setitem__(self, key, c)
247 247 return c
248 248 else:
249 249 # undefined, create lazy value, used for container methods
250 250 v = LazyConfigValue()
251 251 dict.__setitem__(self, key, v)
252 252 return v
253 253
254 254 def __setitem__(self, key, value):
255 255 if _is_section_key(key):
256 256 if not isinstance(value, Config):
257 257 raise ValueError('values whose keys begin with an uppercase '
258 258 'char must be Config instances: %r, %r' % (key, value))
259 259 dict.__setitem__(self, key, value)
260 260
261 261 def __getattr__(self, key):
262 262 if key.startswith('__'):
263 263 return dict.__getattr__(self, key)
264 264 try:
265 265 return self.__getitem__(key)
266 266 except KeyError as e:
267 267 raise AttributeError(e)
268 268
269 269 def __setattr__(self, key, value):
270 270 if key.startswith('__'):
271 271 return dict.__setattr__(self, key, value)
272 272 try:
273 273 self.__setitem__(key, value)
274 274 except KeyError as e:
275 275 raise AttributeError(e)
276 276
277 277 def __delattr__(self, key):
278 278 if key.startswith('__'):
279 279 return dict.__delattr__(self, key)
280 280 try:
281 281 dict.__delitem__(self, key)
282 282 except KeyError as e:
283 283 raise AttributeError(e)
284 284
285 285
286 286 #-----------------------------------------------------------------------------
287 287 # Config loading classes
288 288 #-----------------------------------------------------------------------------
289 289
290 290
291 291 class ConfigLoader(object):
292 292 """A object for loading configurations from just about anywhere.
293 293
294 294 The resulting configuration is packaged as a :class:`Struct`.
295 295
296 296 Notes
297 297 -----
298 298 A :class:`ConfigLoader` does one thing: load a config from a source
299 299 (file, command line arguments) and returns the data as a :class:`Struct`.
300 300 There are lots of things that :class:`ConfigLoader` does not do. It does
301 301 not implement complex logic for finding config files. It does not handle
302 302 default values or merge multiple configs. These things need to be
303 303 handled elsewhere.
304 304 """
305 305
306 306 def __init__(self):
307 307 """A base class for config loaders.
308 308
309 309 Examples
310 310 --------
311 311
312 312 >>> cl = ConfigLoader()
313 313 >>> config = cl.load_config()
314 314 >>> config
315 315 {}
316 316 """
317 317 self.clear()
318 318
319 319 def clear(self):
320 320 self.config = Config()
321 321
322 322 def load_config(self):
323 323 """Load a config from somewhere, return a :class:`Config` instance.
324 324
325 325 Usually, this will cause self.config to be set and then returned.
326 326 However, in most cases, :meth:`ConfigLoader.clear` should be called
327 327 to erase any previous state.
328 328 """
329 329 self.clear()
330 330 return self.config
331 331
332 332
333 333 class FileConfigLoader(ConfigLoader):
334 334 """A base class for file based configurations.
335 335
336 336 As we add more file based config loaders, the common logic should go
337 337 here.
338 338 """
339 339 pass
340 340
341 341
342 342 class PyFileConfigLoader(FileConfigLoader):
343 343 """A config loader for pure python files.
344 344
345 345 This calls execfile on a plain python file and looks for attributes
346 346 that are all caps. These attribute are added to the config Struct.
347 347 """
348 348
349 349 def __init__(self, filename, path=None):
350 350 """Build a config loader for a filename and path.
351 351
352 352 Parameters
353 353 ----------
354 354 filename : str
355 355 The file name of the config file.
356 356 path : str, list, tuple
357 357 The path to search for the config file on, or a sequence of
358 358 paths to try in order.
359 359 """
360 360 super(PyFileConfigLoader, self).__init__()
361 361 self.filename = filename
362 362 self.path = path
363 363 self.full_filename = ''
364 364 self.data = None
365 365
366 366 def load_config(self):
367 367 """Load the config from a file and return it as a Struct."""
368 368 self.clear()
369 369 try:
370 370 self._find_file()
371 371 except IOError as e:
372 372 raise ConfigFileNotFound(str(e))
373 373 self._read_file_as_dict()
374 374 self._convert_to_config()
375 375 return self.config
376 376
377 377 def _find_file(self):
378 378 """Try to find the file by searching the paths."""
379 379 self.full_filename = filefind(self.filename, self.path)
380 380
381 381 def _read_file_as_dict(self):
382 382 """Load the config file into self.config, with recursive loading."""
383 383 # This closure is made available in the namespace that is used
384 384 # to exec the config file. It allows users to call
385 385 # load_subconfig('myconfig.py') to load config files recursively.
386 386 # It needs to be a closure because it has references to self.path
387 387 # and self.config. The sub-config is loaded with the same path
388 388 # as the parent, but it uses an empty config which is then merged
389 389 # with the parents.
390 390
391 391 # If a profile is specified, the config file will be loaded
392 392 # from that profile
393 393
394 394 def load_subconfig(fname, profile=None):
395 395 # import here to prevent circular imports
396 396 from IPython.core.profiledir import ProfileDir, ProfileDirError
397 397 if profile is not None:
398 398 try:
399 399 profile_dir = ProfileDir.find_profile_dir_by_name(
400 400 get_ipython_dir(),
401 401 profile,
402 402 )
403 403 except ProfileDirError:
404 404 return
405 405 path = profile_dir.location
406 406 else:
407 407 path = self.path
408 408 loader = PyFileConfigLoader(fname, path)
409 409 try:
410 410 sub_config = loader.load_config()
411 411 except ConfigFileNotFound:
412 412 # Pass silently if the sub config is not there. This happens
413 413 # when a user s using a profile, but not the default config.
414 414 pass
415 415 else:
416 416 self.config.merge(sub_config)
417 417
418 418 # Again, this needs to be a closure and should be used in config
419 419 # files to get the config being loaded.
420 420 def get_config():
421 421 return self.config
422 422
423 423 namespace = dict(
424 424 load_subconfig=load_subconfig,
425 425 get_config=get_config,
426 426 __file__=self.full_filename,
427 427 )
428 428 fs_encoding = sys.getfilesystemencoding() or 'ascii'
429 429 conf_filename = self.full_filename.encode(fs_encoding)
430 430 py3compat.execfile(conf_filename, namespace)
431 431
432 432 def _convert_to_config(self):
433 433 if self.data is None:
434 434 ConfigLoaderError('self.data does not exist')
435 435
436 436
437 437 class CommandLineConfigLoader(ConfigLoader):
438 438 """A config loader for command line arguments.
439 439
440 440 As we add more command line based loaders, the common logic should go
441 441 here.
442 442 """
443 443
444 444 def _exec_config_str(self, lhs, rhs):
445 445 """execute self.config.<lhs> = <rhs>
446 446
447 447 * expands ~ with expanduser
448 448 * tries to assign with raw eval, otherwise assigns with just the string,
449 449 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
450 450 equivalent are `--C.a=4` and `--C.a='4'`.
451 451 """
452 452 rhs = os.path.expanduser(rhs)
453 453 try:
454 454 # Try to see if regular Python syntax will work. This
455 455 # won't handle strings as the quote marks are removed
456 456 # by the system shell.
457 457 value = eval(rhs)
458 458 except (NameError, SyntaxError):
459 459 # This case happens if the rhs is a string.
460 460 value = rhs
461 461
462 462 exec(u'self.config.%s = value' % lhs)
463 463
464 464 def _load_flag(self, cfg):
465 465 """update self.config from a flag, which can be a dict or Config"""
466 466 if isinstance(cfg, (dict, Config)):
467 467 # don't clobber whole config sections, update
468 468 # each section from config:
469 469 for sec,c in iteritems(cfg):
470 470 self.config[sec].update(c)
471 471 else:
472 472 raise TypeError("Invalid flag: %r" % cfg)
473 473
474 474 # raw --identifier=value pattern
475 475 # but *also* accept '-' as wordsep, for aliases
476 476 # accepts: --foo=a
477 477 # --Class.trait=value
478 478 # --alias-name=value
479 479 # rejects: -foo=value
480 480 # --foo
481 481 # --Class.trait
482 482 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
483 483
484 484 # just flags, no assignments, with two *or one* leading '-'
485 485 # accepts: --foo
486 486 # -foo-bar-again
487 487 # rejects: --anything=anything
488 488 # --two.word
489 489
490 490 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
491 491
492 492 class KeyValueConfigLoader(CommandLineConfigLoader):
493 493 """A config loader that loads key value pairs from the command line.
494 494
495 495 This allows command line options to be gives in the following form::
496 496
497 497 ipython --profile="foo" --InteractiveShell.autocall=False
498 498 """
499 499
500 500 def __init__(self, argv=None, aliases=None, flags=None):
501 501 """Create a key value pair config loader.
502 502
503 503 Parameters
504 504 ----------
505 505 argv : list
506 506 A list that has the form of sys.argv[1:] which has unicode
507 507 elements of the form u"key=value". If this is None (default),
508 508 then sys.argv[1:] will be used.
509 509 aliases : dict
510 510 A dict of aliases for configurable traits.
511 511 Keys are the short aliases, Values are the resolved trait.
512 512 Of the form: `{'alias' : 'Configurable.trait'}`
513 513 flags : dict
514 514 A dict of flags, keyed by str name. Vaues can be Config objects,
515 515 dicts, or "key=value" strings. If Config or dict, when the flag
516 516 is triggered, The flag is loaded as `self.config.update(m)`.
517 517
518 518 Returns
519 519 -------
520 520 config : Config
521 521 The resulting Config object.
522 522
523 523 Examples
524 524 --------
525 525
526 526 >>> from IPython.config.loader import KeyValueConfigLoader
527 527 >>> cl = KeyValueConfigLoader()
528 528 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
529 529 >>> sorted(d.items())
530 530 [('A', {'name': 'brian'}), ('B', {'number': 0})]
531 531 """
532 532 self.clear()
533 533 if argv is None:
534 534 argv = sys.argv[1:]
535 535 self.argv = argv
536 536 self.aliases = aliases or {}
537 537 self.flags = flags or {}
538 538
539 539
540 540 def clear(self):
541 541 super(KeyValueConfigLoader, self).clear()
542 542 self.extra_args = []
543 543
544 544
545 545 def _decode_argv(self, argv, enc=None):
546 546 """decode argv if bytes, using stin.encoding, falling back on default enc"""
547 547 uargv = []
548 548 if enc is None:
549 549 enc = DEFAULT_ENCODING
550 550 for arg in argv:
551 551 if not isinstance(arg, unicode_type):
552 552 # only decode if not already decoded
553 553 arg = arg.decode(enc)
554 554 uargv.append(arg)
555 555 return uargv
556 556
557 557
558 558 def load_config(self, argv=None, aliases=None, flags=None):
559 559 """Parse the configuration and generate the Config object.
560 560
561 561 After loading, any arguments that are not key-value or
562 562 flags will be stored in self.extra_args - a list of
563 563 unparsed command-line arguments. This is used for
564 564 arguments such as input files or subcommands.
565 565
566 566 Parameters
567 567 ----------
568 568 argv : list, optional
569 569 A list that has the form of sys.argv[1:] which has unicode
570 570 elements of the form u"key=value". If this is None (default),
571 571 then self.argv will be used.
572 572 aliases : dict
573 573 A dict of aliases for configurable traits.
574 574 Keys are the short aliases, Values are the resolved trait.
575 575 Of the form: `{'alias' : 'Configurable.trait'}`
576 576 flags : dict
577 577 A dict of flags, keyed by str name. Values can be Config objects
578 578 or dicts. When the flag is triggered, The config is loaded as
579 579 `self.config.update(cfg)`.
580 580 """
581 581 self.clear()
582 582 if argv is None:
583 583 argv = self.argv
584 584 if aliases is None:
585 585 aliases = self.aliases
586 586 if flags is None:
587 587 flags = self.flags
588 588
589 589 # ensure argv is a list of unicode strings:
590 590 uargv = self._decode_argv(argv)
591 591 for idx,raw in enumerate(uargv):
592 592 # strip leading '-'
593 593 item = raw.lstrip('-')
594 594
595 595 if raw == '--':
596 596 # don't parse arguments after '--'
597 597 # this is useful for relaying arguments to scripts, e.g.
598 598 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
599 599 self.extra_args.extend(uargv[idx+1:])
600 600 break
601 601
602 602 if kv_pattern.match(raw):
603 603 lhs,rhs = item.split('=',1)
604 604 # Substitute longnames for aliases.
605 605 if lhs in aliases:
606 606 lhs = aliases[lhs]
607 607 if '.' not in lhs:
608 608 # probably a mistyped alias, but not technically illegal
609 609 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
610 610 try:
611 611 self._exec_config_str(lhs, rhs)
612 612 except Exception:
613 613 raise ArgumentError("Invalid argument: '%s'" % raw)
614 614
615 615 elif flag_pattern.match(raw):
616 616 if item in flags:
617 617 cfg,help = flags[item]
618 618 self._load_flag(cfg)
619 619 else:
620 620 raise ArgumentError("Unrecognized flag: '%s'"%raw)
621 621 elif raw.startswith('-'):
622 622 kv = '--'+item
623 623 if kv_pattern.match(kv):
624 624 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
625 625 else:
626 626 raise ArgumentError("Invalid argument: '%s'"%raw)
627 627 else:
628 628 # keep all args that aren't valid in a list,
629 629 # in case our parent knows what to do with them.
630 630 self.extra_args.append(item)
631 631 return self.config
632 632
633 633 class ArgParseConfigLoader(CommandLineConfigLoader):
634 634 """A loader that uses the argparse module to load from the command line."""
635 635
636 636 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
637 637 """Create a config loader for use with argparse.
638 638
639 639 Parameters
640 640 ----------
641 641
642 642 argv : optional, list
643 643 If given, used to read command-line arguments from, otherwise
644 644 sys.argv[1:] is used.
645 645
646 646 parser_args : tuple
647 647 A tuple of positional arguments that will be passed to the
648 648 constructor of :class:`argparse.ArgumentParser`.
649 649
650 650 parser_kw : dict
651 651 A tuple of keyword arguments that will be passed to the
652 652 constructor of :class:`argparse.ArgumentParser`.
653 653
654 654 Returns
655 655 -------
656 656 config : Config
657 657 The resulting Config object.
658 658 """
659 659 super(CommandLineConfigLoader, self).__init__()
660 660 self.clear()
661 661 if argv is None:
662 662 argv = sys.argv[1:]
663 663 self.argv = argv
664 664 self.aliases = aliases or {}
665 665 self.flags = flags or {}
666 666
667 667 self.parser_args = parser_args
668 668 self.version = parser_kw.pop("version", None)
669 669 kwargs = dict(argument_default=argparse.SUPPRESS)
670 670 kwargs.update(parser_kw)
671 671 self.parser_kw = kwargs
672 672
673 673 def load_config(self, argv=None, aliases=None, flags=None):
674 674 """Parse command line arguments and return as a Config object.
675 675
676 676 Parameters
677 677 ----------
678 678
679 679 args : optional, list
680 680 If given, a list with the structure of sys.argv[1:] to parse
681 681 arguments from. If not given, the instance's self.argv attribute
682 682 (given at construction time) is used."""
683 683 self.clear()
684 684 if argv is None:
685 685 argv = self.argv
686 686 if aliases is None:
687 687 aliases = self.aliases
688 688 if flags is None:
689 689 flags = self.flags
690 690 self._create_parser(aliases, flags)
691 691 self._parse_args(argv)
692 692 self._convert_to_config()
693 693 return self.config
694 694
695 695 def get_extra_args(self):
696 696 if hasattr(self, 'extra_args'):
697 697 return self.extra_args
698 698 else:
699 699 return []
700 700
701 701 def _create_parser(self, aliases=None, flags=None):
702 702 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
703 703 self._add_arguments(aliases, flags)
704 704
705 705 def _add_arguments(self, aliases=None, flags=None):
706 706 raise NotImplementedError("subclasses must implement _add_arguments")
707 707
708 708 def _parse_args(self, args):
709 709 """self.parser->self.parsed_data"""
710 710 # decode sys.argv to support unicode command-line options
711 711 enc = DEFAULT_ENCODING
712 712 uargs = [py3compat.cast_unicode(a, enc) for a in args]
713 713 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
714 714
715 715 def _convert_to_config(self):
716 716 """self.parsed_data->self.config"""
717 717 for k, v in iteritems(vars(self.parsed_data)):
718 718 exec("self.config.%s = v"%k, locals(), globals())
719 719
720 720 class KVArgParseConfigLoader(ArgParseConfigLoader):
721 721 """A config loader that loads aliases and flags with argparse,
722 722 but will use KVLoader for the rest. This allows better parsing
723 723 of common args, such as `ipython -c 'print 5'`, but still gets
724 724 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
725 725
726 726 def _add_arguments(self, aliases=None, flags=None):
727 727 self.alias_flags = {}
728 728 # print aliases, flags
729 729 if aliases is None:
730 730 aliases = self.aliases
731 731 if flags is None:
732 732 flags = self.flags
733 733 paa = self.parser.add_argument
734 734 for key,value in iteritems(aliases):
735 735 if key in flags:
736 736 # flags
737 737 nargs = '?'
738 738 else:
739 739 nargs = None
740 740 if len(key) is 1:
741 741 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
742 742 else:
743 743 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
744 744 for key, (value, help) in iteritems(flags):
745 745 if key in self.aliases:
746 746 #
747 747 self.alias_flags[self.aliases[key]] = value
748 748 continue
749 749 if len(key) is 1:
750 750 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
751 751 else:
752 752 paa('--'+key, action='append_const', dest='_flags', const=value)
753 753
754 754 def _convert_to_config(self):
755 755 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
756 756 # remove subconfigs list from namespace before transforming the Namespace
757 757 if '_flags' in self.parsed_data:
758 758 subcs = self.parsed_data._flags
759 759 del self.parsed_data._flags
760 760 else:
761 761 subcs = []
762 762
763 763 for k, v in iteritems(vars(self.parsed_data)):
764 764 if v is None:
765 765 # it was a flag that shares the name of an alias
766 766 subcs.append(self.alias_flags[k])
767 767 else:
768 768 # eval the KV assignment
769 769 self._exec_config_str(k, v)
770 770
771 771 for subc in subcs:
772 772 self._load_flag(subc)
773 773
774 774 if self.extra_args:
775 775 sub_parser = KeyValueConfigLoader()
776 776 sub_parser.load_config(self.extra_args)
777 777 self.config.merge(sub_parser.config)
778 778 self.extra_args = sub_parser.extra_args
779 779
780 780
781 781 def load_pyconfig_files(config_files, path):
782 782 """Load multiple Python config files, merging each of them in turn.
783 783
784 784 Parameters
785 785 ==========
786 786 config_files : list of str
787 787 List of config files names to load and merge into the config.
788 788 path : unicode
789 789 The full path to the location of the config files.
790 790 """
791 791 config = Config()
792 792 for cf in config_files:
793 793 loader = PyFileConfigLoader(cf, path=path)
794 794 try:
795 795 next_config = loader.load_config()
796 796 except ConfigFileNotFound:
797 797 pass
798 798 except:
799 799 raise
800 800 else:
801 801 config.merge(next_config)
802 802 return config
@@ -1,360 +1,375
1 1 # encoding: utf-8
2 2 """
3 3 Tests for IPython.config.configurable
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez (design help)
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 from unittest import TestCase
23 23
24 24 from IPython.config.configurable import (
25 25 Configurable,
26 26 SingletonConfigurable
27 27 )
28 28
29 29 from IPython.utils.traitlets import (
30 30 Integer, Float, Unicode, List, Dict, Set,
31 31 )
32 32
33 33 from IPython.config.loader import Config
34 34 from IPython.utils.py3compat import PY3
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Test cases
38 38 #-----------------------------------------------------------------------------
39 39
40 40
41 41 class MyConfigurable(Configurable):
42 42 a = Integer(1, config=True, help="The integer a.")
43 43 b = Float(1.0, config=True, help="The integer b.")
44 44 c = Unicode('no config')
45 45
46 46
47 47 mc_help=u"""MyConfigurable options
48 48 ----------------------
49 49 --MyConfigurable.a=<Integer>
50 50 Default: 1
51 51 The integer a.
52 52 --MyConfigurable.b=<Float>
53 53 Default: 1.0
54 54 The integer b."""
55 55
56 56 mc_help_inst=u"""MyConfigurable options
57 57 ----------------------
58 58 --MyConfigurable.a=<Integer>
59 59 Current: 5
60 60 The integer a.
61 61 --MyConfigurable.b=<Float>
62 62 Current: 4.0
63 63 The integer b."""
64 64
65 65 # On Python 3, the Integer trait is a synonym for Int
66 66 if PY3:
67 67 mc_help = mc_help.replace(u"<Integer>", u"<Int>")
68 68 mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>")
69 69
70 70 class Foo(Configurable):
71 71 a = Integer(0, config=True, help="The integer a.")
72 72 b = Unicode('nope', config=True)
73 73
74 74
75 75 class Bar(Foo):
76 76 b = Unicode('gotit', config=False, help="The string b.")
77 77 c = Float(config=True, help="The string c.")
78 78
79 79
80 80 class TestConfigurable(TestCase):
81 81
82 82 def test_default(self):
83 83 c1 = Configurable()
84 84 c2 = Configurable(config=c1.config)
85 85 c3 = Configurable(config=c2.config)
86 86 self.assertEqual(c1.config, c2.config)
87 87 self.assertEqual(c2.config, c3.config)
88 88
89 89 def test_custom(self):
90 90 config = Config()
91 91 config.foo = 'foo'
92 92 config.bar = 'bar'
93 93 c1 = Configurable(config=config)
94 94 c2 = Configurable(config=c1.config)
95 95 c3 = Configurable(config=c2.config)
96 96 self.assertEqual(c1.config, config)
97 97 self.assertEqual(c2.config, config)
98 98 self.assertEqual(c3.config, config)
99 99 # Test that copies are not made
100 100 self.assertTrue(c1.config is config)
101 101 self.assertTrue(c2.config is config)
102 102 self.assertTrue(c3.config is config)
103 103 self.assertTrue(c1.config is c2.config)
104 104 self.assertTrue(c2.config is c3.config)
105 105
106 106 def test_inheritance(self):
107 107 config = Config()
108 108 config.MyConfigurable.a = 2
109 109 config.MyConfigurable.b = 2.0
110 110 c1 = MyConfigurable(config=config)
111 111 c2 = MyConfigurable(config=c1.config)
112 112 self.assertEqual(c1.a, config.MyConfigurable.a)
113 113 self.assertEqual(c1.b, config.MyConfigurable.b)
114 114 self.assertEqual(c2.a, config.MyConfigurable.a)
115 115 self.assertEqual(c2.b, config.MyConfigurable.b)
116 116
117 117 def test_parent(self):
118 118 config = Config()
119 119 config.Foo.a = 10
120 120 config.Foo.b = "wow"
121 121 config.Bar.b = 'later'
122 122 config.Bar.c = 100.0
123 123 f = Foo(config=config)
124 124 b = Bar(config=f.config)
125 125 self.assertEqual(f.a, 10)
126 126 self.assertEqual(f.b, 'wow')
127 127 self.assertEqual(b.b, 'gotit')
128 128 self.assertEqual(b.c, 100.0)
129 129
130 130 def test_override1(self):
131 131 config = Config()
132 132 config.MyConfigurable.a = 2
133 133 config.MyConfigurable.b = 2.0
134 134 c = MyConfigurable(a=3, config=config)
135 135 self.assertEqual(c.a, 3)
136 136 self.assertEqual(c.b, config.MyConfigurable.b)
137 137 self.assertEqual(c.c, 'no config')
138 138
139 139 def test_override2(self):
140 140 config = Config()
141 141 config.Foo.a = 1
142 142 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
143 143 config.Bar.c = 10.0
144 144 c = Bar(config=config)
145 145 self.assertEqual(c.a, config.Foo.a)
146 146 self.assertEqual(c.b, 'gotit')
147 147 self.assertEqual(c.c, config.Bar.c)
148 148 c = Bar(a=2, b='and', c=20.0, config=config)
149 149 self.assertEqual(c.a, 2)
150 150 self.assertEqual(c.b, 'and')
151 151 self.assertEqual(c.c, 20.0)
152 152
153 153 def test_help(self):
154 154 self.assertEqual(MyConfigurable.class_get_help(), mc_help)
155 155
156 156 def test_help_inst(self):
157 157 inst = MyConfigurable(a=5, b=4)
158 158 self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
159 159
160 160
161 161 class TestSingletonConfigurable(TestCase):
162 162
163 163 def test_instance(self):
164 164 class Foo(SingletonConfigurable): pass
165 165 self.assertEqual(Foo.initialized(), False)
166 166 foo = Foo.instance()
167 167 self.assertEqual(Foo.initialized(), True)
168 168 self.assertEqual(foo, Foo.instance())
169 169 self.assertEqual(SingletonConfigurable._instance, None)
170 170
171 171 def test_inheritance(self):
172 172 class Bar(SingletonConfigurable): pass
173 173 class Bam(Bar): pass
174 174 self.assertEqual(Bar.initialized(), False)
175 175 self.assertEqual(Bam.initialized(), False)
176 176 bam = Bam.instance()
177 177 bam == Bar.instance()
178 178 self.assertEqual(Bar.initialized(), True)
179 179 self.assertEqual(Bam.initialized(), True)
180 180 self.assertEqual(bam, Bam._instance)
181 181 self.assertEqual(bam, Bar._instance)
182 182 self.assertEqual(SingletonConfigurable._instance, None)
183 183
184 184
185 185 class MyParent(Configurable):
186 186 pass
187 187
188 188 class MyParent2(MyParent):
189 189 pass
190 190
191 191 class TestParentConfigurable(TestCase):
192 192
193 193 def test_parent_config(self):
194 194 cfg = Config({
195 195 'MyParent' : {
196 196 'MyConfigurable' : {
197 197 'b' : 2.0,
198 198 }
199 199 }
200 200 })
201 201 parent = MyParent(config=cfg)
202 202 myc = MyConfigurable(parent=parent)
203 203 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
204 204
205 205 def test_parent_inheritance(self):
206 206 cfg = Config({
207 207 'MyParent' : {
208 208 'MyConfigurable' : {
209 209 'b' : 2.0,
210 210 }
211 211 }
212 212 })
213 213 parent = MyParent2(config=cfg)
214 214 myc = MyConfigurable(parent=parent)
215 215 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
216 216
217 217 def test_multi_parent(self):
218 218 cfg = Config({
219 219 'MyParent2' : {
220 220 'MyParent' : {
221 221 'MyConfigurable' : {
222 222 'b' : 2.0,
223 223 }
224 224 },
225 225 # this one shouldn't count
226 226 'MyConfigurable' : {
227 227 'b' : 3.0,
228 228 },
229 229 }
230 230 })
231 231 parent2 = MyParent2(config=cfg)
232 232 parent = MyParent(parent=parent2)
233 233 myc = MyConfigurable(parent=parent)
234 234 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
235 235
236 236 def test_parent_priority(self):
237 237 cfg = Config({
238 238 'MyConfigurable' : {
239 239 'b' : 2.0,
240 240 },
241 241 'MyParent' : {
242 242 'MyConfigurable' : {
243 243 'b' : 3.0,
244 244 }
245 245 },
246 246 'MyParent2' : {
247 247 'MyConfigurable' : {
248 248 'b' : 4.0,
249 249 }
250 250 }
251 251 })
252 252 parent = MyParent2(config=cfg)
253 253 myc = MyConfigurable(parent=parent)
254 254 self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
255 255
256 256 def test_multi_parent_priority(self):
257 257 cfg = Config({
258 258 'MyConfigurable' : {
259 259 'b' : 2.0,
260 260 },
261 261 'MyParent' : {
262 262 'MyConfigurable' : {
263 263 'b' : 3.0,
264 264 }
265 265 },
266 266 'MyParent2' : {
267 267 'MyConfigurable' : {
268 268 'b' : 4.0,
269 269 }
270 270 },
271 271 'MyParent2' : {
272 272 'MyParent' : {
273 273 'MyConfigurable' : {
274 274 'b' : 5.0,
275 275 }
276 276 }
277 277 }
278 278 })
279 279 parent2 = MyParent2(config=cfg)
280 280 parent = MyParent2(parent=parent2)
281 281 myc = MyConfigurable(parent=parent)
282 282 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
283 283
284 284 class Containers(Configurable):
285 285 lis = List(config=True)
286 286 def _lis_default(self):
287 287 return [-1]
288 288
289 289 s = Set(config=True)
290 290 def _s_default(self):
291 291 return {'a'}
292 292
293 293 d = Dict(config=True)
294 294 def _d_default(self):
295 295 return {'a' : 'b'}
296 296
297 297 class TestConfigContainers(TestCase):
298 298 def test_extend(self):
299 299 c = Config()
300 300 c.Containers.lis.extend(list(range(5)))
301 301 obj = Containers(config=c)
302 302 self.assertEqual(obj.lis, list(range(-1,5)))
303 303
304 304 def test_insert(self):
305 305 c = Config()
306 306 c.Containers.lis.insert(0, 'a')
307 307 c.Containers.lis.insert(1, 'b')
308 308 obj = Containers(config=c)
309 309 self.assertEqual(obj.lis, ['a', 'b', -1])
310 310
311 311 def test_prepend(self):
312 312 c = Config()
313 313 c.Containers.lis.prepend([1,2])
314 314 c.Containers.lis.prepend([2,3])
315 315 obj = Containers(config=c)
316 316 self.assertEqual(obj.lis, [2,3,1,2,-1])
317 317
318 318 def test_prepend_extend(self):
319 319 c = Config()
320 320 c.Containers.lis.prepend([1,2])
321 321 c.Containers.lis.extend([2,3])
322 322 obj = Containers(config=c)
323 323 self.assertEqual(obj.lis, [1,2,-1,2,3])
324 324
325 325 def test_append_extend(self):
326 326 c = Config()
327 327 c.Containers.lis.append([1,2])
328 328 c.Containers.lis.extend([2,3])
329 329 obj = Containers(config=c)
330 330 self.assertEqual(obj.lis, [-1,[1,2],2,3])
331 331
332 332 def test_extend_append(self):
333 333 c = Config()
334 334 c.Containers.lis.extend([2,3])
335 335 c.Containers.lis.append([1,2])
336 336 obj = Containers(config=c)
337 337 self.assertEqual(obj.lis, [-1,2,3,[1,2]])
338 338
339 339 def test_insert_extend(self):
340 340 c = Config()
341 341 c.Containers.lis.insert(0, 1)
342 342 c.Containers.lis.extend([2,3])
343 343 obj = Containers(config=c)
344 344 self.assertEqual(obj.lis, [1,-1,2,3])
345 345
346 346 def test_set_update(self):
347 347 c = Config()
348 348 c.Containers.s.update({0,1,2})
349 349 c.Containers.s.update({3})
350 350 obj = Containers(config=c)
351 351 self.assertEqual(obj.s, {'a', 0, 1, 2, 3})
352 352
353 353 def test_dict_update(self):
354 354 c = Config()
355 355 c.Containers.d.update({'c' : 'd'})
356 356 c.Containers.d.update({'e' : 'f'})
357 357 obj = Containers(config=c)
358 358 self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'})
359 359
360 def test_update_twice(self):
361 c = Config()
362 c.MyConfigurable.a = 5
363 m = MyConfigurable(config=c)
364 self.assertEqual(m.a, 5)
365
366 c2 = Config()
367 c2.MyConfigurable.a = 10
368 m.update_config(c2)
369 self.assertEqual(m.a, 10)
370
371 c2.MyConfigurable.a = 15
372 m.update_config(c2)
373 self.assertEqual(m.a, 15)
374
360 375
@@ -1,327 +1,337
1 1 # encoding: utf-8
2 2 """
3 3 Tests for IPython.config.loader
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez (design help)
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import os
23 23 import pickle
24 24 import sys
25 25 from tempfile import mkstemp
26 26 from unittest import TestCase
27 27
28 28 from nose import SkipTest
29 29
30 30 from IPython.testing.tools import mute_warn
31 31
32 32 from IPython.config.loader import (
33 33 Config,
34 34 LazyConfigValue,
35 35 PyFileConfigLoader,
36 36 KeyValueConfigLoader,
37 37 ArgParseConfigLoader,
38 38 KVArgParseConfigLoader,
39 39 ConfigError,
40 40 )
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Actual tests
44 44 #-----------------------------------------------------------------------------
45 45
46 46
47 47 pyfile = """
48 48 c = get_config()
49 49 c.a=10
50 50 c.b=20
51 51 c.Foo.Bar.value=10
52 52 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3
53 53 c.D.C.value='hi there'
54 54 """
55 55
56 56 class TestPyFileCL(TestCase):
57 57
58 58 def test_basic(self):
59 59 fd, fname = mkstemp('.py')
60 60 f = os.fdopen(fd, 'w')
61 61 f.write(pyfile)
62 62 f.close()
63 63 # Unlink the file
64 64 cl = PyFileConfigLoader(fname)
65 65 config = cl.load_config()
66 66 self.assertEqual(config.a, 10)
67 67 self.assertEqual(config.b, 20)
68 68 self.assertEqual(config.Foo.Bar.value, 10)
69 69 self.assertEqual(config.Foo.Bam.value, list(range(10)))
70 70 self.assertEqual(config.D.C.value, 'hi there')
71 71
72 72 class MyLoader1(ArgParseConfigLoader):
73 73 def _add_arguments(self, aliases=None, flags=None):
74 74 p = self.parser
75 75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
76 76 p.add_argument('-b', dest='MyClass.bar', type=int)
77 77 p.add_argument('-n', dest='n', action='store_true')
78 78 p.add_argument('Global.bam', type=str)
79 79
80 80 class MyLoader2(ArgParseConfigLoader):
81 81 def _add_arguments(self, aliases=None, flags=None):
82 82 subparsers = self.parser.add_subparsers(dest='subparser_name')
83 83 subparser1 = subparsers.add_parser('1')
84 84 subparser1.add_argument('-x',dest='Global.x')
85 85 subparser2 = subparsers.add_parser('2')
86 86 subparser2.add_argument('y')
87 87
88 88 class TestArgParseCL(TestCase):
89 89
90 90 def test_basic(self):
91 91 cl = MyLoader1()
92 92 config = cl.load_config('-f hi -b 10 -n wow'.split())
93 93 self.assertEqual(config.Global.foo, 'hi')
94 94 self.assertEqual(config.MyClass.bar, 10)
95 95 self.assertEqual(config.n, True)
96 96 self.assertEqual(config.Global.bam, 'wow')
97 97 config = cl.load_config(['wow'])
98 98 self.assertEqual(list(config.keys()), ['Global'])
99 99 self.assertEqual(list(config.Global.keys()), ['bam'])
100 100 self.assertEqual(config.Global.bam, 'wow')
101 101
102 102 def test_add_arguments(self):
103 103 cl = MyLoader2()
104 104 config = cl.load_config('2 frobble'.split())
105 105 self.assertEqual(config.subparser_name, '2')
106 106 self.assertEqual(config.y, 'frobble')
107 107 config = cl.load_config('1 -x frobble'.split())
108 108 self.assertEqual(config.subparser_name, '1')
109 109 self.assertEqual(config.Global.x, 'frobble')
110 110
111 111 def test_argv(self):
112 112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
113 113 config = cl.load_config()
114 114 self.assertEqual(config.Global.foo, 'hi')
115 115 self.assertEqual(config.MyClass.bar, 10)
116 116 self.assertEqual(config.n, True)
117 117 self.assertEqual(config.Global.bam, 'wow')
118 118
119 119
120 120 class TestKeyValueCL(TestCase):
121 121 klass = KeyValueConfigLoader
122 122
123 123 def test_basic(self):
124 124 cl = self.klass()
125 125 argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]]
126 126 with mute_warn():
127 127 config = cl.load_config(argv)
128 128 self.assertEqual(config.a, 10)
129 129 self.assertEqual(config.b, 20)
130 130 self.assertEqual(config.Foo.Bar.value, 10)
131 131 self.assertEqual(config.Foo.Bam.value, list(range(10)))
132 132 self.assertEqual(config.D.C.value, 'hi there')
133 133
134 134 def test_expanduser(self):
135 135 cl = self.klass()
136 136 argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
137 137 with mute_warn():
138 138 config = cl.load_config(argv)
139 139 self.assertEqual(config.a, os.path.expanduser('~/1/2/3'))
140 140 self.assertEqual(config.b, os.path.expanduser('~'))
141 141 self.assertEqual(config.c, os.path.expanduser('~/'))
142 142 self.assertEqual(config.d, '~/')
143 143
144 144 def test_extra_args(self):
145 145 cl = self.klass()
146 146 with mute_warn():
147 147 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
148 148 self.assertEqual(cl.extra_args, ['b', 'd'])
149 149 self.assertEqual(config.a, 5)
150 150 self.assertEqual(config.c, 10)
151 151 with mute_warn():
152 152 config = cl.load_config(['--', '--a=5', '--c=10'])
153 153 self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
154 154
155 155 def test_unicode_args(self):
156 156 cl = self.klass()
157 157 argv = [u'--a=épsîlön']
158 158 with mute_warn():
159 159 config = cl.load_config(argv)
160 160 self.assertEqual(config.a, u'épsîlön')
161 161
162 162 def test_unicode_bytes_args(self):
163 163 uarg = u'--a=é'
164 164 try:
165 165 barg = uarg.encode(sys.stdin.encoding)
166 166 except (TypeError, UnicodeEncodeError):
167 167 raise SkipTest("sys.stdin.encoding can't handle 'é'")
168 168
169 169 cl = self.klass()
170 170 with mute_warn():
171 171 config = cl.load_config([barg])
172 172 self.assertEqual(config.a, u'é')
173 173
174 174 def test_unicode_alias(self):
175 175 cl = self.klass()
176 176 argv = [u'--a=épsîlön']
177 177 with mute_warn():
178 178 config = cl.load_config(argv, aliases=dict(a='A.a'))
179 179 self.assertEqual(config.A.a, u'épsîlön')
180 180
181 181
182 182 class TestArgParseKVCL(TestKeyValueCL):
183 183 klass = KVArgParseConfigLoader
184 184
185 185 def test_expanduser2(self):
186 186 cl = self.klass()
187 187 argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"]
188 188 with mute_warn():
189 189 config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
190 190 self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3'))
191 191 self.assertEqual(config.A.b, '~/1/2/3')
192 192
193 193 def test_eval(self):
194 194 cl = self.klass()
195 195 argv = ['-c', 'a=5']
196 196 with mute_warn():
197 197 config = cl.load_config(argv, aliases=dict(c='A.c'))
198 198 self.assertEqual(config.A.c, u"a=5")
199 199
200 200
201 201 class TestConfig(TestCase):
202 202
203 203 def test_setget(self):
204 204 c = Config()
205 205 c.a = 10
206 206 self.assertEqual(c.a, 10)
207 207 self.assertEqual('b' in c, False)
208 208
209 209 def test_auto_section(self):
210 210 c = Config()
211 211 self.assertNotIn('A', c)
212 212 assert not c._has_section('A')
213 213 A = c.A
214 214 A.foo = 'hi there'
215 215 self.assertIn('A', c)
216 216 assert c._has_section('A')
217 217 self.assertEqual(c.A.foo, 'hi there')
218 218 del c.A
219 219 self.assertEqual(c.A, Config())
220 220
221 221 def test_merge_doesnt_exist(self):
222 222 c1 = Config()
223 223 c2 = Config()
224 224 c2.bar = 10
225 225 c2.Foo.bar = 10
226 226 c1.merge(c2)
227 227 self.assertEqual(c1.Foo.bar, 10)
228 228 self.assertEqual(c1.bar, 10)
229 229 c2.Bar.bar = 10
230 230 c1.merge(c2)
231 231 self.assertEqual(c1.Bar.bar, 10)
232 232
233 233 def test_merge_exists(self):
234 234 c1 = Config()
235 235 c2 = Config()
236 236 c1.Foo.bar = 10
237 237 c1.Foo.bam = 30
238 238 c2.Foo.bar = 20
239 239 c2.Foo.wow = 40
240 240 c1.merge(c2)
241 241 self.assertEqual(c1.Foo.bam, 30)
242 242 self.assertEqual(c1.Foo.bar, 20)
243 243 self.assertEqual(c1.Foo.wow, 40)
244 244 c2.Foo.Bam.bam = 10
245 245 c1.merge(c2)
246 246 self.assertEqual(c1.Foo.Bam.bam, 10)
247 247
248 248 def test_deepcopy(self):
249 249 c1 = Config()
250 250 c1.Foo.bar = 10
251 251 c1.Foo.bam = 30
252 252 c1.a = 'asdf'
253 253 c1.b = range(10)
254 254 import copy
255 255 c2 = copy.deepcopy(c1)
256 256 self.assertEqual(c1, c2)
257 257 self.assertTrue(c1 is not c2)
258 258 self.assertTrue(c1.Foo is not c2.Foo)
259 259
260 260 def test_builtin(self):
261 261 c1 = Config()
262 262 c1.format = "json"
263 263
264 264 def test_fromdict(self):
265 265 c1 = Config({'Foo' : {'bar' : 1}})
266 266 self.assertEqual(c1.Foo.__class__, Config)
267 267 self.assertEqual(c1.Foo.bar, 1)
268 268
269 269 def test_fromdictmerge(self):
270 270 c1 = Config()
271 271 c2 = Config({'Foo' : {'bar' : 1}})
272 272 c1.merge(c2)
273 273 self.assertEqual(c1.Foo.__class__, Config)
274 274 self.assertEqual(c1.Foo.bar, 1)
275 275
276 276 def test_fromdictmerge2(self):
277 277 c1 = Config({'Foo' : {'baz' : 2}})
278 278 c2 = Config({'Foo' : {'bar' : 1}})
279 279 c1.merge(c2)
280 280 self.assertEqual(c1.Foo.__class__, Config)
281 281 self.assertEqual(c1.Foo.bar, 1)
282 282 self.assertEqual(c1.Foo.baz, 2)
283 283 self.assertNotIn('baz', c2.Foo)
284 284
285 285 def test_contains(self):
286 286 c1 = Config({'Foo' : {'baz' : 2}})
287 287 c2 = Config({'Foo' : {'bar' : 1}})
288 288 self.assertIn('Foo', c1)
289 289 self.assertIn('Foo.baz', c1)
290 290 self.assertIn('Foo.bar', c2)
291 291 self.assertNotIn('Foo.bar', c1)
292 292
293 293 def test_pickle_config(self):
294 294 cfg = Config()
295 295 cfg.Foo.bar = 1
296 296 pcfg = pickle.dumps(cfg)
297 297 cfg2 = pickle.loads(pcfg)
298 298 self.assertEqual(cfg2, cfg)
299 299
300 300 def test_getattr_section(self):
301 301 cfg = Config()
302 302 self.assertNotIn('Foo', cfg)
303 303 Foo = cfg.Foo
304 304 assert isinstance(Foo, Config)
305 305 self.assertIn('Foo', cfg)
306 306
307 307 def test_getitem_section(self):
308 308 cfg = Config()
309 309 self.assertNotIn('Foo', cfg)
310 310 Foo = cfg['Foo']
311 311 assert isinstance(Foo, Config)
312 312 self.assertIn('Foo', cfg)
313 313
314 314 def test_getattr_not_section(self):
315 315 cfg = Config()
316 316 self.assertNotIn('foo', cfg)
317 317 foo = cfg.foo
318 318 assert isinstance(foo, LazyConfigValue)
319 319 self.assertIn('foo', cfg)
320 320
321 321 def test_getitem_not_section(self):
322 322 cfg = Config()
323 323 self.assertNotIn('foo', cfg)
324 324 foo = cfg['foo']
325 325 assert isinstance(foo, LazyConfigValue)
326 326 self.assertIn('foo', cfg)
327
328 def test_merge_copies(self):
329 c = Config()
330 c2 = Config()
331 c2.Foo.trait = []
332 c.merge(c2)
333 c2.Foo.trait.append(1)
334 self.assertIsNot(c.Foo, c2.Foo)
335 self.assertEqual(c.Foo.trait, [])
336 self.assertEqual(c2.Foo.trait, [1])
327 337
General Comments 0
You need to be logged in to leave comments. Login now