| name | python-uv-scripts |
| description | Python single-file script development using uv and PEP 723 inline metadata. Prevents invalid patterns like [tool.uv.metadata]. Use when creating standalone Python utilities, converting scripts to uv format, managing script dependencies, implementing script testing, or establishing team standards for script development. |
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
When to Use This Skill
Activate this skill when:
- Creating standalone Python utilities or automation scripts
- Converting scripts from
requirements.txtto uv format - Adding dependencies to existing single-file scripts
- Implementing testing for utility scripts
- Establishing team standards for script development
- Reviewing scripts for security or best practices
- Setting up CI/CD for script execution
- Creating infrastructure automation tools (like cluster health checkers)
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
1. Security Patterns
Don't hardcode secrets:
# ❌ BAD
password = "super_secret"
# ✅ GOOD - Use environment variables
import os
password = os.getenv("PROXMOX_PASSWORD")
if not password:
raise ValueError("PROXMOX_PASSWORD not set")
Better - Use keyring:
# /// script
# dependencies = ["keyring>=24.0.0"]
# ///
import keyring
password = keyring.get_password("proxmox", "api_user")
Best - Use Infisical (following repository pattern):
# /// script
# dependencies = ["infisical-python>=2.3.3"]
# ///
from infisical import InfisicalClient
client = InfisicalClient()
password = client.get_secret("PROXMOX_PASSWORD", path="/matrix")
See patterns/security-patterns.md for complete guide.
2. 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.
3. 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
"""
4. 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.
5. 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
Pattern: CLI Application
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "typer>=0.9.0",
# "rich>=13.0.0",
# ]
# ///
import typer
from rich import print
app = typer.Typer()
@app.command()
def hello(name: str):
"""Greet someone"""
print(f"[green]Hello {name}![/green]")
if __name__ == "__main__":
app()
See patterns/cli-applications.md.
Pattern: API Client
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "httpx>=0.27.0",
# ]
# ///
import httpx
import os
def get_proxmox_nodes(api_url: str, token: str):
"""Fetch Proxmox cluster nodes"""
headers = {"Authorization": f"PVEAPIToken={token}"}
with httpx.Client(verify=False) as client:
response = client.get(f"{api_url}/nodes", headers=headers)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
api_url = os.getenv("PROXMOX_API_URL")
token = os.getenv("PROXMOX_TOKEN")
nodes = get_proxmox_nodes(api_url, token)
for node in nodes['data']:
print(f"{node['node']}: {node['status']}")
Pattern: Data Processing
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "polars>=0.20.0",
# ]
# ///
import polars as pl
import sys
def analyze_logs(log_file: str):
"""Analyze log file for errors"""
df = pl.read_csv(log_file)
errors = df.filter(pl.col("level") == "ERROR")
print(f"Total errors: {len(errors)}")
by_component = errors.group_by("component").count()
print("\nErrors by component:")
print(by_component)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: analyze_logs.py <log_file>")
sys.exit(1)
analyze_logs(sys.argv[1])
See patterns/data-processing.md.
Pattern: System Automation
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "psutil>=5.9.0",
# ]
# ///
import psutil
import sys
def check_disk_space(threshold: int = 80):
"""Check if disk usage exceeds threshold"""
usage = psutil.disk_usage('/')
percent = usage.percent
if percent >= threshold:
print(f"WARNING: Disk usage at {percent}%", file=sys.stderr)
sys.exit(1)
print(f"OK: Disk usage at {percent}%")
if __name__ == "__main__":
threshold = int(sys.argv[1]) if len(sys.argv) > 1 else 80
check_disk_space(threshold)
See patterns/system-automation.md.
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
Working Examples
- NetBox API Client - Production-ready API client with Infisical, validation, error handling, and Rich output
- Examples README - Complete examples directory with progressive complexity
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.Zfor 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 -
--quietflag for production scripts - Keep it focused - Single file, single purpose
- Test inline - Add
--testflag for simple validation - Secure secrets - Never hardcode, use env vars or keyring