{ "cells": [ { "attachments": { "67258d94-84e6-4a0c-ae8f-c74332ec082e.jpg": { "image/jpeg": "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" } }, "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "\n", "Author: Jens Henrik Göbbert\n", "------------------------------------" ] }, { "cell_type": "markdown", "metadata": { "toc-hr-collapsed": false }, "source": [ "# Create your own Jupyter Kernel\n", "-------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building your own Jupyter kernel is a three step process\n", "1. Create/Pimp new virtual Python environment\n", " * venv\n", "2. Create/Edit launch script for the Jupyter kernel\n", " * kernel.sh\n", "3. Create/Edit Jupyter kernel configuration\n", " * kernel.json" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Settings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Set kernel name\n", " - must be lower case\n", " - change if you like" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INPUT NEEDED:\n", "KERNEL_NAME=${USER}_kernel\n", "\n", "export KERNEL_NAME=$(echo \"${KERNEL_NAME}\" | awk '{print tolower($0)}')\n", "echo ${KERNEL_NAME} # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* List directories where JupyterLab will search for kernels" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# JUPYTER SEARCH PATH (for kernels-directory)\n", "echo \"jupyter search paths for kernels-directories\"\n", "if [ -z $JUPYTER_PATH ]; then\n", " echo \"$HOME/.local/share/jupyter\"\n", "else\n", " tr ':' '\\n' <<< \"$JUPYTER_PATH\"\n", "fi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Set kernel type\n", " - private kernel = \"\\${HOME}/.local/\" \n", " - project kernel = \"\\${PROJECT}/.local/\" \n", " - other kernel = \"\\<your-path\\>\" (ensure it is part of $JUPYTER_PATH or your kernel will not be found by JuypterLab)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INPUT NEEDED:\n", "export KERNEL_TYPE=private # private, project or other\n", "export KERNEL_SPECS_PREFIX=/p/home/jusers/$USER/jureca/.local\n", "\n", "###################\n", "# project kernel\n", "if [ \"${KERNEL_TYPE}\" == \"project\" ]; then\n", " export KERNEL_SPECS_PREFIX=${PROJECT}/.local\n", " echo \"project kernel\"\n", "# private kernel\n", "elif [ \"${KERNEL_TYPE}\" == \"private\" ]; then\n", " export KERNEL_SPECS_PREFIX=${HOME}/.local\n", " echo \"private kernel\"\n", "else\n", " if [ ! -d \"$KERNEL_SPECS_PREFIX\" ]; then\n", " echo \"ERROR: please create directory $KERNEL_SPECS_PREFIX\"\n", " fi\n", " echo \"other kernel\"\n", "fi\n", "export KERNEL_SPECS_DIR=${KERNEL_SPECS_PREFIX}/share/jupyter/kernels\n", "\n", "# check if kernel name is unique\n", "if [ -d \"${KERNEL_SPECS_DIR}/${KERNEL_NAME}\" ]; then\n", " echo \"ERROR: Kernel already exists in ${KERNEL_SPECS_DIR}/${KERNEL_NAME}\"\n", " echo \" Rename kernel name or remove directory.\"\n", "fi\n", "\n", "echo ${KERNEL_SPECS_DIR}/${KERNEL_NAME} # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Set directory for kernels virtual environment\n", " - change if you like" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# INPUT NEEDED:\n", "export KERNEL_VENVS_DIR=${PROJECT}/${USER}/jupyter/kernels\n", "\n", "###################\n", "mkdir -p ${KERNEL_VENVS_DIR}\n", "if [ \"${KERNEL_TYPE}\" != \"private\" ] && [ \"${KERNEL_TYPE}\" != \"other\" ]; then\n", " echo \"Please check the permissions and ensure your project partners have read/execute permissions:\"\n", " namei -l ${KERNEL_VENVS_DIR}\n", "fi\n", "\n", "echo ${KERNEL_VENVS_DIR} # double check\n", "ls -lt ${KERNEL_VENVS_DIR}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Create/Pimp new virtual Python environment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 1.1 - Load required modules" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "module -q purge\n", "module -q use $OTHERSTAGES \n", "module -q load Stages/Devel-2019a 2> /dev/null # any stage can be used\n", "module -q load GCCcore/.8.3.0 2> /dev/null\n", "module -q load Python/3.6.8 # only Python is required\n", "module list # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 1.2 - Load extra modules you need for your kernel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# module load <module you need>" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 1.3 - Create and activate a virtual environment for the kernel \n", "and ensure python packages installed in the virtual environment are always prefered" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if [ -d \"${KERNEL_VENVS_DIR}/${KERNEL_NAME}\" ]; then\n", " echo \"ERROR: Directory for virtual environment already ${KERNEL_VENVS_DIR}/${KERNEL_NAME}\"\n", " echo \" Rename kernel name or remove directory.\"\n", "else\n", " python -m venv --system-site-packages ${KERNEL_VENVS_DIR}/${KERNEL_NAME}\n", " source ${KERNEL_VENVS_DIR}/${KERNEL_NAME}/bin/activate\n", " export PYTHONPATH=${VIRTUAL_ENV}/lib/python3.6/site-packages:${PYTHONPATH}\n", " echo ${VIRTUAL_ENV} # double check\n", "fi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 1.4 - Install Python libraries required for communication with Jupyter" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "which pip\n", "pip install --ignore-installed ipykernel\n", "ls ${VIRTUAL_ENV}/lib/python3.6/site-packages/ # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 1.5 - Install whatever else you need in your Python virtual environment (using pip)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#pip install <python-package you need>" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Create/Edit launch script for the Jupyter kernel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 2.1 - Create launch script, which loads your Python virtual environment and starts the ipykernel process inside:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "echo '#!/bin/bash'\"\n", "\n", "# Load required modules\n", "module purge\n", "module use \"'$OTHERSTAGES'\"\n", "module load Stages/Devel-2019a\n", "module load GCCcore/.8.3.0\n", "module load Python/3.6.8\n", "\n", "# Load extra modules you need for your kernel (as you did in step 1.2)\n", "#module load <module you need>\n", " \n", "# Activate your Python virtual environment\n", "source ${KERNEL_VENVS_DIR}/${KERNEL_NAME}/bin/activate\n", " \n", "# Ensure python packages installed in the virtual environment are always prefered\n", "export PYTHONPATH=${VIRTUAL_ENV}/lib/python3.6/site-packages:\"'${PYTHONPATH}'\"\n", " \n", "exec python -m ipykernel \"'$@' > ${VIRTUAL_ENV}/kernel.sh\n", "chmod +x ${VIRTUAL_ENV}/kernel.sh\n", "\n", "cat ${VIRTUAL_ENV}/kernel.sh # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Create/Edit Jupyter kernel configuration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 3.1 - Create Jupyter kernel configuration directory and files" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "python -m ipykernel install --name=${KERNEL_NAME} --prefix ${VIRTUAL_ENV}\n", "export VIRTUAL_ENV_KERNELS=${VIRTUAL_ENV}/share/jupyter/kernels" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 3.2 - Adjust kernel.json file" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mv ${VIRTUAL_ENV_KERNELS}/${KERNEL_NAME}/kernel.json ${VIRTUAL_ENV_KERNELS}/${KERNEL_NAME}/kernel.json.orig\n", "\n", "echo '{\n", " \"argv\": [\n", " \"'${KERNEL_VENVS_DIR}/${KERNEL_NAME}/kernel.sh'\",\n", " \"-m\",\n", " \"ipykernel_launcher\",\n", " \"-f\",\n", " \"{connection_file}\"\n", " ],\n", " \"display_name\": \"'${KERNEL_NAME}'\",\n", " \"language\": \"python\"\n", "}' > ${VIRTUAL_ENV_KERNELS}/${KERNEL_NAME}/kernel.json\n", "\n", "cat ${VIRTUAL_ENV_KERNELS}/${KERNEL_NAME}/kernel.json # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 3.3 - Create link to kernel specs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cd ${KERNEL_SPECS_DIR}\n", "ln -s ${VIRTUAL_ENV_KERNELS}/${KERNEL_NAME} .\n", "\n", "ls ${KERNEL_SPECS_DIR} # double check" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Cleanup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "deactivate" ] } ], "metadata": { "kernelspec": { "display_name": "Bash", "language": "bash", "name": "bash" }, "language_info": { "codemirror_mode": "shell", "file_extension": ".sh", "mimetype": "text/x-sh", "name": "bash" } }, "nbformat": 4, "nbformat_minor": 4 }