##// END OF EJS Templates
fix type=str->unicode in argparse kv loader...
MinRK -
Show More
@@ -1,661 +1,661 b''
1 1 """A simple configuration system.
2 2
3 3 Authors
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 7 * Min RK
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import __builtin__ as builtin_mod
22 22 import re
23 23 import sys
24 24
25 25 from IPython.external import argparse
26 26 from IPython.utils.path import filefind, get_ipython_dir
27 27 from IPython.utils import py3compat, text, warn
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Exceptions
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 class ConfigError(Exception):
35 35 pass
36 36
37 37
38 38 class ConfigLoaderError(ConfigError):
39 39 pass
40 40
41 41 class ArgumentError(ConfigLoaderError):
42 42 pass
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Argparse fix
46 46 #-----------------------------------------------------------------------------
47 47
48 48 # Unfortunately argparse by default prints help messages to stderr instead of
49 49 # stdout. This makes it annoying to capture long help screens at the command
50 50 # line, since one must know how to pipe stderr, which many users don't know how
51 51 # to do. So we override the print_help method with one that defaults to
52 52 # stdout and use our class instead.
53 53
54 54 class ArgumentParser(argparse.ArgumentParser):
55 55 """Simple argparse subclass that prints help to stdout by default."""
56 56
57 57 def print_help(self, file=None):
58 58 if file is None:
59 59 file = sys.stdout
60 60 return super(ArgumentParser, self).print_help(file)
61 61
62 62 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Config class for holding config information
66 66 #-----------------------------------------------------------------------------
67 67
68 68
69 69 class Config(dict):
70 70 """An attribute based dict that can do smart merges."""
71 71
72 72 def __init__(self, *args, **kwds):
73 73 dict.__init__(self, *args, **kwds)
74 74 # This sets self.__dict__ = self, but it has to be done this way
75 75 # because we are also overriding __setattr__.
76 76 dict.__setattr__(self, '__dict__', self)
77 77
78 78 def _merge(self, other):
79 79 to_update = {}
80 80 for k, v in other.iteritems():
81 81 if not self.has_key(k):
82 82 to_update[k] = v
83 83 else: # I have this key
84 84 if isinstance(v, Config):
85 85 # Recursively merge common sub Configs
86 86 self[k]._merge(v)
87 87 else:
88 88 # Plain updates for non-Configs
89 89 to_update[k] = v
90 90
91 91 self.update(to_update)
92 92
93 93 def _is_section_key(self, key):
94 94 if key[0].upper()==key[0] and not key.startswith('_'):
95 95 return True
96 96 else:
97 97 return False
98 98
99 99 def __contains__(self, key):
100 100 if self._is_section_key(key):
101 101 return True
102 102 else:
103 103 return super(Config, self).__contains__(key)
104 104 # .has_key is deprecated for dictionaries.
105 105 has_key = __contains__
106 106
107 107 def _has_section(self, key):
108 108 if self._is_section_key(key):
109 109 if super(Config, self).__contains__(key):
110 110 return True
111 111 return False
112 112
113 113 def copy(self):
114 114 return type(self)(dict.copy(self))
115 115
116 116 def __copy__(self):
117 117 return self.copy()
118 118
119 119 def __deepcopy__(self, memo):
120 120 import copy
121 121 return type(self)(copy.deepcopy(self.items()))
122 122
123 123 def __getitem__(self, key):
124 124 # We cannot use directly self._is_section_key, because it triggers
125 125 # infinite recursion on top of PyPy. Instead, we manually fish the
126 126 # bound method.
127 127 is_section_key = self.__class__._is_section_key.__get__(self)
128 128
129 129 # Because we use this for an exec namespace, we need to delegate
130 130 # the lookup of names in __builtin__ to itself. This means
131 131 # that you can't have section or attribute names that are
132 132 # builtins.
133 133 try:
134 134 return getattr(builtin_mod, key)
135 135 except AttributeError:
136 136 pass
137 137 if is_section_key(key):
138 138 try:
139 139 return dict.__getitem__(self, key)
140 140 except KeyError:
141 141 c = Config()
142 142 dict.__setitem__(self, key, c)
143 143 return c
144 144 else:
145 145 return dict.__getitem__(self, key)
146 146
147 147 def __setitem__(self, key, value):
148 148 # Don't allow names in __builtin__ to be modified.
149 149 if hasattr(builtin_mod, key):
150 150 raise ConfigError('Config variable names cannot have the same name '
151 151 'as a Python builtin: %s' % key)
152 152 if self._is_section_key(key):
153 153 if not isinstance(value, Config):
154 154 raise ValueError('values whose keys begin with an uppercase '
155 155 'char must be Config instances: %r, %r' % (key, value))
156 156 else:
157 157 dict.__setitem__(self, key, value)
158 158
159 159 def __getattr__(self, key):
160 160 try:
161 161 return self.__getitem__(key)
162 162 except KeyError, e:
163 163 raise AttributeError(e)
164 164
165 165 def __setattr__(self, key, value):
166 166 try:
167 167 self.__setitem__(key, value)
168 168 except KeyError, e:
169 169 raise AttributeError(e)
170 170
171 171 def __delattr__(self, key):
172 172 try:
173 173 dict.__delitem__(self, key)
174 174 except KeyError, e:
175 175 raise AttributeError(e)
176 176
177 177
178 178 #-----------------------------------------------------------------------------
179 179 # Config loading classes
180 180 #-----------------------------------------------------------------------------
181 181
182 182
183 183 class ConfigLoader(object):
184 184 """A object for loading configurations from just about anywhere.
185 185
186 186 The resulting configuration is packaged as a :class:`Struct`.
187 187
188 188 Notes
189 189 -----
190 190 A :class:`ConfigLoader` does one thing: load a config from a source
191 191 (file, command line arguments) and returns the data as a :class:`Struct`.
192 192 There are lots of things that :class:`ConfigLoader` does not do. It does
193 193 not implement complex logic for finding config files. It does not handle
194 194 default values or merge multiple configs. These things need to be
195 195 handled elsewhere.
196 196 """
197 197
198 198 def __init__(self):
199 199 """A base class for config loaders.
200 200
201 201 Examples
202 202 --------
203 203
204 204 >>> cl = ConfigLoader()
205 205 >>> config = cl.load_config()
206 206 >>> config
207 207 {}
208 208 """
209 209 self.clear()
210 210
211 211 def clear(self):
212 212 self.config = Config()
213 213
214 214 def load_config(self):
215 215 """Load a config from somewhere, return a :class:`Config` instance.
216 216
217 217 Usually, this will cause self.config to be set and then returned.
218 218 However, in most cases, :meth:`ConfigLoader.clear` should be called
219 219 to erase any previous state.
220 220 """
221 221 self.clear()
222 222 return self.config
223 223
224 224
225 225 class FileConfigLoader(ConfigLoader):
226 226 """A base class for file based configurations.
227 227
228 228 As we add more file based config loaders, the common logic should go
229 229 here.
230 230 """
231 231 pass
232 232
233 233
234 234 class PyFileConfigLoader(FileConfigLoader):
235 235 """A config loader for pure python files.
236 236
237 237 This calls execfile on a plain python file and looks for attributes
238 238 that are all caps. These attribute are added to the config Struct.
239 239 """
240 240
241 241 def __init__(self, filename, path=None):
242 242 """Build a config loader for a filename and path.
243 243
244 244 Parameters
245 245 ----------
246 246 filename : str
247 247 The file name of the config file.
248 248 path : str, list, tuple
249 249 The path to search for the config file on, or a sequence of
250 250 paths to try in order.
251 251 """
252 252 super(PyFileConfigLoader, self).__init__()
253 253 self.filename = filename
254 254 self.path = path
255 255 self.full_filename = ''
256 256 self.data = None
257 257
258 258 def load_config(self):
259 259 """Load the config from a file and return it as a Struct."""
260 260 self.clear()
261 261 self._find_file()
262 262 self._read_file_as_dict()
263 263 self._convert_to_config()
264 264 return self.config
265 265
266 266 def _find_file(self):
267 267 """Try to find the file by searching the paths."""
268 268 self.full_filename = filefind(self.filename, self.path)
269 269
270 270 def _read_file_as_dict(self):
271 271 """Load the config file into self.config, with recursive loading."""
272 272 # This closure is made available in the namespace that is used
273 273 # to exec the config file. It allows users to call
274 274 # load_subconfig('myconfig.py') to load config files recursively.
275 275 # It needs to be a closure because it has references to self.path
276 276 # and self.config. The sub-config is loaded with the same path
277 277 # as the parent, but it uses an empty config which is then merged
278 278 # with the parents.
279 279
280 280 # If a profile is specified, the config file will be loaded
281 281 # from that profile
282 282
283 283 def load_subconfig(fname, profile=None):
284 284 # import here to prevent circular imports
285 285 from IPython.core.profiledir import ProfileDir, ProfileDirError
286 286 if profile is not None:
287 287 try:
288 288 profile_dir = ProfileDir.find_profile_dir_by_name(
289 289 get_ipython_dir(),
290 290 profile,
291 291 )
292 292 except ProfileDirError:
293 293 return
294 294 path = profile_dir.location
295 295 else:
296 296 path = self.path
297 297 loader = PyFileConfigLoader(fname, path)
298 298 try:
299 299 sub_config = loader.load_config()
300 300 except IOError:
301 301 # Pass silently if the sub config is not there. This happens
302 302 # when a user s using a profile, but not the default config.
303 303 pass
304 304 else:
305 305 self.config._merge(sub_config)
306 306
307 307 # Again, this needs to be a closure and should be used in config
308 308 # files to get the config being loaded.
309 309 def get_config():
310 310 return self.config
311 311
312 312 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
313 313 fs_encoding = sys.getfilesystemencoding() or 'ascii'
314 314 conf_filename = self.full_filename.encode(fs_encoding)
315 315 py3compat.execfile(conf_filename, namespace)
316 316
317 317 def _convert_to_config(self):
318 318 if self.data is None:
319 319 ConfigLoaderError('self.data does not exist')
320 320
321 321
322 322 class CommandLineConfigLoader(ConfigLoader):
323 323 """A config loader for command line arguments.
324 324
325 325 As we add more command line based loaders, the common logic should go
326 326 here.
327 327 """
328 328
329 329 def _exec_config_str(self, lhs, rhs):
330 330 exec_str = 'self.config.' + lhs + '=' + rhs
331 331 try:
332 332 # Try to see if regular Python syntax will work. This
333 333 # won't handle strings as the quote marks are removed
334 334 # by the system shell.
335 335 exec exec_str in locals(), globals()
336 336 except (NameError, SyntaxError):
337 337 # This case happens if the rhs is a string but without
338 338 # the quote marks. Use repr, to get quote marks, and
339 339 # 'u' prefix and see if
340 340 # it succeeds. If it still fails, we let it raise.
341 341 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
342 342 exec exec_str in locals(), globals()
343 343
344 344 def _load_flag(self, cfg):
345 345 """update self.config from a flag, which can be a dict or Config"""
346 346 if isinstance(cfg, (dict, Config)):
347 347 # don't clobber whole config sections, update
348 348 # each section from config:
349 349 for sec,c in cfg.iteritems():
350 350 self.config[sec].update(c)
351 351 else:
352 352 raise ValueError("Invalid flag: '%s'"%raw)
353 353
354 354 # raw --identifier=value pattern
355 355 # but *also* accept '-' as wordsep, for aliases
356 356 # accepts: --foo=a
357 357 # --Class.trait=value
358 358 # --alias-name=value
359 359 # rejects: -foo=value
360 360 # --foo
361 361 # --Class.trait
362 362 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
363 363
364 364 # just flags, no assignments, with two *or one* leading '-'
365 365 # accepts: --foo
366 366 # -foo-bar-again
367 367 # rejects: --anything=anything
368 368 # --two.word
369 369
370 370 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
371 371
372 372 class KeyValueConfigLoader(CommandLineConfigLoader):
373 373 """A config loader that loads key value pairs from the command line.
374 374
375 375 This allows command line options to be gives in the following form::
376 376
377 377 ipython --profile="foo" --InteractiveShell.autocall=False
378 378 """
379 379
380 380 def __init__(self, argv=None, aliases=None, flags=None):
381 381 """Create a key value pair config loader.
382 382
383 383 Parameters
384 384 ----------
385 385 argv : list
386 386 A list that has the form of sys.argv[1:] which has unicode
387 387 elements of the form u"key=value". If this is None (default),
388 388 then sys.argv[1:] will be used.
389 389 aliases : dict
390 390 A dict of aliases for configurable traits.
391 391 Keys are the short aliases, Values are the resolved trait.
392 392 Of the form: `{'alias' : 'Configurable.trait'}`
393 393 flags : dict
394 394 A dict of flags, keyed by str name. Vaues can be Config objects,
395 395 dicts, or "key=value" strings. If Config or dict, when the flag
396 396 is triggered, The flag is loaded as `self.config.update(m)`.
397 397
398 398 Returns
399 399 -------
400 400 config : Config
401 401 The resulting Config object.
402 402
403 403 Examples
404 404 --------
405 405
406 406 >>> from IPython.config.loader import KeyValueConfigLoader
407 407 >>> cl = KeyValueConfigLoader()
408 408 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
409 409 {'A': {'name': 'brian'}, 'B': {'number': 0}}
410 410 """
411 411 self.clear()
412 412 if argv is None:
413 413 argv = sys.argv[1:]
414 414 self.argv = argv
415 415 self.aliases = aliases or {}
416 416 self.flags = flags or {}
417 417
418 418
419 419 def clear(self):
420 420 super(KeyValueConfigLoader, self).clear()
421 421 self.extra_args = []
422 422
423 423
424 424 def _decode_argv(self, argv, enc=None):
425 425 """decode argv if bytes, using stin.encoding, falling back on default enc"""
426 426 uargv = []
427 427 if enc is None:
428 428 enc = text.getdefaultencoding()
429 429 for arg in argv:
430 430 if not isinstance(arg, unicode):
431 431 # only decode if not already decoded
432 432 arg = arg.decode(enc)
433 433 uargv.append(arg)
434 434 return uargv
435 435
436 436
437 437 def load_config(self, argv=None, aliases=None, flags=None):
438 438 """Parse the configuration and generate the Config object.
439 439
440 440 After loading, any arguments that are not key-value or
441 441 flags will be stored in self.extra_args - a list of
442 442 unparsed command-line arguments. This is used for
443 443 arguments such as input files or subcommands.
444 444
445 445 Parameters
446 446 ----------
447 447 argv : list, optional
448 448 A list that has the form of sys.argv[1:] which has unicode
449 449 elements of the form u"key=value". If this is None (default),
450 450 then self.argv will be used.
451 451 aliases : dict
452 452 A dict of aliases for configurable traits.
453 453 Keys are the short aliases, Values are the resolved trait.
454 454 Of the form: `{'alias' : 'Configurable.trait'}`
455 455 flags : dict
456 456 A dict of flags, keyed by str name. Values can be Config objects
457 457 or dicts. When the flag is triggered, The config is loaded as
458 458 `self.config.update(cfg)`.
459 459 """
460 460 from IPython.config.configurable import Configurable
461 461
462 462 self.clear()
463 463 if argv is None:
464 464 argv = self.argv
465 465 if aliases is None:
466 466 aliases = self.aliases
467 467 if flags is None:
468 468 flags = self.flags
469 469
470 470 # ensure argv is a list of unicode strings:
471 471 uargv = self._decode_argv(argv)
472 472 for idx,raw in enumerate(uargv):
473 473 # strip leading '-'
474 474 item = raw.lstrip('-')
475 475
476 476 if raw == '--':
477 477 # don't parse arguments after '--'
478 478 # this is useful for relaying arguments to scripts, e.g.
479 479 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
480 480 self.extra_args.extend(uargv[idx+1:])
481 481 break
482 482
483 483 if kv_pattern.match(raw):
484 484 lhs,rhs = item.split('=',1)
485 485 # Substitute longnames for aliases.
486 486 if lhs in aliases:
487 487 lhs = aliases[lhs]
488 488 if '.' not in lhs:
489 489 # probably a mistyped alias, but not technically illegal
490 490 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
491 491 self._exec_config_str(lhs, rhs)
492 492
493 493 elif flag_pattern.match(raw):
494 494 if item in flags:
495 495 cfg,help = flags[item]
496 496 self._load_flag(cfg)
497 497 else:
498 498 raise ArgumentError("Unrecognized flag: '%s'"%raw)
499 499 elif raw.startswith('-'):
500 500 kv = '--'+item
501 501 if kv_pattern.match(kv):
502 502 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
503 503 else:
504 504 raise ArgumentError("Invalid argument: '%s'"%raw)
505 505 else:
506 506 # keep all args that aren't valid in a list,
507 507 # in case our parent knows what to do with them.
508 508 self.extra_args.append(item)
509 509 return self.config
510 510
511 511 class ArgParseConfigLoader(CommandLineConfigLoader):
512 512 """A loader that uses the argparse module to load from the command line."""
513 513
514 514 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
515 515 """Create a config loader for use with argparse.
516 516
517 517 Parameters
518 518 ----------
519 519
520 520 argv : optional, list
521 521 If given, used to read command-line arguments from, otherwise
522 522 sys.argv[1:] is used.
523 523
524 524 parser_args : tuple
525 525 A tuple of positional arguments that will be passed to the
526 526 constructor of :class:`argparse.ArgumentParser`.
527 527
528 528 parser_kw : dict
529 529 A tuple of keyword arguments that will be passed to the
530 530 constructor of :class:`argparse.ArgumentParser`.
531 531
532 532 Returns
533 533 -------
534 534 config : Config
535 535 The resulting Config object.
536 536 """
537 537 super(CommandLineConfigLoader, self).__init__()
538 538 self.clear()
539 539 if argv is None:
540 540 argv = sys.argv[1:]
541 541 self.argv = argv
542 542 self.aliases = aliases or {}
543 543 self.flags = flags or {}
544 544
545 545 self.parser_args = parser_args
546 546 self.version = parser_kw.pop("version", None)
547 547 kwargs = dict(argument_default=argparse.SUPPRESS)
548 548 kwargs.update(parser_kw)
549 549 self.parser_kw = kwargs
550 550
551 551 def load_config(self, argv=None, aliases=None, flags=None):
552 552 """Parse command line arguments and return as a Config object.
553 553
554 554 Parameters
555 555 ----------
556 556
557 557 args : optional, list
558 558 If given, a list with the structure of sys.argv[1:] to parse
559 559 arguments from. If not given, the instance's self.argv attribute
560 560 (given at construction time) is used."""
561 561 self.clear()
562 562 if argv is None:
563 563 argv = self.argv
564 564 if aliases is None:
565 565 aliases = self.aliases
566 566 if flags is None:
567 567 flags = self.flags
568 568 self._create_parser(aliases, flags)
569 569 self._parse_args(argv)
570 570 self._convert_to_config()
571 571 return self.config
572 572
573 573 def get_extra_args(self):
574 574 if hasattr(self, 'extra_args'):
575 575 return self.extra_args
576 576 else:
577 577 return []
578 578
579 579 def _create_parser(self, aliases=None, flags=None):
580 580 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
581 581 self._add_arguments(aliases, flags)
582 582
583 583 def _add_arguments(self, aliases=None, flags=None):
584 584 raise NotImplementedError("subclasses must implement _add_arguments")
585 585
586 586 def _parse_args(self, args):
587 587 """self.parser->self.parsed_data"""
588 588 # decode sys.argv to support unicode command-line options
589 589 enc = text.getdefaultencoding()
590 590 uargs = [py3compat.cast_unicode(a, enc) for a in args]
591 591 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
592 592
593 593 def _convert_to_config(self):
594 594 """self.parsed_data->self.config"""
595 595 for k, v in vars(self.parsed_data).iteritems():
596 596 exec "self.config.%s = v"%k in locals(), globals()
597 597
598 598 class KVArgParseConfigLoader(ArgParseConfigLoader):
599 599 """A config loader that loads aliases and flags with argparse,
600 600 but will use KVLoader for the rest. This allows better parsing
601 601 of common args, such as `ipython -c 'print 5'`, but still gets
602 602 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
603 603
604 604 def _convert_to_config(self):
605 605 """self.parsed_data->self.config"""
606 606 for k, v in vars(self.parsed_data).iteritems():
607 607 self._exec_config_str(k, v)
608 608
609 609 def _add_arguments(self, aliases=None, flags=None):
610 610 self.alias_flags = {}
611 611 # print aliases, flags
612 612 if aliases is None:
613 613 aliases = self.aliases
614 614 if flags is None:
615 615 flags = self.flags
616 616 paa = self.parser.add_argument
617 617 for key,value in aliases.iteritems():
618 618 if key in flags:
619 619 # flags
620 620 nargs = '?'
621 621 else:
622 622 nargs = None
623 623 if len(key) is 1:
624 paa('-'+key, '--'+key, type=str, dest=value, nargs=nargs)
624 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
625 625 else:
626 paa('--'+key, type=str, dest=value, nargs=nargs)
626 paa('--'+key, type=unicode, dest=value, nargs=nargs)
627 627 for key, (value, help) in flags.iteritems():
628 628 if key in self.aliases:
629 629 #
630 630 self.alias_flags[self.aliases[key]] = value
631 631 continue
632 632 if len(key) is 1:
633 633 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
634 634 else:
635 635 paa('--'+key, action='append_const', dest='_flags', const=value)
636 636
637 637 def _convert_to_config(self):
638 638 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
639 639 # remove subconfigs list from namespace before transforming the Namespace
640 640 if '_flags' in self.parsed_data:
641 641 subcs = self.parsed_data._flags
642 642 del self.parsed_data._flags
643 643 else:
644 644 subcs = []
645 645
646 646 for k, v in vars(self.parsed_data).iteritems():
647 647 if v is None:
648 648 # it was a flag that shares the name of an alias
649 649 subcs.append(self.alias_flags[k])
650 650 else:
651 651 # eval the KV assignment
652 652 self._exec_config_str(k, v)
653 653
654 654 for subc in subcs:
655 655 self._load_flag(subc)
656 656
657 657 if self.extra_args:
658 658 sub_parser = KeyValueConfigLoader()
659 659 sub_parser.load_config(self.extra_args)
660 660 self.config._merge(sub_parser.config)
661 661 self.extra_args = sub_parser.extra_args
@@ -1,225 +1,237 b''
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-2009 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 sys
24 24 from tempfile import mkstemp
25 25 from unittest import TestCase
26 26
27 27 from nose import SkipTest
28 28
29 29 from IPython.testing.tools import mute_warn
30 30
31 31 from IPython.utils.traitlets import Int, Unicode
32 32 from IPython.config.configurable import Configurable
33 33 from IPython.config.loader import (
34 34 Config,
35 35 PyFileConfigLoader,
36 36 KeyValueConfigLoader,
37 37 ArgParseConfigLoader,
38 KVArgParseConfigLoader,
38 39 ConfigError
39 40 )
40 41
41 42 #-----------------------------------------------------------------------------
42 43 # Actual tests
43 44 #-----------------------------------------------------------------------------
44 45
45 46
46 47 pyfile = """
47 48 c = get_config()
48 49 c.a=10
49 50 c.b=20
50 51 c.Foo.Bar.value=10
51 52 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3
52 53 c.D.C.value='hi there'
53 54 """
54 55
55 56 class TestPyFileCL(TestCase):
56 57
57 58 def test_basic(self):
58 59 fd, fname = mkstemp('.py')
59 60 f = os.fdopen(fd, 'w')
60 61 f.write(pyfile)
61 62 f.close()
62 63 # Unlink the file
63 64 cl = PyFileConfigLoader(fname)
64 65 config = cl.load_config()
65 66 self.assertEquals(config.a, 10)
66 67 self.assertEquals(config.b, 20)
67 68 self.assertEquals(config.Foo.Bar.value, 10)
68 69 self.assertEquals(config.Foo.Bam.value, range(10))
69 70 self.assertEquals(config.D.C.value, 'hi there')
70 71
71 72 class MyLoader1(ArgParseConfigLoader):
72 73 def _add_arguments(self, aliases=None, flags=None):
73 74 p = self.parser
74 75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
75 76 p.add_argument('-b', dest='MyClass.bar', type=int)
76 77 p.add_argument('-n', dest='n', action='store_true')
77 78 p.add_argument('Global.bam', type=str)
78 79
79 80 class MyLoader2(ArgParseConfigLoader):
80 81 def _add_arguments(self, aliases=None, flags=None):
81 82 subparsers = self.parser.add_subparsers(dest='subparser_name')
82 83 subparser1 = subparsers.add_parser('1')
83 84 subparser1.add_argument('-x',dest='Global.x')
84 85 subparser2 = subparsers.add_parser('2')
85 86 subparser2.add_argument('y')
86 87
87 88 class TestArgParseCL(TestCase):
88 89
89 90 def test_basic(self):
90 91 cl = MyLoader1()
91 92 config = cl.load_config('-f hi -b 10 -n wow'.split())
92 93 self.assertEquals(config.Global.foo, 'hi')
93 94 self.assertEquals(config.MyClass.bar, 10)
94 95 self.assertEquals(config.n, True)
95 96 self.assertEquals(config.Global.bam, 'wow')
96 97 config = cl.load_config(['wow'])
97 98 self.assertEquals(config.keys(), ['Global'])
98 99 self.assertEquals(config.Global.keys(), ['bam'])
99 100 self.assertEquals(config.Global.bam, 'wow')
100 101
101 102 def test_add_arguments(self):
102 103 cl = MyLoader2()
103 104 config = cl.load_config('2 frobble'.split())
104 105 self.assertEquals(config.subparser_name, '2')
105 106 self.assertEquals(config.y, 'frobble')
106 107 config = cl.load_config('1 -x frobble'.split())
107 108 self.assertEquals(config.subparser_name, '1')
108 109 self.assertEquals(config.Global.x, 'frobble')
109 110
110 111 def test_argv(self):
111 112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
112 113 config = cl.load_config()
113 114 self.assertEquals(config.Global.foo, 'hi')
114 115 self.assertEquals(config.MyClass.bar, 10)
115 116 self.assertEquals(config.n, True)
116 117 self.assertEquals(config.Global.bam, 'wow')
117 118
118 119
119 120 class TestKeyValueCL(TestCase):
121 klass = KeyValueConfigLoader
120 122
121 123 def test_basic(self):
122 cl = KeyValueConfigLoader()
124 cl = self.klass()
123 125 argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]]
124 126 with mute_warn():
125 127 config = cl.load_config(argv)
126 128 self.assertEquals(config.a, 10)
127 129 self.assertEquals(config.b, 20)
128 130 self.assertEquals(config.Foo.Bar.value, 10)
129 131 self.assertEquals(config.Foo.Bam.value, range(10))
130 132 self.assertEquals(config.D.C.value, 'hi there')
131 133
132 134 def test_extra_args(self):
133 cl = KeyValueConfigLoader()
135 cl = self.klass()
134 136 with mute_warn():
135 137 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
136 138 self.assertEquals(cl.extra_args, ['b', 'd'])
137 139 self.assertEquals(config.a, 5)
138 140 self.assertEquals(config.c, 10)
139 141 with mute_warn():
140 142 config = cl.load_config(['--', '--a=5', '--c=10'])
141 143 self.assertEquals(cl.extra_args, ['--a=5', '--c=10'])
142 144
143 145 def test_unicode_args(self):
144 cl = KeyValueConfigLoader()
146 cl = self.klass()
145 147 argv = [u'--a=épsîlön']
146 148 with mute_warn():
147 149 config = cl.load_config(argv)
148 150 self.assertEquals(config.a, u'épsîlön')
149 151
150 152 def test_unicode_bytes_args(self):
151 153 uarg = u'--a=é'
152 154 try:
153 155 barg = uarg.encode(sys.stdin.encoding)
154 156 except (TypeError, UnicodeEncodeError):
155 157 raise SkipTest("sys.stdin.encoding can't handle 'é'")
156 158
157 cl = KeyValueConfigLoader()
159 cl = self.klass()
158 160 with mute_warn():
159 161 config = cl.load_config([barg])
160 162 self.assertEquals(config.a, u'é')
161 163
164 def test_unicode_alias(self):
165 cl = self.klass()
166 argv = [u'--a=épsîlön']
167 with mute_warn():
168 config = cl.load_config(argv, aliases=dict(a='A.a'))
169 self.assertEquals(config.A.a, u'épsîlön')
170
171
172 class TestArgParseKVCL(TestKeyValueCL):
173 klass = KVArgParseConfigLoader
162 174
163 175 class TestConfig(TestCase):
164 176
165 177 def test_setget(self):
166 178 c = Config()
167 179 c.a = 10
168 180 self.assertEquals(c.a, 10)
169 181 self.assertEquals(c.has_key('b'), False)
170 182
171 183 def test_auto_section(self):
172 184 c = Config()
173 185 self.assertEquals(c.has_key('A'), True)
174 186 self.assertEquals(c._has_section('A'), False)
175 187 A = c.A
176 188 A.foo = 'hi there'
177 189 self.assertEquals(c._has_section('A'), True)
178 190 self.assertEquals(c.A.foo, 'hi there')
179 191 del c.A
180 192 self.assertEquals(len(c.A.keys()),0)
181 193
182 194 def test_merge_doesnt_exist(self):
183 195 c1 = Config()
184 196 c2 = Config()
185 197 c2.bar = 10
186 198 c2.Foo.bar = 10
187 199 c1._merge(c2)
188 200 self.assertEquals(c1.Foo.bar, 10)
189 201 self.assertEquals(c1.bar, 10)
190 202 c2.Bar.bar = 10
191 203 c1._merge(c2)
192 204 self.assertEquals(c1.Bar.bar, 10)
193 205
194 206 def test_merge_exists(self):
195 207 c1 = Config()
196 208 c2 = Config()
197 209 c1.Foo.bar = 10
198 210 c1.Foo.bam = 30
199 211 c2.Foo.bar = 20
200 212 c2.Foo.wow = 40
201 213 c1._merge(c2)
202 214 self.assertEquals(c1.Foo.bam, 30)
203 215 self.assertEquals(c1.Foo.bar, 20)
204 216 self.assertEquals(c1.Foo.wow, 40)
205 217 c2.Foo.Bam.bam = 10
206 218 c1._merge(c2)
207 219 self.assertEquals(c1.Foo.Bam.bam, 10)
208 220
209 221 def test_deepcopy(self):
210 222 c1 = Config()
211 223 c1.Foo.bar = 10
212 224 c1.Foo.bam = 30
213 225 c1.a = 'asdf'
214 226 c1.b = range(10)
215 227 import copy
216 228 c2 = copy.deepcopy(c1)
217 229 self.assertEquals(c1, c2)
218 230 self.assert_(c1 is not c2)
219 231 self.assert_(c1.Foo is not c2.Foo)
220 232
221 233 def test_builtin(self):
222 234 c1 = Config()
223 235 exec 'foo = True' in c1
224 236 self.assertEquals(c1.foo, True)
225 237 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
General Comments 0
You need to be logged in to leave comments. Login now