zurück / back

Running Python scripts using uv

Yet another package manager?

Uv is a rather new "Python package and project manager". There are other tools that tried to improve Python packaging, but I usually don't care much about them. They solve problems that do not affect me too much in my daily life. If in doubt, I prefer to go with the built-in stuff instead of adding another tool that has its own complexity.

Uv aims to be the all-in-one solution for all your Python project management and packaging problems. But there is one specific scenario where it really shines. Let me show it to you!

Small scripts with dependencies

Basic Python skills can get you a long way in solving day-to-day data processing tasks due to its rich ecosystem of available libraries. You need to scale, crop,... a bunch of images in a tree of subfolders? Pillow and less than 50 lines of code will probably do the job.

For me, such scripts usually end up having a few command-line options and 100 or 200 lines of code, but everything still fits in a single file, and making it a "real" project with a pyproject.toml feels like overkill. Uv has a nice option to manage and run such scripts.

uv init --script

With uv you can manage dependencies within your script. To get started, you can run

uv init --script my_script.py --python 3.13

The resulting my_script.py should look like this:

# /// script
# requires-python = ">=3.13"
# dependencies = []
# ///


def main() -> None:
    print("Hello from my_script.py!")


if __name__ == "__main__":
    main()

Without dependencies, there is nothing to see yet, so let's add Click as a dependency and adapt the script accordingly:

# /// script
# requires-python = ">=3.13"
# dependencies = ["click"]
# ///

import click


@click.group()
def cli():
    pass


@cli.command()
def doit():
    print("Hello from doit!")


if __name__ == "__main__":
    cli()

Now run the script via

uv run --script my_script.py

You should see this output:

Installed 1 package in 3ms
Usage: my_script.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  doit

As you can see, one package was automatically installed. And Click works as expected. To double-check run

uv run --script my_script.py doit

You should see:

Hello from doit!

No package was installed this time because it already happened during the first execution. So nothing to do this time. That's nice, but the command is obviously too long. Let's apply some default "Unix magic":

Remove the .py extension from the script and make it executable via chmod a+x my_script. Next, add the following two lines to the very top of the script:

#!/usr/bin/env -S uv run --script
# vim: syntax=python
#
# /// script
[...]

The first line tells your shell how to run this script. The second one tells NeoVim which syntax highlighting to use for this file. If you are not using the best editor in the world, you will have to adapt accordingly. That's it! Now you can run your script like this:

./my_script doit

No need to install it, and additional dependencies will be installed automatically if they are missing.

Impressum