Python Single-File Scripts with uv
Expert guidance for creating production-ready, self-contained Python scripts using uv's inline dependency management (PEP 723).
Quick Start
Create Your First uv Script
#!/usr/bin/env -S uv run --script
/// script
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27.0",
"rich>=13.0.0",
]
///
import httpx from rich import print
response = httpx.get("https://api.github.com") print(f"[green]Status: {response.status_code}[/green]")
Make it executable:
chmod +x script.py ./script.py # uv automatically installs dependencies
Convert Existing Script
Add inline metadata to existing script
./tools/convert_to_uv.py existing_script.py
Validate PEP 723 metadata
./tools/validate_script.py script.py
Core Concepts
What is PEP 723?
PEP 723 defines inline script metadata for Python files:
/// script
requires-python = ">=3.11"
dependencies = [
"package>=1.0.0",
]
///
Benefits:
-
✅ Dependencies live with the code
-
✅ No separate requirements.txt
-
✅ Reproducible execution
-
✅ Version constraints included
-
✅ Self-documenting
uv Script Execution Modes
Mode 1: Inline Dependencies (Recommended for utilities)
#!/usr/bin/env -S uv run --script
/// script
dependencies = ["httpx"]
///
Mode 2: Project Mode (For larger scripts)
uv run script.py # Uses pyproject.toml
Mode 3: No Dependencies
#!/usr/bin/env -S uv run
Standard library only
Critical Anti-Patterns: What NOT to Do
❌ NEVER Use [tool.uv.metadata]
WRONG - This will cause errors:
/// script
requires-python = ">=3.11"
[tool.uv.metadata] # ❌ THIS DOES NOT WORK
purpose = "testing"
///
Error:
error: TOML parse error at line 3, column 7
unknown field metadata
Why: [tool.uv.metadata] is not part of PEP 723 and is not supported by uv.
CORRECT - Use Python docstrings for metadata:
/// script
requires-python = ">=3.11"
dependencies = []
///
""" Purpose: Testing automation Team: DevOps Author: team@example.com """
Valid tool.uv fields (if needed):
/// script
requires-python = ">=3.11"
dependencies = []
[tool.uv]
exclude-newer = "2025-01-01T00:00:00Z" # For reproducibility
///
Real-World Examples from This Repository
Example 1: Cluster Health Checker
See examples/03-production-ready/check_cluster_health_enhanced.py
Current version (basic):
#!/usr/bin/env python3 import subprocess
Manual dependency installation required
Enhanced with uv (production-ready):
#!/usr/bin/env -S uv run --script --quiet
/// script
requires-python = ">=3.11"
dependencies = [
"rich>=13.0.0",
"typer>=0.9.0",
]
///
""" Purpose: Cluster health monitoring Team: Infrastructure """
import typer from rich.console import Console from rich.table import Table
Example 2: CEPH Health Monitor
See examples/03-production-ready/ceph_health.py
Pattern: JSON API interaction with structured output
Best Practices from This Repository
- Security Patterns
See reference/security-patterns.md for complete security guide including:
-
Secrets management (environment variables, keyring, Infisical)
-
Input validation
-
Dependency security
-
File operations security
-
Command execution security
- Version Pinning Strategy
Following this repository's approach (from pyproject.toml ):
/// script
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27.0", # Minimum version for compatibility
"rich>=13.0.0", # Known good version
"ansible>=11.1.0", # Match project requirements
]
///
Pinning levels:
-
=X.Y.Z
-
Minimum version (most flexible)
-
~=X.Y.Z
-
Compatible release (patch updates only)
-
==X.Y.Z
-
Exact version (most strict)
See reference/dependency-management.md.
- Team Standards
File naming:
check_cluster_health.py # ✅ Descriptive, snake_case validate_template.py # ✅ Action-oriented cluster.py # ❌ Too generic
Shebang pattern:
#!/usr/bin/env -S uv run --script --quiet
--quiet suppresses uv's own output
Documentation template:
#!/usr/bin/env -S uv run --script
/// script
requires-python = ">=3.11"
dependencies = []
///
""" Check Proxmox cluster health
Purpose: cluster-monitoring Team: infrastructure Author: devops@spaceships.work
Usage: python check_cluster_health.py [--node NODE] [--json]
Examples: python check_cluster_health.py --node foxtrot python check_cluster_health.py --json """
- Error Handling Patterns
Following Ansible best practices from this repository:
import sys import subprocess
def run_command(cmd: str) -> str: """Execute command with proper error handling""" try: result = subprocess.run( cmd.split(), capture_output=True, text=True, check=True ) return result.stdout except subprocess.CalledProcessError as e: print(f"Error: Command failed: {cmd}", file=sys.stderr) print(f" {e.stderr}", file=sys.stderr) sys.exit(1) except FileNotFoundError: print(f"Error: Command not found: {cmd.split()[0]}", file=sys.stderr) sys.exit(1)
See patterns/error-handling.md.
- Testing Patterns
Inline testing (for simple scripts):
#!/usr/bin/env -S uv run --script
/// script
requires-python = ">=3.11"
dependencies = []
///
def validate_ip(ip: str) -> bool: """Validate IP address format""" import re pattern = r'^(\d{1,3}.){3}\d{1,3}$' return bool(re.match(pattern, ip))
Inline tests
if name == "main": import sys
# Run tests if --test flag provided
if len(sys.argv) > 1 and sys.argv[1] == "--test":
assert validate_ip("192.168.1.1") == True
assert validate_ip("256.1.1.1") == False
print("All tests passed!")
sys.exit(0)
# Normal execution
print(validate_ip("192.168.3.5"))
See workflows/testing-strategies.md.
When NOT to Use Single-File Scripts
See anti-patterns/when-not-to-use.md for details.
Use a proper project instead when:
-
❌ Script exceeds 500 lines
-
❌ Multiple modules/files needed
-
❌ Complex configuration management
-
❌ Requires packaging/distribution
-
❌ Shared library code across multiple scripts
-
❌ Web applications or long-running services
Example - Too Complex for Single File:
This should be a uv project, not a script:
- 15+ dependencies
- Database models
- API routes
- Background workers
- Configuration management
- Multiple environments
Common Patterns
See pattern guides for complete examples:
-
CLI Applications - Typer, Click, argparse patterns
-
API Clients - httpx, requests, authentication
-
Data Processing - Polars, pandas, analysis
-
System Automation - psutil, subprocess, system admin
CI/CD Integration
GitHub Actions
name: Run Health Checks
on: schedule: - cron: '0 */6 * * *' # Every 6 hours
jobs: health-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Check cluster health
run: |
uv run --script tools/check_cluster_health.py --json
env:
PROXMOX_TOKEN: ${{ secrets.PROXMOX_TOKEN }}
GitLab CI
cluster-health: image: ghcr.io/astral-sh/uv:python3.11-bookworm-slim script: - uv run --script tools/check_cluster_health.py only: - schedules
See workflows/ci-cd-integration.md.
Tools Available
Script Validation
Validate PEP 723 metadata
./tools/validate_script.py script.py
Output:
✓ Valid PEP 723 metadata
✓ Python version specified
✓ Dependencies properly formatted
Script Conversion
Convert requirements.txt-based script to uv
./tools/convert_to_uv.py old_script.py
Creates:
- old_script_uv.py with inline dependencies
- Preserves original script
Progressive Disclosure
For deeper knowledge:
Reference Documentation
-
PEP 723 Specification - Complete inline metadata spec
-
Dependency Management - Version pinning strategies
-
Security Patterns - Secrets, validation, input sanitization
Pattern Guides
-
CLI Applications - Typer, Click, argparse patterns
-
API Clients - httpx, requests, authentication
-
Data Processing - Polars, pandas, analysis
-
System Automation - psutil, subprocess, system admin
-
Error Handling - Exception handling, logging
Note: See Common Patterns section above for quick access to these guides.
Working Examples
-
NetBox API Client - Production-ready API client with Infisical, validation, error handling, and Rich output
-
Cluster Health Checker - Production-ready monitoring script with Typer, Rich, and JSON output
Anti-Patterns
-
When NOT to Use - Signs you need a proper project
-
Common Mistakes - Pitfalls and how to avoid them
Workflows
-
Team Adoption - Rolling out uv scripts across teams
-
CI/CD Integration - GitHub Actions, GitLab CI
-
Testing Strategies - Inline tests, pytest integration
Related Skills
-
Ansible Best Practices - Many Ansible modules could be standalone uv scripts
-
Proxmox Infrastructure - Validation tools use this pattern
-
NetBox + PowerDNS Integration - API interaction scripts
Quick Reference
Shebang Options
Standard script execution
#!/usr/bin/env -S uv run --script
Quiet mode (suppress uv output)
#!/usr/bin/env -S uv run --script --quiet
With Python version
#!/usr/bin/env -S uv run --script --python 3.11
Common Dependencies
CLI applications
"typer>=0.9.0" # Modern CLI framework "click>=8.0.0" # Alternative CLI framework "rich>=13.0.0" # Rich text and formatting
API clients
"httpx>=0.27.0" # Modern async HTTP client "requests>=2.31.0" # Traditional HTTP client
Data processing
"polars>=0.20.0" # Fast dataframe library "pandas>=2.0.0" # Traditional dataframe library
Infrastructure
"ansible>=11.1.0" # Automation (from this repo) "infisical-python>=2.3.3" # Secrets (from this repo)
System automation
"psutil>=5.9.0" # System monitoring
Metadata Template
#!/usr/bin/env -S uv run --script --quiet
/// script
requires-python = ">=3.11"
dependencies = [
# Add dependencies here
]
///
""" One-line description
Purpose: describe-purpose Team: team-name Author: email@example.com
Usage: python script.py [OPTIONS]
Examples: python script.py --help """
Best Practices Summary
-
Always specify Python version - requires-python = ">=3.11"
-
Pin dependencies appropriately - Use >=X.Y.Z for utilities
-
Add metadata in docstrings - Put team info, purpose, and author in module docstring
-
Include comprehensive docstrings - Document purpose, usage, and examples
-
Handle errors gracefully - Use try/except with clear messages
-
Validate inputs - Check arguments before processing
-
Use quiet mode - --quiet flag for production scripts
-
Keep it focused - Single file, single purpose
-
Test inline - Add --test flag for simple validation
-
Secure secrets - Never hardcode, use env vars or keyring