Last active
October 31, 2024 03:13
-
-
Save xmodar/7bcb7cbcc9a263ef8f758e1bad9a80eb to your computer and use it in GitHub Desktop.
Manage Python virtual environments with uv (astral.sh/uv)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Install this tool with the followig command: | |
# curl -LsSf https://gist.githubusercontent.com/xmodar/7bcb7cbcc9a263ef8f758e1bad9a80eb/raw/uvn.sh | bash | |
uvn() { | |
if [[ "$1" == "-h" ]]; then | |
echo "Manage Python virtual environments with uv (astral.sh/uv)" | |
echo "" | |
echo "Author: @xmodar" | |
echo "Link : https://gist.github.com/xmodar/7bcb7cbcc9a263ef8f758e1bad9a80eb" | |
echo "Also : https://github.com/xmodar/uvn" | |
echo "" | |
echo "Usage:" | |
echo " uvn # List environments" | |
echo " uvn [<env>|.] # Activate environment" | |
echo " uvn -d # Deactivate environment" | |
echo " uvn -v <env> [-q] # Get environment version" | |
echo " uvn -n <env> # Create new environment" | |
echo " uvn -r <env> # Remove environment" | |
echo " uvn -e <env> # Export as requirements.txt" | |
echo " uvn -s <env> # Export as inline script metadata" | |
echo " uvn -c <env> <new> # Clone environment" | |
echo " uvn -h # Display this help message" | |
return 0 | |
fi | |
local venv_dir=~/.virtualenvs | |
# Check if uv exists | |
command -v uv &>/dev/null || { | |
echo -e "Error: 'uvn' requires 'uv'. Install it with:\n\n"\ | |
" curl -LsSf https://astral.sh/uv/install.sh | sh\n" | |
return 1 | |
} | |
# List available environments | |
if [[ $# -eq 0 ]]; then | |
for env_path in "$venv_dir"/*; do | |
name=$(basename "$env_path") | |
version=$(uvn -v "$name" -q) | |
if [[ -n "$version" ]]; then | |
active=$([[ $VIRTUAL_ENV == $env_path ]] && echo "*" || echo "") | |
echo -e "$name$active\t@ $version" | |
fi | |
done | |
# Activate the specified environment | |
elif [[ $# -eq 1 ]]; then | |
if [[ "$1" == '-d' ]]; then | |
deactivate | |
else | |
if [[ "$1" == "." ]]; then | |
env_path="$(pwd)/.venv/bin/activate" | |
else | |
env_path="$venv_dir/$1/bin/activate" | |
fi | |
if [[ -f $env_path ]]; then | |
source "$env_path" | |
else | |
echo "Environment '$1' does not exist" >&2 | |
return 1 | |
fi | |
fi | |
# Get the specified environment version | |
elif [[ "$1" == "-v" && -n "$2" ]]; then | |
version=$("$venv_dir/$2/bin/python" --version 2>/dev/null | cut -c 8-) | |
if [[ -n "$version" ]]; then | |
echo "$version" | |
else | |
[[ "$3" != "-q" ]] && echo "Environment '$2' does not exist" >&2 | |
return 1 | |
fi | |
# Create a new environment | |
elif [[ "$1" == "-n" && -n "$2" ]]; then | |
if [[ -n $(uvn -v $2 -q) ]]; then | |
echo "Environment '$2' already exists" >&2 | |
return 1 | |
fi | |
result=$(uv venv "$venv_dir/$2" "${@:3}" 2>&1) | |
version=$(uvn -v "$2" -q) | |
if [[ -n "$version" ]]; then | |
echo "Created environment '$2' @ "$version"" | |
else | |
echo "$result" | |
return 1 | |
fi | |
# Remove the specified environment | |
elif [[ "$1" == "-r" && -n "$2" ]]; then | |
if [[ -n $(uvn -v $2 -q) ]]; then | |
rm -rf "$venv_dir/$2" && echo "Removed environment '$2'" | |
else | |
echo "Environment '$2' does not exist" >&2 | |
return 1 | |
fi | |
# Export the specified environment | |
elif [[ "$1" == "-e" && -n "$2" ]]; then | |
if [[ -n $(uvn -v $2 -q) ]]; then | |
echo "# python==$(uvn -v "$2")" # Add Python version as a comment | |
VIRTUAL_ENV="$venv_dir/$2" uv pip freeze 2>/dev/null | |
else | |
echo "Environment '$2' does not exist" >&2 | |
return 1 | |
fi | |
# Export the specified environment as inline script metadata | |
elif [[ "$1" == "-s" && -n "$2" ]]; then | |
version=$(uvn -v $2 -q) | |
if [[ -n "$version" ]]; then | |
echo "#!/usr/bin/env -S uv run" | |
echo "# /// script" | |
next_version=$(echo "$version" | awk -F. '{print $1"."$2+1}') | |
echo "# requires-python = \">="$version",<$next_version\"" | |
requirements=$(VIRTUAL_ENV="$venv_dir/$2" uv pip tree -d 0 2>/dev/null) | |
if [[ -n "$requirements" ]]; then | |
echo "# dependencies = [" | |
echo "$requirements" | awk '{gsub(/^v/, "", $2); printf "# \"%s>=%s\",\n", $1, $2}' | |
echo "# ]" | |
fi | |
echo "# ///" | |
else | |
echo "Environment '$2' does not exist" >&2 | |
return 1 | |
fi | |
# Clone the specified environment | |
elif [[ "$1" == "-c" && -n "$2" && -n "$3" ]]; then | |
if [[ -n $(uvn -v "$3" -q) ]]; then | |
echo "Environment '$3' already exists" >&2 | |
return 1 | |
fi | |
version=$(uvn -v "$2" -q) | |
if [[ -n "$version" ]]; then | |
uvn -n "$3" -p "$version" | |
uvn -e "$2" | VIRTUAL_ENV="$venv_dir/$3" uv pip install -r - | |
else | |
echo "Environment '$2' does not exist" >&2 | |
return 1 | |
fi | |
# If no valid option is matched | |
else | |
uvn -h | |
return 1 | |
fi | |
} | |
add_bash_function() { | |
local func="$(type "$1" | tail -n +2)" | |
local name=$(echo "${func%% *}") | |
local target=""$HOME"/.bash_functions/"$name".sh" | |
mkdir -p "$(dirname "$target")" | |
echo "$func" > "$target" | |
local parent=$(dirname "$target") | |
local body="for file in "$parent"/*.sh; do source \$file; done" | |
local block=$(echo -e "# /- "$parent"\n"$body"\n# "$parent" -/") | |
if ! grep -qF "$block" ~/.bashrc; then | |
echo "$block" >> ~/.bashrc | |
fi | |
} | |
add_bash_function uvn |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment