##// END OF EJS Templates
make Config.merge a public method...
MinRK -
Show More
@@ -1,561 +1,561 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import logging
23 23 import os
24 24 import re
25 25 import sys
26 26 from copy import deepcopy
27 27 from collections import defaultdict
28 28
29 29 from IPython.external.decorator import decorator
30 30
31 31 from IPython.config.configurable import SingletonConfigurable
32 32 from IPython.config.loader import (
33 33 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound,
34 34 )
35 35
36 36 from IPython.utils.traitlets import (
37 37 Unicode, List, Enum, Dict, Instance, TraitError
38 38 )
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.utils.text import indent, wrap_paragraphs, dedent
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # function for re-wrapping a helpstring
44 44 #-----------------------------------------------------------------------------
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Descriptions for the various sections
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # merge flags&aliases into options
51 51 option_description = """
52 52 Arguments that take values are actually convenience aliases to full
53 53 Configurables, whose aliases are listed on the help line. For more information
54 54 on full configurables, see '--help-all'.
55 55 """.strip() # trim newlines of front and back
56 56
57 57 keyvalue_description = """
58 58 Parameters are set from command-line arguments of the form:
59 59 `--Class.trait=value`.
60 60 This line is evaluated in Python, so simple expressions are allowed, e.g.::
61 61 `--C.a='range(3)'` For setting C.a=[0,1,2].
62 62 """.strip() # trim newlines of front and back
63 63
64 64 subcommand_description = """
65 65 Subcommands are launched as `{app} cmd [args]`. For information on using
66 66 subcommand 'cmd', do: `{app} cmd -h`.
67 67 """.strip().format(app=os.path.basename(sys.argv[0]))
68 68 # get running program name
69 69
70 70 #-----------------------------------------------------------------------------
71 71 # Application class
72 72 #-----------------------------------------------------------------------------
73 73
74 74 @decorator
75 75 def catch_config_error(method, app, *args, **kwargs):
76 76 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
77 77
78 78 On a TraitError (generally caused by bad config), this will print the trait's
79 79 message, and exit the app.
80 80
81 81 For use on init methods, to prevent invoking excepthook on invalid input.
82 82 """
83 83 try:
84 84 return method(app, *args, **kwargs)
85 85 except (TraitError, ArgumentError) as e:
86 86 app.print_description()
87 87 app.print_help()
88 88 app.print_examples()
89 89 app.log.fatal("Bad config encountered during initialization:")
90 90 app.log.fatal(str(e))
91 91 app.log.debug("Config at the time: %s", app.config)
92 92 app.exit(1)
93 93
94 94
95 95 class ApplicationError(Exception):
96 96 pass
97 97
98 98 class LevelFormatter(logging.Formatter):
99 99 """Formatter with additional `highlevel` record
100 100
101 101 This field is empty if log level is less than highlevel_limit,
102 102 otherwise it is formatted with self.highlevel_format.
103 103
104 104 Useful for adding 'WARNING' to warning messages,
105 105 without adding 'INFO' to info, etc.
106 106 """
107 107 highlevel_limit = logging.WARN
108 108 highlevel_format = " %(levelname)s |"
109 109
110 110 def format(self, record):
111 111 if record.levelno >= self.highlevel_limit:
112 112 record.highlevel = self.highlevel_format % record.__dict__
113 113 else:
114 114 record.highlevel = ""
115 115
116 116 return super(LevelFormatter, self).format(record)
117 117
118 118
119 119 class Application(SingletonConfigurable):
120 120 """A singleton application with full configuration support."""
121 121
122 122 # The name of the application, will usually match the name of the command
123 123 # line application
124 124 name = Unicode(u'application')
125 125
126 126 # The description of the application that is printed at the beginning
127 127 # of the help.
128 128 description = Unicode(u'This is an application.')
129 129 # default section descriptions
130 130 option_description = Unicode(option_description)
131 131 keyvalue_description = Unicode(keyvalue_description)
132 132 subcommand_description = Unicode(subcommand_description)
133 133
134 134 # The usage and example string that goes at the end of the help string.
135 135 examples = Unicode()
136 136
137 137 # A sequence of Configurable subclasses whose config=True attributes will
138 138 # be exposed at the command line.
139 139 classes = List([])
140 140
141 141 # The version string of this application.
142 142 version = Unicode(u'0.0')
143 143
144 144 # The log level for the application
145 145 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
146 146 default_value=logging.WARN,
147 147 config=True,
148 148 help="Set the log level by value or name.")
149 149 def _log_level_changed(self, name, old, new):
150 150 """Adjust the log level when log_level is set."""
151 151 if isinstance(new, basestring):
152 152 new = getattr(logging, new)
153 153 self.log_level = new
154 154 self.log.setLevel(new)
155 155
156 156 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
157 157 help="The date format used by logging formatters for %(asctime)s"
158 158 )
159 159 def _log_datefmt_changed(self, name, old, new):
160 160 self._log_format_changed()
161 161
162 162 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
163 163 help="The Logging format template",
164 164 )
165 165 def _log_format_changed(self, name, old, new):
166 166 """Change the log formatter when log_format is set."""
167 167 _log_handler = self.log.handlers[0]
168 168 _log_formatter = LevelFormatter(new, datefmt=self._log_datefmt)
169 169 _log_handler.setFormatter(_log_formatter)
170 170
171 171 log = Instance(logging.Logger)
172 172 def _log_default(self):
173 173 """Start logging for this application.
174 174
175 175 The default is to log to stderr using a StreamHandler, if no default
176 176 handler already exists. The log level starts at logging.WARN, but this
177 177 can be adjusted by setting the ``log_level`` attribute.
178 178 """
179 179 log = logging.getLogger(self.__class__.__name__)
180 180 log.setLevel(self.log_level)
181 181 log.propagate = False
182 182 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
183 183 while _log:
184 184 if _log.handlers:
185 185 return log
186 186 if not _log.propagate:
187 187 break
188 188 else:
189 189 _log = _log.parent
190 190 if sys.executable.endswith('pythonw.exe'):
191 191 # this should really go to a file, but file-logging is only
192 192 # hooked up in parallel applications
193 193 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
194 194 else:
195 195 _log_handler = logging.StreamHandler()
196 196 _log_formatter = LevelFormatter(self.log_format, datefmt=self.log_datefmt)
197 197 _log_handler.setFormatter(_log_formatter)
198 198 log.addHandler(_log_handler)
199 199 return log
200 200
201 201 # the alias map for configurables
202 202 aliases = Dict({'log-level' : 'Application.log_level'})
203 203
204 204 # flags for loading Configurables or store_const style flags
205 205 # flags are loaded from this dict by '--key' flags
206 206 # this must be a dict of two-tuples, the first element being the Config/dict
207 207 # and the second being the help string for the flag
208 208 flags = Dict()
209 209 def _flags_changed(self, name, old, new):
210 210 """ensure flags dict is valid"""
211 211 for key,value in new.iteritems():
212 212 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
213 213 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
214 214 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
215 215
216 216
217 217 # subcommands for launching other applications
218 218 # if this is not empty, this will be a parent Application
219 219 # this must be a dict of two-tuples,
220 220 # the first element being the application class/import string
221 221 # and the second being the help string for the subcommand
222 222 subcommands = Dict()
223 223 # parse_command_line will initialize a subapp, if requested
224 224 subapp = Instance('IPython.config.application.Application', allow_none=True)
225 225
226 226 # extra command-line arguments that don't set config values
227 227 extra_args = List(Unicode)
228 228
229 229
230 230 def __init__(self, **kwargs):
231 231 SingletonConfigurable.__init__(self, **kwargs)
232 232 # Ensure my class is in self.classes, so my attributes appear in command line
233 233 # options and config files.
234 234 if self.__class__ not in self.classes:
235 235 self.classes.insert(0, self.__class__)
236 236
237 237 def _config_changed(self, name, old, new):
238 238 SingletonConfigurable._config_changed(self, name, old, new)
239 239 self.log.debug('Config changed:')
240 240 self.log.debug(repr(new))
241 241
242 242 @catch_config_error
243 243 def initialize(self, argv=None):
244 244 """Do the basic steps to configure me.
245 245
246 246 Override in subclasses.
247 247 """
248 248 self.parse_command_line(argv)
249 249
250 250
251 251 def start(self):
252 252 """Start the app mainloop.
253 253
254 254 Override in subclasses.
255 255 """
256 256 if self.subapp is not None:
257 257 return self.subapp.start()
258 258
259 259 def print_alias_help(self):
260 260 """Print the alias part of the help."""
261 261 if not self.aliases:
262 262 return
263 263
264 264 lines = []
265 265 classdict = {}
266 266 for cls in self.classes:
267 267 # include all parents (up to, but excluding Configurable) in available names
268 268 for c in cls.mro()[:-3]:
269 269 classdict[c.__name__] = c
270 270
271 271 for alias, longname in self.aliases.iteritems():
272 272 classname, traitname = longname.split('.',1)
273 273 cls = classdict[classname]
274 274
275 275 trait = cls.class_traits(config=True)[traitname]
276 276 help = cls.class_get_trait_help(trait).splitlines()
277 277 # reformat first line
278 278 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
279 279 if len(alias) == 1:
280 280 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
281 281 lines.extend(help)
282 282 # lines.append('')
283 283 print os.linesep.join(lines)
284 284
285 285 def print_flag_help(self):
286 286 """Print the flag part of the help."""
287 287 if not self.flags:
288 288 return
289 289
290 290 lines = []
291 291 for m, (cfg,help) in self.flags.iteritems():
292 292 prefix = '--' if len(m) > 1 else '-'
293 293 lines.append(prefix+m)
294 294 lines.append(indent(dedent(help.strip())))
295 295 # lines.append('')
296 296 print os.linesep.join(lines)
297 297
298 298 def print_options(self):
299 299 if not self.flags and not self.aliases:
300 300 return
301 301 lines = ['Options']
302 302 lines.append('-'*len(lines[0]))
303 303 lines.append('')
304 304 for p in wrap_paragraphs(self.option_description):
305 305 lines.append(p)
306 306 lines.append('')
307 307 print os.linesep.join(lines)
308 308 self.print_flag_help()
309 309 self.print_alias_help()
310 310 print
311 311
312 312 def print_subcommands(self):
313 313 """Print the subcommand part of the help."""
314 314 if not self.subcommands:
315 315 return
316 316
317 317 lines = ["Subcommands"]
318 318 lines.append('-'*len(lines[0]))
319 319 lines.append('')
320 320 for p in wrap_paragraphs(self.subcommand_description):
321 321 lines.append(p)
322 322 lines.append('')
323 323 for subc, (cls, help) in self.subcommands.iteritems():
324 324 lines.append(subc)
325 325 if help:
326 326 lines.append(indent(dedent(help.strip())))
327 327 lines.append('')
328 328 print os.linesep.join(lines)
329 329
330 330 def print_help(self, classes=False):
331 331 """Print the help for each Configurable class in self.classes.
332 332
333 333 If classes=False (the default), only flags and aliases are printed.
334 334 """
335 335 self.print_subcommands()
336 336 self.print_options()
337 337
338 338 if classes:
339 339 if self.classes:
340 340 print "Class parameters"
341 341 print "----------------"
342 342 print
343 343 for p in wrap_paragraphs(self.keyvalue_description):
344 344 print p
345 345 print
346 346
347 347 for cls in self.classes:
348 348 cls.class_print_help()
349 349 print
350 350 else:
351 351 print "To see all available configurables, use `--help-all`"
352 352 print
353 353
354 354 def print_description(self):
355 355 """Print the application description."""
356 356 for p in wrap_paragraphs(self.description):
357 357 print p
358 358 print
359 359
360 360 def print_examples(self):
361 361 """Print usage and examples.
362 362
363 363 This usage string goes at the end of the command line help string
364 364 and should contain examples of the application's usage.
365 365 """
366 366 if self.examples:
367 367 print "Examples"
368 368 print "--------"
369 369 print
370 370 print indent(dedent(self.examples.strip()))
371 371 print
372 372
373 373 def print_version(self):
374 374 """Print the version string."""
375 375 print self.version
376 376
377 377 def update_config(self, config):
378 378 """Fire the traits events when the config is updated."""
379 379 # Save a copy of the current config.
380 380 newconfig = deepcopy(self.config)
381 381 # Merge the new config into the current one.
382 newconfig._merge(config)
382 newconfig.merge(config)
383 383 # Save the combined config as self.config, which triggers the traits
384 384 # events.
385 385 self.config = newconfig
386 386
387 387 @catch_config_error
388 388 def initialize_subcommand(self, subc, argv=None):
389 389 """Initialize a subcommand with argv."""
390 390 subapp,help = self.subcommands.get(subc)
391 391
392 392 if isinstance(subapp, basestring):
393 393 subapp = import_item(subapp)
394 394
395 395 # clear existing instances
396 396 self.__class__.clear_instance()
397 397 # instantiate
398 398 self.subapp = subapp.instance()
399 399 # and initialize subapp
400 400 self.subapp.initialize(argv)
401 401
402 402 def flatten_flags(self):
403 403 """flatten flags and aliases, so cl-args override as expected.
404 404
405 405 This prevents issues such as an alias pointing to InteractiveShell,
406 406 but a config file setting the same trait in TerminalInteraciveShell
407 407 getting inappropriate priority over the command-line arg.
408 408
409 409 Only aliases with exactly one descendent in the class list
410 410 will be promoted.
411 411
412 412 """
413 413 # build a tree of classes in our list that inherit from a particular
414 414 # it will be a dict by parent classname of classes in our list
415 415 # that are descendents
416 416 mro_tree = defaultdict(list)
417 417 for cls in self.classes:
418 418 clsname = cls.__name__
419 419 for parent in cls.mro()[1:-3]:
420 420 # exclude cls itself and Configurable,HasTraits,object
421 421 mro_tree[parent.__name__].append(clsname)
422 422 # flatten aliases, which have the form:
423 423 # { 'alias' : 'Class.trait' }
424 424 aliases = {}
425 425 for alias, cls_trait in self.aliases.iteritems():
426 426 cls,trait = cls_trait.split('.',1)
427 427 children = mro_tree[cls]
428 428 if len(children) == 1:
429 429 # exactly one descendent, promote alias
430 430 cls = children[0]
431 431 aliases[alias] = '.'.join([cls,trait])
432 432
433 433 # flatten flags, which are of the form:
434 434 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
435 435 flags = {}
436 436 for key, (flagdict, help) in self.flags.iteritems():
437 437 newflag = {}
438 438 for cls, subdict in flagdict.iteritems():
439 439 children = mro_tree[cls]
440 440 # exactly one descendent, promote flag section
441 441 if len(children) == 1:
442 442 cls = children[0]
443 443 newflag[cls] = subdict
444 444 flags[key] = (newflag, help)
445 445 return flags, aliases
446 446
447 447 @catch_config_error
448 448 def parse_command_line(self, argv=None):
449 449 """Parse the command line arguments."""
450 450 argv = sys.argv[1:] if argv is None else argv
451 451
452 452 if argv and argv[0] == 'help':
453 453 # turn `ipython help notebook` into `ipython notebook -h`
454 454 argv = argv[1:] + ['-h']
455 455
456 456 if self.subcommands and len(argv) > 0:
457 457 # we have subcommands, and one may have been specified
458 458 subc, subargv = argv[0], argv[1:]
459 459 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
460 460 # it's a subcommand, and *not* a flag or class parameter
461 461 return self.initialize_subcommand(subc, subargv)
462 462
463 463 # Arguments after a '--' argument are for the script IPython may be
464 464 # about to run, not IPython iteslf. For arguments parsed here (help and
465 465 # version), we want to only search the arguments up to the first
466 466 # occurrence of '--', which we're calling interpreted_argv.
467 467 try:
468 468 interpreted_argv = argv[:argv.index('--')]
469 469 except ValueError:
470 470 interpreted_argv = argv
471 471
472 472 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
473 473 self.print_description()
474 474 self.print_help('--help-all' in interpreted_argv)
475 475 self.print_examples()
476 476 self.exit(0)
477 477
478 478 if '--version' in interpreted_argv or '-V' in interpreted_argv:
479 479 self.print_version()
480 480 self.exit(0)
481 481
482 482 # flatten flags&aliases, so cl-args get appropriate priority:
483 483 flags,aliases = self.flatten_flags()
484 484
485 485 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
486 486 flags=flags)
487 487 config = loader.load_config()
488 488 self.update_config(config)
489 489 # store unparsed args in extra_args
490 490 self.extra_args = loader.extra_args
491 491
492 492 @catch_config_error
493 493 def load_config_file(self, filename, path=None):
494 494 """Load a .py based config file by filename and path."""
495 495 loader = PyFileConfigLoader(filename, path=path)
496 496 try:
497 497 config = loader.load_config()
498 498 except ConfigFileNotFound:
499 499 # problem finding the file, raise
500 500 raise
501 501 except Exception:
502 502 # try to get the full filename, but it will be empty in the
503 503 # unlikely event that the error raised before filefind finished
504 504 filename = loader.full_filename or filename
505 505 # problem while running the file
506 506 self.log.error("Exception while loading config file %s",
507 507 filename, exc_info=True)
508 508 else:
509 509 self.log.debug("Loaded config file: %s", loader.full_filename)
510 510 self.update_config(config)
511 511
512 512 def generate_config_file(self):
513 513 """generate default config file from Configurables"""
514 514 lines = ["# Configuration file for %s."%self.name]
515 515 lines.append('')
516 516 lines.append('c = get_config()')
517 517 lines.append('')
518 518 for cls in self.classes:
519 519 lines.append(cls.class_config_section())
520 520 return '\n'.join(lines)
521 521
522 522 def exit(self, exit_status=0):
523 523 self.log.debug("Exiting application: %s" % self.name)
524 524 sys.exit(exit_status)
525 525
526 526 #-----------------------------------------------------------------------------
527 527 # utility functions, for convenience
528 528 #-----------------------------------------------------------------------------
529 529
530 530 def boolean_flag(name, configurable, set_help='', unset_help=''):
531 531 """Helper for building basic --trait, --no-trait flags.
532 532
533 533 Parameters
534 534 ----------
535 535
536 536 name : str
537 537 The name of the flag.
538 538 configurable : str
539 539 The 'Class.trait' string of the trait to be set/unset with the flag
540 540 set_help : unicode
541 541 help string for --name flag
542 542 unset_help : unicode
543 543 help string for --no-name flag
544 544
545 545 Returns
546 546 -------
547 547
548 548 cfg : dict
549 549 A dict with two keys: 'name', and 'no-name', for setting and unsetting
550 550 the trait, respectively.
551 551 """
552 552 # default helpstrings
553 553 set_help = set_help or "set %s=True"%configurable
554 554 unset_help = unset_help or "set %s=False"%configurable
555 555
556 556 cls,trait = configurable.split('.')
557 557
558 558 setter = {cls : {trait : True}}
559 559 unsetter = {cls : {trait : False}}
560 560 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
561 561
@@ -1,356 +1,356 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for objects that are configurable.
4 4
5 5 Inheritance diagram:
6 6
7 7 .. inheritance-diagram:: IPython.config.configurable
8 8 :parts: 3
9 9
10 10 Authors:
11 11
12 12 * Brian Granger
13 13 * Fernando Perez
14 14 * Min RK
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2008-2011 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27
28 28 import datetime
29 29 from copy import deepcopy
30 30
31 31 from loader import Config
32 32 from IPython.utils.traitlets import HasTraits, Instance
33 33 from IPython.utils.text import indent, wrap_paragraphs
34 34
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Helper classes for Configurables
38 38 #-----------------------------------------------------------------------------
39 39
40 40
41 41 class ConfigurableError(Exception):
42 42 pass
43 43
44 44
45 45 class MultipleInstanceError(ConfigurableError):
46 46 pass
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Configurable implementation
50 50 #-----------------------------------------------------------------------------
51 51
52 52 class Configurable(HasTraits):
53 53
54 54 config = Instance(Config, (), {})
55 55 created = None
56 56
57 57 def __init__(self, **kwargs):
58 58 """Create a configurable given a config config.
59 59
60 60 Parameters
61 61 ----------
62 62 config : Config
63 63 If this is empty, default values are used. If config is a
64 64 :class:`Config` instance, it will be used to configure the
65 65 instance.
66 66
67 67 Notes
68 68 -----
69 69 Subclasses of Configurable must call the :meth:`__init__` method of
70 70 :class:`Configurable` *before* doing anything else and using
71 71 :func:`super`::
72 72
73 73 class MyConfigurable(Configurable):
74 74 def __init__(self, config=None):
75 75 super(MyConfigurable, self).__init__(config=config)
76 76 # Then any other code you need to finish initialization.
77 77
78 78 This ensures that instances will be configured properly.
79 79 """
80 80 config = kwargs.pop('config', None)
81 81 if config is not None:
82 82 # We used to deepcopy, but for now we are trying to just save
83 83 # by reference. This *could* have side effects as all components
84 84 # will share config. In fact, I did find such a side effect in
85 85 # _config_changed below. If a config attribute value was a mutable type
86 86 # all instances of a component were getting the same copy, effectively
87 87 # making that a class attribute.
88 88 # self.config = deepcopy(config)
89 89 self.config = config
90 90 # This should go second so individual keyword arguments override
91 91 # the values in config.
92 92 super(Configurable, self).__init__(**kwargs)
93 93 self.created = datetime.datetime.now()
94 94
95 95 #-------------------------------------------------------------------------
96 96 # Static trait notifiations
97 97 #-------------------------------------------------------------------------
98 98
99 99 def _config_changed(self, name, old, new):
100 100 """Update all the class traits having ``config=True`` as metadata.
101 101
102 102 For any class trait with a ``config`` metadata attribute that is
103 103 ``True``, we update the trait with the value of the corresponding
104 104 config entry.
105 105 """
106 106 # Get all traits with a config metadata entry that is True
107 107 traits = self.traits(config=True)
108 108
109 109 # We auto-load config section for this class as well as any parent
110 110 # classes that are Configurable subclasses. This starts with Configurable
111 111 # and works down the mro loading the config for each section.
112 112 section_names = [cls.__name__ for cls in \
113 113 reversed(self.__class__.__mro__) if
114 114 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
115 115
116 116 for sname in section_names:
117 117 # Don't do a blind getattr as that would cause the config to
118 118 # dynamically create the section with name self.__class__.__name__.
119 119 if new._has_section(sname):
120 120 my_config = new[sname]
121 121 for k, v in traits.iteritems():
122 122 # Don't allow traitlets with config=True to start with
123 123 # uppercase. Otherwise, they are confused with Config
124 124 # subsections. But, developers shouldn't have uppercase
125 125 # attributes anyways! (PEP 6)
126 126 if k[0].upper()==k[0] and not k.startswith('_'):
127 127 raise ConfigurableError('Configurable traitlets with '
128 128 'config=True must start with a lowercase so they are '
129 129 'not confused with Config subsections: %s.%s' % \
130 130 (self.__class__.__name__, k))
131 131 try:
132 132 # Here we grab the value from the config
133 133 # If k has the naming convention of a config
134 134 # section, it will be auto created.
135 135 config_value = my_config[k]
136 136 except KeyError:
137 137 pass
138 138 else:
139 139 # print "Setting %s.%s from %s.%s=%r" % \
140 140 # (self.__class__.__name__,k,sname,k,config_value)
141 141 # We have to do a deepcopy here if we don't deepcopy the entire
142 142 # config object. If we don't, a mutable config_value will be
143 143 # shared by all instances, effectively making it a class attribute.
144 144 setattr(self, k, deepcopy(config_value))
145 145
146 146 def update_config(self, config):
147 147 """Fire the traits events when the config is updated."""
148 148 # Save a copy of the current config.
149 149 newconfig = deepcopy(self.config)
150 150 # Merge the new config into the current one.
151 newconfig._merge(config)
151 newconfig.merge(config)
152 152 # Save the combined config as self.config, which triggers the traits
153 153 # events.
154 154 self.config = newconfig
155 155
156 156 @classmethod
157 157 def class_get_help(cls, inst=None):
158 158 """Get the help string for this class in ReST format.
159 159
160 160 If `inst` is given, it's current trait values will be used in place of
161 161 class defaults.
162 162 """
163 163 assert inst is None or isinstance(inst, cls)
164 164 cls_traits = cls.class_traits(config=True)
165 165 final_help = []
166 166 final_help.append(u'%s options' % cls.__name__)
167 167 final_help.append(len(final_help[0])*u'-')
168 168 for k, v in sorted(cls.class_traits(config=True).iteritems()):
169 169 help = cls.class_get_trait_help(v, inst)
170 170 final_help.append(help)
171 171 return '\n'.join(final_help)
172 172
173 173 @classmethod
174 174 def class_get_trait_help(cls, trait, inst=None):
175 175 """Get the help string for a single trait.
176 176
177 177 If `inst` is given, it's current trait values will be used in place of
178 178 the class default.
179 179 """
180 180 assert inst is None or isinstance(inst, cls)
181 181 lines = []
182 182 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
183 183 lines.append(header)
184 184 if inst is not None:
185 185 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
186 186 else:
187 187 try:
188 188 dvr = repr(trait.get_default_value())
189 189 except Exception:
190 190 dvr = None # ignore defaults we can't construct
191 191 if dvr is not None:
192 192 if len(dvr) > 64:
193 193 dvr = dvr[:61]+'...'
194 194 lines.append(indent('Default: %s' % dvr, 4))
195 195 if 'Enum' in trait.__class__.__name__:
196 196 # include Enum choices
197 197 lines.append(indent('Choices: %r' % (trait.values,)))
198 198
199 199 help = trait.get_metadata('help')
200 200 if help is not None:
201 201 help = '\n'.join(wrap_paragraphs(help, 76))
202 202 lines.append(indent(help, 4))
203 203 return '\n'.join(lines)
204 204
205 205 @classmethod
206 206 def class_print_help(cls, inst=None):
207 207 """Get the help string for a single trait and print it."""
208 208 print cls.class_get_help(inst)
209 209
210 210 @classmethod
211 211 def class_config_section(cls):
212 212 """Get the config class config section"""
213 213 def c(s):
214 214 """return a commented, wrapped block."""
215 215 s = '\n\n'.join(wrap_paragraphs(s, 78))
216 216
217 217 return '# ' + s.replace('\n', '\n# ')
218 218
219 219 # section header
220 220 breaker = '#' + '-'*78
221 221 s = "# %s configuration" % cls.__name__
222 222 lines = [breaker, s, breaker, '']
223 223 # get the description trait
224 224 desc = cls.class_traits().get('description')
225 225 if desc:
226 226 desc = desc.default_value
227 227 else:
228 228 # no description trait, use __doc__
229 229 desc = getattr(cls, '__doc__', '')
230 230 if desc:
231 231 lines.append(c(desc))
232 232 lines.append('')
233 233
234 234 parents = []
235 235 for parent in cls.mro():
236 236 # only include parents that are not base classes
237 237 # and are not the class itself
238 238 # and have some configurable traits to inherit
239 239 if parent is not cls and issubclass(parent, Configurable) and \
240 240 parent.class_traits(config=True):
241 241 parents.append(parent)
242 242
243 243 if parents:
244 244 pstr = ', '.join([ p.__name__ for p in parents ])
245 245 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
246 246 lines.append('')
247 247
248 248 for name, trait in cls.class_traits(config=True).iteritems():
249 249 help = trait.get_metadata('help') or ''
250 250 lines.append(c(help))
251 251 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
252 252 lines.append('')
253 253 return '\n'.join(lines)
254 254
255 255
256 256
257 257 class SingletonConfigurable(Configurable):
258 258 """A configurable that only allows one instance.
259 259
260 260 This class is for classes that should only have one instance of itself
261 261 or *any* subclass. To create and retrieve such a class use the
262 262 :meth:`SingletonConfigurable.instance` method.
263 263 """
264 264
265 265 _instance = None
266 266
267 267 @classmethod
268 268 def _walk_mro(cls):
269 269 """Walk the cls.mro() for parent classes that are also singletons
270 270
271 271 For use in instance()
272 272 """
273 273
274 274 for subclass in cls.mro():
275 275 if issubclass(cls, subclass) and \
276 276 issubclass(subclass, SingletonConfigurable) and \
277 277 subclass != SingletonConfigurable:
278 278 yield subclass
279 279
280 280 @classmethod
281 281 def clear_instance(cls):
282 282 """unset _instance for this class and singleton parents.
283 283 """
284 284 if not cls.initialized():
285 285 return
286 286 for subclass in cls._walk_mro():
287 287 if isinstance(subclass._instance, cls):
288 288 # only clear instances that are instances
289 289 # of the calling class
290 290 subclass._instance = None
291 291
292 292 @classmethod
293 293 def instance(cls, *args, **kwargs):
294 294 """Returns a global instance of this class.
295 295
296 296 This method create a new instance if none have previously been created
297 297 and returns a previously created instance is one already exists.
298 298
299 299 The arguments and keyword arguments passed to this method are passed
300 300 on to the :meth:`__init__` method of the class upon instantiation.
301 301
302 302 Examples
303 303 --------
304 304
305 305 Create a singleton class using instance, and retrieve it::
306 306
307 307 >>> from IPython.config.configurable import SingletonConfigurable
308 308 >>> class Foo(SingletonConfigurable): pass
309 309 >>> foo = Foo.instance()
310 310 >>> foo == Foo.instance()
311 311 True
312 312
313 313 Create a subclass that is retrived using the base class instance::
314 314
315 315 >>> class Bar(SingletonConfigurable): pass
316 316 >>> class Bam(Bar): pass
317 317 >>> bam = Bam.instance()
318 318 >>> bam == Bar.instance()
319 319 True
320 320 """
321 321 # Create and save the instance
322 322 if cls._instance is None:
323 323 inst = cls(*args, **kwargs)
324 324 # Now make sure that the instance will also be returned by
325 325 # parent classes' _instance attribute.
326 326 for subclass in cls._walk_mro():
327 327 subclass._instance = inst
328 328
329 329 if isinstance(cls._instance, cls):
330 330 return cls._instance
331 331 else:
332 332 raise MultipleInstanceError(
333 333 'Multiple incompatible subclass instances of '
334 334 '%s are being created.' % cls.__name__
335 335 )
336 336
337 337 @classmethod
338 338 def initialized(cls):
339 339 """Has an instance been created?"""
340 340 return hasattr(cls, "_instance") and cls._instance is not None
341 341
342 342
343 343 class LoggingConfigurable(Configurable):
344 344 """A parent class for Configurables that log.
345 345
346 346 Subclasses have a log trait, and the default behavior
347 347 is to get the logger from the currently running Application
348 348 via Application.instance().log.
349 349 """
350 350
351 351 log = Instance('logging.Logger')
352 352 def _log_default(self):
353 353 from IPython.config.application import Application
354 354 return Application.instance().log
355 355
356 356
@@ -1,719 +1,724 b''
1 1 """A simple configuration system.
2 2
3 3 Inheritance diagram:
4 4
5 5 .. inheritance-diagram:: IPython.config.loader
6 6 :parts: 3
7 7
8 8 Authors
9 9 -------
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min RK
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 import __builtin__ as builtin_mod
27 27 import os
28 28 import re
29 29 import sys
30 30
31 31 from IPython.external import argparse
32 32 from IPython.utils.path import filefind, get_ipython_dir
33 33 from IPython.utils import py3compat, text, warn
34 34 from IPython.utils.encoding import DEFAULT_ENCODING
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Exceptions
38 38 #-----------------------------------------------------------------------------
39 39
40 40
41 41 class ConfigError(Exception):
42 42 pass
43 43
44 44 class ConfigLoaderError(ConfigError):
45 45 pass
46 46
47 47 class ConfigFileNotFound(ConfigError):
48 48 pass
49 49
50 50 class ArgumentError(ConfigLoaderError):
51 51 pass
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Argparse fix
55 55 #-----------------------------------------------------------------------------
56 56
57 57 # Unfortunately argparse by default prints help messages to stderr instead of
58 58 # stdout. This makes it annoying to capture long help screens at the command
59 59 # line, since one must know how to pipe stderr, which many users don't know how
60 60 # to do. So we override the print_help method with one that defaults to
61 61 # stdout and use our class instead.
62 62
63 63 class ArgumentParser(argparse.ArgumentParser):
64 64 """Simple argparse subclass that prints help to stdout by default."""
65 65
66 66 def print_help(self, file=None):
67 67 if file is None:
68 68 file = sys.stdout
69 69 return super(ArgumentParser, self).print_help(file)
70 70
71 71 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Config class for holding config information
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class Config(dict):
79 79 """An attribute based dict that can do smart merges."""
80 80
81 81 def __init__(self, *args, **kwds):
82 82 dict.__init__(self, *args, **kwds)
83 83 # This sets self.__dict__ = self, but it has to be done this way
84 84 # because we are also overriding __setattr__.
85 85 dict.__setattr__(self, '__dict__', self)
86 86 self._ensure_subconfig()
87 87
88 88 def _ensure_subconfig(self):
89 89 """ensure that sub-dicts that should be Config objects are
90 90
91 91 casts dicts that are under section keys to Config objects,
92 92 which is necessary for constructing Config objects from dict literals.
93 93 """
94 94 for key in self:
95 95 obj = self[key]
96 96 if self._is_section_key(key) \
97 97 and isinstance(obj, dict) \
98 98 and not isinstance(obj, Config):
99 99 dict.__setattr__(self, key, Config(obj))
100
100
101 101 def _merge(self, other):
102 """deprecated alias, use Config.merge()"""
103 self.merge(other)
104
105 def merge(self, other):
106 """merge another config object into this one"""
102 107 to_update = {}
103 108 for k, v in other.iteritems():
104 109 if k not in self:
105 110 to_update[k] = v
106 111 else: # I have this key
107 if isinstance(v, Config):
112 if isinstance(v, Config) and isinstance(self[k], Config):
108 113 # Recursively merge common sub Configs
109 self[k]._merge(v)
114 self[k].merge(v)
110 115 else:
111 116 # Plain updates for non-Configs
112 117 to_update[k] = v
113 118
114 119 self.update(to_update)
115 120
116 121 def _is_section_key(self, key):
117 122 if key[0].upper()==key[0] and not key.startswith('_'):
118 123 return True
119 124 else:
120 125 return False
121 126
122 127 def __contains__(self, key):
123 128 if self._is_section_key(key):
124 129 return True
125 130 else:
126 131 return super(Config, self).__contains__(key)
127 132 # .has_key is deprecated for dictionaries.
128 133 has_key = __contains__
129 134
130 135 def _has_section(self, key):
131 136 if self._is_section_key(key):
132 137 if super(Config, self).__contains__(key):
133 138 return True
134 139 return False
135 140
136 141 def copy(self):
137 142 return type(self)(dict.copy(self))
138 143
139 144 def __copy__(self):
140 145 return self.copy()
141 146
142 147 def __deepcopy__(self, memo):
143 148 import copy
144 149 return type(self)(copy.deepcopy(self.items()))
145 150
146 151 def __getitem__(self, key):
147 152 # We cannot use directly self._is_section_key, because it triggers
148 153 # infinite recursion on top of PyPy. Instead, we manually fish the
149 154 # bound method.
150 155 is_section_key = self.__class__._is_section_key.__get__(self)
151 156
152 157 # Because we use this for an exec namespace, we need to delegate
153 158 # the lookup of names in __builtin__ to itself. This means
154 159 # that you can't have section or attribute names that are
155 160 # builtins.
156 161 try:
157 162 return getattr(builtin_mod, key)
158 163 except AttributeError:
159 164 pass
160 165 if is_section_key(key):
161 166 try:
162 167 return dict.__getitem__(self, key)
163 168 except KeyError:
164 169 c = Config()
165 170 dict.__setitem__(self, key, c)
166 171 return c
167 172 else:
168 173 return dict.__getitem__(self, key)
169 174
170 175 def __setitem__(self, key, value):
171 176 # Don't allow names in __builtin__ to be modified.
172 177 if hasattr(builtin_mod, key):
173 178 raise ConfigError('Config variable names cannot have the same name '
174 179 'as a Python builtin: %s' % key)
175 180 if self._is_section_key(key):
176 181 if not isinstance(value, Config):
177 182 raise ValueError('values whose keys begin with an uppercase '
178 183 'char must be Config instances: %r, %r' % (key, value))
179 184 else:
180 185 dict.__setitem__(self, key, value)
181 186
182 187 def __getattr__(self, key):
183 188 try:
184 189 return self.__getitem__(key)
185 190 except KeyError as e:
186 191 raise AttributeError(e)
187 192
188 193 def __setattr__(self, key, value):
189 194 try:
190 195 self.__setitem__(key, value)
191 196 except KeyError as e:
192 197 raise AttributeError(e)
193 198
194 199 def __delattr__(self, key):
195 200 try:
196 201 dict.__delitem__(self, key)
197 202 except KeyError as e:
198 203 raise AttributeError(e)
199 204
200 205
201 206 #-----------------------------------------------------------------------------
202 207 # Config loading classes
203 208 #-----------------------------------------------------------------------------
204 209
205 210
206 211 class ConfigLoader(object):
207 212 """A object for loading configurations from just about anywhere.
208 213
209 214 The resulting configuration is packaged as a :class:`Struct`.
210 215
211 216 Notes
212 217 -----
213 218 A :class:`ConfigLoader` does one thing: load a config from a source
214 219 (file, command line arguments) and returns the data as a :class:`Struct`.
215 220 There are lots of things that :class:`ConfigLoader` does not do. It does
216 221 not implement complex logic for finding config files. It does not handle
217 222 default values or merge multiple configs. These things need to be
218 223 handled elsewhere.
219 224 """
220 225
221 226 def __init__(self):
222 227 """A base class for config loaders.
223 228
224 229 Examples
225 230 --------
226 231
227 232 >>> cl = ConfigLoader()
228 233 >>> config = cl.load_config()
229 234 >>> config
230 235 {}
231 236 """
232 237 self.clear()
233 238
234 239 def clear(self):
235 240 self.config = Config()
236 241
237 242 def load_config(self):
238 243 """Load a config from somewhere, return a :class:`Config` instance.
239 244
240 245 Usually, this will cause self.config to be set and then returned.
241 246 However, in most cases, :meth:`ConfigLoader.clear` should be called
242 247 to erase any previous state.
243 248 """
244 249 self.clear()
245 250 return self.config
246 251
247 252
248 253 class FileConfigLoader(ConfigLoader):
249 254 """A base class for file based configurations.
250 255
251 256 As we add more file based config loaders, the common logic should go
252 257 here.
253 258 """
254 259 pass
255 260
256 261
257 262 class PyFileConfigLoader(FileConfigLoader):
258 263 """A config loader for pure python files.
259 264
260 265 This calls execfile on a plain python file and looks for attributes
261 266 that are all caps. These attribute are added to the config Struct.
262 267 """
263 268
264 269 def __init__(self, filename, path=None):
265 270 """Build a config loader for a filename and path.
266 271
267 272 Parameters
268 273 ----------
269 274 filename : str
270 275 The file name of the config file.
271 276 path : str, list, tuple
272 277 The path to search for the config file on, or a sequence of
273 278 paths to try in order.
274 279 """
275 280 super(PyFileConfigLoader, self).__init__()
276 281 self.filename = filename
277 282 self.path = path
278 283 self.full_filename = ''
279 284 self.data = None
280 285
281 286 def load_config(self):
282 287 """Load the config from a file and return it as a Struct."""
283 288 self.clear()
284 289 try:
285 290 self._find_file()
286 291 except IOError as e:
287 292 raise ConfigFileNotFound(str(e))
288 293 self._read_file_as_dict()
289 294 self._convert_to_config()
290 295 return self.config
291 296
292 297 def _find_file(self):
293 298 """Try to find the file by searching the paths."""
294 299 self.full_filename = filefind(self.filename, self.path)
295 300
296 301 def _read_file_as_dict(self):
297 302 """Load the config file into self.config, with recursive loading."""
298 303 # This closure is made available in the namespace that is used
299 304 # to exec the config file. It allows users to call
300 305 # load_subconfig('myconfig.py') to load config files recursively.
301 306 # It needs to be a closure because it has references to self.path
302 307 # and self.config. The sub-config is loaded with the same path
303 308 # as the parent, but it uses an empty config which is then merged
304 309 # with the parents.
305 310
306 311 # If a profile is specified, the config file will be loaded
307 312 # from that profile
308 313
309 314 def load_subconfig(fname, profile=None):
310 315 # import here to prevent circular imports
311 316 from IPython.core.profiledir import ProfileDir, ProfileDirError
312 317 if profile is not None:
313 318 try:
314 319 profile_dir = ProfileDir.find_profile_dir_by_name(
315 320 get_ipython_dir(),
316 321 profile,
317 322 )
318 323 except ProfileDirError:
319 324 return
320 325 path = profile_dir.location
321 326 else:
322 327 path = self.path
323 328 loader = PyFileConfigLoader(fname, path)
324 329 try:
325 330 sub_config = loader.load_config()
326 331 except ConfigFileNotFound:
327 332 # Pass silently if the sub config is not there. This happens
328 333 # when a user s using a profile, but not the default config.
329 334 pass
330 335 else:
331 self.config._merge(sub_config)
336 self.config.merge(sub_config)
332 337
333 338 # Again, this needs to be a closure and should be used in config
334 339 # files to get the config being loaded.
335 340 def get_config():
336 341 return self.config
337 342
338 343 namespace = dict(
339 344 load_subconfig=load_subconfig,
340 345 get_config=get_config,
341 346 __file__=self.full_filename,
342 347 )
343 348 fs_encoding = sys.getfilesystemencoding() or 'ascii'
344 349 conf_filename = self.full_filename.encode(fs_encoding)
345 350 py3compat.execfile(conf_filename, namespace)
346 351
347 352 def _convert_to_config(self):
348 353 if self.data is None:
349 354 ConfigLoaderError('self.data does not exist')
350 355
351 356
352 357 class CommandLineConfigLoader(ConfigLoader):
353 358 """A config loader for command line arguments.
354 359
355 360 As we add more command line based loaders, the common logic should go
356 361 here.
357 362 """
358 363
359 364 def _exec_config_str(self, lhs, rhs):
360 365 """execute self.config.<lhs> = <rhs>
361 366
362 367 * expands ~ with expanduser
363 368 * tries to assign with raw eval, otherwise assigns with just the string,
364 369 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
365 370 equivalent are `--C.a=4` and `--C.a='4'`.
366 371 """
367 372 rhs = os.path.expanduser(rhs)
368 373 try:
369 374 # Try to see if regular Python syntax will work. This
370 375 # won't handle strings as the quote marks are removed
371 376 # by the system shell.
372 377 value = eval(rhs)
373 378 except (NameError, SyntaxError):
374 379 # This case happens if the rhs is a string.
375 380 value = rhs
376 381
377 382 exec u'self.config.%s = value' % lhs
378 383
379 384 def _load_flag(self, cfg):
380 385 """update self.config from a flag, which can be a dict or Config"""
381 386 if isinstance(cfg, (dict, Config)):
382 387 # don't clobber whole config sections, update
383 388 # each section from config:
384 389 for sec,c in cfg.iteritems():
385 390 self.config[sec].update(c)
386 391 else:
387 392 raise TypeError("Invalid flag: %r" % cfg)
388 393
389 394 # raw --identifier=value pattern
390 395 # but *also* accept '-' as wordsep, for aliases
391 396 # accepts: --foo=a
392 397 # --Class.trait=value
393 398 # --alias-name=value
394 399 # rejects: -foo=value
395 400 # --foo
396 401 # --Class.trait
397 402 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
398 403
399 404 # just flags, no assignments, with two *or one* leading '-'
400 405 # accepts: --foo
401 406 # -foo-bar-again
402 407 # rejects: --anything=anything
403 408 # --two.word
404 409
405 410 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
406 411
407 412 class KeyValueConfigLoader(CommandLineConfigLoader):
408 413 """A config loader that loads key value pairs from the command line.
409 414
410 415 This allows command line options to be gives in the following form::
411 416
412 417 ipython --profile="foo" --InteractiveShell.autocall=False
413 418 """
414 419
415 420 def __init__(self, argv=None, aliases=None, flags=None):
416 421 """Create a key value pair config loader.
417 422
418 423 Parameters
419 424 ----------
420 425 argv : list
421 426 A list that has the form of sys.argv[1:] which has unicode
422 427 elements of the form u"key=value". If this is None (default),
423 428 then sys.argv[1:] will be used.
424 429 aliases : dict
425 430 A dict of aliases for configurable traits.
426 431 Keys are the short aliases, Values are the resolved trait.
427 432 Of the form: `{'alias' : 'Configurable.trait'}`
428 433 flags : dict
429 434 A dict of flags, keyed by str name. Vaues can be Config objects,
430 435 dicts, or "key=value" strings. If Config or dict, when the flag
431 436 is triggered, The flag is loaded as `self.config.update(m)`.
432 437
433 438 Returns
434 439 -------
435 440 config : Config
436 441 The resulting Config object.
437 442
438 443 Examples
439 444 --------
440 445
441 446 >>> from IPython.config.loader import KeyValueConfigLoader
442 447 >>> cl = KeyValueConfigLoader()
443 448 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
444 449 >>> sorted(d.items())
445 450 [('A', {'name': 'brian'}), ('B', {'number': 0})]
446 451 """
447 452 self.clear()
448 453 if argv is None:
449 454 argv = sys.argv[1:]
450 455 self.argv = argv
451 456 self.aliases = aliases or {}
452 457 self.flags = flags or {}
453 458
454 459
455 460 def clear(self):
456 461 super(KeyValueConfigLoader, self).clear()
457 462 self.extra_args = []
458 463
459 464
460 465 def _decode_argv(self, argv, enc=None):
461 466 """decode argv if bytes, using stin.encoding, falling back on default enc"""
462 467 uargv = []
463 468 if enc is None:
464 469 enc = DEFAULT_ENCODING
465 470 for arg in argv:
466 471 if not isinstance(arg, unicode):
467 472 # only decode if not already decoded
468 473 arg = arg.decode(enc)
469 474 uargv.append(arg)
470 475 return uargv
471 476
472 477
473 478 def load_config(self, argv=None, aliases=None, flags=None):
474 479 """Parse the configuration and generate the Config object.
475 480
476 481 After loading, any arguments that are not key-value or
477 482 flags will be stored in self.extra_args - a list of
478 483 unparsed command-line arguments. This is used for
479 484 arguments such as input files or subcommands.
480 485
481 486 Parameters
482 487 ----------
483 488 argv : list, optional
484 489 A list that has the form of sys.argv[1:] which has unicode
485 490 elements of the form u"key=value". If this is None (default),
486 491 then self.argv will be used.
487 492 aliases : dict
488 493 A dict of aliases for configurable traits.
489 494 Keys are the short aliases, Values are the resolved trait.
490 495 Of the form: `{'alias' : 'Configurable.trait'}`
491 496 flags : dict
492 497 A dict of flags, keyed by str name. Values can be Config objects
493 498 or dicts. When the flag is triggered, The config is loaded as
494 499 `self.config.update(cfg)`.
495 500 """
496 501 from IPython.config.configurable import Configurable
497 502
498 503 self.clear()
499 504 if argv is None:
500 505 argv = self.argv
501 506 if aliases is None:
502 507 aliases = self.aliases
503 508 if flags is None:
504 509 flags = self.flags
505 510
506 511 # ensure argv is a list of unicode strings:
507 512 uargv = self._decode_argv(argv)
508 513 for idx,raw in enumerate(uargv):
509 514 # strip leading '-'
510 515 item = raw.lstrip('-')
511 516
512 517 if raw == '--':
513 518 # don't parse arguments after '--'
514 519 # this is useful for relaying arguments to scripts, e.g.
515 520 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
516 521 self.extra_args.extend(uargv[idx+1:])
517 522 break
518 523
519 524 if kv_pattern.match(raw):
520 525 lhs,rhs = item.split('=',1)
521 526 # Substitute longnames for aliases.
522 527 if lhs in aliases:
523 528 lhs = aliases[lhs]
524 529 if '.' not in lhs:
525 530 # probably a mistyped alias, but not technically illegal
526 531 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
527 532 try:
528 533 self._exec_config_str(lhs, rhs)
529 534 except Exception:
530 535 raise ArgumentError("Invalid argument: '%s'" % raw)
531 536
532 537 elif flag_pattern.match(raw):
533 538 if item in flags:
534 539 cfg,help = flags[item]
535 540 self._load_flag(cfg)
536 541 else:
537 542 raise ArgumentError("Unrecognized flag: '%s'"%raw)
538 543 elif raw.startswith('-'):
539 544 kv = '--'+item
540 545 if kv_pattern.match(kv):
541 546 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
542 547 else:
543 548 raise ArgumentError("Invalid argument: '%s'"%raw)
544 549 else:
545 550 # keep all args that aren't valid in a list,
546 551 # in case our parent knows what to do with them.
547 552 self.extra_args.append(item)
548 553 return self.config
549 554
550 555 class ArgParseConfigLoader(CommandLineConfigLoader):
551 556 """A loader that uses the argparse module to load from the command line."""
552 557
553 558 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
554 559 """Create a config loader for use with argparse.
555 560
556 561 Parameters
557 562 ----------
558 563
559 564 argv : optional, list
560 565 If given, used to read command-line arguments from, otherwise
561 566 sys.argv[1:] is used.
562 567
563 568 parser_args : tuple
564 569 A tuple of positional arguments that will be passed to the
565 570 constructor of :class:`argparse.ArgumentParser`.
566 571
567 572 parser_kw : dict
568 573 A tuple of keyword arguments that will be passed to the
569 574 constructor of :class:`argparse.ArgumentParser`.
570 575
571 576 Returns
572 577 -------
573 578 config : Config
574 579 The resulting Config object.
575 580 """
576 581 super(CommandLineConfigLoader, self).__init__()
577 582 self.clear()
578 583 if argv is None:
579 584 argv = sys.argv[1:]
580 585 self.argv = argv
581 586 self.aliases = aliases or {}
582 587 self.flags = flags or {}
583 588
584 589 self.parser_args = parser_args
585 590 self.version = parser_kw.pop("version", None)
586 591 kwargs = dict(argument_default=argparse.SUPPRESS)
587 592 kwargs.update(parser_kw)
588 593 self.parser_kw = kwargs
589 594
590 595 def load_config(self, argv=None, aliases=None, flags=None):
591 596 """Parse command line arguments and return as a Config object.
592 597
593 598 Parameters
594 599 ----------
595 600
596 601 args : optional, list
597 602 If given, a list with the structure of sys.argv[1:] to parse
598 603 arguments from. If not given, the instance's self.argv attribute
599 604 (given at construction time) is used."""
600 605 self.clear()
601 606 if argv is None:
602 607 argv = self.argv
603 608 if aliases is None:
604 609 aliases = self.aliases
605 610 if flags is None:
606 611 flags = self.flags
607 612 self._create_parser(aliases, flags)
608 613 self._parse_args(argv)
609 614 self._convert_to_config()
610 615 return self.config
611 616
612 617 def get_extra_args(self):
613 618 if hasattr(self, 'extra_args'):
614 619 return self.extra_args
615 620 else:
616 621 return []
617 622
618 623 def _create_parser(self, aliases=None, flags=None):
619 624 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
620 625 self._add_arguments(aliases, flags)
621 626
622 627 def _add_arguments(self, aliases=None, flags=None):
623 628 raise NotImplementedError("subclasses must implement _add_arguments")
624 629
625 630 def _parse_args(self, args):
626 631 """self.parser->self.parsed_data"""
627 632 # decode sys.argv to support unicode command-line options
628 633 enc = DEFAULT_ENCODING
629 634 uargs = [py3compat.cast_unicode(a, enc) for a in args]
630 635 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
631 636
632 637 def _convert_to_config(self):
633 638 """self.parsed_data->self.config"""
634 639 for k, v in vars(self.parsed_data).iteritems():
635 640 exec "self.config.%s = v"%k in locals(), globals()
636 641
637 642 class KVArgParseConfigLoader(ArgParseConfigLoader):
638 643 """A config loader that loads aliases and flags with argparse,
639 644 but will use KVLoader for the rest. This allows better parsing
640 645 of common args, such as `ipython -c 'print 5'`, but still gets
641 646 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
642 647
643 648 def _add_arguments(self, aliases=None, flags=None):
644 649 self.alias_flags = {}
645 650 # print aliases, flags
646 651 if aliases is None:
647 652 aliases = self.aliases
648 653 if flags is None:
649 654 flags = self.flags
650 655 paa = self.parser.add_argument
651 656 for key,value in aliases.iteritems():
652 657 if key in flags:
653 658 # flags
654 659 nargs = '?'
655 660 else:
656 661 nargs = None
657 662 if len(key) is 1:
658 663 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
659 664 else:
660 665 paa('--'+key, type=unicode, dest=value, nargs=nargs)
661 666 for key, (value, help) in flags.iteritems():
662 667 if key in self.aliases:
663 668 #
664 669 self.alias_flags[self.aliases[key]] = value
665 670 continue
666 671 if len(key) is 1:
667 672 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
668 673 else:
669 674 paa('--'+key, action='append_const', dest='_flags', const=value)
670 675
671 676 def _convert_to_config(self):
672 677 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
673 678 # remove subconfigs list from namespace before transforming the Namespace
674 679 if '_flags' in self.parsed_data:
675 680 subcs = self.parsed_data._flags
676 681 del self.parsed_data._flags
677 682 else:
678 683 subcs = []
679 684
680 685 for k, v in vars(self.parsed_data).iteritems():
681 686 if v is None:
682 687 # it was a flag that shares the name of an alias
683 688 subcs.append(self.alias_flags[k])
684 689 else:
685 690 # eval the KV assignment
686 691 self._exec_config_str(k, v)
687 692
688 693 for subc in subcs:
689 694 self._load_flag(subc)
690 695
691 696 if self.extra_args:
692 697 sub_parser = KeyValueConfigLoader()
693 698 sub_parser.load_config(self.extra_args)
694 self.config._merge(sub_parser.config)
699 self.config.merge(sub_parser.config)
695 700 self.extra_args = sub_parser.extra_args
696 701
697 702
698 703 def load_pyconfig_files(config_files, path):
699 704 """Load multiple Python config files, merging each of them in turn.
700 705
701 706 Parameters
702 707 ==========
703 708 config_files : list of str
704 709 List of config files names to load and merge into the config.
705 710 path : unicode
706 711 The full path to the location of the config files.
707 712 """
708 713 config = Config()
709 714 for cf in config_files:
710 715 loader = PyFileConfigLoader(cf, path=path)
711 716 try:
712 717 next_config = loader.load_config()
713 718 except ConfigFileNotFound:
714 719 pass
715 720 except:
716 721 raise
717 722 else:
718 config._merge(next_config)
723 config.merge(next_config)
719 724 return config
@@ -1,284 +1,284 b''
1 1 # encoding: utf-8
2 2 """
3 3 Tests for IPython.config.loader
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez (design help)
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import os
23 23 import sys
24 24 from tempfile import mkstemp
25 25 from unittest import TestCase
26 26
27 27 from nose import SkipTest
28 28
29 29 from IPython.testing.tools import mute_warn
30 30
31 31 from IPython.utils.traitlets import Unicode
32 32 from IPython.config.configurable import Configurable
33 33 from IPython.config.loader import (
34 34 Config,
35 35 PyFileConfigLoader,
36 36 KeyValueConfigLoader,
37 37 ArgParseConfigLoader,
38 38 KVArgParseConfigLoader,
39 39 ConfigError
40 40 )
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Actual tests
44 44 #-----------------------------------------------------------------------------
45 45
46 46
47 47 pyfile = """
48 48 c = get_config()
49 49 c.a=10
50 50 c.b=20
51 51 c.Foo.Bar.value=10
52 52 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3
53 53 c.D.C.value='hi there'
54 54 """
55 55
56 56 class TestPyFileCL(TestCase):
57 57
58 58 def test_basic(self):
59 59 fd, fname = mkstemp('.py')
60 60 f = os.fdopen(fd, 'w')
61 61 f.write(pyfile)
62 62 f.close()
63 63 # Unlink the file
64 64 cl = PyFileConfigLoader(fname)
65 65 config = cl.load_config()
66 66 self.assertEqual(config.a, 10)
67 67 self.assertEqual(config.b, 20)
68 68 self.assertEqual(config.Foo.Bar.value, 10)
69 69 self.assertEqual(config.Foo.Bam.value, range(10))
70 70 self.assertEqual(config.D.C.value, 'hi there')
71 71
72 72 class MyLoader1(ArgParseConfigLoader):
73 73 def _add_arguments(self, aliases=None, flags=None):
74 74 p = self.parser
75 75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
76 76 p.add_argument('-b', dest='MyClass.bar', type=int)
77 77 p.add_argument('-n', dest='n', action='store_true')
78 78 p.add_argument('Global.bam', type=str)
79 79
80 80 class MyLoader2(ArgParseConfigLoader):
81 81 def _add_arguments(self, aliases=None, flags=None):
82 82 subparsers = self.parser.add_subparsers(dest='subparser_name')
83 83 subparser1 = subparsers.add_parser('1')
84 84 subparser1.add_argument('-x',dest='Global.x')
85 85 subparser2 = subparsers.add_parser('2')
86 86 subparser2.add_argument('y')
87 87
88 88 class TestArgParseCL(TestCase):
89 89
90 90 def test_basic(self):
91 91 cl = MyLoader1()
92 92 config = cl.load_config('-f hi -b 10 -n wow'.split())
93 93 self.assertEqual(config.Global.foo, 'hi')
94 94 self.assertEqual(config.MyClass.bar, 10)
95 95 self.assertEqual(config.n, True)
96 96 self.assertEqual(config.Global.bam, 'wow')
97 97 config = cl.load_config(['wow'])
98 98 self.assertEqual(config.keys(), ['Global'])
99 99 self.assertEqual(config.Global.keys(), ['bam'])
100 100 self.assertEqual(config.Global.bam, 'wow')
101 101
102 102 def test_add_arguments(self):
103 103 cl = MyLoader2()
104 104 config = cl.load_config('2 frobble'.split())
105 105 self.assertEqual(config.subparser_name, '2')
106 106 self.assertEqual(config.y, 'frobble')
107 107 config = cl.load_config('1 -x frobble'.split())
108 108 self.assertEqual(config.subparser_name, '1')
109 109 self.assertEqual(config.Global.x, 'frobble')
110 110
111 111 def test_argv(self):
112 112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
113 113 config = cl.load_config()
114 114 self.assertEqual(config.Global.foo, 'hi')
115 115 self.assertEqual(config.MyClass.bar, 10)
116 116 self.assertEqual(config.n, True)
117 117 self.assertEqual(config.Global.bam, 'wow')
118 118
119 119
120 120 class TestKeyValueCL(TestCase):
121 121 klass = KeyValueConfigLoader
122 122
123 123 def test_basic(self):
124 124 cl = self.klass()
125 125 argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]]
126 126 with mute_warn():
127 127 config = cl.load_config(argv)
128 128 self.assertEqual(config.a, 10)
129 129 self.assertEqual(config.b, 20)
130 130 self.assertEqual(config.Foo.Bar.value, 10)
131 131 self.assertEqual(config.Foo.Bam.value, range(10))
132 132 self.assertEqual(config.D.C.value, 'hi there')
133 133
134 134 def test_expanduser(self):
135 135 cl = self.klass()
136 136 argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
137 137 with mute_warn():
138 138 config = cl.load_config(argv)
139 139 self.assertEqual(config.a, os.path.expanduser('~/1/2/3'))
140 140 self.assertEqual(config.b, os.path.expanduser('~'))
141 141 self.assertEqual(config.c, os.path.expanduser('~/'))
142 142 self.assertEqual(config.d, '~/')
143 143
144 144 def test_extra_args(self):
145 145 cl = self.klass()
146 146 with mute_warn():
147 147 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
148 148 self.assertEqual(cl.extra_args, ['b', 'd'])
149 149 self.assertEqual(config.a, 5)
150 150 self.assertEqual(config.c, 10)
151 151 with mute_warn():
152 152 config = cl.load_config(['--', '--a=5', '--c=10'])
153 153 self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
154 154
155 155 def test_unicode_args(self):
156 156 cl = self.klass()
157 157 argv = [u'--a=épsîlön']
158 158 with mute_warn():
159 159 config = cl.load_config(argv)
160 160 self.assertEqual(config.a, u'épsîlön')
161 161
162 162 def test_unicode_bytes_args(self):
163 163 uarg = u'--a=é'
164 164 try:
165 165 barg = uarg.encode(sys.stdin.encoding)
166 166 except (TypeError, UnicodeEncodeError):
167 167 raise SkipTest("sys.stdin.encoding can't handle 'é'")
168 168
169 169 cl = self.klass()
170 170 with mute_warn():
171 171 config = cl.load_config([barg])
172 172 self.assertEqual(config.a, u'é')
173 173
174 174 def test_unicode_alias(self):
175 175 cl = self.klass()
176 176 argv = [u'--a=épsîlön']
177 177 with mute_warn():
178 178 config = cl.load_config(argv, aliases=dict(a='A.a'))
179 179 self.assertEqual(config.A.a, u'épsîlön')
180 180
181 181
182 182 class TestArgParseKVCL(TestKeyValueCL):
183 183 klass = KVArgParseConfigLoader
184 184
185 185 def test_expanduser2(self):
186 186 cl = self.klass()
187 187 argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"]
188 188 with mute_warn():
189 189 config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
190 190 self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3'))
191 191 self.assertEqual(config.A.b, '~/1/2/3')
192 192
193 193 def test_eval(self):
194 194 cl = self.klass()
195 195 argv = ['-c', 'a=5']
196 196 with mute_warn():
197 197 config = cl.load_config(argv, aliases=dict(c='A.c'))
198 198 self.assertEqual(config.A.c, u"a=5")
199 199
200 200
201 201 class TestConfig(TestCase):
202 202
203 203 def test_setget(self):
204 204 c = Config()
205 205 c.a = 10
206 206 self.assertEqual(c.a, 10)
207 207 self.assertEqual('b' in c, False)
208 208
209 209 def test_auto_section(self):
210 210 c = Config()
211 211 self.assertEqual('A' in c, True)
212 212 self.assertEqual(c._has_section('A'), False)
213 213 A = c.A
214 214 A.foo = 'hi there'
215 215 self.assertEqual(c._has_section('A'), True)
216 216 self.assertEqual(c.A.foo, 'hi there')
217 217 del c.A
218 218 self.assertEqual(len(c.A.keys()),0)
219 219
220 220 def test_merge_doesnt_exist(self):
221 221 c1 = Config()
222 222 c2 = Config()
223 223 c2.bar = 10
224 224 c2.Foo.bar = 10
225 c1._merge(c2)
225 c1.merge(c2)
226 226 self.assertEqual(c1.Foo.bar, 10)
227 227 self.assertEqual(c1.bar, 10)
228 228 c2.Bar.bar = 10
229 c1._merge(c2)
229 c1.merge(c2)
230 230 self.assertEqual(c1.Bar.bar, 10)
231 231
232 232 def test_merge_exists(self):
233 233 c1 = Config()
234 234 c2 = Config()
235 235 c1.Foo.bar = 10
236 236 c1.Foo.bam = 30
237 237 c2.Foo.bar = 20
238 238 c2.Foo.wow = 40
239 c1._merge(c2)
239 c1.merge(c2)
240 240 self.assertEqual(c1.Foo.bam, 30)
241 241 self.assertEqual(c1.Foo.bar, 20)
242 242 self.assertEqual(c1.Foo.wow, 40)
243 243 c2.Foo.Bam.bam = 10
244 c1._merge(c2)
244 c1.merge(c2)
245 245 self.assertEqual(c1.Foo.Bam.bam, 10)
246 246
247 247 def test_deepcopy(self):
248 248 c1 = Config()
249 249 c1.Foo.bar = 10
250 250 c1.Foo.bam = 30
251 251 c1.a = 'asdf'
252 252 c1.b = range(10)
253 253 import copy
254 254 c2 = copy.deepcopy(c1)
255 255 self.assertEqual(c1, c2)
256 256 self.assertTrue(c1 is not c2)
257 257 self.assertTrue(c1.Foo is not c2.Foo)
258 258
259 259 def test_builtin(self):
260 260 c1 = Config()
261 261 exec 'foo = True' in c1
262 262 self.assertEqual(c1.foo, True)
263 263 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
264 264
265 265 def test_fromdict(self):
266 266 c1 = Config({'Foo' : {'bar' : 1}})
267 267 self.assertEqual(c1.Foo.__class__, Config)
268 268 self.assertEqual(c1.Foo.bar, 1)
269 269
270 def test_fromdict_merge(self):
270 def test_fromdictmerge(self):
271 271 c1 = Config()
272 272 c2 = Config({'Foo' : {'bar' : 1}})
273 c1._merge(c2)
273 c1.merge(c2)
274 274 self.assertEqual(c1.Foo.__class__, Config)
275 275 self.assertEqual(c1.Foo.bar, 1)
276 276
277 def test_fromdict_merge2(self):
277 def test_fromdictmerge2(self):
278 278 c1 = Config({'Foo' : {'baz' : 2}})
279 279 c2 = Config({'Foo' : {'bar' : 1}})
280 c1._merge(c2)
280 c1.merge(c2)
281 281 self.assertEqual(c1.Foo.__class__, Config)
282 282 self.assertEqual(c1.Foo.bar, 1)
283 283 self.assertEqual(c1.Foo.baz, 2)
284 284 self.assertRaises(AttributeError, getattr, c2.Foo, 'baz')
General Comments 0
You need to be logged in to leave comments. Login now