##// END OF EJS Templates
Merge pull request #12547 from meeseeksmachine/auto-backport-of-pr-12543-on-7.x
Matthias Bussonnier -
r26013:bd584b2f merge
parent child Browse files
Show More
@@ -1,441 +1,469 b''
1 1 # encoding: utf-8
2 2 """
3 3 A mixin for :class:`~IPython.core.application.Application` classes that
4 4 launch InteractiveShell instances, load extensions, etc.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import glob
11 11 from itertools import chain
12 12 import os
13 13 import sys
14 14
15 15 from traitlets.config.application import boolean_flag
16 16 from traitlets.config.configurable import Configurable
17 17 from traitlets.config.loader import Config
18 18 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
19 19 from IPython.core import pylabtools
20 20 from IPython.utils.contexts import preserve_keys
21 21 from IPython.utils.path import filefind
22 import traitlets
22 23 from traitlets import (
23 24 Unicode, Instance, List, Bool, CaselessStrEnum, observe,
25 DottedObjectName,
24 26 )
25 27 from IPython.terminal import pt_inputhooks
26 28
27 29 #-----------------------------------------------------------------------------
28 30 # Aliases and Flags
29 31 #-----------------------------------------------------------------------------
30 32
31 33 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
32 34
33 35 backend_keys = sorted(pylabtools.backends.keys())
34 36 backend_keys.insert(0, 'auto')
35 37
36 38 shell_flags = {}
37 39
38 40 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
39 41 addflag('autoindent', 'InteractiveShell.autoindent',
40 42 'Turn on autoindenting.', 'Turn off autoindenting.'
41 43 )
42 44 addflag('automagic', 'InteractiveShell.automagic',
43 45 """Turn on the auto calling of magic commands. Type %%magic at the
44 46 IPython prompt for more information.""",
45 47 'Turn off the auto calling of magic commands.'
46 48 )
47 49 addflag('pdb', 'InteractiveShell.pdb',
48 50 "Enable auto calling the pdb debugger after every exception.",
49 51 "Disable auto calling the pdb debugger after every exception."
50 52 )
51 53 addflag('pprint', 'PlainTextFormatter.pprint',
52 54 "Enable auto pretty printing of results.",
53 55 "Disable auto pretty printing of results."
54 56 )
55 57 addflag('color-info', 'InteractiveShell.color_info',
56 58 """IPython can display information about objects via a set of functions,
57 59 and optionally can use colors for this, syntax highlighting
58 60 source code and various other elements. This is on by default, but can cause
59 61 problems with some pagers. If you see such problems, you can disable the
60 62 colours.""",
61 63 "Disable using colors for info related things."
62 64 )
63 65 addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd',
64 66 "Exclude the current working directory from sys.path",
65 67 "Include the current working directory in sys.path",
66 68 )
67 69 nosep_config = Config()
68 70 nosep_config.InteractiveShell.separate_in = ''
69 71 nosep_config.InteractiveShell.separate_out = ''
70 72 nosep_config.InteractiveShell.separate_out2 = ''
71 73
72 74 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
73 75 shell_flags['pylab'] = (
74 76 {'InteractiveShellApp' : {'pylab' : 'auto'}},
75 77 """Pre-load matplotlib and numpy for interactive use with
76 78 the default matplotlib backend."""
77 79 )
78 80 shell_flags['matplotlib'] = (
79 81 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
80 82 """Configure matplotlib for interactive use with
81 83 the default matplotlib backend."""
82 84 )
83 85
84 86 # it's possible we don't want short aliases for *all* of these:
85 87 shell_aliases = dict(
86 88 autocall='InteractiveShell.autocall',
87 89 colors='InteractiveShell.colors',
88 90 logfile='InteractiveShell.logfile',
89 91 logappend='InteractiveShell.logappend',
90 92 c='InteractiveShellApp.code_to_run',
91 93 m='InteractiveShellApp.module_to_run',
92 ext='InteractiveShellApp.extra_extension',
94 ext="InteractiveShellApp.extra_extensions",
93 95 gui='InteractiveShellApp.gui',
94 96 pylab='InteractiveShellApp.pylab',
95 97 matplotlib='InteractiveShellApp.matplotlib',
96 98 )
97 99 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
98 100
101 if traitlets.version_info < (5, 0):
102 # traitlets 4 doesn't handle lists on CLI
103 shell_aliases["ext"] = "InteractiveShellApp.extra_extension"
104
105
99 106 #-----------------------------------------------------------------------------
100 107 # Main classes and functions
101 108 #-----------------------------------------------------------------------------
102 109
103 110 class InteractiveShellApp(Configurable):
104 111 """A Mixin for applications that start InteractiveShell instances.
105 112
106 113 Provides configurables for loading extensions and executing files
107 114 as part of configuring a Shell environment.
108 115
109 116 The following methods should be called by the :meth:`initialize` method
110 117 of the subclass:
111 118
112 119 - :meth:`init_path`
113 120 - :meth:`init_shell` (to be implemented by the subclass)
114 121 - :meth:`init_gui_pylab`
115 122 - :meth:`init_extensions`
116 123 - :meth:`init_code`
117 124 """
118 125 extensions = List(Unicode(),
119 126 help="A list of dotted module names of IPython extensions to load."
120 127 ).tag(config=True)
121 extra_extension = Unicode('',
122 help="dotted module name of an IPython extension to load."
128
129 extra_extension = Unicode(
130 "",
131 help="""
132 DEPRECATED. Dotted module name of a single extra IPython extension to load.
133
134 Only one extension can be added this way.
135
136 Only used with traitlets < 5.0, plural extra_extensions list is used in traitlets 5.
137 """,
138 ).tag(config=True)
139
140 extra_extensions = List(
141 DottedObjectName(),
142 help="""
143 Dotted module name(s) of one or more IPython extensions to load.
144
145 For specifying extra extensions to load on the command-line.
146
147 .. versionadded:: 7.10
148 """,
123 149 ).tag(config=True)
124 150
125 151 reraise_ipython_extension_failures = Bool(False,
126 152 help="Reraise exceptions encountered loading IPython extensions?",
127 153 ).tag(config=True)
128 154
129 155 # Extensions that are always loaded (not configurable)
130 156 default_extensions = List(Unicode(), [u'storemagic']).tag(config=False)
131 157
132 158 hide_initial_ns = Bool(True,
133 159 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
134 160 be hidden from tools like %who?"""
135 161 ).tag(config=True)
136 162
137 163 exec_files = List(Unicode(),
138 164 help="""List of files to run at IPython startup."""
139 165 ).tag(config=True)
140 166 exec_PYTHONSTARTUP = Bool(True,
141 167 help="""Run the file referenced by the PYTHONSTARTUP environment
142 168 variable at IPython startup."""
143 169 ).tag(config=True)
144 170 file_to_run = Unicode('',
145 171 help="""A file to be run""").tag(config=True)
146 172
147 173 exec_lines = List(Unicode(),
148 174 help="""lines of code to run at IPython startup."""
149 175 ).tag(config=True)
150 176 code_to_run = Unicode('',
151 177 help="Execute the given command string."
152 178 ).tag(config=True)
153 179 module_to_run = Unicode('',
154 180 help="Run the module as a script."
155 181 ).tag(config=True)
156 182 gui = CaselessStrEnum(gui_keys, allow_none=True,
157 183 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
158 184 ).tag(config=True)
159 185 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
160 186 help="""Configure matplotlib for interactive use with
161 187 the default matplotlib backend."""
162 188 ).tag(config=True)
163 189 pylab = CaselessStrEnum(backend_keys, allow_none=True,
164 190 help="""Pre-load matplotlib and numpy for interactive use,
165 191 selecting a particular matplotlib backend and loop integration.
166 192 """
167 193 ).tag(config=True)
168 194 pylab_import_all = Bool(True,
169 195 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
170 196 and an ``import *`` is done from numpy and pylab, when using pylab mode.
171 197
172 198 When False, pylab mode should not import any names into the user namespace.
173 199 """
174 200 ).tag(config=True)
175 201 ignore_cwd = Bool(
176 202 False,
177 203 help="""If True, IPython will not add the current working directory to sys.path.
178 204 When False, the current working directory is added to sys.path, allowing imports
179 205 of modules defined in the current directory."""
180 206 ).tag(config=True)
181 207 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
182 208 allow_none=True)
183 209 # whether interact-loop should start
184 210 interact = Bool(True)
185 211
186 212 user_ns = Instance(dict, args=None, allow_none=True)
187 213 @observe('user_ns')
188 214 def _user_ns_changed(self, change):
189 215 if self.shell is not None:
190 216 self.shell.user_ns = change['new']
191 217 self.shell.init_user_ns()
192 218
193 219 def init_path(self):
194 220 """Add current working directory, '', to sys.path
195 221
196 222 Unlike Python's default, we insert before the first `site-packages`
197 223 or `dist-packages` directory,
198 224 so that it is after the standard library.
199 225
200 226 .. versionchanged:: 7.2
201 227 Try to insert after the standard library, instead of first.
202 228 .. versionchanged:: 8.0
203 229 Allow optionally not including the current directory in sys.path
204 230 """
205 231 if '' in sys.path or self.ignore_cwd:
206 232 return
207 233 for idx, path in enumerate(sys.path):
208 234 parent, last_part = os.path.split(path)
209 235 if last_part in {'site-packages', 'dist-packages'}:
210 236 break
211 237 else:
212 238 # no site-packages or dist-packages found (?!)
213 239 # back to original behavior of inserting at the front
214 240 idx = 0
215 241 sys.path.insert(idx, '')
216 242
217 243 def init_shell(self):
218 244 raise NotImplementedError("Override in subclasses")
219 245
220 246 def init_gui_pylab(self):
221 247 """Enable GUI event loop integration, taking pylab into account."""
222 248 enable = False
223 249 shell = self.shell
224 250 if self.pylab:
225 251 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
226 252 key = self.pylab
227 253 elif self.matplotlib:
228 254 enable = shell.enable_matplotlib
229 255 key = self.matplotlib
230 256 elif self.gui:
231 257 enable = shell.enable_gui
232 258 key = self.gui
233 259
234 260 if not enable:
235 261 return
236 262
237 263 try:
238 264 r = enable(key)
239 265 except ImportError:
240 266 self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?")
241 267 self.shell.showtraceback()
242 268 return
243 269 except Exception:
244 270 self.log.warning("GUI event loop or pylab initialization failed")
245 271 self.shell.showtraceback()
246 272 return
247 273
248 274 if isinstance(r, tuple):
249 275 gui, backend = r[:2]
250 276 self.log.info("Enabling GUI event loop integration, "
251 277 "eventloop=%s, matplotlib=%s", gui, backend)
252 278 if key == "auto":
253 279 print("Using matplotlib backend: %s" % backend)
254 280 else:
255 281 gui = r
256 282 self.log.info("Enabling GUI event loop integration, "
257 283 "eventloop=%s", gui)
258 284
259 285 def init_extensions(self):
260 286 """Load all IPython extensions in IPythonApp.extensions.
261 287
262 288 This uses the :meth:`ExtensionManager.load_extensions` to load all
263 289 the extensions listed in ``self.extensions``.
264 290 """
265 291 try:
266 292 self.log.debug("Loading IPython extensions...")
267 extensions = self.default_extensions + self.extensions
293 extensions = (
294 self.default_extensions + self.extensions + self.extra_extensions
295 )
268 296 if self.extra_extension:
269 297 extensions.append(self.extra_extension)
270 298 for ext in extensions:
271 299 try:
272 300 self.log.info("Loading IPython extension: %s" % ext)
273 301 self.shell.extension_manager.load_extension(ext)
274 302 except:
275 303 if self.reraise_ipython_extension_failures:
276 304 raise
277 305 msg = ("Error in loading extension: {ext}\n"
278 306 "Check your config files in {location}".format(
279 307 ext=ext,
280 308 location=self.profile_dir.location
281 309 ))
282 310 self.log.warning(msg, exc_info=True)
283 311 except:
284 312 if self.reraise_ipython_extension_failures:
285 313 raise
286 314 self.log.warning("Unknown error in loading extensions:", exc_info=True)
287 315
288 316 def init_code(self):
289 317 """run the pre-flight code, specified via exec_lines"""
290 318 self._run_startup_files()
291 319 self._run_exec_lines()
292 320 self._run_exec_files()
293 321
294 322 # Hide variables defined here from %who etc.
295 323 if self.hide_initial_ns:
296 324 self.shell.user_ns_hidden.update(self.shell.user_ns)
297 325
298 326 # command-line execution (ipython -i script.py, ipython -m module)
299 327 # should *not* be excluded from %whos
300 328 self._run_cmd_line_code()
301 329 self._run_module()
302 330
303 331 # flush output, so itwon't be attached to the first cell
304 332 sys.stdout.flush()
305 333 sys.stderr.flush()
306 334
307 335 def _run_exec_lines(self):
308 336 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
309 337 if not self.exec_lines:
310 338 return
311 339 try:
312 340 self.log.debug("Running code from IPythonApp.exec_lines...")
313 341 for line in self.exec_lines:
314 342 try:
315 343 self.log.info("Running code in user namespace: %s" %
316 344 line)
317 345 self.shell.run_cell(line, store_history=False)
318 346 except:
319 347 self.log.warning("Error in executing line in user "
320 348 "namespace: %s" % line)
321 349 self.shell.showtraceback()
322 350 except:
323 351 self.log.warning("Unknown error in handling IPythonApp.exec_lines:")
324 352 self.shell.showtraceback()
325 353
326 354 def _exec_file(self, fname, shell_futures=False):
327 355 try:
328 356 full_filename = filefind(fname, [u'.', self.ipython_dir])
329 357 except IOError:
330 358 self.log.warning("File not found: %r"%fname)
331 359 return
332 360 # Make sure that the running script gets a proper sys.argv as if it
333 361 # were run from a system shell.
334 362 save_argv = sys.argv
335 363 sys.argv = [full_filename] + self.extra_args[1:]
336 364 try:
337 365 if os.path.isfile(full_filename):
338 366 self.log.info("Running file in user namespace: %s" %
339 367 full_filename)
340 368 # Ensure that __file__ is always defined to match Python
341 369 # behavior.
342 370 with preserve_keys(self.shell.user_ns, '__file__'):
343 371 self.shell.user_ns['__file__'] = fname
344 372 if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'):
345 373 self.shell.safe_execfile_ipy(full_filename,
346 374 shell_futures=shell_futures)
347 375 else:
348 376 # default to python, even without extension
349 377 self.shell.safe_execfile(full_filename,
350 378 self.shell.user_ns,
351 379 shell_futures=shell_futures,
352 380 raise_exceptions=True)
353 381 finally:
354 382 sys.argv = save_argv
355 383
356 384 def _run_startup_files(self):
357 385 """Run files from profile startup directory"""
358 386 startup_dirs = [self.profile_dir.startup_dir] + [
359 387 os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS)
360 388 ]
361 389 startup_files = []
362 390
363 391 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
364 392 not (self.file_to_run or self.code_to_run or self.module_to_run):
365 393 python_startup = os.environ['PYTHONSTARTUP']
366 394 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
367 395 try:
368 396 self._exec_file(python_startup)
369 397 except:
370 398 self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
371 399 self.shell.showtraceback()
372 400 for startup_dir in startup_dirs[::-1]:
373 401 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
374 402 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
375 403 if not startup_files:
376 404 return
377 405
378 406 self.log.debug("Running startup files from %s...", startup_dir)
379 407 try:
380 408 for fname in sorted(startup_files):
381 409 self._exec_file(fname)
382 410 except:
383 411 self.log.warning("Unknown error in handling startup files:")
384 412 self.shell.showtraceback()
385 413
386 414 def _run_exec_files(self):
387 415 """Run files from IPythonApp.exec_files"""
388 416 if not self.exec_files:
389 417 return
390 418
391 419 self.log.debug("Running files in IPythonApp.exec_files...")
392 420 try:
393 421 for fname in self.exec_files:
394 422 self._exec_file(fname)
395 423 except:
396 424 self.log.warning("Unknown error in handling IPythonApp.exec_files:")
397 425 self.shell.showtraceback()
398 426
399 427 def _run_cmd_line_code(self):
400 428 """Run code or file specified at the command-line"""
401 429 if self.code_to_run:
402 430 line = self.code_to_run
403 431 try:
404 432 self.log.info("Running code given at command line (c=): %s" %
405 433 line)
406 434 self.shell.run_cell(line, store_history=False)
407 435 except:
408 436 self.log.warning("Error in executing line in user namespace: %s" %
409 437 line)
410 438 self.shell.showtraceback()
411 439 if not self.interact:
412 440 self.exit(1)
413 441
414 442 # Like Python itself, ignore the second if the first of these is present
415 443 elif self.file_to_run:
416 444 fname = self.file_to_run
417 445 if os.path.isdir(fname):
418 446 fname = os.path.join(fname, "__main__.py")
419 447 if not os.path.exists(fname):
420 448 self.log.warning("File '%s' doesn't exist", fname)
421 449 if not self.interact:
422 450 self.exit(2)
423 451 try:
424 452 self._exec_file(fname, shell_futures=True)
425 453 except:
426 454 self.shell.showtraceback(tb_offset=4)
427 455 if not self.interact:
428 456 self.exit(1)
429 457
430 458 def _run_module(self):
431 459 """Run module specified at the command-line."""
432 460 if self.module_to_run:
433 461 # Make sure that the module gets a proper sys.argv as if it were
434 462 # run using `python -m`.
435 463 save_argv = sys.argv
436 464 sys.argv = [sys.executable] + self.extra_args
437 465 try:
438 466 self.shell.safe_run_module(self.module_to_run,
439 467 self.shell.user_ns)
440 468 finally:
441 469 sys.argv = save_argv
General Comments 0
You need to be logged in to leave comments. Login now