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
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.
You can reply to this post via Mastodon:
Waiting to load comments