##// END OF EJS Templates
don't create LazyConfigValue on `__` config names...
MinRK -
Show More
@@ -1,821 +1,824 b''
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 builtin_mod, 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 class Config(dict):
167 167 """An attribute based dict that can do smart merges."""
168 168
169 169 def __init__(self, *args, **kwds):
170 170 dict.__init__(self, *args, **kwds)
171 171 # This sets self.__dict__ = self, but it has to be done this way
172 172 # because we are also overriding __setattr__.
173 173 dict.__setattr__(self, '__dict__', self)
174 174 self._ensure_subconfig()
175 175
176 176 def _ensure_subconfig(self):
177 177 """ensure that sub-dicts that should be Config objects are
178 178
179 179 casts dicts that are under section keys to Config objects,
180 180 which is necessary for constructing Config objects from dict literals.
181 181 """
182 182 for key in self:
183 183 obj = self[key]
184 184 if self._is_section_key(key) \
185 185 and isinstance(obj, dict) \
186 186 and not isinstance(obj, Config):
187 187 dict.__setattr__(self, key, Config(obj))
188 188
189 189 def _merge(self, other):
190 190 """deprecated alias, use Config.merge()"""
191 191 self.merge(other)
192 192
193 193 def merge(self, other):
194 194 """merge another config object into this one"""
195 195 to_update = {}
196 196 for k, v in iteritems(other):
197 197 if k not in self:
198 198 to_update[k] = v
199 199 else: # I have this key
200 200 if isinstance(v, Config) and isinstance(self[k], Config):
201 201 # Recursively merge common sub Configs
202 202 self[k].merge(v)
203 203 else:
204 204 # Plain updates for non-Configs
205 205 to_update[k] = v
206 206
207 207 self.update(to_update)
208 208
209 209 def _is_section_key(self, key):
210 210 if key[0].upper()==key[0] and not key.startswith('_'):
211 211 return True
212 212 else:
213 213 return False
214 214
215 215 def __contains__(self, key):
216 216 # allow nested contains of the form `"Section.key" in config`
217 217 if '.' in key:
218 218 first, remainder = key.split('.', 1)
219 219 if first not in self:
220 220 return False
221 221 return remainder in self[first]
222 222
223 223 # we always have Sections
224 224 if self._is_section_key(key):
225 225 return True
226 226 else:
227 227 return super(Config, self).__contains__(key)
228 228 # .has_key is deprecated for dictionaries.
229 229 has_key = __contains__
230 230
231 231 def _has_section(self, key):
232 232 if self._is_section_key(key):
233 233 if super(Config, self).__contains__(key):
234 234 return True
235 235 return False
236 236
237 237 def copy(self):
238 238 return type(self)(dict.copy(self))
239 239
240 240 def __copy__(self):
241 241 return self.copy()
242 242
243 243 def __deepcopy__(self, memo):
244 244 import copy
245 245 return type(self)(copy.deepcopy(list(self.items())))
246 246
247 247 def __getitem__(self, key):
248 248 # We cannot use directly self._is_section_key, because it triggers
249 249 # infinite recursion on top of PyPy. Instead, we manually fish the
250 250 # bound method.
251 251 is_section_key = self.__class__._is_section_key.__get__(self)
252 252
253 253 # Because we use this for an exec namespace, we need to delegate
254 254 # the lookup of names in __builtin__ to itself. This means
255 255 # that you can't have section or attribute names that are
256 256 # builtins.
257 257 try:
258 258 return getattr(builtin_mod, key)
259 259 except AttributeError:
260 260 pass
261 261 if is_section_key(key):
262 262 try:
263 263 return dict.__getitem__(self, key)
264 264 except KeyError:
265 265 c = Config()
266 266 dict.__setitem__(self, key, c)
267 267 return c
268 268 else:
269 269 try:
270 270 return dict.__getitem__(self, key)
271 271 except KeyError:
272 # undefined
272 if key.startswith('__'):
273 # don't create LazyConfig on special method requests
274 raise
275 # undefined, create lazy value, used for container methods
273 276 v = LazyConfigValue()
274 277 dict.__setitem__(self, key, v)
275 278 return v
276 279
277 280
278 281 def __setitem__(self, key, value):
279 282 if self._is_section_key(key):
280 283 if not isinstance(value, Config):
281 284 raise ValueError('values whose keys begin with an uppercase '
282 285 'char must be Config instances: %r, %r' % (key, value))
283 286 else:
284 287 dict.__setitem__(self, key, value)
285 288
286 289 def __getattr__(self, key):
287 290 try:
288 291 return self.__getitem__(key)
289 292 except KeyError as e:
290 293 raise AttributeError(e)
291 294
292 295 def __setattr__(self, key, value):
293 296 try:
294 297 self.__setitem__(key, value)
295 298 except KeyError as e:
296 299 raise AttributeError(e)
297 300
298 301 def __delattr__(self, key):
299 302 try:
300 303 dict.__delitem__(self, key)
301 304 except KeyError as e:
302 305 raise AttributeError(e)
303 306
304 307
305 308 #-----------------------------------------------------------------------------
306 309 # Config loading classes
307 310 #-----------------------------------------------------------------------------
308 311
309 312
310 313 class ConfigLoader(object):
311 314 """A object for loading configurations from just about anywhere.
312 315
313 316 The resulting configuration is packaged as a :class:`Struct`.
314 317
315 318 Notes
316 319 -----
317 320 A :class:`ConfigLoader` does one thing: load a config from a source
318 321 (file, command line arguments) and returns the data as a :class:`Struct`.
319 322 There are lots of things that :class:`ConfigLoader` does not do. It does
320 323 not implement complex logic for finding config files. It does not handle
321 324 default values or merge multiple configs. These things need to be
322 325 handled elsewhere.
323 326 """
324 327
325 328 def __init__(self):
326 329 """A base class for config loaders.
327 330
328 331 Examples
329 332 --------
330 333
331 334 >>> cl = ConfigLoader()
332 335 >>> config = cl.load_config()
333 336 >>> config
334 337 {}
335 338 """
336 339 self.clear()
337 340
338 341 def clear(self):
339 342 self.config = Config()
340 343
341 344 def load_config(self):
342 345 """Load a config from somewhere, return a :class:`Config` instance.
343 346
344 347 Usually, this will cause self.config to be set and then returned.
345 348 However, in most cases, :meth:`ConfigLoader.clear` should be called
346 349 to erase any previous state.
347 350 """
348 351 self.clear()
349 352 return self.config
350 353
351 354
352 355 class FileConfigLoader(ConfigLoader):
353 356 """A base class for file based configurations.
354 357
355 358 As we add more file based config loaders, the common logic should go
356 359 here.
357 360 """
358 361 pass
359 362
360 363
361 364 class PyFileConfigLoader(FileConfigLoader):
362 365 """A config loader for pure python files.
363 366
364 367 This calls execfile on a plain python file and looks for attributes
365 368 that are all caps. These attribute are added to the config Struct.
366 369 """
367 370
368 371 def __init__(self, filename, path=None):
369 372 """Build a config loader for a filename and path.
370 373
371 374 Parameters
372 375 ----------
373 376 filename : str
374 377 The file name of the config file.
375 378 path : str, list, tuple
376 379 The path to search for the config file on, or a sequence of
377 380 paths to try in order.
378 381 """
379 382 super(PyFileConfigLoader, self).__init__()
380 383 self.filename = filename
381 384 self.path = path
382 385 self.full_filename = ''
383 386 self.data = None
384 387
385 388 def load_config(self):
386 389 """Load the config from a file and return it as a Struct."""
387 390 self.clear()
388 391 try:
389 392 self._find_file()
390 393 except IOError as e:
391 394 raise ConfigFileNotFound(str(e))
392 395 self._read_file_as_dict()
393 396 self._convert_to_config()
394 397 return self.config
395 398
396 399 def _find_file(self):
397 400 """Try to find the file by searching the paths."""
398 401 self.full_filename = filefind(self.filename, self.path)
399 402
400 403 def _read_file_as_dict(self):
401 404 """Load the config file into self.config, with recursive loading."""
402 405 # This closure is made available in the namespace that is used
403 406 # to exec the config file. It allows users to call
404 407 # load_subconfig('myconfig.py') to load config files recursively.
405 408 # It needs to be a closure because it has references to self.path
406 409 # and self.config. The sub-config is loaded with the same path
407 410 # as the parent, but it uses an empty config which is then merged
408 411 # with the parents.
409 412
410 413 # If a profile is specified, the config file will be loaded
411 414 # from that profile
412 415
413 416 def load_subconfig(fname, profile=None):
414 417 # import here to prevent circular imports
415 418 from IPython.core.profiledir import ProfileDir, ProfileDirError
416 419 if profile is not None:
417 420 try:
418 421 profile_dir = ProfileDir.find_profile_dir_by_name(
419 422 get_ipython_dir(),
420 423 profile,
421 424 )
422 425 except ProfileDirError:
423 426 return
424 427 path = profile_dir.location
425 428 else:
426 429 path = self.path
427 430 loader = PyFileConfigLoader(fname, path)
428 431 try:
429 432 sub_config = loader.load_config()
430 433 except ConfigFileNotFound:
431 434 # Pass silently if the sub config is not there. This happens
432 435 # when a user s using a profile, but not the default config.
433 436 pass
434 437 else:
435 438 self.config.merge(sub_config)
436 439
437 440 # Again, this needs to be a closure and should be used in config
438 441 # files to get the config being loaded.
439 442 def get_config():
440 443 return self.config
441 444
442 445 namespace = dict(
443 446 load_subconfig=load_subconfig,
444 447 get_config=get_config,
445 448 __file__=self.full_filename,
446 449 )
447 450 fs_encoding = sys.getfilesystemencoding() or 'ascii'
448 451 conf_filename = self.full_filename.encode(fs_encoding)
449 452 py3compat.execfile(conf_filename, namespace)
450 453
451 454 def _convert_to_config(self):
452 455 if self.data is None:
453 456 ConfigLoaderError('self.data does not exist')
454 457
455 458
456 459 class CommandLineConfigLoader(ConfigLoader):
457 460 """A config loader for command line arguments.
458 461
459 462 As we add more command line based loaders, the common logic should go
460 463 here.
461 464 """
462 465
463 466 def _exec_config_str(self, lhs, rhs):
464 467 """execute self.config.<lhs> = <rhs>
465 468
466 469 * expands ~ with expanduser
467 470 * tries to assign with raw eval, otherwise assigns with just the string,
468 471 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
469 472 equivalent are `--C.a=4` and `--C.a='4'`.
470 473 """
471 474 rhs = os.path.expanduser(rhs)
472 475 try:
473 476 # Try to see if regular Python syntax will work. This
474 477 # won't handle strings as the quote marks are removed
475 478 # by the system shell.
476 479 value = eval(rhs)
477 480 except (NameError, SyntaxError):
478 481 # This case happens if the rhs is a string.
479 482 value = rhs
480 483
481 484 exec(u'self.config.%s = value' % lhs)
482 485
483 486 def _load_flag(self, cfg):
484 487 """update self.config from a flag, which can be a dict or Config"""
485 488 if isinstance(cfg, (dict, Config)):
486 489 # don't clobber whole config sections, update
487 490 # each section from config:
488 491 for sec,c in iteritems(cfg):
489 492 self.config[sec].update(c)
490 493 else:
491 494 raise TypeError("Invalid flag: %r" % cfg)
492 495
493 496 # raw --identifier=value pattern
494 497 # but *also* accept '-' as wordsep, for aliases
495 498 # accepts: --foo=a
496 499 # --Class.trait=value
497 500 # --alias-name=value
498 501 # rejects: -foo=value
499 502 # --foo
500 503 # --Class.trait
501 504 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
502 505
503 506 # just flags, no assignments, with two *or one* leading '-'
504 507 # accepts: --foo
505 508 # -foo-bar-again
506 509 # rejects: --anything=anything
507 510 # --two.word
508 511
509 512 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
510 513
511 514 class KeyValueConfigLoader(CommandLineConfigLoader):
512 515 """A config loader that loads key value pairs from the command line.
513 516
514 517 This allows command line options to be gives in the following form::
515 518
516 519 ipython --profile="foo" --InteractiveShell.autocall=False
517 520 """
518 521
519 522 def __init__(self, argv=None, aliases=None, flags=None):
520 523 """Create a key value pair config loader.
521 524
522 525 Parameters
523 526 ----------
524 527 argv : list
525 528 A list that has the form of sys.argv[1:] which has unicode
526 529 elements of the form u"key=value". If this is None (default),
527 530 then sys.argv[1:] will be used.
528 531 aliases : dict
529 532 A dict of aliases for configurable traits.
530 533 Keys are the short aliases, Values are the resolved trait.
531 534 Of the form: `{'alias' : 'Configurable.trait'}`
532 535 flags : dict
533 536 A dict of flags, keyed by str name. Vaues can be Config objects,
534 537 dicts, or "key=value" strings. If Config or dict, when the flag
535 538 is triggered, The flag is loaded as `self.config.update(m)`.
536 539
537 540 Returns
538 541 -------
539 542 config : Config
540 543 The resulting Config object.
541 544
542 545 Examples
543 546 --------
544 547
545 548 >>> from IPython.config.loader import KeyValueConfigLoader
546 549 >>> cl = KeyValueConfigLoader()
547 550 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
548 551 >>> sorted(d.items())
549 552 [('A', {'name': 'brian'}), ('B', {'number': 0})]
550 553 """
551 554 self.clear()
552 555 if argv is None:
553 556 argv = sys.argv[1:]
554 557 self.argv = argv
555 558 self.aliases = aliases or {}
556 559 self.flags = flags or {}
557 560
558 561
559 562 def clear(self):
560 563 super(KeyValueConfigLoader, self).clear()
561 564 self.extra_args = []
562 565
563 566
564 567 def _decode_argv(self, argv, enc=None):
565 568 """decode argv if bytes, using stin.encoding, falling back on default enc"""
566 569 uargv = []
567 570 if enc is None:
568 571 enc = DEFAULT_ENCODING
569 572 for arg in argv:
570 573 if not isinstance(arg, unicode_type):
571 574 # only decode if not already decoded
572 575 arg = arg.decode(enc)
573 576 uargv.append(arg)
574 577 return uargv
575 578
576 579
577 580 def load_config(self, argv=None, aliases=None, flags=None):
578 581 """Parse the configuration and generate the Config object.
579 582
580 583 After loading, any arguments that are not key-value or
581 584 flags will be stored in self.extra_args - a list of
582 585 unparsed command-line arguments. This is used for
583 586 arguments such as input files or subcommands.
584 587
585 588 Parameters
586 589 ----------
587 590 argv : list, optional
588 591 A list that has the form of sys.argv[1:] which has unicode
589 592 elements of the form u"key=value". If this is None (default),
590 593 then self.argv will be used.
591 594 aliases : dict
592 595 A dict of aliases for configurable traits.
593 596 Keys are the short aliases, Values are the resolved trait.
594 597 Of the form: `{'alias' : 'Configurable.trait'}`
595 598 flags : dict
596 599 A dict of flags, keyed by str name. Values can be Config objects
597 600 or dicts. When the flag is triggered, The config is loaded as
598 601 `self.config.update(cfg)`.
599 602 """
600 603 self.clear()
601 604 if argv is None:
602 605 argv = self.argv
603 606 if aliases is None:
604 607 aliases = self.aliases
605 608 if flags is None:
606 609 flags = self.flags
607 610
608 611 # ensure argv is a list of unicode strings:
609 612 uargv = self._decode_argv(argv)
610 613 for idx,raw in enumerate(uargv):
611 614 # strip leading '-'
612 615 item = raw.lstrip('-')
613 616
614 617 if raw == '--':
615 618 # don't parse arguments after '--'
616 619 # this is useful for relaying arguments to scripts, e.g.
617 620 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
618 621 self.extra_args.extend(uargv[idx+1:])
619 622 break
620 623
621 624 if kv_pattern.match(raw):
622 625 lhs,rhs = item.split('=',1)
623 626 # Substitute longnames for aliases.
624 627 if lhs in aliases:
625 628 lhs = aliases[lhs]
626 629 if '.' not in lhs:
627 630 # probably a mistyped alias, but not technically illegal
628 631 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
629 632 try:
630 633 self._exec_config_str(lhs, rhs)
631 634 except Exception:
632 635 raise ArgumentError("Invalid argument: '%s'" % raw)
633 636
634 637 elif flag_pattern.match(raw):
635 638 if item in flags:
636 639 cfg,help = flags[item]
637 640 self._load_flag(cfg)
638 641 else:
639 642 raise ArgumentError("Unrecognized flag: '%s'"%raw)
640 643 elif raw.startswith('-'):
641 644 kv = '--'+item
642 645 if kv_pattern.match(kv):
643 646 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
644 647 else:
645 648 raise ArgumentError("Invalid argument: '%s'"%raw)
646 649 else:
647 650 # keep all args that aren't valid in a list,
648 651 # in case our parent knows what to do with them.
649 652 self.extra_args.append(item)
650 653 return self.config
651 654
652 655 class ArgParseConfigLoader(CommandLineConfigLoader):
653 656 """A loader that uses the argparse module to load from the command line."""
654 657
655 658 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
656 659 """Create a config loader for use with argparse.
657 660
658 661 Parameters
659 662 ----------
660 663
661 664 argv : optional, list
662 665 If given, used to read command-line arguments from, otherwise
663 666 sys.argv[1:] is used.
664 667
665 668 parser_args : tuple
666 669 A tuple of positional arguments that will be passed to the
667 670 constructor of :class:`argparse.ArgumentParser`.
668 671
669 672 parser_kw : dict
670 673 A tuple of keyword arguments that will be passed to the
671 674 constructor of :class:`argparse.ArgumentParser`.
672 675
673 676 Returns
674 677 -------
675 678 config : Config
676 679 The resulting Config object.
677 680 """
678 681 super(CommandLineConfigLoader, self).__init__()
679 682 self.clear()
680 683 if argv is None:
681 684 argv = sys.argv[1:]
682 685 self.argv = argv
683 686 self.aliases = aliases or {}
684 687 self.flags = flags or {}
685 688
686 689 self.parser_args = parser_args
687 690 self.version = parser_kw.pop("version", None)
688 691 kwargs = dict(argument_default=argparse.SUPPRESS)
689 692 kwargs.update(parser_kw)
690 693 self.parser_kw = kwargs
691 694
692 695 def load_config(self, argv=None, aliases=None, flags=None):
693 696 """Parse command line arguments and return as a Config object.
694 697
695 698 Parameters
696 699 ----------
697 700
698 701 args : optional, list
699 702 If given, a list with the structure of sys.argv[1:] to parse
700 703 arguments from. If not given, the instance's self.argv attribute
701 704 (given at construction time) is used."""
702 705 self.clear()
703 706 if argv is None:
704 707 argv = self.argv
705 708 if aliases is None:
706 709 aliases = self.aliases
707 710 if flags is None:
708 711 flags = self.flags
709 712 self._create_parser(aliases, flags)
710 713 self._parse_args(argv)
711 714 self._convert_to_config()
712 715 return self.config
713 716
714 717 def get_extra_args(self):
715 718 if hasattr(self, 'extra_args'):
716 719 return self.extra_args
717 720 else:
718 721 return []
719 722
720 723 def _create_parser(self, aliases=None, flags=None):
721 724 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
722 725 self._add_arguments(aliases, flags)
723 726
724 727 def _add_arguments(self, aliases=None, flags=None):
725 728 raise NotImplementedError("subclasses must implement _add_arguments")
726 729
727 730 def _parse_args(self, args):
728 731 """self.parser->self.parsed_data"""
729 732 # decode sys.argv to support unicode command-line options
730 733 enc = DEFAULT_ENCODING
731 734 uargs = [py3compat.cast_unicode(a, enc) for a in args]
732 735 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
733 736
734 737 def _convert_to_config(self):
735 738 """self.parsed_data->self.config"""
736 739 for k, v in iteritems(vars(self.parsed_data)):
737 740 exec("self.config.%s = v"%k, locals(), globals())
738 741
739 742 class KVArgParseConfigLoader(ArgParseConfigLoader):
740 743 """A config loader that loads aliases and flags with argparse,
741 744 but will use KVLoader for the rest. This allows better parsing
742 745 of common args, such as `ipython -c 'print 5'`, but still gets
743 746 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
744 747
745 748 def _add_arguments(self, aliases=None, flags=None):
746 749 self.alias_flags = {}
747 750 # print aliases, flags
748 751 if aliases is None:
749 752 aliases = self.aliases
750 753 if flags is None:
751 754 flags = self.flags
752 755 paa = self.parser.add_argument
753 756 for key,value in iteritems(aliases):
754 757 if key in flags:
755 758 # flags
756 759 nargs = '?'
757 760 else:
758 761 nargs = None
759 762 if len(key) is 1:
760 763 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
761 764 else:
762 765 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
763 766 for key, (value, help) in iteritems(flags):
764 767 if key in self.aliases:
765 768 #
766 769 self.alias_flags[self.aliases[key]] = value
767 770 continue
768 771 if len(key) is 1:
769 772 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
770 773 else:
771 774 paa('--'+key, action='append_const', dest='_flags', const=value)
772 775
773 776 def _convert_to_config(self):
774 777 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
775 778 # remove subconfigs list from namespace before transforming the Namespace
776 779 if '_flags' in self.parsed_data:
777 780 subcs = self.parsed_data._flags
778 781 del self.parsed_data._flags
779 782 else:
780 783 subcs = []
781 784
782 785 for k, v in iteritems(vars(self.parsed_data)):
783 786 if v is None:
784 787 # it was a flag that shares the name of an alias
785 788 subcs.append(self.alias_flags[k])
786 789 else:
787 790 # eval the KV assignment
788 791 self._exec_config_str(k, v)
789 792
790 793 for subc in subcs:
791 794 self._load_flag(subc)
792 795
793 796 if self.extra_args:
794 797 sub_parser = KeyValueConfigLoader()
795 798 sub_parser.load_config(self.extra_args)
796 799 self.config.merge(sub_parser.config)
797 800 self.extra_args = sub_parser.extra_args
798 801
799 802
800 803 def load_pyconfig_files(config_files, path):
801 804 """Load multiple Python config files, merging each of them in turn.
802 805
803 806 Parameters
804 807 ==========
805 808 config_files : list of str
806 809 List of config files names to load and merge into the config.
807 810 path : unicode
808 811 The full path to the location of the config files.
809 812 """
810 813 config = Config()
811 814 for cf in config_files:
812 815 loader = PyFileConfigLoader(cf, path=path)
813 816 try:
814 817 next_config = loader.load_config()
815 818 except ConfigFileNotFound:
816 819 pass
817 820 except:
818 821 raise
819 822 else:
820 823 config.merge(next_config)
821 824 return config
General Comments 0
You need to be logged in to leave comments. Login now