From 10f21d69761fd56c7236d65e9e371d9bea6cabf7 Mon Sep 17 00:00:00 2001 From: Andre Faria Date: Tue, 10 Mar 2026 23:01:29 +0000 Subject: [PATCH 1/2] feat(plugins/python): add python plugin --- plugins/README.md | 1 + plugins/python/README.md | 82 ++++++++++++++++++++ plugins/python/python.plugin.sh | 133 ++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 plugins/python/README.md create mode 100644 plugins/python/python.plugin.sh 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..4f0c3a734 --- /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 `cd` | + +## 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 overrides `cd` to automatically activate a virtual environment when you enter a directory that contains one, and deactivate it when you leave. + +```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..65eb19dc0 --- /dev/null +++ b/plugins/python/python.plugin.sh @@ -0,0 +1,133 @@ +#! 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 + if [[ $(type -t deactivate) == function ]] && [[ -n ${VIRTUAL_ENV-} ]] && + [[ $PWD != "${VIRTUAL_ENV%/*}"* ]]; then + deactivate > /dev/null 2>&1 + 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 + } + + function _omb_plugin_python_cd { + command cd "$@" || return $? + _omb_plugin_python_auto_vrun + } + alias cd='_omb_plugin_python_cd' + + # Run once for the shell's starting directory + _omb_plugin_python_auto_vrun +fi From 2815389932d5b3142f48c87eeda2358f1f5cfdeb Mon Sep 17 00:00:00 2001 From: Andre Faria Date: Wed, 11 Mar 2026 12:20:12 +0000 Subject: [PATCH 2/2] fix(python): fix venv deactivation boundary check and replate cd alias with PROMPT_COMMAND hook --- plugins/python/README.md | 10 +++++----- plugins/python/python.plugin.sh | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/plugins/python/README.md b/plugins/python/README.md index 4f0c3a734..a133696e0 100644 --- a/plugins/python/README.md +++ b/plugins/python/README.md @@ -14,10 +14,10 @@ plugins=(python) 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 `cd` | +| 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 @@ -74,7 +74,7 @@ mkv .venv # create and activate ./.venv ## Auto-activate venv -When `OMB_PLUGIN_PYTHON_AUTO_VRUN=true`, the plugin overrides `cd` to automatically activate a virtual environment when you enter a directory that contains one, and deactivate it when you leave. +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) diff --git a/plugins/python/python.plugin.sh b/plugins/python/python.plugin.sh index 65eb19dc0..7fa97bc81 100644 --- a/plugins/python/python.plugin.sh +++ b/plugins/python/python.plugin.sh @@ -102,10 +102,16 @@ function mkv { 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 - if [[ $(type -t deactivate) == function ]] && [[ -n ${VIRTUAL_ENV-} ]] && - [[ $PWD != "${VIRTUAL_ENV%/*}"* ]]; then - deactivate > /dev/null 2>&1 + # 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 @@ -122,12 +128,15 @@ if [[ ${OMB_PLUGIN_PYTHON_AUTO_VRUN-} == true ]]; then done } - function _omb_plugin_python_cd { - command cd "$@" || return $? + # 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 } - alias cd='_omb_plugin_python_cd' + _omb_util_add_prompt_command _omb_plugin_python_auto_vrun_hook # Run once for the shell's starting directory - _omb_plugin_python_auto_vrun + _omb_plugin_python_auto_vrun_hook fi