diff --git a/plugins/README.md b/plugins/README.md index 28fd28bc9..bba577f48 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -49,6 +49,7 @@ By leveraging these plugins, you can streamline your workflow and tackle coding | nvm | Node.js version manager allowing easy switching between different Node.js versions. | | progress | Insufficient information provided to give a precise description. | | pyenv | Tool for managing multiple Python versions within a system. | +| [python](python) | Aliases and utilities for Python development, including venv management and pip shortcuts. | | rbenv | Tool for managing your app's Ruby environment. | | sdkman | Version and package manager for development tools such as Java, Kotlin, and Gradle. | | sudo | Utility for executing commands with superuser privileges on Unix and Unix-like systems. | diff --git a/plugins/python/README.md b/plugins/python/README.md new file mode 100644 index 000000000..a133696e0 --- /dev/null +++ b/plugins/python/README.md @@ -0,0 +1,82 @@ +# python plugin + +Add [oh-my-bash](https://ohmybash.github.io) integration for [Python](https://www.python.org/) development, providing aliases and utilities for everyday workflows including virtual environment management, pip, and project housekeeping. Inspired by the [oh-my-zsh python plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/python). + +## Installation + +Enable the plugin by adding it to your oh-my-bash `plugins` definition in `~/.bashrc`. + +```sh +plugins=(python) +``` + +## Configuration + +Set these variables **before** the `source "$OSH/oh-my-bash.sh"` line in your `~/.bashrc`. + +| Variable | Default | Description | +|--------------------------------|-----------|----------------------------------------------------------| +| `OMB_PLUGIN_PYTHON_VENV_NAME` | `venv` | Default venv directory name used by `vrun` and `mkv` | +| `OMB_PLUGIN_PYTHON_AUTO_VRUN` | _(unset)_ | Set to `true` to auto-activate venvs on directory change | + +## Aliases + +### General + +| Alias | Command | Description | +|------------|----------------------------|----------------------------------------------------------| +| `py` | `python3` | Shorthand for python3 (if `py` is not already installed) | +| `pyfind` | `find . -name "*.py"` | Find all Python files in current tree | +| `pygrep` | `grep -rn --include="*.py"`| Search inside Python files | +| `pyserver` | `python3 -m http.server` | Serve current directory over HTTP | + +### pip + +| Alias | Command | Description | +|----------|------------------------------------|------------------------------------------| +| `pipi` | `pip install` | Install a package | +| `pipu` | `pip uninstall` | Uninstall a package | +| `pipup` | `pip install --upgrade` | Upgrade a package | +| `pipr` | `pip install -r requirements.txt` | Install from requirements.txt | +| `pipf` | `pip freeze` | Print installed packages | +| `pipfr` | `pip freeze > requirements.txt` | Save installed packages to requirements | +| `pipls` | `pip list` | List installed packages | +| `pipout` | `pip list --outdated` | List outdated packages | + +## Functions + +### `pyclean [dir...]` + +Remove compiled bytecode files and cache directories (`__pycache__`, `.mypy_cache`, `.pytest_cache`) from the given directories, or the current directory if none are specified. + +```bash +pyclean # clean current directory +pyclean src tests # clean specific directories +``` + +### `vrun [name]` + +Activate a virtual environment by name. If no name is given, searches for the first existing directory in `$OMB_PLUGIN_PYTHON_VENV_NAME`, `venv`, `.venv` (in that order). + +```bash +vrun # auto-detect and activate +vrun .venv # activate a specific venv +``` + +### `mkv [name]` + +Create a new virtual environment with `python3 -m venv` and immediately activate it. Defaults to `$OMB_PLUGIN_PYTHON_VENV_NAME`. + +```bash +mkv # create and activate ./venv +mkv .venv # create and activate ./.venv +``` + +## Auto-activate venv + +When `OMB_PLUGIN_PYTHON_AUTO_VRUN=true`, the plugin registers a `PROMPT_COMMAND` hook to automatically activate a virtual environment when you enter a directory that contains one, and deactivate it when you leave. Using `PROMPT_COMMAND` instead of aliasing `cd` ensures compatibility with other plugins that also hook into directory changes (e.g. `nvm`). + +```sh +# ~/.bashrc (before sourcing oh-my-bash) +OMB_PLUGIN_PYTHON_AUTO_VRUN=true +``` diff --git a/plugins/python/python.plugin.sh b/plugins/python/python.plugin.sh new file mode 100644 index 000000000..7fa97bc81 --- /dev/null +++ b/plugins/python/python.plugin.sh @@ -0,0 +1,142 @@ +#! bash oh-my-bash.module +# Description: Aliases and utilities for Python development +# Inspired by the oh-my-zsh python plugin (https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/python) +# +# @var[opt] OMB_PLUGIN_PYTHON_VENV_NAME default venv directory name (default: venv) +# @var[opt] OMB_PLUGIN_PYTHON_AUTO_VRUN set to 'true' to auto-activate venv on directory change + +# Use 'py' as a shorthand for python3 if not already defined +_omb_util_command_exists py || alias py='python3' + +# Find Python source files +alias pyfind='find . -name "*.py"' + +# Grep within Python source files +alias pygrep='grep -rn --include="*.py"' + +# Serve the current directory over HTTP +alias pyserver='python3 -m http.server' + +# Remove compiled bytecode and cache directories left by Python tools +function pyclean { + find "${@:-.}" -type f -name "*.py[co]" -delete + find "${@:-.}" -type d -name "__pycache__" -delete + find "${@:-.}" -depth -type d -name ".mypy_cache" -exec rm -r "{}" + + find "${@:-.}" -depth -type d -name ".pytest_cache" -exec rm -r "{}" + +} + +#------------------------------------------------------------------------------ +# pip aliases + +alias pipi='pip install' +alias pipu='pip uninstall' +alias pipup='pip install --upgrade' +alias pipr='pip install -r requirements.txt' +alias pipf='pip freeze' +alias pipfr='pip freeze > requirements.txt' +alias pipls='pip list' +alias pipout='pip list --outdated' + +#------------------------------------------------------------------------------ +# Virtual environment management +# +# @var[opt] OMB_PLUGIN_PYTHON_VENV_NAME default venv name used by vrun/mkv (default: venv) + +: "${OMB_PLUGIN_PYTHON_VENV_NAME:=venv}" + +# Build the ordered, deduplicated list of venv names to search +_omb_plugin_python_venv_names=("$OMB_PLUGIN_PYTHON_VENV_NAME") +for _omb_plugin_python__n in venv .venv; do + [[ " ${_omb_plugin_python_venv_names[*]} " == *" $_omb_plugin_python__n "* ]] || + _omb_plugin_python_venv_names+=("$_omb_plugin_python__n") +done +unset -v _omb_plugin_python__n + +# Activate a virtual environment. +# Usage: vrun [name] +# If no name is given, searches for the first matching directory in +# $OMB_PLUGIN_PYTHON_VENV_NAME, venv, .venv (in that order). +function vrun { + if [[ -z ${1-} ]]; then + local name + for name in "${_omb_plugin_python_venv_names[@]}"; do + if [[ -d $name ]]; then + vrun "$name" + return $? + fi + done + _omb_util_print 'vrun: no virtual environment found in current directory' >&2 + return 1 + fi + + local name=$1 + if [[ ! -d $name ]]; then + _omb_util_print "vrun: no such venv in current directory: $name" >&2 + return 1 + fi + if [[ ! -f $name/bin/activate ]]; then + _omb_util_print "vrun: '$name' is not a proper virtual environment" >&2 + return 1 + fi + + # shellcheck source=/dev/null + . "$name/bin/activate" || return $? + _omb_util_print "Activated virtual environment $name" +} + +# Create a new virtual environment and immediately activate it. +# Usage: mkv [name] +# Defaults to $OMB_PLUGIN_PYTHON_VENV_NAME if no name is given. +function mkv { + local name=${1:-$OMB_PLUGIN_PYTHON_VENV_NAME} + python3 -m venv "$name" || return $? + _omb_util_print "Created venv in '$PWD/$name'" + vrun "$name" +} + +#------------------------------------------------------------------------------ +# Auto-activate venv on directory change +# +# When OMB_PLUGIN_PYTHON_AUTO_VRUN=true, automatically activates a venv when +# entering a directory that contains one, and deactivates it when leaving. + +if [[ ${OMB_PLUGIN_PYTHON_AUTO_VRUN-} == true ]]; then + function _omb_plugin_python_auto_vrun { + # Deactivate if we have left the project directory that owns the current venv. + # Use an exact boundary check to avoid matching sibling dirs with the same prefix + # (e.g. /path/project2 when the venv belongs to /path/project). + if [[ $(type -t deactivate) == function ]] && [[ -n ${VIRTUAL_ENV-} ]]; then + local _omb_plugin_python_project_dir=${VIRTUAL_ENV%/*} + if [[ $PWD != "$_omb_plugin_python_project_dir" && + $PWD != "$_omb_plugin_python_project_dir/"* ]]; then + deactivate > /dev/null 2>&1 + fi + unset -v _omb_plugin_python_project_dir + fi + + # Skip if this directory is already the active venv's home + [[ -n ${VIRTUAL_ENV-} && $PWD == "${VIRTUAL_ENV%/*}" ]] && return 0 + + local name + for name in "${_omb_plugin_python_venv_names[@]}"; do + if [[ -f $name/bin/activate ]]; then + [[ $(type -t deactivate) == function ]] && deactivate > /dev/null 2>&1 + # shellcheck source=/dev/null + source "$name/bin/activate" > /dev/null 2>&1 + break + fi + done + } + + # Use PROMPT_COMMAND instead of aliasing cd so other plugins' cd hooks are not clobbered. + _omb_plugin_python_last_pwd= + function _omb_plugin_python_auto_vrun_hook { + [[ ${_omb_plugin_python_last_pwd-} == "$PWD" ]] && return 0 + _omb_plugin_python_last_pwd=$PWD + _omb_plugin_python_auto_vrun + } + _omb_util_add_prompt_command _omb_plugin_python_auto_vrun_hook + + # Run once for the shell's starting directory + _omb_plugin_python_auto_vrun_hook +fi