uv icon indicating copy to clipboard operation
uv copied to clipboard

Add strict command mode to `uv run`

Open zanieb opened this issue 10 months ago • 5 comments

This would only allow you to run a command provided by a package in the current environment.

Maybe we'd want to use a new flag --strict or re-use the --isolated flag.

zanieb avatar Apr 17 '24 16:04 zanieb

We could also enable this by default and consider a relaxed mode to allow targets outside the requirements.

Poetry's cool with running something else:

❯ poetry run echo test
test

zanieb avatar Apr 17 '24 22:04 zanieb

If you'll permit me to, I would like to jump in here with a suggestion.

I would like to think of poetry run as meaning "please act exactly like the shell I am used to, but in addition with the Python venv activated."

First of all, I think this is by far the most intuitive CLI experience. Second, it would also be consistent with the user experience ofsource .venv/bin/activate

Unfortunately, poetry doesn't do this: it doesn't use my current shell (either the login $SHELL or the one that I am currently using), and it necessarily also doesn't offer any of the aliases I have set up! (Super inconvenient.)

For this reason I actually have my own CLI tool to fix this. Extracting the relevant code, it basically looks like this:

def run(commands: list[str]):  # e.g. running `mytool run foo bar` on the CLI will produce `commands = ["foo", "bar"]`
    shell_path = os.environ.get("SHELL", "/bin/sh")
    shell_name = pathlib.Path(shell_path).stem
    command, *args = commands
    if shell_name in {"sh", "bash", "zsh"}:
        # This `&& echo` is quite a subtle point.
        #
        # bash and zsh have some optimizations (and different optimizations to each other!) in which if they detect that
        # you're running something sufficiently simple, then they will skip creating a subshell and directly execute the
        # provided command. (Here's a reference: https://stackoverflow.com/a/56620122)
        #
        # In practice the speed difference is going to be small, and this optimization sometimes makes our `bash`
        # instance below (created in `subprocess.run`) visible to the end user. That is, their preferred shell might
        # *not* be the PPID of the command they run, if that shell optimizes itself out!
        #
        # We would prefer that this not happen: things occuring inside `mytool run` should feel the same as if you were
        # on your existing command line.
        command = command + ' "$@" && echo'
    elif shell_name == "fish":
        command = command + " $argv"
    else:
        raise Exception(f"Unsupported shell {shell_name} at {shell_path}")
    process = subprocess.run(
        [
            "bash",
            "-c",
            'source "$1" && shift && "$@"',
            "--",
            f"{_get_venv_path()}/bin/activate",
            shell_path,
            "-c",
            command,
            "--",
            *args,
        ],
        cwd=pathlib.Path.cwd(),
        check=False,
    )
    sys.exit(process.returncode)

I expect that probably still takes a bit of work to get portable across Windows, non-{zsh,bash,fish} shells, etc, but you see the essence of it.

This is now much more useful. For example, this means that I can now easily start my $EDITOR inside the correct venv, which is what is needed for the LSP to operate correctly.

Do you think it would make sense for uv run to operate under this model instead?

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

@patrick-kidger That aligns with your statement here https://github.com/astral-sh/uv/issues/1910#issuecomment-2060719717. What I'm not entirely sure if it simplies implementation to do this on uv run or uv shell either way. One can argue uv shell is an alias to uv run $SHELL from this perspective and you'd end up spawning a new shell, and having to do the same level of plumbing uv shell would need.

samypr100 avatar Apr 24 '24 01:04 samypr100

Very interesting, looks hard . We might want to track that separately from this issue as it's a very different idea. I could see a flag to enable / disable that behavior?

zanieb avatar Apr 24 '24 13:04 zanieb

I'm glad it seems interesting!

Which bit would be tricky -- the handling of shells that aren't on the blessed list?

IIUC this is already a limitation of uv when activateing -- c.f. the need for a separate activate.fish -- so I don't think this would impose any new restriction on compatibility.

patrick-kidger avatar Apr 24 '24 18:04 patrick-kidger

I am expecting uv run to work more like conda run, just as what @patrick-kidger describes:

I would like to think of poetry run as meaning "please act exactly like the shell I am used to, but in addition with the Python venv activated."

First of all, I think this is by far the most intuitive CLI experience. Second, it would also be consistent with the user experience ofsource .venv/bin/activate

The ability to use aliases does not matter much to me.

Currently, uv run forcibly requires pyproject.toml to exist:

$ uv run ls
warning: `uv run` is experimental and may change without warning.
error: No `pyproject.toml` found in current directory or any parent directory

Where I have a .venv folder in the same directory, and uv pip install just works. It is very counter-intuitive that a pyproject.toml is required to run uv run: I want to run command as in the specified venv, why do I need a project for it?

