##// END OF EJS Templates
Don't rely upon traitlets copying self.config...
Min RK -
Show More
@@ -1,455 +1,458 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating configurables.
6 handling configuration and creating configurables.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import atexit
15 import atexit
16 from copy import deepcopy
16 import glob
17 import glob
17 import logging
18 import logging
18 import os
19 import os
19 import shutil
20 import shutil
20 import sys
21 import sys
21
22
22 from traitlets.config.application import Application, catch_config_error
23 from traitlets.config.application import Application, catch_config_error
23 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 from IPython.core import release, crashhandler
25 from IPython.core import release, crashhandler
25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 from IPython.paths import get_ipython_dir, get_ipython_package_dir
27 from IPython.paths import get_ipython_dir, get_ipython_package_dir
27 from IPython.utils.path import ensure_dir_exists
28 from IPython.utils.path import ensure_dir_exists
28 from IPython.utils import py3compat
29 from IPython.utils import py3compat
29 from traitlets import (
30 from traitlets import (
30 List, Unicode, Type, Bool, Dict, Set, Instance, Undefined,
31 List, Unicode, Type, Bool, Dict, Set, Instance, Undefined,
31 default, observe,
32 default, observe,
32 )
33 )
33
34
34 if os.name == 'nt':
35 if os.name == 'nt':
35 programdata = os.environ.get('PROGRAMDATA', None)
36 programdata = os.environ.get('PROGRAMDATA', None)
36 if programdata:
37 if programdata:
37 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
38 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
38 else: # PROGRAMDATA is not defined by default on XP.
39 else: # PROGRAMDATA is not defined by default on XP.
39 SYSTEM_CONFIG_DIRS = []
40 SYSTEM_CONFIG_DIRS = []
40 else:
41 else:
41 SYSTEM_CONFIG_DIRS = [
42 SYSTEM_CONFIG_DIRS = [
42 "/usr/local/etc/ipython",
43 "/usr/local/etc/ipython",
43 "/etc/ipython",
44 "/etc/ipython",
44 ]
45 ]
45
46
46 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
47 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
47 if _envvar in {None, ''}:
48 if _envvar in {None, ''}:
48 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
49 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
49 else:
50 else:
50 if _envvar.lower() in {'1','true'}:
51 if _envvar.lower() in {'1','true'}:
51 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
52 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
52 elif _envvar.lower() in {'0','false'} :
53 elif _envvar.lower() in {'0','false'} :
53 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
54 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
54 else:
55 else:
55 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
56 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
56
57
57 # aliases and flags
58 # aliases and flags
58
59
59 base_aliases = {
60 base_aliases = {
60 'profile-dir' : 'ProfileDir.location',
61 'profile-dir' : 'ProfileDir.location',
61 'profile' : 'BaseIPythonApplication.profile',
62 'profile' : 'BaseIPythonApplication.profile',
62 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
63 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
63 'log-level' : 'Application.log_level',
64 'log-level' : 'Application.log_level',
64 'config' : 'BaseIPythonApplication.extra_config_file',
65 'config' : 'BaseIPythonApplication.extra_config_file',
65 }
66 }
66
67
67 base_flags = dict(
68 base_flags = dict(
68 debug = ({'Application' : {'log_level' : logging.DEBUG}},
69 debug = ({'Application' : {'log_level' : logging.DEBUG}},
69 "set log level to logging.DEBUG (maximize logging output)"),
70 "set log level to logging.DEBUG (maximize logging output)"),
70 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
71 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
71 "set log level to logging.CRITICAL (minimize logging output)"),
72 "set log level to logging.CRITICAL (minimize logging output)"),
72 init = ({'BaseIPythonApplication' : {
73 init = ({'BaseIPythonApplication' : {
73 'copy_config_files' : True,
74 'copy_config_files' : True,
74 'auto_create' : True}
75 'auto_create' : True}
75 }, """Initialize profile with default config files. This is equivalent
76 }, """Initialize profile with default config files. This is equivalent
76 to running `ipython profile create <profile>` prior to startup.
77 to running `ipython profile create <profile>` prior to startup.
77 """)
78 """)
78 )
79 )
79
80
80 class ProfileAwareConfigLoader(PyFileConfigLoader):
81 class ProfileAwareConfigLoader(PyFileConfigLoader):
81 """A Python file config loader that is aware of IPython profiles."""
82 """A Python file config loader that is aware of IPython profiles."""
82 def load_subconfig(self, fname, path=None, profile=None):
83 def load_subconfig(self, fname, path=None, profile=None):
83 if profile is not None:
84 if profile is not None:
84 try:
85 try:
85 profile_dir = ProfileDir.find_profile_dir_by_name(
86 profile_dir = ProfileDir.find_profile_dir_by_name(
86 get_ipython_dir(),
87 get_ipython_dir(),
87 profile,
88 profile,
88 )
89 )
89 except ProfileDirError:
90 except ProfileDirError:
90 return
91 return
91 path = profile_dir.location
92 path = profile_dir.location
92 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
93 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
93
94
94 class BaseIPythonApplication(Application):
95 class BaseIPythonApplication(Application):
95
96
96 name = Unicode(u'ipython')
97 name = Unicode(u'ipython')
97 description = Unicode(u'IPython: an enhanced interactive Python shell.')
98 description = Unicode(u'IPython: an enhanced interactive Python shell.')
98 version = Unicode(release.version)
99 version = Unicode(release.version)
99
100
100 aliases = Dict(base_aliases)
101 aliases = Dict(base_aliases)
101 flags = Dict(base_flags)
102 flags = Dict(base_flags)
102 classes = List([ProfileDir])
103 classes = List([ProfileDir])
103
104
104 # enable `load_subconfig('cfg.py', profile='name')`
105 # enable `load_subconfig('cfg.py', profile='name')`
105 python_config_loader_class = ProfileAwareConfigLoader
106 python_config_loader_class = ProfileAwareConfigLoader
106
107
107 # Track whether the config_file has changed,
108 # Track whether the config_file has changed,
108 # because some logic happens only if we aren't using the default.
109 # because some logic happens only if we aren't using the default.
109 config_file_specified = Set()
110 config_file_specified = Set()
110
111
111 config_file_name = Unicode()
112 config_file_name = Unicode()
112 @default('config_file_name')
113 @default('config_file_name')
113 def _config_file_name_default(self):
114 def _config_file_name_default(self):
114 return self.name.replace('-','_') + u'_config.py'
115 return self.name.replace('-','_') + u'_config.py'
115 @observe('config_file_name')
116 @observe('config_file_name')
116 def _config_file_name_changed(self, change):
117 def _config_file_name_changed(self, change):
117 if change['new'] != change['old']:
118 if change['new'] != change['old']:
118 self.config_file_specified.add(change['new'])
119 self.config_file_specified.add(change['new'])
119
120
120 # The directory that contains IPython's builtin profiles.
121 # The directory that contains IPython's builtin profiles.
121 builtin_profile_dir = Unicode(
122 builtin_profile_dir = Unicode(
122 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
123 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
123 )
124 )
124
125
125 config_file_paths = List(Unicode())
126 config_file_paths = List(Unicode())
126 @default('config_file_paths')
127 @default('config_file_paths')
127 def _config_file_paths_default(self):
128 def _config_file_paths_default(self):
128 return [py3compat.getcwd()]
129 return [py3compat.getcwd()]
129
130
130 extra_config_file = Unicode(
131 extra_config_file = Unicode(
131 help="""Path to an extra config file to load.
132 help="""Path to an extra config file to load.
132
133
133 If specified, load this config file in addition to any other IPython config.
134 If specified, load this config file in addition to any other IPython config.
134 """).tag(config=True)
135 """).tag(config=True)
135 @observe('extra_config_file')
136 @observe('extra_config_file')
136 def _extra_config_file_changed(self, change):
137 def _extra_config_file_changed(self, change):
137 old = change['old']
138 old = change['old']
138 new = change['new']
139 new = change['new']
139 try:
140 try:
140 self.config_files.remove(old)
141 self.config_files.remove(old)
141 except ValueError:
142 except ValueError:
142 pass
143 pass
143 self.config_file_specified.add(new)
144 self.config_file_specified.add(new)
144 self.config_files.append(new)
145 self.config_files.append(new)
145
146
146 profile = Unicode(u'default',
147 profile = Unicode(u'default',
147 help="""The IPython profile to use."""
148 help="""The IPython profile to use."""
148 ).tag(config=True)
149 ).tag(config=True)
149
150
150 @observe('profile')
151 @observe('profile')
151 def _profile_changed(self, change):
152 def _profile_changed(self, change):
152 self.builtin_profile_dir = os.path.join(
153 self.builtin_profile_dir = os.path.join(
153 get_ipython_package_dir(), u'config', u'profile', change['new']
154 get_ipython_package_dir(), u'config', u'profile', change['new']
154 )
155 )
155
156
156 ipython_dir = Unicode(
157 ipython_dir = Unicode(
157 help="""
158 help="""
158 The name of the IPython directory. This directory is used for logging
159 The name of the IPython directory. This directory is used for logging
159 configuration (through profiles), history storage, etc. The default
160 configuration (through profiles), history storage, etc. The default
160 is usually $HOME/.ipython. This option can also be specified through
161 is usually $HOME/.ipython. This option can also be specified through
161 the environment variable IPYTHONDIR.
162 the environment variable IPYTHONDIR.
162 """
163 """
163 ).tag(config=True)
164 ).tag(config=True)
164 @default('ipython_dir')
165 @default('ipython_dir')
165 def _ipython_dir_default(self):
166 def _ipython_dir_default(self):
166 d = get_ipython_dir()
167 d = get_ipython_dir()
167 self._ipython_dir_changed({
168 self._ipython_dir_changed({
168 'name': 'ipython_dir',
169 'name': 'ipython_dir',
169 'old': d,
170 'old': d,
170 'new': d,
171 'new': d,
171 })
172 })
172 return d
173 return d
173
174
174 _in_init_profile_dir = False
175 _in_init_profile_dir = False
175 profile_dir = Instance(ProfileDir, allow_none=True)
176 profile_dir = Instance(ProfileDir, allow_none=True)
176 @default('profile_dir')
177 @default('profile_dir')
177 def _profile_dir_default(self):
178 def _profile_dir_default(self):
178 # avoid recursion
179 # avoid recursion
179 if self._in_init_profile_dir:
180 if self._in_init_profile_dir:
180 return
181 return
181 # profile_dir requested early, force initialization
182 # profile_dir requested early, force initialization
182 self.init_profile_dir()
183 self.init_profile_dir()
183 return self.profile_dir
184 return self.profile_dir
184
185
185 overwrite = Bool(False,
186 overwrite = Bool(False,
186 help="""Whether to overwrite existing config files when copying"""
187 help="""Whether to overwrite existing config files when copying"""
187 ).tag(config=True)
188 ).tag(config=True)
188 auto_create = Bool(False,
189 auto_create = Bool(False,
189 help="""Whether to create profile dir if it doesn't exist"""
190 help="""Whether to create profile dir if it doesn't exist"""
190 ).tag(config=True)
191 ).tag(config=True)
191
192
192 config_files = List(Unicode())
193 config_files = List(Unicode())
193 @default('config_files')
194 @default('config_files')
194 def _config_files_default(self):
195 def _config_files_default(self):
195 return [self.config_file_name]
196 return [self.config_file_name]
196
197
197 copy_config_files = Bool(False,
198 copy_config_files = Bool(False,
198 help="""Whether to install the default config files into the profile dir.
199 help="""Whether to install the default config files into the profile dir.
199 If a new profile is being created, and IPython contains config files for that
200 If a new profile is being created, and IPython contains config files for that
200 profile, then they will be staged into the new directory. Otherwise,
201 profile, then they will be staged into the new directory. Otherwise,
201 default config files will be automatically generated.
202 default config files will be automatically generated.
202 """).tag(config=True)
203 """).tag(config=True)
203
204
204 verbose_crash = Bool(False,
205 verbose_crash = Bool(False,
205 help="""Create a massive crash report when IPython encounters what may be an
206 help="""Create a massive crash report when IPython encounters what may be an
206 internal error. The default is to append a short message to the
207 internal error. The default is to append a short message to the
207 usual traceback""").tag(config=True)
208 usual traceback""").tag(config=True)
208
209
209 # The class to use as the crash handler.
210 # The class to use as the crash handler.
210 crash_handler_class = Type(crashhandler.CrashHandler)
211 crash_handler_class = Type(crashhandler.CrashHandler)
211
212
212 @catch_config_error
213 @catch_config_error
213 def __init__(self, **kwargs):
214 def __init__(self, **kwargs):
214 super(BaseIPythonApplication, self).__init__(**kwargs)
215 super(BaseIPythonApplication, self).__init__(**kwargs)
215 # ensure current working directory exists
216 # ensure current working directory exists
216 try:
217 try:
217 py3compat.getcwd()
218 py3compat.getcwd()
218 except:
219 except:
219 # exit if cwd doesn't exist
220 # exit if cwd doesn't exist
220 self.log.error("Current working directory doesn't exist.")
221 self.log.error("Current working directory doesn't exist.")
221 self.exit(1)
222 self.exit(1)
222
223
223 #-------------------------------------------------------------------------
224 #-------------------------------------------------------------------------
224 # Various stages of Application creation
225 # Various stages of Application creation
225 #-------------------------------------------------------------------------
226 #-------------------------------------------------------------------------
226
227
227 deprecated_subcommands = {}
228 deprecated_subcommands = {}
228
229
229 def initialize_subcommand(self, subc, argv=None):
230 def initialize_subcommand(self, subc, argv=None):
230 if subc in self.deprecated_subcommands:
231 if subc in self.deprecated_subcommands:
231 self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed "
232 self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed "
232 "in future versions.".format(sub=subc))
233 "in future versions.".format(sub=subc))
233 self.log.warning("You likely want to use `jupyter {sub}` in the "
234 self.log.warning("You likely want to use `jupyter {sub}` in the "
234 "future".format(sub=subc))
235 "future".format(sub=subc))
235 return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv)
236 return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv)
236
237
237 def init_crash_handler(self):
238 def init_crash_handler(self):
238 """Create a crash handler, typically setting sys.excepthook to it."""
239 """Create a crash handler, typically setting sys.excepthook to it."""
239 self.crash_handler = self.crash_handler_class(self)
240 self.crash_handler = self.crash_handler_class(self)
240 sys.excepthook = self.excepthook
241 sys.excepthook = self.excepthook
241 def unset_crashhandler():
242 def unset_crashhandler():
242 sys.excepthook = sys.__excepthook__
243 sys.excepthook = sys.__excepthook__
243 atexit.register(unset_crashhandler)
244 atexit.register(unset_crashhandler)
244
245
245 def excepthook(self, etype, evalue, tb):
246 def excepthook(self, etype, evalue, tb):
246 """this is sys.excepthook after init_crashhandler
247 """this is sys.excepthook after init_crashhandler
247
248
248 set self.verbose_crash=True to use our full crashhandler, instead of
249 set self.verbose_crash=True to use our full crashhandler, instead of
249 a regular traceback with a short message (crash_handler_lite)
250 a regular traceback with a short message (crash_handler_lite)
250 """
251 """
251
252
252 if self.verbose_crash:
253 if self.verbose_crash:
253 return self.crash_handler(etype, evalue, tb)
254 return self.crash_handler(etype, evalue, tb)
254 else:
255 else:
255 return crashhandler.crash_handler_lite(etype, evalue, tb)
256 return crashhandler.crash_handler_lite(etype, evalue, tb)
256
257
257 @observe('ipython_dir')
258 @observe('ipython_dir')
258 def _ipython_dir_changed(self, change):
259 def _ipython_dir_changed(self, change):
259 old = change['old']
260 old = change['old']
260 new = change['new']
261 new = change['new']
261 if old is not Undefined:
262 if old is not Undefined:
262 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
263 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
263 sys.getfilesystemencoding()
264 sys.getfilesystemencoding()
264 )
265 )
265 if str_old in sys.path:
266 if str_old in sys.path:
266 sys.path.remove(str_old)
267 sys.path.remove(str_old)
267 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
268 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
268 sys.getfilesystemencoding()
269 sys.getfilesystemencoding()
269 )
270 )
270 sys.path.append(str_path)
271 sys.path.append(str_path)
271 ensure_dir_exists(new)
272 ensure_dir_exists(new)
272 readme = os.path.join(new, 'README')
273 readme = os.path.join(new, 'README')
273 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
274 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
274 if not os.path.exists(readme) and os.path.exists(readme_src):
275 if not os.path.exists(readme) and os.path.exists(readme_src):
275 shutil.copy(readme_src, readme)
276 shutil.copy(readme_src, readme)
276 for d in ('extensions', 'nbextensions'):
277 for d in ('extensions', 'nbextensions'):
277 path = os.path.join(new, d)
278 path = os.path.join(new, d)
278 try:
279 try:
279 ensure_dir_exists(path)
280 ensure_dir_exists(path)
280 except OSError as e:
281 except OSError as e:
281 # this will not be EEXIST
282 # this will not be EEXIST
282 self.log.error("couldn't create path %s: %s", path, e)
283 self.log.error("couldn't create path %s: %s", path, e)
283 self.log.debug("IPYTHONDIR set to: %s" % new)
284 self.log.debug("IPYTHONDIR set to: %s" % new)
284
285
285 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
286 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
286 """Load the config file.
287 """Load the config file.
287
288
288 By default, errors in loading config are handled, and a warning
289 By default, errors in loading config are handled, and a warning
289 printed on screen. For testing, the suppress_errors option is set
290 printed on screen. For testing, the suppress_errors option is set
290 to False, so errors will make tests fail.
291 to False, so errors will make tests fail.
291
292
292 `supress_errors` default value is to be `None` in which case the
293 `supress_errors` default value is to be `None` in which case the
293 behavior default to the one of `traitlets.Application`.
294 behavior default to the one of `traitlets.Application`.
294
295
295 The default value can be set :
296 The default value can be set :
296 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
297 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
297 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
298 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
298 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
299 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
299
300
300 Any other value are invalid, and will make IPython exit with a non-zero return code.
301 Any other value are invalid, and will make IPython exit with a non-zero return code.
301 """
302 """
302
303
303
304
304 self.log.debug("Searching path %s for config files", self.config_file_paths)
305 self.log.debug("Searching path %s for config files", self.config_file_paths)
305 base_config = 'ipython_config.py'
306 base_config = 'ipython_config.py'
306 self.log.debug("Attempting to load config file: %s" %
307 self.log.debug("Attempting to load config file: %s" %
307 base_config)
308 base_config)
308 try:
309 try:
309 if suppress_errors is not None:
310 if suppress_errors is not None:
310 old_value = Application.raise_config_file_errors
311 old_value = Application.raise_config_file_errors
311 Application.raise_config_file_errors = not suppress_errors;
312 Application.raise_config_file_errors = not suppress_errors;
312 Application.load_config_file(
313 Application.load_config_file(
313 self,
314 self,
314 base_config,
315 base_config,
315 path=self.config_file_paths
316 path=self.config_file_paths
316 )
317 )
317 except ConfigFileNotFound:
318 except ConfigFileNotFound:
318 # ignore errors loading parent
319 # ignore errors loading parent
319 self.log.debug("Config file %s not found", base_config)
320 self.log.debug("Config file %s not found", base_config)
320 pass
321 pass
321 if suppress_errors is not None:
322 if suppress_errors is not None:
322 Application.raise_config_file_errors = old_value
323 Application.raise_config_file_errors = old_value
323
324
324 for config_file_name in self.config_files:
325 for config_file_name in self.config_files:
325 if not config_file_name or config_file_name == base_config:
326 if not config_file_name or config_file_name == base_config:
326 continue
327 continue
327 self.log.debug("Attempting to load config file: %s" %
328 self.log.debug("Attempting to load config file: %s" %
328 self.config_file_name)
329 self.config_file_name)
329 try:
330 try:
330 Application.load_config_file(
331 Application.load_config_file(
331 self,
332 self,
332 config_file_name,
333 config_file_name,
333 path=self.config_file_paths
334 path=self.config_file_paths
334 )
335 )
335 except ConfigFileNotFound:
336 except ConfigFileNotFound:
336 # Only warn if the default config file was NOT being used.
337 # Only warn if the default config file was NOT being used.
337 if config_file_name in self.config_file_specified:
338 if config_file_name in self.config_file_specified:
338 msg = self.log.warning
339 msg = self.log.warning
339 else:
340 else:
340 msg = self.log.debug
341 msg = self.log.debug
341 msg("Config file not found, skipping: %s", config_file_name)
342 msg("Config file not found, skipping: %s", config_file_name)
342 except Exception:
343 except Exception:
343 # For testing purposes.
344 # For testing purposes.
344 if not suppress_errors:
345 if not suppress_errors:
345 raise
346 raise
346 self.log.warning("Error loading config file: %s" %
347 self.log.warning("Error loading config file: %s" %
347 self.config_file_name, exc_info=True)
348 self.config_file_name, exc_info=True)
348
349
349 def init_profile_dir(self):
350 def init_profile_dir(self):
350 """initialize the profile dir"""
351 """initialize the profile dir"""
351 self._in_init_profile_dir = True
352 self._in_init_profile_dir = True
352 if self.profile_dir is not None:
353 if self.profile_dir is not None:
353 # already ran
354 # already ran
354 return
355 return
355 if 'ProfileDir.location' not in self.config:
356 if 'ProfileDir.location' not in self.config:
356 # location not specified, find by profile name
357 # location not specified, find by profile name
357 try:
358 try:
358 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
359 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
359 except ProfileDirError:
360 except ProfileDirError:
360 # not found, maybe create it (always create default profile)
361 # not found, maybe create it (always create default profile)
361 if self.auto_create or self.profile == 'default':
362 if self.auto_create or self.profile == 'default':
362 try:
363 try:
363 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
364 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
364 except ProfileDirError:
365 except ProfileDirError:
365 self.log.fatal("Could not create profile: %r"%self.profile)
366 self.log.fatal("Could not create profile: %r"%self.profile)
366 self.exit(1)
367 self.exit(1)
367 else:
368 else:
368 self.log.info("Created profile dir: %r"%p.location)
369 self.log.info("Created profile dir: %r"%p.location)
369 else:
370 else:
370 self.log.fatal("Profile %r not found."%self.profile)
371 self.log.fatal("Profile %r not found."%self.profile)
371 self.exit(1)
372 self.exit(1)
372 else:
373 else:
373 self.log.debug("Using existing profile dir: %r"%p.location)
374 self.log.debug("Using existing profile dir: %r"%p.location)
374 else:
375 else:
375 location = self.config.ProfileDir.location
376 location = self.config.ProfileDir.location
376 # location is fully specified
377 # location is fully specified
377 try:
378 try:
378 p = ProfileDir.find_profile_dir(location, self.config)
379 p = ProfileDir.find_profile_dir(location, self.config)
379 except ProfileDirError:
380 except ProfileDirError:
380 # not found, maybe create it
381 # not found, maybe create it
381 if self.auto_create:
382 if self.auto_create:
382 try:
383 try:
383 p = ProfileDir.create_profile_dir(location, self.config)
384 p = ProfileDir.create_profile_dir(location, self.config)
384 except ProfileDirError:
385 except ProfileDirError:
385 self.log.fatal("Could not create profile directory: %r"%location)
386 self.log.fatal("Could not create profile directory: %r"%location)
386 self.exit(1)
387 self.exit(1)
387 else:
388 else:
388 self.log.debug("Creating new profile dir: %r"%location)
389 self.log.debug("Creating new profile dir: %r"%location)
389 else:
390 else:
390 self.log.fatal("Profile directory %r not found."%location)
391 self.log.fatal("Profile directory %r not found."%location)
391 self.exit(1)
392 self.exit(1)
392 else:
393 else:
393 self.log.info("Using existing profile dir: %r"%location)
394 self.log.info("Using existing profile dir: %r"%location)
394 # if profile_dir is specified explicitly, set profile name
395 # if profile_dir is specified explicitly, set profile name
395 dir_name = os.path.basename(p.location)
396 dir_name = os.path.basename(p.location)
396 if dir_name.startswith('profile_'):
397 if dir_name.startswith('profile_'):
397 self.profile = dir_name[8:]
398 self.profile = dir_name[8:]
398
399
399 self.profile_dir = p
400 self.profile_dir = p
400 self.config_file_paths.append(p.location)
401 self.config_file_paths.append(p.location)
401 self._in_init_profile_dir = False
402 self._in_init_profile_dir = False
402
403
403 def init_config_files(self):
404 def init_config_files(self):
404 """[optionally] copy default config files into profile dir."""
405 """[optionally] copy default config files into profile dir."""
405 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
406 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
406 # copy config files
407 # copy config files
407 path = self.builtin_profile_dir
408 path = self.builtin_profile_dir
408 if self.copy_config_files:
409 if self.copy_config_files:
409 src = self.profile
410 src = self.profile
410
411
411 cfg = self.config_file_name
412 cfg = self.config_file_name
412 if path and os.path.exists(os.path.join(path, cfg)):
413 if path and os.path.exists(os.path.join(path, cfg)):
413 self.log.warning("Staging %r from %s into %r [overwrite=%s]"%(
414 self.log.warning("Staging %r from %s into %r [overwrite=%s]"%(
414 cfg, src, self.profile_dir.location, self.overwrite)
415 cfg, src, self.profile_dir.location, self.overwrite)
415 )
416 )
416 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
417 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
417 else:
418 else:
418 self.stage_default_config_file()
419 self.stage_default_config_file()
419 else:
420 else:
420 # Still stage *bundled* config files, but not generated ones
421 # Still stage *bundled* config files, but not generated ones
421 # This is necessary for `ipython profile=sympy` to load the profile
422 # This is necessary for `ipython profile=sympy` to load the profile
422 # on the first go
423 # on the first go
423 files = glob.glob(os.path.join(path, '*.py'))
424 files = glob.glob(os.path.join(path, '*.py'))
424 for fullpath in files:
425 for fullpath in files:
425 cfg = os.path.basename(fullpath)
426 cfg = os.path.basename(fullpath)
426 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
427 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
427 # file was copied
428 # file was copied
428 self.log.warning("Staging bundled %s from %s into %r"%(
429 self.log.warning("Staging bundled %s from %s into %r"%(
429 cfg, self.profile, self.profile_dir.location)
430 cfg, self.profile, self.profile_dir.location)
430 )
431 )
431
432
432
433
433 def stage_default_config_file(self):
434 def stage_default_config_file(self):
434 """auto generate default config file, and stage it into the profile."""
435 """auto generate default config file, and stage it into the profile."""
435 s = self.generate_config_file()
436 s = self.generate_config_file()
436 fname = os.path.join(self.profile_dir.location, self.config_file_name)
437 fname = os.path.join(self.profile_dir.location, self.config_file_name)
437 if self.overwrite or not os.path.exists(fname):
438 if self.overwrite or not os.path.exists(fname):
438 self.log.warning("Generating default config file: %r"%(fname))
439 self.log.warning("Generating default config file: %r"%(fname))
439 with open(fname, 'w') as f:
440 with open(fname, 'w') as f:
440 f.write(s)
441 f.write(s)
441
442
442 @catch_config_error
443 @catch_config_error
443 def initialize(self, argv=None):
444 def initialize(self, argv=None):
444 # don't hook up crash handler before parsing command-line
445 # don't hook up crash handler before parsing command-line
445 self.parse_command_line(argv)
446 self.parse_command_line(argv)
446 self.init_crash_handler()
447 self.init_crash_handler()
447 if self.subapp is not None:
448 if self.subapp is not None:
448 # stop here if subapp is taking over
449 # stop here if subapp is taking over
449 return
450 return
450 cl_config = self.config
451 # save a copy of CLI config to re-load after config files
452 # so that it has highest priority
453 cl_config = deepcopy(self.config)
451 self.init_profile_dir()
454 self.init_profile_dir()
452 self.init_config_files()
455 self.init_config_files()
453 self.load_config_file()
456 self.load_config_file()
454 # enforce cl-opts override configfile opts:
457 # enforce cl-opts override configfile opts:
455 self.update_config(cl_config)
458 self.update_config(cl_config)
@@ -1,50 +1,74 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.core.application"""
2 """Tests for IPython.core.application"""
3
3
4 import os
4 import os
5 import tempfile
5 import tempfile
6
6
7 import nose.tools as nt
8
9 from traitlets import Unicode
10
7 from IPython.core.application import BaseIPythonApplication
11 from IPython.core.application import BaseIPythonApplication
8 from IPython.testing import decorators as dec
12 from IPython.testing import decorators as dec
9 from IPython.utils import py3compat
13 from IPython.utils import py3compat
14 from IPython.utils.tempdir import TemporaryDirectory
15
10
16
11 @dec.onlyif_unicode_paths
17 @dec.onlyif_unicode_paths
12 def test_unicode_cwd():
18 def test_unicode_cwd():
13 """Check that IPython starts with non-ascii characters in the path."""
19 """Check that IPython starts with non-ascii characters in the path."""
14 wd = tempfile.mkdtemp(suffix=u"€")
20 wd = tempfile.mkdtemp(suffix=u"€")
15
21
16 old_wd = py3compat.getcwd()
22 old_wd = py3compat.getcwd()
17 os.chdir(wd)
23 os.chdir(wd)
18 #raise Exception(repr(py3compat.getcwd()))
24 #raise Exception(repr(py3compat.getcwd()))
19 try:
25 try:
20 app = BaseIPythonApplication()
26 app = BaseIPythonApplication()
21 # The lines below are copied from Application.initialize()
27 # The lines below are copied from Application.initialize()
22 app.init_profile_dir()
28 app.init_profile_dir()
23 app.init_config_files()
29 app.init_config_files()
24 app.load_config_file(suppress_errors=False)
30 app.load_config_file(suppress_errors=False)
25 finally:
31 finally:
26 os.chdir(old_wd)
32 os.chdir(old_wd)
27
33
28 @dec.onlyif_unicode_paths
34 @dec.onlyif_unicode_paths
29 def test_unicode_ipdir():
35 def test_unicode_ipdir():
30 """Check that IPython starts with non-ascii characters in the IP dir."""
36 """Check that IPython starts with non-ascii characters in the IP dir."""
31 ipdir = tempfile.mkdtemp(suffix=u"€")
37 ipdir = tempfile.mkdtemp(suffix=u"€")
32
38
33 # Create the config file, so it tries to load it.
39 # Create the config file, so it tries to load it.
34 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
40 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
35 pass
41 pass
36
42
37 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
43 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
38 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
44 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
39 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
45 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
40 try:
46 try:
41 app = BaseIPythonApplication()
47 app = BaseIPythonApplication()
42 # The lines below are copied from Application.initialize()
48 # The lines below are copied from Application.initialize()
43 app.init_profile_dir()
49 app.init_profile_dir()
44 app.init_config_files()
50 app.init_config_files()
45 app.load_config_file(suppress_errors=False)
51 app.load_config_file(suppress_errors=False)
46 finally:
52 finally:
47 if old_ipdir1:
53 if old_ipdir1:
48 os.environ["IPYTHONDIR"] = old_ipdir1
54 os.environ["IPYTHONDIR"] = old_ipdir1
49 if old_ipdir2:
55 if old_ipdir2:
50 os.environ["IPYTHONDIR"] = old_ipdir2
56 os.environ["IPYTHONDIR"] = old_ipdir2
57
58 def test_cli_priority():
59 with TemporaryDirectory() as td:
60
61 class TestApp(BaseIPythonApplication):
62 test = Unicode().tag(config=True)
63
64 # Create the config file, so it tries to load it.
65 with open(os.path.join(td, 'ipython_config.py'), "w") as f:
66 f.write("c.TestApp.test = 'config file'")
67
68 app = TestApp()
69 app.initialize(['--profile-dir', td])
70 nt.assert_equal(app.test, 'config file')
71 app = TestApp()
72 app.initialize(['--profile-dir', td, '--TestApp.test=cli'])
73 nt.assert_equal(app.test, 'cli')
74
General Comments 0
You need to be logged in to leave comments. Login now