I have adopted uv for a lot of Python development. I'm also a heavy user of direnv, which I like as a tool for setting up project-specific environments.

Much like Hynek describes, I've found uv sync to be fast enough to put into the chdir path for new directories. Here's how I'm doing it.

Direnv Libraries

First, it turns out you can pretty easily define custom direnv functions like the built-in ones (layout python, etc...). You do this by adding functions to ~/.config/direnv/direnvrc or in ~/.config/direnv/lib/ as shell scripts. I use this extensively to make my .envrc files easier to maintain and smaller. Now that I'm using uv here is my default for python:

function use_standard-python() {
    source_up_if_exists

    dotenv_if_exists

    source_env_if_exists .envrc.local

    use venv

    uv sync
}

What does that even mean?

Let me explain each of these commands and why they are there:

  • source_up_if_exists -- this direnv stdlib function is here because I often group my projects into directories with common configuration. For example, when working on Chicon 8, I had a top level .envrc that set up the AWS configuration to support deploying Wellington and the Chicon 8 website. This searches up til it finds a .envrc in a higher directory, and uses that. source_up is the noisier, less-adaptable sibling.

  • dotenv_if_exists -- this loads .env from the current working directory. 12-factor apps often have environment-driven configuration, and docker compose uses them relatively seamlessly as well. Doing this makes it easier to run commands from my shell that behave like my development environment.

  • source_env_if_exists .envrc.local -- sometimes you need more complex functionality in a project than just environment variables. Having this here lets me use .envrc.local for that. This comes after .env because sometimes you want to change those values.

  • use venv -- this is a function that activates the project .venv (creating it if needed); I'm old and set in my ways, and I prefer . .venv/bin/activate.fish in my shell to the more newfangled "prefix it with a runner" mode.

  • uv sync -- this is a super fast, "install my development and main dependencies" command. This was way, way too slow with pip, pip-tools, poetry, pdm, or hatch, but with uv, I don't mind having this in my .envrc

A sidebar on use venv

use venv is a custom direnv function I have as well, and mine is a bit complicated because it evolved as I was choosing a way to set up virtualenvs. At the present, though, it can be written very simply:

function use_venv() {
    uv venv
    source .venv/bin/activate
}

Using it in a sentence

With this set up in direnv's configuration, all I need in my .envrc file is this:

use standard-python

I've been using this pattern for a while now; it lets me upgrade how I do default Python setups, with project specific settings, easily.

Feedback

You can reply to this post via Mastodon:

Waiting to load comments

Reply to @offby1's post

With an account on the Fediverse or Mastodon, you can respond to this post. Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.

Copy and paste this URL into the search field of your favourite Fediverse app or the web interface of your Mastodon server.