name: writing-python description: Use this before writing Python code, creating Python scripts, modifying Python files, or when user asks to "write python", "create a python script", "implement in python". TRIGGER when starting any Python development task.
Python Development with UV
Modern Python development using uv for package management, PEP 723 for single-file scripts, and best-in-class tooling.
Quick Start
Single-File Scripts (Default)
By default, create self-contained scripts using PEP 723 format:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "typer",
# "rich",
# ]
# ///
"""
Script description and usage examples.
Usage:
uv run python3 script.py --help
uv run python3 script.py --option value
"""
import sys
# ... rest of script
Run with: uv run python3 script.py
Multi-File Projects
For larger projects requiring multiple files:
# Pin Python version
uv python pin 3.12
# Create virtual environment
uv venv --python 3.12
# Activate environment
source .venv/bin/activate
# Add dependencies
uv add package-name
# Run script
uv run python script.py
Development Tools
Testing, Linting, Type Checking
All tooling configuration is centralized in /tests/pyproject.toml.
Run from /tests directory:
cd tests
# Run tests
uv run pytest
uv run pytest -v # verbose
# Type checking
uv run pyright
uv run pyright --stats
# Linting & formatting
uv run ruff check ../.opencode/skill
uv run ruff check --fix ../.opencode/skill
uv run ruff format ../.opencode/skill
Tool choices:
- Ruff - Linter/formatter (replaces Black, isort, Flake8)
- Pyright - Type checker (replaces MyPy)
- Pytest - Test runner
For detailed usage, see:
references/ruff.md- Linting and formattingreferences/pyright.md- Static type checkingreferences/pytest.md- Testing framework
Script Development Workflow
Start Small - Build Incrementally
- Basic structure - Create script with
--helpflag - Test immediately - Run with
uv run python3 script.py --help - Add
--dry-run- Show what would happen without executing - Test again - Verify dry-run output
- Add
--verbose- Detailed output for debugging - Test again - Verify verbose mode
- Continue incrementally - Add features one at a time, testing each
Shebang Format
#!/usr/bin/env uv run python3
PEP 723 Dependencies
# /// script
# dependencies = [
# "typer", # Modern CLI framework
# "rich", # Beautiful terminal output
# "httpx", # Modern HTTP client
# ]
# ///
Minimize dependencies - Try using stdlib first.
UV Commands Reference
Package Management
uv add <package> # Add package to pyproject.toml
uv remove <package> # Remove package
uv sync # Install/sync dependencies
uv lock # Create/update lock file
Python Version Management
uv python install <version> # Install Python version
uv python list # List installed versions
uv python pin <version> # Set project Python version
Running Scripts
uv run python script.py # Run with project environment
uvx <tool> # Run tool in isolated environment
uv tool install <package> # Install global tool
Preferred Libraries
Core Utilities
- uv - Package manager (never use pip/python3 directly)
- typer - Modern CLI framework (built on click)
- rich - Beautiful terminal output
- python-dotenv - Environment variables (or Pydantic-Settings)
Development
- pytest - Testing framework
- ruff - Fast linting and formatting
- pyright - Static type checking
When Needed
- httpx - Modern HTTP client (replaces requests)
- Pydantic-Settings - Type-safe configuration with validation
- Polars - Fast DataFrame library (pandas alternative)
- DuckDB - Embedded analytical database
- Loguru - Simple, powerful logging
Test-Driven Development
TDD Cycle
- Red - Write failing test for new functionality
- Green - Write minimal code to pass test
- Refactor - Improve code while keeping tests green
When to Use TDD
General approach: Code directly as you see fit.
Use TDD when: Facing issues or building complex components.
Development Sequence (When Using TDD)
- Stubs - Define basic structure and interfaces
- Pseudocode - Plan detailed logic within stubs
- Data Layer - Implement data persistence and management
- Business Logic - Implement core application rules
- CLI/Frontend - Implement user interaction
Test Structure
See references/pytest.md for comprehensive testing guide.
Directory Structure
.opencode/skill/<skill>/
├── SKILL.md
└── scripts/
├── <script>.py
└── tests/
└── test_<script>.py
Helper Function Pattern
from pathlib import Path
import subprocess
SCRIPT_PATH = Path(__file__).parent.parent / "script.py"
def run_script(*args, env=None):
"""Execute script with uv run."""
cmd = ["uv", "run", str(SCRIPT_PATH)] + list(args)
result = subprocess.run(cmd, capture_output=True, text=True, env=env)
return result.stdout, result.stderr, result.returncode
Test Class Organization
class TestVersion:
"""Test --version flag."""
def test_version_flag(self):
"""--version should output version and exit with 0."""
stdout, stderr, code = run_script("--version")
assert code == 0
assert "version" in stdout.lower()
Exit Code Standards
| Code | Meaning |
|---|---|
| 0 | Success (version, help, dry-run) |
| 1 | Runtime/API error |
| 2 | Validation error |
| 130 | Keyboard interrupt |
Type Checking
See references/pyright.md for comprehensive type checking guide.
Basic Type Hints
def greet(name: str) -> str:
return f"Hello, {name}"
def find_user(id: int) -> str | None:
return None
def process(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
Docstrings
Use structured docstrings with Args, Returns, and Raises sections:
def calculate_total(items: list[dict], tax_rate: float = 0.0) -> float:
"""Calculate the total cost of items including tax.
Args:
items: List of item dictionaries with 'price' keys
tax_rate: Tax rate as decimal (e.g., 0.08 for 8%)
Returns:
Total cost including tax
Raises:
ValueError: If items is empty or tax_rate is negative
"""
if not items:
raise ValueError("Items list cannot be empty")
if tax_rate < 0:
raise ValueError("Tax rate cannot be negative")
subtotal = sum(item["price"] for item in items)
return subtotal * (1 + tax_rate)
Best Practices
- Use uv exclusively - Never run
python3orpipdirectly - Start with PEP 723 - Single-file scripts by default
- Minimize dependencies - Try stdlib first
- Test incrementally - Build and test feature by feature
- Use type hints - Catch errors early with pyright
- Format with ruff - Consistent code style
- Follow exit codes - 0 for success, 1 for runtime errors, 2 for validation
Common Pitfalls
Mutable Default Arguments
Never use mutable objects (lists, dicts) as default argument values:
# BAD - The list persists across calls!
def add_item(item, items=[]):
items.append(item)
return items
add_item("a") # ['a']
add_item("b") # ['a', 'b'] - Unexpected!
# GOOD - Use None and create inside function
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Bare Except Clauses
Never use bare except: - always catch specific exceptions:
# BAD - Catches everything including KeyboardInterrupt
try:
do_something()
except:
pass
# GOOD - Catch specific exceptions
try:
do_something()
except (ValueError, TypeError) as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
Comparing with None
Use is / is not for None comparisons:
# BAD
if value == None:
...
# GOOD
if value is None:
...
Security
Environment Variables
Store secrets in .env files, never in code:
# Load from .env file
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv("API_KEY")
if not api_key:
print("Error: API_KEY not set", file=sys.stderr)
sys.exit(1)
Required Practices
- Never commit secrets - Add
.envto.gitignore - Never log secrets - Don't print API keys, passwords, or tokens
- Never hardcode - Use environment variables for all credentials
- Validate early - Check for required env vars at startup
.gitignore Entry
# Environment variables
.env
.env.local
.env.*.local
Bundled Resources
references/ruff.md- Linting and formatting guidereferences/pyright.md- Type checking guidereferences/pytest.md- Testing framework guide