akernel 0.3.4__tar.gz → 0.4.0__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.
Files changed (62) hide show
  1. {akernel-0.3.4 → akernel-0.4.0}/.github/workflows/main.yml +4 -4
  2. akernel-0.4.0/.gitignore +2 -0
  3. akernel-0.4.0/PKG-INFO +69 -0
  4. akernel-0.4.0/README.md +41 -0
  5. akernel-0.4.0/plugins/akernel_task/README.md +15 -0
  6. {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/akernel_task.py +5 -3
  7. akernel-0.4.0/plugins/akernel_task/fps_akernel_task/main.py +30 -0
  8. akernel-0.4.0/plugins/akernel_task/fps_akernel_task/models.py +5 -0
  9. {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/pyproject.toml +6 -6
  10. {akernel-0.3.4 → akernel-0.4.0}/pyproject.toml +15 -20
  11. akernel-0.4.0/share/jupyter/kernels/akernel-thread/kernel.json +11 -0
  12. akernel-0.4.0/src/akernel/__init__.py +3 -0
  13. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/akernel.py +61 -48
  14. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/comm.py +2 -1
  15. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/manager.py +3 -2
  16. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/connect.py +1 -4
  17. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/display/display.py +1 -1
  18. akernel-0.4.0/src/akernel/execution.py +68 -0
  19. akernel-0.4.0/src/akernel/kernel.py +692 -0
  20. akernel-0.4.0/src/akernel/kernelspec.py +20 -0
  21. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/message.py +14 -9
  22. akernel-0.4.0/src/akernel/traceback.py +85 -0
  23. akernel-0.4.0/tests/test_anyio_kernel.py +55 -0
  24. akernel-0.4.0/tests/test_execution.py +187 -0
  25. akernel-0.4.0/tests/test_interrupt.py +346 -0
  26. akernel-0.4.0/tests/test_kernel.py +182 -0
  27. akernel-0.4.0/tests/test_shutdown.py +154 -0
  28. akernel-0.4.0/tests/test_startup.py +98 -0
  29. akernel-0.4.0/tests/test_traceback.py +83 -0
  30. akernel-0.3.4/MANIFEST.in +0 -1
  31. akernel-0.3.4/PKG-INFO +0 -171
  32. akernel-0.3.4/README.md +0 -139
  33. akernel-0.3.4/binder/environment.yml +0 -10
  34. akernel-0.3.4/binder/postBuild +0 -5
  35. akernel-0.3.4/examples/concurrency.ipynb +0 -127
  36. akernel-0.3.4/examples/reactivity.ipynb +0 -271
  37. akernel-0.3.4/plugins/akernel_task/README.md +0 -3
  38. akernel-0.3.4/plugins/akernel_task/fps_akernel_task/main.py +0 -14
  39. akernel-0.3.4/src/akernel/__init__.py +0 -6
  40. akernel-0.3.4/src/akernel/cache.py +0 -20
  41. akernel-0.3.4/src/akernel/code.py +0 -259
  42. akernel-0.3.4/src/akernel/execution.py +0 -160
  43. akernel-0.3.4/src/akernel/kernel.py +0 -545
  44. akernel-0.3.4/src/akernel/kernelspec.py +0 -23
  45. akernel-0.3.4/src/akernel/traceback.py +0 -47
  46. akernel-0.3.4/tests/conftest.py +0 -23
  47. akernel-0.3.4/tests/test_async_code.py +0 -95
  48. akernel-0.3.4/tests/test_execution.py +0 -238
  49. akernel-0.3.4/tests/test_kernel.py +0 -237
  50. akernel-0.3.4/tests/test_react_code.py +0 -61
  51. {akernel-0.3.4 → akernel-0.4.0}/.pre-commit-config.yaml +0 -0
  52. {akernel-0.3.4 → akernel-0.4.0}/LICENSE +0 -0
  53. {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/LICENSE +0 -0
  54. {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/__init__.py +0 -0
  55. {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/py.typed +0 -0
  56. {akernel-0.3.4 → akernel-0.4.0}/share/jupyter/kernels/akernel/kernel.json +0 -0
  57. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/__init__.py +0 -0
  58. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/__init__.py +0 -0
  59. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/getipython.py +0 -0
  60. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/interactiveshell.py +0 -0
  61. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/__init__.py +0 -0
  62. {akernel-0.3.4 → akernel-0.4.0}/src/akernel/display/__init__.py +0 -0
@@ -10,7 +10,7 @@ on:
10
10
 
11
11
  defaults:
12
12
  run:
13
- shell: bash -l {0}
13
+ shell: bash
14
14
 
15
15
  jobs:
16
16
  test:
@@ -20,7 +20,7 @@ jobs:
20
20
  fail-fast: false
21
21
  matrix:
22
22
  os: [ubuntu-latest, macos-latest, windows-latest]
23
- python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
23
+ python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ]
24
24
 
25
25
  steps:
26
26
  - name: Checkout
@@ -33,7 +33,7 @@ jobs:
33
33
 
34
34
  - name: Install akernel
35
35
  run: |
36
- pip install . --group test
36
+ pip install ".[subprocess]" --group dev
37
37
 
38
38
  - name: Check style and types
39
39
  run: |
@@ -44,5 +44,5 @@ jobs:
44
44
  - name: Run tests
45
45
  run: |
46
46
  akernel --help
47
- test -f ${pythonLocation}/../share/jupyter/kernels/akernel/kernel.json
47
+ python -c "from pathlib import Path; import sys; assert (Path(sys.prefix) / 'share/jupyter/kernels/akernel/kernel.json').is_file()"
48
48
  pytest -v --reruns 5
@@ -0,0 +1,2 @@
1
+ __pycache__/
2
+ .ipynb_checkpoints/
akernel-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.5
2
+ Name: akernel
3
+ Version: 0.4.0
4
+ Summary: An asynchronous Python Jupyter kernel
5
+ Project-URL: Homepage, https://github.com/davidbrochart/akernel
6
+ Author-email: David Brochart <david.brochart@gmail.com>
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: jupyter
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: anyio<5.0.0,>=4.14.2
21
+ Requires-Dist: comm<1,>=0.1.3
22
+ Requires-Dist: python-dateutil
23
+ Requires-Dist: rich<15,>=14.0
24
+ Provides-Extra: subprocess
25
+ Requires-Dist: cyclopts<5.0.0,>=4.22.2; extra == 'subprocess'
26
+ Requires-Dist: zmq-anyio<0.5.0,>=0.4.1; extra == 'subprocess'
27
+ Description-Content-Type: text/markdown
28
+
29
+ [![Build Status](https://github.com/davidbrochart/akernel/workflows/CI/badge.svg)](https://github.com/davidbrochart/akernel/actions)
30
+
31
+ # akernel
32
+
33
+ An asynchronous Python Jupyter kernel built on AnyIO:
34
+ - runs in a separate process or in-process.
35
+ - supports top-level `await`.
36
+
37
+ ## Install
38
+
39
+ For a standalone, out-of-process kernel, install the `subprocess` extra:
40
+
41
+ ```bash
42
+ pip install "akernel[subprocess]"
43
+ ```
44
+
45
+ This will give you a `Python 3 (akernel)` in JupyterLab.
46
+
47
+ `akernel` can also run in-process with [Jupyverse](https://github.com/jupyter-server/jupyverse).
48
+ Install it with:
49
+
50
+ ```bash
51
+ pip install "fps-akernel-task"
52
+ ```
53
+
54
+ This will give you both `Python 3 (akernel)` and `Python 3 (akernel-thread)` kernels
55
+ in JupyterLab.
56
+
57
+ ## In-process kernels
58
+
59
+ They run in Jupyverse's process, so running blocking user code in the kernel
60
+ will also block Jupyverse.
61
+
62
+ `akernel-thread` is an in-process kernel that runs user code in a separate thread,
63
+ which won't block Jupyverse.
64
+
65
+ ## Limitations
66
+
67
+ - Cell output redirection currently uses the kernel's `print` function rather
68
+ than capturing all writes to `stdout` and `stderr`.
69
+ - In-process kernels cannot be interrupted while running blocking code.
@@ -0,0 +1,41 @@
1
+ [![Build Status](https://github.com/davidbrochart/akernel/workflows/CI/badge.svg)](https://github.com/davidbrochart/akernel/actions)
2
+
3
+ # akernel
4
+
5
+ An asynchronous Python Jupyter kernel built on AnyIO:
6
+ - runs in a separate process or in-process.
7
+ - supports top-level `await`.
8
+
9
+ ## Install
10
+
11
+ For a standalone, out-of-process kernel, install the `subprocess` extra:
12
+
13
+ ```bash
14
+ pip install "akernel[subprocess]"
15
+ ```
16
+
17
+ This will give you a `Python 3 (akernel)` in JupyterLab.
18
+
19
+ `akernel` can also run in-process with [Jupyverse](https://github.com/jupyter-server/jupyverse).
20
+ Install it with:
21
+
22
+ ```bash
23
+ pip install "fps-akernel-task"
24
+ ```
25
+
26
+ This will give you both `Python 3 (akernel)` and `Python 3 (akernel-thread)` kernels
27
+ in JupyterLab.
28
+
29
+ ## In-process kernels
30
+
31
+ They run in Jupyverse's process, so running blocking user code in the kernel
32
+ will also block Jupyverse.
33
+
34
+ `akernel-thread` is an in-process kernel that runs user code in a separate thread,
35
+ which won't block Jupyverse.
36
+
37
+ ## Limitations
38
+
39
+ - Cell output redirection currently uses the kernel's `print` function rather
40
+ than capturing all writes to `stdout` and `stderr`.
41
+ - In-process kernels cannot be interrupted while running blocking code.
@@ -0,0 +1,15 @@
1
+ # fps-akernel-task
2
+
3
+ An FPS plugin for the kernel task API.
4
+
5
+ ## Development installation
6
+
7
+ Install both packages from the repository root, in the environment running Jupyverse:
8
+
9
+ ```bash
10
+ python -m pip install -e . -e plugins/akernel_task
11
+ ```
12
+
13
+ Installing only the plugin can resolve its `akernel` dependency from PyPI instead
14
+ of this checkout. While working on unreleased kernel API changes, the plugin and
15
+ kernel must come from the same checkout. Restart Jupyverse after reinstalling.
@@ -2,14 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  from anyio import TASK_STATUS_IGNORED, create_task_group, sleep_forever
4
4
  from anyio.abc import TaskStatus
5
- from jupyverse_api.kernel import Kernel as _Kernel
5
+ from jupyverse_kernel import Kernel as _Kernel
6
6
 
7
7
  from akernel.kernel import Kernel
8
8
 
9
9
 
10
10
  class AKernelTask(_Kernel):
11
- def __init__(self, *args, **kwargs):
11
+ def __init__(self, *args, execute_in_thread: bool = False, **kwargs):
12
12
  super().__init__()
13
+ self.execute_in_thread = execute_in_thread
13
14
 
14
15
  async def start(self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED) -> None:
15
16
  async with (
@@ -37,6 +38,7 @@ class AKernelTask(_Kernel):
37
38
  self._to_stdin_receive_stream,
38
39
  self._from_stdin_send_stream,
39
40
  self._from_iopub_send_stream,
41
+ execute_in_thread=self.execute_in_thread,
40
42
  )
41
43
  self.task_group.start_soon(self.kernel.start)
42
44
  task_status.started()
@@ -47,4 +49,4 @@ class AKernelTask(_Kernel):
47
49
  self.task_group.cancel_scope.cancel()
48
50
 
49
51
  async def interrupt(self) -> None:
50
- pass
52
+ self.kernel.interrupt()
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import partial
4
+
5
+ from fps import Module
6
+ from jupyverse_kernel import KernelFactory
7
+ from jupyverse_kernels import Kernels
8
+
9
+ from .akernel_task import AKernelTask
10
+ from .models import KernelConfig
11
+
12
+
13
+ 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 = KernelConfig(execute_in_thread=execute_in_thread).execute_in_thread
17
+
18
+ async def prepare(self) -> None:
19
+ kernels = await self.get(Kernels)
20
+ kernels.register_kernel_factory(
21
+ "akernel", KernelFactory(partial(AKernelTask, execute_in_thread=self.execute_in_thread))
22
+ )
23
+
24
+
25
+ class AKernelThreadTaskModule(Module):
26
+ async def prepare(self) -> None:
27
+ kernels = await self.get(Kernels)
28
+ kernels.register_kernel_factory(
29
+ "akernel-thread", KernelFactory(partial(AKernelTask, execute_in_thread=True))
30
+ )
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class KernelConfig(BaseModel):
5
+ execute_in_thread: bool
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fps_akernel_task"
7
- version = "0.1.3"
7
+ version = "0.2.0"
8
8
  description = "An FPS plugin for the kernel task API"
9
9
  keywords = ["jupyter", "server", "fastapi", "plugins"]
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  dependencies = [
12
- "jupyverse-api >=0.10.1,<0.11.0",
12
+ "jupyverse-api >=0.15.0,<0.16.0",
13
13
  "anyio",
14
- "akernel >=0.3.4",
14
+ "akernel >=0.4.0",
15
15
  ]
16
16
 
17
17
  [[project.authors]]
@@ -29,5 +29,5 @@ text = "MIT"
29
29
  Homepage = "https://github.com/davidbrochart/akernel"
30
30
 
31
31
  [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"}
@@ -4,60 +4,51 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "akernel"
7
- version = "0.3.4"
7
+ version = "0.4.0"
8
8
  description = "An asynchronous Python Jupyter kernel"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
- requires-python = ">=3.9"
11
+ requires-python = ">=3.10"
12
12
  authors = [{name = "David Brochart", email = "david.brochart@gmail.com"}]
13
13
  classifiers = [
14
14
  "Development Status :: 4 - Beta",
15
15
  "Programming Language :: Python",
16
- "Programming Language :: Python :: 3.9",
17
16
  "Programming Language :: Python :: 3.10",
18
17
  "Programming Language :: Python :: 3.11",
19
18
  "Programming Language :: Python :: 3.12",
20
19
  "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
21
  "Programming Language :: Python :: Implementation :: CPython",
22
22
  "Programming Language :: Python :: Implementation :: PyPy",
23
23
  ]
24
24
  keywords = [ "jupyter" ]
25
25
  dependencies = [
26
+ "anyio >=4.14.2,<5.0.0",
26
27
  "python-dateutil",
27
- "colorama",
28
- "gast >=0.6.0, <0.7.0",
28
+ "rich >=14.0,<15",
29
29
  "comm >=0.1.3,<1",
30
30
  ]
31
31
 
32
32
  [dependency-groups]
33
- test = [
33
+ dev = [
34
+ "trio",
34
35
  "mypy",
35
36
  "ruff",
36
37
  "pytest",
37
- "pytest-asyncio",
38
38
  "pytest-rerunfailures",
39
39
  "types-python-dateutil",
40
40
  "kernel_driver >=0.0.7",
41
- "ipyx >=0.1.7",
42
- "zict",
41
+ "tornado >=6.1; sys_platform == 'win32'",
43
42
  ]
44
43
 
45
44
  [project.optional-dependencies]
46
45
  subprocess = [
47
- "zmq-anyio >=0.3.9,<0.4.0",
48
- "typer >=0.4.0",
49
- ]
50
-
51
- react = [
52
- "ipyx >=0.1.7",
53
- ]
54
-
55
- cache = [
56
- "zict",
46
+ "zmq-anyio >=0.4.1,<0.5.0",
47
+ "cyclopts >=4.22.2,<5.0.0",
57
48
  ]
58
49
 
59
50
  [project.scripts]
60
- akernel = "akernel.akernel:cli"
51
+ akernel = "akernel.akernel:app"
61
52
 
62
53
  [tool.hatch.build.targets.wheel]
63
54
  ignore-vcs = true
@@ -65,6 +56,7 @@ packages = ["src/akernel"]
65
56
 
66
57
  [tool.hatch.build.targets.wheel.shared-data]
67
58
  "share/jupyter/kernels/akernel/kernel.json" = "share/jupyter/kernels/akernel/kernel.json"
59
+ "share/jupyter/kernels/akernel-thread/kernel.json" = "share/jupyter/kernels/akernel-thread/kernel.json"
68
60
 
69
61
  [project.urls]
70
62
  Homepage = "https://github.com/davidbrochart/akernel"
@@ -75,3 +67,6 @@ exclude = ["examples"]
75
67
 
76
68
  [tool.uv.sources]
77
69
  fps-akernel-task = { workspace = true }
70
+
71
+ [tool.pytest.ini_options]
72
+ anyio_mode = "auto"
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version("akernel")
@@ -1,57 +1,65 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from typing import Optional, cast
4
+ from typing import Annotated, cast
5
5
 
6
- import typer
7
- from anyio import create_memory_object_stream, create_task_group, run, sleep_forever
6
+ from anyio import Event, create_memory_object_stream, create_task_group, run
7
+ from cyclopts import App, Parameter
8
8
 
9
9
  from .connect import connect_channel
10
10
  from .kernel import Kernel
11
11
  from .kernelspec import write_kernelspec
12
+ from .message import deserialize, feed_identities
12
13
 
14
+ app = App()
13
15
 
14
- cli = typer.Typer()
15
16
 
16
-
17
- @cli.command()
18
- 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
- ):
24
- kernel_name = "akernel"
25
- if mode:
26
- modes = mode.split("-")
27
- modes.sort()
28
- mode = "-".join(modes)
29
- kernel_name += f"-{mode}"
30
- display_name = f"Python 3 ({kernel_name})"
31
- write_kernelspec(kernel_name, mode, display_name, cache_dir)
17
+ @app.command()
18
+ def install(execute_in_thread: bool = False) -> None:
19
+ """Install the kernel, optionally executing user code in a thread."""
20
+ kernel_name = "akernel-thread" if execute_in_thread else "akernel"
21
+ write_kernelspec(kernel_name, f"Python 3 ({kernel_name})", execute_in_thread)
32
22
 
33
23
 
34
- @cli.command()
24
+ @app.command()
35
25
  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."),
26
+ connection_file: Annotated[str, Parameter(alias=["-f"])],
27
+ execute_in_thread: bool = False,
41
28
  ):
42
- akernel = AKernel(mode, cache_dir, connection_file)
29
+ """Launch the kernel.
30
+
31
+ Args:
32
+ connection_file: Path to the connection file.
33
+ execute_in_thread: Whether to run user code in a thread.
34
+ """
35
+ akernel = AKernel(connection_file, execute_in_thread)
43
36
  run(akernel.start)
44
37
 
45
38
 
46
39
  class AKernel:
47
- def __init__(self, mode, cache_dir, connection_file):
48
- self._to_shell_send_stream, self._to_shell_receive_stream = create_memory_object_stream[list[bytes]]()
49
- self._from_shell_send_stream, self._from_shell_receive_stream = create_memory_object_stream[list[bytes]]()
50
- self._to_control_send_stream, self._to_control_receive_stream = create_memory_object_stream[list[bytes]]()
51
- self._from_control_send_stream, self._from_control_receive_stream = create_memory_object_stream[list[bytes]]()
52
- self._to_stdin_send_stream, self._to_stdin_receive_stream = create_memory_object_stream[list[bytes]]()
53
- self._from_stdin_send_stream, self._from_stdin_receive_stream = create_memory_object_stream[list[bytes]]()
54
- self._from_iopub_send_stream, self._from_iopub_receive_stream = create_memory_object_stream[list[bytes]](max_buffer_size=float("inf"))
40
+ def __init__(self, connection_file, execute_in_thread=False):
41
+ self._shutdown_reply_sent = Event()
42
+ self._to_shell_send_stream, self._to_shell_receive_stream = create_memory_object_stream[
43
+ list[bytes]
44
+ ]()
45
+ self._from_shell_send_stream, self._from_shell_receive_stream = create_memory_object_stream[
46
+ list[bytes]
47
+ ]()
48
+ self._to_control_send_stream, self._to_control_receive_stream = create_memory_object_stream[
49
+ list[bytes]
50
+ ]()
51
+ self._from_control_send_stream, self._from_control_receive_stream = (
52
+ create_memory_object_stream[list[bytes]]()
53
+ )
54
+ self._to_stdin_send_stream, self._to_stdin_receive_stream = create_memory_object_stream[
55
+ list[bytes]
56
+ ]()
57
+ self._from_stdin_send_stream, self._from_stdin_receive_stream = create_memory_object_stream[
58
+ list[bytes]
59
+ ]()
60
+ self._from_iopub_send_stream, self._from_iopub_receive_stream = create_memory_object_stream[
61
+ list[bytes]
62
+ ](max_buffer_size=float("inf"))
55
63
  self.kernel = Kernel(
56
64
  self._to_shell_receive_stream,
57
65
  self._from_shell_send_stream,
@@ -60,8 +68,7 @@ class AKernel:
60
68
  self._to_stdin_receive_stream,
61
69
  self._from_stdin_send_stream,
62
70
  self._from_iopub_send_stream,
63
- mode,
64
- cache_dir,
71
+ execute_in_thread,
65
72
  )
66
73
  with open(connection_file) as f:
67
74
  connection_cfg = json.load(f)
@@ -73,7 +80,6 @@ class AKernel:
73
80
 
74
81
  async def start(self) -> None:
75
82
  async with (
76
- create_task_group() as tg,
77
83
  self._to_shell_send_stream,
78
84
  self._to_shell_receive_stream,
79
85
  self._from_shell_send_stream,
@@ -92,8 +98,8 @@ class AKernel:
92
98
  self.control_channel,
93
99
  self.stdin_channel,
94
100
  self.iopub_channel,
101
+ create_task_group() as tg,
95
102
  ):
96
- tg.start_soon(self.kernel.start)
97
103
  tg.start_soon(self.to_shell)
98
104
  tg.start_soon(self.from_shell)
99
105
  tg.start_soon(self.to_control)
@@ -101,39 +107,46 @@ class AKernel:
101
107
  tg.start_soon(self.to_stdin)
102
108
  tg.start_soon(self.from_stdin)
103
109
  tg.start_soon(self.from_iopub)
104
- await sleep_forever()
110
+ await self.kernel.start()
111
+ # Receiving from the memory stream does not mean the reply has
112
+ # reached the socket yet. Keep the forwarder alive until it has.
113
+ await self._shutdown_reply_sent.wait()
114
+ tg.cancel_scope.cancel()
105
115
 
106
116
  async def to_shell(self) -> None:
107
117
  while True:
108
- msg = await self.shell_channel.arecv_multipart().wait()
118
+ msg = await self.shell_channel.arecv_multipart()
109
119
  await self._to_shell_send_stream.send(msg)
110
120
 
111
121
  async def from_shell(self) -> None:
112
122
  async for msg in self._from_shell_receive_stream:
113
- await self.shell_channel.asend_multipart(msg, copy=True).wait()
123
+ await self.shell_channel.asend_multipart(msg, copy=True)
114
124
 
115
125
  async def to_control(self) -> None:
116
126
  while True:
117
- msg = await self.control_channel.arecv_multipart().wait()
127
+ msg = await self.control_channel.arecv_multipart()
118
128
  await self._to_control_send_stream.send(msg)
119
129
 
120
130
  async def from_control(self) -> None:
121
131
  async for msg in self._from_control_receive_stream:
122
- await self.control_channel.asend_multipart(msg, copy=True).wait()
132
+ await self.control_channel.asend_multipart(msg, copy=True)
133
+ reply = deserialize(feed_identities(msg)[1])
134
+ if reply["header"]["msg_type"] == "shutdown_reply" and not reply["content"]["restart"]:
135
+ self._shutdown_reply_sent.set()
123
136
 
124
137
  async def to_stdin(self) -> None:
125
138
  while True:
126
- msg = await self.stdin_channel.arecv_multipart().wait()
139
+ msg = await self.stdin_channel.arecv_multipart()
127
140
  await self._to_stdin_send_stream.send(msg)
128
141
 
129
142
  async def from_stdin(self) -> None:
130
143
  async for msg in self._from_stdin_receive_stream:
131
- await self.stdin_channel.asend_multipart(msg, copy=True).wait()
144
+ await self.stdin_channel.asend_multipart(msg, copy=True)
132
145
 
133
146
  async def from_iopub(self) -> None:
134
147
  async for msg in self._from_iopub_receive_stream:
135
- await self.iopub_channel.asend_multipart(msg, copy=True).wait()
148
+ await self.iopub_channel.asend_multipart(msg, copy=True)
136
149
 
137
150
 
138
151
  if __name__ == "__main__":
139
- cli()
152
+ app()
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Callable
3
+ from collections.abc import Callable
4
+ from typing import Any
4
5
 
5
6
  import comm
6
7
 
@@ -1,4 +1,5 @@
1
- from typing import Dict, Callable, cast
1
+ from collections.abc import Callable
2
+ from typing import cast
2
3
 
3
4
  import comm
4
5
 
@@ -7,7 +8,7 @@ from .comm import Comm
7
8
 
8
9
  class CommManager(comm.CommManager):
9
10
  comms: dict[str, comm.base_comm.BaseComm]
10
- targets: Dict[str, Callable]
11
+ targets: dict[str, Callable]
11
12
 
12
13
  def __init__(self) -> None:
13
14
  super().__init__()
@@ -1,15 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Union
4
-
5
3
  import zmq
6
4
  from zmq import Context
7
5
  from zmq_anyio import Socket
8
6
 
9
-
10
7
  context = Context()
11
8
 
12
- cfg_t = dict[str, Union[str, int]]
9
+ cfg_t = dict[str, str | int]
13
10
 
14
11
  channel_socket_types = {
15
12
  "shell": zmq.ROUTER,
@@ -8,7 +8,7 @@ def display(*args, raw: bool = False) -> None:
8
8
  data = args[0]
9
9
  msg = create_message(
10
10
  "display_data",
11
- content=dict(data=data, transient={}, metadata={}),
11
+ content={"data": data, "transient": {}, "metadata": {}},
12
12
  parent_header=parent_header,
13
13
  )
14
14
  to_send = serialize(msg, KERNEL.key)
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+ import __future__
3
+
4
+ import ast
5
+ from dataclasses import dataclass
6
+ from inspect import CO_COROUTINE
7
+ from types import CodeType
8
+ from typing import Any
9
+
10
+ from .traceback import get_traceback
11
+
12
+ FUTURE_FLAGS = sum(getattr(__future__, name).compiler_flag for name in __future__.all_feature_names)
13
+
14
+
15
+ @dataclass
16
+ class CompiledCell:
17
+ body: CodeType
18
+ expression: CodeType | None
19
+
20
+
21
+ def compile_cell(code: str, task_i: int = 0) -> CompiledCell:
22
+ filename = f"<cell-{task_i}>"
23
+ tree = ast.parse(code, filename=filename)
24
+ flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
25
+ # Validate the entire cell before executing any part of it. This also
26
+ # determines future-import flags needed by the final expression.
27
+ full_code = compile(tree, filename, "exec", flags=flags, dont_inherit=True)
28
+ flags |= full_code.co_flags & FUTURE_FLAGS
29
+ expression = None
30
+ last_statement = tree.body[-1] if tree.body else None
31
+ if isinstance(last_statement, ast.Expr):
32
+ tree.body.pop()
33
+ expression = compile(
34
+ ast.Expression(last_statement.value),
35
+ filename,
36
+ "eval",
37
+ flags=flags,
38
+ dont_inherit=True,
39
+ )
40
+ body = compile(tree, filename, "exec", flags=flags, dont_inherit=True)
41
+ return CompiledCell(body, expression)
42
+
43
+
44
+ def prepare_cell(code: str, task_i: int = 0, execution_count: int = 0):
45
+ traceback = []
46
+ exception = None
47
+ cell = None
48
+ try:
49
+ cell = compile_cell(code, task_i)
50
+ except (SyntaxError, ValueError) as exc:
51
+ # Python 3.10 reports null bytes in source as ValueError.
52
+ exception = exc
53
+ traceback = get_traceback(
54
+ code, exception, None, execution_count, {f"<cell-{task_i}>": code}
55
+ )
56
+ return cell, traceback, exception
57
+
58
+
59
+ async def execute_cell(cell: CompiledCell, namespace: dict[str, Any]) -> Any:
60
+ result = eval(cell.body, namespace)
61
+ if cell.body.co_flags & CO_COROUTINE:
62
+ await result
63
+ if cell.expression is not None:
64
+ result = eval(cell.expression, namespace)
65
+ if cell.expression.co_flags & CO_COROUTINE:
66
+ result = await result
67
+ return result
68
+ return None