##// END OF EJS Templates
Trim which options are exposed on kernelspec CLI
Thomas Kluyver -
Show More
@@ -1,389 +1,389 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating configurables.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import atexit
31 31 import glob
32 32 import logging
33 33 import os
34 34 import shutil
35 35 import sys
36 36
37 37 from IPython.config.application import Application, catch_config_error
38 38 from IPython.config.loader import ConfigFileNotFound
39 39 from IPython.core import release, crashhandler
40 40 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
42 42 from IPython.utils import py3compat
43 43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Classes and functions
47 47 #-----------------------------------------------------------------------------
48 48
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Base Application Class
52 52 #-----------------------------------------------------------------------------
53 53
54 54 # aliases and flags
55 55
56 56 base_aliases = {
57 57 'profile-dir' : 'ProfileDir.location',
58 58 'profile' : 'BaseIPythonApplication.profile',
59 59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 60 'log-level' : 'Application.log_level',
61 61 'config' : 'BaseIPythonApplication.extra_config_file',
62 62 }
63 63
64 64 base_flags = dict(
65 65 debug = ({'Application' : {'log_level' : logging.DEBUG}},
66 66 "set log level to logging.DEBUG (maximize logging output)"),
67 67 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
68 68 "set log level to logging.CRITICAL (minimize logging output)"),
69 69 init = ({'BaseIPythonApplication' : {
70 70 'copy_config_files' : True,
71 71 'auto_create' : True}
72 72 }, """Initialize profile with default config files. This is equivalent
73 73 to running `ipython profile create <profile>` prior to startup.
74 74 """)
75 75 )
76 76
77 77
78 78 class BaseIPythonApplication(Application):
79 79
80 80 name = Unicode(u'ipython')
81 81 description = Unicode(u'IPython: an enhanced interactive Python shell.')
82 82 version = Unicode(release.version)
83 83
84 84 aliases = Dict(base_aliases)
85 85 flags = Dict(base_flags)
86 86 classes = List([ProfileDir])
87 87
88 88 # Track whether the config_file has changed,
89 89 # because some logic happens only if we aren't using the default.
90 90 config_file_specified = Set()
91 91
92 92 config_file_name = Unicode()
93 93 def _config_file_name_default(self):
94 94 return self.name.replace('-','_') + u'_config.py'
95 95 def _config_file_name_changed(self, name, old, new):
96 96 if new != old:
97 97 self.config_file_specified.add(new)
98 98
99 99 # The directory that contains IPython's builtin profiles.
100 100 builtin_profile_dir = Unicode(
101 101 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
102 102 )
103 103
104 104 config_file_paths = List(Unicode)
105 105 def _config_file_paths_default(self):
106 106 return [py3compat.getcwd()]
107 107
108 108 extra_config_file = Unicode(config=True,
109 109 help="""Path to an extra config file to load.
110 110
111 111 If specified, load this config file in addition to any other IPython config.
112 112 """)
113 113 def _extra_config_file_changed(self, name, old, new):
114 114 try:
115 115 self.config_files.remove(old)
116 116 except ValueError:
117 117 pass
118 118 self.config_file_specified.add(new)
119 119 self.config_files.append(new)
120 120
121 121 profile = Unicode(u'default', config=True,
122 122 help="""The IPython profile to use."""
123 123 )
124 124
125 125 def _profile_changed(self, name, old, new):
126 126 self.builtin_profile_dir = os.path.join(
127 127 get_ipython_package_dir(), u'config', u'profile', new
128 128 )
129 129
130 130 ipython_dir = Unicode(config=True,
131 131 help="""
132 132 The name of the IPython directory. This directory is used for logging
133 133 configuration (through profiles), history storage, etc. The default
134 is usually $HOME/.ipython. This options can also be specified through
134 is usually $HOME/.ipython. This option can also be specified through
135 135 the environment variable IPYTHONDIR.
136 136 """
137 137 )
138 138 def _ipython_dir_default(self):
139 139 d = get_ipython_dir()
140 140 self._ipython_dir_changed('ipython_dir', d, d)
141 141 return d
142 142
143 143 _in_init_profile_dir = False
144 144 profile_dir = Instance(ProfileDir)
145 145 def _profile_dir_default(self):
146 146 # avoid recursion
147 147 if self._in_init_profile_dir:
148 148 return
149 149 # profile_dir requested early, force initialization
150 150 self.init_profile_dir()
151 151 return self.profile_dir
152 152
153 153 overwrite = Bool(False, config=True,
154 154 help="""Whether to overwrite existing config files when copying""")
155 155 auto_create = Bool(False, config=True,
156 156 help="""Whether to create profile dir if it doesn't exist""")
157 157
158 158 config_files = List(Unicode)
159 159 def _config_files_default(self):
160 160 return [self.config_file_name]
161 161
162 162 copy_config_files = Bool(False, config=True,
163 163 help="""Whether to install the default config files into the profile dir.
164 164 If a new profile is being created, and IPython contains config files for that
165 165 profile, then they will be staged into the new directory. Otherwise,
166 166 default config files will be automatically generated.
167 167 """)
168 168
169 169 verbose_crash = Bool(False, config=True,
170 170 help="""Create a massive crash report when IPython encounters what may be an
171 171 internal error. The default is to append a short message to the
172 172 usual traceback""")
173 173
174 174 # The class to use as the crash handler.
175 175 crash_handler_class = Type(crashhandler.CrashHandler)
176 176
177 177 @catch_config_error
178 178 def __init__(self, **kwargs):
179 179 super(BaseIPythonApplication, self).__init__(**kwargs)
180 180 # ensure current working directory exists
181 181 try:
182 182 directory = py3compat.getcwd()
183 183 except:
184 184 # raise exception
185 185 self.log.error("Current working directory doesn't exist.")
186 186 raise
187 187
188 188 #-------------------------------------------------------------------------
189 189 # Various stages of Application creation
190 190 #-------------------------------------------------------------------------
191 191
192 192 def init_crash_handler(self):
193 193 """Create a crash handler, typically setting sys.excepthook to it."""
194 194 self.crash_handler = self.crash_handler_class(self)
195 195 sys.excepthook = self.excepthook
196 196 def unset_crashhandler():
197 197 sys.excepthook = sys.__excepthook__
198 198 atexit.register(unset_crashhandler)
199 199
200 200 def excepthook(self, etype, evalue, tb):
201 201 """this is sys.excepthook after init_crashhandler
202 202
203 203 set self.verbose_crash=True to use our full crashhandler, instead of
204 204 a regular traceback with a short message (crash_handler_lite)
205 205 """
206 206
207 207 if self.verbose_crash:
208 208 return self.crash_handler(etype, evalue, tb)
209 209 else:
210 210 return crashhandler.crash_handler_lite(etype, evalue, tb)
211 211
212 212 def _ipython_dir_changed(self, name, old, new):
213 213 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
214 214 sys.getfilesystemencoding()
215 215 )
216 216 if str_old in sys.path:
217 217 sys.path.remove(str_old)
218 218 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
219 219 sys.getfilesystemencoding()
220 220 )
221 221 sys.path.append(str_path)
222 222 ensure_dir_exists(new)
223 223 readme = os.path.join(new, 'README')
224 224 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
225 225 if not os.path.exists(readme) and os.path.exists(readme_src):
226 226 shutil.copy(readme_src, readme)
227 227 for d in ('extensions', 'nbextensions'):
228 228 path = os.path.join(new, d)
229 229 try:
230 230 ensure_dir_exists(path)
231 231 except OSError:
232 232 # this will not be EEXIST
233 233 self.log.error("couldn't create path %s: %s", path, e)
234 234 self.log.debug("IPYTHONDIR set to: %s" % new)
235 235
236 236 def load_config_file(self, suppress_errors=True):
237 237 """Load the config file.
238 238
239 239 By default, errors in loading config are handled, and a warning
240 240 printed on screen. For testing, the suppress_errors option is set
241 241 to False, so errors will make tests fail.
242 242 """
243 243 self.log.debug("Searching path %s for config files", self.config_file_paths)
244 244 base_config = 'ipython_config.py'
245 245 self.log.debug("Attempting to load config file: %s" %
246 246 base_config)
247 247 try:
248 248 Application.load_config_file(
249 249 self,
250 250 base_config,
251 251 path=self.config_file_paths
252 252 )
253 253 except ConfigFileNotFound:
254 254 # ignore errors loading parent
255 255 self.log.debug("Config file %s not found", base_config)
256 256 pass
257 257
258 258 for config_file_name in self.config_files:
259 259 if not config_file_name or config_file_name == base_config:
260 260 continue
261 261 self.log.debug("Attempting to load config file: %s" %
262 262 self.config_file_name)
263 263 try:
264 264 Application.load_config_file(
265 265 self,
266 266 config_file_name,
267 267 path=self.config_file_paths
268 268 )
269 269 except ConfigFileNotFound:
270 270 # Only warn if the default config file was NOT being used.
271 271 if config_file_name in self.config_file_specified:
272 272 msg = self.log.warn
273 273 else:
274 274 msg = self.log.debug
275 275 msg("Config file not found, skipping: %s", config_file_name)
276 276 except:
277 277 # For testing purposes.
278 278 if not suppress_errors:
279 279 raise
280 280 self.log.warn("Error loading config file: %s" %
281 281 self.config_file_name, exc_info=True)
282 282
283 283 def init_profile_dir(self):
284 284 """initialize the profile dir"""
285 285 self._in_init_profile_dir = True
286 286 if self.profile_dir is not None:
287 287 # already ran
288 288 return
289 289 if 'ProfileDir.location' not in self.config:
290 290 # location not specified, find by profile name
291 291 try:
292 292 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
293 293 except ProfileDirError:
294 294 # not found, maybe create it (always create default profile)
295 295 if self.auto_create or self.profile == 'default':
296 296 try:
297 297 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
298 298 except ProfileDirError:
299 299 self.log.fatal("Could not create profile: %r"%self.profile)
300 300 self.exit(1)
301 301 else:
302 302 self.log.info("Created profile dir: %r"%p.location)
303 303 else:
304 304 self.log.fatal("Profile %r not found."%self.profile)
305 305 self.exit(1)
306 306 else:
307 307 self.log.info("Using existing profile dir: %r"%p.location)
308 308 else:
309 309 location = self.config.ProfileDir.location
310 310 # location is fully specified
311 311 try:
312 312 p = ProfileDir.find_profile_dir(location, self.config)
313 313 except ProfileDirError:
314 314 # not found, maybe create it
315 315 if self.auto_create:
316 316 try:
317 317 p = ProfileDir.create_profile_dir(location, self.config)
318 318 except ProfileDirError:
319 319 self.log.fatal("Could not create profile directory: %r"%location)
320 320 self.exit(1)
321 321 else:
322 322 self.log.info("Creating new profile dir: %r"%location)
323 323 else:
324 324 self.log.fatal("Profile directory %r not found."%location)
325 325 self.exit(1)
326 326 else:
327 327 self.log.info("Using existing profile dir: %r"%location)
328 328 # if profile_dir is specified explicitly, set profile name
329 329 dir_name = os.path.basename(p.location)
330 330 if dir_name.startswith('profile_'):
331 331 self.profile = dir_name[8:]
332 332
333 333 self.profile_dir = p
334 334 self.config_file_paths.append(p.location)
335 335 self._in_init_profile_dir = False
336 336
337 337 def init_config_files(self):
338 338 """[optionally] copy default config files into profile dir."""
339 339 # copy config files
340 340 path = self.builtin_profile_dir
341 341 if self.copy_config_files:
342 342 src = self.profile
343 343
344 344 cfg = self.config_file_name
345 345 if path and os.path.exists(os.path.join(path, cfg)):
346 346 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
347 347 cfg, src, self.profile_dir.location, self.overwrite)
348 348 )
349 349 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
350 350 else:
351 351 self.stage_default_config_file()
352 352 else:
353 353 # Still stage *bundled* config files, but not generated ones
354 354 # This is necessary for `ipython profile=sympy` to load the profile
355 355 # on the first go
356 356 files = glob.glob(os.path.join(path, '*.py'))
357 357 for fullpath in files:
358 358 cfg = os.path.basename(fullpath)
359 359 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
360 360 # file was copied
361 361 self.log.warn("Staging bundled %s from %s into %r"%(
362 362 cfg, self.profile, self.profile_dir.location)
363 363 )
364 364
365 365
366 366 def stage_default_config_file(self):
367 367 """auto generate default config file, and stage it into the profile."""
368 368 s = self.generate_config_file()
369 369 fname = os.path.join(self.profile_dir.location, self.config_file_name)
370 370 if self.overwrite or not os.path.exists(fname):
371 371 self.log.warn("Generating default config file: %r"%(fname))
372 372 with open(fname, 'w') as f:
373 373 f.write(s)
374 374
375 375 @catch_config_error
376 376 def initialize(self, argv=None):
377 377 # don't hook up crash handler before parsing command-line
378 378 self.parse_command_line(argv)
379 379 self.init_crash_handler()
380 380 if self.subapp is not None:
381 381 # stop here if subapp is taking over
382 382 return
383 383 cl_config = self.config
384 384 self.init_profile_dir()
385 385 self.init_config_files()
386 386 self.load_config_file()
387 387 # enforce cl-opts override configfile opts:
388 388 self.update_config(cl_config)
389 389
@@ -1,109 +1,120 b''
1 1
2 2 # Copyright (c) IPython Development Team.
3 3 # Distributed under the terms of the Modified BSD License.
4 4
5 5 import errno
6 6 import os.path
7 7
8 8 from IPython.config.application import Application
9 from IPython.core.application import BaseIPythonApplication, base_flags
9 from IPython.core.application import (
10 BaseIPythonApplication, base_flags, base_aliases
11 )
10 12 from IPython.utils.traitlets import Instance, Dict, Unicode, Bool
11 13
12 14 from .kernelspec import KernelSpecManager
13 15
14 16 def _pythonfirst(s):
15 17 "Sort key function that will put strings starting with 'python' first."
16 18 if s.startswith('python'):
17 19 return ''
18 20 return s
19 21
20 22 class ListKernelSpecs(BaseIPythonApplication):
21 23 description = """List installed kernel specifications."""
22 24 kernel_spec_manager = Instance(KernelSpecManager)
25
26 # Not all of the base aliases are meaningful (e.g. profile)
27 aliases = {k: base_aliases[k] for k in ['ipython-dir', 'log-level']}
28 flags = {'debug': base_flags['debug'],}
23 29
24 30 def _kernel_spec_manager_default(self):
25 31 return KernelSpecManager(ipython_dir=self.ipython_dir)
26 32
27 33 def start(self):
28 34 print("Available kernels:")
29 35 for kernelname in sorted(self.kernel_spec_manager.find_kernel_specs(),
30 36 key=_pythonfirst):
31 37 print(" %s" % kernelname)
32 38
33 39
34 40 class InstallKernelSpec(BaseIPythonApplication):
35 41 description = """Install a kernel specification directory."""
36 42 kernel_spec_manager = Instance(KernelSpecManager)
37 43
38 44 def _kernel_spec_manager_default(self):
39 45 return KernelSpecManager(ipython_dir=self.ipython_dir)
40 46
41 47 sourcedir = Unicode()
42 48 kernel_name = Unicode("", config=True,
43 49 help="Install the kernel spec with this name"
44 50 )
45 51 def _kernel_name_default(self):
46 52 return os.path.basename(self.sourcedir)
47 53
48 54 system = Bool(False, config=True,
49 55 help="""
50 56 Try to install the kernel spec to the systemwide directory instead of
51 57 the per-user directory.
52 58 """
53 59 )
54 60 replace = Bool(False, config=True,
55 61 help="Replace any existing kernel spec with this name."
56 62 )
57 63
58 64 aliases = {'name': 'InstallKernelSpec.kernel_name'}
65 for k in ['ipython-dir', 'log-level']:
66 aliases[k] = base_aliases[k]
59 67
60 68 flags = {'system': ({'InstallKernelSpec': {'system': True}},
61 69 "Install to the systemwide kernel registry"),
62 70 'replace': ({'InstallKernelSpec': {'replace': True}},
63 71 "Replace any existing kernel spec with this name."),
72 'debug': base_flags['debug'],
64 73 }
65 flags.update(base_flags)
66 74
67 75 def parse_command_line(self, argv):
68 76 super(InstallKernelSpec, self).parse_command_line(argv)
69 77 # accept positional arg as profile name
70 78 if self.extra_args:
71 79 self.sourcedir = self.extra_args[0]
72 80 else:
73 81 print("No source directory specified.")
74 82 self.exit(1)
75 83
76 84 def start(self):
77 85 try:
78 86 self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
79 87 kernel_name=self.kernel_name,
80 88 system=self.system,
81 89 replace=self.replace,
82 90 )
83 91 except OSError as e:
84 92 if e.errno == errno.EACCES:
85 93 print("Permission denied")
86 94 self.exit(1)
87 95 elif e.errno == errno.EEXIST:
88 96 print("A kernel spec named %r is already present" % self.kernel_name)
89 97 self.exit(1)
90 98 raise
91 99
92 100 class KernelSpecApp(Application):
93 101 name = "ipython kernelspec"
94 102 description = """Manage IPython kernel specifications."""
95 103
96 104 subcommands = Dict(dict(
97 105 list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
98 106 install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0])
99 107 ))
108
109 aliases = {}
110 flags = {}
100 111
101 112 def start(self):
102 113 if self.subapp is None:
103 114 print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
104 115 print()
105 116 self.print_description()
106 117 self.print_subcommands()
107 118 self.exit(1)
108 119 else:
109 120 return self.subapp.start()
General Comments 0
You need to be logged in to leave comments. Login now