kiwi-array-jupyter 0.2.47__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,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: kiwi-array-jupyter
3
+ Version: 0.2.47
4
+ Summary: Jupyter kernel for Kiwi.
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: kiwi-array==0.2.47
8
+ Requires-Dist: ipykernel>=6.29
9
+ Requires-Dist: jupyter_client>=8.6
10
+ Requires-Dist: jupyter_core>=5.7
11
+
12
+ # Kiwi Jupyter Kernel
13
+
14
+ `kiwi-array-jupyter` is a Jupyter kernel for Kiwi that takes the direct-bridge
15
+ route:
16
+
17
+ - Jupyter runs a small Python `ipykernel` host
18
+ - the Python host loads the local Kiwi C ABI bridge with `ctypes`
19
+ - the bridge owns a persistent Kiwi `Session`
20
+ - the bridge and `Session` come from the Kiwi source checkout or the packaged wheel payload
21
+
22
+ This deliberately avoids the JSON REPL transport. The kernel boundary is:
23
+
24
+ ```text
25
+ Jupyter
26
+ -> ipykernel
27
+ -> ctypes
28
+ -> libkiwi_bridge
29
+ -> Kiwi Session
30
+ -> MLX
31
+ ```
32
+
33
+ ## What Is Here
34
+
35
+ - `build.zig`: small Zig regression target wired against [`../../src/kiwi_bridge.zig`](../../src/kiwi_bridge.zig)
36
+ - [`../../bridge/include/kiwi_bridge.h`](../../bridge/include/kiwi_bridge.h): shared C ABI for persistent Kiwi sessions
37
+ - [`../runtime/src/kiwi_array/bridge.py`](../runtime/src/kiwi_array/bridge.py): main-owned Python bridge wrapper consumed by this kernel
38
+ - `zig-src/tests.zig`: focused bridge regression tests
39
+ - `pyproject.toml`: editable Python package for the kernel host
40
+ - `src/kiwi_array_jupyter_kernel/binding.py`: thin re-export of the main-owned Python bridge wrapper
41
+ - `src/kiwi_array_jupyter_kernel/execution.py`: notebook cell execution helpers
42
+ - `src/kiwi_array_jupyter_kernel/kernel.py`: `ipykernel` integration
43
+ - `src/kiwi_array_jupyter_kernel/install.py`: bridge build plus kernelspec install helper
44
+ - `src/kiwi_array_jupyter_kernel/assets/kernel-logo.png`: vendored kernelspec logo source used for Jupyter icon files
45
+
46
+ ## Current Scope
47
+
48
+ This first cut is intentionally narrow:
49
+
50
+ - execution is direct and stateful
51
+ - cells run line-by-line using the same blank-line and full-line-comment rules as script mode
52
+ - assignments suppress output
53
+ - echoed results surface in the notebook
54
+ - JSON strings that carry a Vega-Lite schema also surface as Vega-Lite MIME data
55
+ - completion and `inspect` cover the current built-in named functions
56
+ - delimiter-only `is_complete` distinguishes incomplete from clearly invalid input
57
+
58
+ It does not yet try to provide:
59
+
60
+ - general structured array display beyond `text/plain`
61
+ - parser-aware completeness checks
62
+ - full symbol completion from live session state
63
+ - a native Zig Jupyter wire-protocol implementation
64
+
65
+ ## Build And Install
66
+
67
+ From the Kiwi repository root:
68
+
69
+ ```sh
70
+ cd python/jupyter
71
+ uv python install --managed-python 3.14.2
72
+ uv sync --managed-python --python 3.14.2 --group dev
73
+ uv run --managed-python python -m kiwi_array_jupyter_kernel.install --user
74
+ ```
75
+
76
+ In a fresh public checkout, bootstrap MLX first from the Kiwi root with
77
+ `KIWI_MLX_BACKEND=cpu scripts/bootstrap_deps.sh` unless `.deps/mlx` and
78
+ `.deps/mlx-c` already exist.
79
+
80
+ The install helper builds the official bridge in `../../zig-out/lib` if needed.
81
+ The installed library is:
82
+
83
+ - `../../zig-out/lib/libkiwi_bridge.dylib` on macOS
84
+ - `../../zig-out/lib/libkiwi_bridge.so` on Linux
85
+
86
+ The install helper writes a kernelspec that launches:
87
+
88
+ ```sh
89
+ python -m kiwi_array_jupyter_kernel -f {connection_file}
90
+ ```
91
+
92
+ When the bridge has already been built, the kernelspec also captures the
93
+ absolute `KIWI_BRIDGE_LIB` path so notebook startup does not depend on
94
+ accidentally finding the right local library at runtime.
95
+
96
+ ## Tests
97
+
98
+ From this directory:
99
+
100
+ ```sh
101
+ uv run --managed-python pytest tests/test_kiwi_jupyter_kernel.py
102
+ zig build test
103
+ ```
104
+
105
+ From the repository root, `make test-jupyter` runs the Python Jupyter kernel
106
+ tests when that target is available.
107
+
108
+ ## Notes
109
+
110
+ - The pinned dev interpreter is the uv-managed CPython `3.14.2` patch release tracked in [`.python-version`](.python-version).
111
+ - The `dev` dependency group includes the Python test stack for the kernel host plus `jupyter_kernel_test` for end-to-end kernel validation work.
112
+ - The kernelspec logo is sourced from the vendored [`src/kiwi_array_jupyter_kernel/assets/kernel-logo.png`](src/kiwi_array_jupyter_kernel/assets/kernel-logo.png), derived from the current website favicon artwork instead of depending on generated website output.
113
+ - The loader looks for packaged wheel libraries first and then `../../zig-out/lib` in source checkouts.
114
+ - Set `KIWI_BRIDGE_LIB` to override the bridge path.
115
+ - `KIWI_JUPYTER_BRIDGE_LIB` is still accepted as a compatibility alias.
116
+ - Set `KIWI_MLX_PREFIX` and `KIWI_MLX_C_INCLUDE` to override the MLX link inputs when building the bridge from Python.
117
+ - Set `KIWI_JUPYTER_DEVICE=cpu` or `gpu` to pin the default MLX device.
@@ -0,0 +1,12 @@
1
+ kiwi_array_jupyter_kernel/__init__.py,sha256=fiqyqv19NWBX0LOG-WYLhi4tB_eP3gfbiIfuFVJcx_0,86
2
+ kiwi_array_jupyter_kernel/__main__.py,sha256=Rexv_oz2WINXaK8I6hFLbVUvDRd43XrZHT-Pms__X2Y,231
3
+ kiwi_array_jupyter_kernel/binding.py,sha256=LQ9jwQLjcjhky03znbtOTmSf4gALNZbcH3f-REqvVbs,1678
4
+ kiwi_array_jupyter_kernel/execution.py,sha256=uS6qIb6dunKXU2KKghyG-Uhh408G1ejCJDQfkMCy0yw,2488
5
+ kiwi_array_jupyter_kernel/install.py,sha256=_kD76sjfZlSaHx_V5WcQrNFuKHgioAKfj-MDQj8Ltx4,5788
6
+ kiwi_array_jupyter_kernel/kernel.py,sha256=1EMOAerSPnqFtwp0rOcbLUK9FDoQ6pELV_-CblvKH3k,5695
7
+ kiwi_array_jupyter_kernel/assets/kernel-logo.png,sha256=xLMso0FjTO4slnlqWoMOsKdTzZ0KC8J7ktaSpk5maIM,37182
8
+ kiwi_array_jupyter-0.2.47.dist-info/METADATA,sha256=l7EmsJJ5W3NiomorkCL2qxGtMGvEG6yQWg49HsECA0Y,4676
9
+ kiwi_array_jupyter-0.2.47.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ kiwi_array_jupyter-0.2.47.dist-info/entry_points.txt,sha256=9iaCDCXiOxpbm_pcRZvzgMrq28wRiCeZ3Zu_eTO65rA,230
11
+ kiwi_array_jupyter-0.2.47.dist-info/top_level.txt,sha256=nbewsgAcmb3-Fcw3cptPjUhL237w2giyMn0R5BHYxDo,26
12
+ kiwi_array_jupyter-0.2.47.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ kiwi-array-jupyter-install = kiwi_array_jupyter_kernel.install:main
3
+ kiwi-array-jupyter-kernel-install = kiwi_array_jupyter_kernel.install:main
4
+ kiwi-jupyter-kernel-install = kiwi_array_jupyter_kernel.install:main
@@ -0,0 +1 @@
1
+ kiwi_array_jupyter_kernel
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ __version__ = "0.2.47"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from ipykernel.kernelapp import IPKernelApp
4
+
5
+ from .kernel import KiwiKernel
6
+
7
+
8
+ def main() -> None:
9
+ IPKernelApp.launch_instance(kernel_class=KiwiKernel)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ DEVICE_ENV_VAR = "KIWI_JUPYTER_DEVICE"
7
+
8
+
9
+ def jupyter_root() -> Path:
10
+ return Path(__file__).resolve().parents[2]
11
+
12
+
13
+ def python_root() -> Path:
14
+ return jupyter_root().parent
15
+
16
+
17
+ def kiwi_root() -> Path:
18
+ return python_root().parent
19
+
20
+
21
+ def experiment_root() -> Path:
22
+ return jupyter_root()
23
+
24
+
25
+ try:
26
+ import kiwi_array.bridge # noqa: F401
27
+ except ImportError:
28
+ _KIWI_BRIDGE_PYTHON_SRC = python_root() / "runtime" / "src"
29
+ if str(_KIWI_BRIDGE_PYTHON_SRC) not in sys.path:
30
+ sys.path.insert(0, str(_KIWI_BRIDGE_PYTHON_SRC))
31
+
32
+ from kiwi_array.bridge import ( # noqa: E402
33
+ AUTOGRAD_PATH_NAMES,
34
+ LEGACY_LIB_ENV_VAR,
35
+ LIB_ENV_VAR,
36
+ RUNTIME_ENV_VAR,
37
+ STATUS_NAMES,
38
+ KiwiBridgeError,
39
+ KiwiEvalResult,
40
+ KiwiSession,
41
+ active_runtime_descriptor,
42
+ build_bridge,
43
+ default_library_candidates,
44
+ discover_runtime_descriptors,
45
+ find_library_path,
46
+ implementation_root,
47
+ load_library,
48
+ platform_library_name,
49
+ runtime_library_env_var,
50
+ runtime_library_search_dir,
51
+ )
52
+
53
+ __all__ = [
54
+ "AUTOGRAD_PATH_NAMES",
55
+ "DEVICE_ENV_VAR",
56
+ "LEGACY_LIB_ENV_VAR",
57
+ "LIB_ENV_VAR",
58
+ "RUNTIME_ENV_VAR",
59
+ "STATUS_NAMES",
60
+ "KiwiBridgeError",
61
+ "KiwiEvalResult",
62
+ "KiwiSession",
63
+ "active_runtime_descriptor",
64
+ "build_bridge",
65
+ "default_library_candidates",
66
+ "discover_runtime_descriptors",
67
+ "experiment_root",
68
+ "find_library_path",
69
+ "implementation_root",
70
+ "jupyter_root",
71
+ "kiwi_root",
72
+ "load_library",
73
+ "platform_library_name",
74
+ "python_root",
75
+ "runtime_library_env_var",
76
+ "runtime_library_search_dir",
77
+ ]
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Iterator, Protocol
5
+
6
+ from .binding import KiwiEvalResult
7
+
8
+
9
+ class SessionProtocol(Protocol):
10
+ def eval(self, source: str) -> KiwiEvalResult:
11
+ ...
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class CellOutput:
16
+ line_no: int
17
+ text: str
18
+ autograd_path: str
19
+ display_mime: str | None = None
20
+ display_data: str | None = None
21
+
22
+
23
+ class CellExecutionError(RuntimeError):
24
+ def __init__(self, line_no: int, status: str) -> None:
25
+ self.line_no = line_no
26
+ self.status = status
27
+ super().__init__(f"line {line_no}: !{status}")
28
+
29
+
30
+ def iter_executable_lines(code: str) -> Iterator[tuple[int, str]]:
31
+ for line_no, raw_line in enumerate(code.splitlines(), start=1):
32
+ line = raw_line.rstrip("\r")
33
+ trimmed = line.strip(" \t")
34
+ if not trimmed or trimmed.startswith("/"):
35
+ continue
36
+ yield line_no, line
37
+
38
+
39
+ def execute_cell(session: SessionProtocol, code: str) -> list[CellOutput]:
40
+ outputs: list[CellOutput] = []
41
+ for line_no, line in iter_executable_lines(code):
42
+ result = session.eval(line)
43
+ if result.status != "ok":
44
+ raise CellExecutionError(line_no, result.status)
45
+ if result.echoed and result.text is not None:
46
+ outputs.append(
47
+ CellOutput(
48
+ line_no=line_no,
49
+ text=result.text,
50
+ autograd_path=result.autograd_path,
51
+ display_mime=result.display_mime,
52
+ display_data=result.display_data,
53
+ )
54
+ )
55
+ return outputs
56
+
57
+
58
+ def simple_is_complete(code: str) -> str:
59
+ stack: list[str] = []
60
+ in_string = False
61
+ pairs = {
62
+ "(": ")",
63
+ "[": "]",
64
+ "{": "}",
65
+ }
66
+
67
+ for raw_line in code.splitlines():
68
+ stripped = raw_line.lstrip(" \t")
69
+ if stripped.startswith("/"):
70
+ continue
71
+
72
+ for ch in raw_line:
73
+ if ch == '"':
74
+ in_string = not in_string
75
+ continue
76
+ if in_string:
77
+ continue
78
+ if ch in "([{":
79
+ stack.append(ch)
80
+ continue
81
+ if ch in ")]}":
82
+ if not stack:
83
+ return "invalid"
84
+ opener = stack.pop()
85
+ if pairs[opener] != ch:
86
+ return "invalid"
87
+
88
+ if in_string or stack:
89
+ return "incomplete"
90
+ return "complete"
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from importlib import resources
5
+ import json
6
+ import os
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Any, Optional, Sequence
12
+
13
+ from .binding import (
14
+ DEVICE_ENV_VAR,
15
+ LIB_ENV_VAR,
16
+ build_bridge,
17
+ experiment_root,
18
+ find_library_path,
19
+ runtime_library_env_var,
20
+ runtime_library_search_dir,
21
+ )
22
+
23
+ DEFAULT_KERNEL_NAME = "kiwi"
24
+ DEFAULT_DISPLAY_NAME = "Kiwi"
25
+
26
+
27
+ def default_logo_candidates() -> list[Path]:
28
+ return [
29
+ Path(__file__).resolve().parent / "assets" / "kernel-logo.png",
30
+ ]
31
+
32
+
33
+ def find_logo_path(path: Optional[str | Path] = None) -> Optional[Path]:
34
+ if path is not None:
35
+ candidate = Path(path).expanduser()
36
+ if candidate.exists():
37
+ return candidate
38
+ raise FileNotFoundError(f"Kiwi kernel logo not found at {candidate}")
39
+
40
+ for candidate in default_logo_candidates():
41
+ if candidate.exists():
42
+ return candidate
43
+ return None
44
+
45
+
46
+ def write_kernel_logos(dst_dir: Path, logo_path: Optional[str | Path] = None) -> None:
47
+ source = find_logo_path(logo_path)
48
+ if source is not None:
49
+ for name in ("logo-32x32.png", "logo-64x64.png"):
50
+ shutil.copy2(source, dst_dir / name)
51
+ return
52
+
53
+ resource = resources.files("kiwi_array_jupyter_kernel") / "assets" / "kernel-logo.png"
54
+ with resources.as_file(resource) as resource_path:
55
+ for name in ("logo-32x32.png", "logo-64x64.png"):
56
+ shutil.copy2(resource_path, dst_dir / name)
57
+
58
+
59
+ def source_python_paths() -> list[Path]:
60
+ candidates = [
61
+ experiment_root() / "src",
62
+ experiment_root().parent / "runtime" / "src",
63
+ ]
64
+ return [path for path in candidates if path.exists()]
65
+
66
+
67
+ def build_kernel_spec(
68
+ display_name: str = DEFAULT_DISPLAY_NAME,
69
+ device: str = "auto",
70
+ python_executable: Optional[str] = None,
71
+ library_path: Optional[str] = None,
72
+ python_path: Optional[Sequence[str | Path]] = None,
73
+ ) -> dict[str, Any]:
74
+ python_cmd = python_executable or sys.executable
75
+ env = {
76
+ DEVICE_ENV_VAR: device,
77
+ }
78
+ if python_path:
79
+ paths = [str(Path(path)) for path in python_path]
80
+ existing_python_path = os.environ.get("PYTHONPATH")
81
+ env["PYTHONPATH"] = (
82
+ os.pathsep.join([*paths, existing_python_path])
83
+ if existing_python_path
84
+ else os.pathsep.join(paths)
85
+ )
86
+ runtime_env_var = runtime_library_env_var()
87
+ if runtime_env_var is not None:
88
+ runtime_dir = runtime_library_search_dir()
89
+ existing = os.environ.get(runtime_env_var)
90
+ env[runtime_env_var] = (
91
+ os.pathsep.join([str(runtime_dir), existing]) if existing else str(runtime_dir)
92
+ )
93
+ if library_path is not None:
94
+ env[LIB_ENV_VAR] = library_path
95
+ return {
96
+ "argv": [python_cmd, "-m", "kiwi_array_jupyter_kernel", "-f", "{connection_file}"],
97
+ "display_name": display_name,
98
+ "language": "kiwi",
99
+ "env": env,
100
+ }
101
+
102
+
103
+ def install_kernel(
104
+ name: str = DEFAULT_KERNEL_NAME,
105
+ display_name: str = DEFAULT_DISPLAY_NAME,
106
+ device: str = "auto",
107
+ user: bool = True,
108
+ prefix: Optional[str] = None,
109
+ skip_build: bool = False,
110
+ optimize: str = "ReleaseFast",
111
+ ) -> str:
112
+ if not skip_build:
113
+ build_bridge(optimize=optimize)
114
+
115
+ from jupyter_client.kernelspec import KernelSpecManager
116
+
117
+ try:
118
+ library_path = str(find_library_path())
119
+ except FileNotFoundError:
120
+ library_path = None
121
+
122
+ spec = build_kernel_spec(
123
+ display_name=display_name,
124
+ device=device,
125
+ library_path=library_path,
126
+ python_path=source_python_paths(),
127
+ )
128
+ with tempfile.TemporaryDirectory() as tmpdir:
129
+ tmp_path = Path(tmpdir)
130
+ (tmp_path / "kernel.json").write_text(json.dumps(spec, indent=2) + "\n")
131
+ write_kernel_logos(tmp_path)
132
+ return KernelSpecManager().install_kernel_spec(tmpdir, kernel_name=name, user=user, prefix=prefix)
133
+
134
+
135
+ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
136
+ parser = argparse.ArgumentParser(description="Install the Kiwi Jupyter kernel.")
137
+ parser.add_argument("--name", default=DEFAULT_KERNEL_NAME, help="Kernel name used by Jupyter.")
138
+ parser.add_argument("--display-name", default=DEFAULT_DISPLAY_NAME, help="Kernel display name shown in Jupyter.")
139
+ parser.add_argument("--device", choices=("auto", "cpu", "gpu"), default="auto", help="Default Kiwi device for the kernel.")
140
+ parser.add_argument("--optimize", choices=("Debug", "ReleaseFast", "ReleaseSafe", "ReleaseSmall"), default="ReleaseFast", help="Zig optimize mode used when building the bridge.")
141
+ parser.add_argument("--skip-build", action="store_true", help="Skip `zig build` before installing the kernelspec.")
142
+ parser.add_argument("--prefix", help="Install the kernelspec under the given prefix.")
143
+ parser.add_argument("--sys-prefix", action="store_true", help="Install the kernelspec into the current Python environment prefix.")
144
+ parser.add_argument("--user", action="store_true", help="Install the kernelspec into the user Jupyter data dir (default).")
145
+ return parser.parse_args(argv)
146
+
147
+
148
+ def main(argv: Optional[list[str]] = None) -> int:
149
+ args = parse_args(argv)
150
+ prefix = args.prefix
151
+ user = True
152
+ if args.sys_prefix:
153
+ prefix = sys.prefix
154
+ user = False
155
+ elif args.prefix is not None:
156
+ user = False
157
+
158
+ location = install_kernel(
159
+ name=args.name,
160
+ display_name=args.display_name,
161
+ device=args.device,
162
+ user=user,
163
+ prefix=prefix,
164
+ skip_build=args.skip_build,
165
+ optimize=args.optimize,
166
+ )
167
+ print(location)
168
+ return 0
169
+
170
+
171
+ if __name__ == "__main__":
172
+ raise SystemExit(main())
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ from typing import Any
7
+
8
+ from ipykernel.kernelbase import Kernel
9
+
10
+ from . import __version__
11
+ from .binding import DEVICE_ENV_VAR, LIB_ENV_VAR, KiwiSession
12
+ from .execution import CellExecutionError, CellOutput, execute_cell, simple_is_complete
13
+
14
+ SYMBOL_DOCS = {
15
+ "exp": "exp[x] applies the elementwise exponential.",
16
+ "grad": (
17
+ "grad[f] returns a function that differentiates scalar-returning f "
18
+ "with respect to its first argument."
19
+ ),
20
+ "log": "log[x] applies the elementwise natural logarithm.",
21
+ "sigmoid": "sigmoid[x] applies the elementwise logistic sigmoid.",
22
+ "tanh": "tanh[x] applies the elementwise hyperbolic tangent.",
23
+ "valuegrad": (
24
+ "valuegrad[f] returns a function that yields both the scalar result "
25
+ "of f and its gradient."
26
+ ),
27
+ }
28
+ COMPLETION_WORDS = tuple(SYMBOL_DOCS)
29
+
30
+
31
+ class KiwiKernel(Kernel):
32
+ implementation = "kiwi-array-jupyter"
33
+ implementation_version = __version__
34
+ language = "kiwi"
35
+ language_version = "0.2"
36
+ banner = "Kiwi 0.2 kernel"
37
+ language_info = {
38
+ "name": "kiwi",
39
+ "mimetype": "text/x-kiwi",
40
+ "file_extension": ".k",
41
+ "pygments_lexer": "text",
42
+ }
43
+
44
+ def __init__(self, **kwargs: Any) -> None:
45
+ super().__init__(**kwargs)
46
+ device = os.environ.get(DEVICE_ENV_VAR, "auto")
47
+ library_path = os.environ.get(LIB_ENV_VAR)
48
+ self._session = KiwiSession(device=device, library_path=library_path)
49
+
50
+ def do_execute(
51
+ self,
52
+ code: str,
53
+ silent: bool,
54
+ store_history: bool = True,
55
+ user_expressions: Any = None,
56
+ allow_stdin: bool = False,
57
+ ) -> dict[str, Any]:
58
+ del store_history, allow_stdin
59
+ try:
60
+ outputs = execute_cell(self._session, code)
61
+ except CellExecutionError as exc:
62
+ if not silent:
63
+ self.send_response(
64
+ self.iopub_socket,
65
+ "error",
66
+ {
67
+ "ename": "KiwiError",
68
+ "evalue": str(exc),
69
+ "traceback": [],
70
+ },
71
+ )
72
+ return {
73
+ "status": "error",
74
+ "ename": "KiwiError",
75
+ "evalue": str(exc),
76
+ "traceback": [],
77
+ }
78
+
79
+ if not silent:
80
+ for output in outputs[:-1]:
81
+ self.send_response(
82
+ self.iopub_socket,
83
+ "stream",
84
+ {
85
+ "name": "stdout",
86
+ "text": output.text + "\n",
87
+ },
88
+ )
89
+
90
+ if outputs:
91
+ last = outputs[-1]
92
+ self.send_response(
93
+ self.iopub_socket,
94
+ "execute_result",
95
+ {
96
+ "execution_count": self.execution_count,
97
+ "data": _display_data_for_output(last),
98
+ "metadata": {"kiwi_autograd_path": last.autograd_path},
99
+ },
100
+ )
101
+
102
+ return {
103
+ "status": "ok",
104
+ "execution_count": self.execution_count,
105
+ "payload": [],
106
+ "user_expressions": {},
107
+ }
108
+
109
+ def do_complete(self, code: str, cursor_pos: int) -> dict[str, Any]:
110
+ prefix = _completion_prefix(code[:cursor_pos])
111
+ matches = [word for word in COMPLETION_WORDS if prefix and word.startswith(prefix)]
112
+ return {
113
+ "matches": matches,
114
+ "cursor_start": cursor_pos - len(prefix),
115
+ "cursor_end": cursor_pos,
116
+ "metadata": {},
117
+ "status": "ok",
118
+ }
119
+
120
+ def do_inspect(
121
+ self,
122
+ code: str,
123
+ cursor_pos: int,
124
+ detail_level: int = 0,
125
+ omit_sections: Any = (),
126
+ ) -> dict[str, Any]:
127
+ del detail_level, omit_sections
128
+ symbol = _symbol_at_cursor(code, cursor_pos)
129
+ if symbol is None:
130
+ return {
131
+ "status": "ok",
132
+ "found": False,
133
+ "data": {},
134
+ "metadata": {},
135
+ }
136
+ doc = SYMBOL_DOCS.get(symbol)
137
+ if doc is None:
138
+ return {
139
+ "status": "ok",
140
+ "found": False,
141
+ "data": {},
142
+ "metadata": {},
143
+ }
144
+ return {
145
+ "status": "ok",
146
+ "found": True,
147
+ "data": {"text/plain": doc},
148
+ "metadata": {},
149
+ }
150
+
151
+ def do_is_complete(self, code: str) -> dict[str, Any]:
152
+ status = simple_is_complete(code)
153
+ reply = {"status": status}
154
+ if status == "incomplete":
155
+ reply["indent"] = " "
156
+ return reply
157
+
158
+ def do_shutdown(self, restart: bool) -> dict[str, Any]:
159
+ self._session.close()
160
+ return {"status": "ok", "restart": restart}
161
+
162
+
163
+ def _completion_prefix(code: str) -> str:
164
+ match = re.search(r"[A-Za-z_][A-Za-z0-9_]*$", code)
165
+ if match is None:
166
+ return ""
167
+ return match.group(0)
168
+
169
+
170
+ def _symbol_at_cursor(code: str, cursor_pos: int) -> str | None:
171
+ for match in re.finditer(r"[A-Za-z_][A-Za-z0-9_]*", code):
172
+ if match.start() <= cursor_pos <= match.end():
173
+ return match.group(0)
174
+ return None
175
+
176
+
177
+ def _display_data_for_output(output: CellOutput) -> dict[str, Any]:
178
+ data: dict[str, Any] = {"text/plain": output.text}
179
+ if output.display_mime is None or output.display_data is None:
180
+ return data
181
+ try:
182
+ data[output.display_mime] = json.loads(output.display_data)
183
+ except json.JSONDecodeError:
184
+ pass
185
+ return data