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