UV Single-File Scripts: Solving Python Dependency Portability

How UV single-file scripts changed my Python workflow and simplified AI-assisted development

I was setting up Claude Code hooks recently and ran into the usual Python dependency mess again: Create a virtual environment, activate it, install packages, cross your fingers that nothing breaks.

The Problem: Dependency Management Friction

Here’s what my hook scripts looked like before:

1
2
3
4
5
6
7
# user_prompt_submit.py
import json
import sys
import requests  # Hope this is installed
from dotenv import load_dotenv  # And this too

# Rest of script...
1
2
3
4
5
# Every. Single. Time.
python -m venv hook_env
source hook_env/bin/activate
pip install requests python-dotenv
python user_prompt_submit.py

This works, but it’s painful. Scripts aren’t portable. New machines need setup. Colleagues can’t just run your code. It’s 2025 and we’re still doing this.

The Discovery: PEP 723 and UV

While researching UV (Ultra-fast Python package installer), I found something interesting. Python now has an official standard for inline script metadata: PEP 723. And UV implements it.

Here’s the same hook script with inline dependencies:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /// script
# requires-python = ">=3.11"
# dependencies = [
#     "requests>=2.28.0",
#     "python-dotenv",
# ]
# ///

import json
import sys
import requests
from dotenv import load_dotenv

# Rest of script...

The magic? I can run this directly:

1
uv run user_prompt_submit.py

UV reads the inline metadata, creates a temporary virtual environment, installs dependencies, and executes the script. The first run takes a few hundred milliseconds. Subsequent runs are blazing fast thanks to UV’s cache.

The AI Connection: Claude Already Knows This

Here’s where it gets interesting. I discovered that Claude 4 already understands PEP 723 syntax. I can ask:

“Write a Python script with inline dependencies that fetches GitHub repo stats”

And Claude generates working code with proper inline dependencies. I save it and run uv run script.py - it just works. No environment setup, no dependency installation, no conflicts.

Performance in Practice

I tested this with a GitHub API script that fetches repo data and formats it with rich tables:

1
2
3
4
5
6
7
# First run (installs dependencies)
time uv run github_stats.py
# real    0m0.847s

# Second run (uses cache)  
time uv run github_stats.py
# real    0m0.063s

The cache makes a huge difference. UV is written in Rust and is fast - often 10-100x faster than pip.

Teaching AI to Use UV

While researching UV workflows, I discovered an interesting technique from Armin Ronacher (Flask creator) for training AI agents to use UV instead of system Python. The idea is to create interceptor scripts that give helpful error messages:

Create Interceptor Scripts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Create directory for interceptors
mkdir -p ~/.local/bin/interceptors

# Create dummy python executable
cat > ~/.local/bin/interceptors/python << 'EOF'
#!/bin/bash
echo "Direct python usage detected!"
echo "Use 'uv run python' instead for proper environment management."
echo ""
echo "For scripts with inline dependencies:"
echo "  uv run script.py"
echo ""
echo "For interactive Python:"
echo "  uv run python"
exit 1
EOF

chmod +x ~/.local/bin/interceptors/python

Setup with direnv

1
2
3
# In your project root, create .envrc
echo 'PATH_add ~/.local/bin/interceptors' > .envrc
direnv allow

Now when AI agents try to run python script.py, they get a helpful error message explaining to use uv run instead. This trains them to use UV automatically in your projects.

A Note on AI-Generated Code

When using AI-generated scripts, I always review the code before running (always review AI code before running):

  1. Check dependencies - Are they legitimate packages?
  2. Review file operations - What files is it accessing?
  3. Examine network calls - Where is it connecting?
  4. Validate inputs - How does it handle user data?

UV makes it easy to see exactly what dependencies a script needs upfront.

Troubleshooting Tips

Python Version Conflicts

1
2
3
4
5
6
7
# Check available Python versions
uv python list

# Use specific version
# /// script
# requires-python = "==3.11"
# ///

Dependency Issues

1
2
3
4
5
# Debug dependency resolution
uv run --verbose script.py

# Clear cache if needed
uv cache clean

Wrapping Up

UV single-file scripts solve the Python dependency portability problem. It is one less thing to worry about. :)

This eliminates the traditional environment management overhead while keeping dependency precision - particularly valuable when working with AI tools where you want to iterate quickly.

uname -a: Human 5.4.0-anxiety #1 SMP Coffee-Deprived x86_64 GNU/Paranoid
Built with Hugo
Theme Stack designed by Jimmy