Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

official support for uv #1250

Open
dbrtly opened this issue Mar 10, 2024 · 20 comments · May be fixed by #1329
Open

official support for uv #1250

dbrtly opened this issue Mar 10, 2024 · 20 comments · May be fixed by #1329
Labels

Comments

@dbrtly
Copy link

dbrtly commented Mar 10, 2024

Is your feature request related to a problem? Please describe.
I'd like direnv to layout python using astral-sh/uv in an elegant way.

Describe the solution you'd like

While this works, it breaks the prompt somehow.
By default, without using direnv, uv uses the name of the base directory for the venv prompt.


realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
layout_python-uv() {
    local python=${1:-python3}
    [[ $# -gt 0 ]] && shift
    unset PYTHONHOME
    if [[ -n $VIRTUAL_ENV ]]; then
        VIRTUAL_ENV=$(realpath "${VIRTUAL_ENV}")
    else
        local python_version
        python_version=$("$python" -c "import platform; print(platform.python_version())")
        if [[ -z $python_version ]]; then
            log_error "Could not detect Python version"
            return 1
        fi
        VIRTUAL_ENV=$PWD/.direnv/python-venv-$python_version
    fi
    export VIRTUAL_ENV
    if [[ ! -d $VIRTUAL_ENV ]]; then
        log_status "no venv found; creating $VIRTUAL_ENV"
        uv venv "$VIRTUAL_ENV" 
    fi

    PATH="${VIRTUAL_ENV}/bin:${PATH}"
    export PATH
    source "${VIRTUAL_ENV}/bin/activate"
}

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

@dbrtly dbrtly added the Feature label Mar 10, 2024
@inklesspen
Copy link

I've been using this in my .config/direnv/lib/python_uv.sh for a while now; it seems to work well:

layout_python_uv() {
  local python=${1:-python}
  [[ $# -gt 0 ]] && shift
  unset PYTHONHOME
  local python_version=$($python -V | cut -w -f 2 | cut -d . -f 1-2)
  if [[ -z $python_version ]]; then
      log_error "Could not find python's version"
      return 1
  fi

  if [[ -n "${VIRTUAL_ENV:-}" ]]; then
      local REPLY
      realpath.absolute "$VIRTUAL_ENV"
      VIRTUAL_ENV=$REPLY
  else
      VIRTUAL_ENV=$(direnv_layout_dir)/python-$python_version
  fi
  if [[ ! -d $VIRTUAL_ENV ]]; then
      uv venv -p $python "$@" "$VIRTUAL_ENV"
  fi
  export VIRTUAL_ENV
  PATH_add "$VIRTUAL_ENV/bin"
}

@pjz
Copy link

pjz commented Sep 7, 2024

Couple of changes to run on linux:

layout_python_uv() {
  local python=${1:-python}
  [[ $# -gt 0 ]] && shift
  unset PYTHONHOME
  local python_path=$(uv python find $python)
  local python_version=$($python_path -V | cut -d' ' -f 2 | cut -d . -f 1-2)
  if [[ -z $python_version ]]; then
      log_error "Could not find python's version"
      return 1
  fi

  if [[ -n "${VIRTUAL_ENV:-}" ]]; then
      local REPLY
      realpath.absolute "$VIRTUAL_ENV"
      VIRTUAL_ENV=$REPLY
  else
      VIRTUAL_ENV=$(direnv_layout_dir)/python-$python_version
  fi
  export UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV
  if [[ ! -d $VIRTUAL_ENV ]]; then
      uv venv -p $python "$@" "$VIRTUAL_ENV"
  fi
  export VIRTUAL_ENV
  PATH_add "$VIRTUAL_ENV/bin"
}
  1. I dunno what version of cut supports -w, but mine doesn't. Replaced with -d' '.
  2. make a python_path tempvar so the version specified doesn't have to be on the $PATH as long as uv python knows where to find it.
  3. export UV_PROJECT_ENVIRONMENT so that running uv alone with use the correct venv.

@zimbatm
Copy link
Member

zimbatm commented Sep 7, 2024

Would be good to add it to the wiki.

If you believe the integration to be stable, sending a PR to add it to the stdlib would be fine as well.

@thernstig
Copy link
Contributor

It has been added to the wiki by @doolio and @sephib and @consoull it seems.

(Though https://github.com/direnv/direnv/wiki was not updated to add it to the list there).

But would be great to add this to stdlib as I think it is quite obvious uv will take over more and more workflows.

@blackary blackary linked a pull request Oct 2, 2024 that will close this issue
@dpprdan
Copy link

dpprdan commented Nov 11, 2024

I am wondering if we all share a common understanding of what an official support for uv should look like?

Here are some things that (IMHO) might be worth considering. These points were mostly triggered by looking at #1329, but it might just make sense to think about this more broadly and define some more concrete requirements for this issue?

  1. uv [init/add/sync/...] vs. uv [pip/venv]

    The uv docs state that the uv pip interface is "intended to be used in legacy workflows or cases where the high-level commands do not provide enough control." For the direnv stdlib this boils down to the "legacy" workflows, I guess, because if you need more control you will probably need more control over direnv as well.

    It also follows that the uv's project interface is the road ahead.

    For direnv this means that the project interface should be the standard and the pip interface the special case, e.g. layout uv and layout uv-pip.

  2. I wonder if it is really useful / necessary to build in uv init into direnv. uv init is only invoked once at the start of a project, so this only saves typing one command when initialising a project at the cost of adding complexity to direnv. Also, AFAICT project aren't initialised by direnv for any other language? Finally, it is fine if this is part of a template (i.e. https://github.com/direnv/direnv/wiki/Python), which every user can modify as they see fit, but building it into stdlib is a different cup of tea.

  3. uv uses Python version files for specifying which Python version to use when creating the project's virtual environment. Defining the python version in .envrc as well isn't DRY then (and the one in .envrc shouldn't be able to override the one in .python-version IMO). I think this is mostly an issue if one wants direnv to initialise the project (see 2.), so one more reason against that, I suppose?

  4. Instead, it might be more useful to add uv sync (and likewise uv pip sync for the uv pip workflow) to ensure that alle project dependencies are installed and up-to-date?

  5. uv provides the uv run command to execute a command or script in the project environment. So with uv, activating a venv is optional. In fact the uv developers "don't activate virtual environments anymore (in favor of uv run) and at least contemplate that virtualenv activation won't find much use in a future Python ecosystem.

    I am sure that not everyone here will agree with the latter, but maybe virtualenv activation should be optional in direnv's uv workflow, e.g. a use venv function?

I guess what I am advocating for here is letting uv do the heavy lifting of managing projects, venvs and python versions and to not override it's defaults unless absolutely necessary. In fact IMHO a uv sync and a (for projects optional) use/activate venv might just suffice. Anyway, feel free to disagree!

@duckpuppy
Copy link

At least for the time being, activating the virtual environment also allows IDEs and other tools to see the dependencies included in the virtual environment. I use NeoVim with Ruff and without an activated venv ruff won't see the dependencies.

@dpprdan
Copy link

dpprdan commented Nov 11, 2024

@duckpuppy I agree that activating the virtual environment should be a feature, albeit possibly an optional one.
(Have you tried uv run ruff check or similar?)

@inklesspen
Copy link

I still use and prefer the uv pip commands. I am uncomfortable with the idea of letting one tool (however rapid its development and adoption) take over the Python ecosystem.

Also there are a variety of situations (using meson-python to build packages, for example) where activating the virtualenv is necessary.

@inklesspen
Copy link

(see mesonbuild/meson-python#630 for more on that)

@thernstig
Copy link
Contributor

thernstig commented Nov 12, 2024

Activating the virtual environment is to me the most important point that direnv should do. All the others are optional.

I don't think it should sync automatically. I do think it should install the Python version specified automatically if it is not installed, or rather warn about it. Please note that often the version can only be specified in pyproject.toml, not necessitating a .python-version. I'd consider using a pyproject.toml the more modern approach.

Ergo: It seems we have very different goals of what a default direnv implementation should do. To me activating the virtual environment seems the most reasonable one.

@serl
Copy link

serl commented Nov 12, 2024

As a just-ordinary-user of Direnv and uv, here's where I settled on (after watching Hynek on YouTube, of course), when in project mode:

uv sync
source .venv/bin/activate

I agree that's a little too blocking on first run, but it's working fine for me for now.

A couple of notes after some fiddling:

  • Python version is taken from .python-version or pyproject.toml (in project.requires-python). If that changes (and it's not compatible anymore), the venv is destroyed and recreated
  • Python version is downloaded if missing
  • We can start with export UV_PROJECT_ENVIRONMENT="$(direnv_layout_dir)/python" to have the virtual env under .direnv/python

So for project with lockfile I don't feel the need for a specific direnv support.

That said some wrapping of uv venv might be necessary, when no uv.lock is present (and it's not planned), and one would like to use uv to download the right Python version and create the venv.

@inklesspen
Copy link

for the record i do not ever ask uv to download some random python binary. i use pyenv's python-build script (which i have installed as a standalone binary), so my .envrc usually looks something like this (with the function definition given here):

layout python_uv /opt/snakes/python-3.12.7/bin/python

or of course sometimes more like this:

layout python_uv /opt/snakes/python-3.12.7/bin/python
load_prefix /opt/homebrew/opt/libressl
load_prefix /opt/homebrew/opt/postgresql@16
export POSTGRES_MAJOR_VERSION=`pg_config --version | perl -wnl -e '/\d+(?=\.)/ and print $&;'`

# Place the data directory inside the project directory
export PGDATA="$(pwd)/postgres-$POSTGRES_MAJOR_VERSION"
# Place Postgres' Unix socket inside the data directory
export PGHOST="$PGDATA"

if [[ ! -d "$PGDATA" ]]; then
	# If the data directory doesn't exist, create an empty one, and...
	initdb
	# ...configure it to listen only on the Unix socket, and...
	cat >> "$PGDATA/postgresql.conf" <<-EOF
		listen_addresses = ''
		unix_socket_directories = '$PGHOST'
	EOF
	# ...create a database using the name Postgres defaults to.
	echo "CREATE DATABASE $USER;" | postgres --single -E postgres
fi

export SQLALCHEMY_URL="postgresql://$USER:@/myproject"
export SQLALCHEMY_WARN_20=1

@offbyone
Copy link

While I would consider using a stdlib uv integration, in its absence I've developed one of my own that suits me well.

If uv support is going in the direnv stdlib, I think it should be set up as much as possible to work with the tool and not with a legacy function that is likely to be less well supported over time. Direnv is a stable tool, and the stdlib should look to the future, not the past.

uv sync is, even on first run, ridiculously fast; I have it run every time I chdir, to no performance cost. What I think it isn't, though, is a "layout"; there's nothing direnv should be managing about where its files go.

@martsa1
Copy link

martsa1 commented Nov 13, 2024

I feel it would be unwise to default to installing python versions via direnv - perhaps such behaviour could be an opt-in preference for those users/teams that want it? I can imagine plenty of users like @inklesspen that will use arbitrary, explicit python binaries by design.

@offbyone
Copy link

I'd like to also add that there's no need for a special uv layout; it can create a virtualenv as part of the standard layout python functionality -- my original local library for it did that instead.

A uv stdlib addition should be a uv project layout, not a uv venv layout.

@offbyone
Copy link

I've proposed a more project-centric integration via #1352.

@dbrtly
Copy link
Author

dbrtly commented Nov 15, 2024

Uv has evolved significantly in 8 months.

uv sync works for me.

Fwiw, I use uv run by default without activating the virtual environment.

@duckpuppy
Copy link

uv run works as long as you're not using any code analysis tools like linters or LSP servers that need to have access to all the dependencies. For those right now we need to have an active venv.

@offbyone
Copy link

That's why #1352 also activates the venv.

@offbyone
Copy link

Can I get a sense from the direnv team what the decision criteria is on these PRs? I'd like to know how much more time I should throw at it, and who's gonna be the decider for if it gets merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging a pull request may close this issue.

10 participants