549 lines
13 KiB
Markdown
549 lines
13 KiB
Markdown
---
|
|
title: "PEP 723 - Inline Script Metadata"
|
|
description: "Official Python specification for embedding dependency metadata in single-file scripts"
|
|
version: "1.0.0"
|
|
last_updated: "2025-11-04"
|
|
document_type: "reference"
|
|
official_specification: "https://peps.python.org/pep-0723/"
|
|
python_compatibility: "3.11+"
|
|
related_docs:
|
|
- "../SKILL.md"
|
|
- "./python-development-orchestration.md"
|
|
---
|
|
|
|
# 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](https://peps.python.org/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](https://packaging.python.org/en/latest/specifications/inline-script-metadata/#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**:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = [
|
|
# "requests<3",
|
|
# "rich",
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
### Rules
|
|
|
|
1. **Opening marker**: `# /// script` (exactly, with spaces)
|
|
2. **Content**: Valid TOML, with each line prefixed by `#` and a space
|
|
3. **Closing marker**: `# ///` (exactly, with spaces)
|
|
4. **Location**: Typically near the top of the file, after shebang and module docstring
|
|
5. **Indentation**: Use consistent comment formatting
|
|
|
|
### Supported Fields
|
|
|
|
```toml
|
|
# /// 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
|
|
|
|
1. **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
|
|
|
|
2. **Sharing standalone scripts**
|
|
- Sending scripts via email, gists, or chat
|
|
- Publishing example scripts in documentation
|
|
- Creating portable automation tools
|
|
|
|
3. **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
|
|
|
|
1. **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`
|
|
|
|
2. **Full project with pyproject.toml**
|
|
- Projects have proper package structure
|
|
- Use `pyproject.toml` for dependency management
|
|
- PEP 723 is for **single-file scripts**, not projects
|
|
|
|
3. **Script is part of a package**
|
|
- Package dependencies are declared in `pyproject.toml`
|
|
- Script uses package-level dependencies
|
|
- No need to duplicate declarations
|
|
|
|
## Shebang Requirements
|
|
|
|
### Scripts with PEP 723 Metadata
|
|
|
|
**Must use** the uv-based shebang for automatic dependency installation:
|
|
|
|
```python
|
|
#!/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):
|
|
|
|
```python
|
|
#!/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
|
|
|
|
```python
|
|
#!/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**:
|
|
|
|
```bash
|
|
chmod +x script.py
|
|
./script.py octocat
|
|
```
|
|
|
|
The script will:
|
|
|
|
1. Read PEP 723 metadata
|
|
2. Install `requests` and `rich` if not present
|
|
3. Execute with dependencies available
|
|
|
|
### Stdlib-Only Script
|
|
|
|
```python
|
|
#!/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/](https://docs.astral.sh/uv/)
|
|
- **PDM**: [https://pdm-project.org/](https://pdm-project.org/)
|
|
- **Hatch**: [https://hatch.pypa.io/](https://hatch.pypa.io/)
|
|
|
|
### Running Scripts with uv
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
pdm run script.py
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Version Constraints
|
|
|
|
```python
|
|
# /// 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
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "mylib @ git+https://github.com/user/mylib.git@v1.0.0",
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Pin Major Versions
|
|
|
|
```python
|
|
# Good - prevents breaking changes
|
|
"requests>=2.31.0,<3"
|
|
|
|
# Avoid - might break on major updates
|
|
"requests"
|
|
```
|
|
|
|
### 2. Document the Script
|
|
|
|
```python
|
|
#!/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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
/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](~/.claude/commands/shebangpython.md)
|
|
|
|
## Troubleshooting
|
|
|
|
### Script Won't Execute
|
|
|
|
**Problem**: `./script.py` fails with "dependencies not found"
|
|
|
|
**Solution**: Check shebang is correct for PEP 723:
|
|
|
|
```python
|
|
#!/usr/bin/env -S uv --quiet run --active --script
|
|
```
|
|
|
|
### Syntax Errors in Metadata
|
|
|
|
**Problem**: TOML parsing fails
|
|
|
|
**Solution**: Validate TOML syntax:
|
|
|
|
```python
|
|
# /// 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):
|
|
|
|
```text
|
|
# requirements.txt
|
|
requests>=2.31.0
|
|
rich>=13.0
|
|
```
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
# script.py (separate file)
|
|
|
|
import requests
|
|
from rich import print
|
|
```
|
|
|
|
**After** (single file):
|
|
|
|
```python
|
|
#!/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):
|
|
|
|
```text
|
|
myproject/
|
|
├── setup.py
|
|
├── requirements.txt
|
|
└── scripts/
|
|
└── tool.py
|
|
```
|
|
|
|
**After** (standalone script):
|
|
|
|
```python
|
|
#!/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
|
|
|
|
1. **PEP 723 = Dependency Metadata for Single-File Scripts**
|
|
- Standard format for declaring dependencies in comments
|
|
- TOML content inside `# ///` delimiters
|
|
|
|
2. **When to Use**
|
|
- Scripts **with external dependencies**
|
|
- Need portability (single file to share)
|
|
- Want automatic dependency installation
|
|
|
|
3. **When NOT to Use**
|
|
- Stdlib-only scripts (nothing to declare)
|
|
- Full projects (use `pyproject.toml`)
|
|
- Package modules (use package dependencies)
|
|
|
|
4. **Shebang Requirements**
|
|
- With PEP 723: `#!/usr/bin/env -S uv --quiet run --active --script`
|
|
- Stdlib only: `#!/usr/bin/env python3`
|
|
|
|
5. **Tool Support**
|
|
- uv, PDM, Hatch all support PEP 723
|
|
- Automatic dependency installation on script execution
|
|
|
|
### Quick Reference
|
|
|
|
```python
|
|
# 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/](https://peps.python.org/pep-0723/)
|
|
- **uv Documentation**: [https://docs.astral.sh/uv/](https://docs.astral.sh/uv/)
|
|
- **Skill Reference**: [Python Development SKILL.md](../SKILL.md)
|
|
- **Shebang Validation**: [/shebangpython command](~/.claude/commands/shebangpython.md)
|