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