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.
@@ -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
+ ]
@@ -0,0 +1,4 @@
1
+ if __name__ == "__main__":
2
+ from async_kernel.command import command_line
3
+
4
+ command_line()
@@ -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)