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