##// END OF EJS Templates
remove profile awareness from load_subconfig...
Min RK -
Show More
@@ -1,621 +1,624 b''
1 1 # encoding: utf-8
2 2 """A base class for a configurable application."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import json
10 10 import logging
11 11 import os
12 12 import re
13 13 import sys
14 14 from copy import deepcopy
15 15 from collections import defaultdict
16 16
17 17 from decorator import decorator
18 18
19 19 from IPython.config.configurable import SingletonConfigurable
20 20 from IPython.config.loader import (
21 21 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
22 22 )
23 23
24 24 from IPython.utils.traitlets import (
25 25 Unicode, List, Enum, Dict, Instance, TraitError
26 26 )
27 27 from IPython.utils.importstring import import_item
28 28 from IPython.utils.text import indent, wrap_paragraphs, dedent
29 29 from IPython.utils import py3compat
30 30 from IPython.utils.py3compat import string_types, iteritems
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Descriptions for the various sections
34 34 #-----------------------------------------------------------------------------
35 35
36 36 # merge flags&aliases into options
37 37 option_description = """
38 38 Arguments that take values are actually convenience aliases to full
39 39 Configurables, whose aliases are listed on the help line. For more information
40 40 on full configurables, see '--help-all'.
41 41 """.strip() # trim newlines of front and back
42 42
43 43 keyvalue_description = """
44 44 Parameters are set from command-line arguments of the form:
45 45 `--Class.trait=value`.
46 46 This line is evaluated in Python, so simple expressions are allowed, e.g.::
47 47 `--C.a='range(3)'` For setting C.a=[0,1,2].
48 48 """.strip() # trim newlines of front and back
49 49
50 50 # sys.argv can be missing, for example when python is embedded. See the docs
51 51 # for details: http://docs.python.org/2/c-api/intro.html#embedding-python
52 52 if not hasattr(sys, "argv"):
53 53 sys.argv = [""]
54 54
55 55 subcommand_description = """
56 56 Subcommands are launched as `{app} cmd [args]`. For information on using
57 57 subcommand 'cmd', do: `{app} cmd -h`.
58 58 """
59 59 # get running program name
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Application class
63 63 #-----------------------------------------------------------------------------
64 64
65 65 @decorator
66 66 def catch_config_error(method, app, *args, **kwargs):
67 67 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
68 68
69 69 On a TraitError (generally caused by bad config), this will print the trait's
70 70 message, and exit the app.
71 71
72 72 For use on init methods, to prevent invoking excepthook on invalid input.
73 73 """
74 74 try:
75 75 return method(app, *args, **kwargs)
76 76 except (TraitError, ArgumentError) as e:
77 77 app.print_help()
78 78 app.log.fatal("Bad config encountered during initialization:")
79 79 app.log.fatal(str(e))
80 80 app.log.debug("Config at the time: %s", app.config)
81 81 app.exit(1)
82 82
83 83
84 84 class ApplicationError(Exception):
85 85 pass
86 86
87 87 class LevelFormatter(logging.Formatter):
88 88 """Formatter with additional `highlevel` record
89 89
90 90 This field is empty if log level is less than highlevel_limit,
91 91 otherwise it is formatted with self.highlevel_format.
92 92
93 93 Useful for adding 'WARNING' to warning messages,
94 94 without adding 'INFO' to info, etc.
95 95 """
96 96 highlevel_limit = logging.WARN
97 97 highlevel_format = " %(levelname)s |"
98 98
99 99 def format(self, record):
100 100 if record.levelno >= self.highlevel_limit:
101 101 record.highlevel = self.highlevel_format % record.__dict__
102 102 else:
103 103 record.highlevel = ""
104 104 return super(LevelFormatter, self).format(record)
105 105
106 106
107 107 class Application(SingletonConfigurable):
108 108 """A singleton application with full configuration support."""
109 109
110 110 # The name of the application, will usually match the name of the command
111 111 # line application
112 112 name = Unicode(u'application')
113 113
114 114 # The description of the application that is printed at the beginning
115 115 # of the help.
116 116 description = Unicode(u'This is an application.')
117 117 # default section descriptions
118 118 option_description = Unicode(option_description)
119 119 keyvalue_description = Unicode(keyvalue_description)
120 120 subcommand_description = Unicode(subcommand_description)
121
122 python_config_loader_class = PyFileConfigLoader
123 json_config_loader_class = JSONFileConfigLoader
121 124
122 125 # The usage and example string that goes at the end of the help string.
123 126 examples = Unicode()
124 127
125 128 # A sequence of Configurable subclasses whose config=True attributes will
126 129 # be exposed at the command line.
127 130 classes = []
128 131 @property
129 132 def _help_classes(self):
130 133 """Define `App.help_classes` if CLI classes should differ from config file classes"""
131 134 return getattr(self, 'help_classes', self.classes)
132 135
133 136 @property
134 137 def _config_classes(self):
135 138 """Define `App.config_classes` if config file classes should differ from CLI classes."""
136 139 return getattr(self, 'config_classes', self.classes)
137 140
138 141 # The version string of this application.
139 142 version = Unicode(u'0.0')
140 143
141 144 # the argv used to initialize the application
142 145 argv = List()
143 146
144 147 # The log level for the application
145 148 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
146 149 default_value=logging.WARN,
147 150 config=True,
148 151 help="Set the log level by value or name.")
149 152 def _log_level_changed(self, name, old, new):
150 153 """Adjust the log level when log_level is set."""
151 154 if isinstance(new, string_types):
152 155 new = getattr(logging, new)
153 156 self.log_level = new
154 157 self.log.setLevel(new)
155 158
156 159 _log_formatter_cls = LevelFormatter
157 160
158 161 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
159 162 help="The date format used by logging formatters for %(asctime)s"
160 163 )
161 164 def _log_datefmt_changed(self, name, old, new):
162 165 self._log_format_changed('log_format', self.log_format, self.log_format)
163 166
164 167 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
165 168 help="The Logging format template",
166 169 )
167 170 def _log_format_changed(self, name, old, new):
168 171 """Change the log formatter when log_format is set."""
169 172 _log_handler = self.log.handlers[0]
170 173 _log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt)
171 174 _log_handler.setFormatter(_log_formatter)
172 175
173 176
174 177 log = Instance(logging.Logger)
175 178 def _log_default(self):
176 179 """Start logging for this application.
177 180
178 181 The default is to log to stderr using a StreamHandler, if no default
179 182 handler already exists. The log level starts at logging.WARN, but this
180 183 can be adjusted by setting the ``log_level`` attribute.
181 184 """
182 185 log = logging.getLogger(self.__class__.__name__)
183 186 log.setLevel(self.log_level)
184 187 log.propagate = False
185 188 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
186 189 while _log:
187 190 if _log.handlers:
188 191 return log
189 192 if not _log.propagate:
190 193 break
191 194 else:
192 195 _log = _log.parent
193 196 if sys.executable.endswith('pythonw.exe'):
194 197 # this should really go to a file, but file-logging is only
195 198 # hooked up in parallel applications
196 199 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
197 200 else:
198 201 _log_handler = logging.StreamHandler()
199 202 _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
200 203 _log_handler.setFormatter(_log_formatter)
201 204 log.addHandler(_log_handler)
202 205 return log
203 206
204 207 # the alias map for configurables
205 208 aliases = Dict({'log-level' : 'Application.log_level'})
206 209
207 210 # flags for loading Configurables or store_const style flags
208 211 # flags are loaded from this dict by '--key' flags
209 212 # this must be a dict of two-tuples, the first element being the Config/dict
210 213 # and the second being the help string for the flag
211 214 flags = Dict()
212 215 def _flags_changed(self, name, old, new):
213 216 """ensure flags dict is valid"""
214 217 for key,value in iteritems(new):
215 218 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
216 219 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
217 220 assert isinstance(value[1], string_types), "Bad flag: %r:%s"%(key,value)
218 221
219 222
220 223 # subcommands for launching other applications
221 224 # if this is not empty, this will be a parent Application
222 225 # this must be a dict of two-tuples,
223 226 # the first element being the application class/import string
224 227 # and the second being the help string for the subcommand
225 228 subcommands = Dict()
226 229 # parse_command_line will initialize a subapp, if requested
227 230 subapp = Instance('IPython.config.application.Application', allow_none=True)
228 231
229 232 # extra command-line arguments that don't set config values
230 233 extra_args = List(Unicode)
231 234
232 235
233 236 def __init__(self, **kwargs):
234 237 SingletonConfigurable.__init__(self, **kwargs)
235 238 # Ensure my class is in self.classes, so my attributes appear in command line
236 239 # options and config files.
237 240 if self.__class__ not in self.classes:
238 241 self.classes.insert(0, self.__class__)
239 242
240 243 def _config_changed(self, name, old, new):
241 244 SingletonConfigurable._config_changed(self, name, old, new)
242 245 self.log.debug('Config changed:')
243 246 self.log.debug(repr(new))
244 247
245 248 @catch_config_error
246 249 def initialize(self, argv=None):
247 250 """Do the basic steps to configure me.
248 251
249 252 Override in subclasses.
250 253 """
251 254 self.parse_command_line(argv)
252 255
253 256
254 257 def start(self):
255 258 """Start the app mainloop.
256 259
257 260 Override in subclasses.
258 261 """
259 262 if self.subapp is not None:
260 263 return self.subapp.start()
261 264
262 265 def print_alias_help(self):
263 266 """Print the alias part of the help."""
264 267 if not self.aliases:
265 268 return
266 269
267 270 lines = []
268 271 classdict = {}
269 272 for cls in self._help_classes:
270 273 # include all parents (up to, but excluding Configurable) in available names
271 274 for c in cls.mro()[:-3]:
272 275 classdict[c.__name__] = c
273 276
274 277 for alias, longname in iteritems(self.aliases):
275 278 classname, traitname = longname.split('.',1)
276 279 cls = classdict[classname]
277 280
278 281 trait = cls.class_traits(config=True)[traitname]
279 282 help = cls.class_get_trait_help(trait).splitlines()
280 283 # reformat first line
281 284 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
282 285 if len(alias) == 1:
283 286 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
284 287 lines.extend(help)
285 288 # lines.append('')
286 289 print(os.linesep.join(lines))
287 290
288 291 def print_flag_help(self):
289 292 """Print the flag part of the help."""
290 293 if not self.flags:
291 294 return
292 295
293 296 lines = []
294 297 for m, (cfg,help) in iteritems(self.flags):
295 298 prefix = '--' if len(m) > 1 else '-'
296 299 lines.append(prefix+m)
297 300 lines.append(indent(dedent(help.strip())))
298 301 # lines.append('')
299 302 print(os.linesep.join(lines))
300 303
301 304 def print_options(self):
302 305 if not self.flags and not self.aliases:
303 306 return
304 307 lines = ['Options']
305 308 lines.append('-'*len(lines[0]))
306 309 lines.append('')
307 310 for p in wrap_paragraphs(self.option_description):
308 311 lines.append(p)
309 312 lines.append('')
310 313 print(os.linesep.join(lines))
311 314 self.print_flag_help()
312 315 self.print_alias_help()
313 316 print()
314 317
315 318 def print_subcommands(self):
316 319 """Print the subcommand part of the help."""
317 320 if not self.subcommands:
318 321 return
319 322
320 323 lines = ["Subcommands"]
321 324 lines.append('-'*len(lines[0]))
322 325 lines.append('')
323 326 for p in wrap_paragraphs(self.subcommand_description.format(
324 327 app=self.name)):
325 328 lines.append(p)
326 329 lines.append('')
327 330 for subc, (cls, help) in iteritems(self.subcommands):
328 331 lines.append(subc)
329 332 if help:
330 333 lines.append(indent(dedent(help.strip())))
331 334 lines.append('')
332 335 print(os.linesep.join(lines))
333 336
334 337 def print_help(self, classes=False):
335 338 """Print the help for each Configurable class in self.classes.
336 339
337 340 If classes=False (the default), only flags and aliases are printed.
338 341 """
339 342 self.print_description()
340 343 self.print_subcommands()
341 344 self.print_options()
342 345
343 346 if classes:
344 347 help_classes = self._help_classes
345 348 if help_classes:
346 349 print("Class parameters")
347 350 print("----------------")
348 351 print()
349 352 for p in wrap_paragraphs(self.keyvalue_description):
350 353 print(p)
351 354 print()
352 355
353 356 for cls in help_classes:
354 357 cls.class_print_help()
355 358 print()
356 359 else:
357 360 print("To see all available configurables, use `--help-all`")
358 361 print()
359 362
360 363 self.print_examples()
361 364
362 365
363 366 def print_description(self):
364 367 """Print the application description."""
365 368 for p in wrap_paragraphs(self.description):
366 369 print(p)
367 370 print()
368 371
369 372 def print_examples(self):
370 373 """Print usage and examples.
371 374
372 375 This usage string goes at the end of the command line help string
373 376 and should contain examples of the application's usage.
374 377 """
375 378 if self.examples:
376 379 print("Examples")
377 380 print("--------")
378 381 print()
379 382 print(indent(dedent(self.examples.strip())))
380 383 print()
381 384
382 385 def print_version(self):
383 386 """Print the version string."""
384 387 print(self.version)
385 388
386 389 def update_config(self, config):
387 390 """Fire the traits events when the config is updated."""
388 391 # Save a copy of the current config.
389 392 newconfig = deepcopy(self.config)
390 393 # Merge the new config into the current one.
391 394 newconfig.merge(config)
392 395 # Save the combined config as self.config, which triggers the traits
393 396 # events.
394 397 self.config = newconfig
395 398
396 399 @catch_config_error
397 400 def initialize_subcommand(self, subc, argv=None):
398 401 """Initialize a subcommand with argv."""
399 402 subapp,help = self.subcommands.get(subc)
400 403
401 404 if isinstance(subapp, string_types):
402 405 subapp = import_item(subapp)
403 406
404 407 # clear existing instances
405 408 self.__class__.clear_instance()
406 409 # instantiate
407 410 self.subapp = subapp.instance(config=self.config)
408 411 # and initialize subapp
409 412 self.subapp.initialize(argv)
410 413
411 414 def flatten_flags(self):
412 415 """flatten flags and aliases, so cl-args override as expected.
413 416
414 417 This prevents issues such as an alias pointing to InteractiveShell,
415 418 but a config file setting the same trait in TerminalInteraciveShell
416 419 getting inappropriate priority over the command-line arg.
417 420
418 421 Only aliases with exactly one descendent in the class list
419 422 will be promoted.
420 423
421 424 """
422 425 # build a tree of classes in our list that inherit from a particular
423 426 # it will be a dict by parent classname of classes in our list
424 427 # that are descendents
425 428 mro_tree = defaultdict(list)
426 429 for cls in self._help_classes:
427 430 clsname = cls.__name__
428 431 for parent in cls.mro()[1:-3]:
429 432 # exclude cls itself and Configurable,HasTraits,object
430 433 mro_tree[parent.__name__].append(clsname)
431 434 # flatten aliases, which have the form:
432 435 # { 'alias' : 'Class.trait' }
433 436 aliases = {}
434 437 for alias, cls_trait in iteritems(self.aliases):
435 438 cls,trait = cls_trait.split('.',1)
436 439 children = mro_tree[cls]
437 440 if len(children) == 1:
438 441 # exactly one descendent, promote alias
439 442 cls = children[0]
440 443 aliases[alias] = '.'.join([cls,trait])
441 444
442 445 # flatten flags, which are of the form:
443 446 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
444 447 flags = {}
445 448 for key, (flagdict, help) in iteritems(self.flags):
446 449 newflag = {}
447 450 for cls, subdict in iteritems(flagdict):
448 451 children = mro_tree[cls]
449 452 # exactly one descendent, promote flag section
450 453 if len(children) == 1:
451 454 cls = children[0]
452 455 newflag[cls] = subdict
453 456 flags[key] = (newflag, help)
454 457 return flags, aliases
455 458
456 459 @catch_config_error
457 460 def parse_command_line(self, argv=None):
458 461 """Parse the command line arguments."""
459 462 argv = sys.argv[1:] if argv is None else argv
460 463 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
461 464
462 465 if argv and argv[0] == 'help':
463 466 # turn `ipython help notebook` into `ipython notebook -h`
464 467 argv = argv[1:] + ['-h']
465 468
466 469 if self.subcommands and len(argv) > 0:
467 470 # we have subcommands, and one may have been specified
468 471 subc, subargv = argv[0], argv[1:]
469 472 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
470 473 # it's a subcommand, and *not* a flag or class parameter
471 474 return self.initialize_subcommand(subc, subargv)
472 475
473 476 # Arguments after a '--' argument are for the script IPython may be
474 477 # about to run, not IPython iteslf. For arguments parsed here (help and
475 478 # version), we want to only search the arguments up to the first
476 479 # occurrence of '--', which we're calling interpreted_argv.
477 480 try:
478 481 interpreted_argv = argv[:argv.index('--')]
479 482 except ValueError:
480 483 interpreted_argv = argv
481 484
482 485 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
483 486 self.print_help('--help-all' in interpreted_argv)
484 487 self.exit(0)
485 488
486 489 if '--version' in interpreted_argv or '-V' in interpreted_argv:
487 490 self.print_version()
488 491 self.exit(0)
489 492
490 493 # flatten flags&aliases, so cl-args get appropriate priority:
491 494 flags,aliases = self.flatten_flags()
492 495 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
493 496 flags=flags, log=self.log)
494 497 config = loader.load_config()
495 498 self.update_config(config)
496 499 # store unparsed args in extra_args
497 500 self.extra_args = loader.extra_args
498 501
499 502 @classmethod
500 503 def _load_config_files(cls, basefilename, path=None, log=None):
501 504 """Load config files (py,json) by filename and path.
502 505
503 506 yield each config object in turn.
504 507 """
505 508
506 509 if not isinstance(path, list):
507 510 path = [path]
508 511 for path in path[::-1]:
509 512 # path list is in descending priority order, so load files backwards:
510 pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log)
511 jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log)
513 pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
514 jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
512 515 config = None
513 516 for loader in [pyloader, jsonloader]:
514 517 try:
515 518 config = loader.load_config()
516 519 except ConfigFileNotFound:
517 520 pass
518 521 except Exception:
519 522 # try to get the full filename, but it will be empty in the
520 523 # unlikely event that the error raised before filefind finished
521 524 filename = loader.full_filename or basefilename
522 525 # problem while running the file
523 526 if log:
524 527 log.error("Exception while loading config file %s",
525 528 filename, exc_info=True)
526 529 else:
527 530 if log:
528 531 log.debug("Loaded config file: %s", loader.full_filename)
529 532 if config:
530 533 yield config
531 534
532 535 raise StopIteration
533 536
534 537
535 538 @catch_config_error
536 539 def load_config_file(self, filename, path=None):
537 540 """Load config files by filename and path."""
538 541 filename, ext = os.path.splitext(filename)
539 542 loaded = []
540 543 for config in self._load_config_files(filename, path=path, log=self.log):
541 544 loaded.append(config)
542 545 self.update_config(config)
543 546 if len(loaded) > 1:
544 547 collisions = loaded[0].collisions(loaded[1])
545 548 if collisions:
546 549 self.log.warn("Collisions detected in {0}.py and {0}.json config files."
547 550 " {0}.json has higher priority: {1}".format(
548 551 filename, json.dumps(collisions, indent=2),
549 552 ))
550 553
551 554
552 555 def generate_config_file(self):
553 556 """generate default config file from Configurables"""
554 557 lines = ["# Configuration file for %s."%self.name]
555 558 lines.append('')
556 559 lines.append('c = get_config()')
557 560 lines.append('')
558 561 for cls in self._config_classes:
559 562 lines.append(cls.class_config_section())
560 563 return '\n'.join(lines)
561 564
562 565 def exit(self, exit_status=0):
563 566 self.log.debug("Exiting application: %s" % self.name)
564 567 sys.exit(exit_status)
565 568
566 569 @classmethod
567 570 def launch_instance(cls, argv=None, **kwargs):
568 571 """Launch a global instance of this Application
569 572
570 573 If a global instance already exists, this reinitializes and starts it
571 574 """
572 575 app = cls.instance(**kwargs)
573 576 app.initialize(argv)
574 577 app.start()
575 578
576 579 #-----------------------------------------------------------------------------
577 580 # utility functions, for convenience
578 581 #-----------------------------------------------------------------------------
579 582
580 583 def boolean_flag(name, configurable, set_help='', unset_help=''):
581 584 """Helper for building basic --trait, --no-trait flags.
582 585
583 586 Parameters
584 587 ----------
585 588
586 589 name : str
587 590 The name of the flag.
588 591 configurable : str
589 592 The 'Class.trait' string of the trait to be set/unset with the flag
590 593 set_help : unicode
591 594 help string for --name flag
592 595 unset_help : unicode
593 596 help string for --no-name flag
594 597
595 598 Returns
596 599 -------
597 600
598 601 cfg : dict
599 602 A dict with two keys: 'name', and 'no-name', for setting and unsetting
600 603 the trait, respectively.
601 604 """
602 605 # default helpstrings
603 606 set_help = set_help or "set %s=True"%configurable
604 607 unset_help = unset_help or "set %s=False"%configurable
605 608
606 609 cls,trait = configurable.split('.')
607 610
608 611 setter = {cls : {trait : True}}
609 612 unsetter = {cls : {trait : False}}
610 613 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
611 614
612 615
613 616 def get_config():
614 617 """Get the config object for the global Application instance, if there is one
615 618
616 619 otherwise return an empty config object
617 620 """
618 621 if Application.initialized():
619 622 return Application.instance().config
620 623 else:
621 624 return Config()
@@ -1,858 +1,835 b''
1 1 # encoding: utf-8
2 2 """A simple configuration system."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import argparse
8 8 import copy
9 9 import logging
10 10 import os
11 11 import re
12 12 import sys
13 13 import json
14 14 from ast import literal_eval
15 15
16 16 from IPython.utils.path import filefind, get_ipython_dir
17 17 from IPython.utils import py3compat
18 18 from IPython.utils.encoding import DEFAULT_ENCODING
19 19 from IPython.utils.py3compat import unicode_type, iteritems
20 20 from IPython.utils.traitlets import HasTraits, List, Any
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Exceptions
24 24 #-----------------------------------------------------------------------------
25 25
26 26
27 27 class ConfigError(Exception):
28 28 pass
29 29
30 30 class ConfigLoaderError(ConfigError):
31 31 pass
32 32
33 33 class ConfigFileNotFound(ConfigError):
34 34 pass
35 35
36 36 class ArgumentError(ConfigLoaderError):
37 37 pass
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Argparse fix
41 41 #-----------------------------------------------------------------------------
42 42
43 43 # Unfortunately argparse by default prints help messages to stderr instead of
44 44 # stdout. This makes it annoying to capture long help screens at the command
45 45 # line, since one must know how to pipe stderr, which many users don't know how
46 46 # to do. So we override the print_help method with one that defaults to
47 47 # stdout and use our class instead.
48 48
49 49 class ArgumentParser(argparse.ArgumentParser):
50 50 """Simple argparse subclass that prints help to stdout by default."""
51 51
52 52 def print_help(self, file=None):
53 53 if file is None:
54 54 file = sys.stdout
55 55 return super(ArgumentParser, self).print_help(file)
56 56
57 57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Config class for holding config information
61 61 #-----------------------------------------------------------------------------
62 62
63 63 class LazyConfigValue(HasTraits):
64 64 """Proxy object for exposing methods on configurable containers
65 65
66 66 Exposes:
67 67
68 68 - append, extend, insert on lists
69 69 - update on dicts
70 70 - update, add on sets
71 71 """
72 72
73 73 _value = None
74 74
75 75 # list methods
76 76 _extend = List()
77 77 _prepend = List()
78 78
79 79 def append(self, obj):
80 80 self._extend.append(obj)
81 81
82 82 def extend(self, other):
83 83 self._extend.extend(other)
84 84
85 85 def prepend(self, other):
86 86 """like list.extend, but for the front"""
87 87 self._prepend[:0] = other
88 88
89 89 _inserts = List()
90 90 def insert(self, index, other):
91 91 if not isinstance(index, int):
92 92 raise TypeError("An integer is required")
93 93 self._inserts.append((index, other))
94 94
95 95 # dict methods
96 96 # update is used for both dict and set
97 97 _update = Any()
98 98 def update(self, other):
99 99 if self._update is None:
100 100 if isinstance(other, dict):
101 101 self._update = {}
102 102 else:
103 103 self._update = set()
104 104 self._update.update(other)
105 105
106 106 # set methods
107 107 def add(self, obj):
108 108 self.update({obj})
109 109
110 110 def get_value(self, initial):
111 111 """construct the value from the initial one
112 112
113 113 after applying any insert / extend / update changes
114 114 """
115 115 if self._value is not None:
116 116 return self._value
117 117 value = copy.deepcopy(initial)
118 118 if isinstance(value, list):
119 119 for idx, obj in self._inserts:
120 120 value.insert(idx, obj)
121 121 value[:0] = self._prepend
122 122 value.extend(self._extend)
123 123
124 124 elif isinstance(value, dict):
125 125 if self._update:
126 126 value.update(self._update)
127 127 elif isinstance(value, set):
128 128 if self._update:
129 129 value.update(self._update)
130 130 self._value = value
131 131 return value
132 132
133 133 def to_dict(self):
134 134 """return JSONable dict form of my data
135 135
136 136 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
137 137 """
138 138 d = {}
139 139 if self._update:
140 140 d['update'] = self._update
141 141 if self._extend:
142 142 d['extend'] = self._extend
143 143 if self._prepend:
144 144 d['prepend'] = self._prepend
145 145 elif self._inserts:
146 146 d['inserts'] = self._inserts
147 147 return d
148 148
149 149
150 150 def _is_section_key(key):
151 151 """Is a Config key a section name (does it start with a capital)?"""
152 152 if key and key[0].upper()==key[0] and not key.startswith('_'):
153 153 return True
154 154 else:
155 155 return False
156 156
157 157
158 158 class Config(dict):
159 159 """An attribute based dict that can do smart merges."""
160 160
161 161 def __init__(self, *args, **kwds):
162 162 dict.__init__(self, *args, **kwds)
163 163 self._ensure_subconfig()
164 164
165 165 def _ensure_subconfig(self):
166 166 """ensure that sub-dicts that should be Config objects are
167 167
168 168 casts dicts that are under section keys to Config objects,
169 169 which is necessary for constructing Config objects from dict literals.
170 170 """
171 171 for key in self:
172 172 obj = self[key]
173 173 if _is_section_key(key) \
174 174 and isinstance(obj, dict) \
175 175 and not isinstance(obj, Config):
176 176 setattr(self, key, Config(obj))
177 177
178 178 def _merge(self, other):
179 179 """deprecated alias, use Config.merge()"""
180 180 self.merge(other)
181 181
182 182 def merge(self, other):
183 183 """merge another config object into this one"""
184 184 to_update = {}
185 185 for k, v in iteritems(other):
186 186 if k not in self:
187 187 to_update[k] = copy.deepcopy(v)
188 188 else: # I have this key
189 189 if isinstance(v, Config) and isinstance(self[k], Config):
190 190 # Recursively merge common sub Configs
191 191 self[k].merge(v)
192 192 else:
193 193 # Plain updates for non-Configs
194 194 to_update[k] = copy.deepcopy(v)
195 195
196 196 self.update(to_update)
197 197
198 198 def collisions(self, other):
199 199 """Check for collisions between two config objects.
200 200
201 201 Returns a dict of the form {"Class": {"trait": "collision message"}}`,
202 202 indicating which values have been ignored.
203 203
204 204 An empty dict indicates no collisions.
205 205 """
206 206 collisions = {}
207 207 for section in self:
208 208 if section not in other:
209 209 continue
210 210 mine = self[section]
211 211 theirs = other[section]
212 212 for key in mine:
213 213 if key in theirs and mine[key] != theirs[key]:
214 214 collisions.setdefault(section, {})
215 215 collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key])
216 216 return collisions
217 217
218 218 def __contains__(self, key):
219 219 # allow nested contains of the form `"Section.key" in config`
220 220 if '.' in key:
221 221 first, remainder = key.split('.', 1)
222 222 if first not in self:
223 223 return False
224 224 return remainder in self[first]
225 225
226 226 return super(Config, self).__contains__(key)
227 227
228 228 # .has_key is deprecated for dictionaries.
229 229 has_key = __contains__
230 230
231 231 def _has_section(self, key):
232 232 return _is_section_key(key) and key in self
233 233
234 234 def copy(self):
235 235 return type(self)(dict.copy(self))
236 236 # copy nested config objects
237 237 for k, v in self.items():
238 238 if isinstance(v, Config):
239 239 new_config[k] = v.copy()
240 240 return new_config
241 241
242 242 def __copy__(self):
243 243 return self.copy()
244 244
245 245 def __deepcopy__(self, memo):
246 246 new_config = type(self)()
247 247 for key, value in self.items():
248 248 if isinstance(value, (Config, LazyConfigValue)):
249 249 # deep copy config objects
250 250 value = copy.deepcopy(value, memo)
251 251 elif type(value) in {dict, list, set, tuple}:
252 252 # shallow copy plain container traits
253 253 value = copy.copy(value)
254 254 new_config[key] = value
255 255 return new_config
256 256
257 257 def __getitem__(self, key):
258 258 try:
259 259 return dict.__getitem__(self, key)
260 260 except KeyError:
261 261 if _is_section_key(key):
262 262 c = Config()
263 263 dict.__setitem__(self, key, c)
264 264 return c
265 265 elif not key.startswith('_'):
266 266 # undefined, create lazy value, used for container methods
267 267 v = LazyConfigValue()
268 268 dict.__setitem__(self, key, v)
269 269 return v
270 270 else:
271 271 raise KeyError
272 272
273 273 def __setitem__(self, key, value):
274 274 if _is_section_key(key):
275 275 if not isinstance(value, Config):
276 276 raise ValueError('values whose keys begin with an uppercase '
277 277 'char must be Config instances: %r, %r' % (key, value))
278 278 dict.__setitem__(self, key, value)
279 279
280 280 def __getattr__(self, key):
281 281 if key.startswith('__'):
282 282 return dict.__getattr__(self, key)
283 283 try:
284 284 return self.__getitem__(key)
285 285 except KeyError as e:
286 286 raise AttributeError(e)
287 287
288 288 def __setattr__(self, key, value):
289 289 if key.startswith('__'):
290 290 return dict.__setattr__(self, key, value)
291 291 try:
292 292 self.__setitem__(key, value)
293 293 except KeyError as e:
294 294 raise AttributeError(e)
295 295
296 296 def __delattr__(self, key):
297 297 if key.startswith('__'):
298 298 return dict.__delattr__(self, key)
299 299 try:
300 300 dict.__delitem__(self, key)
301 301 except KeyError as e:
302 302 raise AttributeError(e)
303 303
304 304
305 305 #-----------------------------------------------------------------------------
306 306 # Config loading classes
307 307 #-----------------------------------------------------------------------------
308 308
309 309
310 310 class ConfigLoader(object):
311 311 """A object for loading configurations from just about anywhere.
312 312
313 313 The resulting configuration is packaged as a :class:`Config`.
314 314
315 315 Notes
316 316 -----
317 317 A :class:`ConfigLoader` does one thing: load a config from a source
318 318 (file, command line arguments) and returns the data as a :class:`Config` object.
319 319 There are lots of things that :class:`ConfigLoader` does not do. It does
320 320 not implement complex logic for finding config files. It does not handle
321 321 default values or merge multiple configs. These things need to be
322 322 handled elsewhere.
323 323 """
324 324
325 325 def _log_default(self):
326 326 from IPython.utils.log import get_logger
327 327 return get_logger()
328 328
329 329 def __init__(self, log=None):
330 330 """A base class for config loaders.
331 331
332 332 log : instance of :class:`logging.Logger` to use.
333 333 By default loger of :meth:`IPython.config.application.Application.instance()`
334 334 will be used
335 335
336 336 Examples
337 337 --------
338 338
339 339 >>> cl = ConfigLoader()
340 340 >>> config = cl.load_config()
341 341 >>> config
342 342 {}
343 343 """
344 344 self.clear()
345 345 if log is None:
346 346 self.log = self._log_default()
347 347 self.log.debug('Using default logger')
348 348 else:
349 349 self.log = log
350 350
351 351 def clear(self):
352 352 self.config = Config()
353 353
354 354 def load_config(self):
355 355 """Load a config from somewhere, return a :class:`Config` instance.
356 356
357 357 Usually, this will cause self.config to be set and then returned.
358 358 However, in most cases, :meth:`ConfigLoader.clear` should be called
359 359 to erase any previous state.
360 360 """
361 361 self.clear()
362 362 return self.config
363 363
364 364
365 365 class FileConfigLoader(ConfigLoader):
366 366 """A base class for file based configurations.
367 367
368 368 As we add more file based config loaders, the common logic should go
369 369 here.
370 370 """
371 371
372 372 def __init__(self, filename, path=None, **kw):
373 373 """Build a config loader for a filename and path.
374 374
375 375 Parameters
376 376 ----------
377 377 filename : str
378 378 The file name of the config file.
379 379 path : str, list, tuple
380 380 The path to search for the config file on, or a sequence of
381 381 paths to try in order.
382 382 """
383 383 super(FileConfigLoader, self).__init__(**kw)
384 384 self.filename = filename
385 385 self.path = path
386 386 self.full_filename = ''
387 387
388 388 def _find_file(self):
389 389 """Try to find the file by searching the paths."""
390 390 self.full_filename = filefind(self.filename, self.path)
391 391
392 392 class JSONFileConfigLoader(FileConfigLoader):
393 393 """A JSON file loader for config"""
394 394
395 395 def load_config(self):
396 396 """Load the config from a file and return it as a Config object."""
397 397 self.clear()
398 398 try:
399 399 self._find_file()
400 400 except IOError as e:
401 401 raise ConfigFileNotFound(str(e))
402 402 dct = self._read_file_as_dict()
403 403 self.config = self._convert_to_config(dct)
404 404 return self.config
405 405
406 406 def _read_file_as_dict(self):
407 407 with open(self.full_filename) as f:
408 408 return json.load(f)
409 409
410 410 def _convert_to_config(self, dictionary):
411 411 if 'version' in dictionary:
412 412 version = dictionary.pop('version')
413 413 else:
414 414 version = 1
415 415 self.log.warn("Unrecognized JSON config file version, assuming version {}".format(version))
416 416
417 417 if version == 1:
418 418 return Config(dictionary)
419 419 else:
420 420 raise ValueError('Unknown version of JSON config file: {version}'.format(version=version))
421 421
422 422
423 423 class PyFileConfigLoader(FileConfigLoader):
424 424 """A config loader for pure python files.
425 425
426 426 This is responsible for locating a Python config file by filename and
427 427 path, then executing it to construct a Config object.
428 428 """
429 429
430 430 def load_config(self):
431 431 """Load the config from a file and return it as a Config object."""
432 432 self.clear()
433 433 try:
434 434 self._find_file()
435 435 except IOError as e:
436 436 raise ConfigFileNotFound(str(e))
437 437 self._read_file_as_dict()
438 438 return self.config
439
440
439
440 def load_subconfig(self, fname, path=None):
441 """Injected into config file namespace as load_subconfig"""
442 if path is None:
443 path = self.path
444
445 loader = self.__class__(fname, path)
446 try:
447 sub_config = loader.load_config()
448 except ConfigFileNotFound:
449 # Pass silently if the sub config is not there,
450 # treat it as an empty config file.
451 pass
452 else:
453 self.config.merge(sub_config)
454
441 455 def _read_file_as_dict(self):
442 456 """Load the config file into self.config, with recursive loading."""
443 # This closure is made available in the namespace that is used
444 # to exec the config file. It allows users to call
445 # load_subconfig('myconfig.py') to load config files recursively.
446 # It needs to be a closure because it has references to self.path
447 # and self.config. The sub-config is loaded with the same path
448 # as the parent, but it uses an empty config which is then merged
449 # with the parents.
450
451 # If a profile is specified, the config file will be loaded
452 # from that profile
453
454 def load_subconfig(fname, profile=None):
455 # import here to prevent circular imports
456 from IPython.core.profiledir import ProfileDir, ProfileDirError
457 if profile is not None:
458 try:
459 profile_dir = ProfileDir.find_profile_dir_by_name(
460 get_ipython_dir(),
461 profile,
462 )
463 except ProfileDirError:
464 return
465 path = profile_dir.location
466 else:
467 path = self.path
468 loader = PyFileConfigLoader(fname, path)
469 try:
470 sub_config = loader.load_config()
471 except ConfigFileNotFound:
472 # Pass silently if the sub config is not there. This happens
473 # when a user s using a profile, but not the default config.
474 pass
475 else:
476 self.config.merge(sub_config)
477
478 # Again, this needs to be a closure and should be used in config
479 # files to get the config being loaded.
480 457 def get_config():
481 458 return self.config
482
459
483 460 namespace = dict(
484 load_subconfig=load_subconfig,
461 load_subconfig=self.load_subconfig,
485 462 get_config=get_config,
486 463 __file__=self.full_filename,
487 464 )
488 465 fs_encoding = sys.getfilesystemencoding() or 'ascii'
489 466 conf_filename = self.full_filename.encode(fs_encoding)
490 467 py3compat.execfile(conf_filename, namespace)
491 468
492 469
493 470 class CommandLineConfigLoader(ConfigLoader):
494 471 """A config loader for command line arguments.
495 472
496 473 As we add more command line based loaders, the common logic should go
497 474 here.
498 475 """
499 476
500 477 def _exec_config_str(self, lhs, rhs):
501 478 """execute self.config.<lhs> = <rhs>
502 479
503 480 * expands ~ with expanduser
504 481 * tries to assign with literal_eval, otherwise assigns with just the string,
505 482 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
506 483 equivalent are `--C.a=4` and `--C.a='4'`.
507 484 """
508 485 rhs = os.path.expanduser(rhs)
509 486 try:
510 487 # Try to see if regular Python syntax will work. This
511 488 # won't handle strings as the quote marks are removed
512 489 # by the system shell.
513 490 value = literal_eval(rhs)
514 491 except (NameError, SyntaxError, ValueError):
515 492 # This case happens if the rhs is a string.
516 493 value = rhs
517 494
518 495 exec(u'self.config.%s = value' % lhs)
519 496
520 497 def _load_flag(self, cfg):
521 498 """update self.config from a flag, which can be a dict or Config"""
522 499 if isinstance(cfg, (dict, Config)):
523 500 # don't clobber whole config sections, update
524 501 # each section from config:
525 502 for sec,c in iteritems(cfg):
526 503 self.config[sec].update(c)
527 504 else:
528 505 raise TypeError("Invalid flag: %r" % cfg)
529 506
530 507 # raw --identifier=value pattern
531 508 # but *also* accept '-' as wordsep, for aliases
532 509 # accepts: --foo=a
533 510 # --Class.trait=value
534 511 # --alias-name=value
535 512 # rejects: -foo=value
536 513 # --foo
537 514 # --Class.trait
538 515 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
539 516
540 517 # just flags, no assignments, with two *or one* leading '-'
541 518 # accepts: --foo
542 519 # -foo-bar-again
543 520 # rejects: --anything=anything
544 521 # --two.word
545 522
546 523 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
547 524
548 525 class KeyValueConfigLoader(CommandLineConfigLoader):
549 526 """A config loader that loads key value pairs from the command line.
550 527
551 528 This allows command line options to be gives in the following form::
552 529
553 530 ipython --profile="foo" --InteractiveShell.autocall=False
554 531 """
555 532
556 533 def __init__(self, argv=None, aliases=None, flags=None, **kw):
557 534 """Create a key value pair config loader.
558 535
559 536 Parameters
560 537 ----------
561 538 argv : list
562 539 A list that has the form of sys.argv[1:] which has unicode
563 540 elements of the form u"key=value". If this is None (default),
564 541 then sys.argv[1:] will be used.
565 542 aliases : dict
566 543 A dict of aliases for configurable traits.
567 544 Keys are the short aliases, Values are the resolved trait.
568 545 Of the form: `{'alias' : 'Configurable.trait'}`
569 546 flags : dict
570 547 A dict of flags, keyed by str name. Vaues can be Config objects,
571 548 dicts, or "key=value" strings. If Config or dict, when the flag
572 549 is triggered, The flag is loaded as `self.config.update(m)`.
573 550
574 551 Returns
575 552 -------
576 553 config : Config
577 554 The resulting Config object.
578 555
579 556 Examples
580 557 --------
581 558
582 559 >>> from IPython.config.loader import KeyValueConfigLoader
583 560 >>> cl = KeyValueConfigLoader()
584 561 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
585 562 >>> sorted(d.items())
586 563 [('A', {'name': 'brian'}), ('B', {'number': 0})]
587 564 """
588 565 super(KeyValueConfigLoader, self).__init__(**kw)
589 566 if argv is None:
590 567 argv = sys.argv[1:]
591 568 self.argv = argv
592 569 self.aliases = aliases or {}
593 570 self.flags = flags or {}
594 571
595 572
596 573 def clear(self):
597 574 super(KeyValueConfigLoader, self).clear()
598 575 self.extra_args = []
599 576
600 577
601 578 def _decode_argv(self, argv, enc=None):
602 579 """decode argv if bytes, using stdin.encoding, falling back on default enc"""
603 580 uargv = []
604 581 if enc is None:
605 582 enc = DEFAULT_ENCODING
606 583 for arg in argv:
607 584 if not isinstance(arg, unicode_type):
608 585 # only decode if not already decoded
609 586 arg = arg.decode(enc)
610 587 uargv.append(arg)
611 588 return uargv
612 589
613 590
614 591 def load_config(self, argv=None, aliases=None, flags=None):
615 592 """Parse the configuration and generate the Config object.
616 593
617 594 After loading, any arguments that are not key-value or
618 595 flags will be stored in self.extra_args - a list of
619 596 unparsed command-line arguments. This is used for
620 597 arguments such as input files or subcommands.
621 598
622 599 Parameters
623 600 ----------
624 601 argv : list, optional
625 602 A list that has the form of sys.argv[1:] which has unicode
626 603 elements of the form u"key=value". If this is None (default),
627 604 then self.argv will be used.
628 605 aliases : dict
629 606 A dict of aliases for configurable traits.
630 607 Keys are the short aliases, Values are the resolved trait.
631 608 Of the form: `{'alias' : 'Configurable.trait'}`
632 609 flags : dict
633 610 A dict of flags, keyed by str name. Values can be Config objects
634 611 or dicts. When the flag is triggered, The config is loaded as
635 612 `self.config.update(cfg)`.
636 613 """
637 614 self.clear()
638 615 if argv is None:
639 616 argv = self.argv
640 617 if aliases is None:
641 618 aliases = self.aliases
642 619 if flags is None:
643 620 flags = self.flags
644 621
645 622 # ensure argv is a list of unicode strings:
646 623 uargv = self._decode_argv(argv)
647 624 for idx,raw in enumerate(uargv):
648 625 # strip leading '-'
649 626 item = raw.lstrip('-')
650 627
651 628 if raw == '--':
652 629 # don't parse arguments after '--'
653 630 # this is useful for relaying arguments to scripts, e.g.
654 631 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
655 632 self.extra_args.extend(uargv[idx+1:])
656 633 break
657 634
658 635 if kv_pattern.match(raw):
659 636 lhs,rhs = item.split('=',1)
660 637 # Substitute longnames for aliases.
661 638 if lhs in aliases:
662 639 lhs = aliases[lhs]
663 640 if '.' not in lhs:
664 641 # probably a mistyped alias, but not technically illegal
665 642 self.log.warn("Unrecognized alias: '%s', it will probably have no effect.", raw)
666 643 try:
667 644 self._exec_config_str(lhs, rhs)
668 645 except Exception:
669 646 raise ArgumentError("Invalid argument: '%s'" % raw)
670 647
671 648 elif flag_pattern.match(raw):
672 649 if item in flags:
673 650 cfg,help = flags[item]
674 651 self._load_flag(cfg)
675 652 else:
676 653 raise ArgumentError("Unrecognized flag: '%s'"%raw)
677 654 elif raw.startswith('-'):
678 655 kv = '--'+item
679 656 if kv_pattern.match(kv):
680 657 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
681 658 else:
682 659 raise ArgumentError("Invalid argument: '%s'"%raw)
683 660 else:
684 661 # keep all args that aren't valid in a list,
685 662 # in case our parent knows what to do with them.
686 663 self.extra_args.append(item)
687 664 return self.config
688 665
689 666 class ArgParseConfigLoader(CommandLineConfigLoader):
690 667 """A loader that uses the argparse module to load from the command line."""
691 668
692 669 def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw):
693 670 """Create a config loader for use with argparse.
694 671
695 672 Parameters
696 673 ----------
697 674
698 675 argv : optional, list
699 676 If given, used to read command-line arguments from, otherwise
700 677 sys.argv[1:] is used.
701 678
702 679 parser_args : tuple
703 680 A tuple of positional arguments that will be passed to the
704 681 constructor of :class:`argparse.ArgumentParser`.
705 682
706 683 parser_kw : dict
707 684 A tuple of keyword arguments that will be passed to the
708 685 constructor of :class:`argparse.ArgumentParser`.
709 686
710 687 Returns
711 688 -------
712 689 config : Config
713 690 The resulting Config object.
714 691 """
715 692 super(CommandLineConfigLoader, self).__init__(log=log)
716 693 self.clear()
717 694 if argv is None:
718 695 argv = sys.argv[1:]
719 696 self.argv = argv
720 697 self.aliases = aliases or {}
721 698 self.flags = flags or {}
722 699
723 700 self.parser_args = parser_args
724 701 self.version = parser_kw.pop("version", None)
725 702 kwargs = dict(argument_default=argparse.SUPPRESS)
726 703 kwargs.update(parser_kw)
727 704 self.parser_kw = kwargs
728 705
729 706 def load_config(self, argv=None, aliases=None, flags=None):
730 707 """Parse command line arguments and return as a Config object.
731 708
732 709 Parameters
733 710 ----------
734 711
735 712 args : optional, list
736 713 If given, a list with the structure of sys.argv[1:] to parse
737 714 arguments from. If not given, the instance's self.argv attribute
738 715 (given at construction time) is used."""
739 716 self.clear()
740 717 if argv is None:
741 718 argv = self.argv
742 719 if aliases is None:
743 720 aliases = self.aliases
744 721 if flags is None:
745 722 flags = self.flags
746 723 self._create_parser(aliases, flags)
747 724 self._parse_args(argv)
748 725 self._convert_to_config()
749 726 return self.config
750 727
751 728 def get_extra_args(self):
752 729 if hasattr(self, 'extra_args'):
753 730 return self.extra_args
754 731 else:
755 732 return []
756 733
757 734 def _create_parser(self, aliases=None, flags=None):
758 735 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
759 736 self._add_arguments(aliases, flags)
760 737
761 738 def _add_arguments(self, aliases=None, flags=None):
762 739 raise NotImplementedError("subclasses must implement _add_arguments")
763 740
764 741 def _parse_args(self, args):
765 742 """self.parser->self.parsed_data"""
766 743 # decode sys.argv to support unicode command-line options
767 744 enc = DEFAULT_ENCODING
768 745 uargs = [py3compat.cast_unicode(a, enc) for a in args]
769 746 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
770 747
771 748 def _convert_to_config(self):
772 749 """self.parsed_data->self.config"""
773 750 for k, v in iteritems(vars(self.parsed_data)):
774 751 exec("self.config.%s = v"%k, locals(), globals())
775 752
776 753 class KVArgParseConfigLoader(ArgParseConfigLoader):
777 754 """A config loader that loads aliases and flags with argparse,
778 755 but will use KVLoader for the rest. This allows better parsing
779 756 of common args, such as `ipython -c 'print 5'`, but still gets
780 757 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
781 758
782 759 def _add_arguments(self, aliases=None, flags=None):
783 760 self.alias_flags = {}
784 761 # print aliases, flags
785 762 if aliases is None:
786 763 aliases = self.aliases
787 764 if flags is None:
788 765 flags = self.flags
789 766 paa = self.parser.add_argument
790 767 for key,value in iteritems(aliases):
791 768 if key in flags:
792 769 # flags
793 770 nargs = '?'
794 771 else:
795 772 nargs = None
796 773 if len(key) is 1:
797 774 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
798 775 else:
799 776 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
800 777 for key, (value, help) in iteritems(flags):
801 778 if key in self.aliases:
802 779 #
803 780 self.alias_flags[self.aliases[key]] = value
804 781 continue
805 782 if len(key) is 1:
806 783 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
807 784 else:
808 785 paa('--'+key, action='append_const', dest='_flags', const=value)
809 786
810 787 def _convert_to_config(self):
811 788 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
812 789 # remove subconfigs list from namespace before transforming the Namespace
813 790 if '_flags' in self.parsed_data:
814 791 subcs = self.parsed_data._flags
815 792 del self.parsed_data._flags
816 793 else:
817 794 subcs = []
818 795
819 796 for k, v in iteritems(vars(self.parsed_data)):
820 797 if v is None:
821 798 # it was a flag that shares the name of an alias
822 799 subcs.append(self.alias_flags[k])
823 800 else:
824 801 # eval the KV assignment
825 802 self._exec_config_str(k, v)
826 803
827 804 for subc in subcs:
828 805 self._load_flag(subc)
829 806
830 807 if self.extra_args:
831 808 sub_parser = KeyValueConfigLoader(log=self.log)
832 809 sub_parser.load_config(self.extra_args)
833 810 self.config.merge(sub_parser.config)
834 811 self.extra_args = sub_parser.extra_args
835 812
836 813
837 814 def load_pyconfig_files(config_files, path):
838 815 """Load multiple Python config files, merging each of them in turn.
839 816
840 817 Parameters
841 818 ==========
842 819 config_files : list of str
843 820 List of config files names to load and merge into the config.
844 821 path : unicode
845 822 The full path to the location of the config files.
846 823 """
847 824 config = Config()
848 825 for cf in config_files:
849 826 loader = PyFileConfigLoader(cf, path=path)
850 827 try:
851 828 next_config = loader.load_config()
852 829 except ConfigFileNotFound:
853 830 pass
854 831 except:
855 832 raise
856 833 else:
857 834 config.merge(next_config)
858 835 return config
@@ -1,380 +1,396 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating configurables.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 import atexit
16 16 import glob
17 17 import logging
18 18 import os
19 19 import shutil
20 20 import sys
21 21
22 22 from IPython.config.application import Application, catch_config_error
23 from IPython.config.loader import ConfigFileNotFound
23 from IPython.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 24 from IPython.core import release, crashhandler
25 25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 26 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
27 27 from IPython.utils import py3compat
28 28 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
29 29
30 30 if os.name == 'nt':
31 31 programdata = os.environ.get('PROGRAMDATA', None)
32 32 if programdata:
33 33 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
34 34 else: # PROGRAMDATA is not defined by default on XP.
35 35 SYSTEM_CONFIG_DIRS = []
36 36 else:
37 37 SYSTEM_CONFIG_DIRS = [
38 38 "/usr/local/etc/ipython",
39 39 "/etc/ipython",
40 40 ]
41 41
42 42
43 43 # aliases and flags
44 44
45 45 base_aliases = {
46 46 'profile-dir' : 'ProfileDir.location',
47 47 'profile' : 'BaseIPythonApplication.profile',
48 48 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
49 49 'log-level' : 'Application.log_level',
50 50 'config' : 'BaseIPythonApplication.extra_config_file',
51 51 }
52 52
53 53 base_flags = dict(
54 54 debug = ({'Application' : {'log_level' : logging.DEBUG}},
55 55 "set log level to logging.DEBUG (maximize logging output)"),
56 56 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
57 57 "set log level to logging.CRITICAL (minimize logging output)"),
58 58 init = ({'BaseIPythonApplication' : {
59 59 'copy_config_files' : True,
60 60 'auto_create' : True}
61 61 }, """Initialize profile with default config files. This is equivalent
62 62 to running `ipython profile create <profile>` prior to startup.
63 63 """)
64 64 )
65 65
66 class ProfileAwareConfigLoader(PyFileConfigLoader):
67 """A Python file config loader that is aware of IPython profiles."""
68 def load_subconfig(self, fname, path=None, profile=None):
69 if profile is not None:
70 try:
71 profile_dir = ProfileDir.find_profile_dir_by_name(
72 get_ipython_dir(),
73 profile,
74 )
75 except ProfileDirError:
76 return
77 path = profile_dir.location
78 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
66 79
67 80 class BaseIPythonApplication(Application):
68 81
69 82 name = Unicode(u'ipython')
70 83 description = Unicode(u'IPython: an enhanced interactive Python shell.')
71 84 version = Unicode(release.version)
72 85
73 86 aliases = Dict(base_aliases)
74 87 flags = Dict(base_flags)
75 88 classes = List([ProfileDir])
89
90 # enable `load_subconfig('cfg.py', profile='name')`
91 python_config_loader_class = ProfileAwareConfigLoader
76 92
77 93 # Track whether the config_file has changed,
78 94 # because some logic happens only if we aren't using the default.
79 95 config_file_specified = Set()
80 96
81 97 config_file_name = Unicode()
82 98 def _config_file_name_default(self):
83 99 return self.name.replace('-','_') + u'_config.py'
84 100 def _config_file_name_changed(self, name, old, new):
85 101 if new != old:
86 102 self.config_file_specified.add(new)
87 103
88 104 # The directory that contains IPython's builtin profiles.
89 105 builtin_profile_dir = Unicode(
90 106 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
91 107 )
92 108
93 109 config_file_paths = List(Unicode)
94 110 def _config_file_paths_default(self):
95 111 return [py3compat.getcwd()]
96 112
97 113 extra_config_file = Unicode(config=True,
98 114 help="""Path to an extra config file to load.
99 115
100 116 If specified, load this config file in addition to any other IPython config.
101 117 """)
102 118 def _extra_config_file_changed(self, name, old, new):
103 119 try:
104 120 self.config_files.remove(old)
105 121 except ValueError:
106 122 pass
107 123 self.config_file_specified.add(new)
108 124 self.config_files.append(new)
109 125
110 126 profile = Unicode(u'default', config=True,
111 127 help="""The IPython profile to use."""
112 128 )
113 129
114 130 def _profile_changed(self, name, old, new):
115 131 self.builtin_profile_dir = os.path.join(
116 132 get_ipython_package_dir(), u'config', u'profile', new
117 133 )
118 134
119 135 ipython_dir = Unicode(config=True,
120 136 help="""
121 137 The name of the IPython directory. This directory is used for logging
122 138 configuration (through profiles), history storage, etc. The default
123 139 is usually $HOME/.ipython. This option can also be specified through
124 140 the environment variable IPYTHONDIR.
125 141 """
126 142 )
127 143 def _ipython_dir_default(self):
128 144 d = get_ipython_dir()
129 145 self._ipython_dir_changed('ipython_dir', d, d)
130 146 return d
131 147
132 148 _in_init_profile_dir = False
133 149 profile_dir = Instance(ProfileDir)
134 150 def _profile_dir_default(self):
135 151 # avoid recursion
136 152 if self._in_init_profile_dir:
137 153 return
138 154 # profile_dir requested early, force initialization
139 155 self.init_profile_dir()
140 156 return self.profile_dir
141 157
142 158 overwrite = Bool(False, config=True,
143 159 help="""Whether to overwrite existing config files when copying""")
144 160 auto_create = Bool(False, config=True,
145 161 help="""Whether to create profile dir if it doesn't exist""")
146 162
147 163 config_files = List(Unicode)
148 164 def _config_files_default(self):
149 165 return [self.config_file_name]
150 166
151 167 copy_config_files = Bool(False, config=True,
152 168 help="""Whether to install the default config files into the profile dir.
153 169 If a new profile is being created, and IPython contains config files for that
154 170 profile, then they will be staged into the new directory. Otherwise,
155 171 default config files will be automatically generated.
156 172 """)
157 173
158 174 verbose_crash = Bool(False, config=True,
159 175 help="""Create a massive crash report when IPython encounters what may be an
160 176 internal error. The default is to append a short message to the
161 177 usual traceback""")
162 178
163 179 # The class to use as the crash handler.
164 180 crash_handler_class = Type(crashhandler.CrashHandler)
165 181
166 182 @catch_config_error
167 183 def __init__(self, **kwargs):
168 184 super(BaseIPythonApplication, self).__init__(**kwargs)
169 185 # ensure current working directory exists
170 186 try:
171 187 directory = py3compat.getcwd()
172 188 except:
173 189 # exit if cwd doesn't exist
174 190 self.log.error("Current working directory doesn't exist.")
175 191 self.exit(1)
176 192
177 193 #-------------------------------------------------------------------------
178 194 # Various stages of Application creation
179 195 #-------------------------------------------------------------------------
180 196
181 197 def init_crash_handler(self):
182 198 """Create a crash handler, typically setting sys.excepthook to it."""
183 199 self.crash_handler = self.crash_handler_class(self)
184 200 sys.excepthook = self.excepthook
185 201 def unset_crashhandler():
186 202 sys.excepthook = sys.__excepthook__
187 203 atexit.register(unset_crashhandler)
188 204
189 205 def excepthook(self, etype, evalue, tb):
190 206 """this is sys.excepthook after init_crashhandler
191 207
192 208 set self.verbose_crash=True to use our full crashhandler, instead of
193 209 a regular traceback with a short message (crash_handler_lite)
194 210 """
195 211
196 212 if self.verbose_crash:
197 213 return self.crash_handler(etype, evalue, tb)
198 214 else:
199 215 return crashhandler.crash_handler_lite(etype, evalue, tb)
200 216
201 217 def _ipython_dir_changed(self, name, old, new):
202 218 if old is not None:
203 219 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
204 220 sys.getfilesystemencoding()
205 221 )
206 222 if str_old in sys.path:
207 223 sys.path.remove(str_old)
208 224 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
209 225 sys.getfilesystemencoding()
210 226 )
211 227 sys.path.append(str_path)
212 228 ensure_dir_exists(new)
213 229 readme = os.path.join(new, 'README')
214 230 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
215 231 if not os.path.exists(readme) and os.path.exists(readme_src):
216 232 shutil.copy(readme_src, readme)
217 233 for d in ('extensions', 'nbextensions'):
218 234 path = os.path.join(new, d)
219 235 try:
220 236 ensure_dir_exists(path)
221 237 except OSError:
222 238 # this will not be EEXIST
223 239 self.log.error("couldn't create path %s: %s", path, e)
224 240 self.log.debug("IPYTHONDIR set to: %s" % new)
225 241
226 242 def load_config_file(self, suppress_errors=True):
227 243 """Load the config file.
228 244
229 245 By default, errors in loading config are handled, and a warning
230 246 printed on screen. For testing, the suppress_errors option is set
231 247 to False, so errors will make tests fail.
232 248 """
233 249 self.log.debug("Searching path %s for config files", self.config_file_paths)
234 250 base_config = 'ipython_config.py'
235 251 self.log.debug("Attempting to load config file: %s" %
236 252 base_config)
237 253 try:
238 254 Application.load_config_file(
239 255 self,
240 256 base_config,
241 257 path=self.config_file_paths
242 258 )
243 259 except ConfigFileNotFound:
244 260 # ignore errors loading parent
245 261 self.log.debug("Config file %s not found", base_config)
246 262 pass
247 263
248 264 for config_file_name in self.config_files:
249 265 if not config_file_name or config_file_name == base_config:
250 266 continue
251 267 self.log.debug("Attempting to load config file: %s" %
252 268 self.config_file_name)
253 269 try:
254 270 Application.load_config_file(
255 271 self,
256 272 config_file_name,
257 273 path=self.config_file_paths
258 274 )
259 275 except ConfigFileNotFound:
260 276 # Only warn if the default config file was NOT being used.
261 277 if config_file_name in self.config_file_specified:
262 278 msg = self.log.warn
263 279 else:
264 280 msg = self.log.debug
265 281 msg("Config file not found, skipping: %s", config_file_name)
266 282 except:
267 283 # For testing purposes.
268 284 if not suppress_errors:
269 285 raise
270 286 self.log.warn("Error loading config file: %s" %
271 287 self.config_file_name, exc_info=True)
272 288
273 289 def init_profile_dir(self):
274 290 """initialize the profile dir"""
275 291 self._in_init_profile_dir = True
276 292 if self.profile_dir is not None:
277 293 # already ran
278 294 return
279 295 if 'ProfileDir.location' not in self.config:
280 296 # location not specified, find by profile name
281 297 try:
282 298 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
283 299 except ProfileDirError:
284 300 # not found, maybe create it (always create default profile)
285 301 if self.auto_create or self.profile == 'default':
286 302 try:
287 303 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
288 304 except ProfileDirError:
289 305 self.log.fatal("Could not create profile: %r"%self.profile)
290 306 self.exit(1)
291 307 else:
292 308 self.log.info("Created profile dir: %r"%p.location)
293 309 else:
294 310 self.log.fatal("Profile %r not found."%self.profile)
295 311 self.exit(1)
296 312 else:
297 313 self.log.debug("Using existing profile dir: %r"%p.location)
298 314 else:
299 315 location = self.config.ProfileDir.location
300 316 # location is fully specified
301 317 try:
302 318 p = ProfileDir.find_profile_dir(location, self.config)
303 319 except ProfileDirError:
304 320 # not found, maybe create it
305 321 if self.auto_create:
306 322 try:
307 323 p = ProfileDir.create_profile_dir(location, self.config)
308 324 except ProfileDirError:
309 325 self.log.fatal("Could not create profile directory: %r"%location)
310 326 self.exit(1)
311 327 else:
312 328 self.log.debug("Creating new profile dir: %r"%location)
313 329 else:
314 330 self.log.fatal("Profile directory %r not found."%location)
315 331 self.exit(1)
316 332 else:
317 333 self.log.info("Using existing profile dir: %r"%location)
318 334 # if profile_dir is specified explicitly, set profile name
319 335 dir_name = os.path.basename(p.location)
320 336 if dir_name.startswith('profile_'):
321 337 self.profile = dir_name[8:]
322 338
323 339 self.profile_dir = p
324 340 self.config_file_paths.append(p.location)
325 341 self._in_init_profile_dir = False
326 342
327 343 def init_config_files(self):
328 344 """[optionally] copy default config files into profile dir."""
329 345 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
330 346 # copy config files
331 347 path = self.builtin_profile_dir
332 348 if self.copy_config_files:
333 349 src = self.profile
334 350
335 351 cfg = self.config_file_name
336 352 if path and os.path.exists(os.path.join(path, cfg)):
337 353 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
338 354 cfg, src, self.profile_dir.location, self.overwrite)
339 355 )
340 356 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
341 357 else:
342 358 self.stage_default_config_file()
343 359 else:
344 360 # Still stage *bundled* config files, but not generated ones
345 361 # This is necessary for `ipython profile=sympy` to load the profile
346 362 # on the first go
347 363 files = glob.glob(os.path.join(path, '*.py'))
348 364 for fullpath in files:
349 365 cfg = os.path.basename(fullpath)
350 366 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
351 367 # file was copied
352 368 self.log.warn("Staging bundled %s from %s into %r"%(
353 369 cfg, self.profile, self.profile_dir.location)
354 370 )
355 371
356 372
357 373 def stage_default_config_file(self):
358 374 """auto generate default config file, and stage it into the profile."""
359 375 s = self.generate_config_file()
360 376 fname = os.path.join(self.profile_dir.location, self.config_file_name)
361 377 if self.overwrite or not os.path.exists(fname):
362 378 self.log.warn("Generating default config file: %r"%(fname))
363 379 with open(fname, 'w') as f:
364 380 f.write(s)
365 381
366 382 @catch_config_error
367 383 def initialize(self, argv=None):
368 384 # don't hook up crash handler before parsing command-line
369 385 self.parse_command_line(argv)
370 386 self.init_crash_handler()
371 387 if self.subapp is not None:
372 388 # stop here if subapp is taking over
373 389 return
374 390 cl_config = self.config
375 391 self.init_profile_dir()
376 392 self.init_config_files()
377 393 self.load_config_file()
378 394 # enforce cl-opts override configfile opts:
379 395 self.update_config(cl_config)
380 396
General Comments 0
You need to be logged in to leave comments. Login now