##// END OF EJS Templates
move `--profile-dir` alias to base IPython app...
MinRK -
Show More
@@ -1,372 +1,373 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import atexit
31 import glob
31 import glob
32 import logging
32 import logging
33 import os
33 import os
34 import shutil
34 import shutil
35 import sys
35 import sys
36
36
37 from IPython.config.application import Application, catch_config_error
37 from IPython.config.application import Application, catch_config_error
38 from IPython.config.loader import ConfigFileNotFound
38 from IPython.config.loader import ConfigFileNotFound
39 from IPython.core import release, crashhandler
39 from IPython.core import release, crashhandler
40 from IPython.core.profiledir import ProfileDir, ProfileDirError
40 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Classes and functions
45 # Classes and functions
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Base Application Class
50 # Base Application Class
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # aliases and flags
53 # aliases and flags
54
54
55 base_aliases = {
55 base_aliases = {
56 'profile-dir' : 'ProfileDir.location',
56 'profile' : 'BaseIPythonApplication.profile',
57 'profile' : 'BaseIPythonApplication.profile',
57 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
58 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
58 'log-level' : 'Application.log_level',
59 'log-level' : 'Application.log_level',
59 'config' : 'BaseIPythonApplication.extra_config_file',
60 'config' : 'BaseIPythonApplication.extra_config_file',
60 }
61 }
61
62
62 base_flags = dict(
63 base_flags = dict(
63 debug = ({'Application' : {'log_level' : logging.DEBUG}},
64 debug = ({'Application' : {'log_level' : logging.DEBUG}},
64 "set log level to logging.DEBUG (maximize logging output)"),
65 "set log level to logging.DEBUG (maximize logging output)"),
65 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
66 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
66 "set log level to logging.CRITICAL (minimize logging output)"),
67 "set log level to logging.CRITICAL (minimize logging output)"),
67 init = ({'BaseIPythonApplication' : {
68 init = ({'BaseIPythonApplication' : {
68 'copy_config_files' : True,
69 'copy_config_files' : True,
69 'auto_create' : True}
70 'auto_create' : True}
70 }, """Initialize profile with default config files. This is equivalent
71 }, """Initialize profile with default config files. This is equivalent
71 to running `ipython profile create <profile>` prior to startup.
72 to running `ipython profile create <profile>` prior to startup.
72 """)
73 """)
73 )
74 )
74
75
75
76
76 class BaseIPythonApplication(Application):
77 class BaseIPythonApplication(Application):
77
78
78 name = Unicode(u'ipython')
79 name = Unicode(u'ipython')
79 description = Unicode(u'IPython: an enhanced interactive Python shell.')
80 description = Unicode(u'IPython: an enhanced interactive Python shell.')
80 version = Unicode(release.version)
81 version = Unicode(release.version)
81
82
82 aliases = Dict(base_aliases)
83 aliases = Dict(base_aliases)
83 flags = Dict(base_flags)
84 flags = Dict(base_flags)
84 classes = List([ProfileDir])
85 classes = List([ProfileDir])
85
86
86 # Track whether the config_file has changed,
87 # Track whether the config_file has changed,
87 # because some logic happens only if we aren't using the default.
88 # because some logic happens only if we aren't using the default.
88 config_file_specified = Set()
89 config_file_specified = Set()
89
90
90 config_file_name = Unicode()
91 config_file_name = Unicode()
91 def _config_file_name_default(self):
92 def _config_file_name_default(self):
92 return self.name.replace('-','_') + u'_config.py'
93 return self.name.replace('-','_') + u'_config.py'
93 def _config_file_name_changed(self, name, old, new):
94 def _config_file_name_changed(self, name, old, new):
94 if new != old:
95 if new != old:
95 self.config_file_specified.add(new)
96 self.config_file_specified.add(new)
96
97
97 # The directory that contains IPython's builtin profiles.
98 # The directory that contains IPython's builtin profiles.
98 builtin_profile_dir = Unicode(
99 builtin_profile_dir = Unicode(
99 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
100 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
100 )
101 )
101
102
102 config_file_paths = List(Unicode)
103 config_file_paths = List(Unicode)
103 def _config_file_paths_default(self):
104 def _config_file_paths_default(self):
104 return [os.getcwdu()]
105 return [os.getcwdu()]
105
106
106 extra_config_file = Unicode(config=True,
107 extra_config_file = Unicode(config=True,
107 help="""Path to an extra config file to load.
108 help="""Path to an extra config file to load.
108
109
109 If specified, load this config file in addition to any other IPython config.
110 If specified, load this config file in addition to any other IPython config.
110 """)
111 """)
111 def _extra_config_file_changed(self, name, old, new):
112 def _extra_config_file_changed(self, name, old, new):
112 try:
113 try:
113 self.config_files.remove(old)
114 self.config_files.remove(old)
114 except ValueError:
115 except ValueError:
115 pass
116 pass
116 self.config_file_specified.add(new)
117 self.config_file_specified.add(new)
117 self.config_files.append(new)
118 self.config_files.append(new)
118
119
119 profile = Unicode(u'default', config=True,
120 profile = Unicode(u'default', config=True,
120 help="""The IPython profile to use."""
121 help="""The IPython profile to use."""
121 )
122 )
122
123
123 def _profile_changed(self, name, old, new):
124 def _profile_changed(self, name, old, new):
124 self.builtin_profile_dir = os.path.join(
125 self.builtin_profile_dir = os.path.join(
125 get_ipython_package_dir(), u'config', u'profile', new
126 get_ipython_package_dir(), u'config', u'profile', new
126 )
127 )
127
128
128 ipython_dir = Unicode(get_ipython_dir(), config=True,
129 ipython_dir = Unicode(get_ipython_dir(), config=True,
129 help="""
130 help="""
130 The name of the IPython directory. This directory is used for logging
131 The name of the IPython directory. This directory is used for logging
131 configuration (through profiles), history storage, etc. The default
132 configuration (through profiles), history storage, etc. The default
132 is usually $HOME/.ipython. This options can also be specified through
133 is usually $HOME/.ipython. This options can also be specified through
133 the environment variable IPYTHONDIR.
134 the environment variable IPYTHONDIR.
134 """
135 """
135 )
136 )
136 _in_init_profile_dir = False
137 _in_init_profile_dir = False
137 profile_dir = Instance(ProfileDir)
138 profile_dir = Instance(ProfileDir)
138 def _profile_dir_default(self):
139 def _profile_dir_default(self):
139 # avoid recursion
140 # avoid recursion
140 if self._in_init_profile_dir:
141 if self._in_init_profile_dir:
141 return
142 return
142 # profile_dir requested early, force initialization
143 # profile_dir requested early, force initialization
143 self.init_profile_dir()
144 self.init_profile_dir()
144 return self.profile_dir
145 return self.profile_dir
145
146
146 overwrite = Bool(False, config=True,
147 overwrite = Bool(False, config=True,
147 help="""Whether to overwrite existing config files when copying""")
148 help="""Whether to overwrite existing config files when copying""")
148 auto_create = Bool(False, config=True,
149 auto_create = Bool(False, config=True,
149 help="""Whether to create profile dir if it doesn't exist""")
150 help="""Whether to create profile dir if it doesn't exist""")
150
151
151 config_files = List(Unicode)
152 config_files = List(Unicode)
152 def _config_files_default(self):
153 def _config_files_default(self):
153 return [self.config_file_name]
154 return [self.config_file_name]
154
155
155 copy_config_files = Bool(False, config=True,
156 copy_config_files = Bool(False, config=True,
156 help="""Whether to install the default config files into the profile dir.
157 help="""Whether to install the default config files into the profile dir.
157 If a new profile is being created, and IPython contains config files for that
158 If a new profile is being created, and IPython contains config files for that
158 profile, then they will be staged into the new directory. Otherwise,
159 profile, then they will be staged into the new directory. Otherwise,
159 default config files will be automatically generated.
160 default config files will be automatically generated.
160 """)
161 """)
161
162
162 verbose_crash = Bool(False, config=True,
163 verbose_crash = Bool(False, config=True,
163 help="""Create a massive crash report when IPython encounters what may be an
164 help="""Create a massive crash report when IPython encounters what may be an
164 internal error. The default is to append a short message to the
165 internal error. The default is to append a short message to the
165 usual traceback""")
166 usual traceback""")
166
167
167 # The class to use as the crash handler.
168 # The class to use as the crash handler.
168 crash_handler_class = Type(crashhandler.CrashHandler)
169 crash_handler_class = Type(crashhandler.CrashHandler)
169
170
170 @catch_config_error
171 @catch_config_error
171 def __init__(self, **kwargs):
172 def __init__(self, **kwargs):
172 super(BaseIPythonApplication, self).__init__(**kwargs)
173 super(BaseIPythonApplication, self).__init__(**kwargs)
173 # ensure current working directory exists
174 # ensure current working directory exists
174 try:
175 try:
175 directory = os.getcwdu()
176 directory = os.getcwdu()
176 except:
177 except:
177 # raise exception
178 # raise exception
178 self.log.error("Current working directory doesn't exist.")
179 self.log.error("Current working directory doesn't exist.")
179 raise
180 raise
180
181
181 # ensure even default IPYTHONDIR exists
182 # ensure even default IPYTHONDIR exists
182 if not os.path.exists(self.ipython_dir):
183 if not os.path.exists(self.ipython_dir):
183 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
184 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
184
185
185 #-------------------------------------------------------------------------
186 #-------------------------------------------------------------------------
186 # Various stages of Application creation
187 # Various stages of Application creation
187 #-------------------------------------------------------------------------
188 #-------------------------------------------------------------------------
188
189
189 def init_crash_handler(self):
190 def init_crash_handler(self):
190 """Create a crash handler, typically setting sys.excepthook to it."""
191 """Create a crash handler, typically setting sys.excepthook to it."""
191 self.crash_handler = self.crash_handler_class(self)
192 self.crash_handler = self.crash_handler_class(self)
192 sys.excepthook = self.excepthook
193 sys.excepthook = self.excepthook
193 def unset_crashhandler():
194 def unset_crashhandler():
194 sys.excepthook = sys.__excepthook__
195 sys.excepthook = sys.__excepthook__
195 atexit.register(unset_crashhandler)
196 atexit.register(unset_crashhandler)
196
197
197 def excepthook(self, etype, evalue, tb):
198 def excepthook(self, etype, evalue, tb):
198 """this is sys.excepthook after init_crashhandler
199 """this is sys.excepthook after init_crashhandler
199
200
200 set self.verbose_crash=True to use our full crashhandler, instead of
201 set self.verbose_crash=True to use our full crashhandler, instead of
201 a regular traceback with a short message (crash_handler_lite)
202 a regular traceback with a short message (crash_handler_lite)
202 """
203 """
203
204
204 if self.verbose_crash:
205 if self.verbose_crash:
205 return self.crash_handler(etype, evalue, tb)
206 return self.crash_handler(etype, evalue, tb)
206 else:
207 else:
207 return crashhandler.crash_handler_lite(etype, evalue, tb)
208 return crashhandler.crash_handler_lite(etype, evalue, tb)
208
209
209 def _ipython_dir_changed(self, name, old, new):
210 def _ipython_dir_changed(self, name, old, new):
210 if old in sys.path:
211 if old in sys.path:
211 sys.path.remove(old)
212 sys.path.remove(old)
212 sys.path.append(os.path.abspath(new))
213 sys.path.append(os.path.abspath(new))
213 if not os.path.isdir(new):
214 if not os.path.isdir(new):
214 os.makedirs(new, mode=0o777)
215 os.makedirs(new, mode=0o777)
215 readme = os.path.join(new, 'README')
216 readme = os.path.join(new, 'README')
216 if not os.path.exists(readme):
217 if not os.path.exists(readme):
217 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
218 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
218 shutil.copy(os.path.join(path, 'README'), readme)
219 shutil.copy(os.path.join(path, 'README'), readme)
219 self.log.debug("IPYTHONDIR set to: %s" % new)
220 self.log.debug("IPYTHONDIR set to: %s" % new)
220
221
221 def load_config_file(self, suppress_errors=True):
222 def load_config_file(self, suppress_errors=True):
222 """Load the config file.
223 """Load the config file.
223
224
224 By default, errors in loading config are handled, and a warning
225 By default, errors in loading config are handled, and a warning
225 printed on screen. For testing, the suppress_errors option is set
226 printed on screen. For testing, the suppress_errors option is set
226 to False, so errors will make tests fail.
227 to False, so errors will make tests fail.
227 """
228 """
228 self.log.debug("Searching path %s for config files", self.config_file_paths)
229 self.log.debug("Searching path %s for config files", self.config_file_paths)
229 base_config = 'ipython_config.py'
230 base_config = 'ipython_config.py'
230 self.log.debug("Attempting to load config file: %s" %
231 self.log.debug("Attempting to load config file: %s" %
231 base_config)
232 base_config)
232 try:
233 try:
233 Application.load_config_file(
234 Application.load_config_file(
234 self,
235 self,
235 base_config,
236 base_config,
236 path=self.config_file_paths
237 path=self.config_file_paths
237 )
238 )
238 except ConfigFileNotFound:
239 except ConfigFileNotFound:
239 # ignore errors loading parent
240 # ignore errors loading parent
240 self.log.debug("Config file %s not found", base_config)
241 self.log.debug("Config file %s not found", base_config)
241 pass
242 pass
242
243
243 for config_file_name in self.config_files:
244 for config_file_name in self.config_files:
244 if not config_file_name or config_file_name == base_config:
245 if not config_file_name or config_file_name == base_config:
245 continue
246 continue
246 self.log.debug("Attempting to load config file: %s" %
247 self.log.debug("Attempting to load config file: %s" %
247 self.config_file_name)
248 self.config_file_name)
248 try:
249 try:
249 Application.load_config_file(
250 Application.load_config_file(
250 self,
251 self,
251 config_file_name,
252 config_file_name,
252 path=self.config_file_paths
253 path=self.config_file_paths
253 )
254 )
254 except ConfigFileNotFound:
255 except ConfigFileNotFound:
255 # Only warn if the default config file was NOT being used.
256 # Only warn if the default config file was NOT being used.
256 if config_file_name in self.config_file_specified:
257 if config_file_name in self.config_file_specified:
257 msg = self.log.warn
258 msg = self.log.warn
258 else:
259 else:
259 msg = self.log.debug
260 msg = self.log.debug
260 msg("Config file not found, skipping: %s", config_file_name)
261 msg("Config file not found, skipping: %s", config_file_name)
261 except:
262 except:
262 # For testing purposes.
263 # For testing purposes.
263 if not suppress_errors:
264 if not suppress_errors:
264 raise
265 raise
265 self.log.warn("Error loading config file: %s" %
266 self.log.warn("Error loading config file: %s" %
266 self.config_file_name, exc_info=True)
267 self.config_file_name, exc_info=True)
267
268
268 def init_profile_dir(self):
269 def init_profile_dir(self):
269 """initialize the profile dir"""
270 """initialize the profile dir"""
270 self._in_init_profile_dir = True
271 self._in_init_profile_dir = True
271 if self.profile_dir is not None:
272 if self.profile_dir is not None:
272 # already ran
273 # already ran
273 return
274 return
274 try:
275 try:
275 # location explicitly specified:
276 # location explicitly specified:
276 location = self.config.ProfileDir.location
277 location = self.config.ProfileDir.location
277 except AttributeError:
278 except AttributeError:
278 # location not specified, find by profile name
279 # location not specified, find by profile name
279 try:
280 try:
280 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
281 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
281 except ProfileDirError:
282 except ProfileDirError:
282 # not found, maybe create it (always create default profile)
283 # not found, maybe create it (always create default profile)
283 if self.auto_create or self.profile == 'default':
284 if self.auto_create or self.profile == 'default':
284 try:
285 try:
285 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
286 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
286 except ProfileDirError:
287 except ProfileDirError:
287 self.log.fatal("Could not create profile: %r"%self.profile)
288 self.log.fatal("Could not create profile: %r"%self.profile)
288 self.exit(1)
289 self.exit(1)
289 else:
290 else:
290 self.log.info("Created profile dir: %r"%p.location)
291 self.log.info("Created profile dir: %r"%p.location)
291 else:
292 else:
292 self.log.fatal("Profile %r not found."%self.profile)
293 self.log.fatal("Profile %r not found."%self.profile)
293 self.exit(1)
294 self.exit(1)
294 else:
295 else:
295 self.log.info("Using existing profile dir: %r"%p.location)
296 self.log.info("Using existing profile dir: %r"%p.location)
296 else:
297 else:
297 # location is fully specified
298 # location is fully specified
298 try:
299 try:
299 p = ProfileDir.find_profile_dir(location, self.config)
300 p = ProfileDir.find_profile_dir(location, self.config)
300 except ProfileDirError:
301 except ProfileDirError:
301 # not found, maybe create it
302 # not found, maybe create it
302 if self.auto_create:
303 if self.auto_create:
303 try:
304 try:
304 p = ProfileDir.create_profile_dir(location, self.config)
305 p = ProfileDir.create_profile_dir(location, self.config)
305 except ProfileDirError:
306 except ProfileDirError:
306 self.log.fatal("Could not create profile directory: %r"%location)
307 self.log.fatal("Could not create profile directory: %r"%location)
307 self.exit(1)
308 self.exit(1)
308 else:
309 else:
309 self.log.info("Creating new profile dir: %r"%location)
310 self.log.info("Creating new profile dir: %r"%location)
310 else:
311 else:
311 self.log.fatal("Profile directory %r not found."%location)
312 self.log.fatal("Profile directory %r not found."%location)
312 self.exit(1)
313 self.exit(1)
313 else:
314 else:
314 self.log.info("Using existing profile dir: %r"%location)
315 self.log.info("Using existing profile dir: %r"%location)
315
316
316 self.profile_dir = p
317 self.profile_dir = p
317 self.config_file_paths.append(p.location)
318 self.config_file_paths.append(p.location)
318 self._in_init_profile_dir = False
319 self._in_init_profile_dir = False
319
320
320 def init_config_files(self):
321 def init_config_files(self):
321 """[optionally] copy default config files into profile dir."""
322 """[optionally] copy default config files into profile dir."""
322 # copy config files
323 # copy config files
323 path = self.builtin_profile_dir
324 path = self.builtin_profile_dir
324 if self.copy_config_files:
325 if self.copy_config_files:
325 src = self.profile
326 src = self.profile
326
327
327 cfg = self.config_file_name
328 cfg = self.config_file_name
328 if path and os.path.exists(os.path.join(path, cfg)):
329 if path and os.path.exists(os.path.join(path, cfg)):
329 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
330 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
330 cfg, src, self.profile_dir.location, self.overwrite)
331 cfg, src, self.profile_dir.location, self.overwrite)
331 )
332 )
332 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
333 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
333 else:
334 else:
334 self.stage_default_config_file()
335 self.stage_default_config_file()
335 else:
336 else:
336 # Still stage *bundled* config files, but not generated ones
337 # Still stage *bundled* config files, but not generated ones
337 # This is necessary for `ipython profile=sympy` to load the profile
338 # This is necessary for `ipython profile=sympy` to load the profile
338 # on the first go
339 # on the first go
339 files = glob.glob(os.path.join(path, '*.py'))
340 files = glob.glob(os.path.join(path, '*.py'))
340 for fullpath in files:
341 for fullpath in files:
341 cfg = os.path.basename(fullpath)
342 cfg = os.path.basename(fullpath)
342 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
343 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
343 # file was copied
344 # file was copied
344 self.log.warn("Staging bundled %s from %s into %r"%(
345 self.log.warn("Staging bundled %s from %s into %r"%(
345 cfg, self.profile, self.profile_dir.location)
346 cfg, self.profile, self.profile_dir.location)
346 )
347 )
347
348
348
349
349 def stage_default_config_file(self):
350 def stage_default_config_file(self):
350 """auto generate default config file, and stage it into the profile."""
351 """auto generate default config file, and stage it into the profile."""
351 s = self.generate_config_file()
352 s = self.generate_config_file()
352 fname = os.path.join(self.profile_dir.location, self.config_file_name)
353 fname = os.path.join(self.profile_dir.location, self.config_file_name)
353 if self.overwrite or not os.path.exists(fname):
354 if self.overwrite or not os.path.exists(fname):
354 self.log.warn("Generating default config file: %r"%(fname))
355 self.log.warn("Generating default config file: %r"%(fname))
355 with open(fname, 'w') as f:
356 with open(fname, 'w') as f:
356 f.write(s)
357 f.write(s)
357
358
358 @catch_config_error
359 @catch_config_error
359 def initialize(self, argv=None):
360 def initialize(self, argv=None):
360 # don't hook up crash handler before parsing command-line
361 # don't hook up crash handler before parsing command-line
361 self.parse_command_line(argv)
362 self.parse_command_line(argv)
362 self.init_crash_handler()
363 self.init_crash_handler()
363 if self.subapp is not None:
364 if self.subapp is not None:
364 # stop here if subapp is taking over
365 # stop here if subapp is taking over
365 return
366 return
366 cl_config = self.config
367 cl_config = self.config
367 self.init_profile_dir()
368 self.init_profile_dir()
368 self.init_config_files()
369 self.init_config_files()
369 self.load_config_file()
370 self.load_config_file()
370 # enforce cl-opts override configfile opts:
371 # enforce cl-opts override configfile opts:
371 self.update_config(cl_config)
372 self.update_config(cl_config)
372
373
@@ -1,277 +1,276 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 The Base Application class for IPython.parallel apps
3 The Base Application class for IPython.parallel apps
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Min RK
8 * Min RK
9
9
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from __future__ import with_statement
23 from __future__ import with_statement
24
24
25 import os
25 import os
26 import logging
26 import logging
27 import re
27 import re
28 import sys
28 import sys
29
29
30 from subprocess import Popen, PIPE
30 from subprocess import Popen, PIPE
31
31
32 from IPython.config.application import catch_config_error, LevelFormatter
32 from IPython.config.application import catch_config_error, LevelFormatter
33 from IPython.core import release
33 from IPython.core import release
34 from IPython.core.crashhandler import CrashHandler
34 from IPython.core.crashhandler import CrashHandler
35 from IPython.core.application import (
35 from IPython.core.application import (
36 BaseIPythonApplication,
36 BaseIPythonApplication,
37 base_aliases as base_ip_aliases,
37 base_aliases as base_ip_aliases,
38 base_flags as base_ip_flags
38 base_flags as base_ip_flags
39 )
39 )
40 from IPython.utils.path import expand_path
40 from IPython.utils.path import expand_path
41
41
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Module errors
45 # Module errors
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class PIDFileError(Exception):
48 class PIDFileError(Exception):
49 pass
49 pass
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Crash handler for this application
53 # Crash handler for this application
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 class ParallelCrashHandler(CrashHandler):
56 class ParallelCrashHandler(CrashHandler):
57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
58
58
59 def __init__(self, app):
59 def __init__(self, app):
60 contact_name = release.authors['Min'][0]
60 contact_name = release.authors['Min'][0]
61 contact_email = release.author_email
61 contact_email = release.author_email
62 bug_tracker = 'https://github.com/ipython/ipython/issues'
62 bug_tracker = 'https://github.com/ipython/ipython/issues'
63 super(ParallelCrashHandler,self).__init__(
63 super(ParallelCrashHandler,self).__init__(
64 app, contact_name, contact_email, bug_tracker
64 app, contact_name, contact_email, bug_tracker
65 )
65 )
66
66
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Main application
69 # Main application
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 base_aliases = {}
71 base_aliases = {}
72 base_aliases.update(base_ip_aliases)
72 base_aliases.update(base_ip_aliases)
73 base_aliases.update({
73 base_aliases.update({
74 'profile-dir' : 'ProfileDir.location',
75 'work-dir' : 'BaseParallelApplication.work_dir',
74 'work-dir' : 'BaseParallelApplication.work_dir',
76 'log-to-file' : 'BaseParallelApplication.log_to_file',
75 'log-to-file' : 'BaseParallelApplication.log_to_file',
77 'clean-logs' : 'BaseParallelApplication.clean_logs',
76 'clean-logs' : 'BaseParallelApplication.clean_logs',
78 'log-url' : 'BaseParallelApplication.log_url',
77 'log-url' : 'BaseParallelApplication.log_url',
79 'cluster-id' : 'BaseParallelApplication.cluster_id',
78 'cluster-id' : 'BaseParallelApplication.cluster_id',
80 })
79 })
81
80
82 base_flags = {
81 base_flags = {
83 'log-to-file' : (
82 'log-to-file' : (
84 {'BaseParallelApplication' : {'log_to_file' : True}},
83 {'BaseParallelApplication' : {'log_to_file' : True}},
85 "send log output to a file"
84 "send log output to a file"
86 )
85 )
87 }
86 }
88 base_flags.update(base_ip_flags)
87 base_flags.update(base_ip_flags)
89
88
90 class BaseParallelApplication(BaseIPythonApplication):
89 class BaseParallelApplication(BaseIPythonApplication):
91 """The base Application for IPython.parallel apps
90 """The base Application for IPython.parallel apps
92
91
93 Principle extensions to BaseIPyythonApplication:
92 Principle extensions to BaseIPyythonApplication:
94
93
95 * work_dir
94 * work_dir
96 * remote logging via pyzmq
95 * remote logging via pyzmq
97 * IOLoop instance
96 * IOLoop instance
98 """
97 """
99
98
100 crash_handler_class = ParallelCrashHandler
99 crash_handler_class = ParallelCrashHandler
101
100
102 def _log_level_default(self):
101 def _log_level_default(self):
103 # temporarily override default_log_level to INFO
102 # temporarily override default_log_level to INFO
104 return logging.INFO
103 return logging.INFO
105
104
106 def _log_format_default(self):
105 def _log_format_default(self):
107 """override default log format to include time"""
106 """override default log format to include time"""
108 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
107 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
109
108
110 work_dir = Unicode(os.getcwdu(), config=True,
109 work_dir = Unicode(os.getcwdu(), config=True,
111 help='Set the working dir for the process.'
110 help='Set the working dir for the process.'
112 )
111 )
113 def _work_dir_changed(self, name, old, new):
112 def _work_dir_changed(self, name, old, new):
114 self.work_dir = unicode(expand_path(new))
113 self.work_dir = unicode(expand_path(new))
115
114
116 log_to_file = Bool(config=True,
115 log_to_file = Bool(config=True,
117 help="whether to log to a file")
116 help="whether to log to a file")
118
117
119 clean_logs = Bool(False, config=True,
118 clean_logs = Bool(False, config=True,
120 help="whether to cleanup old logfiles before starting")
119 help="whether to cleanup old logfiles before starting")
121
120
122 log_url = Unicode('', config=True,
121 log_url = Unicode('', config=True,
123 help="The ZMQ URL of the iplogger to aggregate logging.")
122 help="The ZMQ URL of the iplogger to aggregate logging.")
124
123
125 cluster_id = Unicode('', config=True,
124 cluster_id = Unicode('', config=True,
126 help="""String id to add to runtime files, to prevent name collisions when
125 help="""String id to add to runtime files, to prevent name collisions when
127 using multiple clusters with a single profile simultaneously.
126 using multiple clusters with a single profile simultaneously.
128
127
129 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
128 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
130
129
131 Since this is text inserted into filenames, typical recommendations apply:
130 Since this is text inserted into filenames, typical recommendations apply:
132 Simple character strings are ideal, and spaces are not recommended (but should
131 Simple character strings are ideal, and spaces are not recommended (but should
133 generally work).
132 generally work).
134 """
133 """
135 )
134 )
136 def _cluster_id_changed(self, name, old, new):
135 def _cluster_id_changed(self, name, old, new):
137 self.name = self.__class__.name
136 self.name = self.__class__.name
138 if new:
137 if new:
139 self.name += '-%s'%new
138 self.name += '-%s'%new
140
139
141 def _config_files_default(self):
140 def _config_files_default(self):
142 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
141 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
143
142
144 loop = Instance('zmq.eventloop.ioloop.IOLoop')
143 loop = Instance('zmq.eventloop.ioloop.IOLoop')
145 def _loop_default(self):
144 def _loop_default(self):
146 from zmq.eventloop.ioloop import IOLoop
145 from zmq.eventloop.ioloop import IOLoop
147 return IOLoop.instance()
146 return IOLoop.instance()
148
147
149 aliases = Dict(base_aliases)
148 aliases = Dict(base_aliases)
150 flags = Dict(base_flags)
149 flags = Dict(base_flags)
151
150
152 @catch_config_error
151 @catch_config_error
153 def initialize(self, argv=None):
152 def initialize(self, argv=None):
154 """initialize the app"""
153 """initialize the app"""
155 super(BaseParallelApplication, self).initialize(argv)
154 super(BaseParallelApplication, self).initialize(argv)
156 self.to_work_dir()
155 self.to_work_dir()
157 self.reinit_logging()
156 self.reinit_logging()
158
157
159 def to_work_dir(self):
158 def to_work_dir(self):
160 wd = self.work_dir
159 wd = self.work_dir
161 if unicode(wd) != os.getcwdu():
160 if unicode(wd) != os.getcwdu():
162 os.chdir(wd)
161 os.chdir(wd)
163 self.log.info("Changing to working dir: %s" % wd)
162 self.log.info("Changing to working dir: %s" % wd)
164 # This is the working dir by now.
163 # This is the working dir by now.
165 sys.path.insert(0, '')
164 sys.path.insert(0, '')
166
165
167 def reinit_logging(self):
166 def reinit_logging(self):
168 # Remove old log files
167 # Remove old log files
169 log_dir = self.profile_dir.log_dir
168 log_dir = self.profile_dir.log_dir
170 if self.clean_logs:
169 if self.clean_logs:
171 for f in os.listdir(log_dir):
170 for f in os.listdir(log_dir):
172 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
171 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
173 try:
172 try:
174 os.remove(os.path.join(log_dir, f))
173 os.remove(os.path.join(log_dir, f))
175 except (OSError, IOError):
174 except (OSError, IOError):
176 # probably just conflict from sibling process
175 # probably just conflict from sibling process
177 # already removing it
176 # already removing it
178 pass
177 pass
179 if self.log_to_file:
178 if self.log_to_file:
180 # Start logging to the new log file
179 # Start logging to the new log file
181 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
182 logfile = os.path.join(log_dir, log_filename)
181 logfile = os.path.join(log_dir, log_filename)
183 open_log_file = open(logfile, 'w')
182 open_log_file = open(logfile, 'w')
184 else:
183 else:
185 open_log_file = None
184 open_log_file = None
186 if open_log_file is not None:
185 if open_log_file is not None:
187 while self.log.handlers:
186 while self.log.handlers:
188 self.log.removeHandler(self.log.handlers[0])
187 self.log.removeHandler(self.log.handlers[0])
189 self._log_handler = logging.StreamHandler(open_log_file)
188 self._log_handler = logging.StreamHandler(open_log_file)
190 self.log.addHandler(self._log_handler)
189 self.log.addHandler(self._log_handler)
191 else:
190 else:
192 self._log_handler = self.log.handlers[0]
191 self._log_handler = self.log.handlers[0]
193 # Add timestamps to log format:
192 # Add timestamps to log format:
194 self._log_formatter = LevelFormatter(self.log_format,
193 self._log_formatter = LevelFormatter(self.log_format,
195 datefmt=self.log_datefmt)
194 datefmt=self.log_datefmt)
196 self._log_handler.setFormatter(self._log_formatter)
195 self._log_handler.setFormatter(self._log_formatter)
197 # do not propagate log messages to root logger
196 # do not propagate log messages to root logger
198 # ipcluster app will sometimes print duplicate messages during shutdown
197 # ipcluster app will sometimes print duplicate messages during shutdown
199 # if this is 1 (default):
198 # if this is 1 (default):
200 self.log.propagate = False
199 self.log.propagate = False
201
200
202 def write_pid_file(self, overwrite=False):
201 def write_pid_file(self, overwrite=False):
203 """Create a .pid file in the pid_dir with my pid.
202 """Create a .pid file in the pid_dir with my pid.
204
203
205 This must be called after pre_construct, which sets `self.pid_dir`.
204 This must be called after pre_construct, which sets `self.pid_dir`.
206 This raises :exc:`PIDFileError` if the pid file exists already.
205 This raises :exc:`PIDFileError` if the pid file exists already.
207 """
206 """
208 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
207 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
209 if os.path.isfile(pid_file):
208 if os.path.isfile(pid_file):
210 pid = self.get_pid_from_file()
209 pid = self.get_pid_from_file()
211 if not overwrite:
210 if not overwrite:
212 raise PIDFileError(
211 raise PIDFileError(
213 'The pid file [%s] already exists. \nThis could mean that this '
212 'The pid file [%s] already exists. \nThis could mean that this '
214 'server is already running with [pid=%s].' % (pid_file, pid)
213 'server is already running with [pid=%s].' % (pid_file, pid)
215 )
214 )
216 with open(pid_file, 'w') as f:
215 with open(pid_file, 'w') as f:
217 self.log.info("Creating pid file: %s" % pid_file)
216 self.log.info("Creating pid file: %s" % pid_file)
218 f.write(repr(os.getpid())+'\n')
217 f.write(repr(os.getpid())+'\n')
219
218
220 def remove_pid_file(self):
219 def remove_pid_file(self):
221 """Remove the pid file.
220 """Remove the pid file.
222
221
223 This should be called at shutdown by registering a callback with
222 This should be called at shutdown by registering a callback with
224 :func:`reactor.addSystemEventTrigger`. This needs to return
223 :func:`reactor.addSystemEventTrigger`. This needs to return
225 ``None``.
224 ``None``.
226 """
225 """
227 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
228 if os.path.isfile(pid_file):
227 if os.path.isfile(pid_file):
229 try:
228 try:
230 self.log.info("Removing pid file: %s" % pid_file)
229 self.log.info("Removing pid file: %s" % pid_file)
231 os.remove(pid_file)
230 os.remove(pid_file)
232 except:
231 except:
233 self.log.warn("Error removing the pid file: %s" % pid_file)
232 self.log.warn("Error removing the pid file: %s" % pid_file)
234
233
235 def get_pid_from_file(self):
234 def get_pid_from_file(self):
236 """Get the pid from the pid file.
235 """Get the pid from the pid file.
237
236
238 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
237 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
239 """
238 """
240 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
239 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
241 if os.path.isfile(pid_file):
240 if os.path.isfile(pid_file):
242 with open(pid_file, 'r') as f:
241 with open(pid_file, 'r') as f:
243 s = f.read().strip()
242 s = f.read().strip()
244 try:
243 try:
245 pid = int(s)
244 pid = int(s)
246 except:
245 except:
247 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
246 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
248 return pid
247 return pid
249 else:
248 else:
250 raise PIDFileError('pid file not found: %s' % pid_file)
249 raise PIDFileError('pid file not found: %s' % pid_file)
251
250
252 def check_pid(self, pid):
251 def check_pid(self, pid):
253 if os.name == 'nt':
252 if os.name == 'nt':
254 try:
253 try:
255 import ctypes
254 import ctypes
256 # returns 0 if no such process (of ours) exists
255 # returns 0 if no such process (of ours) exists
257 # positive int otherwise
256 # positive int otherwise
258 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
257 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
259 except Exception:
258 except Exception:
260 self.log.warn(
259 self.log.warn(
261 "Could not determine whether pid %i is running via `OpenProcess`. "
260 "Could not determine whether pid %i is running via `OpenProcess`. "
262 " Making the likely assumption that it is."%pid
261 " Making the likely assumption that it is."%pid
263 )
262 )
264 return True
263 return True
265 return bool(p)
264 return bool(p)
266 else:
265 else:
267 try:
266 try:
268 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
267 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
269 output,_ = p.communicate()
268 output,_ = p.communicate()
270 except OSError:
269 except OSError:
271 self.log.warn(
270 self.log.warn(
272 "Could not determine whether pid %i is running via `ps x`. "
271 "Could not determine whether pid %i is running via `ps x`. "
273 " Making the likely assumption that it is."%pid
272 " Making the likely assumption that it is."%pid
274 )
273 )
275 return True
274 return True
276 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
275 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
277 return pid in pids
276 return pid in pids
General Comments 0
You need to be logged in to leave comments. Login now