Technical note
uv: modernizing your Python project

It has now been a year that I have been working with uv, the package manager that revolutionizes the way we use Python.
What is uv?
uv is an open-source Python package manager written in Rust by the company Astral.
uv aims to be a performant, easy-to-learn and portable tool.
In this article I will share three frequent use cases from my work with uv.
Creating a project with a specific Python version
uv is an executable that you install on your computer and that is independent of Python, unlike pip. This portable side of uv is very practical: it lets you create a Python project while specifying the language version.
Here is an example:
uv init --python 3.11 .
uv generates a project structure that looks like this:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 26/04/2025 21:45 5 .python-version
-a---- 26/04/2025 21:45 84 hello.py
-a---- 26/04/2025 21:45 152 pyproject.toml
-a---- 26/04/2025 21:45 0 README.md
By default it generates a hello.py file containing a test Python script to run. You can of course delete it.
Running the program with uv run
uvdemo> uv run .\hello.py
Using Python 3.11.10
Creating virtual environment at: .venv
Hello from uvdemo!
When I run the project with the command uv run .\hello.py, the Python language version I specified is automatically downloaded to run my code.
I find this rather extraordinary: specifying the Python version I want in my project, without having to install it manually, is really cool.
In the file tree generated by uv init, you can see a .python-version file containing a string corresponding to the Python version.
There is also a pyproject.toml file that lets you specify the dependencies. This file is not specific to uv; the poetry package manager uses it as well.
Managing my code dependencies properly
For the demo, suppose I want to create a CLI tool in Python that depends on version 0.7.0 of fire. With this simple command line I add fire to my project.
uv add fire==0.7.0
Behind the scenes uv did things for me so that the version of fire I use stays the same if I want to rebuild the project later, or if another developer wants to run my code.
The pyproject.toml file was updated to include the dependencies:
[project]
name = "uvdemo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fire==0.7.0",
]
More interestingly, a uv.lock file was added to the project tree to hold the metadata needed to install this specific version of fire. This guarantees that exactly this version of fire, as well as the exact versions of the Python packages that fire==0.7.0 depends on, will be installed when I run my project.
Sharing a script with all its dependencies
This feature gives Python back all its glory as a scripting language. The name of this feature: inline script metadata.
Here is an example of a script that uses the textual package to display the current time in your console.
# /// script
# dependencies = [
# "textual",
# ]
# ///
"""
An App to show the current time.
"""
from datetime import datetime
from textual.app import App, ComposeResult
from textual.widgets import Digits
class ClockApp(App):
CSS = """
Screen { align: center middle; }
Digits { width: auto; }
"""
def compose(self) -> ComposeResult:
yield Digits("")
def on_ready(self) -> None:
self.update_clock()
self.set_interval(1, self.update_clock)
def update_clock(self) -> None:
clock = datetime.now().time()
self.query_one(Digits).update(f"{clock:%T}")
if __name__ == "__main__":
app = ClockApp()
app.run()
uvdemo> uv run .\textual_script.py
Reading inline script metadata from: .\textual_script.py
Installed 10 packages in 435ms
This feature is really convenient when you share scripts. The user of your script does not have to install all the dependencies before running your project. Everything is specified in the script itself, and uv takes care of the rest.