1"""The IPython kernel implementation""" 2 3import asyncio 4import builtins 5from contextlib import contextmanager 6from functools import partial 7import getpass 8import signal 9import sys 10 11from IPython.core import release 12from ipython_genutils.py3compat import safe_unicode 13from IPython.utils.tokenutil import token_at_cursor, line_at_cursor 14from tornado import gen 15from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat 16 17from .comm import CommManager 18from .kernelbase import Kernel as KernelBase 19from .zmqshell import ZMQInteractiveShell 20from .eventloops import _use_appnope 21 22try: 23 from IPython.core.interactiveshell import _asyncio_runner 24except ImportError: 25 _asyncio_runner = None 26 27try: 28 from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter 29 _use_experimental_60_completion = True 30except ImportError: 31 _use_experimental_60_completion = False 32 33_EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental' 34 35 36class IPythonKernel(KernelBase): 37 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', 38 allow_none=True) 39 shell_class = Type(ZMQInteractiveShell) 40 41 use_experimental_completions = Bool(True, 42 help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", 43 ).tag(config=True) 44 45 user_module = Any() 46 @observe('user_module') 47 @observe_compat 48 def _user_module_changed(self, change): 49 if self.shell is not None: 50 self.shell.user_module = change['new'] 51 52 user_ns = Instance(dict, args=None, allow_none=True) 53 @observe('user_ns') 54 @observe_compat 55 def _user_ns_changed(self, change): 56 if self.shell is not None: 57 self.shell.user_ns = change['new'] 58 self.shell.init_user_ns() 59 60 # A reference to the Python builtin 'raw_input' function. 61 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3) 62 _sys_raw_input = Any() 63 _sys_eval_input = Any() 64 65 def __init__(self, **kwargs): 66 super(IPythonKernel, self).__init__(**kwargs) 67 68 # Initialize the InteractiveShell subclass 69 self.shell = self.shell_class.instance(parent=self, 70 profile_dir = self.profile_dir, 71 user_module = self.user_module, 72 user_ns = self.user_ns, 73 kernel = self, 74 ) 75 self.shell.displayhook.session = self.session 76 self.shell.displayhook.pub_socket = self.iopub_socket 77 self.shell.displayhook.topic = self._topic('execute_result') 78 self.shell.display_pub.session = self.session 79 self.shell.display_pub.pub_socket = self.iopub_socket 80 81 self.comm_manager = CommManager(parent=self, kernel=self) 82 83 self.shell.configurables.append(self.comm_manager) 84 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ] 85 for msg_type in comm_msg_types: 86 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) 87 88 if _use_appnope() and self._darwin_app_nap: 89 # Disable app-nap as the kernel is not a gui but can have guis 90 import appnope 91 appnope.nope() 92 93 help_links = List([ 94 { 95 'text': "Python Reference", 96 'url': "https://docs.python.org/%i.%i" % sys.version_info[:2], 97 }, 98 { 99 'text': "IPython Reference", 100 'url': "https://ipython.org/documentation.html", 101 }, 102 { 103 'text': "NumPy Reference", 104 'url': "https://docs.scipy.org/doc/numpy/reference/", 105 }, 106 { 107 'text': "SciPy Reference", 108 'url': "https://docs.scipy.org/doc/scipy/reference/", 109 }, 110 { 111 'text': "Matplotlib Reference", 112 'url': "https://matplotlib.org/contents.html", 113 }, 114 { 115 'text': "SymPy Reference", 116 'url': "http://docs.sympy.org/latest/index.html", 117 }, 118 { 119 'text': "pandas Reference", 120 'url': "https://pandas.pydata.org/pandas-docs/stable/", 121 }, 122 ]).tag(config=True) 123 124 # Kernel info fields 125 implementation = 'ipython' 126 implementation_version = release.version 127 language_info = { 128 'name': 'python', 129 'version': sys.version.split()[0], 130 'mimetype': 'text/x-python', 131 'codemirror_mode': { 132 'name': 'ipython', 133 'version': sys.version_info[0] 134 }, 135 'pygments_lexer': 'ipython%d' % 3, 136 'nbconvert_exporter': 'python', 137 'file_extension': '.py' 138 } 139 140 @property 141 def banner(self): 142 return self.shell.banner 143 144 def start(self): 145 self.shell.exit_now = False 146 super(IPythonKernel, self).start() 147 148 def set_parent(self, ident, parent): 149 """Overridden from parent to tell the display hook and output streams 150 about the parent message. 151 """ 152 super(IPythonKernel, self).set_parent(ident, parent) 153 self.shell.set_parent(parent) 154 155 def init_metadata(self, parent): 156 """Initialize metadata. 157 158 Run at the beginning of each execution request. 159 """ 160 md = super(IPythonKernel, self).init_metadata(parent) 161 # FIXME: remove deprecated ipyparallel-specific code 162 # This is required for ipyparallel < 5.0 163 md.update({ 164 'dependencies_met' : True, 165 'engine' : self.ident, 166 }) 167 return md 168 169 def finish_metadata(self, parent, metadata, reply_content): 170 """Finish populating metadata. 171 172 Run after completing an execution request. 173 """ 174 # FIXME: remove deprecated ipyparallel-specific code 175 # This is required by ipyparallel < 5.0 176 metadata['status'] = reply_content['status'] 177 if reply_content['status'] == 'error' and reply_content['ename'] == 'UnmetDependency': 178 metadata['dependencies_met'] = False 179 180 return metadata 181 182 def _forward_input(self, allow_stdin=False): 183 """Forward raw_input and getpass to the current frontend. 184 185 via input_request 186 """ 187 self._allow_stdin = allow_stdin 188 189 self._sys_raw_input = builtins.input 190 builtins.input = self.raw_input 191 192 self._save_getpass = getpass.getpass 193 getpass.getpass = self.getpass 194 195 def _restore_input(self): 196 """Restore raw_input, getpass""" 197 builtins.input = self._sys_raw_input 198 199 getpass.getpass = self._save_getpass 200 201 @property 202 def execution_count(self): 203 return self.shell.execution_count 204 205 @execution_count.setter 206 def execution_count(self, value): 207 # Ignore the incrementing done by KernelBase, in favour of our shell's 208 # execution counter. 209 pass 210 211 @contextmanager 212 def _cancel_on_sigint(self, future): 213 """ContextManager for capturing SIGINT and cancelling a future 214 215 SIGINT raises in the event loop when running async code, 216 but we want it to halt a coroutine. 217 218 Ideally, it would raise KeyboardInterrupt, 219 but this turns it into a CancelledError. 220 At least it gets a decent traceback to the user. 221 """ 222 sigint_future = asyncio.Future() 223 224 # whichever future finishes first, 225 # cancel the other one 226 def cancel_unless_done(f, _ignored): 227 if f.cancelled() or f.done(): 228 return 229 f.cancel() 230 231 # when sigint finishes, 232 # abort the coroutine with CancelledError 233 sigint_future.add_done_callback( 234 partial(cancel_unless_done, future) 235 ) 236 # when the main future finishes, 237 # stop watching for SIGINT events 238 future.add_done_callback( 239 partial(cancel_unless_done, sigint_future) 240 ) 241 242 def handle_sigint(*args): 243 def set_sigint_result(): 244 if sigint_future.cancelled() or sigint_future.done(): 245 return 246 sigint_future.set_result(1) 247 # use add_callback for thread safety 248 self.io_loop.add_callback(set_sigint_result) 249 250 # set the custom sigint hander during this context 251 save_sigint = signal.signal(signal.SIGINT, handle_sigint) 252 try: 253 yield 254 finally: 255 # restore the previous sigint handler 256 signal.signal(signal.SIGINT, save_sigint) 257 258 @gen.coroutine 259 def do_execute(self, code, silent, store_history=True, 260 user_expressions=None, allow_stdin=False): 261 shell = self.shell # we'll need this a lot here 262 263 self._forward_input(allow_stdin) 264 265 reply_content = {} 266 if hasattr(shell, 'run_cell_async') and hasattr(shell, 'should_run_async'): 267 run_cell = shell.run_cell_async 268 should_run_async = shell.should_run_async 269 else: 270 should_run_async = lambda cell: False 271 # older IPython, 272 # use blocking run_cell and wrap it in coroutine 273 @gen.coroutine 274 def run_cell(*args, **kwargs): 275 return shell.run_cell(*args, **kwargs) 276 try: 277 278 # default case: runner is asyncio and asyncio is already running 279 # TODO: this should check every case for "are we inside the runner", 280 # not just asyncio 281 if ( 282 _asyncio_runner 283 and should_run_async(code) 284 and shell.loop_runner is _asyncio_runner 285 and asyncio.get_event_loop().is_running() 286 ): 287 coro = run_cell(code, store_history=store_history, silent=silent) 288 coro_future = asyncio.ensure_future(coro) 289 290 with self._cancel_on_sigint(coro_future): 291 res = None 292 try: 293 res = yield coro_future 294 finally: 295 shell.events.trigger('post_execute') 296 if not silent: 297 shell.events.trigger('post_run_cell', res) 298 else: 299 # runner isn't already running, 300 # make synchronous call, 301 # letting shell dispatch to loop runners 302 res = shell.run_cell(code, store_history=store_history, silent=silent) 303 finally: 304 self._restore_input() 305 306 if res.error_before_exec is not None: 307 err = res.error_before_exec 308 else: 309 err = res.error_in_exec 310 311 if res.success: 312 reply_content['status'] = 'ok' 313 else: 314 reply_content['status'] = 'error' 315 316 reply_content.update({ 317 'traceback': shell._last_traceback or [], 318 'ename': str(type(err).__name__), 319 'evalue': safe_unicode(err), 320 }) 321 322 # FIXME: deprecated piece for ipyparallel (remove in 5.0): 323 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, 324 method='execute') 325 reply_content['engine_info'] = e_info 326 327 328 # Return the execution counter so clients can display prompts 329 reply_content['execution_count'] = shell.execution_count - 1 330 331 if 'traceback' in reply_content: 332 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback'])) 333 334 335 # At this point, we can tell whether the main code execution succeeded 336 # or not. If it did, we proceed to evaluate user_expressions 337 if reply_content['status'] == 'ok': 338 reply_content['user_expressions'] = \ 339 shell.user_expressions(user_expressions or {}) 340 else: 341 # If there was an error, don't even try to compute expressions 342 reply_content['user_expressions'] = {} 343 344 # Payloads should be retrieved regardless of outcome, so we can both 345 # recover partial output (that could have been generated early in a 346 # block, before an error) and always clear the payload system. 347 reply_content['payload'] = shell.payload_manager.read_payload() 348 # Be aggressive about clearing the payload because we don't want 349 # it to sit in memory until the next execute_request comes in. 350 shell.payload_manager.clear_payload() 351 352 return reply_content 353 354 def do_complete(self, code, cursor_pos): 355 if _use_experimental_60_completion and self.use_experimental_completions: 356 return self._experimental_do_complete(code, cursor_pos) 357 358 # FIXME: IPython completers currently assume single line, 359 # but completion messages give multi-line context 360 # For now, extract line from cell, based on cursor_pos: 361 if cursor_pos is None: 362 cursor_pos = len(code) 363 line, offset = line_at_cursor(code, cursor_pos) 364 line_cursor = cursor_pos - offset 365 366 txt, matches = self.shell.complete('', line, line_cursor) 367 return {'matches' : matches, 368 'cursor_end' : cursor_pos, 369 'cursor_start' : cursor_pos - len(txt), 370 'metadata' : {}, 371 'status' : 'ok'} 372 373 def _experimental_do_complete(self, code, cursor_pos): 374 """ 375 Experimental completions from IPython, using Jedi. 376 """ 377 if cursor_pos is None: 378 cursor_pos = len(code) 379 with _provisionalcompleter(): 380 raw_completions = self.shell.Completer.completions(code, cursor_pos) 381 completions = list(_rectify_completions(code, raw_completions)) 382 383 comps = [] 384 for comp in completions: 385 comps.append(dict( 386 start=comp.start, 387 end=comp.end, 388 text=comp.text, 389 type=comp.type, 390 )) 391 392 if completions: 393 s = completions[0].start 394 e = completions[0].end 395 matches = [c.text for c in completions] 396 else: 397 s = cursor_pos 398 e = cursor_pos 399 matches = [] 400 401 return {'matches': matches, 402 'cursor_end': e, 403 'cursor_start': s, 404 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, 405 'status': 'ok'} 406 407 408 409 def do_inspect(self, code, cursor_pos, detail_level=0): 410 name = token_at_cursor(code, cursor_pos) 411 412 reply_content = {'status' : 'ok'} 413 reply_content['data'] = {} 414 reply_content['metadata'] = {} 415 try: 416 reply_content['data'].update( 417 self.shell.object_inspect_mime( 418 name, 419 detail_level=detail_level 420 ) 421 ) 422 if not self.shell.enable_html_pager: 423 reply_content['data'].pop('text/html') 424 reply_content['found'] = True 425 except KeyError: 426 reply_content['found'] = False 427 428 return reply_content 429 430 def do_history(self, hist_access_type, output, raw, session=0, start=0, 431 stop=None, n=None, pattern=None, unique=False): 432 if hist_access_type == 'tail': 433 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, 434 include_latest=True) 435 436 elif hist_access_type == 'range': 437 hist = self.shell.history_manager.get_range(session, start, stop, 438 raw=raw, output=output) 439 440 elif hist_access_type == 'search': 441 hist = self.shell.history_manager.search( 442 pattern, raw=raw, output=output, n=n, unique=unique) 443 else: 444 hist = [] 445 446 return { 447 'status': 'ok', 448 'history' : list(hist), 449 } 450 451 def do_shutdown(self, restart): 452 self.shell.exit_now = True 453 return dict(status='ok', restart=restart) 454 455 def do_is_complete(self, code): 456 transformer_manager = getattr(self.shell, 'input_transformer_manager', None) 457 if transformer_manager is None: 458 # input_splitter attribute is deprecated 459 transformer_manager = self.shell.input_splitter 460 status, indent_spaces = transformer_manager.check_complete(code) 461 r = {'status': status} 462 if status == 'incomplete': 463 r['indent'] = ' ' * indent_spaces 464 return r 465 466 def do_apply(self, content, bufs, msg_id, reply_metadata): 467 from .serialize import serialize_object, unpack_apply_message 468 shell = self.shell 469 try: 470 working = shell.user_ns 471 472 prefix = "_"+str(msg_id).replace("-","")+"_" 473 474 f,args,kwargs = unpack_apply_message(bufs, working, copy=False) 475 476 fname = getattr(f, '__name__', 'f') 477 478 fname = prefix+"f" 479 argname = prefix+"args" 480 kwargname = prefix+"kwargs" 481 resultname = prefix+"result" 482 483 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None } 484 # print ns 485 working.update(ns) 486 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname) 487 try: 488 exec(code, shell.user_global_ns, shell.user_ns) 489 result = working.get(resultname) 490 finally: 491 for key in ns: 492 working.pop(key) 493 494 result_buf = serialize_object(result, 495 buffer_threshold=self.session.buffer_threshold, 496 item_threshold=self.session.item_threshold, 497 ) 498 499 except BaseException as e: 500 # invoke IPython traceback formatting 501 shell.showtraceback() 502 reply_content = { 503 'traceback': shell._last_traceback or [], 504 'ename': str(type(e).__name__), 505 'evalue': safe_unicode(e), 506 } 507 # FIXME: deprecated piece for ipyparallel (remove in 5.0): 508 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') 509 reply_content['engine_info'] = e_info 510 511 self.send_response(self.iopub_socket, 'error', reply_content, 512 ident=self._topic('error')) 513 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) 514 result_buf = [] 515 reply_content['status'] = 'error' 516 else: 517 reply_content = {'status' : 'ok'} 518 519 return reply_content, result_buf 520 521 def do_clear(self): 522 self.shell.reset(False) 523 return dict(status='ok') 524 525 526# This exists only for backwards compatibility - use IPythonKernel instead 527 528class Kernel(IPythonKernel): 529 def __init__(self, *args, **kwargs): 530 import warnings 531 warnings.warn('Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel', 532 DeprecationWarning) 533 super(Kernel, self).__init__(*args, **kwargs) 534