##// END OF EJS Templates
use App.instance() in Application.initialize_subapp...
MinRK -
Show More
@@ -1,359 +1,361 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import re
23 import re
24 import sys
24 import sys
25
25
26 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.configurable import SingletonConfigurable
27 from IPython.config.loader import (
27 from IPython.config.loader import (
28 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
28 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
29 )
29 )
30
30
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 Unicode, List, Int, Enum, Dict, Instance
32 Unicode, List, Int, Enum, Dict, Instance
33 )
33 )
34 from IPython.utils.importstring import import_item
34 from IPython.utils.importstring import import_item
35 from IPython.utils.text import indent
35 from IPython.utils.text import indent
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Descriptions for the various sections
38 # Descriptions for the various sections
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 flag_description = """
41 flag_description = """
42 Flags are command-line arguments passed as '--<flag>'.
42 Flags are command-line arguments passed as '--<flag>'.
43 These take no parameters, unlike regular key-value arguments.
43 These take no parameters, unlike regular key-value arguments.
44 They are typically used for setting boolean flags, or enabling
44 They are typically used for setting boolean flags, or enabling
45 modes that involve setting multiple options together.
45 modes that involve setting multiple options together.
46 """.strip() # trim newlines of front and back
46 """.strip() # trim newlines of front and back
47
47
48 alias_description = """
48 alias_description = """
49 These are commonly set parameters, given abbreviated aliases for convenience.
49 These are commonly set parameters, given abbreviated aliases for convenience.
50 They are set in the same `name=value` way as class parameters, where
50 They are set in the same `name=value` way as class parameters, where
51 <name> is replaced by the real parameter for which it is an alias.
51 <name> is replaced by the real parameter for which it is an alias.
52 """.strip() # trim newlines of front and back
52 """.strip() # trim newlines of front and back
53
53
54 keyvalue_description = """
54 keyvalue_description = """
55 Parameters are set from command-line arguments of the form:
55 Parameters are set from command-line arguments of the form:
56 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
56 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
57 This line is evaluated in Python, so simple expressions are allowed, e.g.
57 This line is evaluated in Python, so simple expressions are allowed, e.g.
58 `C.a='range(3)'` For setting C.a=[0,1,2]
58 `C.a='range(3)'` For setting C.a=[0,1,2]
59 """.strip() # trim newlines of front and back
59 """.strip() # trim newlines of front and back
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Application class
62 # Application class
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 class ApplicationError(Exception):
66 class ApplicationError(Exception):
67 pass
67 pass
68
68
69
69
70 class Application(SingletonConfigurable):
70 class Application(SingletonConfigurable):
71 """A singleton application with full configuration support."""
71 """A singleton application with full configuration support."""
72
72
73 # The name of the application, will usually match the name of the command
73 # The name of the application, will usually match the name of the command
74 # line application
74 # line application
75 name = Unicode(u'application')
75 name = Unicode(u'application')
76
76
77 # The description of the application that is printed at the beginning
77 # The description of the application that is printed at the beginning
78 # of the help.
78 # of the help.
79 description = Unicode(u'This is an application.')
79 description = Unicode(u'This is an application.')
80 # default section descriptions
80 # default section descriptions
81 flag_description = Unicode(flag_description)
81 flag_description = Unicode(flag_description)
82 alias_description = Unicode(alias_description)
82 alias_description = Unicode(alias_description)
83 keyvalue_description = Unicode(keyvalue_description)
83 keyvalue_description = Unicode(keyvalue_description)
84
84
85
85
86 # A sequence of Configurable subclasses whose config=True attributes will
86 # A sequence of Configurable subclasses whose config=True attributes will
87 # be exposed at the command line.
87 # be exposed at the command line.
88 classes = List([])
88 classes = List([])
89
89
90 # The version string of this application.
90 # The version string of this application.
91 version = Unicode(u'0.0')
91 version = Unicode(u'0.0')
92
92
93 # The log level for the application
93 # The log level for the application
94 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
94 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
95 config=True,
95 config=True,
96 help="Set the log level.")
96 help="Set the log level.")
97
97
98 # the alias map for configurables
98 # the alias map for configurables
99 aliases = Dict(dict(log_level='Application.log_level'))
99 aliases = Dict(dict(log_level='Application.log_level'))
100
100
101 # flags for loading Configurables or store_const style flags
101 # flags for loading Configurables or store_const style flags
102 # flags are loaded from this dict by '--key' flags
102 # flags are loaded from this dict by '--key' flags
103 # this must be a dict of two-tuples, the first element being the Config/dict
103 # this must be a dict of two-tuples, the first element being the Config/dict
104 # and the second being the help string for the flag
104 # and the second being the help string for the flag
105 flags = Dict()
105 flags = Dict()
106
106
107 # subcommands for launching other applications
107 # subcommands for launching other applications
108 # if this is not empty, this will be a parent Application
108 # if this is not empty, this will be a parent Application
109 # this must be a dict of two-tuples, the first element being the application class/import string
109 # this must be a dict of two-tuples, the first element being the application class/import string
110 # and the second being the help string for the subcommand
110 # and the second being the help string for the subcommand
111 subcommands = Dict()
111 subcommands = Dict()
112 # parse_command_line will initialize a subapp, if requested
112 # parse_command_line will initialize a subapp, if requested
113 subapp = Instance('IPython.config.application.Application', allow_none=True)
113 subapp = Instance('IPython.config.application.Application', allow_none=True)
114
114
115 # extra command-line arguments that don't set config values
115 # extra command-line arguments that don't set config values
116 extra_args = List(Unicode)
116 extra_args = List(Unicode)
117
117
118
118
119 def __init__(self, **kwargs):
119 def __init__(self, **kwargs):
120 SingletonConfigurable.__init__(self, **kwargs)
120 SingletonConfigurable.__init__(self, **kwargs)
121 # Add my class to self.classes so my attributes appear in command line
121 # Add my class to self.classes so my attributes appear in command line
122 # options.
122 # options.
123 self.classes.insert(0, self.__class__)
123 self.classes.insert(0, self.__class__)
124
124
125 # ensure self.flags dict is valid
125 # ensure self.flags dict is valid
126 for key,value in self.flags.iteritems():
126 for key,value in self.flags.iteritems():
127 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
127 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
128 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
128 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
129 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
129 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
130 self.init_logging()
130 self.init_logging()
131
131
132 def _config_changed(self, name, old, new):
132 def _config_changed(self, name, old, new):
133 SingletonConfigurable._config_changed(self, name, old, new)
133 SingletonConfigurable._config_changed(self, name, old, new)
134 self.log.debug('Config changed:')
134 self.log.debug('Config changed:')
135 self.log.debug(repr(new))
135 self.log.debug(repr(new))
136
136
137 def init_logging(self):
137 def init_logging(self):
138 """Start logging for this application.
138 """Start logging for this application.
139
139
140 The default is to log to stdout using a StreaHandler. The log level
140 The default is to log to stdout using a StreaHandler. The log level
141 starts at loggin.WARN, but this can be adjusted by setting the
141 starts at loggin.WARN, but this can be adjusted by setting the
142 ``log_level`` attribute.
142 ``log_level`` attribute.
143 """
143 """
144 self.log = logging.getLogger(self.__class__.__name__)
144 self.log = logging.getLogger(self.__class__.__name__)
145 self.log.setLevel(self.log_level)
145 self.log.setLevel(self.log_level)
146 self._log_handler = logging.StreamHandler()
146 self._log_handler = logging.StreamHandler()
147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
148 self._log_handler.setFormatter(self._log_formatter)
148 self._log_handler.setFormatter(self._log_formatter)
149 self.log.addHandler(self._log_handler)
149 self.log.addHandler(self._log_handler)
150
150
151 def initialize(self, argv=None):
151 def initialize(self, argv=None):
152 """Do the basic steps to configure me.
152 """Do the basic steps to configure me.
153
153
154 Override in subclasses.
154 Override in subclasses.
155 """
155 """
156 self.parse_command_line(argv)
156 self.parse_command_line(argv)
157
157
158
158
159 def start(self):
159 def start(self):
160 """Start the app mainloop.
160 """Start the app mainloop.
161
161
162 Override in subclasses.
162 Override in subclasses.
163 """
163 """
164 if self.subapp is not None:
164 if self.subapp is not None:
165 return self.subapp.start()
165 return self.subapp.start()
166
166
167 def _log_level_changed(self, name, old, new):
167 def _log_level_changed(self, name, old, new):
168 """Adjust the log level when log_level is set."""
168 """Adjust the log level when log_level is set."""
169 self.log.setLevel(new)
169 self.log.setLevel(new)
170
170
171 def print_alias_help(self):
171 def print_alias_help(self):
172 """print the alias part of the help"""
172 """print the alias part of the help"""
173 if not self.aliases:
173 if not self.aliases:
174 return
174 return
175
175
176 lines = ['Aliases']
176 lines = ['Aliases']
177 lines.append('-'*len(lines[0]))
177 lines.append('-'*len(lines[0]))
178 lines.append(self.alias_description)
178 lines.append(self.alias_description)
179 lines.append('')
179 lines.append('')
180
180
181 classdict = {}
181 classdict = {}
182 for cls in self.classes:
182 for cls in self.classes:
183 # include all parents (up to, but excluding Configurable) in available names
183 # include all parents (up to, but excluding Configurable) in available names
184 for c in cls.mro()[:-3]:
184 for c in cls.mro()[:-3]:
185 classdict[c.__name__] = c
185 classdict[c.__name__] = c
186
186
187 for alias, longname in self.aliases.iteritems():
187 for alias, longname in self.aliases.iteritems():
188 classname, traitname = longname.split('.',1)
188 classname, traitname = longname.split('.',1)
189 cls = classdict[classname]
189 cls = classdict[classname]
190
190
191 trait = cls.class_traits(config=True)[traitname]
191 trait = cls.class_traits(config=True)[traitname]
192 help = cls.class_get_trait_help(trait)
192 help = cls.class_get_trait_help(trait)
193 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
193 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
194 lines.append(help)
194 lines.append(help)
195 lines.append('')
195 lines.append('')
196 print '\n'.join(lines)
196 print '\n'.join(lines)
197
197
198 def print_flag_help(self):
198 def print_flag_help(self):
199 """print the flag part of the help"""
199 """print the flag part of the help"""
200 if not self.flags:
200 if not self.flags:
201 return
201 return
202
202
203 lines = ['Flags']
203 lines = ['Flags']
204 lines.append('-'*len(lines[0]))
204 lines.append('-'*len(lines[0]))
205 lines.append(self.flag_description)
205 lines.append(self.flag_description)
206 lines.append('')
206 lines.append('')
207
207
208 for m, (cfg,help) in self.flags.iteritems():
208 for m, (cfg,help) in self.flags.iteritems():
209 lines.append('--'+m)
209 lines.append('--'+m)
210 lines.append(indent(help.strip(), flatten=True))
210 lines.append(indent(help.strip(), flatten=True))
211 lines.append('')
211 lines.append('')
212 print '\n'.join(lines)
212 print '\n'.join(lines)
213
213
214 def print_subcommands(self):
214 def print_subcommands(self):
215 """print the subcommand part of the help"""
215 """print the subcommand part of the help"""
216 if not self.subcommands:
216 if not self.subcommands:
217 return
217 return
218
218
219 lines = ["Subcommands"]
219 lines = ["Subcommands"]
220 lines.append('-'*len(lines[0]))
220 lines.append('-'*len(lines[0]))
221 for subc, (cls,help) in self.subcommands.iteritems():
221 for subc, (cls,help) in self.subcommands.iteritems():
222 lines.append("%s : %s"%(subc, cls))
222 lines.append("%s : %s"%(subc, cls))
223 if help:
223 if help:
224 lines.append(indent(help.strip(), flatten=True))
224 lines.append(indent(help.strip(), flatten=True))
225 lines.append('')
225 lines.append('')
226 print '\n'.join(lines)
226 print '\n'.join(lines)
227
227
228 def print_help(self, classes=False):
228 def print_help(self, classes=False):
229 """Print the help for each Configurable class in self.classes.
229 """Print the help for each Configurable class in self.classes.
230
230
231 If classes=False (the default), only flags and aliases are printed
231 If classes=False (the default), only flags and aliases are printed
232 """
232 """
233 self.print_subcommands()
233 self.print_subcommands()
234 self.print_flag_help()
234 self.print_flag_help()
235 self.print_alias_help()
235 self.print_alias_help()
236
236
237 if classes:
237 if classes:
238 if self.classes:
238 if self.classes:
239 print "Class parameters"
239 print "Class parameters"
240 print "----------------"
240 print "----------------"
241 print self.keyvalue_description
241 print self.keyvalue_description
242 print
242 print
243
243
244 for cls in self.classes:
244 for cls in self.classes:
245 cls.class_print_help()
245 cls.class_print_help()
246 print
246 print
247 else:
247 else:
248 print "To see all available configurables, use `--help-all`"
248 print "To see all available configurables, use `--help-all`"
249 print
249 print
250
250
251 def print_description(self):
251 def print_description(self):
252 """Print the application description."""
252 """Print the application description."""
253 print self.description
253 print self.description
254 print
254 print
255
255
256 def print_version(self):
256 def print_version(self):
257 """Print the version string."""
257 """Print the version string."""
258 print self.version
258 print self.version
259
259
260 def update_config(self, config):
260 def update_config(self, config):
261 """Fire the traits events when the config is updated."""
261 """Fire the traits events when the config is updated."""
262 # Save a copy of the current config.
262 # Save a copy of the current config.
263 newconfig = deepcopy(self.config)
263 newconfig = deepcopy(self.config)
264 # Merge the new config into the current one.
264 # Merge the new config into the current one.
265 newconfig._merge(config)
265 newconfig._merge(config)
266 # Save the combined config as self.config, which triggers the traits
266 # Save the combined config as self.config, which triggers the traits
267 # events.
267 # events.
268 self.config = newconfig
268 self.config = newconfig
269
269
270 def initialize_subcommand(self, subc, argv=None):
270 def initialize_subcommand(self, subc, argv=None):
271 """Initialize a subcommand with argv"""
271 """Initialize a subcommand with argv"""
272 subapp,help = self.subcommands.get(subc)
272 subapp,help = self.subcommands.get(subc)
273
273
274 if isinstance(subapp, basestring):
274 if isinstance(subapp, basestring):
275 subapp = import_item(subapp)
275 subapp = import_item(subapp)
276
276
277 # clear existing instances
278 self.__class__.clear_instance()
277 # instantiate
279 # instantiate
278 self.subapp = subapp()
280 self.subapp = subapp.instance()
279 # and initialize subapp
281 # and initialize subapp
280 self.subapp.initialize(argv)
282 self.subapp.initialize(argv)
281
283
282 def parse_command_line(self, argv=None):
284 def parse_command_line(self, argv=None):
283 """Parse the command line arguments."""
285 """Parse the command line arguments."""
284 argv = sys.argv[1:] if argv is None else argv
286 argv = sys.argv[1:] if argv is None else argv
285
287
286 if self.subcommands and len(argv) > 0:
288 if self.subcommands and len(argv) > 0:
287 # we have subcommands, and one may have been specified
289 # we have subcommands, and one may have been specified
288 subc, subargv = argv[0], argv[1:]
290 subc, subargv = argv[0], argv[1:]
289 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
291 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
290 # it's a subcommand, and *not* a flag or class parameter
292 # it's a subcommand, and *not* a flag or class parameter
291 return self.initialize_subcommand(subc, subargv)
293 return self.initialize_subcommand(subc, subargv)
292
294
293 if '-h' in argv or '--help' in argv or '--help-all' in argv:
295 if '-h' in argv or '--help' in argv or '--help-all' in argv:
294 self.print_description()
296 self.print_description()
295 self.print_help('--help-all' in argv)
297 self.print_help('--help-all' in argv)
296 self.exit(0)
298 self.exit(0)
297
299
298 if '--version' in argv:
300 if '--version' in argv:
299 self.print_version()
301 self.print_version()
300 self.exit(0)
302 self.exit(0)
301
303
302 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
304 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
303 flags=self.flags)
305 flags=self.flags)
304 try:
306 try:
305 config = loader.load_config()
307 config = loader.load_config()
306 except ArgumentError as e:
308 except ArgumentError as e:
307 self.log.fatal(str(e))
309 self.log.fatal(str(e))
308 self.print_description()
310 self.print_description()
309 self.print_help()
311 self.print_help()
310 self.exit(1)
312 self.exit(1)
311 self.update_config(config)
313 self.update_config(config)
312 # store unparsed args in extra_args
314 # store unparsed args in extra_args
313 self.extra_args = loader.extra_args
315 self.extra_args = loader.extra_args
314
316
315 def load_config_file(self, filename, path=None):
317 def load_config_file(self, filename, path=None):
316 """Load a .py based config file by filename and path."""
318 """Load a .py based config file by filename and path."""
317 loader = PyFileConfigLoader(filename, path=path)
319 loader = PyFileConfigLoader(filename, path=path)
318 config = loader.load_config()
320 config = loader.load_config()
319 self.update_config(config)
321 self.update_config(config)
320
322
321 def exit(self, exit_status=0):
323 def exit(self, exit_status=0):
322 self.log.debug("Exiting application: %s" % self.name)
324 self.log.debug("Exiting application: %s" % self.name)
323 sys.exit(exit_status)
325 sys.exit(exit_status)
324
326
325 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
326 # utility functions, for convenience
328 # utility functions, for convenience
327 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
328
330
329 def boolean_flag(name, configurable, set_help='', unset_help=''):
331 def boolean_flag(name, configurable, set_help='', unset_help=''):
330 """helper for building basic --trait, --no-trait flags
332 """helper for building basic --trait, --no-trait flags
331
333
332 Parameters
334 Parameters
333 ----------
335 ----------
334
336
335 name : str
337 name : str
336 The name of the flag.
338 The name of the flag.
337 configurable : str
339 configurable : str
338 The 'Class.trait' string of the trait to be set/unset with the flag
340 The 'Class.trait' string of the trait to be set/unset with the flag
339 set_help : unicode
341 set_help : unicode
340 help string for --name flag
342 help string for --name flag
341 unset_help : unicode
343 unset_help : unicode
342 help string for --no-name flag
344 help string for --no-name flag
343
345
344 Returns
346 Returns
345 -------
347 -------
346
348
347 cfg : dict
349 cfg : dict
348 A dict with two keys: 'name', and 'no-name', for setting and unsetting
350 A dict with two keys: 'name', and 'no-name', for setting and unsetting
349 the trait, respectively.
351 the trait, respectively.
350 """
352 """
351 # default helpstrings
353 # default helpstrings
352 set_help = set_help or "set %s=True"%configurable
354 set_help = set_help or "set %s=True"%configurable
353 unset_help = unset_help or "set %s=False"%configurable
355 unset_help = unset_help or "set %s=False"%configurable
354
356
355 cls,trait = configurable.split('.')
357 cls,trait = configurable.split('.')
356
358
357 setter = {cls : {trait : True}}
359 setter = {cls : {trait : True}}
358 unsetter = {cls : {trait : False}}
360 unsetter = {cls : {trait : False}}
359 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
361 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
General Comments 0
You need to be logged in to leave comments. Login now