async-kernel 0.1.0__py3-none-any.whl
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.
- async_kernel/__init__.py +34 -0
- async_kernel/__main__.py +4 -0
- async_kernel/asyncshell.py +313 -0
- async_kernel/caller.py +745 -0
- async_kernel/comm.py +116 -0
- async_kernel/command.py +158 -0
- async_kernel/compiler.py +96 -0
- async_kernel/debugger.py +584 -0
- async_kernel/iostream.py +65 -0
- async_kernel/kernel.py +997 -0
- async_kernel/kernelspec.py +129 -0
- async_kernel/resources/logo-32x32.png +0 -0
- async_kernel/resources/logo-64x64.png +0 -0
- async_kernel/resources/logo-svg.svg +265 -0
- async_kernel/typing.py +310 -0
- async_kernel/utils.py +116 -0
- async_kernel-0.1.0.dist-info/METADATA +94 -0
- async_kernel-0.1.0.dist-info/RECORD +22 -0
- async_kernel-0.1.0.dist-info/WHEEL +4 -0
- async_kernel-0.1.0.dist-info/entry_points.txt +2 -0
- async_kernel-0.1.0.dist-info/licenses/IPYTHON_LICENSE +30 -0
- async_kernel-0.1.0.dist-info/licenses/LICENSE +22 -0
async_kernel/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
+
|
|
4
|
+
from async_kernel import utils
|
|
5
|
+
from async_kernel.caller import Caller, Future
|
|
6
|
+
from async_kernel.kernel import Kernel
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version(distribution_name="async-kernel")
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
# package is not installed
|
|
12
|
+
__version__ = "not installed"
|
|
13
|
+
|
|
14
|
+
kernel_protocol_version = "5.4"
|
|
15
|
+
kernel_protocol_version_info = {
|
|
16
|
+
"name": "python",
|
|
17
|
+
"version": ".".join(map(str, sys.version_info[0:3])),
|
|
18
|
+
"mimetype": "text/x-python",
|
|
19
|
+
"codemirror_mode": {"name": "ipython", "version": 3},
|
|
20
|
+
"pygments_lexer": "ipython3",
|
|
21
|
+
"nbconvert_exporter": "python",
|
|
22
|
+
"file_extension": ".py",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Caller",
|
|
28
|
+
"Future",
|
|
29
|
+
"Kernel",
|
|
30
|
+
"__version__",
|
|
31
|
+
"kernel_protocol_version",
|
|
32
|
+
"kernel_protocol_version_info",
|
|
33
|
+
"utils",
|
|
34
|
+
]
|
async_kernel/__main__.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
import json
|
|
5
|
+
import pathlib
|
|
6
|
+
import sys
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
import IPython.core.release
|
|
11
|
+
from IPython.core.displayhook import DisplayHook
|
|
12
|
+
from IPython.core.displaypub import DisplayPublisher
|
|
13
|
+
from IPython.core.interactiveshell import ExecutionResult, InteractiveShell, InteractiveShellABC
|
|
14
|
+
from IPython.core.magic import Magics, line_magic, magics_class
|
|
15
|
+
from jupyter_client.jsonutil import json_default
|
|
16
|
+
from jupyter_core.paths import jupyter_runtime_dir
|
|
17
|
+
from traitlets import CFloat, Dict, Instance, Type, default, observe
|
|
18
|
+
from typing_extensions import override
|
|
19
|
+
|
|
20
|
+
from async_kernel import utils
|
|
21
|
+
from async_kernel.caller import Caller
|
|
22
|
+
from async_kernel.compiler import XCachingCompiler
|
|
23
|
+
from async_kernel.typing import Content, MetadataKeys, Tags
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from async_kernel.kernel import Kernel
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ["AsyncDisplayHook", "AsyncDisplayPublisher", "AsyncInteractiveShell"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AsyncDisplayHook(DisplayHook):
|
|
33
|
+
"""A displayhook subclass that publishes data using ZeroMQ.
|
|
34
|
+
|
|
35
|
+
This is intended to work with an InteractiveShell instance. It sends a dict of different
|
|
36
|
+
representations of the object."""
|
|
37
|
+
|
|
38
|
+
kernel: Instance[Kernel] = Instance("async_kernel.Kernel", ())
|
|
39
|
+
content: Dict[str, Any] = Dict()
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@override
|
|
43
|
+
def prompt_count(self) -> int:
|
|
44
|
+
return self.kernel.execution_count
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def start_displayhook(self) -> None:
|
|
48
|
+
"""Start the display hook."""
|
|
49
|
+
self.content = {}
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
def write_output_prompt(self) -> None:
|
|
53
|
+
"""Write the output prompt."""
|
|
54
|
+
self.content["execution_count"] = self.prompt_count
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
def write_format_data(self, format_dict, md_dict=None) -> None:
|
|
58
|
+
"""Write format data to the message."""
|
|
59
|
+
self.content["data"] = format_dict
|
|
60
|
+
self.content["metadata"] = md_dict
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
def finish_displayhook(self) -> None:
|
|
64
|
+
"""Finish up all displayhook activities."""
|
|
65
|
+
if self.content:
|
|
66
|
+
self.kernel.iopub_send("display_data", content=self.content)
|
|
67
|
+
self.content = {}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AsyncDisplayPublisher(DisplayPublisher):
|
|
71
|
+
"""A display publisher that publishes data using a ZeroMQ PUB socket."""
|
|
72
|
+
|
|
73
|
+
topic: ClassVar = b"display_data"
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def publish( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
77
|
+
self,
|
|
78
|
+
data: Content,
|
|
79
|
+
metadata: dict | None = None,
|
|
80
|
+
*,
|
|
81
|
+
transient: dict | None = None,
|
|
82
|
+
update: bool = False,
|
|
83
|
+
**kwargs,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Publish a display-data message.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
data: A mime-bundle dict, keyed by mime-type.
|
|
89
|
+
metadata: Metadata associated with the data.
|
|
90
|
+
transient: Transient data that may only be relevant during a live display, such as display_id.
|
|
91
|
+
Transient data should not be persisted to documents.
|
|
92
|
+
update: If True, send an update_display_data message instead of display_data.
|
|
93
|
+
|
|
94
|
+
[Reference](https://jupyter-client.readthedocs.io/en/stable/messaging.html#update-display-data)
|
|
95
|
+
"""
|
|
96
|
+
utils.get_kernel().iopub_send(
|
|
97
|
+
msg_or_type="update_display_data" if update else "display_data",
|
|
98
|
+
content={"data": data, "metadata": metadata or {}, "transient": transient or {}} | kwargs,
|
|
99
|
+
ident=self.topic,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@override
|
|
103
|
+
def clear_output(self, wait: bool = False) -> None:
|
|
104
|
+
"""Clear output associated with the current execution (cell).
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
wait: If True, the output will not be cleared immediately,
|
|
108
|
+
instead waiting for the next display before clearing.
|
|
109
|
+
This reduces bounce during repeated clear & display loops.
|
|
110
|
+
"""
|
|
111
|
+
utils.get_kernel().iopub_send(msg_or_type="clear_output", content={"wait": wait}, ident=self.topic)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AsyncInteractiveShell(InteractiveShell):
|
|
115
|
+
"""An [IPython InteractiveShell][IPython.core.interactiveshell.InteractiveShell] modified to work with [Async kernel][async_kernel.Kernel].
|
|
116
|
+
|
|
117
|
+
!!! note "Notable differences"
|
|
118
|
+
|
|
119
|
+
- All [execute requests][async_kernel.Kernel.execute_request] are run asynchronously.
|
|
120
|
+
- Supports a soft timeout with the metadata {"timeout":<value in seconds>}[^1].
|
|
121
|
+
|
|
122
|
+
[^1]: When the execution time exceeds the timeout value, the code execution will "move on".
|
|
123
|
+
- Not all features are support (see "not-supported" features listed below).
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
displayhook_class = Type(AsyncDisplayHook)
|
|
128
|
+
display_pub_class = Type(AsyncDisplayPublisher)
|
|
129
|
+
displayhook: Instance[AsyncDisplayHook]
|
|
130
|
+
display_pub: Instance[AsyncDisplayPublisher]
|
|
131
|
+
compiler_class = Type(XCachingCompiler)
|
|
132
|
+
compile: Instance[XCachingCompiler]
|
|
133
|
+
user_ns_hidden = Dict()
|
|
134
|
+
_main_mod_cache = Dict()
|
|
135
|
+
|
|
136
|
+
execute_request_timeout = CFloat(default_value=None, allow_none=True)
|
|
137
|
+
"A timeout in seconds to complete [execute requests][async_kernel.Kernel.execute_request]."
|
|
138
|
+
|
|
139
|
+
run_cell = None # pyright: ignore[reportAssignmentType]
|
|
140
|
+
"**not-supported**"
|
|
141
|
+
should_run_async = None # pyright: ignore[reportAssignmentType]
|
|
142
|
+
loop_runner_map = None
|
|
143
|
+
"**not-supported**"
|
|
144
|
+
loop_runner = None
|
|
145
|
+
"**not-supported**"
|
|
146
|
+
debug = None
|
|
147
|
+
"**not-supported**"
|
|
148
|
+
readline_use = False
|
|
149
|
+
"**not-supported**"
|
|
150
|
+
autoindent = False
|
|
151
|
+
"**not-supported**"
|
|
152
|
+
|
|
153
|
+
@default("banner1")
|
|
154
|
+
def _default_banner1(self) -> str:
|
|
155
|
+
return (
|
|
156
|
+
f"Python {sys.version}\n"
|
|
157
|
+
f"Async kernel ({self.kernel.kernel_name})\n"
|
|
158
|
+
f"IPython shell {IPython.core.release.version}\n"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def kernel(self) -> Kernel:
|
|
163
|
+
"The current kernel."
|
|
164
|
+
return utils.get_kernel()
|
|
165
|
+
|
|
166
|
+
@observe("exit_now")
|
|
167
|
+
def _update_exit_now(self, _) -> None:
|
|
168
|
+
"""stop eventloop when exit_now fires"""
|
|
169
|
+
if self.exit_now:
|
|
170
|
+
self.kernel.stop()
|
|
171
|
+
|
|
172
|
+
def ask_exit(self) -> None:
|
|
173
|
+
if self.kernel.raw_input("Are you sure you want to stop the kernel?\ny/[n]\n") == "y":
|
|
174
|
+
self.exit_now = True
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
def init_create_namespaces(self, user_module=None, user_ns=None) -> None:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
@override
|
|
181
|
+
def save_sys_module_state(self) -> None:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
@override
|
|
185
|
+
def init_sys_modules(self) -> None:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
@override
|
|
190
|
+
def execution_count(self) -> int:
|
|
191
|
+
return self.kernel.execution_count
|
|
192
|
+
|
|
193
|
+
@execution_count.setter
|
|
194
|
+
def execution_count(self, value) -> None:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
@override
|
|
199
|
+
def user_ns(self) -> dict[Any, Any]:
|
|
200
|
+
if not hasattr(self, "_user_ns"):
|
|
201
|
+
self.user_ns = {}
|
|
202
|
+
return self._user_ns
|
|
203
|
+
|
|
204
|
+
@user_ns.setter
|
|
205
|
+
def user_ns(self, ns: dict) -> None:
|
|
206
|
+
assert hasattr(ns, "clear")
|
|
207
|
+
assert isinstance(ns, dict)
|
|
208
|
+
self._user_ns = ns
|
|
209
|
+
self.init_user_ns()
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
@override
|
|
213
|
+
def user_global_ns(self) -> dict[Any, Any]:
|
|
214
|
+
return self.user_ns
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
@override
|
|
218
|
+
def ns_table(self) -> dict[str, dict[Any, Any] | dict[str, Any]]:
|
|
219
|
+
return {"user_global": self.user_ns, "user_local": self.user_ns, "builtin": builtins.__dict__}
|
|
220
|
+
|
|
221
|
+
@override
|
|
222
|
+
async def run_cell_async(
|
|
223
|
+
self,
|
|
224
|
+
raw_cell: str,
|
|
225
|
+
store_history=False,
|
|
226
|
+
silent=False,
|
|
227
|
+
shell_futures=True,
|
|
228
|
+
*,
|
|
229
|
+
transformed_cell: str | None = None,
|
|
230
|
+
preprocessing_exc_tuple: tuple | None = None,
|
|
231
|
+
cell_id: str | None = None,
|
|
232
|
+
) -> ExecutionResult:
|
|
233
|
+
"""Run a complete IPython cell asynchronously.
|
|
234
|
+
|
|
235
|
+
This function runs [execute requests][async_kernel.Kernel.execute_request] for the kernel
|
|
236
|
+
wrapping [InteractiveShell][IPython.core.interactiveshell.InteractiveShell.run_cell_async].
|
|
237
|
+
"""
|
|
238
|
+
with anyio.fail_after(delay=utils.get_execute_request_timeout()):
|
|
239
|
+
result: ExecutionResult = await super().run_cell_async(
|
|
240
|
+
raw_cell=raw_cell,
|
|
241
|
+
store_history=store_history,
|
|
242
|
+
silent=silent,
|
|
243
|
+
shell_futures=shell_futures,
|
|
244
|
+
transformed_cell=transformed_cell,
|
|
245
|
+
preprocessing_exc_tuple=preprocessing_exc_tuple,
|
|
246
|
+
cell_id=cell_id,
|
|
247
|
+
)
|
|
248
|
+
self.events.trigger("post_execute")
|
|
249
|
+
if not silent:
|
|
250
|
+
self.events.trigger("post_run_cell", result)
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
@override
|
|
254
|
+
def _showtraceback(self, etype, evalue, stb) -> None:
|
|
255
|
+
if Tags.suppress_error in utils.get_tags():
|
|
256
|
+
if msg := utils.get_metadata().get(MetadataKeys.suppress_error_message, "⚠"):
|
|
257
|
+
print(msg)
|
|
258
|
+
return
|
|
259
|
+
if utils.get_execute_request_timeout() is not None and etype is self.kernel.CancelledError:
|
|
260
|
+
etype, evalue, stb = TimeoutError, "Cell execute timeout", []
|
|
261
|
+
self.kernel.iopub_send(
|
|
262
|
+
msg_or_type="error",
|
|
263
|
+
content={"traceback": stb, "ename": str(etype.__name__), "evalue": str(evalue)},
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@override
|
|
267
|
+
def init_magics(self) -> None:
|
|
268
|
+
"""Initialize magics."""
|
|
269
|
+
super().init_magics()
|
|
270
|
+
self.register_magics(KernelMagics)
|
|
271
|
+
|
|
272
|
+
@override
|
|
273
|
+
def enable_gui(self, gui=None) -> None:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@magics_class
|
|
278
|
+
class KernelMagics(Magics):
|
|
279
|
+
"""Extra magics for async kernel."""
|
|
280
|
+
|
|
281
|
+
@line_magic
|
|
282
|
+
def connect_info(self, _) -> None:
|
|
283
|
+
"""Print information for connecting other clients to this kernel."""
|
|
284
|
+
kernel = utils.get_kernel()
|
|
285
|
+
connection_file = pathlib.Path(kernel.connection_file)
|
|
286
|
+
# if it's in the default dir, truncate to basename
|
|
287
|
+
if jupyter_runtime_dir() == str(connection_file.parent):
|
|
288
|
+
connection_file = connection_file.name
|
|
289
|
+
info = kernel.get_connection_info()
|
|
290
|
+
print(
|
|
291
|
+
json.dumps(info, indent=2, default=json_default),
|
|
292
|
+
"Paste the above JSON into a file, and connect with:\n"
|
|
293
|
+
+ " $> jupyter <app> --existing <file>\n"
|
|
294
|
+
+ "or, if you are local, you can connect with just:\n"
|
|
295
|
+
+ f" $> jupyter <app> --existing {connection_file}\n"
|
|
296
|
+
+ "or even just:\n"
|
|
297
|
+
+ " $> jupyter <app> --existing\n"
|
|
298
|
+
+ "if this is the most recent Jupyter kernel you have started.",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
@line_magic
|
|
302
|
+
def callers(self, _) -> None:
|
|
303
|
+
"Print a table of [Callers][async_kernel.Caller], indicating its status including: -running - protected - on the current thread."
|
|
304
|
+
lines = ["\t".join(["Running", "Protected", "\t", "Name"]), "─" * 70]
|
|
305
|
+
for caller in Caller.all_callers(running_only=False):
|
|
306
|
+
symbol = " ✓" if caller.running else " ✗"
|
|
307
|
+
current_thread: Literal["← current thread", ""] = "← current thread" if caller is Caller() else ""
|
|
308
|
+
protected = " 🔐" if caller.protected else ""
|
|
309
|
+
lines.append("\t".join([symbol, protected, "", caller.thread.name, current_thread]))
|
|
310
|
+
print(*lines, sep="\n")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
InteractiveShellABC.register(AsyncInteractiveShell)
|