conkernelclient 0.0.19__tar.gz → 0.0.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {conkernelclient-0.0.19/conkernelclient.egg-info → conkernelclient-0.0.20}/PKG-INFO +2 -2
- conkernelclient-0.0.20/conkernelclient/__init__.py +4 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient/_modidx.py +9 -17
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient/core.py +88 -90
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient/ops.py +24 -87
- {conkernelclient-0.0.19 → conkernelclient-0.0.20/conkernelclient.egg-info}/PKG-INFO +2 -2
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/requires.txt +1 -1
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/pyproject.toml +1 -1
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/tests/test_races.py +1 -1
- conkernelclient-0.0.19/conkernelclient/__init__.py +0 -3
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/LICENSE +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/MANIFEST.in +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/README.md +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/SOURCES.txt +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/dependency_links.txt +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/entry_points.txt +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/top_level.txt +0 -0
- {conkernelclient-0.0.19 → conkernelclient-0.0.20}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conkernelclient
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.20
|
|
4
4
|
Summary: Concurrent-safe Jupyter KernelClient
|
|
5
5
|
Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: jupyter_client
|
|
16
16
|
Requires-Dist: fastcore>=2.1.17
|
|
17
|
-
Requires-Dist: jupywire>=0.1.
|
|
17
|
+
Requires-Dist: jupywire>=0.1.5
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: ipykernel; extra == "dev"
|
|
20
20
|
Requires-Dist: ipymini>=0.1.17; extra == "dev"
|
|
@@ -8,20 +8,22 @@ d = { 'settings': { 'branch': 'main',
|
|
|
8
8
|
'syms': { 'conkernelclient.core': { 'conkernelclient.core.ConKernelClient': ('core.html#conkernelclient', 'conkernelclient/core.py'),
|
|
9
9
|
'conkernelclient.core.ConKernelClient.__init__': ( 'core.html#conkernelclient.__init__',
|
|
10
10
|
'conkernelclient/core.py'),
|
|
11
|
-
'conkernelclient.core.ConKernelClient._async_recv_reply': ( 'core.html#conkernelclient._async_recv_reply',
|
|
12
|
-
'conkernelclient/core.py'),
|
|
13
11
|
'conkernelclient.core.ConKernelClient._check_alive': ( 'core.html#conkernelclient._check_alive',
|
|
14
12
|
'conkernelclient/core.py'),
|
|
15
|
-
'conkernelclient.core.ConKernelClient.
|
|
16
|
-
|
|
13
|
+
'conkernelclient.core.ConKernelClient._pump': ( 'core.html#conkernelclient._pump',
|
|
14
|
+
'conkernelclient/core.py'),
|
|
15
|
+
'conkernelclient.core.ConKernelClient._route': ( 'core.html#conkernelclient._route',
|
|
16
|
+
'conkernelclient/core.py'),
|
|
17
|
+
'conkernelclient.core.ConKernelClient._watch_hb': ( 'core.html#conkernelclient._watch_hb',
|
|
18
|
+
'conkernelclient/core.py'),
|
|
17
19
|
'conkernelclient.core.ConKernelClient.astop_channels': ( 'core.html#conkernelclient.astop_channels',
|
|
18
20
|
'conkernelclient/core.py'),
|
|
19
21
|
'conkernelclient.core.ConKernelClient.execute': ( 'core.html#conkernelclient.execute',
|
|
20
22
|
'conkernelclient/core.py'),
|
|
23
|
+
'conkernelclient.core.ConKernelClient.send': ( 'core.html#conkernelclient.send',
|
|
24
|
+
'conkernelclient/core.py'),
|
|
21
25
|
'conkernelclient.core.ConKernelClient.start_channels': ( 'core.html#conkernelclient.start_channels',
|
|
22
26
|
'conkernelclient/core.py'),
|
|
23
|
-
'conkernelclient.core.ConKernelClient.stdin_send': ( 'core.html#conkernelclient.stdin_send',
|
|
24
|
-
'conkernelclient/core.py'),
|
|
25
27
|
'conkernelclient.core.ConKernelClient.stop_channels': ( 'core.html#conkernelclient.stop_channels',
|
|
26
28
|
'conkernelclient/core.py'),
|
|
27
29
|
'conkernelclient.core.ConKernelClient.wait_for_ready': ( 'core.html#conkernelclient.wait_for_ready',
|
|
@@ -31,7 +33,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
31
33
|
'conkernelclient/core.py'),
|
|
32
34
|
'conkernelclient.core.ConKernelManager._transport_encryption_default': ( 'core.html#conkernelmanager._transport_encryption_default',
|
|
33
35
|
'conkernelclient/core.py'),
|
|
34
|
-
'conkernelclient.core.
|
|
36
|
+
'conkernelclient.core._dead_status': ('core.html#_dead_status', 'conkernelclient/core.py'),
|
|
35
37
|
'conkernelclient.core._send': ('core.html#_send', 'conkernelclient/core.py'),
|
|
36
38
|
'conkernelclient.core.apply_session_patch': ( 'core.html#apply_session_patch',
|
|
37
39
|
'conkernelclient/core.py')},
|
|
@@ -42,14 +44,10 @@ d = { 'settings': { 'branch': 'main',
|
|
|
42
44
|
'conkernelclient/ops.py'),
|
|
43
45
|
'conkernelclient.ops.ConKernelClient.ctl': ('ops.html#conkernelclient.ctl', 'conkernelclient/ops.py'),
|
|
44
46
|
'conkernelclient.ops.ConKernelClient.dap': ('ops.html#conkernelclient.dap', 'conkernelclient/ops.py'),
|
|
45
|
-
'conkernelclient.ops.ConKernelClient.eval_expr': ( 'ops.html#conkernelclient.eval_expr',
|
|
46
|
-
'conkernelclient/ops.py'),
|
|
47
47
|
'conkernelclient.ops.ConKernelClient.exec_drain': ( 'ops.html#conkernelclient.exec_drain',
|
|
48
48
|
'conkernelclient/ops.py'),
|
|
49
49
|
'conkernelclient.ops.ConKernelClient.exec_ok': ( 'ops.html#conkernelclient.exec_ok',
|
|
50
50
|
'conkernelclient/ops.py'),
|
|
51
|
-
'conkernelclient.ops.ConKernelClient.exec_outs': ( 'ops.html#conkernelclient.exec_outs',
|
|
52
|
-
'conkernelclient/ops.py'),
|
|
53
51
|
'conkernelclient.ops.ConKernelClient.input_reply': ( 'ops.html#conkernelclient.input_reply',
|
|
54
52
|
'conkernelclient/ops.py'),
|
|
55
53
|
'conkernelclient.ops.ConKernelClient.interrupt': ( 'ops.html#conkernelclient.interrupt',
|
|
@@ -58,13 +56,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
58
56
|
'conkernelclient/ops.py'),
|
|
59
57
|
'conkernelclient.ops.ConKernelClient.iopub_flush': ( 'ops.html#conkernelclient.iopub_flush',
|
|
60
58
|
'conkernelclient/ops.py'),
|
|
61
|
-
'conkernelclient.ops.ConKernelClient.reply': ( 'ops.html#conkernelclient.reply',
|
|
62
|
-
'conkernelclient/ops.py'),
|
|
63
59
|
'conkernelclient.ops.ConKernelClient.shell_request': ( 'ops.html#conkernelclient.shell_request',
|
|
64
60
|
'conkernelclient/ops.py'),
|
|
65
|
-
'conkernelclient.ops.ConKernelClient.user_exprs': ( 'ops.html#conkernelclient.user_exprs',
|
|
66
|
-
'conkernelclient/ops.py'),
|
|
67
|
-
'conkernelclient.ops.EvalError': ('ops.html#evalerror', 'conkernelclient/ops.py'),
|
|
68
61
|
'conkernelclient.ops._DapProxy': ('ops.html#_dapproxy', 'conkernelclient/ops.py'),
|
|
69
62
|
'conkernelclient.ops._DapProxy.__getattr__': ( 'ops.html#_dapproxy.__getattr__',
|
|
70
63
|
'conkernelclient/ops.py'),
|
|
@@ -75,6 +68,5 @@ d = { 'settings': { 'branch': 'main',
|
|
|
75
68
|
'conkernelclient.ops._ReqProxy.__init__': ('ops.html#_reqproxy.__init__', 'conkernelclient/ops.py'),
|
|
76
69
|
'conkernelclient.ops.iopub_streams': ('ops.html#iopub_streams', 'conkernelclient/ops.py'),
|
|
77
70
|
'conkernelclient.ops.iter_timeout': ('ops.html#iter_timeout', 'conkernelclient/ops.py'),
|
|
78
|
-
'conkernelclient.ops.parse_expr': ('ops.html#parse_expr', 'conkernelclient/ops.py'),
|
|
79
71
|
'conkernelclient.ops.reconnect': ('ops.html#reconnect', 'conkernelclient/ops.py'),
|
|
80
72
|
'conkernelclient.ops.run_kernel': ('ops.html#run_kernel', 'conkernelclient/ops.py')}}}
|
|
@@ -5,7 +5,7 @@ Docs: https://AnswerDotAI.github.io/conkernelclient/core.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['
|
|
8
|
+
__all__ = ['apply_session_patch', 'ConKernelClient', 'ConKernelManager']
|
|
9
9
|
|
|
10
10
|
# %% ../nbs/00_core.ipynb #d3b521c0
|
|
11
11
|
from jupyter_client import AsyncKernelClient, AsyncKernelManager
|
|
@@ -14,15 +14,13 @@ from zmq.error import ZMQError
|
|
|
14
14
|
from traitlets import Type, default
|
|
15
15
|
from fastcore.utils import patch
|
|
16
16
|
from jupywire.ops import EvalOps
|
|
17
|
+
from jupywire.route import RouterOps, DeadKernelError
|
|
17
18
|
from queue import Empty
|
|
18
|
-
import asyncio, zmq.asyncio, time, logging
|
|
19
|
+
import asyncio, inspect, zmq.asyncio, time, logging
|
|
19
20
|
|
|
20
21
|
# %% ../nbs/00_core.ipynb #737a0fc1
|
|
21
22
|
_log = logging.getLogger(__name__)
|
|
22
23
|
|
|
23
|
-
# %% ../nbs/00_core.ipynb #a093006c
|
|
24
|
-
class DeadKernelError(Exception): pass
|
|
25
|
-
|
|
26
24
|
# %% ../nbs/00_core.ipynb #374b75d0
|
|
27
25
|
def _send(self, stream, msg_or_type, content=None, parent=None, ident=None,
|
|
28
26
|
buffers=None, track=False, header=None, metadata=None):
|
|
@@ -48,7 +46,12 @@ def apply_session_patch():
|
|
|
48
46
|
Session.send = _send
|
|
49
47
|
|
|
50
48
|
# %% ../nbs/00_core.ipynb #d6a5fa6a
|
|
51
|
-
|
|
49
|
+
def _dead_status():
|
|
50
|
+
"The dead `status` rustygate broadcasts, synthesized client-side for zmq transport loss and heartbeat silence"
|
|
51
|
+
return dict(channel='iopub', msg_type='status', header={}, parent_header={}, content=dict(execution_state='dead'), buffers=[])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ConKernelClient(RouterOps, EvalOps, AsyncKernelClient):
|
|
52
55
|
def __init__(self, *args, **kwargs):
|
|
53
56
|
apply_session_patch()
|
|
54
57
|
# jupyter_client's `get_connection_info` returns curve keys as str, but the client traits are `Bytes`
|
|
@@ -56,86 +59,81 @@ class ConKernelClient(EvalOps, AsyncKernelClient):
|
|
|
56
59
|
if isinstance(kwargs.get(k), str): kwargs[k] = kwargs[k].encode()
|
|
57
60
|
super().__init__(*args, **kwargs)
|
|
58
61
|
|
|
59
|
-
def _fail_pending(self, exc:Exception, skip=None):
|
|
60
|
-
for k,(q,_) in list(getattr(self, '_pending', {}).items()):
|
|
61
|
-
if k != skip:
|
|
62
|
-
try: q.put_nowait(exc)
|
|
63
|
-
except asyncio.QueueFull: pass
|
|
64
|
-
|
|
65
62
|
def _check_alive(self):
|
|
66
63
|
if not self.channels_running: raise RuntimeError("Channels not running")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
64
|
+
pumps = getattr(self, '_pumps', None)
|
|
65
|
+
if not pumps or any(t.done() for t in pumps): raise DeadKernelError('kernel died')
|
|
66
|
+
|
|
67
|
+
# %% ../nbs/00_core.ipynb #fd6e6aa9
|
|
68
|
+
@patch
|
|
69
|
+
async def _route(self:ConKernelClient, msg):
|
|
70
|
+
r = self.route(msg)
|
|
71
|
+
if inspect.isawaitable(r): await r
|
|
72
|
+
|
|
73
|
+
@patch
|
|
74
|
+
async def _pump(self:ConKernelClient, name):
|
|
75
|
+
"Drain one zmq channel into the router; a socket error is transport loss, delivered as the dead status."
|
|
76
|
+
ch = getattr(self, f'{name}_channel')
|
|
77
|
+
try:
|
|
78
|
+
while True: await self._route(dict(await ch.get_msg(timeout=None), channel=name))
|
|
79
|
+
except asyncio.CancelledError: raise
|
|
80
|
+
except Exception as e:
|
|
81
|
+
_log.warning(f"{name} pump died: {e}")
|
|
82
|
+
await self._route(_dead_status())
|
|
83
|
+
|
|
84
|
+
@patch
|
|
85
|
+
async def _watch_hb(self:ConKernelClient):
|
|
86
|
+
"Declare death after three consecutive unanswered heartbeat echoes; the kernel echoes from a dedicated thread, so sustained silence means the process is gone."
|
|
87
|
+
misses = 0
|
|
88
|
+
while True:
|
|
89
|
+
await asyncio.sleep(self.hb_channel.time_to_dead)
|
|
90
|
+
misses = 0 if self.hb_channel.is_beating() else misses + 1
|
|
91
|
+
if misses >= 3: return await self._route(_dead_status())
|
|
92
|
+
|
|
93
|
+
# %% ../nbs/00_core.ipynb #394db3b2
|
|
94
|
+
@patch
|
|
95
|
+
async def start_channels(self:ConKernelClient, shell:bool=True, iopub:bool=True, stdin:bool=True, hb:bool=True, control:bool=True):
|
|
96
|
+
"Start channels, wait for ready, then launch the router pumps and the heartbeat watcher"
|
|
97
|
+
super(ConKernelClient, self).start_channels(shell=shell, iopub=iopub, stdin=stdin, hb=hb, control=control)
|
|
98
|
+
await self.wait_for_ready()
|
|
99
|
+
self._init_router()
|
|
100
|
+
chans = [c for c, on in (('shell',shell), ('iopub',iopub), ('stdin',stdin), ('control',control)) if on]
|
|
101
|
+
self._pumps = [asyncio.create_task(self._pump(c)) for c in chans]
|
|
102
|
+
if hb: self._pumps.append(asyncio.create_task(self._watch_hb()))
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
@patch
|
|
106
|
+
def stop_channels(self:ConKernelClient):
|
|
107
|
+
"Stop channels and cancel the router pumps"
|
|
108
|
+
if getattr(self, '_pumps', None): self.fail_waiters(RuntimeError("Channels stopped before reply"))
|
|
109
|
+
super(ConKernelClient, self).stop_channels()
|
|
110
|
+
for t in getattr(self, '_pumps', []): t.cancel()
|
|
111
|
+
self._pumps = []
|
|
112
|
+
|
|
113
|
+
# %% ../nbs/00_core.ipynb #a930c688
|
|
114
|
+
@patch
|
|
115
|
+
def send(self:ConKernelClient, msg, channel):
|
|
116
|
+
"Transmit a built message dict on `channel` (buffers ride as extra frames); a closed socket raises `DeadKernelError`."
|
|
117
|
+
ch = getattr(self, f'{channel}_channel')
|
|
118
|
+
try:
|
|
119
|
+
if msg.get('buffers'): self.session.send(ch.socket, msg, buffers=msg['buffers'])
|
|
120
|
+
else: ch.send(msg)
|
|
121
|
+
except (AssertionError, OSError, ZMQError) as e: raise DeadKernelError(f"Kernel socket closed: {e}") from e
|
|
122
|
+
|
|
123
|
+
@patch
|
|
124
|
+
def execute(self:ConKernelClient, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None, stop_on_error=True,
|
|
125
|
+
msg_id=None, metadata=None, subshell_id=None, buffers=None):
|
|
126
|
+
"Send an `execute_request`, fire-and-forget; returns its msg_id"
|
|
127
|
+
self._check_alive()
|
|
128
|
+
if user_expressions is None: user_expressions = {}
|
|
129
|
+
if allow_stdin is None: allow_stdin = self.allow_stdin
|
|
130
|
+
msg = self.session.msg("execute_request", dict(code=code, silent=silent, store_history=store_history,
|
|
131
|
+
user_expressions=user_expressions, allow_stdin=allow_stdin, stop_on_error=stop_on_error), metadata=metadata)
|
|
132
|
+
if buffers: msg["buffers"] = buffers
|
|
133
|
+
if msg_id is not None: msg["header"]["msg_id"] = msg_id
|
|
134
|
+
if subshell_id is not None: msg["header"]["subshell_id"] = subshell_id
|
|
135
|
+
self.send(msg, 'shell')
|
|
136
|
+
return msg["header"]["msg_id"]
|
|
139
137
|
|
|
140
138
|
# %% ../nbs/00_core.ipynb #3771c654
|
|
141
139
|
@patch
|
|
@@ -174,13 +172,13 @@ async def wait_for_ready(self:ConKernelClient, timeout=None):
|
|
|
174
172
|
# %% ../nbs/00_core.ipynb #967c10a1
|
|
175
173
|
@patch
|
|
176
174
|
async def astop_channels(self:ConKernelClient):
|
|
177
|
-
"Stop channels and cancel the
|
|
178
|
-
self.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try: await
|
|
175
|
+
"Stop channels and cancel the router pumps, awaiting their exit"
|
|
176
|
+
if getattr(self, '_pumps', None): self.fail_waiters(RuntimeError("Channels stopped before reply"))
|
|
177
|
+
for t in getattr(self, '_pumps', []):
|
|
178
|
+
t.cancel()
|
|
179
|
+
try: await t
|
|
182
180
|
except asyncio.CancelledError: pass
|
|
183
|
-
|
|
181
|
+
self._pumps = []
|
|
184
182
|
super(ConKernelClient, self).stop_channels()
|
|
185
183
|
|
|
186
184
|
# %% ../nbs/00_core.ipynb #b828c222
|
|
@@ -5,21 +5,17 @@ Docs: https://AnswerDotAI.github.io/conkernelclient/ops.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_ops.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['default_timeout', 'iter_timeout', 'iopub_streams', '
|
|
9
|
-
'iopub_msgs', 'output_types']
|
|
8
|
+
__all__ = ['default_timeout', 'iter_timeout', 'iopub_streams', 'run_kernel', 'reconnect']
|
|
10
9
|
|
|
11
10
|
# %% ../nbs/01_ops.ipynb #23936f1c
|
|
12
11
|
from fastcore.utils import *
|
|
13
|
-
from fastcore.ansi import strip_ansi
|
|
14
|
-
from fastcore.nbio import msgs2outs, preferred_out
|
|
15
12
|
from .core import ConKernelClient, ConKernelManager, DeadKernelError
|
|
16
|
-
from jupywire.ops import parent_id, iopub_msgs
|
|
13
|
+
from jupywire.ops import parent_id, iopub_msgs
|
|
17
14
|
from jupyter_client.kernelspec import KernelSpec
|
|
18
|
-
_all_ = ['parent_id', 'iopub_msgs', 'output_types']
|
|
19
15
|
from contextlib import asynccontextmanager
|
|
20
|
-
from ast import literal_eval
|
|
21
16
|
from queue import Empty
|
|
22
|
-
import
|
|
17
|
+
import time
|
|
18
|
+
|
|
23
19
|
|
|
24
20
|
# %% ../nbs/01_ops.ipynb #d62f6b51
|
|
25
21
|
default_timeout = 10
|
|
@@ -31,11 +27,19 @@ def iter_timeout(timeout=None, default=default_timeout):
|
|
|
31
27
|
|
|
32
28
|
# %% ../nbs/01_ops.ipynb #ad596b2b
|
|
33
29
|
@patch
|
|
30
|
+
async def exec_drain(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
31
|
+
"Execute `code`; return `(reply, outputs)` where outputs are the request's iopub messages"
|
|
32
|
+
msgs = await self.run(code, timeout=timeout, **kw)
|
|
33
|
+
reply = next(m for m in msgs if m['msg_type'] == 'execute_reply')
|
|
34
|
+
return reply, [m for m in msgs if m['channel'] == 'iopub']
|
|
35
|
+
|
|
36
|
+
# %% ../nbs/01_ops.ipynb #113e926f
|
|
37
|
+
@patch
|
|
34
38
|
async def iopub_drain(self:ConKernelClient, msg_id, timeout=default_timeout):
|
|
35
|
-
"Collect iopub messages parented to `msg_id` until its idle status arrives. Other requests' messages are discarded: one drain at a time"
|
|
39
|
+
"Collect unmatched iopub messages parented to `msg_id` from the attached `JmsgQueues` until its idle status arrives. Other requests' messages are discarded: one drain at a time"
|
|
36
40
|
res = []
|
|
37
41
|
for rem in iter_timeout(timeout):
|
|
38
|
-
try: msg = await self.
|
|
42
|
+
try: msg = await self.jmsgq.get('iopub', timeout=rem)
|
|
39
43
|
except Empty: continue
|
|
40
44
|
if parent_id(msg) != msg_id: continue
|
|
41
45
|
res.append(msg)
|
|
@@ -45,9 +49,9 @@ async def iopub_drain(self:ConKernelClient, msg_id, timeout=default_timeout):
|
|
|
45
49
|
# %% ../nbs/01_ops.ipynb #e705c283
|
|
46
50
|
@patch
|
|
47
51
|
async def iopub_flush(self:ConKernelClient, timeout=0.1):
|
|
48
|
-
"Discard
|
|
52
|
+
"Discard pending iopub messages from the attached `JmsgQueues`, e.g. leftovers from fire-and-forget executes"
|
|
49
53
|
try:
|
|
50
|
-
while True: await self.
|
|
54
|
+
while True: await self.jmsgq.get('iopub', timeout=timeout)
|
|
51
55
|
except Empty: pass
|
|
52
56
|
|
|
53
57
|
# %% ../nbs/01_ops.ipynb #ebdbd6eb
|
|
@@ -57,12 +61,6 @@ def iopub_streams(msgs, name=None):
|
|
|
57
61
|
return streams if name is None else [m for m in streams if m['content'].get('name') == name]
|
|
58
62
|
|
|
59
63
|
# %% ../nbs/01_ops.ipynb #52e7c8f1
|
|
60
|
-
@patch
|
|
61
|
-
async def exec_drain(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
62
|
-
"Execute `code`; return `(reply, outputs)` where outputs are the request's iopub messages"
|
|
63
|
-
reply = await self.execute(code, reply=True, timeout=timeout, **kw)
|
|
64
|
-
return reply, await self.iopub_drain(parent_id(reply), timeout=timeout)
|
|
65
|
-
|
|
66
64
|
@patch
|
|
67
65
|
async def exec_ok(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
68
66
|
"`exec_drain`, asserting the reply status is ok"
|
|
@@ -70,49 +68,7 @@ async def exec_ok(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
|
70
68
|
assert reply['content']['status'] == 'ok', reply['content']
|
|
71
69
|
return reply, outputs
|
|
72
70
|
|
|
73
|
-
@patch
|
|
74
|
-
async def exec_outs(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
75
|
-
"Execute `code` and return just its nbformat-style outputs"
|
|
76
|
-
reply, outputs = await self.exec_drain(code, timeout=timeout, **kw)
|
|
77
|
-
return msgs2outs(outputs)
|
|
78
|
-
|
|
79
|
-
# %% ../nbs/01_ops.ipynb #77ab9da4
|
|
80
|
-
class EvalError(Exception):
|
|
81
|
-
"An `eval_expr` expression raised in the kernel"
|
|
82
|
-
|
|
83
|
-
def parse_expr(s):
|
|
84
|
-
"The `literal_eval` of `s` when its form allows, else `s` unchanged"
|
|
85
|
-
try: return literal_eval(s)
|
|
86
|
-
except Exception: return s
|
|
87
|
-
|
|
88
|
-
@patch
|
|
89
|
-
async def user_exprs(self:ConKernelClient, exprs:dict, code:str='', timeout=default_timeout, **kw):
|
|
90
|
-
"Run `code` and evaluate each of the `exprs` expressions in the same round trip; returns the reply content (statuses and mimebundles unparsed)"
|
|
91
|
-
return (await self.execute(code, reply=True, timeout=timeout, user_expressions=exprs, store_history=False, **kw))['content']
|
|
92
|
-
|
|
93
|
-
@patch
|
|
94
|
-
async def eval_expr(self:ConKernelClient, expr:str, code:str='', timeout=default_timeout, **kw):
|
|
95
|
-
"Evaluate `expr` in the kernel (optionally after running `code`) and return its value: parsed via `literal_eval` when its repr allows, else the repr string. Raises `EvalError` if the kernel raises"
|
|
96
|
-
cts = await self.user_exprs({'_': expr}, code, timeout=timeout, **kw)
|
|
97
|
-
if cts['status'] != 'ok': raise EvalError(f"{cts.get('ename')}: {cts.get('evalue')}")
|
|
98
|
-
r = cts['user_expressions']['_']
|
|
99
|
-
if r.get('status') != 'ok': raise EvalError(f"{r.get('ename')}: {r.get('evalue')}")
|
|
100
|
-
return parse_expr(r['data']['text/plain'])
|
|
101
|
-
|
|
102
|
-
# %% ../nbs/01_ops.ipynb #d9bbaa8d
|
|
103
|
-
@patch
|
|
104
|
-
def reply(self:ConKernelClient,
|
|
105
|
-
code, # A string of code in the kernel's language.
|
|
106
|
-
user_expressions:NoneType=None, # A dict mapping names to expressions to be evaluated in the user's dict
|
|
107
|
-
allow_stdin:NoneType=None, # Flag for whether the kernel can send stdin requests to frontends.
|
|
108
|
-
cts_typ:str='code', timeout:int=None, msg_id:NoneType=None, **kw
|
|
109
|
-
):
|
|
110
|
-
return self.execute(code, user_expressions=user_expressions, allow_stdin=allow_stdin, reply=True,
|
|
111
|
-
cts_typ=cts_typ, timeout=timeout or getattr(self, 'default_timeout', default_timeout), msg_id=msg_id, **kw)
|
|
112
|
-
|
|
113
71
|
# %% ../nbs/01_ops.ipynb #5de02acb
|
|
114
|
-
from jupywire.ops import EvalOps, EvalException, try_eval
|
|
115
|
-
|
|
116
72
|
@patch
|
|
117
73
|
def _pre_ipy(self:ConKernelClient):
|
|
118
74
|
if not self.shell_channel.is_alive(): raise DeadKernelError()
|
|
@@ -120,40 +76,21 @@ def _pre_ipy(self:ConKernelClient):
|
|
|
120
76
|
|
|
121
77
|
# %% ../nbs/01_ops.ipynb #aafc1ab4
|
|
122
78
|
@patch
|
|
123
|
-
def shell_request(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
subshell_id=None, # Route via this subshell (header field)
|
|
127
|
-
metadata=None, # Message metadata (e.g. ipywidgets control-comm version check)
|
|
128
|
-
**content
|
|
129
|
-
):
|
|
130
|
-
"Send an arbitrary shell request, routed through the reply reader like `execute`. Returns a coroutine for the reply, or just the msg_id when `reply=False` (for fire-and-forget types like `comm_open` that never get replies)"
|
|
131
|
-
if not self._check_alive(): return asyncio.sleep(0) if reply else None
|
|
132
|
-
msg = self.session.msg(msg_type, content, metadata=metadata)
|
|
133
|
-
if msg_id is not None: msg['header']['msg_id'] = msg_id
|
|
134
|
-
if subshell_id is not None: msg['header']['subshell_id'] = subshell_id
|
|
135
|
-
mid = msg['header']['msg_id']
|
|
136
|
-
if reply: self._pending[mid] = (asyncio.Queue(maxsize=1), False)
|
|
137
|
-
if buffers: self.session.send(self.shell_channel.socket, msg, buffers=buffers)
|
|
138
|
-
else: self.shell_channel.send(msg)
|
|
139
|
-
return self._async_recv_reply(mid, timeout=timeout) if reply else mid
|
|
79
|
+
def shell_request(self:ConKernelClient, msg_type, timeout=default_timeout, msg_id=None, subshell_id=None, metadata=None, buffers=None, **content):
|
|
80
|
+
"A named shell request, content as keyword arguments; returns an awaitable of its reply"
|
|
81
|
+
return self.request(msg_type, content, timeout=timeout, msg_id=msg_id, subshell_id=subshell_id, metadata=metadata, buffers=buffers)
|
|
140
82
|
|
|
141
83
|
@patch
|
|
142
|
-
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
self.control_channel.send(msg)
|
|
146
|
-
for rem in iter_timeout(timeout):
|
|
147
|
-
try: reply = await self.get_control_msg(timeout=rem)
|
|
148
|
-
except Empty: continue
|
|
149
|
-
if parent_id(reply) == msg['header']['msg_id']: return reply
|
|
150
|
-
raise TimeoutError(f"no reply to {msg_type}")
|
|
84
|
+
def control_request(self:ConKernelClient, msg_type, timeout=default_timeout, **content):
|
|
85
|
+
"A named control request, content as keyword arguments; returns an awaitable of its reply"
|
|
86
|
+
return self.request(msg_type, content, channel='control', timeout=timeout)
|
|
151
87
|
|
|
152
88
|
# %% ../nbs/01_ops.ipynb #4f01d2b6
|
|
153
89
|
@patch
|
|
154
90
|
async def input_reply(self:ConKernelClient, value:str):
|
|
155
91
|
"Answer the kernel's pending input_request (from `input()` in an execute sent with `allow_stdin=True`)"
|
|
156
|
-
|
|
92
|
+
self.input(value)
|
|
93
|
+
|
|
157
94
|
|
|
158
95
|
# %% ../nbs/01_ops.ipynb #97d75b1e
|
|
159
96
|
class _ReqProxy:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conkernelclient
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.20
|
|
4
4
|
Summary: Concurrent-safe Jupyter KernelClient
|
|
5
5
|
Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: jupyter_client
|
|
16
16
|
Requires-Dist: fastcore>=2.1.17
|
|
17
|
-
Requires-Dist: jupywire>=0.1.
|
|
17
|
+
Requires-Dist: jupywire>=0.1.5
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: ipykernel; extra == "dev"
|
|
20
20
|
Requires-Dist: ipymini>=0.1.17; extra == "dev"
|
|
@@ -41,7 +41,7 @@ def test_burst_replies_under_send_jitter():
|
|
|
41
41
|
"20 pending replies at once, every send jittered: the reader must route each reply to its waiter."
|
|
42
42
|
async def _run():
|
|
43
43
|
async with run_kernel("ipymini", ["ipymini", "-f", "{connection_file}"], manager_cls=LockedKernelManager) as (km, kc):
|
|
44
|
-
reps = [kc.
|
|
44
|
+
reps = [kc.reply(f"x{i} = {i}; x{i}", timeout=30) for i in range(20)]
|
|
45
45
|
for i, r in enumerate(await asyncio.gather(*reps)): assert r["content"]["status"] == "ok", (i, r["content"])
|
|
46
46
|
asyncio.run(_run())
|
|
47
47
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{conkernelclient-0.0.19 → conkernelclient-0.0.20}/conkernelclient.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|