{ "cells": [ { "cell_type": "markdown", "id": "57c473fb", "metadata": {}, "source": [ "# Setting up a venv-sandboxed Python kernel\n", "\n", "This notebooks explains how to set up a new venv-sandboxed Python kernel and make it available in your JupyterLab environment, assuming that you are running this notebook in a standard linux-based environment and a regular (non-root) using running commands in a bash terminal. \n", "\n", "> This is meant to provide helpful hints to get you over the hump, not as a comprehensive tutorial. This may not work for your environment!\n", "\n", "> You might also want to consult the [official IPython kernel installation documentation](https://ipython.readthedocs.io/en/stable/install/kernel_install.html) for a more general explanation. " ] }, { "cell_type": "markdown", "id": "d1e7b9f7-81c1-414b-9c4f-4fcc2b0d6ecc", "metadata": {}, "source": [ "## Creating and installing a new venv-sandboxed Python kernel" ] }, { "cell_type": "markdown", "id": "59a0fcf5-55da-43af-b6ee-e4950fe51b6b", "metadata": { "tags": [] }, "source": [ "From a bash terminal, `cd` to your home directory, and create a new hidden directory in which you will put all your virtual environments.\n", "\n", "```\n", "user@host:~$ mkdir .virtualenvs\n", "```\n", "\n", "Then, create a new venv (the venv in this example is named \"foo\", because of course it is).\n", "\n", "```\n", "user@host:~$ python3 -m venv .virtualenvs/foo\n", "```\n", "\n", "Activate the new venv. Once activated, the venv name is displayed in parentheses at the front of your command prompt, as shown below.\n", "\n", "```\n", "user@host:~$ source .virtualenvs/foo/bin/activate\n", "(foo) user:host:~$\n", "```\n", "\n", "Use `pip` to install ipykernel inside your new venv.\n", "\n", "```\n", "(foo) user@host:~$ pip install ipykernel\n", "```\n", "\n", "Install your new ipykernel into your user kernel library, and give it a nice name that will make sense when you see the new kernel listed in the JupyterLab interface.\n", "\n", "```\n", "(foo) user@host:~$ python -m ipykernel install --user --name foo --display-name \"Python 3 (venv:foo)\"\n", "```\n", "\n", "Deactivate the venv in your terminal when you are done.\n", "\n", "```\n", "(foo) user@host:~$ deactivate\n", "```\n", "\n", "When you open a new Launcher tab in JupyterLab, you should see the new venv-sandboxed Python kernel listed next to the default system Python kernel.\n", "\n", "To display a list to confirm which jupyter kernels are currently installed in your environment, you can run the following command from a terminal.\n", "\n", "```\n", "user@host:~$ jupyter kernelspec list\n", "```\n", "\n", "To uninstall the \"foo\" kernel, run the following command.\n", "\n", "```\n", "user@host:~$ jupyter kernelspec uninstall foo\n", "```\n", "\n", "This only uninstalls the jupyter kernel. It does not remove the venv containing the kernelspec from your system. To delete a venv, just delete the directory it is contained in.\n", "\n", "```\n", "user@host:~$ rm -rf .virtualenvs/foo\n", "```\n", "\n", "## Installing Python packages\n", "\n", "There are multiple ways to install Python packages into your new venv, such that your venv-sandboxed Python kernel will pick these up when you run import commands in your code.\n", "\n", "The easiest way is to use built-in `%pip` IPython magic commands from a JupyterLab notebook (or console) that is running your venv-sandboxed kernelspec. \n", "\n", "For example, to install the `dev` branch of the `ws3` package in the new \"foo\" venv you could run this line of code from inside the kernel." ] }, { "cell_type": "code", "execution_count": 1, "id": "a4c0fa22-c1f6-4ccb-8e2a-733aa4bad057", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting git+https://github.com/UBC-FRESH/ws3@dev\n", " Cloning https://github.com/UBC-FRESH/ws3 (to revision dev) to /tmp/pip-req-build-l2gxo0fg\n", " Running command git clone --filter=blob:none --quiet https://github.com/UBC-FRESH/ws3 /tmp/pip-req-build-l2gxo0fg\n", " Resolved https://github.com/UBC-FRESH/ws3 to commit 5c23c01504c5a9fbe1e0d73b79468b90e5e2536e\n", " Installing build dependencies ... \u001b[?25ldone\n", "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", "\u001b[?25hRequirement already satisfied: dill in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (0.4.0)\n", "Requirement already satisfied: fiona in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.10.1)\n", "Requirement already satisfied: libcbm in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.8.1)\n", "Requirement already satisfied: matplotlib in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (3.10.5)\n", "Requirement already satisfied: numba in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (0.61.2)\n", "Requirement already satisfied: numpy>=1.21 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.2.6)\n", "Requirement already satisfied: pandas>=1.3 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.3.1)\n", "Requirement already satisfied: profilehooks in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.13.0)\n", "Requirement already satisfied: pulp in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (3.2.2)\n", "Requirement already satisfied: rasterio in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.4.3)\n", "Requirement already satisfied: scipy>=1.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.16.1)\n", "Requirement already satisfied: python-dateutil>=2.8.2 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2.9.0.post0)\n", "Requirement already satisfied: pytz>=2020.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2025.2)\n", "Requirement already satisfied: tzdata>=2022.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2025.2)\n", "Requirement already satisfied: six>=1.5 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=1.3->ws3==1.0.1.post1) (1.17.0)\n", "Requirement already satisfied: attrs>=19.2.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (25.3.0)\n", "Requirement already satisfied: certifi in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (2025.8.3)\n", "Requirement already satisfied: click~=8.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (8.2.1)\n", "Requirement already satisfied: click-plugins>=1.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (1.1.1.2)\n", "Requirement already satisfied: cligj>=0.5 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (0.7.2)\n", "Requirement already satisfied: numexpr>=2.8.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (2.11.0)\n", "Requirement already satisfied: pyyaml in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (6.0.2)\n", "Requirement already satisfied: mock in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (5.2.0)\n", "Requirement already satisfied: openpyxl in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (3.1.5)\n", "Requirement already satisfied: contourpy>=1.0.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (1.3.3)\n", "Requirement already satisfied: cycler>=0.10 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (0.12.1)\n", "Requirement already satisfied: fonttools>=4.22.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (4.59.0)\n", "Requirement already satisfied: kiwisolver>=1.3.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (1.4.8)\n", "Requirement already satisfied: packaging>=20.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (25.0)\n", "Requirement already satisfied: pillow>=8 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (11.3.0)\n", "Requirement already satisfied: pyparsing>=2.3.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (3.2.3)\n", "Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from numba->ws3==1.0.1.post1) (0.44.0)\n", "Requirement already satisfied: et-xmlfile in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from openpyxl->libcbm->ws3==1.0.1.post1) (2.0.0)\n", "Requirement already satisfied: affine in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from rasterio->ws3==1.0.1.post1) (2.4.0)\n", "Building wheels for collected packages: ws3\n", " Building wheel for ws3 (pyproject.toml) ... \u001b[?25ldone\n", "\u001b[?25h Created wheel for ws3: filename=ws3-1.0.1.post1-py3-none-any.whl size=68862 sha256=cb2ddcef266d270a5e8882073129b1bafdd07c9749038093605532fd7a95e81c\n", " Stored in directory: /tmp/pip-ephem-wheel-cache-5068uxut/wheels/bc/c0/27/54eff522cdcea94d53d5fd804ea8e244cc4c9d364c99149628\n", "Successfully built ws3\n", "Installing collected packages: ws3\n", " Attempting uninstall: ws3\n", " Found existing installation: ws3 1.1.0.dev0\n", " Uninstalling ws3-1.1.0.dev0:\n", " Successfully uninstalled ws3-1.1.0.dev0\n", "Successfully installed ws3-1.0.1.post1\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -U git+https://github.com/UBC-FRESH/ws3@dev" ] }, { "cell_type": "markdown", "id": "6a94052a-e9a2-4e41-ac88-0762e9c542ba", "metadata": { "tags": [] }, "source": [ "You can test to make sure that your kernel is now picking up the venv-sandboxed version of the package by importing the package and inspecting the value of the package `__path__` attribute." ] }, { "cell_type": "code", "execution_count": 3, "id": "875d9f38-f286-46bf-9ed0-c2bb5909b105", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "(['/home/gep/projects/ws3/.venv/lib/python3.12/site-packages/ws3'],\n", " '1.0.1-post1')" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import ws3\n", "ws3.__path__, ws3.__version__" ] }, { "cell_type": "markdown", "id": "b506d92e-73a5-4385-a736-22c9f11f6aec", "metadata": { "tags": [] }, "source": [ "You can also use the \"%pip\" magic command to uninstall packages from your venv. \n", "\n", "For example to uninstall the ws3 package we installed earlier in the \"foo\" venv, run the following command from inside the kernel. Note the \"-y\" flag, which skips asking for user confirmation before uninstalling stuff (else the kernel will hang forever waiting for your response, which you cannot provide from inside a Jupyter notebook or console environment)." ] }, { "cell_type": "code", "execution_count": 4, "id": "b3682eee-70cc-41a1-a525-bb50cdb92a75", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found existing installation: ws3 1.0.1.post1\n", "Uninstalling ws3-1.0.1.post1:\n", " Successfully uninstalled ws3-1.0.1.post1\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip uninstall -y ws3" ] }, { "cell_type": "markdown", "id": "5fa1bd99-49f5-42f2-bd3a-4ac65380f559", "metadata": { "tags": [] }, "source": [ "Note also that it is possible to install an \"editable\" version of a Python package inside your venv-sandboxed kernel by cloning a GitHub repo for the Python package you want to use into your project environment and running the \"%pip\" magic command with the \"-e\" flag and point to the local directory containing the cloned copy of the package code.\n", "\n", "For example, we can clone the `dev` branch of the `ws3` GitHub repo into our project directory like this." ] }, { "cell_type": "code", "execution_count": 6, "id": "604418fe-24b5-4729-8c74-8e9d274c9ea3", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fatal: destination path 'ws3' already exists and is not an empty directory.\n" ] } ], "source": [ "!git clone https://github.com/UBC-FRESH/ws3" ] }, { "cell_type": "markdown", "id": "795f86a9-fd01-4bce-86aa-fd2882a18fe4", "metadata": { "tags": [] }, "source": [ "Then we can configure our venv-sandboxed kernel environment to use that local copy of the code when Python imports `ws3` by running the following magic command (assuming this notebook is running the venv kernel)." ] }, { "cell_type": "code", "execution_count": 7, "id": "9d25fbe5-21d5-4fff-b412-27fbae9c07a4", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Obtaining file:///home/gep/projects/ws3/examples/ws3\n", " Installing build dependencies ... \u001b[?25ldone\n", "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", "\u001b[?25h Installing backend dependencies ... \u001b[?25ldone\n", "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", "\u001b[?25hRequirement already satisfied: dill in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (0.4.0)\n", "Requirement already satisfied: fiona in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.10.1)\n", "Requirement already satisfied: libcbm in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.8.1)\n", "Requirement already satisfied: matplotlib in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (3.10.5)\n", "Requirement already satisfied: numba in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (0.61.2)\n", "Requirement already satisfied: numpy>=1.21 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.2.6)\n", "Requirement already satisfied: pandas>=1.3 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (2.3.1)\n", "Requirement already satisfied: profilehooks in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.13.0)\n", "Requirement already satisfied: pulp in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (3.2.2)\n", "Requirement already satisfied: rasterio in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.4.3)\n", "Requirement already satisfied: scipy>=1.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from ws3==1.0.1.post1) (1.16.1)\n", "Requirement already satisfied: python-dateutil>=2.8.2 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2.9.0.post0)\n", "Requirement already satisfied: pytz>=2020.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2025.2)\n", "Requirement already satisfied: tzdata>=2022.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from pandas>=1.3->ws3==1.0.1.post1) (2025.2)\n", "Requirement already satisfied: six>=1.5 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=1.3->ws3==1.0.1.post1) (1.17.0)\n", "Requirement already satisfied: attrs>=19.2.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (25.3.0)\n", "Requirement already satisfied: certifi in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (2025.8.3)\n", "Requirement already satisfied: click~=8.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (8.2.1)\n", "Requirement already satisfied: click-plugins>=1.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (1.1.1.2)\n", "Requirement already satisfied: cligj>=0.5 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from fiona->ws3==1.0.1.post1) (0.7.2)\n", "Requirement already satisfied: numexpr>=2.8.7 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (2.11.0)\n", "Requirement already satisfied: pyyaml in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (6.0.2)\n", "Requirement already satisfied: mock in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (5.2.0)\n", "Requirement already satisfied: openpyxl in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from libcbm->ws3==1.0.1.post1) (3.1.5)\n", "Requirement already satisfied: contourpy>=1.0.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (1.3.3)\n", "Requirement already satisfied: cycler>=0.10 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (0.12.1)\n", "Requirement already satisfied: fonttools>=4.22.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (4.59.0)\n", "Requirement already satisfied: kiwisolver>=1.3.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (1.4.8)\n", "Requirement already satisfied: packaging>=20.0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (25.0)\n", "Requirement already satisfied: pillow>=8 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (11.3.0)\n", "Requirement already satisfied: pyparsing>=2.3.1 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from matplotlib->ws3==1.0.1.post1) (3.2.3)\n", "Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from numba->ws3==1.0.1.post1) (0.44.0)\n", "Requirement already satisfied: et-xmlfile in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from openpyxl->libcbm->ws3==1.0.1.post1) (2.0.0)\n", "Requirement already satisfied: affine in /home/gep/projects/ws3/.venv/lib/python3.12/site-packages (from rasterio->ws3==1.0.1.post1) (2.4.0)\n", "Building wheels for collected packages: ws3\n", " Building editable for ws3 (pyproject.toml) ... \u001b[?25ldone\n", "\u001b[?25h Created wheel for ws3: filename=ws3-1.0.1.post1-py3-none-any.whl size=4151 sha256=5364d53efb2ade8d3f483247aa0d497ba2c6c722362da96508d45f7bc5fd9f7a\n", " Stored in directory: /tmp/pip-ephem-wheel-cache-w4nyts_d/wheels/92/4f/40/0e1d2f2584d079a190b79c34d238033826099ccae6b5868838\n", "Successfully built ws3\n", "Installing collected packages: ws3\n", "Successfully installed ws3-1.0.1.post1\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -e ./ws3" ] }, { "cell_type": "markdown", "id": "5be95ad5-4866-478a-ac7e-f76a1bacd1db", "metadata": { "tags": [] }, "source": [ "We can check that this is working the way I intended by importing the ws3 package and inspecting its __path__ attribute." ] }, { "cell_type": "code", "execution_count": 8, "id": "0472b10d-12ae-40a3-acd9-f56b11467b34", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "(['/home/gep/projects/ws3/.venv/lib/python3.12/site-packages/ws3'],\n", " '1.0.1-post1')" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import ws3\n", "ws3.__path__, ws3.__version__" ] }, { "cell_type": "markdown", "id": "71539ec7-9d8a-4bc2-b6cc-8e8847b70319", "metadata": { "tags": [] }, "source": [ "If you want to get really fancy, you can set up your notebook to autoreload the local package anytime you modify the source code by adding these two lines of code to the top of your notebook." ] }, { "cell_type": "code", "execution_count": 9, "id": "10e5be3e-674e-4c94-99b9-b00d3b8eb9e9", "metadata": { "tags": [] }, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "markdown", "id": "c447c927-fdee-4b86-b237-de8ee9e8c7dd", "metadata": { "tags": [] }, "source": [ "This is the basic pattern if you are working on a project that uses `ws3` and anticipate needing to tweak the code (at least a little bit) to get it to do _exactly_ what you need it to do for your particular project." ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }