##// END OF EJS Templates
default config files are automatically generated...
MinRK -
Show More
@@ -1,379 +1,394 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 from copy import deepcopy
23 22 import logging
23 import os
24 24 import re
25 25 import sys
26 from copy import deepcopy
26 27
27 28 from IPython.config.configurable import SingletonConfigurable
28 29 from IPython.config.loader import (
29 30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 31 )
31 32
32 33 from IPython.utils.traitlets import (
33 34 Unicode, List, Int, Enum, Dict, Instance
34 35 )
35 36 from IPython.utils.importstring import import_item
36 37 from IPython.utils.text import indent, wrap_paragraphs, dedent
37 38
38 39 #-----------------------------------------------------------------------------
39 40 # function for re-wrapping a helpstring
40 41 #-----------------------------------------------------------------------------
41 42
42 43 #-----------------------------------------------------------------------------
43 44 # Descriptions for the various sections
44 45 #-----------------------------------------------------------------------------
45 46
46 47 flag_description = """
47 48 Flags are command-line arguments passed as '--<flag>'.
48 49 These take no parameters, unlike regular key-value arguments.
49 50 They are typically used for setting boolean flags, or enabling
50 51 modes that involve setting multiple options together.
51 52
52 53 Flags *always* begin with '--', never just one '-'.
53 54 """.strip() # trim newlines of front and back
54 55
55 56 alias_description = """
56 57 These are commonly set parameters, given abbreviated aliases for convenience.
57 58 They are set in the same `name=value` way as class parameters, where
58 59 <name> is replaced by the real parameter for which it is an alias.
59 60 """.strip() # trim newlines of front and back
60 61
61 62 keyvalue_description = """
62 63 Parameters are set from command-line arguments of the form:
63 64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
64 65 This line is evaluated in Python, so simple expressions are allowed, e.g.
65 66 `C.a='range(3)'` For setting C.a=[0,1,2]
66 67 """.strip() # trim newlines of front and back
67 68
68 69 #-----------------------------------------------------------------------------
69 70 # Application class
70 71 #-----------------------------------------------------------------------------
71 72
72 73
73 74 class ApplicationError(Exception):
74 75 pass
75 76
76 77
77 78 class Application(SingletonConfigurable):
78 79 """A singleton application with full configuration support."""
79 80
80 81 # The name of the application, will usually match the name of the command
81 82 # line application
82 83 name = Unicode(u'application')
83 84
84 85 # The description of the application that is printed at the beginning
85 86 # of the help.
86 87 description = Unicode(u'This is an application.')
87 88 # default section descriptions
88 89 flag_description = Unicode(flag_description)
89 90 alias_description = Unicode(alias_description)
90 91 keyvalue_description = Unicode(keyvalue_description)
91 92
92 93
93 94 # A sequence of Configurable subclasses whose config=True attributes will
94 95 # be exposed at the command line.
95 96 classes = List([])
96 97
97 98 # The version string of this application.
98 99 version = Unicode(u'0.0')
99 100
100 101 # The log level for the application
101 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
102 104 config=True,
103 help="Set the log level.")
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
107 if isinstance(new, basestring):
108 self.log_level = getattr(logging, new)
104 109
105 110 # the alias map for configurables
106 111 aliases = Dict(dict(log_level='Application.log_level'))
107 112
108 113 # flags for loading Configurables or store_const style flags
109 114 # flags are loaded from this dict by '--key' flags
110 115 # this must be a dict of two-tuples, the first element being the Config/dict
111 116 # and the second being the help string for the flag
112 117 flags = Dict()
113 118 def _flags_changed(self, name, old, new):
114 119 """ensure flags dict is valid"""
115 120 for key,value in new.iteritems():
116 121 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
117 122 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
118 123 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
119 124
120 125
121 126 # subcommands for launching other applications
122 127 # if this is not empty, this will be a parent Application
123 128 # this must be a dict of two-tuples,
124 129 # the first element being the application class/import string
125 130 # and the second being the help string for the subcommand
126 131 subcommands = Dict()
127 132 # parse_command_line will initialize a subapp, if requested
128 133 subapp = Instance('IPython.config.application.Application', allow_none=True)
129 134
130 135 # extra command-line arguments that don't set config values
131 136 extra_args = List(Unicode)
132 137
133 138
134 139 def __init__(self, **kwargs):
135 140 SingletonConfigurable.__init__(self, **kwargs)
136 141 # Add my class to self.classes so my attributes appear in command line
137 142 # options.
138 143 self.classes.insert(0, self.__class__)
139 144
140 145 self.init_logging()
141 146
142 147 def _config_changed(self, name, old, new):
143 148 SingletonConfigurable._config_changed(self, name, old, new)
144 149 self.log.debug('Config changed:')
145 150 self.log.debug(repr(new))
146 151
147 152 def init_logging(self):
148 153 """Start logging for this application.
149 154
150 155 The default is to log to stdout using a StreaHandler. The log level
151 156 starts at loggin.WARN, but this can be adjusted by setting the
152 157 ``log_level`` attribute.
153 158 """
154 159 self.log = logging.getLogger(self.__class__.__name__)
155 160 self.log.setLevel(self.log_level)
156 161 self._log_handler = logging.StreamHandler()
157 162 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
158 163 self._log_handler.setFormatter(self._log_formatter)
159 164 self.log.addHandler(self._log_handler)
160 165
161 166 def initialize(self, argv=None):
162 167 """Do the basic steps to configure me.
163 168
164 169 Override in subclasses.
165 170 """
166 171 self.parse_command_line(argv)
167 172
168 173
169 174 def start(self):
170 175 """Start the app mainloop.
171 176
172 177 Override in subclasses.
173 178 """
174 179 if self.subapp is not None:
175 180 return self.subapp.start()
176 181
177 182 def _log_level_changed(self, name, old, new):
178 183 """Adjust the log level when log_level is set."""
179 184 self.log.setLevel(new)
180 185
181 186 def print_alias_help(self):
182 187 """Print the alias part of the help."""
183 188 if not self.aliases:
184 189 return
185 190
186 191 lines = ['Aliases']
187 192 lines.append('-'*len(lines[0]))
188 193 lines.append('')
189 194 for p in wrap_paragraphs(self.alias_description):
190 195 lines.append(p)
191 196 lines.append('')
192 197
193 198 classdict = {}
194 199 for cls in self.classes:
195 200 # include all parents (up to, but excluding Configurable) in available names
196 201 for c in cls.mro()[:-3]:
197 202 classdict[c.__name__] = c
198 203
199 204 for alias, longname in self.aliases.iteritems():
200 205 classname, traitname = longname.split('.',1)
201 206 cls = classdict[classname]
202 207
203 208 trait = cls.class_traits(config=True)[traitname]
204 209 help = cls.class_get_trait_help(trait)
205 210 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
206 211 lines.append(help)
207 212 lines.append('')
208 213 print '\n'.join(lines)
209 214
210 215 def print_flag_help(self):
211 216 """Print the flag part of the help."""
212 217 if not self.flags:
213 218 return
214 219
215 220 lines = ['Flags']
216 221 lines.append('-'*len(lines[0]))
217 222 lines.append('')
218 223 for p in wrap_paragraphs(self.flag_description):
219 224 lines.append(p)
220 225 lines.append('')
221 226
222 227 for m, (cfg,help) in self.flags.iteritems():
223 228 lines.append('--'+m)
224 229 lines.append(indent(dedent(help.strip())))
225 230 lines.append('')
226 231 print '\n'.join(lines)
227 232
228 233 def print_subcommands(self):
229 234 """Print the subcommand part of the help."""
230 235 if not self.subcommands:
231 236 return
232 237
233 238 lines = ["Subcommands"]
234 239 lines.append('-'*len(lines[0]))
235 240 for subc, (cls,help) in self.subcommands.iteritems():
236 241 lines.append("%s : %s"%(subc, cls))
237 242 if help:
238 243 lines.append(indent(dedent(help.strip())))
239 244 lines.append('')
240 245 print '\n'.join(lines)
241 246
242 247 def print_help(self, classes=False):
243 248 """Print the help for each Configurable class in self.classes.
244 249
245 250 If classes=False (the default), only flags and aliases are printed.
246 251 """
247 252 self.print_subcommands()
248 253 self.print_flag_help()
249 254 self.print_alias_help()
250 255
251 256 if classes:
252 257 if self.classes:
253 258 print "Class parameters"
254 259 print "----------------"
255 260 print
256 261 for p in wrap_paragraphs(self.keyvalue_description):
257 262 print p
258 263 print
259 264
260 265 for cls in self.classes:
261 266 cls.class_print_help()
262 267 print
263 268 else:
264 269 print "To see all available configurables, use `--help-all`"
265 270 print
266 271
267 272 def print_description(self):
268 273 """Print the application description."""
269 274 for p in wrap_paragraphs(self.description):
270 275 print p
271 276 print
272 277
273 278 def print_version(self):
274 279 """Print the version string."""
275 280 print self.version
276 281
277 282 def update_config(self, config):
278 283 """Fire the traits events when the config is updated."""
279 284 # Save a copy of the current config.
280 285 newconfig = deepcopy(self.config)
281 286 # Merge the new config into the current one.
282 287 newconfig._merge(config)
283 288 # Save the combined config as self.config, which triggers the traits
284 289 # events.
285 290 self.config = newconfig
286 291
287 292 def initialize_subcommand(self, subc, argv=None):
288 293 """Initialize a subcommand with argv."""
289 294 subapp,help = self.subcommands.get(subc)
290 295
291 296 if isinstance(subapp, basestring):
292 297 subapp = import_item(subapp)
293 298
294 299 # clear existing instances
295 300 self.__class__.clear_instance()
296 301 # instantiate
297 302 self.subapp = subapp.instance()
298 303 # and initialize subapp
299 304 self.subapp.initialize(argv)
300 305
301 306 def parse_command_line(self, argv=None):
302 307 """Parse the command line arguments."""
303 308 argv = sys.argv[1:] if argv is None else argv
304 309
305 310 if self.subcommands and len(argv) > 0:
306 311 # we have subcommands, and one may have been specified
307 312 subc, subargv = argv[0], argv[1:]
308 313 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
309 314 # it's a subcommand, and *not* a flag or class parameter
310 315 return self.initialize_subcommand(subc, subargv)
311 316
312 317 if '-h' in argv or '--help' in argv or '--help-all' in argv:
313 318 self.print_description()
314 319 self.print_help('--help-all' in argv)
315 320 self.exit(0)
316 321
317 322 if '--version' in argv:
318 323 self.print_version()
319 324 self.exit(0)
320 325
321 326 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
322 327 flags=self.flags)
323 328 try:
324 329 config = loader.load_config()
325 330 except ArgumentError as e:
326 331 self.log.fatal(str(e))
327 332 self.print_description()
328 333 self.print_help()
329 334 self.exit(1)
330 335 self.update_config(config)
331 336 # store unparsed args in extra_args
332 337 self.extra_args = loader.extra_args
333 338
334 339 def load_config_file(self, filename, path=None):
335 340 """Load a .py based config file by filename and path."""
336 341 loader = PyFileConfigLoader(filename, path=path)
337 342 config = loader.load_config()
338 343 self.update_config(config)
339 344
345 def generate_config_file(self):
346 """generate default config file from Configurables"""
347 lines = ["# Configuration file for %s."%self.name]
348 lines.append('')
349 lines.append('c = get_config()')
350 lines.append('')
351 for cls in self.classes:
352 lines.append(cls.class_config_section())
353 return '\n'.join(lines)
354
340 355 def exit(self, exit_status=0):
341 356 self.log.debug("Exiting application: %s" % self.name)
342 357 sys.exit(exit_status)
343 358
344 359 #-----------------------------------------------------------------------------
345 360 # utility functions, for convenience
346 361 #-----------------------------------------------------------------------------
347 362
348 363 def boolean_flag(name, configurable, set_help='', unset_help=''):
349 364 """Helper for building basic --trait, --no-trait flags.
350 365
351 366 Parameters
352 367 ----------
353 368
354 369 name : str
355 370 The name of the flag.
356 371 configurable : str
357 372 The 'Class.trait' string of the trait to be set/unset with the flag
358 373 set_help : unicode
359 374 help string for --name flag
360 375 unset_help : unicode
361 376 help string for --no-name flag
362 377
363 378 Returns
364 379 -------
365 380
366 381 cfg : dict
367 382 A dict with two keys: 'name', and 'no-name', for setting and unsetting
368 383 the trait, respectively.
369 384 """
370 385 # default helpstrings
371 386 set_help = set_help or "set %s=True"%configurable
372 387 unset_help = unset_help or "set %s=False"%configurable
373 388
374 389 cls,trait = configurable.split('.')
375 390
376 391 setter = {cls : {trait : True}}
377 392 unsetter = {cls : {trait : False}}
378 393 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
379 394
@@ -1,283 +1,315 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A base class for objects that are configurable.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10 * Min RK
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import datetime
25 25 from copy import deepcopy
26 26
27 27 from loader import Config
28 28 from IPython.utils.traitlets import HasTraits, Instance
29 29 from IPython.utils.text import indent, wrap_paragraphs
30 30
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Helper classes for Configurables
34 34 #-----------------------------------------------------------------------------
35 35
36 36
37 37 class ConfigurableError(Exception):
38 38 pass
39 39
40 40
41 41 class MultipleInstanceError(ConfigurableError):
42 42 pass
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Configurable implementation
46 46 #-----------------------------------------------------------------------------
47 47
48 48 class Configurable(HasTraits):
49 49
50 50 config = Instance(Config,(),{})
51 51 created = None
52 52
53 53 def __init__(self, **kwargs):
54 54 """Create a configurable given a config config.
55 55
56 56 Parameters
57 57 ----------
58 58 config : Config
59 59 If this is empty, default values are used. If config is a
60 60 :class:`Config` instance, it will be used to configure the
61 61 instance.
62 62
63 63 Notes
64 64 -----
65 65 Subclasses of Configurable must call the :meth:`__init__` method of
66 66 :class:`Configurable` *before* doing anything else and using
67 67 :func:`super`::
68 68
69 69 class MyConfigurable(Configurable):
70 70 def __init__(self, config=None):
71 71 super(MyConfigurable, self).__init__(config)
72 72 # Then any other code you need to finish initialization.
73 73
74 74 This ensures that instances will be configured properly.
75 75 """
76 76 config = kwargs.pop('config', None)
77 77 if config is not None:
78 78 # We used to deepcopy, but for now we are trying to just save
79 79 # by reference. This *could* have side effects as all components
80 80 # will share config. In fact, I did find such a side effect in
81 81 # _config_changed below. If a config attribute value was a mutable type
82 82 # all instances of a component were getting the same copy, effectively
83 83 # making that a class attribute.
84 84 # self.config = deepcopy(config)
85 85 self.config = config
86 86 # This should go second so individual keyword arguments override
87 87 # the values in config.
88 88 super(Configurable, self).__init__(**kwargs)
89 89 self.created = datetime.datetime.now()
90 90
91 91 #-------------------------------------------------------------------------
92 92 # Static trait notifiations
93 93 #-------------------------------------------------------------------------
94 94
95 95 def _config_changed(self, name, old, new):
96 96 """Update all the class traits having ``config=True`` as metadata.
97 97
98 98 For any class trait with a ``config`` metadata attribute that is
99 99 ``True``, we update the trait with the value of the corresponding
100 100 config entry.
101 101 """
102 102 # Get all traits with a config metadata entry that is True
103 103 traits = self.traits(config=True)
104 104
105 105 # We auto-load config section for this class as well as any parent
106 106 # classes that are Configurable subclasses. This starts with Configurable
107 107 # and works down the mro loading the config for each section.
108 108 section_names = [cls.__name__ for cls in \
109 109 reversed(self.__class__.__mro__) if
110 110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
111 111
112 112 for sname in section_names:
113 113 # Don't do a blind getattr as that would cause the config to
114 114 # dynamically create the section with name self.__class__.__name__.
115 115 if new._has_section(sname):
116 116 my_config = new[sname]
117 117 for k, v in traits.iteritems():
118 118 # Don't allow traitlets with config=True to start with
119 119 # uppercase. Otherwise, they are confused with Config
120 120 # subsections. But, developers shouldn't have uppercase
121 121 # attributes anyways! (PEP 6)
122 122 if k[0].upper()==k[0] and not k.startswith('_'):
123 123 raise ConfigurableError('Configurable traitlets with '
124 124 'config=True must start with a lowercase so they are '
125 125 'not confused with Config subsections: %s.%s' % \
126 126 (self.__class__.__name__, k))
127 127 try:
128 128 # Here we grab the value from the config
129 129 # If k has the naming convention of a config
130 130 # section, it will be auto created.
131 131 config_value = my_config[k]
132 132 except KeyError:
133 133 pass
134 134 else:
135 135 # print "Setting %s.%s from %s.%s=%r" % \
136 136 # (self.__class__.__name__,k,sname,k,config_value)
137 137 # We have to do a deepcopy here if we don't deepcopy the entire
138 138 # config object. If we don't, a mutable config_value will be
139 139 # shared by all instances, effectively making it a class attribute.
140 140 setattr(self, k, deepcopy(config_value))
141 141
142 142 @classmethod
143 143 def class_get_help(cls):
144 144 """Get the help string for this class in ReST format."""
145 145 cls_traits = cls.class_traits(config=True)
146 146 final_help = []
147 147 final_help.append(u'%s options' % cls.__name__)
148 148 final_help.append(len(final_help[0])*u'-')
149 149 for k,v in cls.class_traits(config=True).iteritems():
150 150 help = cls.class_get_trait_help(v)
151 151 final_help.append(help)
152 152 return '\n'.join(final_help)
153 153
154 154 @classmethod
155 155 def class_get_trait_help(cls, trait):
156 156 """Get the help string for a single trait."""
157 157 lines = []
158 158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
159 159 lines.append(header)
160 160 try:
161 161 dvr = repr(trait.get_default_value())
162 162 except Exception:
163 163 dvr = None # ignore defaults we can't construct
164 164 if dvr is not None:
165 165 if len(dvr) > 64:
166 166 dvr = dvr[:61]+'...'
167 167 lines.append(indent('Default: %s'%dvr, 4))
168 168 if 'Enum' in trait.__class__.__name__:
169 169 # include Enum choices
170 170 lines.append(indent('Choices: %r'%(trait.values,)))
171 171
172 172 help = trait.get_metadata('help')
173 173 if help is not None:
174 174 help = '\n'.join(wrap_paragraphs(help, 76))
175 175 lines.append(indent(help, 4))
176 176 return '\n'.join(lines)
177 177
178 178 @classmethod
179 179 def class_print_help(cls):
180 180 """Get the help string for a single trait and print it."""
181 181 print cls.class_get_help()
182 182
183 @classmethod
184 def class_config_section(cls):
185 """Get the config class config section"""
186 def c(s):
187 """return a commented, wrapped block."""
188 s = '\n\n'.join(wrap_paragraphs(s, 78))
189
190 return '# ' + s.replace('\n', '\n# ')
191
192 # section header
193 breaker = '#' + '-'*78
194 s = "# %s configuration"%cls.__name__
195 lines = [breaker, s, breaker, '']
196 # get the description trait
197 desc = cls.class_traits().get('description')
198 if desc:
199 desc = desc.default_value
200 else:
201 # no description trait, use __doc__
202 desc = getattr(cls, '__doc__', '')
203 if desc:
204 lines.append(c(desc))
205 lines.append('')
206
207 for name,trait in cls.class_traits(config=True).iteritems():
208 help = trait.get_metadata('help') or ''
209 lines.append(c(help))
210 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
211 lines.append('')
212 return '\n'.join(lines)
213
214
183 215
184 216 class SingletonConfigurable(Configurable):
185 217 """A configurable that only allows one instance.
186 218
187 219 This class is for classes that should only have one instance of itself
188 220 or *any* subclass. To create and retrieve such a class use the
189 221 :meth:`SingletonConfigurable.instance` method.
190 222 """
191 223
192 224 _instance = None
193 225
194 226 @classmethod
195 227 def _walk_mro(cls):
196 228 """Walk the cls.mro() for parent classes that are also singletons
197 229
198 230 For use in instance()
199 231 """
200 232
201 233 for subclass in cls.mro():
202 234 if issubclass(cls, subclass) and \
203 235 issubclass(subclass, SingletonConfigurable) and \
204 236 subclass != SingletonConfigurable:
205 237 yield subclass
206 238
207 239 @classmethod
208 240 def clear_instance(cls):
209 241 """unset _instance for this class and singleton parents.
210 242 """
211 243 if not cls.initialized():
212 244 return
213 245 for subclass in cls._walk_mro():
214 246 if isinstance(subclass._instance, cls):
215 247 # only clear instances that are instances
216 248 # of the calling class
217 249 subclass._instance = None
218 250
219 251 @classmethod
220 252 def instance(cls, *args, **kwargs):
221 253 """Returns a global instance of this class.
222 254
223 255 This method create a new instance if none have previously been created
224 256 and returns a previously created instance is one already exists.
225 257
226 258 The arguments and keyword arguments passed to this method are passed
227 259 on to the :meth:`__init__` method of the class upon instantiation.
228 260
229 261 Examples
230 262 --------
231 263
232 264 Create a singleton class using instance, and retrieve it::
233 265
234 266 >>> from IPython.config.configurable import SingletonConfigurable
235 267 >>> class Foo(SingletonConfigurable): pass
236 268 >>> foo = Foo.instance()
237 269 >>> foo == Foo.instance()
238 270 True
239 271
240 272 Create a subclass that is retrived using the base class instance::
241 273
242 274 >>> class Bar(SingletonConfigurable): pass
243 275 >>> class Bam(Bar): pass
244 276 >>> bam = Bam.instance()
245 277 >>> bam == Bar.instance()
246 278 True
247 279 """
248 280 # Create and save the instance
249 281 if cls._instance is None:
250 282 inst = cls(*args, **kwargs)
251 283 # Now make sure that the instance will also be returned by
252 284 # parent classes' _instance attribute.
253 285 for subclass in cls._walk_mro():
254 286 subclass._instance = inst
255 287
256 288 if isinstance(cls._instance, cls):
257 289 return cls._instance
258 290 else:
259 291 raise MultipleInstanceError(
260 292 'Multiple incompatible subclass instances of '
261 293 '%s are being created.' % cls.__name__
262 294 )
263 295
264 296 @classmethod
265 297 def initialized(cls):
266 298 """Has an instance been created?"""
267 299 return hasattr(cls, "_instance") and cls._instance is not None
268 300
269 301
270 302 class LoggingConfigurable(Configurable):
271 303 """A parent class for Configurables that log.
272 304
273 305 Subclasses have a log trait, and the default behavior
274 306 is to get the logger from the currently running Application
275 307 via Application.instance().log.
276 308 """
277 309
278 310 log = Instance('logging.Logger')
279 311 def _log_default(self):
280 312 from IPython.config.application import Application
281 313 return Application.instance().log
282 314
283 315
@@ -1,259 +1,290 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008-2011 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import logging
31 31 import os
32 32 import shutil
33 33 import sys
34 34
35 35 from IPython.config.application import Application
36 36 from IPython.config.configurable import Configurable
37 37 from IPython.config.loader import Config
38 38 from IPython.core import release, crashhandler
39 39 from IPython.core.profiledir import ProfileDir, ProfileDirError
40 40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
41 41 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Classes and functions
45 45 #-----------------------------------------------------------------------------
46 46
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Base Application Class
50 50 #-----------------------------------------------------------------------------
51 51
52 52 # aliases and flags
53 53
54 54 base_aliases = dict(
55 55 profile='BaseIPythonApplication.profile',
56 56 ipython_dir='BaseIPythonApplication.ipython_dir',
57 57 )
58 58
59 59 base_flags = dict(
60 60 debug = ({'Application' : {'log_level' : logging.DEBUG}},
61 61 "set log level to logging.DEBUG (maximize logging output)"),
62 62 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
63 63 "set log level to logging.CRITICAL (minimize logging output)"),
64 64 init = ({'BaseIPythonApplication' : {
65 65 'copy_config_files' : True,
66 66 'auto_create' : True}
67 67 }, "Initialize profile with default config files")
68 68 )
69 69
70 70
71 71 class BaseIPythonApplication(Application):
72 72
73 73 name = Unicode(u'ipython')
74 74 description = Unicode(u'IPython: an enhanced interactive Python shell.')
75 75 version = Unicode(release.version)
76 76
77 77 aliases = Dict(base_aliases)
78 78 flags = Dict(base_flags)
79 79
80 80 # Track whether the config_file has changed,
81 81 # because some logic happens only if we aren't using the default.
82 82 config_file_specified = Bool(False)
83 83
84 84 config_file_name = Unicode(u'ipython_config.py')
85 def _config_file_name_default(self):
86 return self.name.replace('-','_') + u'_config.py'
85 87 def _config_file_name_changed(self, name, old, new):
86 88 if new != old:
87 89 self.config_file_specified = True
88 90
89 91 # The directory that contains IPython's builtin profiles.
90 92 builtin_profile_dir = Unicode(
91 93 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
92 94 )
93 95
94 96 config_file_paths = List(Unicode)
95 97 def _config_file_paths_default(self):
96 98 return [os.getcwdu()]
97 99
98 100 profile = Unicode(u'default', config=True,
99 101 help="""The IPython profile to use."""
100 102 )
101 103 def _profile_changed(self, name, old, new):
102 104 self.builtin_profile_dir = os.path.join(
103 105 get_ipython_package_dir(), u'config', u'profile', new
104 106 )
105 107
106
107 108 ipython_dir = Unicode(get_ipython_dir(), config=True,
108 109 help="""
109 110 The name of the IPython directory. This directory is used for logging
110 111 configuration (through profiles), history storage, etc. The default
111 112 is usually $HOME/.ipython. This options can also be specified through
112 113 the environment variable IPYTHON_DIR.
113 114 """
114 115 )
115 116
116 117 overwrite = Bool(False, config=True,
117 118 help="""Whether to overwrite existing config files when copying""")
118 119 auto_create = Bool(False, config=True,
119 120 help="""Whether to create profile dir if it doesn't exist""")
120 121
121 122 config_files = List(Unicode)
122 123 def _config_files_default(self):
123 124 return [u'ipython_config.py']
124 125
125 126 copy_config_files = Bool(False, config=True,
126 help="""Whether to copy the default config files into the profile dir.""")
127 help="""Whether to install the default config files into the profile dir.
128 If a new profile is being created, and IPython contains config files for that
129 profile, then they will be staged into the new directory. Otherwise,
130 default config files will be automatically generated.
131 """)
127 132
128 133 # The class to use as the crash handler.
129 134 crash_handler_class = Type(crashhandler.CrashHandler)
130 135
131 136 def __init__(self, **kwargs):
132 137 super(BaseIPythonApplication, self).__init__(**kwargs)
133 138 # ensure even default IPYTHON_DIR exists
134 139 if not os.path.exists(self.ipython_dir):
135 140 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
136 141
137 142 #-------------------------------------------------------------------------
138 143 # Various stages of Application creation
139 144 #-------------------------------------------------------------------------
140 145
141 146 def init_crash_handler(self):
142 147 """Create a crash handler, typically setting sys.excepthook to it."""
143 148 self.crash_handler = self.crash_handler_class(self)
144 149 sys.excepthook = self.crash_handler
145 150
146 151 def _ipython_dir_changed(self, name, old, new):
147 152 if old in sys.path:
148 153 sys.path.remove(old)
149 154 sys.path.append(os.path.abspath(new))
150 155 if not os.path.isdir(new):
151 156 os.makedirs(new, mode=0777)
152 157 readme = os.path.join(new, 'README')
153 158 if not os.path.exists(readme):
154 159 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
155 160 shutil.copy(os.path.join(path, 'README'), readme)
156 161 self.log.debug("IPYTHON_DIR set to: %s" % new)
157 162
158 163 def load_config_file(self, suppress_errors=True):
159 164 """Load the config file.
160 165
161 166 By default, errors in loading config are handled, and a warning
162 167 printed on screen. For testing, the suppress_errors option is set
163 168 to False, so errors will make tests fail.
164 169 """
170 base_config = 'ipython_config.py'
171 self.log.debug("Attempting to load config file: %s" %
172 base_config)
173 try:
174 Application.load_config_file(
175 self,
176 base_config,
177 path=self.config_file_paths
178 )
179 except IOError:
180 # ignore errors loading parent
181 pass
182 if self.config_file_name == base_config:
183 # don't load secondary config
184 return
165 185 self.log.debug("Attempting to load config file: %s" %
166 186 self.config_file_name)
167 187 try:
168 188 Application.load_config_file(
169 189 self,
170 190 self.config_file_name,
171 191 path=self.config_file_paths
172 192 )
173 193 except IOError:
174 194 # Only warn if the default config file was NOT being used.
175 195 if self.config_file_specified:
176 196 self.log.warn("Config file not found, skipping: %s" %
177 197 self.config_file_name)
178 198 except:
179 199 # For testing purposes.
180 200 if not suppress_errors:
181 201 raise
182 202 self.log.warn("Error loading config file: %s" %
183 203 self.config_file_name, exc_info=True)
184 204
185 205 def init_profile_dir(self):
186 206 """initialize the profile dir"""
187 207 try:
188 208 # location explicitly specified:
189 209 location = self.config.ProfileDir.location
190 210 except AttributeError:
191 211 # location not specified, find by profile name
192 212 try:
193 213 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
194 214 except ProfileDirError:
195 215 # not found, maybe create it (always create default profile)
196 216 if self.auto_create or self.profile=='default':
197 217 try:
198 218 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
199 219 except ProfileDirError:
200 220 self.log.fatal("Could not create profile: %r"%self.profile)
201 221 self.exit(1)
202 222 else:
203 223 self.log.info("Created profile dir: %r"%p.location)
204 224 else:
205 225 self.log.fatal("Profile %r not found."%self.profile)
206 226 self.exit(1)
207 227 else:
208 228 self.log.info("Using existing profile dir: %r"%p.location)
209 229 else:
210 230 # location is fully specified
211 231 try:
212 232 p = ProfileDir.find_profile_dir(location, self.config)
213 233 except ProfileDirError:
214 234 # not found, maybe create it
215 235 if self.auto_create:
216 236 try:
217 237 p = ProfileDir.create_profile_dir(location, self.config)
218 238 except ProfileDirError:
219 239 self.log.fatal("Could not create profile directory: %r"%location)
220 240 self.exit(1)
221 241 else:
222 242 self.log.info("Creating new profile dir: %r"%location)
223 243 else:
224 244 self.log.fatal("Profile directory %r not found."%location)
225 245 self.exit(1)
226 246 else:
227 247 self.log.info("Using existing profile dir: %r"%location)
228 248
229 249 self.profile_dir = p
230 250 self.config_file_paths.append(p.location)
231 251
232 252 def init_config_files(self):
233 253 """[optionally] copy default config files into profile dir."""
234 254 # copy config files
235 255 if self.copy_config_files:
236 256 path = self.builtin_profile_dir
237 257 src = self.profile
238 if not os.path.exists(path):
239 # use default if new profile doesn't have a preset
240 path = None
241 src = 'default'
242 258
243 self.log.debug("Staging %s config files into %r [overwrite=%s]"%(
244 src, self.profile_dir.location, self.overwrite)
259 cfg = self.config_file_name
260 if path and os.path.exists(os.path.join(path, cfg)):
261 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
262 cfg, src, self.profile_dir.location, self.overwrite)
245 263 )
246
247 for cfg in self.config_files:
248 264 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
265 else:
266 self.stage_default_config_file()
267
268 def stage_default_config_file(self):
269 """auto generate default config file, and stage it into the profile."""
270 s = self.generate_config_file()
271 fname = os.path.join(self.profile_dir.location, self.config_file_name)
272 if self.overwrite or not os.path.exists(fname):
273 self.log.warn("Generating default config file: %r"%(fname))
274 with open(fname, 'w') as f:
275 f.write(s)
276
249 277
250 278 def initialize(self, argv=None):
251 279 self.init_crash_handler()
252 280 self.parse_command_line(argv)
281 if self.subapp is not None:
282 # stop here if subapp is taking over
283 return
253 284 cl_config = self.config
254 285 self.init_profile_dir()
255 286 self.init_config_files()
256 287 self.load_config_file()
257 288 # enforce cl-opts override configfile opts:
258 289 self.update_config(cl_config)
259 290
@@ -1,174 +1,210 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import logging
25 25 import os
26 26
27 27 from IPython.config.application import Application, boolean_flag
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags, base_aliases
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.path import get_ipython_dir
33 33 from IPython.utils.traitlets import Unicode, Bool, Dict
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Constants
37 37 #-----------------------------------------------------------------------------
38 38
39 39 create_help = """Create an ipcluster profile by name
40 40
41 41 Create an ipython profile directory by its name or
42 42 profile directory path. Profile directories contain
43 43 configuration, log and security related files and are named
44 44 using the convention 'profile_<name>'. By default they are
45 45 located in your ipython directory. Once created, you will
46 46 can edit the configuration files in the profile
47 47 directory to configure IPython. Most users will create a
48 48 cluster directory by profile name,
49 49 `ipython profile create myprofile`, which will put the directory
50 50 in `<ipython_dir>/profile_myprofile`.
51 51 """
52 52 list_help = """List available IPython profiles
53 53
54 54 List all available profiles, by profile location, that can
55 55 be found in the current working directly or in the ipython
56 56 directory. Profile directories are named using the convention
57 57 'profile_<profile>'.
58 58 """
59 59 profile_help = """Manage IPython profiles
60 60
61 61 Profile directories contain
62 62 configuration, log and security related files and are named
63 63 using the convention 'profile_<name>'. By default they are
64 64 located in your ipython directory. You can create profiles
65 65 with `ipython profile create <name>`, or see the profiles you
66 66 already have with `ipython profile list`
67 67
68 68 To get started configuring IPython, simply do:
69 69
70 70 $> ipython profile create
71 71
72 72 and IPython will create the default profile in <ipython_dir>/profile_default,
73 73 where you can edit ipython_config.py to start configuring IPython.
74 74
75 75 """
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Profile Application Class (for `ipython profile` subcommand)
79 79 #-----------------------------------------------------------------------------
80 80
81 81
82 82
83 83 class ProfileList(Application):
84 84 name = u'ipython-profile'
85 85 description = list_help
86 86
87 87 aliases = Dict(dict(
88 88 ipython_dir = 'ProfileList.ipython_dir',
89 89 log_level = 'Application.log_level',
90 90 ))
91 91 flags = Dict(dict(
92 92 debug = ({'Application' : {'log_level' : 0}},
93 93 "Set log_level to 0, maximizing log output."
94 94 )
95 95 ))
96 96 ipython_dir = Unicode(get_ipython_dir(), config=True,
97 97 help="""
98 98 The name of the IPython directory. This directory is used for logging
99 99 configuration (through profiles), history storage, etc. The default
100 100 is usually $HOME/.ipython. This options can also be specified through
101 101 the environment variable IPYTHON_DIR.
102 102 """
103 103 )
104 104
105 105 def list_profile_dirs(self):
106 106 # Find the search paths
107 107 paths = [os.getcwdu(), self.ipython_dir]
108 108
109 109 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
110 110 for path in paths:
111 111 files = os.listdir(path)
112 112 for f in files:
113 113 full_path = os.path.join(path, f)
114 114 if os.path.isdir(full_path) and f.startswith('profile_'):
115 115 profile = f.split('_',1)[-1]
116 116 start_cmd = 'ipython profile=%s' % profile
117 117 print start_cmd + " ==> " + full_path
118 118
119 119 def start(self):
120 120 self.list_profile_dirs()
121 121
122 122
123 123 create_flags = {}
124 124 create_flags.update(base_flags)
125 125 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
126 126 "reset config files to defaults", "leave existing config files"))
127 127 create_flags.update(boolean_flag('cluster', 'ProfileCreate.cluster',
128 128 "Include parallel computing config files",
129 129 "Don't include parallel computing config files"))
130 130
131 131 class ProfileCreate(BaseIPythonApplication):
132 132 name = u'ipython-profile'
133 133 description = create_help
134 134 auto_create = Bool(True, config=False)
135 135
136 136 def _copy_config_files_default(self):
137 137 return True
138 138
139 139 cluster = Bool(False, config=True,
140 140 help="whether to include parallel computing config files")
141 141 def _cluster_changed(self, name, old, new):
142 142 cluster_files = [ 'ipcontroller_config.py',
143 143 'ipengine_config.py',
144 144 'ipcluster_config.py'
145 145 ]
146 146 if new:
147 147 for cf in cluster_files:
148 148 self.config_files.append(cf)
149 149 else:
150 150 for cf in cluster_files:
151 151 if cf in self.config_files:
152 152 self.config_files.remove(cf)
153 153
154 154 def parse_command_line(self, argv):
155 155 super(ProfileCreate, self).parse_command_line(argv)
156 156 # accept positional arg as profile name
157 157 if self.extra_args:
158 158 self.profile = self.extra_args[0]
159 159
160 160 flags = Dict(create_flags)
161 161
162 162 aliases = Dict(dict(profile='BaseIPythonApplication.profile'))
163 163
164 164 classes = [ProfileDir]
165 165
166 def init_config_files(self):
167 super(ProfileCreate, self).init_config_files()
168 # use local imports, since these classes may import from here
169 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
170 apps = [TerminalIPythonApp]
171 try:
172 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
173 except ImportError:
174 pass
175 else:
176 apps.append(IPythonQtConsoleApp)
177 if self.cluster:
178 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
179 from IPython.parallel.apps.ipengineapp import IPEngineApp
180 from IPython.parallel.apps.ipclusterapp import IPClusterStart
181 from IPython.parallel.apps.iploggerapp import IPLoggerApp
182 apps.extend([
183 IPControllerApp,
184 IPEngineApp,
185 IPClusterStart,
186 IPLoggerApp,
187 ])
188 for App in apps:
189 app = App()
190 app.config.update(self.config)
191 app.log = self.log
192 app.overwrite = self.overwrite
193 app.copy_config_files=True
194 app.profile = self.profile
195 app.init_profile_dir()
196 app.init_config_files()
197 print 'tic'
198
199 def stage_default_config_file(self):
200 pass
201
166 202 class ProfileApp(Application):
167 203 name = u'ipython-profile'
168 204 description = profile_help
169 205
170 206 subcommands = Dict(dict(
171 207 create = (ProfileCreate, "Create a new profile dir with default config files"),
172 208 list = (ProfileList, "List existing profiles")
173 209 ))
174 210
@@ -1,358 +1,357 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2010 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader
34 34 )
35 35 from IPython.config.application import boolean_flag
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.crashhandler import CrashHandler
39 39 from IPython.core.formatters import PlainTextFormatter
40 40 from IPython.core.application import (
41 41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 42 )
43 43 from IPython.core.shellapp import (
44 44 InteractiveShellApp, shell_flags, shell_aliases
45 45 )
46 46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 47 from IPython.lib import inputhook
48 48 from IPython.utils.path import get_ipython_dir, check_for_old_config
49 49 from IPython.utils.traitlets import (
50 50 Bool, Dict, CaselessStrEnum
51 51 )
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Globals, utilities and helpers
55 55 #-----------------------------------------------------------------------------
56 56
57 57 #: The default config file name for this application.
58 58 default_config_file_name = u'ipython_config.py'
59 59
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Crash handler for this application
63 63 #-----------------------------------------------------------------------------
64 64
65 65 _message_template = """\
66 66 Oops, $self.app_name crashed. We do our best to make it stable, but...
67 67
68 68 A crash report was automatically generated with the following information:
69 69 - A verbatim copy of the crash traceback.
70 70 - A copy of your input history during this session.
71 71 - Data on your current $self.app_name configuration.
72 72
73 73 It was left in the file named:
74 74 \t'$self.crash_report_fname'
75 75 If you can email this file to the developers, the information in it will help
76 76 them in understanding and correcting the problem.
77 77
78 78 You can mail it to: $self.contact_name at $self.contact_email
79 79 with the subject '$self.app_name Crash Report'.
80 80
81 81 If you want to do it now, the following command will work (under Unix):
82 82 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
83 83
84 84 To ensure accurate tracking of this issue, please file a report about it at:
85 85 $self.bug_tracker
86 86 """
87 87
88 88 class IPAppCrashHandler(CrashHandler):
89 89 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
90 90
91 91 message_template = _message_template
92 92
93 93 def __init__(self, app):
94 94 contact_name = release.authors['Fernando'][0]
95 95 contact_email = release.authors['Fernando'][1]
96 96 bug_tracker = 'http://github.com/ipython/ipython/issues'
97 97 super(IPAppCrashHandler,self).__init__(
98 98 app, contact_name, contact_email, bug_tracker
99 99 )
100 100
101 101 def make_report(self,traceback):
102 102 """Return a string containing a crash report."""
103 103
104 104 sec_sep = self.section_sep
105 105 # Start with parent report
106 106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 107 # Add interactive-specific info we may have
108 108 rpt_add = report.append
109 109 try:
110 110 rpt_add(sec_sep+"History of session input:")
111 111 for line in self.app.shell.user_ns['_ih']:
112 112 rpt_add(line)
113 113 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 114 rpt_add(self.app.shell._last_input_line+'\n')
115 115 except:
116 116 pass
117 117
118 118 return ''.join(report)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # Aliases and Flags
122 122 #-----------------------------------------------------------------------------
123 123 flags = dict(base_flags)
124 124 flags.update(shell_flags)
125 125 addflag = lambda *args: flags.update(boolean_flag(*args))
126 126 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 127 'Turn on auto editing of files with syntax errors.',
128 128 'Turn off auto editing of files with syntax errors.'
129 129 )
130 130 addflag('banner', 'TerminalIPythonApp.display_banner',
131 131 "Display a banner upon starting IPython.",
132 132 "Don't display a banner upon starting IPython."
133 133 )
134 134 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 135 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 136 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 137 you can force a direct exit without any confirmation.""",
138 138 "Don't prompt the user when exiting."
139 139 )
140 140 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 141 "Enable auto setting the terminal title.",
142 142 "Disable auto setting the terminal title."
143 143 )
144 144 classic_config = Config()
145 145 classic_config.InteractiveShell.cache_size = 0
146 146 classic_config.PlainTextFormatter.pprint = False
147 147 classic_config.InteractiveShell.prompt_in1 = '>>> '
148 148 classic_config.InteractiveShell.prompt_in2 = '... '
149 149 classic_config.InteractiveShell.prompt_out = ''
150 150 classic_config.InteractiveShell.separate_in = ''
151 151 classic_config.InteractiveShell.separate_out = ''
152 152 classic_config.InteractiveShell.separate_out2 = ''
153 153 classic_config.InteractiveShell.colors = 'NoColor'
154 154 classic_config.InteractiveShell.xmode = 'Plain'
155 155
156 156 flags['classic']=(
157 157 classic_config,
158 158 "Gives IPython a similar feel to the classic Python prompt."
159 159 )
160 160 # # log doesn't make so much sense this way anymore
161 161 # paa('--log','-l',
162 162 # action='store_true', dest='InteractiveShell.logstart',
163 163 # help="Start logging to the default log file (./ipython_log.py).")
164 164 #
165 165 # # quick is harder to implement
166 166 flags['quick']=(
167 167 {'TerminalIPythonApp' : {'quick' : True}},
168 168 "Enable quick startup with no config files."
169 169 )
170 170
171 171 flags['i'] = (
172 172 {'TerminalIPythonApp' : {'force_interact' : True}},
173 173 "If running code from the command line, become interactive afterwards."
174 174 )
175 175 flags['pylab'] = (
176 176 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
177 177 """Pre-load matplotlib and numpy for interactive use with
178 178 the default matplotlib backend."""
179 179 )
180 180
181 181 aliases = dict(base_aliases)
182 182 aliases.update(shell_aliases)
183 183
184 184 # it's possible we don't want short aliases for *all* of these:
185 185 aliases.update(dict(
186 186 gui='TerminalIPythonApp.gui',
187 187 pylab='TerminalIPythonApp.pylab',
188 188 ))
189 189
190 190 #-----------------------------------------------------------------------------
191 191 # Main classes and functions
192 192 #-----------------------------------------------------------------------------
193 193
194 194 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
195 195 name = u'ipython'
196 196 description = usage.cl_usage
197 197 default_config_file_name = default_config_file_name
198 198 crash_handler_class = IPAppCrashHandler
199 199
200 200 flags = Dict(flags)
201 201 aliases = Dict(aliases)
202 202 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
203 203 subcommands = Dict(dict(
204 204 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
205 205 """Launch the IPython Qt Console."""
206 206 ),
207 207 profile = ("IPython.core.profileapp.ProfileApp",
208 208 "Create and manage IPython profiles.")
209 209 ))
210 210
211 # *do* autocreate requested profile
211 # *do* autocreate requested profile, but don't create the config file.
212 212 auto_create=Bool(True)
213 copy_config_files=Bool(True)
214 213 # configurables
215 214 ignore_old_config=Bool(False, config=True,
216 215 help="Suppress warning messages about legacy config files"
217 216 )
218 217 quick = Bool(False, config=True,
219 218 help="""Start IPython quickly by skipping the loading of config files."""
220 219 )
221 220 def _quick_changed(self, name, old, new):
222 221 if new:
223 222 self.load_config_file = lambda *a, **kw: None
224 223 self.ignore_old_config=True
225 224
226 225 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
227 226 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
228 227 )
229 228 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
230 229 config=True,
231 230 help="""Pre-load matplotlib and numpy for interactive use,
232 231 selecting a particular matplotlib backend and loop integration.
233 232 """
234 233 )
235 234 display_banner = Bool(True, config=True,
236 235 help="Whether to display a banner upon starting IPython."
237 236 )
238 237
239 238 # if there is code of files to run from the cmd line, don't interact
240 239 # unless the --i flag (App.force_interact) is true.
241 240 force_interact = Bool(False, config=True,
242 241 help="""If a command or file is given via the command-line,
243 242 e.g. 'ipython foo.py"""
244 243 )
245 244 def _force_interact_changed(self, name, old, new):
246 245 if new:
247 246 self.interact = True
248 247
249 248 def _file_to_run_changed(self, name, old, new):
250 249 if new and not self.force_interact:
251 250 self.interact = False
252 251 _code_to_run_changed = _file_to_run_changed
253 252
254 253 # internal, not-configurable
255 254 interact=Bool(True)
256 255
257 256
258 257 def initialize(self, argv=None):
259 258 """Do actions after construct, but before starting the app."""
260 259 super(TerminalIPythonApp, self).initialize(argv)
261 260 if self.subapp is not None:
262 261 # don't bother initializing further, starting subapp
263 262 return
264 263 if not self.ignore_old_config:
265 264 check_for_old_config(self.ipython_dir)
266 265 # print self.extra_args
267 266 if self.extra_args:
268 267 self.file_to_run = self.extra_args[0]
269 268 # create the shell
270 269 self.init_shell()
271 270 # and draw the banner
272 271 self.init_banner()
273 272 # Now a variety of things that happen after the banner is printed.
274 273 self.init_gui_pylab()
275 274 self.init_extensions()
276 275 self.init_code()
277 276
278 277 def init_shell(self):
279 278 """initialize the InteractiveShell instance"""
280 279 # I am a little hesitant to put these into InteractiveShell itself.
281 280 # But that might be the place for them
282 281 sys.path.insert(0, '')
283 282
284 283 # Create an InteractiveShell instance.
285 284 # shell.display_banner should always be False for the terminal
286 285 # based app, because we call shell.show_banner() by hand below
287 286 # so the banner shows *before* all extension loading stuff.
288 287 self.shell = TerminalInteractiveShell.instance(config=self.config,
289 288 display_banner=False, profile_dir=self.profile_dir,
290 289 ipython_dir=self.ipython_dir)
291 290
292 291 def init_banner(self):
293 292 """optionally display the banner"""
294 293 if self.display_banner and self.interact:
295 294 self.shell.show_banner()
296 295 # Make sure there is a space below the banner.
297 296 if self.log_level <= logging.INFO: print
298 297
299 298
300 299 def init_gui_pylab(self):
301 300 """Enable GUI event loop integration, taking pylab into account."""
302 301 gui = self.gui
303 302
304 303 # Using `pylab` will also require gui activation, though which toolkit
305 304 # to use may be chosen automatically based on mpl configuration.
306 305 if self.pylab:
307 306 activate = self.shell.enable_pylab
308 307 if self.pylab == 'auto':
309 308 gui = None
310 309 else:
311 310 gui = self.pylab
312 311 else:
313 312 # Enable only GUI integration, no pylab
314 313 activate = inputhook.enable_gui
315 314
316 315 if gui or self.pylab:
317 316 try:
318 317 self.log.info("Enabling GUI event loop integration, "
319 318 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
320 319 activate(gui)
321 320 except:
322 321 self.log.warn("Error in enabling GUI event loop integration:")
323 322 self.shell.showtraceback()
324 323
325 324 def start(self):
326 325 if self.subapp is not None:
327 326 return self.subapp.start()
328 327 # perform any prexec steps:
329 328 if self.interact:
330 329 self.log.debug("Starting IPython's mainloop...")
331 330 self.shell.mainloop()
332 331 else:
333 332 self.log.debug("IPython not interactive...")
334 333
335 334
336 335 def load_default_config(ipython_dir=None):
337 336 """Load the default config file from the default ipython_dir.
338 337
339 338 This is useful for embedded shells.
340 339 """
341 340 if ipython_dir is None:
342 341 ipython_dir = get_ipython_dir()
343 342 profile_dir = os.path.join(ipython_dir, 'profile_default')
344 343 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
345 344 config = cl.load_config()
346 345 return config
347 346
348 347
349 348 def launch_new_instance():
350 349 """Create and run a full blown IPython instance"""
351 350 app = TerminalIPythonApp.instance()
352 351 app.initialize()
353 352 app.start()
354 353
355 354
356 355 if __name__ == '__main__':
357 356 launch_new_instance()
358 357
@@ -1,446 +1,445 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import errno
25 25 import logging
26 26 import os
27 27 import re
28 28 import signal
29 29
30 30 from subprocess import check_call, CalledProcessError, PIPE
31 31 import zmq
32 32 from zmq.eventloop import ioloop
33 33
34 34 from IPython.config.application import Application, boolean_flag
35 35 from IPython.config.loader import Config
36 36 from IPython.core.application import BaseIPythonApplication
37 37 from IPython.core.profiledir import ProfileDir
38 38 from IPython.utils.daemonize import daemonize
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.utils.traitlets import Int, Unicode, Bool, CFloat, Dict, List
41 41
42 42 from IPython.parallel.apps.baseapp import (
43 43 BaseParallelApplication,
44 44 PIDFileError,
45 45 base_flags, base_aliases
46 46 )
47 47
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Module level variables
51 51 #-----------------------------------------------------------------------------
52 52
53 53
54 54 default_config_file_name = u'ipcluster_config.py'
55 55
56 56
57 57 _description = """Start an IPython cluster for parallel computing.
58 58
59 59 An IPython cluster consists of 1 controller and 1 or more engines.
60 60 This command automates the startup of these processes using a wide
61 61 range of startup methods (SSH, local processes, PBS, mpiexec,
62 62 Windows HPC Server 2008). To start a cluster with 4 engines on your
63 63 local host simply do 'ipcluster start n=4'. For more complex usage
64 64 you will typically do 'ipcluster create profile=mycluster', then edit
65 65 configuration files, followed by 'ipcluster start profile=mycluster n=4'.
66 66 """
67 67
68 68
69 69 # Exit codes for ipcluster
70 70
71 71 # This will be the exit code if the ipcluster appears to be running because
72 72 # a .pid file exists
73 73 ALREADY_STARTED = 10
74 74
75 75
76 76 # This will be the exit code if ipcluster stop is run, but there is not .pid
77 77 # file to be found.
78 78 ALREADY_STOPPED = 11
79 79
80 80 # This will be the exit code if ipcluster engines is run, but there is not .pid
81 81 # file to be found.
82 82 NO_CLUSTER = 12
83 83
84 84
85 85 #-----------------------------------------------------------------------------
86 86 # Main application
87 87 #-----------------------------------------------------------------------------
88 88 start_help = """Start an IPython cluster for parallel computing
89 89
90 90 Start an ipython cluster by its profile name or cluster
91 91 directory. Cluster directories contain configuration, log and
92 92 security related files and are named using the convention
93 93 'profile_<name>' and should be creating using the 'start'
94 94 subcommand of 'ipcluster'. If your cluster directory is in
95 95 the cwd or the ipython directory, you can simply refer to it
96 96 using its profile name, 'ipcluster start n=4 profile=<profile>`,
97 97 otherwise use the 'profile_dir' option.
98 98 """
99 99 stop_help = """Stop a running IPython cluster
100 100
101 101 Stop a running ipython cluster by its profile name or cluster
102 102 directory. Cluster directories are named using the convention
103 103 'profile_<name>'. If your cluster directory is in
104 104 the cwd or the ipython directory, you can simply refer to it
105 105 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
106 106 use the 'profile_dir' option.
107 107 """
108 108 engines_help = """Start engines connected to an existing IPython cluster
109 109
110 110 Start one or more engines to connect to an existing Cluster
111 111 by profile name or cluster directory.
112 112 Cluster directories contain configuration, log and
113 113 security related files and are named using the convention
114 114 'profile_<name>' and should be creating using the 'start'
115 115 subcommand of 'ipcluster'. If your cluster directory is in
116 116 the cwd or the ipython directory, you can simply refer to it
117 117 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
118 118 otherwise use the 'profile_dir' option.
119 119 """
120 120 stop_aliases = dict(
121 121 signal='IPClusterStop.signal',
122 122 profile='BaseIPythonApplication.profile',
123 123 profile_dir='ProfileDir.location',
124 124 )
125 125
126 126 class IPClusterStop(BaseParallelApplication):
127 127 name = u'ipcluster'
128 128 description = stop_help
129 129 config_file_name = Unicode(default_config_file_name)
130 130
131 131 signal = Int(signal.SIGINT, config=True,
132 132 help="signal to use for stopping processes.")
133 133
134 134 aliases = Dict(stop_aliases)
135 135
136 136 def start(self):
137 137 """Start the app for the stop subcommand."""
138 138 try:
139 139 pid = self.get_pid_from_file()
140 140 except PIDFileError:
141 141 self.log.critical(
142 142 'Could not read pid file, cluster is probably not running.'
143 143 )
144 144 # Here I exit with a unusual exit status that other processes
145 145 # can watch for to learn how I existed.
146 146 self.remove_pid_file()
147 147 self.exit(ALREADY_STOPPED)
148 148
149 149 if not self.check_pid(pid):
150 150 self.log.critical(
151 151 'Cluster [pid=%r] is not running.' % pid
152 152 )
153 153 self.remove_pid_file()
154 154 # Here I exit with a unusual exit status that other processes
155 155 # can watch for to learn how I existed.
156 156 self.exit(ALREADY_STOPPED)
157 157
158 158 elif os.name=='posix':
159 159 sig = self.signal
160 160 self.log.info(
161 161 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
162 162 )
163 163 try:
164 164 os.kill(pid, sig)
165 165 except OSError:
166 166 self.log.error("Stopping cluster failed, assuming already dead.",
167 167 exc_info=True)
168 168 self.remove_pid_file()
169 169 elif os.name=='nt':
170 170 try:
171 171 # kill the whole tree
172 172 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
173 173 except (CalledProcessError, OSError):
174 174 self.log.error("Stopping cluster failed, assuming already dead.",
175 175 exc_info=True)
176 176 self.remove_pid_file()
177 177
178 178 engine_aliases = {}
179 179 engine_aliases.update(base_aliases)
180 180 engine_aliases.update(dict(
181 181 n='IPClusterEngines.n',
182 182 elauncher = 'IPClusterEngines.engine_launcher_class',
183 183 ))
184 184 class IPClusterEngines(BaseParallelApplication):
185 185
186 186 name = u'ipcluster'
187 187 description = engines_help
188 188 usage = None
189 189 config_file_name = Unicode(default_config_file_name)
190 190 default_log_level = logging.INFO
191 191 classes = List()
192 192 def _classes_default(self):
193 193 from IPython.parallel.apps import launcher
194 194 launchers = launcher.all_launchers
195 195 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
196 196 return [ProfileDir]+eslaunchers
197 197
198 198 n = Int(2, config=True,
199 199 help="The number of engines to start.")
200 200
201 201 engine_launcher_class = Unicode('LocalEngineSetLauncher',
202 202 config=True,
203 203 help="The class for launching a set of Engines."
204 204 )
205 205 daemonize = Bool(False, config=True,
206 206 help='Daemonize the ipcluster program. This implies --log-to-file')
207 207
208 208 def _daemonize_changed(self, name, old, new):
209 209 if new:
210 210 self.log_to_file = True
211 211
212 212 aliases = Dict(engine_aliases)
213 213 # flags = Dict(flags)
214 214 _stopping = False
215 215
216 216 def initialize(self, argv=None):
217 217 super(IPClusterEngines, self).initialize(argv)
218 218 self.init_signal()
219 219 self.init_launchers()
220 220
221 221 def init_launchers(self):
222 222 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
223 223 self.engine_launcher.on_stop(lambda r: self.loop.stop())
224 224
225 225 def init_signal(self):
226 226 # Setup signals
227 227 signal.signal(signal.SIGINT, self.sigint_handler)
228 228
229 229 def build_launcher(self, clsname):
230 230 """import and instantiate a Launcher based on importstring"""
231 231 if '.' not in clsname:
232 232 # not a module, presume it's the raw name in apps.launcher
233 233 clsname = 'IPython.parallel.apps.launcher.'+clsname
234 234 # print repr(clsname)
235 235 klass = import_item(clsname)
236 236
237 237 launcher = klass(
238 238 work_dir=self.profile_dir.location, config=self.config, log=self.log
239 239 )
240 240 return launcher
241 241
242 242 def start_engines(self):
243 243 self.log.info("Starting %i engines"%self.n)
244 244 self.engine_launcher.start(
245 245 self.n,
246 246 self.profile_dir.location
247 247 )
248 248
249 249 def stop_engines(self):
250 250 self.log.info("Stopping Engines...")
251 251 if self.engine_launcher.running:
252 252 d = self.engine_launcher.stop()
253 253 return d
254 254 else:
255 255 return None
256 256
257 257 def stop_launchers(self, r=None):
258 258 if not self._stopping:
259 259 self._stopping = True
260 260 self.log.error("IPython cluster: stopping")
261 261 self.stop_engines()
262 262 # Wait a few seconds to let things shut down.
263 263 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
264 264 dc.start()
265 265
266 266 def sigint_handler(self, signum, frame):
267 267 self.log.debug("SIGINT received, stopping launchers...")
268 268 self.stop_launchers()
269 269
270 270 def start_logging(self):
271 271 # Remove old log files of the controller and engine
272 272 if self.clean_logs:
273 273 log_dir = self.profile_dir.log_dir
274 274 for f in os.listdir(log_dir):
275 275 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
276 276 os.remove(os.path.join(log_dir, f))
277 277 # This will remove old log files for ipcluster itself
278 278 # super(IPBaseParallelApplication, self).start_logging()
279 279
280 280 def start(self):
281 281 """Start the app for the engines subcommand."""
282 282 self.log.info("IPython cluster: started")
283 283 # First see if the cluster is already running
284 284
285 285 # Now log and daemonize
286 286 self.log.info(
287 287 'Starting engines with [daemon=%r]' % self.daemonize
288 288 )
289 289 # TODO: Get daemonize working on Windows or as a Windows Server.
290 290 if self.daemonize:
291 291 if os.name=='posix':
292 292 daemonize()
293 293
294 294 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
295 295 dc.start()
296 296 # Now write the new pid file AFTER our new forked pid is active.
297 297 # self.write_pid_file()
298 298 try:
299 299 self.loop.start()
300 300 except KeyboardInterrupt:
301 301 pass
302 302 except zmq.ZMQError as e:
303 303 if e.errno == errno.EINTR:
304 304 pass
305 305 else:
306 306 raise
307 307
308 308 start_aliases = {}
309 309 start_aliases.update(engine_aliases)
310 310 start_aliases.update(dict(
311 311 delay='IPClusterStart.delay',
312 312 clean_logs='IPClusterStart.clean_logs',
313 313 ))
314 314
315 315 class IPClusterStart(IPClusterEngines):
316 316
317 317 name = u'ipcluster'
318 318 description = start_help
319 319 default_log_level = logging.INFO
320 320 auto_create = Bool(True, config=True,
321 321 help="whether to create the profile_dir if it doesn't exist")
322 322 classes = List()
323 323 def _classes_default(self,):
324 324 from IPython.parallel.apps import launcher
325 return [ProfileDir]+launcher.all_launchers
325 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
326 326
327 327 clean_logs = Bool(True, config=True,
328 328 help="whether to cleanup old logs before starting")
329 329
330 330 delay = CFloat(1., config=True,
331 331 help="delay (in s) between starting the controller and the engines")
332 332
333 333 controller_launcher_class = Unicode('LocalControllerLauncher',
334 334 config=True,
335 335 help="The class for launching a Controller."
336 336 )
337 337 reset = Bool(False, config=True,
338 338 help="Whether to reset config files as part of '--create'."
339 339 )
340 340
341 341 # flags = Dict(flags)
342 342 aliases = Dict(start_aliases)
343 343
344 344 def init_launchers(self):
345 345 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
346 346 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
347 347 self.controller_launcher.on_stop(self.stop_launchers)
348 348
349 349 def start_controller(self):
350 350 self.controller_launcher.start(
351 351 self.profile_dir.location
352 352 )
353 353
354 354 def stop_controller(self):
355 355 # self.log.info("In stop_controller")
356 356 if self.controller_launcher and self.controller_launcher.running:
357 357 return self.controller_launcher.stop()
358 358
359 359 def stop_launchers(self, r=None):
360 360 if not self._stopping:
361 361 self.stop_controller()
362 362 super(IPClusterStart, self).stop_launchers()
363 363
364 364 def start(self):
365 365 """Start the app for the start subcommand."""
366 366 # First see if the cluster is already running
367 367 try:
368 368 pid = self.get_pid_from_file()
369 369 except PIDFileError:
370 370 pass
371 371 else:
372 372 if self.check_pid(pid):
373 373 self.log.critical(
374 374 'Cluster is already running with [pid=%s]. '
375 375 'use "ipcluster stop" to stop the cluster.' % pid
376 376 )
377 377 # Here I exit with a unusual exit status that other processes
378 378 # can watch for to learn how I existed.
379 379 self.exit(ALREADY_STARTED)
380 380 else:
381 381 self.remove_pid_file()
382 382
383 383
384 384 # Now log and daemonize
385 385 self.log.info(
386 386 'Starting ipcluster with [daemon=%r]' % self.daemonize
387 387 )
388 388 # TODO: Get daemonize working on Windows or as a Windows Server.
389 389 if self.daemonize:
390 390 if os.name=='posix':
391 391 daemonize()
392 392
393 393 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
394 394 dc.start()
395 395 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
396 396 dc.start()
397 397 # Now write the new pid file AFTER our new forked pid is active.
398 398 self.write_pid_file()
399 399 try:
400 400 self.loop.start()
401 401 except KeyboardInterrupt:
402 402 pass
403 403 except zmq.ZMQError as e:
404 404 if e.errno == errno.EINTR:
405 405 pass
406 406 else:
407 407 raise
408 408 finally:
409 409 self.remove_pid_file()
410 410
411 411 base='IPython.parallel.apps.ipclusterapp.IPCluster'
412 412
413 class IPBaseParallelApplication(Application):
413 class IPClusterApp(Application):
414 414 name = u'ipcluster'
415 415 description = _description
416 416
417 subcommands = {'create' : (base+'Create', create_help),
418 'list' : (base+'List', list_help),
417 subcommands = {
419 418 'start' : (base+'Start', start_help),
420 419 'stop' : (base+'Stop', stop_help),
421 420 'engines' : (base+'Engines', engines_help),
422 421 }
423 422
424 423 # no aliases or flags for parent App
425 424 aliases = Dict()
426 425 flags = Dict()
427 426
428 427 def start(self):
429 428 if self.subapp is None:
430 429 print "No subcommand specified! Must specify one of: %s"%(self.subcommands.keys())
431 430 print
432 431 self.print_subcommands()
433 432 self.exit(1)
434 433 else:
435 434 return self.subapp.start()
436 435
437 436 def launch_new_instance():
438 437 """Create and run the IPython cluster."""
439 438 app = IPBaseParallelApplication.instance()
440 439 app.initialize()
441 440 app.start()
442 441
443 442
444 443 if __name__ == '__main__':
445 444 launch_new_instance()
446 445
@@ -1,276 +1,276 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython engine application
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import json
25 25 import os
26 26 import sys
27 27
28 28 import zmq
29 29 from zmq.eventloop import ioloop
30 30
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.parallel.apps.baseapp import BaseParallelApplication
33 33 from IPython.zmq.log import EnginePUBHandler
34 34
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.zmq.session import Session
37 37 from IPython.parallel.engine.engine import EngineFactory
38 38 from IPython.parallel.engine.streamkernel import Kernel
39 39 from IPython.parallel.util import disambiguate_url
40 40
41 41 from IPython.utils.importstring import import_item
42 42 from IPython.utils.traitlets import Bool, Unicode, Dict, List
43 43
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Module level variables
47 47 #-----------------------------------------------------------------------------
48 48
49 49 #: The default config file name for this application
50 50 default_config_file_name = u'ipengine_config.py'
51 51
52 52 _description = """Start an IPython engine for parallel computing.
53 53
54 54 IPython engines run in parallel and perform computations on behalf of a client
55 55 and controller. A controller needs to be started before the engines. The
56 56 engine can be configured using command line options or using a cluster
57 57 directory. Cluster directories contain config, log and security files and are
58 58 usually located in your ipython directory and named as "profile_name".
59 59 See the `profile` and `profile_dir` options for details.
60 60 """
61 61
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # MPI configuration
65 65 #-----------------------------------------------------------------------------
66 66
67 67 mpi4py_init = """from mpi4py import MPI as mpi
68 68 mpi.size = mpi.COMM_WORLD.Get_size()
69 69 mpi.rank = mpi.COMM_WORLD.Get_rank()
70 70 """
71 71
72 72
73 73 pytrilinos_init = """from PyTrilinos import Epetra
74 74 class SimpleStruct:
75 75 pass
76 76 mpi = SimpleStruct()
77 77 mpi.rank = 0
78 78 mpi.size = 0
79 79 """
80 80
81 81 class MPI(Configurable):
82 82 """Configurable for MPI initialization"""
83 83 use = Unicode('', config=True,
84 84 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
85 85 )
86 86
87 87 def _on_use_changed(self, old, new):
88 88 # load default init script if it's not set
89 89 if not self.init_script:
90 90 self.init_script = self.default_inits.get(new, '')
91 91
92 92 init_script = Unicode('', config=True,
93 93 help="Initialization code for MPI")
94 94
95 95 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
96 96 config=True)
97 97
98 98
99 99 #-----------------------------------------------------------------------------
100 100 # Main application
101 101 #-----------------------------------------------------------------------------
102 102
103 103
104 104 class IPEngineApp(BaseParallelApplication):
105 105
106 app_name = Unicode(u'ipengine')
106 name = Unicode(u'ipengine')
107 107 description = Unicode(_description)
108 108 config_file_name = Unicode(default_config_file_name)
109 109 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
110 110
111 111 startup_script = Unicode(u'', config=True,
112 112 help='specify a script to be run at startup')
113 113 startup_command = Unicode('', config=True,
114 114 help='specify a command to be run at startup')
115 115
116 116 url_file = Unicode(u'', config=True,
117 117 help="""The full location of the file containing the connection information for
118 118 the controller. If this is not given, the file must be in the
119 119 security directory of the cluster directory. This location is
120 120 resolved using the `profile` or `profile_dir` options.""",
121 121 )
122 122
123 123 url_file_name = Unicode(u'ipcontroller-engine.json')
124 124 log_url = Unicode('', config=True,
125 125 help="""The URL for the iploggerapp instance, for forwarding
126 126 logging to a central location.""")
127 127
128 128 aliases = Dict(dict(
129 129 file = 'IPEngineApp.url_file',
130 130 c = 'IPEngineApp.startup_command',
131 131 s = 'IPEngineApp.startup_script',
132 132
133 133 ident = 'Session.session',
134 134 user = 'Session.username',
135 135 exec_key = 'Session.keyfile',
136 136
137 137 url = 'EngineFactory.url',
138 138 ip = 'EngineFactory.ip',
139 139 transport = 'EngineFactory.transport',
140 140 port = 'EngineFactory.regport',
141 141 location = 'EngineFactory.location',
142 142
143 143 timeout = 'EngineFactory.timeout',
144 144
145 145 profile = "IPEngineApp.profile",
146 146 profile_dir = 'ProfileDir.location',
147 147
148 148 mpi = 'MPI.use',
149 149
150 150 log_level = 'IPEngineApp.log_level',
151 151 log_url = 'IPEngineApp.log_url'
152 152 ))
153 153
154 154 # def find_key_file(self):
155 155 # """Set the key file.
156 156 #
157 157 # Here we don't try to actually see if it exists for is valid as that
158 158 # is hadled by the connection logic.
159 159 # """
160 160 # config = self.master_config
161 161 # # Find the actual controller key file
162 162 # if not config.Global.key_file:
163 163 # try_this = os.path.join(
164 164 # config.Global.profile_dir,
165 165 # config.Global.security_dir,
166 166 # config.Global.key_file_name
167 167 # )
168 168 # config.Global.key_file = try_this
169 169
170 170 def find_url_file(self):
171 171 """Set the key file.
172 172
173 173 Here we don't try to actually see if it exists for is valid as that
174 174 is hadled by the connection logic.
175 175 """
176 176 config = self.config
177 177 # Find the actual controller key file
178 178 if not self.url_file:
179 179 self.url_file = os.path.join(
180 180 self.profile_dir.security_dir,
181 181 self.url_file_name
182 182 )
183 183 def init_engine(self):
184 184 # This is the working dir by now.
185 185 sys.path.insert(0, '')
186 186 config = self.config
187 187 # print config
188 188 self.find_url_file()
189 189
190 190 # if os.path.exists(config.Global.key_file) and config.Global.secure:
191 191 # config.SessionFactory.exec_key = config.Global.key_file
192 192 if os.path.exists(self.url_file):
193 193 with open(self.url_file) as f:
194 194 d = json.loads(f.read())
195 195 for k,v in d.iteritems():
196 196 if isinstance(v, unicode):
197 197 d[k] = v.encode()
198 198 if d['exec_key']:
199 199 config.Session.key = d['exec_key']
200 200 d['url'] = disambiguate_url(d['url'], d['location'])
201 201 config.EngineFactory.url = d['url']
202 202 config.EngineFactory.location = d['location']
203 203
204 204 try:
205 205 exec_lines = config.Kernel.exec_lines
206 206 except AttributeError:
207 207 config.Kernel.exec_lines = []
208 208 exec_lines = config.Kernel.exec_lines
209 209
210 210 if self.startup_script:
211 211 enc = sys.getfilesystemencoding() or 'utf8'
212 212 cmd="execfile(%r)"%self.startup_script.encode(enc)
213 213 exec_lines.append(cmd)
214 214 if self.startup_command:
215 215 exec_lines.append(self.startup_command)
216 216
217 217 # Create the underlying shell class and Engine
218 218 # shell_class = import_item(self.master_config.Global.shell_class)
219 219 # print self.config
220 220 try:
221 221 self.engine = EngineFactory(config=config, log=self.log)
222 222 except:
223 223 self.log.error("Couldn't start the Engine", exc_info=True)
224 224 self.exit(1)
225 225
226 226 def forward_logging(self):
227 227 if self.log_url:
228 228 self.log.info("Forwarding logging to %s"%self.log_url)
229 229 context = self.engine.context
230 230 lsock = context.socket(zmq.PUB)
231 231 lsock.connect(self.log_url)
232 232 self.log.removeHandler(self._log_handler)
233 233 handler = EnginePUBHandler(self.engine, lsock)
234 234 handler.setLevel(self.log_level)
235 235 self.log.addHandler(handler)
236 236 self._log_handler = handler
237 237 #
238 238 def init_mpi(self):
239 239 global mpi
240 240 self.mpi = MPI(config=self.config)
241 241
242 242 mpi_import_statement = self.mpi.init_script
243 243 if mpi_import_statement:
244 244 try:
245 245 self.log.info("Initializing MPI:")
246 246 self.log.info(mpi_import_statement)
247 247 exec mpi_import_statement in globals()
248 248 except:
249 249 mpi = None
250 250 else:
251 251 mpi = None
252 252
253 253 def initialize(self, argv=None):
254 254 super(IPEngineApp, self).initialize(argv)
255 255 self.init_mpi()
256 256 self.init_engine()
257 257 self.forward_logging()
258 258
259 259 def start(self):
260 260 self.engine.start()
261 261 try:
262 262 self.engine.loop.start()
263 263 except KeyboardInterrupt:
264 264 self.log.critical("Engine Interrupted, shutting down...\n")
265 265
266 266
267 267 def launch_new_instance():
268 268 """Create and run the IPython engine"""
269 269 app = IPEngineApp.instance()
270 270 app.initialize()
271 271 app.start()
272 272
273 273
274 274 if __name__ == '__main__':
275 275 launch_new_instance()
276 276
@@ -1,101 +1,101 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A simple IPython logger application
5 5
6 6 Authors:
7 7
8 8 * MinRK
9 9
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import sys
25 25
26 26 import zmq
27 27
28 28 from IPython.core.profiledir import ProfileDir
29 29 from IPython.utils.traitlets import Bool, Dict, Unicode
30 30
31 31 from IPython.parallel.apps.baseapp import (
32 32 BaseParallelApplication,
33 33 base_aliases
34 34 )
35 35 from IPython.parallel.apps.logwatcher import LogWatcher
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Module level variables
39 39 #-----------------------------------------------------------------------------
40 40
41 41 #: The default config file name for this application
42 42 default_config_file_name = u'iplogger_config.py'
43 43
44 44 _description = """Start an IPython logger for parallel computing.
45 45
46 46 IPython controllers and engines (and your own processes) can broadcast log messages
47 47 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
48 48 logger can be configured using command line options or using a cluster
49 49 directory. Cluster directories contain config, log and security files and are
50 50 usually located in your ipython directory and named as "profile_name".
51 51 See the `profile` and `profile_dir` options for details.
52 52 """
53 53
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Main application
57 57 #-----------------------------------------------------------------------------
58 58 aliases = {}
59 59 aliases.update(base_aliases)
60 60 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
61 61
62 62 class IPLoggerApp(BaseParallelApplication):
63 63
64 name = u'iploggerz'
64 name = u'iplogger'
65 65 description = _description
66 66 config_file_name = Unicode(default_config_file_name)
67 67
68 68 classes = [LogWatcher, ProfileDir]
69 69 aliases = Dict(aliases)
70 70
71 71 def initialize(self, argv=None):
72 72 super(IPLoggerApp, self).initialize(argv)
73 73 self.init_watcher()
74 74
75 75 def init_watcher(self):
76 76 try:
77 77 self.watcher = LogWatcher(config=self.config, log=self.log)
78 78 except:
79 79 self.log.error("Couldn't start the LogWatcher", exc_info=True)
80 80 self.exit(1)
81 81 self.log.info("Listening for log messages on %r"%self.watcher.url)
82 82
83 83
84 84 def start(self):
85 85 self.watcher.start()
86 86 try:
87 87 self.watcher.loop.start()
88 88 except KeyboardInterrupt:
89 89 self.log.critical("Logging Interrupted, shutting down...\n")
90 90
91 91
92 92 def launch_new_instance():
93 93 """Create and run the IPython LogWatcher"""
94 94 app = IPLoggerApp.instance()
95 95 app.initialize()
96 96 app.start()
97 97
98 98
99 99 if __name__ == '__main__':
100 100 launch_new_instance()
101 101
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now