Skip to content

Instantly share code, notes, and snippets.

@dhsrocha
Last active April 4, 2020 04:27
Show Gist options
  • Save dhsrocha/ee88f54c2e913c59412817e210bd206b to your computer and use it in GitHub Desktop.
Save dhsrocha/ee88f54c2e913c59412817e210bd206b to your computer and use it in GitHub Desktop.
Python environment setup with pyenv and pipenv. Also contains self-check logic and middleware setup powered by Shellcheck and Docker respectively.
#!/usr/bin/env sh
set -e
# @see https://gist.github.com/dhsrocha/ee88f54c2e913c59412817e210bd206b/edit
# ::: Global variables
readonly RC="$HOME/.bashrc"
readonly PROJ_NAME="${PWD##*/}"
readonly PROJ_VER="3.8.2"
readonly VENV_FILE=".python-version"
readonly GITHUB_RAW="https://raw.githubusercontent.com"
# ::: Parameter parsing
readonly IS_LOG="$(if [ "$1" != "-q" ]; then echo 1; else echo 0; fi)"
# ::: Public functions
# Set up python environment for the project. Main function.
setup() {
# Pre-conditions for testing:
# * .pyenv folder absent;
# * .rc file with no export/eval code appended;
# * local .python-version file absent; and
# * Variables not exported (fresh terminal).
# Execution scenarios:
# - All pre-conditions are satisfied to run the dependency manager;
# - One of the instruments has an inappropriate virtualization activated; and
# - None of the instruments is installed or activated.
readonly PY_CURR_VER="$(python -V 2>&1 | grep -Po '(?<=Python )(.+)')"
readonly VENV_NAME="$(python -c 'import sys;print(sys.prefix)' |
sed 's|.*/||')"
log "$FINE" "::: Starting python environment setup. :::" &&
env_manager &&
pyenv shell "$PROJ_NAME" &&
log "✔ Updating pip and setuptools." &&
pip install -q --upgrade pip setuptools &&
dep_manager &&
log "$FINE" "::: Setup concluded. :::"
}
# ::: Private functions :::
{
# Sets up pyenv (https://github.com/pyenv/pyenv).
env_manager() {
[ -z "$(command -v pyenv)" ] && {
log "$WARN" "✔ Installing or replacing pyenv distribution."
[ -d "$HOME/.pyenv" ] && rm -rf "$HOME/.pyenv"
export PATH=$HOME/.pyenv/bin:$PATH && (curl https://pyenv.run | sh)
}
{
log "✔ Initializing pyenv."
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
} && {
grep -qE "(eval.+pyenv)" "$RC" || {
log "$WARN" "✔ Appending pyenv initialization snippet to $RC." && {
printf "\n# ::: Pyenv configuration :::\n"
echo "# https://github.com/pyenv/pyenv-virtualenv/issues/36"
echo "# https://github.com/python-poetry/poetry/issues/172"
echo "export PATH=\"\$HOME/.pyenv/bin:\$PATH\""
echo "if [ -z \"\$PYENV_INITIALIZED\" ]; then"
echo " eval \"\$(pyenv init -)\""
echo " eval \"\$(pyenv virtualenv-init -)\""
echo " export PYENV_INITIALIZED=1"
echo "fi"
} >>"$RC"
}
}
[ "$VENV_NAME" != "$PROJ_NAME" ] && {
[ -d "$DIR/$PROJ_VER/envs/$PROJ_NAME" ] && [ -h "$DIR/$PROJ_NAME" ] &&
log "$WARN" "✔ Unlinking any environment linked to this project." &&
unlink "$DIR/$PROJ_NAME"
[ -f "$VENV_FILE" ] &&
log "$WARN" "✔ Removing pre-existent .python-version file." &&
rm "$VENV_FILE" && {
# shellcheck disable=SC1091
. deactivate
}
}
readonly DIR="$HOME/.pyenv/versions" && [ ! -d "$DIR/$PROJ_VER" ] &&
log "$WARN" "✔ Installing python distribution managed by pyenv." &&
pyenv install "$PROJ_VER"
[ ! -d "$DIR/$PROJ_NAME" ] &&
log "$WARN" "✔ Creating virtualenv provided by pyenv." &&
pyenv virtualenv --clear -q -f "$PROJ_VER" "$PROJ_NAME"
[ "$(pyenv versions | grep -E "\*\s$PROJ_NAME")" = "" ] &&
log "$WARN" "✔ Loading virtualenv provided by pyenv." &&
pyenv local "$PROJ_NAME"
return 0
}
# Sets up pypoetry (https://python-poetry.org).
dep_manager() {
[ "$PROJ_NAME" != "$PYENV_VERSION" ] && # (pyenv shell "$PROJ_NAME")
throw "Environment not ready for dependency management."
[ -z "$(command -v poetry)" ] && # then
log "✔ Installing python poetry." &&
pyenv shell system &&
readonly POETRY_URL="/python-poetry/poetry/master/get-poetry.py" &&
(curl -sSL "${GITHUB_RAW}${POETRY_URL}" | python) &&
pyenv shell --unset
[ -n "$(command -v poetry)" ] && # else
log "✔ Updating pypoetry." && poetry self update
# shellcheck disable=SC1091
# shellcheck source=$HOME/.poetry/env
. "$HOME/.poetry/env"
grep -qE ".poetry/env" "$RC" || {
log "$WARN" "✔ Appending pypoetry initialization snippet to $RC." && {
printf "\n# ::: PyPoetry configuration :::\n"
echo "# shellcheck source=\$HOME/.poetry/env"
echo ". \$HOME/.poetry/env"
} >>"$RC"
}
# https://github.com/python-poetry/poetry#enable-tab-completion-for-bash-fish-or-zsh
# https://github.com/python-poetry/poetry/issues/1017
readonly POETRY_FILE="/etc/bash_completion.d/poetry.bash-completion" &&
[ ! -f "$POETRY_FILE" ] &&
log "$WARN" "✔ Adding poetry completation to $RC." &&
(poetry completions bash | sudo tee "$POETRY_FILE")
[ "$(poetry env list | grep -E "$PROJ_NAME")" != "" ] &&
# https://python-poetry.org/docs/managing-environments/
log "$WARN" "✔ Cleaning other dangling virtualenvs created by poetry." &&
poetry env remove "$(command -v python)"
[ -f "./pyproject.toml" ] && # then
log "✔ Checking and adding dependencies with pypoetry." &&
poetry check --no-ansi && poetry update
[ ! -f "./pyproject.toml" ] && # else
log "$WARN" "✔ Initializing project with pypoetry." && poetry init -n
readonly PY_DEPS="
flake8 black mypy xdoctest ipdb click
pytest pytest-cov pytest-flakes pytest-bdd pytest-instafail"
for I in $PY_DEPS; do poetry show -q "$I" || poetry add -D "$I"; done
return 0
}
# ::: Utility functions
# Prints customized colored messages. can be set to be silent conditionally.
readonly FINE=32 && readonly WARN=33 && readonly INFO=34
log() {
set -u
[ "$IS_LOG" -eq 1 ] && { case "$1" in 31 | 32 | 33 | 34)
[ -t 1 ] && printf "\033[%sm%s\033[0m\n" "$1" "$2" || echo "$2"
;;
*) printf "\033[${INFO}m%s\033[0m\n" "$*" ;; esac }
set +u
return 0
}
# Prints red colored messages and then finishes the program with a error exit.
throw() { log 31 "ERROR: $*" 1>&2 && exit 1; }
# Set up self-checking logic for this script (https://www.shellcheck.net/).
check() {
[ -z "$(command -v shellcheck)" ] &&
log "$WARN" "Install shellcheck into system." &&
sudo apt update && sudo apt install shellcheck
shellcheck -a -s sh "$0" || throw "Failed on script's self-checking."
return "$?"
}
# ::: Supporting programs's installation functions
# Set up docker (https://docs.docker.com/install/linux/docker-ce/debian/).
middleware() {
[ -z "$(command -v docker)" ] &&
log "$WARN" "Install docker into system." &&
(readonly REL="$(lsb_release -cs)" &&
sudo apt remove docker docker-engine docker.io containerd runc &&
sudo apt install curl apt-transport-https \
ca-certificates gnupg2 software-properties-common &&
curl -fsSL https://download.docker.com/linux/debian/gpg |
sudo apt-key add - && sudo apt-key fingerprint 0EBFCD88 &&
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian $REL stable" &&
sudo apt-get update &&
sudo apt-get install docker-ce docker-ce-cli containerd.io)
readonly DOCKR_CMP_VER="1.25.4" &&
readonly URL_DCKR="https://github.com/docker/compose/releases/download/$DOCKR_CMP_VER/docker-compose" &&
[ -n "$(command -v docker)" ] && [ -z "$(command -v docker-compose)" ] &&
log "$WARN" "Install docker-compose into system." &&
(sudo curl -L "$URL_DCKR-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose &&
sudo chmod +x /usr/local/bin/docker-compose &&
docker-compose --version)
return 0
}
# Set up terraform (https://www.terraform.io/).
deploy() {
[ -z "$(command -v terraform)" ] &&
log "$WARN" "Install terraform into system." &&
(DEST="$HOME/Downloads/" && readonly TF_VER="0.12.23" &&
FILE="terraform_${TF_VER}_linux_amd64.zip" &&
curl "https://releases.hashicorp.com/terraform/$TF_VER/$FILE" \
-L -o "$DEST/$FILE" &&
sudo apt install unzip &&
unzip "$DEST/$FILE" -d "$DEST" &&
sudo mv terraform "/bin" &&
rm "$DEST")
return 0
}
# Set up AWS CLI (https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html).
cloud() {
[ -z "$(command -v aws)" ] &&
log "$WARN" "Install AWS CLI into system." &&
(sudo apt install zip unzip &&
FILE="awscliv2.zip" &&
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "$FILE" &&
unzip "$FILE" && sudo "./aws/install" && rm "$FILE" "./aws")
return 0
}
# Sets up a .editorconfig file to the project (https://editorconfig.org).
formatting() {
[ ! -f ".editorconfig" ] &&
log "$WARN" "Create .editorconfig file for the python project." && {
echo "# https://editorconfig.org"
echo && echo "root = true"
echo && echo "[*]"
echo "charset = utf-8" && echo "max_line_length = 79"
echo "indent_size = 2" && echo "tab_width = 2"
echo "indent_style = space" && echo "trim_trailing_whitespace = true"
echo "insert_final_newline = true" && echo "end_of_line = lf"
echo && echo "[{*.pyw, *.py}]"
echo "indent_size = 4" && echo "tab_width = 4"
} >".editorconfig"
return 0
}
# Sets up a .gitignore file to the project (https://git-scm.com/).
ignore() {
[ ! -f ".gitignore" ] &&
log "$WARN" "Create .gitignore file for the python project." &&
readonly URL_IG="https://www.gitignore.io/api/pydev,flask,django,python,terraform,pycharm+all,jupyternotebooks" &&
curl "$URL_IG" >>".gitignore"
return 0
}
# Sets up git bash prompt (https://github.com/magicmonty/bash-git-prompt).
git_prompt() {
[ "$GIT_PROMPT_ONLY_IN_REPO" = 1 ] || {
log "Create git bash prompt into system." &&
readonly PROMPT=".bash-git-prompt/gitprompt.sh" &&
git clone "https://github.com/magicmonty/bash-git-prompt.git" \
"$HOME/.bash-git-prompt" --depth=1
grep -qE "($PROMPT)" "$RC" || {
echo "# ::: https://github.com/magicmonty/bash-git-prompt#via-git-clone"
echo "[ -f "\$HOME/$PROMPT" ] &&"
echo " export GIT_PROMPT_ONLY_IN_REPO=1 &&"
echo " . \$HOME/$PROMPT"
} >>"$RC"
}
return 0
}
}
# ::: Run block :::
main() {
sudo -k &&
check && ignore && formatting &&
git_prompt && middleware && cloud && deploy &&
setup "$1"
}
main "$1"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment