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