uv icon indicating copy to clipboard operation
uv copied to clipboard

Add a command to activate the virtual environment, e.g., `uv shell`

Open twiddles opened this issue 1 year ago • 74 comments
trafficstars

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.

twiddles avatar Feb 23 '24 12:02 twiddles

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! .

mgaitan avatar Feb 24 '24 03:02 mgaitan

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.

rgilton avatar Feb 24 '24 10:02 rgilton

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 right now.

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}"
}

jfcherng avatar Feb 25 '24 20:02 jfcherng

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 :(

mitsuhiko avatar Feb 26 '24 07:02 mitsuhiko

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?)

rgilton avatar Feb 26 '24 21:02 rgilton

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.

NMertsch avatar Feb 27 '24 06:02 NMertsch

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)]$ 

rgilton avatar Feb 28 '24 10:02 rgilton

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.

tiagofassoni avatar Apr 08 '24 04:04 tiagofassoni

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

zanieb avatar Apr 08 '24 04:04 zanieb

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.

rgilton avatar Apr 08 '24 09:04 rgilton

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

samypr100 avatar Apr 10 '24 04:04 samypr100

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.].

patrick-kidger avatar Apr 17 '24 08:04 patrick-kidger

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)

zefr0x avatar May 16 '24 21:05 zefr0x

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.).

lmanc avatar May 18 '24 10:05 lmanc

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.

zanieb avatar May 18 '24 13:05 zanieb

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 ?

NikosAlexandris avatar Jun 11 '24 08:06 NikosAlexandris

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.

zefr0x avatar Jul 29 '24 14:07 zefr0x

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.

mitsuhiko avatar Jul 29 '24 16:07 mitsuhiko

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.

anddam avatar Aug 06 '24 20:08 anddam

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?

chrisrodrigue avatar Aug 08 '24 21:08 chrisrodrigue