I’ve been using anaconda environments and jupyter notebook a lot recently. One thing I love about conda envs are that once created it can be used anywhere. Python’s venv requires you to set up the virtual env in a directory. My anaconda setup get too messy at times with too many envs. Sometimes I forget which env I used to do the work and ends up creating a new one. Also the conda commands seems pretty hard to remember (I’m getting used to it now). So the best possible solution that I could come up with was to write a script.
The script let’s me start the jupyter notebook from any directory on an existing environment or a new one. It lists all the existing environments on the screen and I can choose from it. This script has been a sigh of relief that I could create conda envs and start notebooks easily.
Working on the script
I started with a python script rather than a shell script so that I could use python libraries to make the work easier. But I ran into many issues along the way, so I had to use both python and shell scripts to make it work.
Dependencies
The only important dependency here is a python library called PyInquirer. It lets us build an interactive CLI with different types of prompts. You should also install notebook
as a global package, if you don’t want to do that, installing it in the current env would also work.
-
Create a python virtual env in a directory
python3 -m venv env source env/bin/activate
-
Install dependencies
pip install PyInquirer pip install notebook
How it works?
It first asks us if we want to create a new env. If yes, then we should enter the env name and dependencies. After that we can choose an env from conda env list. It then starts the selected environement. Also, it asks us if we want to open the notebook.
Coding it
-
Create a python file
cli.py
and import dependencies# cli.py from PyInquirer import prompt import subprocess import re, os, sys # current working directory CURR_WD = os.getcwd()
-
Add prompts for creating a new env
# cli.py # prompts user whether to create new env # returns a dictionary of answers answers = prompt([ { 'type': 'confirm', 'name': 'new', 'message': 'Create a new environment?', 'default': False } ]) new_env = answers['new']
It prompts a yes or no question and stores the our reponse in a
new
variable. -
Create virtual env if
new
is True:# cli.py if new_env is True: name = prompt([ { 'type': 'input', 'name': 'name', 'message': 'Name of the environment:' } ])['name'] packages = prompt([ { 'type': 'input', 'name': 'packages', 'message': 'Enter the packages to be installed:' } ])['packages'] add_notebook = prompt([ { 'type': 'confirm', 'name': 'add_notebook', 'message': 'Want to install jupyter notebook?', 'default': False } ])['add_notebook'] add_notebook = 1 if add_notebook else 0 # runs `create.sh` file with args subprocess.run('{}/create.sh {} {} {}'.format(CURR_WD, name, add_notebook, packages), shell=True)
It asks users for info like name of env, packages to be installed and whether jupyter notebook to be installed. The
subprocess.run
function is used to run a shell command from python. Here it runs another shell script which is created in same directory ascli.py
. Thecreate.sh
script is used to create a virtual env. Thename
,add_notebook
andpackages
variables become the arguments to the script. It uses these arguments to create a conda env.Note: You might think why not just run the
conda create
command using subprocess, but I felt it would be easier to run the bash commands on a shell script. -
The
create.sh
file# create.sh #!/bin/bash . ~/anaconda3/etc/profile.d/conda.sh # array of arguments args=($@) # dependencies dps='' # loop and create a string of dependencies for ((i=2; i<$#; i++)) do dps+="${args[$i]} " done # create a conda env conda create -y -n $1 $dps; # activate the env conda activate $1 # install notebook if `add_notebook` is 1 if [ $2 -eq "1" ] then pip install notebook fi # deactivate the env conda deactivate
It creates a string of the dependencies and run the
conda create
command. It then installjupyter notebook
ifadd_notebook
argument val was 1. -
Prompt for selecting conda env
# cli.py # runs `conda info --envs` and pipe the response to `screen_output` screen_output = subprocess.run('conda info --envs', stdout=subprocess.PIPE, text=True, shell=True).stdout # uses regex to find the env names from `screen_output` envs_dirty = re.findall(r'\n(\w*)', screen_output) # removes empty values envs_clean = [env for env in envs_dirty if env] # Prompts the user to select an env answers = prompt([ { 'type': 'list', 'name': 'env', 'message': 'Choose an environment:', 'choices': envs_clean } ]) # env to be activated env = answers['env']
-
Prompt the user to choose whether to open a notebook in the current env
# cli.py answers = prompt([ { 'type': 'confirm', 'name': 'open_notebook', 'message': 'Wanna open jupyter notebook?', 'default': False } ]) open_notebook = answers['open_notebook'] open_notebook = 1 if open_notebook else 0
-
Creating a list of all active notebooks
# cli.py screen_output = subprocess.run('{}/venv/bin/jupyter notebook list'.format(CURR_WD), stdout=subprocess.PIPE, text=True, shell=True).stdout # creates a list of port number of active notebooks using regex prev_hosts = re.findall(r'localhost:(\d*)', screen_output)
-
Starting the environment and opening notebook
# cli.py try: subprocess.run('sh {}/start.sh {} {}'.format(CURR_WD, env, open_notebook), shell=True) except KeyboardInterrupt: screen_output = subprocess.run('{}/venv/bin/jupyter notebook list'.format(CURR_WD), stdout=subprocess.PIPE, text=True, shell=True).stdout # create a list of all the running notebooks curr_hosts = re.findall(r'localhost:(\d*)', screen_output) # difference gives the list of notebooks started by start.sh hosts = list(set(curr_hosts) - set(prev_hosts)) # stop the notebooks for host in hosts: res = subprocess.run('{}/venv/bin/jupyter notebook stop {}'.format(CURR_WD, host), stdout=subprocess.PIPE, text=True, shell=True) # exit the program sys.exit()
It calls the
start.sh
script which creates a virtual env and starts the notebook. The best way to close the notebook is to hitctrl+c
. Here the catch block captures theKeyboardInterrupt
error and closes the notebook. -
The
start.sh
file# start.sh #!/bin/sh # run conda.sh # . is an alternative to `source` . ~/anaconda3/etc/profile.d/conda.sh # activate conda env conda activate $1 # start notebook if [ $2 -eq "1" ] then jupyter notebook fi
Last steps
Our script is now complete. The final steps are to make the make the script runnable from any directory. To do that we’ll give executable permissions to all the scripts and add it to .bashrc
chmod +x ./cli.py
chmod +x ./create.sh
chmod +x ./start.sh
Finally we can give an alias to the cli.py
. To do that, open .bashrc vim ~/.bashrc
and add an alias to the file
# .bashrc
alias open-notebook='<path_to_the_file>/cli.py'
Fire up a new terminal and we can run the script from anywhere using open-notebook
command.
Final ‘cli.py’ file
from PyInquirer import prompt
import subprocess
import re, os, sys
# current working directory
CURR_WD = os.getcwd()
# prompts user whether to create new env
# returns a dictionary of answers
answers = prompt([
{
'type': 'confirm',
'name': 'new',
'message': 'Create a new environment?',
'default': False
}
])
new_env = answers['new']
if new_env is True:
name = prompt([
{
'type': 'input',
'name': 'name',
'message': 'Name of the environment:'
}
])['name']
packages = prompt([
{
'type': 'input',
'name': 'packages',
'message': 'Enter the packages to be installed:'
}
])['packages']
add_notebook = prompt([
{
'type': 'confirm',
'name': 'add_notebook',
'message': 'Want to install jupyter notebook?',
'default': False
}
])['add_notebook']
add_notebook = 1 if add_notebook else 0
# runs `create.sh` file with args
subprocess.run('{}/create.sh {} {} {}'.format(CURR_WD, name, add_notebook, packages), shell=True)
# runs `conda info --envs` and pipe the response to `screen_output`
screen_output = subprocess.run('conda info --envs', stdout=subprocess.PIPE, text=True, shell=True).stdout
# uses regex to find the env names from `screen_output`
envs_dirty = re.findall(r'\n(\w*)', screen_output)
# removes empty values
envs_clean = [env for env in envs_dirty if env]
# Prompts the user to select an env
answers = prompt([
{
'type': 'list',
'name': 'env',
'message': 'Choose an environment:',
'choices': envs_clean
}
])
# env to be activated
env = answers['env']
answers = prompt([
{
'type': 'confirm',
'name': 'open_notebook',
'message': 'Wanna open jupyter notebook?',
'default': False
}
])
open_notebook = answers['open_notebook']
open_notebook = 1 if open_notebook else 0
screen_output = subprocess.run('{}/venv/bin/jupyter notebook list'.format(CURR_WD), stdout=subprocess.PIPE, text=True, shell=True).stdout
# creates a list of port number of active notebooks using regex
prev_hosts = re.findall(r'localhost:(\d*)', screen_output)
try:
subprocess.run('sh {}/start.sh {} {}'.format(CURR_WD, env, open_notebook), shell=True)
except KeyboardInterrupt:
screen_output = subprocess.run('{}/venv/bin/jupyter notebook list'.format(CURR_WD), stdout=subprocess.PIPE, text=True, shell=True).stdout
# create a list of all the running notebooks
curr_hosts = re.findall(r'localhost:(\d*)', screen_output)
# difference gives the list of notebooks started by start.sh
hosts = list(set(curr_hosts) - set(prev_hosts))
# stop the notebooks
for host in hosts:
res = subprocess.run('{}/venv/bin/jupyter notebook stop {}'.format(CURR_WD, host), stdout=subprocess.PIPE, text=True, shell=True)
# exit the program
sys.exit()