astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
26.39k stars 767 forks source link

Feature Request: `uv shell` #1910

Open twiddles opened 8 months ago

twiddles commented 8 months ago

Summary I propose the introduction of a new command, uv shell. This command would provide users with a quick and straightforward way to enter the associated Python virtual environment (venv) for their projects.

Motivation The inspiration for uv shell comes from the convenience and simplicity offered by the poetry shell command in Poetry. Having worked with poetry on multiple OSs over the last year, I miss this intuitive approach to environment management. I propose introducing a similar capability into uv.

Proposed Solution The uv shell command would activate the project's Python virtual environment (basically a shorthand for .venv/bin/activate), allowing users to immediately start working within the context of that environment OR return an error message if the venv is missing. This feature would abstract away the need to manually source the venv activation script, thus providing a more seamless development workflow across operating systems.

mgaitan commented 8 months ago

This is somewhat similar to the workon <venv> command of virtualenvwrapper (a tool I have been using for many years), except that here each virtualenv is referrer by a name and not its path (its not stored inside the project directory but in as a subidir of a command path defined by $WORKON_HOME . like ~/.venvs)

Also, a functionality that I find useful of virtualenvwrapper are the hooks (pre/post hooks on venv activation and venv creation)

If you can include some of these features in uv many of us would be grateful! .

rgilton commented 8 months ago

I frequently use pipenv shell, and the killer feature of this is that it starts a new shell. Whereas activating a virtual environment modifies the currently running shell. This means that one can exit the shell without exiting the terminal window one is running the shell in. It would be great if uv shell could replicate this behaviour.

jfcherng commented 8 months ago

Would like to have an even shorter alias at the same time such as uv sh (just like uv v for uv venv).


Fwiw, this is what I have for my Bash shell on both Windows (git-bash) and Linux right now.

Bash Script

You can add this into your .bashrc / .bash_aliases or whatever auto sourced bash file.

