374 lines
7.1 KiB
Markdown
374 lines
7.1 KiB
Markdown
# Anti-Patterns: Common Mistakes with uv Scripts
|
|
|
|
This document lists common mistakes when creating uv scripts and how to avoid them.
|
|
|
|
## Critical Mistakes
|
|
|
|
### ❌ Using [tool.uv.metadata]
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = []
|
|
# [tool.uv.metadata] # ❌ THIS DOES NOT WORK
|
|
# purpose = "testing"
|
|
# team = "devops"
|
|
# ///
|
|
```
|
|
|
|
**Error**:
|
|
|
|
```text
|
|
error: TOML parse error at line 4, column 7
|
|
|
|
|
4 | [tool.uv.metadata]
|
|
| ^^
|
|
unknown field `metadata`
|
|
```
|
|
|
|
**Why**: `[tool.uv.metadata]` is not part of PEP 723 and is not supported by uv.
|
|
|
|
**CORRECT**: Use docstrings for metadata:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = []
|
|
# ///
|
|
"""
|
|
Purpose: Testing automation
|
|
Team: DevOps
|
|
Author: team@example.com
|
|
"""
|
|
```
|
|
|
|
**NOTE**: `[tool.uv]` (without `.metadata`) IS valid for fields like `exclude-newer`. See reference/pep-723-spec.md.
|
|
|
|
### ❌ Adding Custom TOML Fields
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# author = "me" # ❌ INVALID
|
|
# version = "1.0.0" # ❌ INVALID
|
|
# description = "test" # ❌ INVALID
|
|
# dependencies = []
|
|
# ///
|
|
```
|
|
|
|
**CORRECT**: Only use `requires-python` and `dependencies` at the top level:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = []
|
|
# ///
|
|
"""
|
|
Author: me
|
|
Version: 1.0.0
|
|
Description: test
|
|
"""
|
|
```
|
|
|
|
### ❌ Hardcoded Secrets
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
API_KEY = "sk-1234567890abcdef" # ❌ NEVER DO THIS
|
|
PASSWORD = "super_secret_password" # ❌ NEVER DO THIS
|
|
```
|
|
|
|
**CORRECT**: Use environment variables:
|
|
|
|
```python
|
|
import os
|
|
import sys
|
|
|
|
API_KEY = os.getenv("API_KEY")
|
|
if not API_KEY:
|
|
print("Error: API_KEY environment variable not set", file=sys.stderr)
|
|
sys.exit(1)
|
|
```
|
|
|
|
**BETTER**: Use Infisical (following repo pattern):
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = [
|
|
# "infisical-python>=2.3.3",
|
|
# ]
|
|
# ///
|
|
|
|
from infisical import InfisicalClient
|
|
|
|
client = InfisicalClient()
|
|
api_key = client.get_secret("API_KEY", path="/production")
|
|
```
|
|
|
|
### ❌ Missing Error Handling
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
import httpx
|
|
|
|
response = httpx.get("https://api.example.com") # ❌ No error handling
|
|
data = response.json()
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
import httpx
|
|
import sys
|
|
|
|
try:
|
|
response = httpx.get("https://api.example.com", timeout=10.0)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
except httpx.HTTPStatusError as e:
|
|
print(f"HTTP error: {e.response.status_code}", file=sys.stderr)
|
|
sys.exit(1)
|
|
except httpx.RequestError as e:
|
|
print(f"Request failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
```
|
|
|
|
## When NOT to Use Single-File Scripts
|
|
|
|
### ❌ Complex Applications
|
|
|
|
Don't use single-file scripts when:
|
|
|
|
- **Script exceeds 500 lines** → Use a proper uv project
|
|
- **Multiple modules needed** → Use a proper uv project
|
|
- **Shared code across scripts** → Use a proper uv project with shared library
|
|
- **Web applications** → Use a proper uv project (Flask/FastAPI/Django)
|
|
- **Long-running services** → Use a proper uv project
|
|
- **Complex configuration** → Use a proper uv project with config files
|
|
|
|
**Example - Too Complex**:
|
|
|
|
```python
|
|
# This should be a uv project, not a script:
|
|
# - 15+ dependencies
|
|
# - Database models
|
|
# - API routes
|
|
# - Background workers
|
|
# - Multiple configuration files
|
|
# - 1000+ lines of code
|
|
```
|
|
|
|
### ❌ Heavy Dependencies
|
|
|
|
**WRONG**: Using heavy ML/data libraries in scripts:
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "tensorflow>=2.15.0", # ❌ Too heavy for script
|
|
# "torch>=2.1.0", # ❌ Too heavy for script
|
|
# "transformers>=4.35.0", # ❌ Too heavy for script
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
**Why**: These create very large environments. Use a proper project instead.
|
|
|
|
**OK**: Lightweight data processing:
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "polars>=0.20.0", # ✓ Reasonable for scripts
|
|
# "httpx>=0.27.0", # ✓ Reasonable for scripts
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
## Common Syntax Mistakes
|
|
|
|
### ❌ Missing # on TOML Lines
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# /// script
|
|
requires-python = ">=3.11" # ❌ Missing # at start
|
|
dependencies = [] # ❌ Missing # at start
|
|
# ///
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
# /// script
|
|
# requires-python = ">=3.11" # ✓ Each line starts with #
|
|
# dependencies = [] # ✓ Each line starts with #
|
|
# ///
|
|
```
|
|
|
|
### ❌ Wrong Marker Format
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# /// scripts # ❌ Wrong: "scripts" not "script"
|
|
# requires-python = ">=3.11"
|
|
# ///
|
|
|
|
# // script # ❌ Wrong: "//" not "///"
|
|
# requires-python = ">=3.11"
|
|
# //
|
|
|
|
# /// script # ❌ Wrong: No closing marker
|
|
# requires-python = ">=3.11"
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
# /// script # ✓ Exactly "/// script"
|
|
# requires-python = ">=3.11"
|
|
# /// # ✓ Exactly "///"
|
|
```
|
|
|
|
## Dependency Management Mistakes
|
|
|
|
### ❌ No Version Constraints
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "httpx", # ❌ No version specified
|
|
# "requests", # ❌ Could break unexpectedly
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "httpx>=0.27.0", # ✓ Minimum version specified
|
|
# "requests>=2.31.0", # ✓ Prevents breaking changes
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
### ❌ Overly Strict Pinning
|
|
|
|
**WRONG** (for utility scripts):
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "httpx==0.27.0", # ❌ Too strict, prevents updates
|
|
# "rich==13.7.0", # ❌ Blocks security fixes
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
# /// script
|
|
# dependencies = [
|
|
# "httpx>=0.27.0", # ✓ Allows updates
|
|
# "rich>=13.0.0", # ✓ Allows security fixes
|
|
# ]
|
|
# ///
|
|
```
|
|
|
|
**NOTE**: Exact pinning (`==`) is appropriate for deployment scripts where reproducibility is critical.
|
|
|
|
## Additional Pitfalls
|
|
|
|
### ❌ Missing Shebang
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
# No shebang - script won't be directly executable
|
|
# /// script
|
|
# dependencies = ["requests"]
|
|
# ///
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# dependencies = ["requests"]
|
|
# ///
|
|
```
|
|
|
|
### ❌ Broad Exception Handling
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
try:
|
|
do_something()
|
|
except Exception: # Too broad!
|
|
pass
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
try:
|
|
do_something()
|
|
except (FileNotFoundError, PermissionError) as e:
|
|
print(f"Error: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
```
|
|
|
|
### ❌ Platform-Specific Code Without Guards
|
|
|
|
**WRONG**:
|
|
|
|
```python
|
|
import pwd # Unix-only, crashes on Windows
|
|
user = pwd.getpwuid(os.getuid())
|
|
```
|
|
|
|
**CORRECT**:
|
|
|
|
```python
|
|
import sys
|
|
if sys.platform != "win32":
|
|
import pwd
|
|
user = pwd.getpwuid(os.getuid())
|
|
else:
|
|
user = os.environ.get("USERNAME")
|
|
```
|
|
|
|
## Summary
|
|
|
|
**Never use**:
|
|
|
|
- `[tool.uv.metadata]` (invalid field)
|
|
- Custom fields in PEP 723 metadata (only `requires-python` and `dependencies`)
|
|
- Hardcoded secrets
|
|
- Scripts for complex applications (>500 lines, web apps, services)
|
|
|
|
**Always use**:
|
|
|
|
- Valid PEP 723 format only
|
|
- `[tool.uv]` (without `.metadata`) for valid fields like `exclude-newer`
|
|
- Environment variables or Infisical for secrets
|
|
- Error handling for external calls
|
|
- Version constraints on dependencies
|
|
- Proper projects when scripts become too complex
|