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