Skip to content

Commit 70d6dc1

Browse files
rashedmytprabhakk-mw
authored andcommitted
Introduces the entrypoint "install-matlab-kernelspec" to update the jupyter kernelspec with the python executable from the current python environment.
1 parent 4833fa2 commit 70d6dc1

File tree

11 files changed

+556
-43
lines changed

11 files changed

+556
-43
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ src/jupyter_matlab_labextension/src/lezer-matlab/src/parser.js
2222
src/jupyter_matlab_labextension/src/lezer-matlab/src/parser.terms.js
2323
integ-tests.log
2424
tests/integration/integ_logs.log
25+
src/jupyter_matlab_kernel/kernelspec/kernel.json

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ Run MATLAB® code in Jupyter® environments such as Jupyter notebooks, JupyterLa
1515
- [Install](#install)
1616
- [Install from PyPI](#install-from-pypi)
1717
- [Build from Source](#build-from-source)
18-
- [Using JupyterHub](#using-jupyterhub)
1918
- [Using JupyterHub and The Littlest JupyterHub](#using-jupyterhub-and-the-littlest-jupyterhub)
2019
- [Using Simulink](#using-simulink)
2120
- [Troubleshooting](#troubleshooting)
@@ -77,7 +76,10 @@ Install this Python package from the Python Package Index (PyPI) or build it fro
7776

7877
```bash
7978
python -m pip install jupyter-matlab-proxy
79+
install-matlab-kernelspec
8080
```
81+
For more information on `install-matlab-kernelspec`, see [Jupyter Kernelspec Installation Utility](https://github.com/mathworks/jupyter-matlab-proxy/blob/main/troubleshooting/troubleshooting.md#jupyter-kernelspec-installation-utility).
82+
8183
Installing this package will not install MATLAB. To execute MATLAB code in Jupyter, you must have [MATLAB installed](https://www.mathworks.com/help/install/install-products.html) separately.
8284

8385
### Build from Source
@@ -87,9 +89,9 @@ Alternatively, you can install this package by building it from the source. This
8789
git clone https://github.com/mathworks/jupyter-matlab-proxy.git
8890
cd jupyter-matlab-proxy
8991
python -m pip install .
92+
install-matlab-kernelspec
9093
```
9194

92-
### Using JupyterHub
9395
### Using JupyterHub and The Littlest JupyterHub
9496

9597
To use MATLAB with JupyterHub, install the `jupyter-matlab-proxy` Python package in the Jupyter environment launched by your JupyterHub platform. For example, if your JupyterHub platform launches Docker containers, install this package in the Docker image used to launch those containers, using the instructions for [Using MATLAB Integration _for Jupyter_ in a Docker Container](https://github.com/mathworks-ref-arch/matlab-integration-for-jupyter).
@@ -225,7 +227,7 @@ This opens an untitled `.m` file where you can write MATLAB code with syntax hig
225227

226228
----
227229

228-
Copyright 2021-2024 The MathWorks, Inc.
230+
Copyright 2021-2025 The MathWorks, Inc.
229231

230232
----
231233

hatch_build.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2025 The MathWorks, Inc.
2+
3+
"""A custom hatch build hook for jupyter_matlab_kernel."""
4+
5+
import json
6+
import sys
7+
from pathlib import Path
8+
9+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
10+
11+
12+
class KernelSpecBuilderHook(BuildHookInterface):
13+
"""A custom build hook for jupyter_matlab_kernel to generate the kernel.json file."""
14+
15+
def initialize(self, version, build_data):
16+
"""Initialize the hook."""
17+
parent_dir = Path(__file__).parent.resolve()
18+
19+
# Add the src directory to the system path to import the get_kernel_spec function
20+
sys.path.insert(0, str(parent_dir / "src"))
21+
from jupyter_matlab_kernel.kernelspec import get_kernel_spec
22+
23+
kernelspec_json = get_kernel_spec(executable="python3")
24+
25+
dest = (
26+
parent_dir / "src" / "jupyter_matlab_kernel" / "kernelspec" / "kernel.json"
27+
)
28+
if dest.exists():
29+
dest.unlink()
30+
31+
with open(dest, "w") as f:
32+
json.dump(kernelspec_json, f, indent=4)

pyproject.toml

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2023-2024 The MathWorks, Inc.
1+
# Copyright 2023-2025 The MathWorks, Inc.
22

33
[build-system]
44
requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version>=0.3.2"]
@@ -38,18 +38,14 @@ classifiers = [
3838
"Programming Language :: Python :: 3.11",
3939
]
4040

41-
# Note: Simpervisor v1.0.0 is required to support jupyter-matlab-proxy on Windows.
42-
# While fresh installs of juptyer-server-proxy will download v1.0.0 of simpervisor,
43-
# Existing installations where jupyter-matlab-proxy is being deployed would not get this update.
44-
# It is safe to update simpervisor to v1.0.0 while keeping jupyter-server-proxy at its existing level.
4541
dependencies = [
4642
"aiohttp",
47-
"jupyter-server-proxy",
48-
"simpervisor>=1.0.0",
43+
"ipykernel",
44+
"jupyter-client",
45+
"jupyter-server-proxy>=4.1.0",
4946
"matlab-proxy>=0.16.0",
5047
"psutil",
5148
"requests",
52-
"ipykernel"
5349
]
5450

5551
[project.urls]
@@ -67,6 +63,11 @@ dev = [
6763
"pytest-playwright",
6864
]
6965

66+
[project.scripts]
67+
# Note: If the following name of the script is updated, it should also be updated
68+
# in main function of kernelspec.py
69+
install-matlab-kernelspec = "jupyter_matlab_kernel.kernelspec:main"
70+
7071
[tool.hatch.envs.hatch-test]
7172
features = [
7273
"dev",
@@ -93,12 +94,13 @@ artifacts = [
9394
]
9495
exclude = [".github"]
9596

97+
# Used to call hatch_build.py
98+
[tool.hatch.build.hooks.custom]
99+
96100
[tool.hatch.build.targets.wheel.shared-data]
97101
"src/jupyter_matlab_labextension/jupyter_matlab_labextension/labextension" = "share/jupyter/labextensions/jupyter_matlab_labextension"
98102
"src/jupyter_matlab_labextension/install.json" = "share/jupyter/labextensions/jupyter_matlab_labextension/install.json"
99-
"src/jupyter_matlab_kernel/kernel.json" = "share/jupyter/kernels/jupyter_matlab_kernel/kernel.json"
100-
"img/logo-64x64.png" = "share/jupyter/kernels/jupyter_matlab_kernel/logo-64x64.png"
101-
"img/logo-svg.svg" = "share/jupyter/kernels/jupyter_matlab_kernel/logo-svg.svg"
103+
"src/jupyter_matlab_kernel/kernelspec" = "share/jupyter/kernels/jupyter_matlab_kernel"
102104

103105
[tool.hatch.build.hooks.jupyter-builder]
104106
dependencies = ["hatch-jupyter-builder>=0.8.1"]

src/jupyter_matlab_kernel/kernel.json

-18
This file was deleted.
+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Copyright 2025 The MathWorks, Inc.
2+
3+
import argparse
4+
import errno
5+
import json
6+
import pathlib
7+
import shutil
8+
import sys
9+
import tempfile
10+
from datetime import datetime
11+
12+
from jupyter_client import kernelspec
13+
14+
STANDARD_PYTHON_EXECUTABLE = "python3"
15+
16+
17+
def get_kernel_spec(executable=None) -> dict:
18+
"""
19+
Generate and return the kernelspec JSON for the MATLAB Kernel.
20+
21+
This function creates a dictionary containing the necessary configuration
22+
for the MATLAB Kernel to be used with Jupyter. The kernelspec includes
23+
information such as the command to start the kernel, display name,
24+
language, and other metadata.
25+
26+
For more information about kernelspecs, see:
27+
https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs
28+
29+
Args:
30+
executable (str, optional): The Python executable to use. Defaults to python3.
31+
32+
Returns:
33+
dict: A dictionary containing the kernelspec configuration for the MATLAB Kernel.
34+
"""
35+
if not executable:
36+
executable = STANDARD_PYTHON_EXECUTABLE
37+
38+
copyright_start_year = 2023
39+
copyright_end_year = datetime.now().year
40+
41+
kernelspec_json = {
42+
"argv": [
43+
executable,
44+
"-m",
45+
"jupyter_matlab_kernel",
46+
"-f",
47+
"{connection_file}",
48+
],
49+
"display_name": "MATLAB Kernel",
50+
"language": "matlab",
51+
"interrupt_mode": "message",
52+
"env": {},
53+
"metadata": {
54+
"debugger": False,
55+
"copyright": f"Copyright {copyright_start_year}-{copyright_end_year} The MathWorks, Inc.",
56+
"description": "Jupyter kernelspec for MATLAB Kernel. For more information, please look at https://jupyter-client.readthedocs.io/en/stable/kernels.html#kernel-specs",
57+
},
58+
}
59+
return kernelspec_json
60+
61+
62+
def install_kernel_spec(
63+
kernel_name: str, executable: str, kernelspec_dir, register_with_jupyter=True
64+
):
65+
"""
66+
Install the MATLAB Kernel kernelspec to the specified directory.
67+
68+
Args:
69+
kernel_name (str): The name of the kernel to be installed.
70+
executable (str): The Python executable path to use for the kernelspec.
71+
kernelspec_dir (Path | str): The directory to install the kernelspec.
72+
register_with_jupyter (bool): Whether to register the kernelspec with Jupyter. Defaults to True. If True,
73+
the kernelspec will be installed to <prefix>/share/jupyter/kernels/<kernel_name>.
74+
75+
Returns:
76+
tuple: A tuple containing the destination path of the installed kernelspec and the kernelspec dictionary.
77+
"""
78+
kernelspec_dict = get_kernel_spec(executable)
79+
kernel_json_path = pathlib.Path(kernelspec_dir) / "kernel.json"
80+
81+
# Copy kernelspec resources to the target directory
82+
shutil.copytree(pathlib.Path(__file__).parent / "kernelspec", kernelspec_dir)
83+
84+
# Write the kernelspec to the kernel.json file
85+
with kernel_json_path.open("w") as f:
86+
json.dump(kernelspec_dict, f, indent=4)
87+
88+
if register_with_jupyter:
89+
dest = kernelspec.install_kernel_spec(
90+
str(kernelspec_dir), kernel_name, prefix=sys.prefix
91+
)
92+
else:
93+
dest = str(kernelspec_dir)
94+
return dest, kernelspec_dict
95+
96+
97+
def main():
98+
"""
99+
Main function for installing or resetting the Jupyter kernelspec for MATLAB Kernel.
100+
101+
This function parses command-line arguments to determine whether to install
102+
or reset the kernelspec. It then calls the appropriate functions to perform
103+
the requested action and prints the result.
104+
105+
The function supports the following options:
106+
--reset: Reset the kernelspec to use the standard Python executable.
107+
108+
If no option is provided, it installs the kernelspec using the current Python executable.
109+
110+
Raises:
111+
OSError: If there's a permission error during installation.
112+
113+
"""
114+
parser = argparse.ArgumentParser(
115+
prog="install-matlab-kernelspec",
116+
description="Install or Reset Jupyter kernelspec for MATLAB Kernel",
117+
)
118+
parser.add_argument(
119+
"--reset",
120+
action="store_true",
121+
help="Reset the kernelspec to the default configuration",
122+
)
123+
parser.add_argument(
124+
"--preview",
125+
action="store_true",
126+
help="Preview the changes made to kernelspec",
127+
)
128+
args = parser.parse_args()
129+
130+
kernel_name = str(pathlib.Path(__file__).parent.name)
131+
if args.reset:
132+
executable = STANDARD_PYTHON_EXECUTABLE
133+
else:
134+
executable = sys.executable
135+
136+
register_kernelspec_with_jupyter = True
137+
if args.preview:
138+
register_kernelspec_with_jupyter = False
139+
140+
try:
141+
kernelspec_dir = pathlib.Path(tempfile.mkdtemp()) / kernel_name
142+
dest, kernelspec_dict = install_kernel_spec(
143+
kernel_name=kernel_name,
144+
executable=executable,
145+
kernelspec_dir=kernelspec_dir,
146+
register_with_jupyter=register_kernelspec_with_jupyter,
147+
)
148+
except OSError as e:
149+
if e.errno == errno.EACCES:
150+
print(e, file=sys.stderr)
151+
sys.exit(1)
152+
raise
153+
154+
if args.preview:
155+
# Replace the destination path to be displayed to the user with the path where the kernelspec would be installed without the dry run mode.
156+
dest = str(
157+
pathlib.Path(sys.prefix) / "share" / "jupyter" / "kernels" / kernel_name
158+
)
159+
print(
160+
f"The following kernelspec for {kernel_name} would be installed in {dest}\n{json.dumps(kernelspec_dict, indent=4)}"
161+
)
162+
sys.exit(0)
163+
164+
print(
165+
f"Installed the following kernelspec for {kernel_name} in {dest}\n{json.dumps(kernelspec_dict, indent=4)}"
166+
)
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
# Copyright 2023 The MathWorks, Inc.
1+
# Copyright 2023-2025 The MathWorks, Inc.
2+
import os
3+
import shutil
24
import sys
3-
from jupyter_client import kernelspec
5+
46
import pytest
5-
import os, shutil
7+
from jupyter_client.kernelspec import find_kernel_specs
8+
from jupyter_matlab_kernel.kernelspec import install_kernel_spec
69

710
TEST_KERNEL_NAME = "jupyter_matlab_kernel_test"
811

912

1013
@pytest.fixture
1114
def UninstallKernel():
1215
yield
13-
kernel_list = kernelspec.find_kernel_specs()
16+
kernel_list = find_kernel_specs()
1417
kernel_path = kernel_list.get(TEST_KERNEL_NAME)
15-
if kernel_path != None and os.path.exists(kernel_path):
18+
if kernel_path is not None and os.path.exists(kernel_path):
1619
shutil.rmtree(kernel_path)
1720

1821

19-
def test_matlab_kernel_registration(UninstallKernel):
22+
def test_matlab_kernel_registration(tmp_path, UninstallKernel):
2023
"""This test checks that the kernel.json file can be installed by JupyterLab."""
2124

22-
kernelspec.install_kernel_spec(
23-
"./src/jupyter_matlab_kernel",
25+
install_kernel_spec(
2426
kernel_name=TEST_KERNEL_NAME,
25-
prefix=sys.prefix,
27+
executable=sys.executable,
28+
kernelspec_dir=(tmp_path / TEST_KERNEL_NAME),
2629
)
2730

28-
kernel_list = kernelspec.find_kernel_specs()
31+
kernel_list = find_kernel_specs()
2932

3033
assert TEST_KERNEL_NAME in kernel_list

0 commit comments

Comments
 (0)