Skip to content

ashwch/auto-uv-env

Repository files navigation

auto-uv-env 🐍

License: MIT UV

Automatic UV-based Python virtual environment management for your shell.

auto-uv-env watches directory changes, discovers the nearest pyproject.toml, creates a project-local virtual environment with uv, and activates/deactivates it automatically.

Architecture Overview

+-------------------------+      --check-safe       +-------------------------+
| shell hook              |------------------------>| auto-uv-env             |
| bash/zsh/fish adapter   |<------------------------| directive producer      |
+------------+------------+      KEY=VALUE lines    +------------+------------+
             |                                                   |
             | applies directives                               | reads pyproject.toml
             v                                                   | validates venv name
      +------+--------------------------+                        |
      | source/deactivate virtualenv    |                        |
      | uv python install / uv venv     |<-----------------------+
      +---------------------------------+

Quick Start

1) Install UV

curl -LsSf https://astral.sh/uv/install.sh | sh

2) Install auto-uv-env

Installer script:

curl -LsSf https://auto-uv-env.ashwch.com/install.sh | sh

Homebrew:

brew tap ashwch/tap
brew install auto-uv-env

3) Add shell integration

Zsh (~/.zshrc):

source $(brew --prefix)/share/auto-uv-env/auto-uv-env.zsh
# or: source /usr/local/share/auto-uv-env/auto-uv-env.zsh

Bash (~/.bashrc):

source $(brew --prefix)/share/auto-uv-env/auto-uv-env.bash
# or: source /usr/local/share/auto-uv-env/auto-uv-env.bash

Fish (~/.config/fish/config.fish):

source (brew --prefix)/share/auto-uv-env/auto-uv-env.fish
# or: source /usr/local/share/auto-uv-env/auto-uv-env.fish

4) Use it

cd my-project/src/app
# 🐍 Setting up Python 3.11 with UV...
# βœ… Virtual environment created
# πŸš€ UV environment activated (Python 3.11.x)

cd ..
# ⬇️  Deactivated UV environment

Core Behavior

  1. Walk upward from $PWD to find the nearest pyproject.toml.
  2. If .auto-uv-env-ignore appears first, skip activation for that subtree.
  3. Read requires-python from the discovered project root.
  4. Create <project-root>/.venv when missing.
  5. Activate on entry and deactivate only environments managed by auto-uv-env.
  6. If a manual venv is active, auto-uv-env does not override it.

Configuration

Environment variables:

  • AUTO_UV_ENV_QUIET=1: suppress status messages.
  • AUTO_UV_ENV_VENV_NAME=.venv: change the venv directory name.
  • AUTO_UV_ENV_PYTHON_VERSION: exported Python version of the active managed env.

pyproject.toml example:

[project]
name = "my-project"
requires-python = ">=3.11"

Disable in a subtree:

touch .auto-uv-env-ignore

Ignore precedence:

  • Upward discovery stops activation when ignore is found before pyproject.toml.
  • Entering an ignored subtree deactivates the currently managed environment.

CLI Reference

auto-uv-env --help
auto-uv-env --version
auto-uv-env --check-safe [DIR]
auto-uv-env --diagnose [DIR]
auto-uv-env --validate

--check-safe emits directive lines (for shell adapters), for example:

CREATE_VENV=1
PYTHON_VERSION=3.11
MSG_SETUP=🐍 Setting up Python 3.11 with UV...
ACTIVATE=/path/to/project/.venv

Why the shell adapters are careful

The shell adapters (bash, zsh, fish) run inside your live shell session, so they have to be boring and predictable.

Two rules they follow

Rule 1: never let one activation run recursively trigger another
Rule 2: always create the venv at an explicit project-root path

Why:

Without Rule 1
--------------
hook starts
  -> creates venv
    -> activation changes shell state
      -> shell/plugin fires hook again
        -> setup repeats
          -> "Setting up Python ..." forever

Without Rule 2
--------------
hook starts
  -> cd into project root
    -> run uv venv
      -> shell state now depends on directory-changing side effects

With both rules:

hook starts
  -> compute project root
  -> create /absolute/project/.venv directly
  -> activate once
  -> finish

This is why the adapters now:

  • use a small re-entry guard while activation is already in progress
  • call uv venv /absolute/path/to/.venv instead of relying on cd
  • set VIRTUAL_ENV_DISABLE_PROMPT=1 only while sourcing the activation script, then restore the prior shell state
  • keep the main executable declarative and the shell adapter imperative

Prompt ownership rule

auto-uv-env owns environment activation
your shell theme owns prompt rendering

Why this matters:

Bad
---
source bin/activate
  -> activation script rewrites PS1 / prompt function
  -> starship or custom shell prompt loses styling

Good
----
temporarily set VIRTUAL_ENV_DISABLE_PROMPT=1 while sourcing bin/activate
then restore the previous shell variable state
  -> environment variables are imported
  -> prompt styling stays under the user's control

Performance

  • v1.0.7 delivered roughly 93% startup overhead improvement versus earlier behavior.
  • Typical startup overhead is low in Python projects and effectively near-zero in non-project directories.
  • Directory-change overhead is typically sub-millisecond.

Choosing This Tool

auto-uv-env is a strong fit when you want:

  • UV-first Python environment automation (no extra orchestration layer)
  • pyproject.toml-driven project discovery
  • Automatic activate/deactivate across Bash, Zsh, and Fish
  • Project-local .venv behavior with minimal setup

If you are deciding between this and direnv, mise, pyenv-virtualenv, or shell-specific plugins, see the decision guide: docs/alternatives.md.

Why the installer is careful too

The install script has its own first-principles rule:

extract first -> install second -> clean up last

Why:

installer shell
  -> ask helper to download + extract release
  -> helper returns extracted path
  -> installer copies files into final install dirs
  -> installer removes temporary files

If cleanup happens too early, installation can fail with a "No such file or directory" error while copying from the extracted release tree.

The repository now includes a dedicated installer regression test for this lifecycle:

./test/test-installer.sh

That test runs the real installer flow with mocked downloads, so future changes can prove the temporary extracted directory stays alive until installation completes.

Documentation Map

For Contributors

Essential checks:

./test/test.sh
./test/test-security.sh
./test/test-shell-integrations.sh
./test/test-deleted-venv.sh
uv tool run pre-commit run --all-files

If your shell currently has an active venv, use a sanitized test invocation:

env -u VIRTUAL_ENV -u _AUTO_UV_ENV_ACTIVATION_DIR -u AUTO_UV_ENV_PYTHON_VERSION ./test/test.sh

Release reminder: merging a PR does not create a GitHub release. Releases are published on v* tag pushes; follow RELEASE.md. Docs publishing reminder: https://auto-uv-env.ashwch.com/ is deployed by docs.yml on pushes to main.

LLM Agent Docs

  • AGENTS.md: canonical architecture, workflows, quality gates, and safety rules.
  • CLAUDE.md: minimal Claude entrypoint that sources AGENTS.md.

License

MIT License. See LICENSE.

Author

Created by Ashwini Chaudhary.

About

Automatic UV-based Python virtual environment management for seamless project workflows

Topics

Resources

License

Contributing

Stars

9 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages