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.
- {akernel-0.3.4 → akernel-0.4.0}/.github/workflows/main.yml +4 -4
- akernel-0.4.0/.gitignore +2 -0
- akernel-0.4.0/PKG-INFO +69 -0
- akernel-0.4.0/README.md +41 -0
- akernel-0.4.0/plugins/akernel_task/README.md +15 -0
- {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/akernel_task.py +5 -3
- akernel-0.4.0/plugins/akernel_task/fps_akernel_task/main.py +30 -0
- akernel-0.4.0/plugins/akernel_task/fps_akernel_task/models.py +5 -0
- {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/pyproject.toml +6 -6
- {akernel-0.3.4 → akernel-0.4.0}/pyproject.toml +15 -20
- akernel-0.4.0/share/jupyter/kernels/akernel-thread/kernel.json +11 -0
- akernel-0.4.0/src/akernel/__init__.py +3 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/akernel.py +61 -48
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/comm.py +2 -1
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/manager.py +3 -2
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/connect.py +1 -4
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/display/display.py +1 -1
- akernel-0.4.0/src/akernel/execution.py +68 -0
- akernel-0.4.0/src/akernel/kernel.py +692 -0
- akernel-0.4.0/src/akernel/kernelspec.py +20 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/message.py +14 -9
- akernel-0.4.0/src/akernel/traceback.py +85 -0
- akernel-0.4.0/tests/test_anyio_kernel.py +55 -0
- akernel-0.4.0/tests/test_execution.py +187 -0
- akernel-0.4.0/tests/test_interrupt.py +346 -0
- akernel-0.4.0/tests/test_kernel.py +182 -0
- akernel-0.4.0/tests/test_shutdown.py +154 -0
- akernel-0.4.0/tests/test_startup.py +98 -0
- akernel-0.4.0/tests/test_traceback.py +83 -0
- akernel-0.3.4/MANIFEST.in +0 -1
- akernel-0.3.4/PKG-INFO +0 -171
- akernel-0.3.4/README.md +0 -139
- akernel-0.3.4/binder/environment.yml +0 -10
- akernel-0.3.4/binder/postBuild +0 -5
- akernel-0.3.4/examples/concurrency.ipynb +0 -127
- akernel-0.3.4/examples/reactivity.ipynb +0 -271
- akernel-0.3.4/plugins/akernel_task/README.md +0 -3
- akernel-0.3.4/plugins/akernel_task/fps_akernel_task/main.py +0 -14
- akernel-0.3.4/src/akernel/__init__.py +0 -6
- akernel-0.3.4/src/akernel/cache.py +0 -20
- akernel-0.3.4/src/akernel/code.py +0 -259
- akernel-0.3.4/src/akernel/execution.py +0 -160
- akernel-0.3.4/src/akernel/kernel.py +0 -545
- akernel-0.3.4/src/akernel/kernelspec.py +0 -23
- akernel-0.3.4/src/akernel/traceback.py +0 -47
- akernel-0.3.4/tests/conftest.py +0 -23
- akernel-0.3.4/tests/test_async_code.py +0 -95
- akernel-0.3.4/tests/test_execution.py +0 -238
- akernel-0.3.4/tests/test_kernel.py +0 -237
- akernel-0.3.4/tests/test_react_code.py +0 -61
- {akernel-0.3.4 → akernel-0.4.0}/.pre-commit-config.yaml +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/LICENSE +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/LICENSE +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/__init__.py +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/plugins/akernel_task/fps_akernel_task/py.typed +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/share/jupyter/kernels/akernel/kernel.json +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/__init__.py +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/__init__.py +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/getipython.py +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/IPython/core/interactiveshell.py +0 -0
- {akernel-0.3.4 → akernel-0.4.0}/src/akernel/comm/__init__.py +0 -0
- {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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
akernel-0.4.0/.gitignore
ADDED
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
|
+
[](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.
|
akernel-0.4.0/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[](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
|
|
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
|
-
|
|
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
|
+
)
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fps_akernel_task"
|
|
7
|
-
version = "0.
|
|
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.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"jupyverse-api >=0.
|
|
12
|
+
"jupyverse-api >=0.15.0,<0.16.0",
|
|
13
13
|
"anyio",
|
|
14
|
-
"akernel >=0.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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.
|
|
48
|
-
"
|
|
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:
|
|
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"
|
|
@@ -1,57 +1,65 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Annotated, cast
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
from
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
@
|
|
24
|
+
@app.command()
|
|
35
25
|
def launch(
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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,
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self.
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
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()
|
|
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)
|
|
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()
|
|
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)
|
|
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()
|
|
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)
|
|
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)
|
|
148
|
+
await self.iopub_channel.asend_multipart(msg, copy=True)
|
|
136
149
|
|
|
137
150
|
|
|
138
151
|
if __name__ == "__main__":
|
|
139
|
-
|
|
152
|
+
app()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
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:
|
|
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,
|
|
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=
|
|
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
|