Initial commit
This commit is contained in:
391
skills/python-uv-scripts/reference/pep-723-spec.md
Normal file
391
skills/python-uv-scripts/reference/pep-723-spec.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# PEP 723: Inline Script Metadata Specification
|
||||
|
||||
Complete reference for PEP 723 inline script metadata format used by uv.
|
||||
|
||||
## Overview
|
||||
|
||||
PEP 723 defines a standardized way to embed dependency and configuration metadata directly within Python script files. This eliminates the need for separate `requirements.txt` files and enables self-contained, reproducible scripts.
|
||||
|
||||
## Basic Format
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "package-name>=1.0.0",
|
||||
# ]
|
||||
# ///
|
||||
```
|
||||
|
||||
**Key Requirements:**
|
||||
|
||||
- Must appear as comments (`#`)
|
||||
- Must use `# /// script` as opening marker
|
||||
- Must use `# ///` as closing marker
|
||||
- Must be valid TOML syntax
|
||||
- Recommended placement: After shebang, before module docstring
|
||||
|
||||
## Complete Specification
|
||||
|
||||
### Required Fields
|
||||
|
||||
#### requires-python
|
||||
|
||||
Specifies minimum Python version:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# ///
|
||||
```
|
||||
|
||||
**Formats:**
|
||||
|
||||
```python
|
||||
requires-python = ">=3.11" # Minimum version
|
||||
requires-python = ">=3.11,<3.13" # Version range
|
||||
requires-python = "==3.12" # Exact version
|
||||
```
|
||||
|
||||
#### dependencies
|
||||
|
||||
Lists required packages:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "httpx>=0.27.0",
|
||||
# "rich>=13.0.0",
|
||||
# "typer~=0.9.0",
|
||||
# ]
|
||||
# ///
|
||||
```
|
||||
|
||||
**Version Specifiers:**
|
||||
|
||||
```python
|
||||
"package" # Any version
|
||||
"package>=1.0" # Minimum version
|
||||
"package>=1.0,<2.0" # Version range
|
||||
"package~=1.2.3" # Compatible release (1.2.x)
|
||||
"package==1.2.3" # Exact version
|
||||
```
|
||||
|
||||
### Optional Fields
|
||||
|
||||
#### [tool.uv] Section
|
||||
|
||||
uv-specific configuration:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
#
|
||||
# [tool.uv]
|
||||
# exclude-newer = "2024-10-01T00:00:00Z"
|
||||
# index-url = "https://pypi.org/simple"
|
||||
# ///
|
||||
```
|
||||
|
||||
**Available options:**
|
||||
|
||||
- `exclude-newer`: Only use packages published before this date
|
||||
- `index-url`: Alternative PyPI index
|
||||
- `extra-index-url`: Additional package indexes
|
||||
- `find-links`: Additional package sources
|
||||
- `no-index`: Ignore PyPI entirely
|
||||
|
||||
#### [tool.uv.sources]
|
||||
|
||||
Custom package sources:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = ["my-package"]
|
||||
#
|
||||
# [tool.uv.sources]
|
||||
# my-package = { git = "https://github.com/user/repo", tag = "v1.0" }
|
||||
# ///
|
||||
```
|
||||
|
||||
#### Valid [tool.uv] Fields
|
||||
|
||||
Additional uv-specific configuration (optional):
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
#
|
||||
# [tool.uv]
|
||||
# exclude-newer = "2025-01-01T00:00:00Z" # Reproducibility constraint
|
||||
# ///
|
||||
```
|
||||
|
||||
**Note**: For custom metadata like purpose, team, author, use Python docstrings instead:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"""
|
||||
Purpose: cluster-monitoring
|
||||
Team: infrastructure
|
||||
Author: devops@example.com
|
||||
Created: 2024-10-20
|
||||
"""
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Minimal Script
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"""Simple script with no dependencies"""
|
||||
|
||||
print("Hello, world!")
|
||||
```
|
||||
|
||||
### Production Script
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script --quiet
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "httpx>=0.27.0",
|
||||
# "rich>=13.0.0",
|
||||
# "typer>=0.9.0",
|
||||
# ]
|
||||
#
|
||||
# [tool.uv]
|
||||
# exclude-newer = "2025-01-01T00:00:00Z"
|
||||
# ///
|
||||
"""
|
||||
API client for Proxmox cluster monitoring
|
||||
|
||||
Purpose: api-client
|
||||
Team: infrastructure
|
||||
Author: devops@spaceships.work
|
||||
|
||||
Usage:
|
||||
check_cluster.py [--node NODE] [--json]
|
||||
"""
|
||||
|
||||
import typer
|
||||
import httpx
|
||||
from rich import print
|
||||
```
|
||||
|
||||
### With Git Dependencies
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "my-internal-lib",
|
||||
# ]
|
||||
#
|
||||
# [tool.uv.sources]
|
||||
# my-internal-lib = { git = "https://github.com/org/lib.git", tag = "v1.2.3" }
|
||||
# ///
|
||||
```
|
||||
|
||||
### With Local Dependencies
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "my-local-package",
|
||||
# ]
|
||||
#
|
||||
# [tool.uv.sources]
|
||||
# my-local-package = { path = "../my-package", editable = true }
|
||||
# ///
|
||||
```
|
||||
|
||||
## Placement Guidelines
|
||||
|
||||
### Correct Placement
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"""Module docstring comes after metadata"""
|
||||
|
||||
import sys
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Multiple Metadata Blocks (Invalid)
|
||||
|
||||
```python
|
||||
# ❌ INVALID - Only one metadata block allowed
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# ///
|
||||
|
||||
# /// script
|
||||
# dependencies = ["httpx"]
|
||||
# ///
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
### Valid Metadata
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "httpx>=0.27.0", # Comments allowed in arrays
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
```
|
||||
|
||||
### Invalid Metadata
|
||||
|
||||
```python
|
||||
# ❌ Missing closing marker
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
|
||||
# ❌ Invalid TOML syntax
|
||||
# /// script
|
||||
# dependencies = httpx # Missing quotes
|
||||
# ///
|
||||
|
||||
# ❌ Not in comments
|
||||
/// script
|
||||
requires-python = ">=3.11"
|
||||
///
|
||||
```
|
||||
|
||||
## Creating Metadata
|
||||
|
||||
### Manual Creation
|
||||
|
||||
Add metadata block manually:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "httpx>=0.27.0",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import httpx
|
||||
```
|
||||
|
||||
### Using uv init
|
||||
|
||||
Generate script with metadata:
|
||||
|
||||
```bash
|
||||
uv init --script my_script.py --python 3.11
|
||||
```
|
||||
|
||||
### Using uv add
|
||||
|
||||
Add dependencies to existing script:
|
||||
|
||||
```bash
|
||||
uv add --script my_script.py httpx rich typer
|
||||
```
|
||||
|
||||
## Validation Tools
|
||||
|
||||
### Check Metadata Validity
|
||||
|
||||
```bash
|
||||
# Parse metadata
|
||||
uv run --script my_script.py --dry-run
|
||||
|
||||
# Validate with custom tool
|
||||
python tools/validate_script.py my_script.py
|
||||
```
|
||||
|
||||
### Extract Metadata
|
||||
|
||||
```python
|
||||
import re
|
||||
import tomllib
|
||||
|
||||
def extract_metadata(script_path: str) -> dict:
|
||||
"""Extract PEP 723 metadata from script"""
|
||||
with open(script_path) as f:
|
||||
content = f.read()
|
||||
|
||||
# Find metadata block
|
||||
pattern = r'# /// script\n((?:# .*\n)+)# ///'
|
||||
match = re.search(pattern, content)
|
||||
|
||||
if not match:
|
||||
return {}
|
||||
|
||||
# Parse TOML (remove leading # from each line)
|
||||
toml_lines = match.group(1).split('\n')
|
||||
toml_content = '\n'.join(line[2:] for line in toml_lines if line.startswith('# '))
|
||||
|
||||
return tomllib.loads(toml_content)
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
|
||||
### PEP 723 Support
|
||||
|
||||
- ✅ uv (native support)
|
||||
- ✅ pip (via `pip-run`)
|
||||
- ✅ pipx (v1.4.0+)
|
||||
- ⚠️ Other tools (check documentation)
|
||||
|
||||
### Fallback for Non-Supporting Tools
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = ["httpx"]
|
||||
# ///
|
||||
"""
|
||||
Fallback installation for non-PEP-723 tools:
|
||||
pip install httpx
|
||||
python script.py
|
||||
"""
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always include requires-python** - Prevents compatibility issues
|
||||
2. **Pin major versions** - `>=X.Y.Z` for stability
|
||||
3. **Add metadata section** - Document purpose and ownership
|
||||
4. **Keep dependencies minimal** - Only what's necessary
|
||||
5. **Document fallbacks** - Help non-uv users
|
||||
6. **Validate syntax** - Use validation tools
|
||||
7. **Version consistently** - Match project conventions
|
||||
|
||||
## References
|
||||
|
||||
- [PEP 723 Specification](https://peps.python.org/pep-0723/)
|
||||
- [uv Documentation](https://docs.astral.sh/uv/)
|
||||
- [TOML Specification](https://toml.io/)
|
||||
- [Python Version Specifiers](https://peps.python.org/pep-0440/)
|
||||
Reference in New Issue
Block a user