E.g. A virtual env may be created for a collection of user-wide terminal cli binaries, and I want to use uv run in the folder containing .venv to manage in the venv. Here the concept of project is naturally absent.

Vigilans avatar May 25 '24 10:05 Vigilans

It is very counter-intuitive that a pyproject.toml is required to run uv run: I want to run command as in the specified venv, why do I need a project for it?

Not speaking to what we'll eventually ship here, but just to be clear, uv run isn't intended to be used with uv pip install. It's part of a totally distinct set of APIs that are actively being worked on, and those APIs are all oriented around the concept of a project and workspace. In that context, it is a lot more intuitive that a project is required -- none of the APIs make sense outside of a project.

The initial thinking behind this issue was that we might want to provide something that's separate from that project API, to let users run a command in an existing virtual environment without activating it. Conceptually, that could be thought of as uv venv run or something, though I'm not actually proposing that as the API.

charliermarsh avatar May 25 '24 12:05 charliermarsh

To be clear, I do actually think we should have some form of uv run that works with the uv pip APIs (I don't know whether it's a flag, or a separate command, or just ignoring the project if there's no pyproject.toml), but uv run itself in its current form is not intended to be used alongside the uv pip APIs, which is where that confusion is coming from.

charliermarsh avatar May 25 '24 12:05 charliermarsh

In that context, it is a lot more intuitive that a project is required -- none of the APIs make sense outside of a project.

So, now we have following venv managers:

  • conda: conda run
  • pyenv: pyenv exec
  • nvm (nodejs): nvm run / nvm exec
  • goenv (golang): goenv exec

They're all capable of:

  • Install specified version of python/nodejs/go
  • Have first-class run/exec that runs a command in specified environment without requirement for a project

And here we have poetry and uv where they:

  • Can only work with a pre-existing installation of python
  • Requires a project to use run command

For people who thought uv to be a venv manager analog to conda, pyenv, nvm and goenv, now they should actually regard uv as alternative for poetry as project manager and shall not expect a first-class run command that works like all above venv managers?

Vigilans avatar May 25 '24 13:05 Vigilans

For people who thought uv to be a venv manager analog to conda, pyenv, nvm and goenv, now they should actually regard uv as alternative for poetry as project manager and shall not expect a first-class run command that works like all above venv managers?

No, this isn't quite the right framing. We plan to support both workflows -- that's why we shipped uv venv and uv pip. uv will be capable of installing specified versions of Python, and we'll probably also add a first-class run or exec command that works with an existing environment without managing it. You'll have all the tools you need to install Python, manage Python versions, create virtual environments, manipulate them, etc., just as you would with virtualenv, pip, pyenv, etc. You can and will be able to use uv as an alternative to those other tools you mentioned.

Separately, though, we want to ship higher-level APIs (at a similar level of abstraction to Poetry) with a more opinionated interface, for users and projects that don't want to operate at that low-level of environment management. The existing uv run was implemented as part of that API. So if we want a first-class run or exec command to execute in an environment without a project (i.e., as an alternative to source .venv/bin/activate), that's totally fine, I'm just explaining that the existing uv run (which is genuinely work-in-progress) was not intended for that use-case.

charliermarsh avatar May 25 '24 13:05 charliermarsh

For now though I'll take a look at enabling uv run to work outside of an environment. I think it used to do that and I changed it.

charliermarsh avatar May 25 '24 13:05 charliermarsh

You'll have all the tools you need to install Python, manage Python versions, create virtual environments, manipulate them, etc., just as you would with virtualenv, pip, pyenv, etc. You can and will be able to use uv as an alternative to those other tools you mentioned.

That is what I initially thought uv would be and why opt-in it. By comparing the behavior of run in conda and poetry, I was trying to model the user's expectation towards a manager, because uv run is a top-level command. Unlike uv venv run, the behavior of uv run may impose a stronger implication of what this tool focuses.

Personally I think uv run can work in both scope (project or venv).

  • It can accept a venv folder to run in a specified environment.
  • When executing without venv, it may keep the current behavior to search for a python project and works the project way (--strict or isolated can apply).
  • Or furthermore, if no pyproject.toml found but .venv folder exists in current directory, use it as specified venv, just like uv pip does.

Vigilans avatar May 25 '24 13:05 Vigilans

I appreciate the conversation, but could you describe the behavior you're looking for in a separate issue please? This issue is supposed to be focused quite narrowly on a uv run mode in which commands that are not in the environment are not allowed.

For what it's worth: I think you're suggesting things that we're already planning on doing (and have done previously), but, as Charlie mentioned, uv run is in the middle of being built and is changing all the time as we explore the best way to implement things.

zanieb avatar May 25 '24 14:05 zanieb