You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
15 KiB

4 years ago
  1. """The IPython kernel implementation"""
  2. import getpass
  3. import sys
  4. from IPython.core import release
  5. from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode
  6. from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
  7. from traitlets import Instance, Type, Any, List, Bool
  8. from .comm import CommManager
  9. from .kernelbase import Kernel as KernelBase
  10. from .zmqshell import ZMQInteractiveShell
  11. try:
  12. from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter
  13. _use_experimental_60_completion = True
  14. except ImportError:
  15. _use_experimental_60_completion = False
  16. _EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental'
  17. class IPythonKernel(KernelBase):
  18. shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
  19. allow_none=True)
  20. shell_class = Type(ZMQInteractiveShell)
  21. use_experimental_completions = Bool(True,
  22. help="Set this flag to False to deactivate the use of experimental IPython completion APIs.",
  23. ).tag(config=True)
  24. user_module = Any()
  25. def _user_module_changed(self, name, old, new):
  26. if self.shell is not None:
  27. self.shell.user_module = new
  28. user_ns = Instance(dict, args=None, allow_none=True)
  29. def _user_ns_changed(self, name, old, new):
  30. if self.shell is not None:
  31. self.shell.user_ns = new
  32. self.shell.init_user_ns()
  33. # A reference to the Python builtin 'raw_input' function.
  34. # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
  35. _sys_raw_input = Any()
  36. _sys_eval_input = Any()
  37. def __init__(self, **kwargs):
  38. super(IPythonKernel, self).__init__(**kwargs)
  39. # Initialize the InteractiveShell subclass
  40. self.shell = self.shell_class.instance(parent=self,
  41. profile_dir = self.profile_dir,
  42. user_module = self.user_module,
  43. user_ns = self.user_ns,
  44. kernel = self,
  45. )
  46. self.shell.displayhook.session = self.session
  47. self.shell.displayhook.pub_socket = self.iopub_socket
  48. self.shell.displayhook.topic = self._topic('execute_result')
  49. self.shell.display_pub.session = self.session
  50. self.shell.display_pub.pub_socket = self.iopub_socket
  51. self.comm_manager = CommManager(parent=self, kernel=self)
  52. self.shell.configurables.append(self.comm_manager)
  53. comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
  54. for msg_type in comm_msg_types:
  55. self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
  56. help_links = List([
  57. {
  58. 'text': "Python Reference",
  59. 'url': "https://docs.python.org/%i.%i" % sys.version_info[:2],
  60. },
  61. {
  62. 'text': "IPython Reference",
  63. 'url': "https://ipython.org/documentation.html",
  64. },
  65. {
  66. 'text': "NumPy Reference",
  67. 'url': "https://docs.scipy.org/doc/numpy/reference/",
  68. },
  69. {
  70. 'text': "SciPy Reference",
  71. 'url': "https://docs.scipy.org/doc/scipy/reference/",
  72. },
  73. {
  74. 'text': "Matplotlib Reference",
  75. 'url': "https://matplotlib.org/contents.html",
  76. },
  77. {
  78. 'text': "SymPy Reference",
  79. 'url': "http://docs.sympy.org/latest/index.html",
  80. },
  81. {
  82. 'text': "pandas Reference",
  83. 'url': "https://pandas.pydata.org/pandas-docs/stable/",
  84. },
  85. ]).tag(config=True)
  86. # Kernel info fields
  87. implementation = 'ipython'
  88. implementation_version = release.version
  89. language_info = {
  90. 'name': 'python',
  91. 'version': sys.version.split()[0],
  92. 'mimetype': 'text/x-python',
  93. 'codemirror_mode': {
  94. 'name': 'ipython',
  95. 'version': sys.version_info[0]
  96. },
  97. 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
  98. 'nbconvert_exporter': 'python',
  99. 'file_extension': '.py'
  100. }
  101. @property
  102. def banner(self):
  103. return self.shell.banner
  104. def start(self):
  105. self.shell.exit_now = False
  106. super(IPythonKernel, self).start()
  107. def set_parent(self, ident, parent):
  108. """Overridden from parent to tell the display hook and output streams
  109. about the parent message.
  110. """
  111. super(IPythonKernel, self).set_parent(ident, parent)
  112. self.shell.set_parent(parent)
  113. def init_metadata(self, parent):
  114. """Initialize metadata.
  115. Run at the beginning of each execution request.
  116. """
  117. md = super(IPythonKernel, self).init_metadata(parent)
  118. # FIXME: remove deprecated ipyparallel-specific code
  119. # This is required for ipyparallel < 5.0
  120. md.update({
  121. 'dependencies_met' : True,
  122. 'engine' : self.ident,
  123. })
  124. return md
  125. def finish_metadata(self, parent, metadata, reply_content):
  126. """Finish populating metadata.
  127. Run after completing an execution request.
  128. """
  129. # FIXME: remove deprecated ipyparallel-specific code
  130. # This is required by ipyparallel < 5.0
  131. metadata['status'] = reply_content['status']
  132. if reply_content['status'] == 'error' and reply_content['ename'] == 'UnmetDependency':
  133. metadata['dependencies_met'] = False
  134. return metadata
  135. def _forward_input(self, allow_stdin=False):
  136. """Forward raw_input and getpass to the current frontend.
  137. via input_request
  138. """
  139. self._allow_stdin = allow_stdin
  140. if PY3:
  141. self._sys_raw_input = builtin_mod.input
  142. builtin_mod.input = self.raw_input
  143. else:
  144. self._sys_raw_input = builtin_mod.raw_input
  145. self._sys_eval_input = builtin_mod.input
  146. builtin_mod.raw_input = self.raw_input
  147. builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
  148. self._save_getpass = getpass.getpass
  149. getpass.getpass = self.getpass
  150. def _restore_input(self):
  151. """Restore raw_input, getpass"""
  152. if PY3:
  153. builtin_mod.input = self._sys_raw_input
  154. else:
  155. builtin_mod.raw_input = self._sys_raw_input
  156. builtin_mod.input = self._sys_eval_input
  157. getpass.getpass = self._save_getpass
  158. @property
  159. def execution_count(self):
  160. return self.shell.execution_count
  161. @execution_count.setter
  162. def execution_count(self, value):
  163. # Ignore the incrememnting done by KernelBase, in favour of our shell's
  164. # execution counter.
  165. pass
  166. def do_execute(self, code, silent, store_history=True,
  167. user_expressions=None, allow_stdin=False):
  168. shell = self.shell # we'll need this a lot here
  169. self._forward_input(allow_stdin)
  170. reply_content = {}
  171. try:
  172. res = shell.run_cell(code, store_history=store_history, silent=silent)
  173. finally:
  174. self._restore_input()
  175. if res.error_before_exec is not None:
  176. err = res.error_before_exec
  177. else:
  178. err = res.error_in_exec
  179. if res.success:
  180. reply_content[u'status'] = u'ok'
  181. else:
  182. reply_content[u'status'] = u'error'
  183. reply_content.update({
  184. u'traceback': shell._last_traceback or [],
  185. u'ename': unicode_type(type(err).__name__),
  186. u'evalue': safe_unicode(err),
  187. })
  188. # FIXME: deprecated piece for ipyparallel (remove in 5.0):
  189. e_info = dict(engine_uuid=self.ident, engine_id=self.int_id,
  190. method='execute')
  191. reply_content['engine_info'] = e_info
  192. # Return the execution counter so clients can display prompts
  193. reply_content['execution_count'] = shell.execution_count - 1
  194. if 'traceback' in reply_content:
  195. self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
  196. # At this point, we can tell whether the main code execution succeeded
  197. # or not. If it did, we proceed to evaluate user_expressions
  198. if reply_content['status'] == 'ok':
  199. reply_content[u'user_expressions'] = \
  200. shell.user_expressions(user_expressions or {})
  201. else:
  202. # If there was an error, don't even try to compute expressions
  203. reply_content[u'user_expressions'] = {}
  204. # Payloads should be retrieved regardless of outcome, so we can both
  205. # recover partial output (that could have been generated early in a
  206. # block, before an error) and always clear the payload system.
  207. reply_content[u'payload'] = shell.payload_manager.read_payload()
  208. # Be aggressive about clearing the payload because we don't want
  209. # it to sit in memory until the next execute_request comes in.
  210. shell.payload_manager.clear_payload()
  211. return reply_content
  212. def do_complete(self, code, cursor_pos):
  213. if _use_experimental_60_completion and self.use_experimental_completions:
  214. return self._experimental_do_complete(code, cursor_pos)
  215. # FIXME: IPython completers currently assume single line,
  216. # but completion messages give multi-line context
  217. # For now, extract line from cell, based on cursor_pos:
  218. if cursor_pos is None:
  219. cursor_pos = len(code)
  220. line, offset = line_at_cursor(code, cursor_pos)
  221. line_cursor = cursor_pos - offset
  222. txt, matches = self.shell.complete('', line, line_cursor)
  223. return {'matches' : matches,
  224. 'cursor_end' : cursor_pos,
  225. 'cursor_start' : cursor_pos - len(txt),
  226. 'metadata' : {},
  227. 'status' : 'ok'}
  228. def _experimental_do_complete(self, code, cursor_pos):
  229. """
  230. Experimental completions from IPython, using Jedi.
  231. """
  232. if cursor_pos is None:
  233. cursor_pos = len(code)
  234. with _provisionalcompleter():
  235. raw_completions = self.shell.Completer.completions(code, cursor_pos)
  236. completions = list(_rectify_completions(code, raw_completions))
  237. comps = []
  238. for comp in completions:
  239. comps.append(dict(
  240. start=comp.start,
  241. end=comp.end,
  242. text=comp.text,
  243. type=comp.type,
  244. ))
  245. if completions:
  246. s = completions[0].start
  247. e = completions[0].end
  248. matches = [c.text for c in completions]
  249. else:
  250. s = cursor_pos
  251. e = cursor_pos
  252. matches = []
  253. return {'matches': matches,
  254. 'cursor_end': e,
  255. 'cursor_start': s,
  256. 'metadata': {_EXPERIMENTAL_KEY_NAME: comps},
  257. 'status': 'ok'}
  258. def do_inspect(self, code, cursor_pos, detail_level=0):
  259. name = token_at_cursor(code, cursor_pos)
  260. info = self.shell.object_inspect(name)
  261. reply_content = {'status' : 'ok'}
  262. reply_content['data'] = data = {}
  263. reply_content['metadata'] = {}
  264. reply_content['found'] = info['found']
  265. if info['found']:
  266. info_text = self.shell.object_inspect_text(
  267. name,
  268. detail_level=detail_level,
  269. )
  270. data['text/plain'] = info_text
  271. return reply_content
  272. def do_history(self, hist_access_type, output, raw, session=0, start=0,
  273. stop=None, n=None, pattern=None, unique=False):
  274. if hist_access_type == 'tail':
  275. hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
  276. include_latest=True)
  277. elif hist_access_type == 'range':
  278. hist = self.shell.history_manager.get_range(session, start, stop,
  279. raw=raw, output=output)
  280. elif hist_access_type == 'search':
  281. hist = self.shell.history_manager.search(
  282. pattern, raw=raw, output=output, n=n, unique=unique)
  283. else:
  284. hist = []
  285. return {
  286. 'status': 'ok',
  287. 'history' : list(hist),
  288. }
  289. def do_shutdown(self, restart):
  290. self.shell.exit_now = True
  291. return dict(status='ok', restart=restart)
  292. def do_is_complete(self, code):
  293. status, indent_spaces = self.shell.input_splitter.check_complete(code)
  294. r = {'status': status}
  295. if status == 'incomplete':
  296. r['indent'] = ' ' * indent_spaces
  297. return r
  298. def do_apply(self, content, bufs, msg_id, reply_metadata):
  299. from .serialize import serialize_object, unpack_apply_message
  300. shell = self.shell
  301. try:
  302. working = shell.user_ns
  303. prefix = "_"+str(msg_id).replace("-","")+"_"
  304. f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
  305. fname = getattr(f, '__name__', 'f')
  306. fname = prefix+"f"
  307. argname = prefix+"args"
  308. kwargname = prefix+"kwargs"
  309. resultname = prefix+"result"
  310. ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
  311. # print ns
  312. working.update(ns)
  313. code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
  314. try:
  315. exec(code, shell.user_global_ns, shell.user_ns)
  316. result = working.get(resultname)
  317. finally:
  318. for key in ns:
  319. working.pop(key)
  320. result_buf = serialize_object(result,
  321. buffer_threshold=self.session.buffer_threshold,
  322. item_threshold=self.session.item_threshold,
  323. )
  324. except BaseException as e:
  325. # invoke IPython traceback formatting
  326. shell.showtraceback()
  327. reply_content = {
  328. u'traceback': shell._last_traceback or [],
  329. u'ename': unicode_type(type(e).__name__),
  330. u'evalue': safe_unicode(e),
  331. }
  332. # FIXME: deprecated piece for ipyparallel (remove in 5.0):
  333. e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
  334. reply_content['engine_info'] = e_info
  335. self.send_response(self.iopub_socket, u'error', reply_content,
  336. ident=self._topic('error'))
  337. self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
  338. result_buf = []
  339. reply_content['status'] = 'error'
  340. else:
  341. reply_content = {'status' : 'ok'}
  342. return reply_content, result_buf
  343. def do_clear(self):
  344. self.shell.reset(False)
  345. return dict(status='ok')
  346. # This exists only for backwards compatibility - use IPythonKernel instead
  347. class Kernel(IPythonKernel):
  348. def __init__(self, *args, **kwargs):
  349. import warnings
  350. warnings.warn('Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel',
  351. DeprecationWarning)
  352. super(Kernel, self).__init__(*args, **kwargs)