things you can do --with uv
What is this post about
In this post, I’ll dive into a powerful feature of uv: the --with
option.
I’ll explain how it works, share practical examples, and show how it solved a key challenge in my latest tool, ayu.
But first of all…
How is uv doing
Since my last post about uv, almost half a year has passed and the all-in-one python version, package, project and tool manager by astral has become a staple in many Python developers’ workflows..
The uv GitHub repo has surpassed 60,000 stars, and the latest 0.7.19 release (Release Notes)
introduces uv’s own stable build backend.
In a future release, this will replace hatchling as the default build backend when initializing projects with uv init
.
The --with
option
About
When running a Python script that depends on external packages, those packages must be installed in the script’s environment.
The recommended approach is to declare dependencies explicitly, either in a project’s pyproject.toml
or
using inline script metadata as defined in PEP 723. Here’s an example:
# /// script
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
Lets say you put the above example in a script.py
file. When you remove the inline script metadata at the beginning, you will
be faced with the following error message when running uv run script.py
.
ModuleNotFoundError: No module named 'requests'
This was to be expected, because we did not install the requests
or rich
dependencies to run the script.
uv supports installing those required dependencies also when invoking the script, if you do not declare them in the metadata part.
This is where the --with
option comes into play.
Usage
If you run the script.py
file now with the following command instead:
uv run --with requests,rich script.py
You will see that uv installs both dependencies on the fly for the invocation of that script. And since uv caches those, consecutive invocations run almost instantly.
What else to do with --with
If you want to start a python REPL with some dependencies in your current project runtime to test something you can easily do so for example with:
uv run --with pandas python
When executing this, pandas
will not be added to your project environment, as stated in the docs.
When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.
Another use case might be if you want to start a jupyter server with access to your projects environment, you can do that with:
uv run --with jupyter jupyter lab
For a more detailed descriptions on how to work with uv and jupyter take a look in the docs section.
The problem it solved for me
The last months I have been working on ayu, which is a command-line tool to make pytest more interactive
and currently looks like this:
The ayu TUI displaying a test tree built from pytest data.
It is a TUI written with textual, which starts a websocket server in the background to collect data from pytest. To get the data from pytest to the websocket server, ayu is at the same time a pytest plugin and utilizes pytest-hooks to send different data based on pytest hooks invoked.
For example the test tree you see in the above picture is built by invoking pytest with --collect-only
when the app is started.
My challenge was ensuring ayu remained independent of the project’s environment while functioning as a pytest plugin.
I wanted users to run it directly (e.g., uvx ayu
or uv tool install ayu
) without needing to install it in their project.
However, as a pytest plugin, ayu must be in the project’s environment to send data to the websocket server.
This is where the --with
option from uv comes to rescue.
I am now able to execute pytest commands from the application, which would use your projects environment, but also install ayu
itself on the fly to send the data from pytest to the TUI via the websocket.
That means, in the background ayu is executing for example
uv run --with ayu pytest --collect-only
to get the test tree and (after some processing) send it to the TUI. When executing tests the following command is invoked:
uv run --with ayu pytest
This runs your normal test suite and gives live updates by showing an icon behind the test node.
Some other features of ayu are also to be able to quickly mark tests and run a specific subset of your test suite. With the latest release, I added a filewatcher using watchfiles, which (if toggled) automatically re-runs all tests in a single file, if the file is updated.
Building ayu the way I wanted it to be, would have not been possible without the amazing features uv provides.
Thanks to uv python developers can now do things which were not possible before and I am exited to see, how astral is continuing to improve the developer experience of many Pythonistas.