conkernelclient 0.0.18__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.18/conkernelclient.egg-info → conkernelclient-0.0.20}/PKG-INFO +2 -2
- conkernelclient-0.0.20/conkernelclient/__init__.py +4 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient/_modidx.py +11 -17
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient/core.py +91 -90
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient/ops.py +31 -90
- {conkernelclient-0.0.18 → conkernelclient-0.0.20/conkernelclient.egg-info}/PKG-INFO +2 -2
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/requires.txt +1 -1
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/pyproject.toml +1 -1
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/tests/test_races.py +2 -3
- conkernelclient-0.0.18/conkernelclient/__init__.py +0 -3
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/LICENSE +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/MANIFEST.in +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/README.md +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/SOURCES.txt +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/dependency_links.txt +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/entry_points.txt +0 -0
- {conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/top_level.txt +0 -0
- {conkernelclient-0.0.18 → 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,28 +8,32 @@ 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',
|
|
28
30
|
'conkernelclient/core.py'),
|
|
29
31
|
'conkernelclient.core.ConKernelManager': ('core.html#conkernelmanager', 'conkernelclient/core.py'),
|
|
32
|
+
'conkernelclient.core.ConKernelManager.__init__': ( 'core.html#conkernelmanager.__init__',
|
|
33
|
+
'conkernelclient/core.py'),
|
|
30
34
|
'conkernelclient.core.ConKernelManager._transport_encryption_default': ( 'core.html#conkernelmanager._transport_encryption_default',
|
|
31
35
|
'conkernelclient/core.py'),
|
|
32
|
-
'conkernelclient.core.
|
|
36
|
+
'conkernelclient.core._dead_status': ('core.html#_dead_status', 'conkernelclient/core.py'),
|
|
33
37
|
'conkernelclient.core._send': ('core.html#_send', 'conkernelclient/core.py'),
|
|
34
38
|
'conkernelclient.core.apply_session_patch': ( 'core.html#apply_session_patch',
|
|
35
39
|
'conkernelclient/core.py')},
|
|
@@ -40,14 +44,10 @@ d = { 'settings': { 'branch': 'main',
|
|
|
40
44
|
'conkernelclient/ops.py'),
|
|
41
45
|
'conkernelclient.ops.ConKernelClient.ctl': ('ops.html#conkernelclient.ctl', 'conkernelclient/ops.py'),
|
|
42
46
|
'conkernelclient.ops.ConKernelClient.dap': ('ops.html#conkernelclient.dap', 'conkernelclient/ops.py'),
|
|
43
|
-
'conkernelclient.ops.ConKernelClient.eval_expr': ( 'ops.html#conkernelclient.eval_expr',
|
|
44
|
-
'conkernelclient/ops.py'),
|
|
45
47
|
'conkernelclient.ops.ConKernelClient.exec_drain': ( 'ops.html#conkernelclient.exec_drain',
|
|
46
48
|
'conkernelclient/ops.py'),
|
|
47
49
|
'conkernelclient.ops.ConKernelClient.exec_ok': ( 'ops.html#conkernelclient.exec_ok',
|
|
48
50
|
'conkernelclient/ops.py'),
|
|
49
|
-
'conkernelclient.ops.ConKernelClient.exec_outs': ( 'ops.html#conkernelclient.exec_outs',
|
|
50
|
-
'conkernelclient/ops.py'),
|
|
51
51
|
'conkernelclient.ops.ConKernelClient.input_reply': ( 'ops.html#conkernelclient.input_reply',
|
|
52
52
|
'conkernelclient/ops.py'),
|
|
53
53
|
'conkernelclient.ops.ConKernelClient.interrupt': ( 'ops.html#conkernelclient.interrupt',
|
|
@@ -56,13 +56,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
56
56
|
'conkernelclient/ops.py'),
|
|
57
57
|
'conkernelclient.ops.ConKernelClient.iopub_flush': ( 'ops.html#conkernelclient.iopub_flush',
|
|
58
58
|
'conkernelclient/ops.py'),
|
|
59
|
-
'conkernelclient.ops.ConKernelClient.reply': ( 'ops.html#conkernelclient.reply',
|
|
60
|
-
'conkernelclient/ops.py'),
|
|
61
59
|
'conkernelclient.ops.ConKernelClient.shell_request': ( 'ops.html#conkernelclient.shell_request',
|
|
62
60
|
'conkernelclient/ops.py'),
|
|
63
|
-
'conkernelclient.ops.ConKernelClient.user_exprs': ( 'ops.html#conkernelclient.user_exprs',
|
|
64
|
-
'conkernelclient/ops.py'),
|
|
65
|
-
'conkernelclient.ops.EvalError': ('ops.html#evalerror', 'conkernelclient/ops.py'),
|
|
66
61
|
'conkernelclient.ops._DapProxy': ('ops.html#_dapproxy', 'conkernelclient/ops.py'),
|
|
67
62
|
'conkernelclient.ops._DapProxy.__getattr__': ( 'ops.html#_dapproxy.__getattr__',
|
|
68
63
|
'conkernelclient/ops.py'),
|
|
@@ -73,6 +68,5 @@ d = { 'settings': { 'branch': 'main',
|
|
|
73
68
|
'conkernelclient.ops._ReqProxy.__init__': ('ops.html#_reqproxy.__init__', 'conkernelclient/ops.py'),
|
|
74
69
|
'conkernelclient.ops.iopub_streams': ('ops.html#iopub_streams', 'conkernelclient/ops.py'),
|
|
75
70
|
'conkernelclient.ops.iter_timeout': ('ops.html#iter_timeout', 'conkernelclient/ops.py'),
|
|
76
|
-
'conkernelclient.ops.parse_expr': ('ops.html#parse_expr', 'conkernelclient/ops.py'),
|
|
77
71
|
'conkernelclient.ops.reconnect': ('ops.html#reconnect', 'conkernelclient/ops.py'),
|
|
78
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,17 +172,20 @@ 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
|
|
187
185
|
class ConKernelManager(AsyncKernelManager):
|
|
188
186
|
client_class,client_factory = ConKernelClient,Type(ConKernelClient)
|
|
187
|
+
def __init__(self, *args, kernel_spec=None, **kwargs):
|
|
188
|
+
super().__init__(*args, **kwargs)
|
|
189
|
+
if kernel_spec is not None: self._kernel_spec = kernel_spec
|
|
189
190
|
@default('transport_encryption')
|
|
190
191
|
def _transport_encryption_default(self): return 'auto'
|
|
@@ -5,20 +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
|
|
17
|
-
|
|
13
|
+
from jupywire.ops import parent_id, iopub_msgs
|
|
14
|
+
from jupyter_client.kernelspec import KernelSpec
|
|
18
15
|
from contextlib import asynccontextmanager
|
|
19
|
-
from ast import literal_eval
|
|
20
16
|
from queue import Empty
|
|
21
|
-
import
|
|
17
|
+
import time
|
|
18
|
+
|
|
22
19
|
|
|
23
20
|
# %% ../nbs/01_ops.ipynb #d62f6b51
|
|
24
21
|
default_timeout = 10
|
|
@@ -30,11 +27,19 @@ def iter_timeout(timeout=None, default=default_timeout):
|
|
|
30
27
|
|
|
31
28
|
# %% ../nbs/01_ops.ipynb #ad596b2b
|
|
32
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
|
|
33
38
|
async def iopub_drain(self:ConKernelClient, msg_id, timeout=default_timeout):
|
|
34
|
-
"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"
|
|
35
40
|
res = []
|
|
36
41
|
for rem in iter_timeout(timeout):
|
|
37
|
-
try: msg = await self.
|
|
42
|
+
try: msg = await self.jmsgq.get('iopub', timeout=rem)
|
|
38
43
|
except Empty: continue
|
|
39
44
|
if parent_id(msg) != msg_id: continue
|
|
40
45
|
res.append(msg)
|
|
@@ -44,9 +49,9 @@ async def iopub_drain(self:ConKernelClient, msg_id, timeout=default_timeout):
|
|
|
44
49
|
# %% ../nbs/01_ops.ipynb #e705c283
|
|
45
50
|
@patch
|
|
46
51
|
async def iopub_flush(self:ConKernelClient, timeout=0.1):
|
|
47
|
-
"Discard
|
|
52
|
+
"Discard pending iopub messages from the attached `JmsgQueues`, e.g. leftovers from fire-and-forget executes"
|
|
48
53
|
try:
|
|
49
|
-
while True: await self.
|
|
54
|
+
while True: await self.jmsgq.get('iopub', timeout=timeout)
|
|
50
55
|
except Empty: pass
|
|
51
56
|
|
|
52
57
|
# %% ../nbs/01_ops.ipynb #ebdbd6eb
|
|
@@ -56,12 +61,6 @@ def iopub_streams(msgs, name=None):
|
|
|
56
61
|
return streams if name is None else [m for m in streams if m['content'].get('name') == name]
|
|
57
62
|
|
|
58
63
|
# %% ../nbs/01_ops.ipynb #52e7c8f1
|
|
59
|
-
@patch
|
|
60
|
-
async def exec_drain(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
61
|
-
"Execute `code`; return `(reply, outputs)` where outputs are the request's iopub messages"
|
|
62
|
-
reply = await self.execute(code, reply=True, timeout=timeout, **kw)
|
|
63
|
-
return reply, await self.iopub_drain(parent_id(reply), timeout=timeout)
|
|
64
|
-
|
|
65
64
|
@patch
|
|
66
65
|
async def exec_ok(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
67
66
|
"`exec_drain`, asserting the reply status is ok"
|
|
@@ -69,49 +68,7 @@ async def exec_ok(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
|
69
68
|
assert reply['content']['status'] == 'ok', reply['content']
|
|
70
69
|
return reply, outputs
|
|
71
70
|
|
|
72
|
-
@patch
|
|
73
|
-
async def exec_outs(self:ConKernelClient, code, timeout=default_timeout, **kw):
|
|
74
|
-
"Execute `code` and return just its nbformat-style outputs"
|
|
75
|
-
reply, outputs = await self.exec_drain(code, timeout=timeout, **kw)
|
|
76
|
-
return msgs2outs(outputs)
|
|
77
|
-
|
|
78
|
-
# %% ../nbs/01_ops.ipynb #77ab9da4
|
|
79
|
-
class EvalError(Exception):
|
|
80
|
-
"An `eval_expr` expression raised in the kernel"
|
|
81
|
-
|
|
82
|
-
def parse_expr(s):
|
|
83
|
-
"The `literal_eval` of `s` when its form allows, else `s` unchanged"
|
|
84
|
-
try: return literal_eval(s)
|
|
85
|
-
except Exception: return s
|
|
86
|
-
|
|
87
|
-
@patch
|
|
88
|
-
async def user_exprs(self:ConKernelClient, exprs:dict, code:str='', timeout=default_timeout, **kw):
|
|
89
|
-
"Run `code` and evaluate each of the `exprs` expressions in the same round trip; returns the reply content (statuses and mimebundles unparsed)"
|
|
90
|
-
return (await self.execute(code, reply=True, timeout=timeout, user_expressions=exprs, store_history=False, **kw))['content']
|
|
91
|
-
|
|
92
|
-
@patch
|
|
93
|
-
async def eval_expr(self:ConKernelClient, expr:str, code:str='', timeout=default_timeout, **kw):
|
|
94
|
-
"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"
|
|
95
|
-
cts = await self.user_exprs({'_': expr}, code, timeout=timeout, **kw)
|
|
96
|
-
if cts['status'] != 'ok': raise EvalError(f"{cts.get('ename')}: {cts.get('evalue')}")
|
|
97
|
-
r = cts['user_expressions']['_']
|
|
98
|
-
if r.get('status') != 'ok': raise EvalError(f"{r.get('ename')}: {r.get('evalue')}")
|
|
99
|
-
return parse_expr(r['data']['text/plain'])
|
|
100
|
-
|
|
101
|
-
# %% ../nbs/01_ops.ipynb #d9bbaa8d
|
|
102
|
-
@patch
|
|
103
|
-
def reply(self:ConKernelClient,
|
|
104
|
-
code, # A string of code in the kernel's language.
|
|
105
|
-
user_expressions:NoneType=None, # A dict mapping names to expressions to be evaluated in the user's dict
|
|
106
|
-
allow_stdin:NoneType=None, # Flag for whether the kernel can send stdin requests to frontends.
|
|
107
|
-
cts_typ:str='code', timeout:int=None, msg_id:NoneType=None, **kw
|
|
108
|
-
):
|
|
109
|
-
return self.execute(code, user_expressions=user_expressions, allow_stdin=allow_stdin, reply=True,
|
|
110
|
-
cts_typ=cts_typ, timeout=timeout or getattr(self, 'default_timeout', default_timeout), msg_id=msg_id, **kw)
|
|
111
|
-
|
|
112
71
|
# %% ../nbs/01_ops.ipynb #5de02acb
|
|
113
|
-
from jupywire.ops import EvalOps, EvalException, try_eval
|
|
114
|
-
|
|
115
72
|
@patch
|
|
116
73
|
def _pre_ipy(self:ConKernelClient):
|
|
117
74
|
if not self.shell_channel.is_alive(): raise DeadKernelError()
|
|
@@ -119,40 +76,21 @@ def _pre_ipy(self:ConKernelClient):
|
|
|
119
76
|
|
|
120
77
|
# %% ../nbs/01_ops.ipynb #aafc1ab4
|
|
121
78
|
@patch
|
|
122
|
-
def shell_request(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
subshell_id=None, # Route via this subshell (header field)
|
|
126
|
-
metadata=None, # Message metadata (e.g. ipywidgets control-comm version check)
|
|
127
|
-
**content
|
|
128
|
-
):
|
|
129
|
-
"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)"
|
|
130
|
-
if not self._check_alive(): return asyncio.sleep(0) if reply else None
|
|
131
|
-
msg = self.session.msg(msg_type, content, metadata=metadata)
|
|
132
|
-
if msg_id is not None: msg['header']['msg_id'] = msg_id
|
|
133
|
-
if subshell_id is not None: msg['header']['subshell_id'] = subshell_id
|
|
134
|
-
mid = msg['header']['msg_id']
|
|
135
|
-
if reply: self._pending[mid] = (asyncio.Queue(maxsize=1), False)
|
|
136
|
-
if buffers: self.session.send(self.shell_channel.socket, msg, buffers=buffers)
|
|
137
|
-
else: self.shell_channel.send(msg)
|
|
138
|
-
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)
|
|
139
82
|
|
|
140
83
|
@patch
|
|
141
|
-
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
self.control_channel.send(msg)
|
|
145
|
-
for rem in iter_timeout(timeout):
|
|
146
|
-
try: reply = await self.get_control_msg(timeout=rem)
|
|
147
|
-
except Empty: continue
|
|
148
|
-
if parent_id(reply) == msg['header']['msg_id']: return reply
|
|
149
|
-
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)
|
|
150
87
|
|
|
151
88
|
# %% ../nbs/01_ops.ipynb #4f01d2b6
|
|
152
89
|
@patch
|
|
153
90
|
async def input_reply(self:ConKernelClient, value:str):
|
|
154
91
|
"Answer the kernel's pending input_request (from `input()` in an execute sent with `allow_stdin=True`)"
|
|
155
|
-
|
|
92
|
+
self.input(value)
|
|
93
|
+
|
|
156
94
|
|
|
157
95
|
# %% ../nbs/01_ops.ipynb #97d75b1e
|
|
158
96
|
class _ReqProxy:
|
|
@@ -202,12 +140,15 @@ async def interrupt(self:ConKernelClient, timeout=5):
|
|
|
202
140
|
# %% ../nbs/01_ops.ipynb #edaf42c9
|
|
203
141
|
@asynccontextmanager
|
|
204
142
|
async def run_kernel(
|
|
205
|
-
kernel_name='python3', # Kernelspec name to launch
|
|
143
|
+
kernel_name='python3', # Kernelspec name to launch, or display name when `argv` is given
|
|
144
|
+
argv=None, # Kernel argv; bypasses kernelspec discovery when provided
|
|
206
145
|
manager_cls=ConKernelManager, # Manager class, e.g. a subclass customizing launch
|
|
207
146
|
**kwargs # Passed to `start_kernel` (e.g. `env`, `cwd`)
|
|
208
147
|
):
|
|
209
|
-
"Start a kernel,
|
|
210
|
-
|
|
148
|
+
"Start a named or direct-argv kernel, yielding `(km, kc)`, and always shut it down"
|
|
149
|
+
manager_kwargs = dict(kernel_name=kernel_name)
|
|
150
|
+
if argv is not None: manager_kwargs['kernel_spec'] = KernelSpec(argv=argv, display_name=kernel_name)
|
|
151
|
+
km = manager_cls(**manager_kwargs)
|
|
211
152
|
await km.start_kernel(**kwargs)
|
|
212
153
|
kc = await km.client().start_channels()
|
|
213
154
|
try: yield km, kc
|
|
@@ -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"
|
|
@@ -37,12 +37,11 @@ class LockedClient(ConKernelClient):
|
|
|
37
37
|
class LockedKernelManager(ConKernelManager):
|
|
38
38
|
client_class,client_factory = LockedClient,Type(LockedClient)
|
|
39
39
|
|
|
40
|
-
|
|
41
40
|
def test_burst_replies_under_send_jitter():
|
|
42
41
|
"20 pending replies at once, every send jittered: the reader must route each reply to its waiter."
|
|
43
42
|
async def _run():
|
|
44
|
-
async with run_kernel(
|
|
45
|
-
reps = [kc.
|
|
43
|
+
async with run_kernel("ipymini", ["ipymini", "-f", "{connection_file}"], manager_cls=LockedKernelManager) as (km, kc):
|
|
44
|
+
reps = [kc.reply(f"x{i} = {i}; x{i}", timeout=30) for i in range(20)]
|
|
46
45
|
for i, r in enumerate(await asyncio.gather(*reps)): assert r["content"]["status"] == "ok", (i, r["content"])
|
|
47
46
|
asyncio.run(_run())
|
|
48
47
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{conkernelclient-0.0.18 → conkernelclient-0.0.20}/conkernelclient.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|