shellapp.py 19 KB

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