13 KiB
title, description, version, last_updated, document_type, official_specification, python_compatibility, related_docs
| title | description | version | last_updated | document_type | official_specification | python_compatibility | related_docs | ||
|---|---|---|---|---|---|---|---|---|---|
| PEP 723 - Inline Script Metadata | Official Python specification for embedding dependency metadata in single-file scripts | 1.0.0 | 2025-11-04 | reference | https://peps.python.org/pep-0723/ | 3.11+ |
|
PEP 723 - Inline Script Metadata
What is PEP 723?
PEP 723 is the official Python specification that defines a standard format for embedding metadata in single-file Python scripts. It allows scripts to declare their dependencies and Python version requirements without requiring separate configuration files like pyproject.toml or requirements.txt.
Official Specification
The model must WebFetch this url before discussing the topic with the user pep-0723
Key Concept
PEP 723 metadata is embedded inside Python comments using a special syntax, making the metadata human-readable and machine-parseable while keeping the script as a single portable file. If implementing anything to interact with this metadata, such as a linting enhancer you must WebFetch inline-script-metadata to get the schema and syntax and implementation.
The Problem It Solves
The Challenge
When sharing Python scripts as standalone files (via email, gists, URLs, or chat), there's a fundamental problem:
- Scripts often need external dependencies (requests, rich, pandas, etc.)
- No standard way to declare these dependencies within the script itself
- Tools can't automatically know what packages to install to run the script
- Users must read documentation or comments to figure out requirements
The Solution
PEP 723 provides a standardized comment-based format that:
- ✅ Embeds dependency declarations directly in the script
- ✅ Remains a valid Python file (metadata is in comments)
- ✅ Is machine-readable by package managers (uv, PDM, Hatch)
- ✅ Keeps everything in a single portable file
Syntax
Format
PEP 723 metadata is written as TOML inside specially-formatted Python comments:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
Rules
- Opening marker:
# /// script(exactly, with spaces) - Content: Valid TOML, with each line prefixed by
#and a space - Closing marker:
# ///(exactly, with spaces) - Location: Typically near the top of the file, after shebang and module docstring
- Indentation: Use consistent comment formatting
Supported Fields
# /// script
# requires-python = ">=3.11" # Minimum Python version
# dependencies = [ # External packages
# "requests>=2.31.0,<3",
# "rich>=13.0",
# "typer[all]>=0.12.0",
# ]
# ///
When to Use PEP 723
✅ Use PEP 723 When
-
Script has external dependencies
- Uses packages from PyPI (requests, pandas, rich, etc.)
- Needs specific package versions
- Example: A CLI tool that fetches data from APIs
-
Sharing standalone scripts
- Sending scripts via email, gists, or chat
- Publishing example scripts in documentation
- Creating portable automation tools
-
Scripts need reproducibility
- Version-pinned dependencies for consistent behavior
- Specific Python version requirements
- Example: Deployment scripts that must work identically across environments
❌ Don't Use PEP 723 When
-
Script uses only stdlib
- No external dependencies = nothing to declare
- Use simple shebang:
#!/usr/bin/env python3 - Example: A script that uses only
argparse,pathlib,json
-
Full project with pyproject.toml
- Projects have proper package structure
- Use
pyproject.tomlfor dependency management - PEP 723 is for single-file scripts, not projects
-
Script is part of a package
- Package dependencies are declared in
pyproject.toml - Script uses package-level dependencies
- No need to duplicate declarations
- Package dependencies are declared in
Shebang Requirements
Scripts with PEP 723 Metadata
Must use the uv-based shebang for automatic dependency installation:
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests", "rich"]
# ///
import requests
from rich import print
Why this shebang?
uv --quiet run --active --script: Tells uv to:- Read PEP 723 metadata from the script
- Install declared dependencies automatically
- Execute the script with correct environment
Stdlib-Only Scripts
Use the standard Python shebang (no PEP 723 needed):
#!/usr/bin/env python3
import argparse
import pathlib
import json
# No dependencies to declare
Why no PEP 723?
- Stdlib is always available (bundled with Python)
- Nothing to declare = no metadata needed
- Simpler is better when appropriate
Complete Example
Script with External Dependencies
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.31.0,<3",
# "rich>=13.0",
# ]
# ///
"""Fetch GitHub user info and display with rich formatting."""
import sys
from typing import Any
import requests
from rich.console import Console
from rich.panel import Panel
console = Console()
def fetch_user(username: str) -> dict[str, Any] | None:
"""Fetch GitHub user data."""
response = requests.get(f"https://api.github.com/users/{username}")
if response.status_code == 200:
return response.json()
return None
def main() -> None:
"""Main entry point."""
if len(sys.argv) != 2:
console.print("[red]Usage: script.py <github-username>[/red]")
sys.exit(1)
username = sys.argv[1]
user = fetch_user(username)
if user:
console.print(
Panel(
f"[bold]{user['name']}[/bold]\n"
f"Followers: {user['followers']}\n"
f"Public Repos: {user['public_repos']}",
title=f"GitHub: {username}",
)
)
else:
console.print(f"[red]User '{username}' not found[/red]")
if __name__ == "__main__":
main()
To run:
chmod +x script.py
./script.py octocat
The script will:
- Read PEP 723 metadata
- Install
requestsandrichif not present - Execute with dependencies available
Stdlib-Only Script
#!/usr/bin/env python3
"""Simple JSON formatter using only stdlib."""
import argparse
import json
import sys
from pathlib import Path
def format_json(input_path: Path, indent: int = 2) -> None:
"""Format JSON file with specified indentation."""
data = json.loads(input_path.read_text())
formatted = json.dumps(data, indent=indent, sort_keys=True)
print(formatted)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(description="Format JSON files")
parser.add_argument("file", type=Path, help="JSON file to format")
parser.add_argument("--indent", type=int, default=2, help="Indentation spaces")
args = parser.parse_args()
format_json(args.file, args.indent)
if __name__ == "__main__":
main()
No PEP 723 needed - all imports are from Python's standard library.
Tool Support
Package Managers
The following tools support PEP 723 inline script metadata:
- uv: https://docs.astral.sh/uv/
- PDM: https://pdm-project.org/
- Hatch: https://hatch.pypa.io/
Running Scripts with uv
# Make script executable
chmod +x script.py
# Run directly (uv reads PEP 723 metadata)
./script.py
# Or explicitly with uv
uv run script.py
Alternative: PDM
pdm run script.py
Common Patterns
Version Constraints
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.31.0,<3", # Major version constraint
# "rich~=13.7", # Compatible release
# "typer[all]", # With extras
# ]
# ///
Development vs Production
For scripts, there's typically no separation - all dependencies are runtime dependencies. If you need development tools (testing, linting), those belong in a full project with pyproject.toml.
Git-Based Dependencies
# /// script
# dependencies = [
# "mylib @ git+https://github.com/user/mylib.git@v1.0.0",
# ]
# ///
Best Practices
1. Pin Major Versions
# Good - prevents breaking changes
"requests>=2.31.0,<3"
# Avoid - might break on major updates
"requests"
2. Document the Script
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests", "rich"]
# ///
"""
Fetch and display GitHub user statistics.
Usage:
./github_stats.py <username>
Example:
./github_stats.py octocat
"""
3. Keep Scripts Focused
PEP 723 is for single-file scripts. If your script is growing large or needs multiple modules, consider creating a proper Python package with pyproject.toml.
4. Test Portability
# Test on clean environment
uv run --isolated script.py args
Comparison: PEP 723 vs pyproject.toml
| Aspect | PEP 723 (Script) | pyproject.toml (Project) |
|---|---|---|
| Use case | Single-file scripts | Multi-module packages |
| Dependencies | Inline comments | Separate TOML file |
| Portability | Single file to share | Requires project structure |
| Complexity | Simple, focused | Full project metadata |
| When to use | Scripts with dependencies | Libraries, applications |
Validation
Using /shebangpython Command
The /shebangpython command validates PEP 723 compliance:
/shebangpython script.py
Checks:
- ✅ Correct shebang for dependency type
- ✅ PEP 723 syntax if external dependencies detected
- ✅ Metadata fields are valid
- ✅ Execute permission set
See: /shebangpython command reference
Troubleshooting
Script Won't Execute
Problem: ./script.py fails with "dependencies not found"
Solution: Check shebang is correct for PEP 723:
#!/usr/bin/env -S uv --quiet run --active --script
Syntax Errors in Metadata
Problem: TOML parsing fails
Solution: Validate TOML syntax:
# /// script
# requires-python = ">=3.11" # ✅ Correct
# dependencies = [ # ✅ Correct - list syntax
# "requests",
# ]
# ///
Performance Concerns
Problem: Script slow to start (installing dependencies)
Solution: uv caches dependencies. First run may be slow, subsequent runs are fast. For production, consider packaging as a proper project.
Migration
From requirements.txt
Before (two files):
# requirements.txt
requests>=2.31.0
rich>=13.0
#!/usr/bin/env python3
# script.py (separate file)
import requests
from rich import print
After (single file):
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.31.0",
# "rich>=13.0",
# ]
# ///
import requests
from rich import print
From Setup.py Scripts
Before (package structure):
myproject/
├── setup.py
├── requirements.txt
└── scripts/
└── tool.py
After (standalone script):
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests", "rich"]
# ///
# tool.py - now fully self-contained
Summary
Key Takeaways
-
PEP 723 = Dependency Metadata for Single-File Scripts
- Standard format for declaring dependencies in comments
- TOML content inside
# ///delimiters
-
When to Use
- Scripts with external dependencies
- Need portability (single file to share)
- Want automatic dependency installation
-
When NOT to Use
- Stdlib-only scripts (nothing to declare)
- Full projects (use
pyproject.toml) - Package modules (use package dependencies)
-
Shebang Requirements
- With PEP 723:
#!/usr/bin/env -S uv --quiet run --active --script - Stdlib only:
#!/usr/bin/env python3
- With PEP 723:
-
Tool Support
- uv, PDM, Hatch all support PEP 723
- Automatic dependency installation on script execution
Quick Reference
# Template for PEP 723 script
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "package-name>=version",
# ]
# ///
"""Script description."""
import package_name
# Your code here
See Also
- Official PEP: https://peps.python.org/pep-0723/
- uv Documentation: https://docs.astral.sh/uv/
- Skill Reference: Python Development SKILL.md
- Shebang Validation: /shebangpython command