astral-sh / uv

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

`--directory` should resolve relative paths relative to current working directory #5613

Closed charliermarsh closed 1 month ago

bluss commented 3 months ago

Uv run's external command is opaque (in meaning), so it's not possible to translate those arguments. One part of the solution for uv run could be either to never change current directory, or set cwd back to the original for the child process.

charliermarsh commented 3 months ago

I was thinking that we’d remove the current set_directory call, and then pass around a project directory as dictated by the directory flag.

abdok96 commented 1 month ago

Do you have any timeline for this change by any chance? I'm currently using the hidden --directory option but it would be great if it can be made public (and ideally add an environment variable for it). Also resolving the paths relative to the passed directory is causing very weird issues in different places. I think this change is the correct choice.

One example where paths relative to the current directory are necessary is when running uv commands from pre-commit local hooks, paths of the files staged for commit are automatically passed to the hook entry (uv command in this case) and the user cannot control this behavior.

Thanks for uv it is looking great!

zanieb commented 1 month ago

per https://github.com/astral-sh/uv/issues/6733#issuecomment-2315983710 our existing behavior matches the Git and Cargo CLIs so we're very unlikely to change it.

The pre-commit example is great though. How do people work around this in other tools? Why do you need to use --directory in this situation? Maybe we need an environment variable to toggle.

abdok96 commented 1 month ago

Thanks for the quick reply. In our case, the simplified project structure was briefly something like:

my-project/ | app/ | | main.py | | pyproject.toml
|
docker/ | docs/ | bin/ | docker-compose.yml | .pre-commit.config.yaml

We also have a local hook for mypy something like:

  - repo: local
    hooks:
      - id: mypy
        name: run mypy check
        language: system
        entry: uv run --directory app -- mypy
        types_or: [python, pyi]
        require_serial: true
        args:
          - --config-file=pyproject.toml

We want to run the mypy command from the virtual environment that has all the python dependencies so that mypy can resolve and follow all the imports when running the type checks.

Because of the behavior of --directory we are passing --config-file as pyproject.toml instead of app/pyproject.toml but that's okay since we can control it.

However, when the commit hook is triggered by pre-commit, we have less control. Let's say app/main.py was changed, the command executed by pre-commit will be something like: uv run --directory app -- mypy --config-file=pyproject.toml app/main.py

And since the paths are resolved relative to the --directory param, mypy won't find a module matching app/app/main.py.

In our case, we managed to get around it by moving the pyproject.toml to the root directory which is probably a more suitable place. However, there might be similar cases that are a bit harder to avoid.

abdok96 commented 1 month ago

Btw, I'm not sure if it helps, but we are in the process of migrating from poetry to uv. I can confirm that in the case of poetry, the behavior is not similar to cargo or git. It resolves paths based on the working directory.

For example, this is the same hook I mentioned in my previous comment, before the migration to uv:

  - repo: local
    hooks:
      - id: mypy
        name: run mypy check
        language: system
        entry: poetry --directory app run mypy
        types_or: [python, pyi]
        require_serial: true
        args:
          - --config-file=app/pyproject.toml

Notice how despite the --directory param, we used to pass the --config-file relative to the working directory not /app. I guess both options have good arguments. A toggle like you suggested might help.

bluss commented 1 month ago

@zanieb Unlikely to implement this issue? What do you think about adding a separate flag to uv run with the behaviour intended by this issue?

bluss commented 1 month ago

If we again look at git it has git -C x --git-dir y and maybe this could be a good model.

Uv can have one flag that changes directory, like -C and one flag that points out which project or pyproject.toml to discover and use as starting point. For convenience, this second project flag should also take a directory value.

Of course in the normal case these are linked and can be used for similar tasks.

aivarannamaa commented 1 month ago

Expanding on the comment from bluss -- What about adding --project, which doesn't change the current directory, but simply indicates where to pick up the environment (or its specification)?

aivarannamaa commented 1 month ago

One more idea: if --project references a script with inline metadata, this script would be used as the spec for the enironment to use.

For example:

I would use both of these forms for adding uv mode to Thonny IDE.

charliermarsh commented 1 month ago

I am open to a dedicated --project flag on the uv run, uv sync, uv lock, etc. to inform workspace discovery. \cc @zanieb

dragly commented 1 month ago

We are also very interested in a --project argument where the paths can remain relative to the current working directory. Our use-case is a monorepo where we are unable to use the workspace feature of uv due to conflicting dependencies between projects. This is the exact use of workspaces that is discouraged in the uv docs today.

(Another solution for us would be if uv had a feature where one could define a workspace without a shared uv.lock, but I think the --project argument sounds like a general solution that could solve more issues overall).

bluss commented 1 month ago

When I take a first look at this, I think --project in uv run --project maybe needs to be a global argument as well? (Or run-specific but handled early.) Because this logic is global (happens before any subcommand) and it is about project/workspace discovery. By the simplest logic: currently it's assumed project root is CWD (or found from CWD), the updated code needs to start with the user provided project directory instead of CWD here.

https://github.com/astral-sh/uv/blob/e36cc99b0d17e38be2c6fb09f02eb7489536176c/crates/uv/src/lib.rs#L103-L129

zanieb commented 1 month ago

I'm also willing to have --project — though Charlie said it'd be a pain to add support :)

charliermarsh commented 1 month ago

I think project is pretty straightforward since it’s limited in scope as to what it affects. I assume it affects project discovery and maybe python-version?

charliermarsh commented 1 month ago

I'll do it.

zanieb commented 1 month ago

Epic. Sounds good to me. The python-version file stuff might be easier with https://github.com/astral-sh/uv/pull/6370

dragly commented 3 weeks ago

Thanks for implementing this! It works perfectly for our needs :)

charliermarsh commented 3 weeks ago

That's great to hear :)