uvsh() {
    local venv_name=${1:-'.venv'}
    venv_name=${venv_name//\//} # remove trailing slashes (Linux)
    venv_name=${venv_name//\\/} # remove trailing slashes (Windows)

    local venv_bin=
    if [[ -d ${WINDIR} ]]; then
        venv_bin='Scripts/activate'
    else
        venv_bin='bin/activate'
    fi

    local activator="${venv_name}/${venv_bin}"
    echo "[INFO] Activate Python venv: ${venv_name} (via ${activator})"

    if [[ ! -f ${activator} ]]; then
        echo "[ERROR] Python venv not found: ${venv_name}"
        return
    fi

    # shellcheck disable=SC1090
    . "${activator}"
}

Usage

mitsuhiko commented 8 months ago

I had this in rye and backed it out because there is just no non hacky way to make this work. I think the desire is real, but the experience just will always have holes. You basically need to allocate a pty, hook it up with the parent pty and then send key presses into it to manipulate the shell environment. It's pretty hacky and breaks stuff in subtle ways :(

rgilton commented 8 months ago

You basically need to allocate a pty, hook it up with the parent pty and then send key presses into it to manipulate the shell environment. It's pretty hacky and breaks stuff in subtle ways :(

Is it not just a case of exec'ing $SHELL, after modifying the environment as required? (Or exec'ing $SHELL with an argument to make it source the venv's activate script on start-up?)

NMertsch commented 8 months ago

For work I'm switching between Windows and Linux a lot. Having one command for activating would be nice to have, so I don't have to deal with .venv\Scripts\activate.bat vs . .venv/bin/activate.

rgilton commented 8 months ago

Here's the script that I am currently using as a stand-in for uv shell. Currently specific to bash, but that fits my use-case. Making it work on other platforms/shells looks reasonably tractable as demonstrated in pipenv.

Quick demo of it in operation:

[rob@zem foo-project(main)]$ uv venv
Using Python 3.12.1 interpreter at /usr/bin/python3
Creating virtualenv at: .venv
Activate with: source .venv/bin/activate
[rob@zem foo-project(main)]$ cd src
[rob@zem src(main)]$ uvs
Entering virtualenv /home/rob/tmp/foo-project/.venv
(foo-project) [rob@zem src(main)]$ 
(foo-project) [rob@zem src(main)]$ uvs
Error: Already within virtualenv
(foo-project) [rob@zem src(main)]$ 
exit
[rob@zem src(main)]$ 
tiagofassoni commented 7 months ago

What about if, instead of hooking up pty's, we just do the bare minimum? We detect the shell (whether zsh, bash, nushell or powershell), the OS, and just call the source file.

Although I'm afraid @mitsuhiko thought of this and decided not to do it, so perhaps it just isn't feasible.

zanieb commented 7 months ago

Unfortunately you can't source things for someone from another process.

rgilton commented 7 months ago

I think there are two separate feature requests that are being combined in this issue:

  1. The ability to spawn a new shell as a new process with the venv activated within it. This is what pipenv shell, and poetry shell do.
  2. The ability to activate the venv within the current shell. As running source .venv/bin/activate would do.

I think the talk of injecting keypresses into a shell via a pty is about the second item, whereas the original request in this issue was about the first. My preference is for the first, as it is straightforward to 'exit' the spawned shell without exiting the terminal emulator/window, whereas if the current shell is modified then it is not easy to 'unmodify' it.

samypr100 commented 7 months ago

I think the first option somewhat reflects what pixi does, https://github.com/prefix-dev/pixi/blob/main/src/cli/shell.rs

I gave their impl a quick whirl on uv to test, and seems to works pretty well

ps_venv_shell
patrick-kidger commented 7 months ago

FWIW I've always considered poetry shell a misfeature from a UX perspective. It's too easy to drop into a shell, then change directory, and lose track of which venv you're actually working in.

So for my team I prefer to recommend always using poetry run (often plus some shell aliases to minimise typing). I think that would make better sense as a first-class citizen for uv. And then if someone still really really wants a shell then this is still accomplishable via uv run [bash|fish|etc.].

zefr0x commented 6 months ago

Unfortunately you can't source things for someone from another process.

What about providing a wrapper shell script to do the sourcing part?

Just like virtualfish, but simple and using uv.

We have two options:

  1. uv will manage every thing from creating to deleting venvs in a global directory. But for the sourcing part the shell script will do the task.
  2. The script will be a wrapper for all related venv management tasks using uv behind-the-scenes. (best, since we will not need to toggle between multiple commands for venv related tasks)
lmanc commented 6 months ago

I don't know if this is relevant here, but sometimes I have difficulties understanding which venv I'm actually using and what's happening under the hood (and why). I'm not sure if there's already a way to figure out these things and I'm just missing it.

Consider this scenario: I have a project in VSCode without a venv. I open the terminal and run uv pip list, which outputs:

Package    Version
---------- -------
pip        23.0.1
setuptools 65.5.0

This output reflects my clean, system environment.

Next, I run uv venv. According to the documentation:

By default, `uv` lists packages in the currently activated virtual environment, or a virtual environment (`.venv`) located in the current working directory or any parent directory, falling back to the system Python if no virtual environment is found.

So, I'm expecting uv to pick the local .venv without activation, and it does, even if the venv is not activated.

Now, if I close VSCode and reopen it, VSCode continues with its "implicit" activation of .venv in every terminal I open from now on.

If, for some reason, I now delete .venv and run uv pip list, I get this error:

$ uv pip list
error: failed to canonicalize path `<path>`
  Caused by: The system cannot find the file specified. (os error 2)

While I expected the same output (the system environment) as before.

I think it's VSCode's fault because I guess it somehow implicitly runs something like source .venv/bin/activate if I open it with a venv already created. However, since the venv is now activated, I think this "overrides" uv's standard venv detection flow when none is activated.

So my point is: it would be nice to have some mechanism inside of uv to check which venv it is currently selecting and why (e.g., "explicit activation of <path>", "implicit use since no other venvs are activated and there's a .venv in the current directory", "implicit system environment use because no venv is activated and no .venv in the current directory," etc.).

zanieb commented 6 months ago

Hi @lmanc — thanks for the write up!

I'm actually adding tracking of all of these sources at #3266 so I think we'll have better logging of that in the near future.

NikosAlexandris commented 5 months ago

Just a kind reminder of the existence of https://github.com/direnv/direnv. It would be actually nice to learn from it and explore integration with it ?

zefr0x commented 3 months ago

Unfortunately you can't source things for someone from another process.

What about providing a wrapper shell script to do the sourcing part?

Just like virtualfish, but simple and using uv.

We have two options:

  1. uv will manage every thing from creating to deleting venvs in a global directory. But for the sourcing part the shell script will do the task.

  2. The script will be a wrapper for all related venv management tasks using uv behind-the-scenes. (best, since we will not need to toggle between multiple commands for venv related tasks)

I wrote a simple function for the Fish shell: https://github.com/zefr0x/dotfiles/blob/f3f20a4ab91c198674211c76ad431ef8260cfe10/utils/fish/functions/vf.fish

It does store all venvs in a global dir, and provides some commands to manage them.

It address the problem for my workflow, and can be used as a reference to implement a general solution that gets packaged with uv.

mitsuhiko commented 3 months ago

I think it's worth looking at this from the lense of what the world might look like in two years. A lot of the reasons people want to "activate" virtualenvs is that this is how we used to do things, not necessarily how we would do things in a slightly changed world.

Even with rye today the need to actually activate a virtualenv is largely gone as the python executable always does the right thing, and rye run does too. So it primarily are things created in a world that assumed that virtualenvs are for activating that need this.

npm got away without any equivalent of "activation" and I think a future Python ecosystem will also no longer find much use in virtualenv activation.

anddam commented 3 months ago

What about providing a wrapper shell script to do the sourcing part?

I just threw this in my ~/.bashrc (MSYS2 on win)

uv () {
  [ -n "$2" ] && d="$2" || d=".venv"
  [ "$1" = "venv" ] && [ -d "$d" ] \
    && source "./${d}/Scripts/activate" \
    || command uv "$@"
}

this mimics the same venv function I have had for years for creating/listing/activating venvs.

Albeit feeling the need for a quicker way to activate the env while using uv I agree with the sentiment that it's not the called process role to alter the parent's environment.

chrisrodrigue commented 3 months ago

It's tricky enough that other projects like pdm have avoided adding this feature. Too many broken corner cases.

See the green note here: Looking for pdm shell?

thernstig commented 2 months ago

Just a kind reminder of the existence of https://github.com/direnv/direnv. It would be actually nice to learn from it and explore integration with it ?

There is a thread about integration that might go official sometime, see https://github.com/direnv/direnv/issues/1250.

pkucmus commented 2 months ago

uv shell (I do like it in Poetry) or not, I would like to have a uv venv --activate or even uv activate something that would set the project's Python interpreter for my current shell.

tanloong commented 2 months ago

And it would be great if uv activate could activate the environment from any subfolder, not just the root. Here is my function to do the subfolder activation in fish:

function activate
    set --function cwd (pwd)
    set --function home (dirname (realpath $HOME))
    set --function venv_path ""
    # Recursive search upward for .venv directory
    while test -n $cwd -a $home != $cwd
        if test -e $cwd/.venv
            set --function venv_path (realpath $cwd/.venv)
            break
        end
        set --function cwd (dirname $cwd)
    end
    if test -n $venv_path
        if test -d $venv_path
            source $venv_path/bin/activate.fish
        else
            echo "Found .venv at $venv_path, but it is not a valid directory"
        end
    else
        echo "Could not find .venv directory"
    end
end
wlnirvana commented 1 month ago

Instead of activating the environment, I personally find it helpful instead to

  1. either uv run <script> to run a script directly
  2. or uv run python to start the repl backed by the virtual environment
seapagan commented 1 month ago

I'll be honest, I originally saw the lack of a 'shell' command as a deal-breaker since I am so used to Poetry. When I stepped back for a sec I realise I can get by with a couple of shell aliases (Linux or Mac):

# uv aliases
alias va='source .venv/bin/activate'
alias vd='deactivate'

This is from my zsh config but all shells offer similar. Windows users can use a Doskey or powershell alias.

Now, I just enter my project folder and type va and ready to go, actually faster that the Poetry equivalent.

I've got my GitHub actions working again, and changed to Renovate from Dependabot so I think I'm ready to go full on uv :grin:

EDIT: direnv now has a working uv script I've just tried and it works perfectly: https://github.com/direnv/direnv/wiki/Python#uv

luxedo commented 1 month ago

(Grabs 🍿)

I like uv shell concept because of consistency. No need to run anything else other than uv tooling. Newcomers won't even have to worry about what is a venv.

Although I like very much using uv run, uv pip, ... I think that uv shell could make it easier for them to migrate to uv .

MrHamel commented 1 month ago

What I do to get around this for now, is use the python plugin in oh-my-zsh to load/unload the environment based on the directory I'm in, by setting this in my .zshrc file:

plugins=(... python)

export PYTHON_VENV_NAME=".venv"
export PYTHON_AUTO_VRUN=true
seapagan commented 1 month ago

What I do to get around this for now, is use the python plugin in oh-my-zsh to load/unload the environment based on the directory I'm in, by setting this in my .zshrc file:

plugins=(... python)

export PYTHON_VENV_NAME=".venv"
export PYTHON_AUTO_VRUN=true

OMG, I really need to RTFM more often, i've been using omz for years :rofl: - that works just prefectly and utterly transparent :+1:

Doesn't seem to interfere with folders where i'm using the direnv method too.

richin13 commented 1 month ago

I've had this in my .zshrc for ages and never have to worry about activating a virtualenv

export DEFAULT_PYTHON_VENV_DIR=.venv

function _py_venv_activation_hook() {
  local ret=$?

  if [ -n "$VIRTUAL_ENV" ]; then
    source $DEFAULT_PYTHON_VENV_DIR/bin/activate 2>/dev/null || deactivate || true
  else
    source $DEFAULT_PYTHON_VENV_DIR/bin/activate 2>/dev/null || true
  fi

  return $ret
}

typeset -g -a precmd_functions
if [[ -z $precmd_functions[(r)_py_venv_activation_hook] ]]; then
  precmd_functions=(_py_venv_activation_hook $precmd_functions);
fi
ultrabear commented 3 weeks ago

Just wanted to leave my two cents, as I see this is a more complicated issue than I first thought (started learning uv, missed poetry shell, yknow the deal)

fish has a flag to run a command before starting interactivity (-C) which I will use going forwards, hope that helps anyone who is (like me) too conservative to use a direnv type functionality:

function uvenv
    fish -C "source .venv/bin/activate.fish"
end
Ravencentric commented 1 week ago

Poetry's poetry shell has been brought up several times in this thread but poetry 2.0 has removed poetry shell in https://github.com/python-poetry/poetry/pull/9763 and replaced it with a command that simply prints the activate command (similar to pdm's solution).

cosmincatalin commented 6 days ago

Not sure why they did this, the shell command is easy, intuitive and does the expected job.

thernstig commented 6 days ago

Note that poetry shell still exists here https://github.com/python-poetry/poetry-plugin-shell.

Ravencentric commented 6 days ago

Not sure why they did this, the shell command is easy, intuitive and does the expected job.

The issues with poetry shell are extensively discussed in:

https://github.com/python-poetry/poetry/issues/9136 https://github.com/python-poetry/poetry-plugin-shell/issues/18

dpprdan commented 1 day ago

It's been mentioned before, but seriously, wouldn't an implementation similar to pdm's be a good compromise here? It has a platform independent activation command, allows for a custom uv shell command if one prefers, and a uv venv activate would fit nicely in the uv pip/venv interface.