Reputation: 2673
How to ensure that distributing Python packages with big dependencies, like NumPy and SciPy, automatically get properly installed on users' computers, regardless of their OS?
The problem is that many such packages are installed with more or less ease through the main channel of pip
, including those mentioned above...
Upvotes: 4
Views: 578
Reputation: 415
The problem with legel's suggestion is that you still end up relying on user's environment to deploy your application.
In the past I have had similar install scripts which would do all the dirty work to deploy my application, but it still relies on network connectivity to download packages and dependencies. I have gotten past that by creating a docker container. I would build a container with my application and all the dependencies, create a new docker image and provide the docker image to customers. The only dependency then is to have docker on the system, and the way to run my app would be to use docker run.
There is another way to package all dependencies is to use http://www.pyinstaller.org/ which creates a standalone application packaging all dependent modules. Disclaimer though: I have not used this on any large applications yet, have just started testing it, and so far works for smaller programs.
Hope this helps!
Upvotes: 0
Reputation: 2673
I don't claim this is perfect, but below is what I've found in practice to be really strong: an install.sh
script, derived here originally for an artificial intelligence library called words2map. The goal was to make sure that users can simply run ./install.sh
and then all the needed pip install this
and pip install that
succeeds in every case (even for brand new servers), regardless of the operating system you're on. (I really wish OS-agnostic installations was industry standard, at least across Mac OSX and every Linux distro...)
Feel free to skip straight to the code, but for those who demand explanation: This install.sh
script relies heavily on the Conda package distribution manager, which works really nicely, given that it builds OS-specific wheels automatically. The installation code below begins by first checking the OS of the user, then installing the appropriate Conda distribution (if on Mac OSX and Linux, in this case), and then finally installing all of the remaining dependencies as needed through Conda. Also, note that if a Linux terminal is detected it first ensures the needed compilation libraries are available through e.g. sudo apt-get install python-dev
(since often users are missing gcc
or something), and finally this automatically adds Conda to the user's PATH for a bash
and zshell
, which seems to cover most terminals (but maybe not, feel free to add). A lot of bells and whistles, but in practice this delivers a really nice UX for helping users immediately get up and running with a new complex Python library you're distributing, as you could test for yourself by trying to immediately download and run words2map. Edits to this are strongly encouraged if they make sense / make this better / clearer for everyone. Okay, on to the show!
#!/bin/bash
download_miniconda() {
echo "Downloading Miniconda for Python dependencies..."
OS_BIT_TYPE="$(uname -m)"
OS_ARCHITECTURE="$(uname -s)"
if [ $OS_BIT_TYPE == "i686" ]; then
OS_BIT_TYPE="x86"
fi
if [ $OS_ARCHITECTURE == "Darwin" ]; then
OS_ARCHITECTURE="MacOSX"
fi
MINICONDA_INSTALL_FILE="Miniconda2-latest-$OS_ARCHITECTURE-$OS_BIT_TYPE.sh"
MINICONDA_DOWNLOAD_URL="https://repo.continuum.io/miniconda/$MINICONDA_INSTALL_FILE"
$(curl -O $MINICONDA_DOWNLOAD_URL)
$(chmod +x $MINICONDA_INSTALL_FILE)
}
install_miniconda() {
echo "Installing Miniconda..."
echo "$(./$MINICONDA_INSTALL_FILE -b -p $HOME/miniconda)"
echo "$(rm $MINICONDA_INSTALL_FILE)"
}
confirm_miniconda_installed() {
if hash conda 2>/dev/null; then
echo "Miniconda installed!"
else
echo "Failed to install Miniconda. Please visit http://conda.pydata.org/docs/install/quick.html to install and then try rerunning this script, making sure that Miniconda is accessible in the PATH"
fi
}
update_script_startup_file() {
echo "if [[ \":\$PATH:\" != *\":\$HOME/miniconda/bin:\"* ]]; then" >> $STARTUP_FILE
echo " export PATH=\"\$PATH:\$HOME/miniconda/bin\"" >> $STARTUP_FILE
echo "fi" >> $STARTUP_FILE
}
add_miniconda_to_path() {
# temporary update to PATH for this script
export PATH="$PATH:$HOME/miniconda/bin"
# permanent update to PATH for user's convenience
if [ -n "`$SHELL -c 'echo $BASH_VERSION'`" ]; then
STARTUP_FILE="$HOME/.bashrc"
update_script_startup_file
elif [ -n "`$SHELL -c 'echo $ZSH_VERSION'`" ]; then
STARTUP_FILE="$HOME/.zshrc"
update_script_startup_file
else
echo "Couldn't automatically add Miniconda to the PATH of your preferred terminal. We suggest working from Bash or ZShell."
fi
}
install_conda_if_needed() {
if hash conda 2>/dev/null; then
echo "Miniconda installed!"
else
if ping -c 1 google.com >> /dev/null 2>&1; then
download_miniconda
install_miniconda
add_miniconda_to_path
confirm_miniconda_installed
else
echo "Looks like you're offline! Please address this and then try rerunning this script."
fi
fi
}
create_conda_environment() {
if hash conda 2>/dev/null; then
CONDA_ENVIRONMENTS="$(conda env list)"
if [[ "$CONDA_ENVIRONMENTS" != *"words2map"* ]]; then
conda create --name words2map --yes cython scikit-learn gensim seaborn
fi
fi
}
install_developer_libraries_as_needed() {
OS_ARCHITECTURE="$(uname -s)"
if [ $OS_ARCHITECTURE == "Linux" ]; then
echo "$(python -mplatform | grep -qi Ubuntu && sudo apt-get update && sudo apt-get install python-dev || sudo yum update -y && sudo yum install python-devel -y && sudo yum groupinstall "Development Tools" -y)"
fi
}
install_python_dependencies() {
if hash conda 2>/dev/null; then
echo 'Installing Python dependencies for words2map...'
source activate words2map
install_developer_libraries_as_needed
pip install hdbscan pattern semidbm nltk unidecode
source deactivate
fi
}
refresh_user_shell() {
if [ -n "`$SHELL -c 'echo $BASH_VERSION'`" ]; then
exec bash
elif [ -n "`$SHELL -c 'echo $ZSH_VERSION'`" ]; then
exec zsh
fi
}
install_conda_if_needed
create_conda_environment
install_python_dependencies
refresh_user_shell
Upvotes: 4