42 comments

[ 4.8 ms ] story [ 61.0 ms ] thread
This is my absolute favourite uv features and the reason I switched to uv.

I have a bunch of scripts in my git-hooks which have dependencies which I don't want in my main venv.

#!/usr/bin/env -S uv run --script --python 3.13

This single feature meant that I could use the dependencies without making its own venv, but just include "brew install uv" as instructions to the devs.

The "declaring script dependencies" thing is incredibly useful: https://docs.astral.sh/uv/guides/scripts/#declaring-script-d...

  # /// script
  # dependencies = [
  #   "requests<3",
  #   "rich",
  # ]
  # ///
  import requests, rich
  # ... script goes here
Save that as script.py and you can use "uv run script.py" to run it with the specified dependencies, magically installed into a temporary virtual environment without you having to think about them at all.

It's an implementation of Python PEP 723: https://peps.python.org/pep-0723/

Claude 4 actually knows about this trick, which means you can ask it to write you a Python script "with inline script dependencies" and it will do the right thing, e.g. https://claude.ai/share/1217b467-d273-40d0-9699-f6a38113f045 - the prompt there was:

  Write a Python script with inline script
  dependencies that uses httpx and click to
  download a large file and show a progress bar
Prior to Claude 4 I had a custom Claude project that included special instructions on how to do this, but that's not necessary any more: https://simonwillison.net/2024/Dec/19/one-shot-python-tools/
> without you having to think about them at all.

Hahahahaha.

Oh. I'm rolling on the floor. Hahahahaha.

How do you never learn? No, honestly, how do you never learn this simple thing: it will break! I will bet my pension on that it will break, and perhaps not you, but some hundreds of developers will have to try to debug and try to figure out where the dependencies went and why they weren't installed correctly, or why something was missing and so on.

There will never be a situation that you don't have to think about something as important as dependencies at all.

I'm not a Python dev, but had to write a script the other day and got all cought up with the virtual env stuff. Why can't `uv` just infer the dependencies from the `import ...` line? Why declare the dependencies twice?
Most _current_ LLMs know about PEP 723, but you have to say "PEP 723" for them to do it properly.
Why doesn't pip support PEP 723? I'm all for spreading the love of our lord and savior uv, but it should be necessary to have an official implementation.
It's completely out of scope for pip, which is purely about modifying the set of packages installed into non-transient environments.

Pipx is a wrapper that does more or less what you're looking for (including PEP 723 support), but it arbitrarily refuses to process top-level packages unless they specify an entry point (which makes them "applications" even with abstract dependencies).

I'm planning to support it in PAPER, which can roughly be described as my vision of what pip and pipx, taken together, should have been.

Oh this looks amazing! I had pretty much stopped using Python for my one-off scripts because of the hassle of dependencies. I can't wait to try this out.
Oh nice, I was already a happy user of the uv-specific shebang with in-script dependencies, but the `uv lock --script example.py` command to create a lock file that is specific to one script takes it to another level! Amazing how this feels so natural and yet only appeared after 20+ years of Python packaging.
Note that this only works for single-file scripts.

If you have a project with modules, and you'd like a module to declare its dependencies, this won't work. uv will only get those dependencies declared in the invoked file.

For a multi-file project, you must have a `pyproject.toml`, see https://docs.astral.sh/uv/guides/projects/#managing-dependen...

In both cases, the script/project writer can use `uv add <dependency>`, just in the single-file case they must add `--script`.

Quick plug here for a simple Jupyter kernel I created:

https://github.com/tobinjones/uvkernel

It’s a pretty minimal wrapper around “uv” and “iPython” to provide the functionality from the article, but for Jupyter notebooks. It’s similar to other projects, but I think my implementation is the least intrusive and a good “citizen” of the Jupyter ecosystem.

There’s also this work-in-progress:

https://github.com/tobinjones/pep723widget

Which provides a companion Jupyter plugin to manage the embedded script dependencies of noteboooks with a UI. Warning — this one is partially vibe-coded and very early days.

How many package managers can one language have? Its a simple language but setting it up is just incredibly bad. Maybe this is the one or should I wait for the next?
I hate such poor docs that don't explain how things work, and instead prefer hiding behind some "magic".

> Constraints can be added to the requested dependency if specific versions are needed:

> uv run --with 'rich>12,<13' example.py

Why not mention that this will make uv download specified versions of specified packages somewhere on the disk. Where? Are those packages going to get cached somewhere? Or will it re-download those same packages again and again every time you run this command?

This is pretty great. Passing python code out to my students is usually also confronted with the question of "How do I run it?", which is usually terrible to answer. Now, I can just tell them to get uv (single command) and run it.
My favorite part of this is the `exclude-newer` feature, giving you a somewhat reproducible script for very low effort.
I just looked this up yesterday by sheer coincidence and was happy it actually worked. What brought it to your attention today?
One gotcha I caught myself in with this technique is using it in a script that would remediate a situation where my home has lost internet and needed the router to be power cycled. When the internet is out, `uv` cannot download the dependencies specified in the script, and the script would fail. Thankfully I noticed this problem after writing it but before needing it to actually work, and refactored my setup to pre-install the needed dependencies. But don't make the same mistake I almost made! Don't use this for code that may need to run airgapped! Even with uv caching you may still get a cache miss.
Love this feature of UV. Here's a one-liner to launch jupyter notebook without even "installing" it:

  uv run --with jupyter jupyter notebook
Everything is put into a temporary virtual environment that's cleaned up afterwards. Best thing is that if you run it from a project it will pick up those dependencies as well.
Heard about this! Finally did something with it and I'm happy with how this tiny gist turned out:

https://gist.github.com/pythoninthegrass/e5b0e23041fe3352666...

tl;dr

Installs 3 deps into the uv cache, then does the following:

1. httpx to call get request from github api

2. sh (library) to run ls command for current directory

3. python-decouple to read an .env file or env var

Uh oh. I am thinking all the ways this can be misused to ship malicious dependencies. Pretty much all SCA tools today will be blind to this.
I wish there was a straightforward way to let VS Code pick up the venv that uv transparently creates.

Out of the box, the Python extension redlines all the third-party imports.

As a workaround, I have to plunge into the guts of uv's Cache directory to tell VS Code the cached venv path manually, and cross fingers that it won't recreate that venv too often.

I recently found a small issue with `uv run`. If you run a script from outside the project folder, it looks for the pyproject.toml on the folder from which you are calling `uv run`, not on the folder where the python script is located (or its parents)! Because of that scripts that store their dependencies in a pyproject.toml cannot be run successfully using a “bare” `uv run path/to/my/script.py` from outside the project folder.

You can work around this surprising behavior by always using inline dependencies, or by using the `--project` argument but this requires that you type the script path twice which is pretty inconvenient.

Other than that uv is awesome, but this small quirk is quite annoying.

There are a lot of bits and pieces that are clicking into place lately in the Python ecosystem. Recently I've been using the combination of Marimo and these uv script dependencies for building reproducible rwporting and diagnostic tooling for other teams.
I like it, and I once thought about this as well:

> Python doesn't require a requirements.txt file, but if you don't maintain this file, or neglect to provide one, it could result in broken functionalities - an unfortunate circumstance for a scripting language.

https://twitter.com/_damnever/status/1697247813854503250

For this use case I would try first to compile the script with nuitka. A single binary is more manageable than doing that