astral-sh / uv

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

Support including dependency executables (e.g., `--include-deps`) in `uv tool install` #6314

Open hynek opened 3 months ago

hynek commented 3 months ago

hi, congrats on 0.3.0!

I've noticed one very useful pipx option that uv tool is currently missing: --install-deps. It means that it also adds the CLIs of packages it depends on. This is crucial for something like Ansible where the essential commands are hidden in sub-packages.

ZingyAwesome commented 3 months ago

It would be great if this could allow multiple dependencies to be installed in the same venv and for all of their executables to be exposed. More info in this rye feature request. This would allow me to install ansible,ansible-core and ansible-lint in the same venv, saving disk space and negating version mismatch issues.

zanieb commented 3 months ago

I've been hesitant to provide a flag to install all executables from all dependencies — what about --executable <name> to include executables from dependencies?

mitsuhiko commented 3 months ago

The way it works in rye is that --include-dep names the particular dependency and installs then the tools that that particular dependency provides so it's not quite as crazy as installing all.

ZingyAwesome commented 3 months ago

Yeah that was what I meant, as long as you can specify multiple dependencies that way. My main issue with rye was that it allowed you to only specify 1 dependency to include executables from (or at least I couldn't get it to work with more than 1).

mitsuhiko commented 3 months ago

I thought --include-dep can be provided more than once, but maybe there is a bug.

hynek commented 3 months ago

I could perfectly live with uv tool install ansible --include-dep=ansible-playbook --include-dep=ansible --include-dep=ansible-inventory

Or however you call it; there's already the --with prefix used, so that might make sense too.

jamesharris-garmin commented 2 months ago

This for ansible's install workflow is the last piece of me replacing pipx with uv tool so supporting this would be excellent.

lucab commented 2 months ago

I had a look at all the linked issues and also asked some feedback out of band; I sketched some code to implement this, and I want to recap the proposed behavior. I'm not interested in debating the specific flag name right now, so let me call it --i-want-ponies for the moment.

Using ansible as an example, I'll highlight these three packages:

uv can offer the following:

As a side note, at some point this may end up bringing in too many executables (e.g. from fat packages like ansible-core), so possibly there will be a need to grow an additional flag to specify an allow-list of globs to filter executables by name. I'll leave that for a separate ticket.

underyx commented 2 months ago

For the record as a workaround you can do this today

$ uv tool install ansible-core --with ansible

because ansible-core exposes the binaries, and the ansible package will provide all the plugins.

jamesharris-garmin commented 2 months ago

This is a nice fix for the simple ansible case, but if my goal is to use ansible-lint I think I would have to install the tools as follows:

$ uv tool install ansible-lint --with ansible

In one invocation, then in a separate tool install:

$ uv tool install ansible-core --with ansible

which would mean that ansible-lint and the rest of ansible could get out of sync, which seems like it would be a nightmare if the two tool environments ever fell out of sync.

meoyawn commented 2 months ago

same, plus I use community.aws.s3_cors module and currently forced to have 2 separate envs:

uv tool install ansible-core --with ansible --with boto3
uv tool install ansible-lint --with ansible --with boto3

in any case thank you so much for uv it's the GOAT

jamesharris-garmin commented 2 months ago

@lucab This formulation sounds reasonable I would leave it up to developers of their tools to provide additional instructions. on how to install properly so that the user experience is correct. I could really use this approach to eliminate sync issues.

KalleDK commented 1 month ago

Came here for the same problem with ansible-lint, that now has it's own environment. I also have some smaller projects with the same problem.

gaby commented 1 month ago

While the workarounds work. It's very painful to have to go through every package we are installing to figure out which deps it has to then add a massive list of flags for each one.

This should be a simple flag "--include-deps" like pipx does.

I don't to install lets say "app A" , and then spend 1hr debugging why apps are missing cause I didnt include a bunch of extra "--include-deps" for each one.

reinout commented 1 month ago

@gaby: out of curiosity, how many packages do you have with that problem? Of the 15 I'm installing, I only have the missing-executable-problem with ansible, to my surprise. What I'm installing is mostly individual tools like "ruff", "docutils", which apparently don't have dependencies with executables.

If you have a "tool-collection-of-my-company" package which groups useful tools as dependencies: yeah, that'll quickly become a pain to install currently.

(I'm all in favour of --include-deps, btw.)

zanieb commented 1 month ago

I don't to install lets say "app A" , and then spend 1hr debugging why apps are missing cause I didnt include a bunch of extra "--include-deps" for each one.

It seems way to easy to pull in a bunch of random executables this way? It sounds like what you need is some sort of uv tool show <name> --include-deps so you can just see the executables up front.

gaby commented 1 month ago

@reinout Yes, besides ansible, pylint, ruff, uv, jc, gitlab (which has sdk/cli), the main use case is internal enterprise tools. Most of them are Typer or Textual apps that are bundled/distributed together in one or many wheels.

@zanieb You mean like before installing? Or if a tool is installed already to be able to see which apps it provides?

Right now we install things using pipx:

pipx install --global MyEnterpriseApp --include-deps. This install the app in a Global VENV (/opt/pipx/venvs/myenterpriseapp) and also adds each app cli to /usr/local/bin/.

These are multi-user systems were most users dont have sudo, they just use the provided tools by the system (python typer apps).

flying-sheep commented 1 month ago

maybe instead of (or as an alternative to) --include-deps, there should be

  1. an easy way to see all executables provided by the deps
  2. a CLI option to include executables from specific deps

E.g. I’d like to install jupyter, jupyter-lab, jupyter-execute and so on, all into one environment.

zanieb commented 1 month ago

We can also do... like uv tool install <package> --executable <name> --executable <name> and we can tell you what packages provide them or just allow installation from dependencies automatically at that point.

flying-sheep commented 1 month ago

That would be more guessable, and would allow for advanced things like like --executable 'jupyter-*'

I’d still also like (a flag for) uv tool list to show all the binaries that aren’t currently active.

stuartmaxwell commented 4 days ago

It seems way to easy to pull in a bunch of random executables this way? It sounds like what you need is some sort of uv tool show <name> --include-deps so you can just see the executables up front.

Could you explain the risk you are concerned about? With pipx if you use the --include-deps argument, you are specifically asking to install the executables from dependencies, which I wouldn't describe as installing "random executables".

Perhaps including a --dry-run option would help to ease concerns? e.g. uv tool install --include-deps --dry-run ansible. You already have the dry-run feature available in: uv pip install --dry-run. This could be extended to show the executables installed by each package when called with uv tool install.

zanieb commented 4 days ago

Could you explain the risk you are concerned about? With pipx if you use the --include-deps argument, you are specifically asking to install the executables from dependencies, which I wouldn't describe as installing "random executables".

Perhaps random was not a great word choice. There can be arbitrary transitive dependencies. It worry about it (1) being surprising which executables are installed and (2) that these executables could conflict with other tools.

A --dry-run option is a solution. I prefer a more explicit interface over --include-deps though.

stuartmaxwell commented 4 days ago

Perhaps random was not a great word choice. There can be arbitrary transitive dependencies. It worry about it (1) being surprising which executables are installed and (2) that these executables could conflict with other tools.

I think I still disagree with the term "arbitrary". If I use the --include-deps argument, then I'm specifically making an informed decision that I want all of the dependent executables installed too, there's nothing arbitrary about it. Sure, I might be surprised that Ansible includes the "ansible-doc" executable, which I've never used or needed, but I'm fine it being there in case I ever want to use it.

With regards to conflicting executables - isn't this only an issue when the executables are linked from the bin directory? And uv already handles the case where an executable already exists in the bin directory.

mgedmin commented 1 day ago

Let's talk specifics. With pipx I like to install ansible and various ansible-related tools (ansible-lint, molecule) in the same virtualenv, which I do with

pipx install --incude-deps ansible
pipx inject ansible dnspython  # some of ansible plugins I use depends on dnspython
pipx inject --include-apps ansible ansible-lint
pipx inject --include-apps ansible molecule

(incidentally, I'd love it if uv tool supported inject. pipx remembers all the packages I've injected in a little .json file somewhere in the virtualenv and can reinstall them all at a later time with pipx reinstall-all, which is very helpful when I upgrade my system python from, say 3.12 to 3.13).

The thing here is that pipx inject has three modes:

If I'd used pipx inject --include-deps ansible-lint, then I'd get an extra, unwanted ~/.local/bin/black, since black is one of the dependencies for ansible-lint. I don't want my generic Python code formatter to be managed as part of an ansible-related tool virtualenv; I want to install and upgrade it separately and explicitly.

I guess this is the concern that @zanieb had: installing scripts from all transitive dependencies is sometimes undesireable. I found that pipx's solution (a separate pipx inject command) allows me to control this in a fine-grained way, and I'd be happy if uv adopted the same solution (uv tool inject [--include-apps|--include-deps] ...).

SVHawk13 commented 3 hours ago

uv tool install ansible installs ansible-lint, ansible-playbook, etc. but only symlinks ansible-community. The below one-liner creates these symlinks:

[ -d "$(uv tool dir)/ansible/bin/" ] && find "$(uv tool dir)/ansible/bin/" -mindepth 1 -maxdepth 1 -type f -executable -regextype posix-extended -regex '^((.+/)?)[^.]+' -print0 | xargs -0 ln -s -t "${HOME}/.local/bin/"
AndydeCleyre commented 3 hours ago

For whatever it's worth as another point of reference, my pipx clone in Zsh which wraps uv optionally takes a --cmd argument whose value is a comma separated list of commands to install. If that's not provided, and there's not an obvious single command from the whole installation (including dependencies), an interactive fuzzy finder in muli-select mode is invoked.