Skip to content

Commit 16938c6

Browse files
committed
Support running user code in a thread
1 parent 6fc0b0c commit 16938c6

8 files changed

Lines changed: 177 additions & 53 deletions

File tree

plugins/akernel_task/fps_akernel_task/akernel_task.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99

1010
class AKernelTask(_Kernel):
11-
def __init__(self, *args, **kwargs):
11+
def __init__(self, *args, execute_in_thread: bool = False, **kwargs):
1212
super().__init__()
13+
self.execute_in_thread = execute_in_thread
1314

1415
async def start(self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED) -> None:
1516
async with (
@@ -37,6 +38,7 @@ async def start(self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED) ->
3738
self._to_stdin_receive_stream,
3839
self._from_stdin_send_stream,
3940
self._from_iopub_send_stream,
41+
execute_in_thread=self.execute_in_thread,
4042
)
4143
self.task_group.start_soon(self.kernel.start)
4244
task_status.started()

plugins/akernel_task/fps_akernel_task/main.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from functools import partial
4+
35
from fps import Module
46

57
from jupyverse_api.kernel import KernelFactory
@@ -9,6 +11,16 @@
911

1012

1113
class AKernelTaskModule(Module):
14+
def __init__(self, *args, execute_in_thread: bool = False, **kwargs) -> None:
15+
super().__init__(*args, **kwargs)
16+
self.execute_in_thread = execute_in_thread
17+
18+
async def prepare(self) -> None:
19+
kernels = await self.get(Kernels)
20+
kernels.register_kernel_factory("akernel", KernelFactory(partial(AKernelTask, execute_in_thread=self.execute_in_thread)))
21+
22+
23+
class AKernelThreadTaskModule(Module):
1224
async def prepare(self) -> None:
1325
kernels = await self.get(Kernels)
14-
kernels.register_kernel_factory("akernel", KernelFactory(AKernelTask))
26+
kernels.register_kernel_factory("akernel-thread", KernelFactory(partial(AKernelTask, execute_in_thread=True)))

plugins/akernel_task/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ text = "MIT"
2929
Homepage = "https://github.com/davidbrochart/akernel"
3030

3131
[project.entry-points]
32-
"fps.modules" = {akernel_task = "fps_akernel_task.main:AKernelTaskModule"}
33-
"jupyverse.modules" = {akernel_task = "fps_akernel_task.main:AKernelTaskModule"}
32+
"fps.modules" = {akernel_task = "fps_akernel_task.main:AKernelTaskModule", akernelthread_task = "fps_akernel_task.main:AKernelThreadTaskModule"}
33+
"jupyverse.modules" = {akernel_task = "fps_akernel_task.main:AKernelTaskModule", akernelthread_task = "fps_akernel_task.main:AKernelThreadTaskModule"}

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ test = [
4444

4545
[project.optional-dependencies]
4646
subprocess = [
47-
"zmq-anyio >=0.3.9,<0.4.0",
48-
"typer >=0.4.0",
47+
"zmq-anyio >=0.3.13,<0.4.0",
48+
"cyclopts >=4.3.0,<5.0.0",
4949
]
5050

5151
react = [
@@ -57,14 +57,15 @@ cache = [
5757
]
5858

5959
[project.scripts]
60-
akernel = "akernel.akernel:cli"
60+
akernel = "akernel.akernel:app"
6161

6262
[tool.hatch.build.targets.wheel]
6363
ignore-vcs = true
6464
packages = ["src/akernel"]
6565

6666
[tool.hatch.build.targets.wheel.shared-data]
6767
"share/jupyter/kernels/akernel/kernel.json" = "share/jupyter/kernels/akernel/kernel.json"
68+
"share/jupyter/kernels/akernel-thread/kernel.json" = "share/jupyter/kernels/akernel-thread/kernel.json"
6869

6970
[project.urls]
7071
Homepage = "https://github.com/davidbrochart/akernel"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"argv": [
3+
"akernel",
4+
"launch",
5+
"--execute-in-thread",
6+
"-f",
7+
"{connection_file}"
8+
],
9+
"display_name": "Python 3 (akernel-thread)",
10+
"language": "python"
11+
}

src/akernel/akernel.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
from __future__ import annotations
22

33
import json
4-
from typing import Optional, cast
4+
from typing import Annotated, cast
55

6-
import typer
76
from anyio import create_memory_object_stream, create_task_group, run, sleep_forever
7+
from cyclopts import App, Parameter
88

99
from .connect import connect_channel
1010
from .kernel import Kernel
1111
from .kernelspec import write_kernelspec
1212

1313

14-
cli = typer.Typer()
14+
app = App()
1515

1616

17-
@cli.command()
17+
@app.command()
1818
def install(
19-
mode: str = typer.Argument("", help="Mode of the kernel to install."),
20-
cache_dir: Optional[str] = typer.Option(
21-
None, "-c", help="Path to the cache directory, if mode is 'cache'."
22-
),
23-
):
19+
mode: str = "",
20+
cache_dir: str | None = None,
21+
) -> None:
22+
"""Install the kernel.
23+
24+
Args:
25+
mode: Mode of the kernel to install.
26+
cache_dir: Path to the cache directory, if mode is 'cache'.
27+
"""
2428
kernel_name = "akernel"
2529
if mode:
2630
modes = mode.split("-")
@@ -31,20 +35,27 @@ def install(
3135
write_kernelspec(kernel_name, mode, display_name, cache_dir)
3236

3337

34-
@cli.command()
38+
@app.command()
3539
def launch(
36-
mode: str = typer.Argument("", help="Mode of the kernel to launch."),
37-
cache_dir: Optional[str] = typer.Option(
38-
None, "-c", help="Path to the cache directory, if mode is 'cache'."
39-
),
40-
connection_file: str = typer.Option(..., "-f", help="Path to the connection file."),
40+
connection_file: Annotated[str, Parameter(alias=["-f"])],
41+
mode: str = "",
42+
cache_dir: str | None = None,
43+
execute_in_thread: bool = False,
4144
):
42-
akernel = AKernel(mode, cache_dir, connection_file)
45+
"""Launch the kernel.
46+
47+
Args:
48+
mode: Mode of the kernel to launch.
49+
cache_dir: Path to the cache directory, if mode is 'cache'.
50+
connection_file: Path to the connection file.
51+
execute_in_thread: Whether to run user code in a thread.
52+
"""
53+
akernel = AKernel(mode, cache_dir, connection_file, execute_in_thread)
4354
run(akernel.start)
4455

4556

4657
class AKernel:
47-
def __init__(self, mode, cache_dir, connection_file):
58+
def __init__(self, mode, cache_dir, connection_file, execute_in_thread):
4859
self._to_shell_send_stream, self._to_shell_receive_stream = create_memory_object_stream[list[bytes]]()
4960
self._from_shell_send_stream, self._from_shell_receive_stream = create_memory_object_stream[list[bytes]]()
5061
self._to_control_send_stream, self._to_control_receive_stream = create_memory_object_stream[list[bytes]]()
@@ -62,6 +73,7 @@ def __init__(self, mode, cache_dir, connection_file):
6273
self._from_iopub_send_stream,
6374
mode,
6475
cache_dir,
76+
execute_in_thread,
6577
)
6678
with open(connection_file) as f:
6779
connection_cfg = json.load(f)
@@ -136,4 +148,4 @@ async def from_iopub(self) -> None:
136148

137149

138150
if __name__ == "__main__":
139-
cli()
151+
app()

0 commit comments

Comments
 (0)