|
5 | 5 | # Copyright (c) Jupyter Development Team.
|
6 | 6 | # Distributed under the terms of the Modified BSD License.
|
7 | 7 |
|
| 8 | +from __future__ import print_function |
| 9 | + |
8 | 10 | try:
|
9 | 11 | from queue import Empty # Python 3
|
10 | 12 | except ImportError:
|
11 | 13 | from Queue import Empty # Python 2
|
| 14 | +import sys |
12 | 15 | import time
|
13 | 16 |
|
| 17 | +try: |
| 18 | + monotonic = time.monotonic |
| 19 | +except NameError: # py2 |
| 20 | + monotonic = time.clock # close enough |
| 21 | + |
14 | 22 | from traitlets import Type
|
15 | 23 | from jupyter_client.channels import HBChannel
|
16 | 24 | from jupyter_client.client import KernelClient
|
17 | 25 | from .channels import ZMQSocketChannel
|
18 | 26 |
|
| 27 | +try: |
| 28 | + TimeoutError |
| 29 | +except NameError: |
| 30 | + # py2 |
| 31 | + TimeoutError = RuntimeError |
| 32 | + |
19 | 33 |
|
20 | 34 | class BlockingKernelClient(KernelClient):
|
21 | 35 | """A BlockingKernelClient """
|
@@ -74,3 +88,111 @@ def wait_for_ready(self, timeout=None):
|
74 | 88 | iopub_channel_class = Type(ZMQSocketChannel)
|
75 | 89 | stdin_channel_class = Type(ZMQSocketChannel)
|
76 | 90 | hb_channel_class = Type(HBChannel)
|
| 91 | + |
| 92 | + def run(self, code, silent=False, store_history=True, |
| 93 | + user_expressions=None, stop_on_error=True, |
| 94 | + timeout=None, |
| 95 | + ): |
| 96 | + """Run code in the kernel, redisplaying output. |
| 97 | +
|
| 98 | + Wraps a call to `.execute`, capturing and redisplaying any output produced. |
| 99 | + The execute_reply is returned. |
| 100 | +
|
| 101 | + Parameters |
| 102 | + ---------- |
| 103 | + code : str |
| 104 | + A string of code in the kernel's language. |
| 105 | +
|
| 106 | + silent : bool, optional (default False) |
| 107 | + If set, the kernel will execute the code as quietly possible, and |
| 108 | + will force store_history to be False. |
| 109 | +
|
| 110 | + store_history : bool, optional (default True) |
| 111 | + If set, the kernel will store command history. This is forced |
| 112 | + to be False if silent is True. |
| 113 | +
|
| 114 | + user_expressions : dict, optional |
| 115 | + A dict mapping names to expressions to be evaluated in the user's |
| 116 | + dict. The expression values are returned as strings formatted using |
| 117 | + :func:`repr`. |
| 118 | +
|
| 119 | + stop_on_error: bool, optional (default True) |
| 120 | + Flag whether to abort the execution queue, if an exception is encountered. |
| 121 | + timeout: int or None (default None) |
| 122 | + Timeout (in seconds) to wait for output. If None, wait forever. |
| 123 | +
|
| 124 | + Returns |
| 125 | + ------- |
| 126 | + reply: dict |
| 127 | + The execute_reply message. |
| 128 | + """ |
| 129 | + if not self.iopub_channel.is_alive(): |
| 130 | + raise RuntimeError("IOPub channel must be running to receive output") |
| 131 | + msg_id = self.execute(code, |
| 132 | + silent=silent, |
| 133 | + store_history=store_history, |
| 134 | + user_expressions=user_expressions, |
| 135 | + stop_on_error=stop_on_error, |
| 136 | + ) |
| 137 | + |
| 138 | + if 'IPython' in sys.modules: |
| 139 | + from IPython import get_ipython |
| 140 | + ip = get_ipython() |
| 141 | + in_kernel = getattr(ip, 'kernel', False) |
| 142 | + if in_kernel: |
| 143 | + socket = ip.display_pub.pub_socket |
| 144 | + session = ip.display_pub.session |
| 145 | + parent_header = ip.display_pub.parent_header |
| 146 | + else: |
| 147 | + in_kernel = False |
| 148 | + |
| 149 | + # set deadline based on timeout |
| 150 | + start = monotonic() |
| 151 | + if timeout is not None: |
| 152 | + deadline = monotonic() + timeout |
| 153 | + |
| 154 | + # wait for output and redisplay it |
| 155 | + while True: |
| 156 | + if timeout is not None: |
| 157 | + timeout = max(0, deadline - monotonic()) |
| 158 | + try: |
| 159 | + msg = self.get_iopub_msg(timeout=timeout) |
| 160 | + except Empty: |
| 161 | + raise TimeoutError("Timeout waiting for IPython output") |
| 162 | + |
| 163 | + if msg['parent_header'].get('msg_id') != msg_id: |
| 164 | + # not from my request |
| 165 | + continue |
| 166 | + msg_type = msg['header']['msg_type'] |
| 167 | + content = msg['content'] |
| 168 | + if msg_type == 'status': |
| 169 | + if content['execution_state'] == 'idle': |
| 170 | + # idle means output is done |
| 171 | + break |
| 172 | + elif msg_type == 'stream': |
| 173 | + stream = getattr(sys, content['name']) |
| 174 | + stream.write(content['text']) |
| 175 | + elif msg_type in ('display_data', 'execute_result', 'error'): |
| 176 | + if in_kernel: |
| 177 | + session.send(socket, msg_type, content, parent=parent_header) |
| 178 | + else: |
| 179 | + if msg_type == 'error': |
| 180 | + print('\n'.join(content['traceback']), file=sys.stderr) |
| 181 | + else: |
| 182 | + sys.stdout.write(content['data'].get('text/plain', '')) |
| 183 | + else: |
| 184 | + pass |
| 185 | + |
| 186 | + # output is done, get the reply |
| 187 | + while True: |
| 188 | + if timeout is not None: |
| 189 | + timeout = max(0, deadline - monotonic()) |
| 190 | + try: |
| 191 | + reply = self.get_shell_msg(timeout=timeout) |
| 192 | + except Empty: |
| 193 | + raise TimeoutError("Timeout waiting for reply") |
| 194 | + if reply['parent_header'].get('msg_id') != msg_id: |
| 195 | + # not my reply, someone may have forgotten to retrieve theirs |
| 196 | + continue |
| 197 | + return reply |
| 198 | + |
0 commit comments