Files
2025-11-29 18:49:58 +08:00

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+
../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

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

  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

# /// 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:

#!/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:

  1. Read PEP 723 metadata
  2. Install requests and rich if not present
  3. 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:

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

  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

# 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