modern-python
Configures Python projects with modern tooling (uv, ruff, ty). Use when creating projects, writing standalone scripts, or migrating from pip/Poetry/mypy/black.
Install
npx skills add https://github.com/trailofbits/skills/tree/main/plugins/modern-python/skills/modern-python
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
git clone https://github.com/trailofbits/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Modern Python
Guide for modern Python tooling and best practices, based on trailofbits/cookiecutter-python.
When to Use This Skill
- Creating a new Python project or package
- Setting up
pyproject.tomlconfiguration - Configuring development tools (linting, formatting, testing)
- Writing Python scripts with external dependencies
- Migrating from legacy tools (when user requests it)
When NOT to Use This Skill
- User wants to keep legacy tooling: Respect existing workflows if explicitly requested
- Python < 3.11 required: These tools target modern Python
- Non-Python projects: Mixed codebases where Python isn't primary
Anti-Patterns to Avoid
| Avoid | Use Instead |
|---|---|
[tool.ty] python-version |
[tool.ty.environment] python-version |
uv pip install |
uv add and uv sync |
| Editing pyproject.toml manually to add deps | uv add <pkg> / uv remove <pkg> |
hatchling build backend |
uv_build (simpler, sufficient for most cases) |
| Poetry | uv (faster, simpler, better ecosystem integration) |
| requirements.txt | PEP 723 for scripts, pyproject.toml for projects |
| mypy / pyright | ty (faster, from Astral team) |
[project.optional-dependencies] for dev tools |
[dependency-groups] (PEP 735) |
Manual virtualenv activation (source .venv/bin/activate) |
uv run <cmd> |
| pre-commit | prek (faster, no Python runtime needed) |
Key principles:
- Always use
uv addanduv removeto manage dependencies - Never manually activate or manage virtual environments—use
uv runfor all commands - Use
[dependency-groups]for dev/test/docs dependencies, not[project.optional-dependencies]
Decision Tree
What are you doing?
│
├─ Single-file script with dependencies?
│ └─ Use PEP 723 inline metadata (./references/pep723-scripts.md)
│
├─ New multi-file project (not distributed)?
│ └─ Minimal uv setup (see Quick Start below)
│
├─ New reusable package/library?
│ └─ Full project setup (see Full Setup below)
│
└─ Migrating existing project?
└─ See Migration Guide below
Tool Overview
| Tool | Purpose | Replaces |
|---|---|---|
| uv | Package/dependency management | pip, virtualenv, pip-tools, pipx, pyenv |
| ruff | Linting AND formatting | flake8, black, isort, pyupgrade, pydocstyle |
| ty | Type checking | mypy, pyright (faster alternative) |
| pytest | Testing with coverage | unittest |
| prek | Pre-commit hooks (setup) | pre-commit (faster, Rust-native) |
Security Tools
| Tool | Purpose | When It Runs |
|---|---|---|
| shellcheck | Shell script linting | pre-commit |
| detect-secrets | Secret detection | pre-commit |
| actionlint | Workflow syntax validation | pre-commit, CI |
| zizmor | Workflow security audit | pre-commit, CI |
| pip-audit | Dependency vulnerability scanning | CI, manual |
| Dependabot | Automated dependency updates | scheduled |
See security-setup.md for configuration and usage.
Quick Start: Minimal Project
For simple multi-file projects not intended for distribution:
# Create project with uv
uv init myproject
cd myproject
# Add dependencies
uv add requests rich
# Add dev dependencies
uv add --group dev pytest ruff ty
# Run code
uv run python src/myproject/main.py
# Run tools
uv run pytest
uv run ruff check .
Full Project Setup
If starting from scratch, ask the user if they prefer to use the Trail of Bits cookiecutter template to bootstrap a complete project with already preconfigured tooling.
uvx cookiecutter gh:trailofbits/cookiecutter-python
1. Create Project Structure
uv init --package myproject
cd myproject
This creates:
myproject/
├── pyproject.toml
├── README.md
├── src/
│ └── myproject/
│ └── __init__.py
└── .python-version
2. Configure pyproject.toml
See pyproject.md for complete configuration reference.
Key sections:
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[dependency-groups]
dev = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}]
lint = ["ruff", "ty"]
test = ["pytest", "pytest-cov"]
audit = ["pip-audit"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "COM812", "ISC001"]
[tool.pytest]
addopts = ["--cov=myproject", "--cov-fail-under=80"]
[tool.ty.terminal]
error-on-warning = true
[tool.ty.environment]
python-version = "3.11"
[tool.ty.rules]
# Strict from day 1 for new projects
possibly-unresolved-reference = "error"
unused-ignore-comment = "warn"
3. Install Dependencies
# Install all dependency groups
uv sync --all-groups
# Or install specific groups
uv sync --group dev
4. Add Makefile
.PHONY: dev lint format test build
dev:
uv sync --all-groups
lint:
uv run ruff format --check && uv run ruff check && uv run ty check src/
format:
uv run ruff format .
test:
uv run pytest
build:
uv build
Migration Guide
When a user requests migration from legacy tooling:
From requirements.txt + pip
First, determine the nature of the code:
For standalone scripts: Convert to PEP 723 inline metadata (see pep723-scripts.md)
For projects:
# Initialize uv in existing project
uv init --bare
# Add dependencies using uv (not by editing pyproject.toml)
uv add requests rich # add each package
# Or import from requirements.txt (review each package before adding)
# Note: Complex version specifiers may need manual handling
grep -v '^#' requirements.txt | grep -v '^-' | grep -v '^\s*$' | while read -r pkg; do
uv add "$pkg" || echo "Failed to add: $pkg"
done
uv sync
Then:
- Delete
requirements.txt,requirements-dev.txt - Delete virtual environment (
venv/,.venv/) - Add
uv.lockto version control
From setup.py / setup.cfg
- Run
uv init --bareto create pyproject.toml - Use
uv addto add each dependency frominstall_requires - Use
uv add --group devfor dev dependencies - Copy non-dependency metadata (name, version, description, etc.) to
[project] - Delete
setup.py,setup.cfg,MANIFEST.in
From flake8 + black + isort
- Remove flake8, black, isort via
uv remove - Delete
.flake8,pyproject.toml [tool.black],[tool.isort]configs - Add ruff:
uv add --group dev ruff - Add ruff configuration (see ruff-config.md)
- Run
uv run ruff check --fix .to apply fixes - Run
uv run ruff format .to format
From mypy / pyright
- Remove mypy/pyright via
uv remove - Delete
mypy.ini,pyrightconfig.json, or[tool.mypy]/[tool.pyright]sections - Add ty:
uv add --group dev ty - Run
uv run ty check src/
Quick Reference: uv Commands
| Command | Description |
|---|---|
uv init |
Create new project |
uv init --package |
Create distributable package |
uv add <pkg> |
Add dependency |
uv add --group dev <pkg> |
Add to dependency group |
uv remove <pkg> |
Remove dependency |
uv sync |
Install dependencies |
uv sync --all-groups |
Install all dependency groups |
uv run <cmd> |
Run command in venv |
uv run --with <pkg> <cmd> |
Run with temporary dependency |
uv build |
Build package |
uv publish |
Publish to PyPI |
Ad-hoc Dependencies with --with
Use uv run --with for one-off commands that need packages not in your project:
# Run Python with a temporary package
uv run --with requests python -c "import requests; print(requests.get('https://httpbin.org/ip').json())"
# Run a module with temporary deps
uv run --with rich python -m rich.progress
# Multiple packages
uv run --with requests --with rich python script.py
# Combine with project deps (adds to existing venv)
uv run --with httpx pytest # project deps + httpx
When to use --with vs uv add:
uv add: Package is a project dependency (goes in pyproject.toml/uv.lock)--with: One-off usage, testing, or scripts outside a project context
See uv-commands.md for complete reference.
Quick Reference: Dependency Groups
[dependency-groups]
dev = ["ruff", "ty"]
test = ["pytest", "pytest-cov", "hypothesis"]
docs = ["sphinx", "myst-parser"]
Install with: uv sync --group dev --group test
Best Practices Checklist
- Use
src/layout for packages - Set
requires-python = ">=3.11" - Configure ruff with
select = ["ALL"]and explicit ignores - Use ty for type checking
- Enforce test coverage minimum (80%+)
- Use dependency groups instead of extras for dev tools
- Add
uv.lockto version control - Use PEP 723 for standalone scripts
Read Next
- migration-checklist.md - Step-by-step migration cleanup
- pyproject.md - Complete pyproject.toml reference
- uv-commands.md - uv command reference
- ruff-config.md - Ruff linting/formatting configuration
- testing.md - pytest and coverage setup
- pep723-scripts.md - PEP 723 inline script metadata
- prek.md - Fast pre-commit hooks with prek
- security-setup.md - Security hooks and dependency scanning
- dependabot.md - Automated dependency updates
Files (skills)
-
agents
-
openai.yaml 226 B
interface: display_name: "Modern Python" short_description: "Set up Python projects with modern tooling" icon_small: "assets/trail-of-bits-mark.svg" icon_large: "assets/trail-of-bits-mark.svg" brand_color: "#D83A34"
-
-
assets
-
trail-of-bits-mark.svg 3 KB · in bundle
-
-
references
-
dependabot.md 1.8 KB
# Dependabot: Automated Dependency Updates [Dependabot](https://docs.github.com/en/code-security/dependabot) automatically creates pull requests to keep your dependencies up to date. GitHub hosts it natively—no external service required. ## Why Use Dependabot? - **Security**: Automatically patches known vulnerabilities - **Freshness**: Keeps dependencies current without manual tracking - **Visibility**: PRs show changelogs and compatibility notes ## Configuration Copy [templates/dependabot.yml](../templates/dependabot.yml) to `.github/dependabot.yml`. The template includes: - Weekly update schedule for pip and GitHub Actions - 7-day cooldown for supply chain protection - Grouping to reduce PR noise ## Supply Chain Protection The `cooldown.default-days: 7` setting delays updates for newly published versions. This provides time for the community to detect compromised packages before they reach your project. **Why this matters:** - Attackers sometimes publish malicious versions of legitimate packages - A 7-day delay allows time for detection and removal - Combined with weekly schedules, this balances security with freshness ## Common Options | Option | Description | |--------|-------------| | `interval` | `daily`, `weekly`, or `monthly` | | `cooldown.default-days` | Days to wait before updating new releases | | `ignore` | Skip specific dependencies or versions | | `groups` | Group related updates into single PRs | | `reviewers` | Auto-assign reviewers to PRs | ## See Also - [GitHub Dependabot docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file) - [security-setup.md](./security-setup.md) - Security tooling overview - [prek.md](./prek.md) - Pre-commit hooks (complementary tool) -
migration-checklist.md 3 KB
# Migration Checklist Comprehensive checklist for migrating Python projects to modern tooling. ## Before Migration - [ ] **Determine layout**: `src/` or flat? Configure `[tool.uv.build-backend]` if flat - [ ] **Decide uv.lock strategy**: app (commit) vs library (.gitignore) - [ ] **Backup current state**: Create a branch or tag before starting ## Cleanup Old Artifacts Find and remove legacy linter comments: ```bash # Find files with old linter pragmas rg "# pylint:|# noqa:|# type: ignore" --files-with-matches # Find missing __init__.py files uv run ruff check --select=INP001 . ``` Remove these files after migration: - [ ] `requirements.txt`, `requirements-dev.txt` - [ ] `setup.py`, `setup.cfg`, `MANIFEST.in` - [ ] `.flake8`, `mypy.ini`, `pyrightconfig.json` - [ ] `tox.ini` (if not needed) - [ ] `Pipfile`, `Pipfile.lock` - [ ] Old virtual environments (`venv/`, `.venv/`) ## .gitignore Updates Add these entries: ```gitignore # Python __pycache__/ *.py[cod] .venv/ # Tools .ruff_cache/ .ty/ # uv (for libraries only - apps should commit uv.lock) # uv.lock ``` ## pyproject.toml Sections to Remove - [ ] `[tool.black]` - [ ] `[tool.isort]` - [ ] `[tool.mypy]` - [ ] `[tool.pyright]` - [ ] `[tool.pylint]` - [ ] `[tool.flake8]` (if present) ## Post-Migration Easy Wins Run these to modernize code automatically: ```bash # Pyupgrade modernization (typing, syntax) uv run ruff check --select=UP --fix . # Unnecessary variable assignments before return uv run ruff check --select=RET504 --fix . # Simplifications (conditionals, comprehensions) uv run ruff check --select=SIM --fix . # Remove commented-out code uv run ruff check --select=ERA --fix . ``` ## CI Cleanup - [ ] Remove scheduled CI triggers (activity without progress is theater) - [ ] Update CI to use `uv sync` and `uv run` - [ ] Pin GitHub Actions to SHA hashes - [ ] Set up security tooling (see [security-setup.md](./security-setup.md)) ## Gradual ty Adoption For legacy codebases with many type errors, start lenient: ```toml [tool.ty.terminal] error-on-warning = true [tool.ty.environment] python-version = "3.11" [tool.ty.rules] # Start with these ignored for legacy codebases possibly-missing-attribute = "ignore" unresolved-import = "ignore" invalid-argument-type = "ignore" not-subscriptable = "ignore" unresolved-attribute = "ignore" ``` Remove rules as you fix errors. Track progress: ```bash # Count remaining issues uv run ty check src/ 2>&1 | grep -c "error" ``` ## Supply Chain Security - [ ] Add pip-audit to dependency groups - [ ] Configure Dependabot with 7-day cooldown - [ ] Pin exact versions in production (`==` not `>=`) See [security-setup.md](./security-setup.md) for pip-audit and Dependabot configuration. ## Verification After migration, verify everything works: ```bash # Install all dependencies uv sync --all-groups # Run linting uv run ruff check . uv run ruff format --check . # Run type checking uv run ty check src/ # Run tests uv run pytest # Security audit uv run pip-audit # Build package (if distributable) uv build ``` -
pep723-scripts.md 5 KB
# PEP 723: Inline Script Metadata PEP 723 allows embedding dependency metadata directly in Python scripts, eliminating the need for separate `requirements.txt` or `pyproject.toml` files for simple scripts. ## When to Use PEP 723 **Use for:** - Single-file scripts with external dependencies - Quick automation scripts - Utility scripts shared between projects - Scripts that need to be self-contained **Don't use for:** - Multi-file projects (use `pyproject.toml`) - Reusable packages/libraries - Projects requiring complex configuration ## Basic Syntax The metadata block uses TOML format embedded in a special comment: ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = [ # "requests", # "rich", # ] # /// import requests from rich import print response = requests.get("https://api.example.com/data") print(response.json()) ``` ## Running Scripts ```bash # With uv (recommended) uv run script.py # Script handles its own dependencies automatically ./script.py # If shebang is set ``` ## Metadata Fields ### Required Python Version ```python # /// script # requires-python = ">=3.11" # /// ``` ### Dependencies ```python # /// script # dependencies = [ # "requests", # "click", # "rich", # ] # /// ``` ### Private Package Index ```python # /// script # dependencies = ["httpx"] # # [tool.uv] # extra-index-url = ["https://pypi.company.com/simple/"] # /// ``` ## Complete Example ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = [ # "httpx", # "rich", # "typer", # ] # /// """Fetch and display API data with nice formatting.""" import httpx import typer from rich.console import Console from rich.table import Table console = Console() app = typer.Typer() @app.command() def fetch(url: str, format: str = "table"): """Fetch data from URL and display it.""" with httpx.Client() as client: response = client.get(url) response.raise_for_status() data = response.json() if format == "table" and isinstance(data, list): table = Table() if data: for key in data[0].keys(): table.add_column(key) for item in data: table.add_row(*[str(v) for v in item.values()]) console.print(table) else: console.print_json(data=data) if __name__ == "__main__": app() ``` ## Creating Scripts with uv ```bash # Create new script with metadata uv init --script myscript.py # Add dependency to existing script uv add --script myscript.py requests # Remove dependency from script uv remove --script myscript.py requests ``` ## Shebang Options ### Basic (requires uv in PATH) ```python #!/usr/bin/env -S uv run --script ``` ### With specific Python version ```python #!/usr/bin/env -S uv run --python 3.12 --script ``` ### Quiet mode (suppress uv output) ```python #!/usr/bin/env -S uv run --quiet --script ``` ## Examples by Use Case ### Data Processing Script ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = ["pandas", "openpyxl"] # /// import pandas as pd import sys df = pd.read_excel(sys.argv[1]) print(df.describe()) ``` ### Web Scraping Script ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = ["httpx", "beautifulsoup4", "lxml"] # /// import httpx from bs4 import BeautifulSoup response = httpx.get("https://example.com") soup = BeautifulSoup(response.text, "lxml") print(soup.title.string) ``` ### CLI Tool Script ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = ["typer", "rich"] # /// import typer from rich import print app = typer.Typer() @app.command() def greet(name: str): print(f"[green]Hello, {name}![/green]") if __name__ == "__main__": app() ``` ### Async Script ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = ["httpx"] # /// import asyncio import httpx async def main(): async with httpx.AsyncClient() as client: urls = ["https://api1.example.com", "https://api2.example.com"] tasks = [client.get(url) for url in urls] responses = await asyncio.gather(*tasks) for r in responses: print(r.status_code) asyncio.run(main()) ``` ## Best Practices 1. **Always specify `requires-python`** - Ensures compatibility 2. **Pin major versions for Python** - Use `>=3.11` not `==3.11` 3. **Omit version constraints for dependencies** - Use `uv add --script` to add dependencies; let uv select versions 4. **Keep scripts focused** - One script, one purpose 5. **Add docstring** - Document what the script does 6. **Use type hints** - Improves readability and catches errors ## Limitations - No support for dependency groups - No support for editable installs - No support for local dependencies (use relative imports) - No lockfile (versions may vary between runs) For projects needing these features, use a full `pyproject.toml` setup instead. -
prek.md 5.8 KB
# prek: Fast Pre-commit Hooks [prek](https://github.com/j178/prek) is a fast, Rust-native drop-in replacement for pre-commit. It uses the same `.pre-commit-config.yaml` format and is fully compatible with existing configurations. ## Why prek over pre-commit? | Feature | prek | pre-commit | |---------|------|------------| | Speed | ~7x faster hook installation | Slower | | Dependencies | Single binary, no runtime needed | Requires Python | | Disk usage | Shared toolchains between hooks | Isolated environments | | Parallelism | Parallel repo cloning and hook execution | Sequential | | Python management | Uses uv automatically | Manual Python setup | | Monorepo support | Built-in workspace mode | Not supported | **Already using prek:** CPython, Apache Airflow, FastAPI, Ruff, Home Assistant, and [many more](https://github.com/j178/prek#who-is-using-prek). ## Installation See [security-setup.md](./security-setup.md#tool-installation) for installation options. ## Quick Start ### For Existing pre-commit Users prek is fully compatible with `.pre-commit-config.yaml`. Just replace commands: ```bash # Instead of: pre-commit install prek install # Instead of: pre-commit run --all-files prek run --all-files # Instead of: pre-commit autoupdate prek auto-update ``` ### New Setup 1. Create `.pre-commit-config.yaml`: ```yaml repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: <latest> # https://github.com/astral-sh/ruff-pre-commit/releases hooks: - id: ruff args: [--fix] - id: ruff-format ``` 2. Install and run: ```bash # Install git hooks prek install # Run manually on all files prek run --all-files # Run specific hook prek run ruff ``` ## Configuration For a complete, copy-paste-ready configuration, see [templates/pre-commit-config.yaml](../templates/pre-commit-config.yaml). ### Recommended `.pre-commit-config.yaml` > **Note:** Versions shown as `<latest>` are placeholders. Always check the linked releases for current stable versions before use. ```yaml # See https://pre-commit.com for more information default_language_version: python: python3.12 repos: # Ruff - linting and formatting - repo: https://github.com/astral-sh/ruff-pre-commit rev: <latest> # https://github.com/astral-sh/ruff-pre-commit/releases hooks: - id: ruff args: [--fix] - id: ruff-format # General file checks (prek builtin - faster, no external deps) - repo: builtin hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-merge-conflict # Security hooks - see security-setup.md for detailed guidance # Shell script linting - repo: https://github.com/koalaman/shellcheck-precommit rev: <latest> # https://github.com/koalaman/shellcheck-precommit/tags hooks: - id: shellcheck args: [--severity=error] # Secret detection - repo: https://github.com/Yelp/detect-secrets rev: <latest> # https://github.com/Yelp/detect-secrets/releases hooks: - id: detect-secrets args: [--baseline, .secrets.baseline] # GitHub Actions linting - repo: https://github.com/rhysd/actionlint rev: <latest> # https://github.com/rhysd/actionlint/releases hooks: - id: actionlint # GitHub Actions security audit - repo: https://github.com/zizmorcore/zizmor-pre-commit rev: <latest> # https://github.com/zizmorcore/zizmor-pre-commit/releases hooks: - id: zizmor args: [--persona=regular, --min-severity=medium, --min-confidence=medium] ``` See [security-setup.md](./security-setup.md) for detailed guidance on each security hook. ### Using Built-in Hooks prek includes Rust-native implementations of common hooks for extra speed: ```yaml repos: - repo: builtin hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-json - id: check-toml ``` ## Commands | Command | Description | |---------|-------------| | `prek install` | Install git hooks | | `prek uninstall` | Remove git hooks | | `prek run` | Run hooks on staged files | | `prek run --all-files` | Run on all files | | `prek run --last-commit` | Run on last commit's files | | `prek run HOOK [HOOK...]` | Run specific hook(s) | | `prek run -d src/` | Run on files in directory | | `prek auto-update` | Update hook versions | | `prek list` | List configured hooks | | `prek clean` | Remove cached environments | ## CI Configuration ### GitHub Actions ```yaml name: Pre-commit on: [push, pull_request] jobs: prek: runs-on: ubuntu-latest steps: - uses: actions/checkout@<sha> # <latest> https://github.com/actions/checkout/releases - uses: j178/prek-action@<sha> # <latest> https://github.com/j178/prek-action/releases ``` Or manually: ```yaml - name: Install prek run: uv tool install prek - name: Run hooks run: prek run --all-files ``` ## Makefile Integration ```makefile .PHONY: hooks hooks-install hooks: prek run --all-files hooks-install: prek install ``` ## Migration from pre-commit 1. Install prek: `uv tool install prek` 2. Remove pre-commit: `pip uninstall pre-commit` or `uv tool uninstall pre-commit` 3. Re-install hooks: `prek install` 4. (Optional) Clean old environments: `rm -rf ~/.cache/pre-commit` Your existing `.pre-commit-config.yaml` works unchanged. ## Best Practices 1. **Use `prek run --all-files` in CI** - Ensures all files are checked, not just changed ones 2. **Pin hook versions** - Use specific `rev` values, not branches 3. **Use `--cooldown-days` for auto-update** - Mitigates supply chain attacks: `prek auto-update --cooldown-days 7` 4. **Prefer built-in hooks** - Use `repo: builtin` for common checks (faster, offline) 5. **Run hooks before commit** - `prek install` sets this up automatically 6. **Initialize detect-secrets baseline** - Run `detect-secrets scan > .secrets.baseline` before first commit -
pyproject.md 5.6 KB
# pyproject.toml Configuration Reference Complete reference for configuring `pyproject.toml` for modern Python projects. **Important**: Always use `uv add` and `uv remove` to manage dependencies. Do not edit the `dependencies` or `dependency-groups` sections directly. ## Complete Example ```toml [project] name = "myproject" version = "0.1.0" description = "A modern Python project" readme = "README.md" license = "MIT" requires-python = ">=3.11" authors = [ { name = "Your Name", email = "you@example.com" } ] classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] dependencies = [ "requests", "rich", ] [project.optional-dependencies] # Use for optional features users can install cli = ["typer"] [project.scripts] myproject = "myproject.cli:main" [project.urls] Homepage = "https://github.com/org/myproject" Documentation = "https://myproject.readthedocs.io" Repository = "https://github.com/org/myproject" [build-system] requires = ["uv_build>=0.9,<1"] # Use latest 0.x; check https://pypi.org/project/uv-build/ build-backend = "uv_build" [dependency-groups] dev = ["ruff", "ty"] test = ["pytest", "pytest-cov", "hypothesis"] docs = ["sphinx", "myst-parser"] [tool.uv] default-groups = ["dev", "test"] [tool.ruff] line-length = 100 target-version = "py311" src = ["src"] [tool.ruff.lint] select = ["ALL"] ignore = [ "D", # pydocstyle (enable selectively) "COM812", # trailing comma (conflicts with formatter) "ISC001", # implicit string concat (conflicts with formatter) ] [tool.ruff.lint.per-file-ignores] "tests/**/*.py" = [ "S101", # assert allowed in tests "PLR2004", # magic values allowed in tests "ANN", # annotations optional in tests ] [tool.ruff.format] quote-style = "double" indent-style = "space" docstring-code-format = true [tool.pytest] testpaths = ["tests"] pythonpath = ["src"] addopts = [ "--cov=myproject", "--cov-report=term-missing", "--cov-fail-under=80", ] [tool.coverage.run] branch = true source = ["src/myproject"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", "if __name__ == .__main__.:", ] ``` ## Section Reference ### [project] Core project metadata following PEP 621. | Field | Required | Description | |-------|----------|-------------| | `name` | Yes | Package name (lowercase, hyphens) | | `version` | Yes | Semantic version | | `description` | No | One-line description | | `readme` | No | Path to README file | | `license` | No | SPDX license identifier | | `requires-python` | Recommended | Python version constraint | | `authors` | No | List of author dicts | | `dependencies` | No | Runtime dependencies | ### [project.optional-dependencies] **Rarely needed.** Only use for optional *runtime* features that end users install: ```toml [project.optional-dependencies] # User installs with: uv add myproject[postgres] postgres = ["psycopg2"] ``` **Do NOT use for dev tools**—use `[dependency-groups]` instead. ### [project.scripts] Console entry points: ```toml [project.scripts] myproject = "myproject.cli:main" myproject-serve = "myproject.server:run" ``` ### [build-system] Build backend configuration. Use `uv_build` for most projects: ```toml [build-system] requires = ["uv_build>=0.9,<1"] # Use latest 0.x; check https://pypi.org/project/uv-build/ build-backend = "uv_build" ``` `uv_build` is simpler and sufficient for most use cases. Use static versioning in `[project] version` rather than VCS-aware dynamic versioning. For flat layout (no `src/` directory), configure the module root: ```toml [tool.uv.build-backend] module-root = "" ``` > **Note:** These tools evolve rapidly. Prefer `>=X.Y,<X+1` constraints to automatically get newer releases within the same major version. ### [dependency-groups] Development dependencies (PEP 735). Unlike optional-dependencies, these are NOT installed by users: ```toml [dependency-groups] dev = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}] lint = ["ruff", "ty"] test = ["pytest", "pytest-cov"] audit = ["pip-audit"] docs = ["sphinx", "myst-parser"] ``` Install with: `uv sync --group dev --group test` ### [tool.uv] uv-specific configuration: ```toml [tool.uv] # Default groups to install with `uv sync` default-groups = ["dev", "test"] # Python version management python-preference = "managed" ``` ## Version Specifiers | Specifier | Meaning | |-----------|---------| | `>=1.0` | At least version 1.0 | | `>=1.0,<2.0` | Version 1.x only | | `~=1.4` | Compatible release (>=1.4, <2.0) | | `==1.4.*` | Any 1.4.x version | ## uv.lock Handling | Project Type | uv.lock in Git? | Why | |--------------|-----------------|-----| | Application | ✅ Commit | Reproducible deploys | | Library | ❌ .gitignore | Users resolve their own deps | ## Common Patterns ### Library Package ```toml [project] dependencies = [] # Minimal runtime deps [project.optional-dependencies] # Optional runtime features (user installs with mylib[async]) async = ["httpx"] [dependency-groups] dev = ["ruff", "ty"] test = ["pytest", "pytest-cov"] ``` ### Application Package ```toml [project] dependencies = [ "fastapi", "uvicorn", "sqlalchemy", ] [project.scripts] myapp = "myapp.main:run" [dependency-groups] dev = ["ruff", "ty", "pytest"] ``` ### CLI Tool ```toml [project] dependencies = [ "typer", "rich", ] [project.scripts] mytool = "mytool.cli:app" [dependency-groups] dev = ["ruff", "ty", "pytest"] ``` -
ruff-config.md 5.3 KB
# Ruff Configuration Reference Ruff is an extremely fast Python linter and formatter written in Rust. It replaces flake8, black, isort, pyupgrade, pydocstyle, and many other tools. ## Basic Setup Add to `pyproject.toml`: ```toml [tool.ruff] line-length = 100 target-version = "py311" src = ["src"] [tool.ruff.lint] select = ["ALL"] ignore = [ "D", # pydocstyle "COM812", # trailing comma (formatter conflict) "ISC001", # string concat (formatter conflict) ] [tool.ruff.format] quote-style = "double" indent-style = "space" docstring-code-format = true ``` ## Running Ruff ```bash # Lint uv run ruff check . uv run ruff check --fix . # Auto-fix uv run ruff check --fix --unsafe-fixes . # Including unsafe fixes # Format uv run ruff format . uv run ruff format --check . # Check only uv run ruff format --diff . # Show diff ``` ## Rule Categories Using `select = ["ALL"]` enables all rules. Common categories: | Code | Category | Description | |------|----------|-------------| | `E`, `W` | pycodestyle | Style errors and warnings | | `F` | Pyflakes | Logical errors | | `I` | isort | Import sorting | | `N` | pep8-naming | Naming conventions | | `D` | pydocstyle | Docstring conventions | | `UP` | pyupgrade | Python upgrade suggestions | | `B` | flake8-bugbear | Bug detection | | `S` | flake8-bandit | Security issues | | `A` | flake8-builtins | Built-in shadowing | | `C4` | flake8-comprehensions | Comprehension improvements | | `DTZ` | flake8-datetimez | Timezone-aware datetime | | `T10` | flake8-debugger | Debugger statements | | `T20` | flake8-print | Print statements | | `PT` | flake8-pytest-style | Pytest style | | `Q` | flake8-quotes | Quote consistency | | `SIM` | flake8-simplify | Simplification suggestions | | `TID` | flake8-tidy-imports | Import hygiene | | `ARG` | flake8-unused-arguments | Unused arguments | | `ERA` | eradicate | Commented-out code | | `PL` | Pylint | Pylint rules | | `RUF` | Ruff-specific | Ruff's own rules | | `ANN` | flake8-annotations | Type annotation checks | ## Recommended Ignores ### Always Ignore (Formatter Conflicts) ```toml ignore = [ "COM812", # missing-trailing-comma "ISC001", # single-line-implicit-string-concatenation ] ``` ### Common Ignores ```toml ignore = [ "D", # Docstrings (enable selectively) "ANN401", # Dynamically typed Any "TD002", # Missing TODO author "TD003", # Missing TODO link "FIX002", # Line contains TODO ] ``` ## Per-File Ignores ```toml [tool.ruff.lint.per-file-ignores] # Tests "tests/**/*.py" = [ "S101", # assert usage "PLR2004", # magic values "ANN", # type annotations "D", # docstrings ] # Scripts "scripts/**/*.py" = [ "T20", # print statements "INP001", # implicit namespace package ] # __init__.py "__init__.py" = [ "F401", # unused imports (re-exports) ] # Migrations "**/migrations/*.py" = [ "ALL", # ignore all ] ``` ## Import Sorting (isort) ```toml [tool.ruff.lint.isort] force-single-line = false known-first-party = ["myproject"] required-imports = ["from __future__ import annotations"] section-order = [ "future", "standard-library", "third-party", "first-party", "local-folder", ] ``` ## Docstring Style (pydocstyle) If enabling docstring checks: ```toml [tool.ruff.lint] select = ["D"] ignore = [ "D100", # Missing module docstring "D104", # Missing public package docstring "D203", # 1 blank line before class docstring (conflicts D211) "D213", # Multi-line summary second line (conflicts D212) ] [tool.ruff.lint.pydocstyle] convention = "google" # or "numpy", "pep257" ``` ## Formatter Configuration ```toml [tool.ruff.format] quote-style = "double" # or "single" indent-style = "space" # or "tab" skip-magic-trailing-comma = false line-ending = "auto" # or "lf", "crlf" docstring-code-format = true docstring-code-line-length = 80 ``` ## Type Checking Ruff does NOT do type checking. Use **ty** (from Astral, the same team behind ruff and uv): ```bash # Add ty to dev dependencies uv add --group dev ty # Run type checking uv run ty check src/ ``` ty is significantly faster than mypy or pyright and integrates well with the modern Python toolchain. ## CI Configuration ```yaml # GitHub Actions - name: Lint run: uv run ruff check --output-format=github . - name: Format check run: uv run ruff format --check . ``` ## Migration from Other Tools ### From flake8 Ruff covers most flake8 plugins. Remove: - flake8 - flake8-* plugins - .flake8 config file ### From black Remove black and use `ruff format`. Remove: - black - [tool.black] config ### From isort Ruff includes isort. Remove: - isort - [tool.isort] config Use `[tool.ruff.lint.isort]` for isort settings. ## Code Modernization Run pyupgrade rules to modernize syntax to your target Python version: ```bash uv run ruff check --select=UP --fix . # Auto-fix upgrades uv run ruff check --select=UP . # Preview only ``` Common modernizations include: - `typing.Optional[X]` → `X | None` - `typing.List[X]` → `list[X]` - `super(ClassName, self)` → `super()` - Format strings and other syntax upgrades ## Line Length Migration If migrating from 120 to 100 char lines, expect manual fixes. For less churn during initial migration, keep existing: ```toml line-length = 120 # Match existing; tighten later ``` -
security-setup.md 6.4 KB
# Security Setup Security tooling for Python projects: pre-commit hooks, CI auditing, and dependency scanning. ## Tool Installation Install these tools before running the quick setup commands below. ### prek (pre-commit runner) ```bash # Homebrew (recommended) brew install prek # Cargo cargo install prek # Standalone installer curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/latest/download/prek-installer.sh | sh ``` ### Security tools Pre-commit hooks auto-install tools when run via prek. For manual CLI usage: ```bash # Homebrew (macOS/Linux) brew install actionlint shellcheck # Python tools via uv uv tool install detect-secrets uv tool install zizmor ``` Alternative installation methods: - **actionlint**: `go install github.com/rhysd/actionlint/cmd/actionlint@latest` - **zizmor**: `cargo install zizmor` - **detect-secrets**: `pipx install detect-secrets` ## Quick Setup ```bash # 1. Install security hooks prek install # 2. Initialize secrets baseline detect-secrets scan > .secrets.baseline # 3. Audit existing workflows actionlint .github/workflows/ zizmor .github/workflows/ ``` See [templates/pre-commit-config.yaml](../templates/pre-commit-config.yaml) for a complete hook configuration. ## Tool Matrix | Tool | Runs | Catches | |------|------|---------| | **shellcheck** | pre-commit | Shell script bugs, quoting issues | | **detect-secrets** | pre-commit | Leaked API keys, passwords, tokens | | **actionlint** | pre-commit, CI | Workflow syntax errors, invalid refs | | **zizmor** | pre-commit, CI | Workflow security issues, excessive permissions | | **pip-audit** | CI, manual | Known CVEs in dependencies | | **Dependabot** | scheduled | Outdated dependencies with vulnerabilities | ## Pre-commit Hooks These run locally before each commit via prek. ### shellcheck - Shell Script Linting Catches common shell scripting errors: unquoted variables, undefined variables, deprecated syntax. ```yaml # In .pre-commit-config.yaml - repo: https://github.com/koalaman/shellcheck-precommit rev: <latest> # https://github.com/koalaman/shellcheck-precommit/tags hooks: - id: shellcheck args: [--severity=error] # Start strict, adjust if needed ``` Common findings: - `SC2086`: Unquoted variable expansion (word splitting risk) - `SC2046`: Unquoted command substitution - `SC2155`: Declare and assign separately to avoid masking return values ### detect-secrets - Secret Detection Prevents accidentally committing API keys, passwords, and tokens. ```yaml - repo: https://github.com/Yelp/detect-secrets rev: <latest> # https://github.com/Yelp/detect-secrets/releases hooks: - id: detect-secrets args: [--baseline, .secrets.baseline] ``` **First-time setup:** ```bash # Generate baseline of existing "secrets" (false positives to ignore) detect-secrets scan > .secrets.baseline # Review the baseline - ensure no real secrets cat .secrets.baseline # Commit the baseline git add .secrets.baseline ``` **When hook fails:** ```bash # View the finding (non-interactive) detect-secrets audit --report .secrets.baseline ``` If false positive: update baseline with `detect-secrets scan --update .secrets.baseline` If real secret: remove from code and rotate the credential. ## CI Security These run in GitHub Actions on every push/PR. ### actionlint - Workflow Syntax Validation Catches syntax errors, invalid action references, and type mismatches before they fail in CI. ```yaml - repo: https://github.com/rhysd/actionlint rev: <latest> # https://github.com/rhysd/actionlint/releases hooks: - id: actionlint ``` Run manually: ```bash actionlint .github/workflows/ ``` Common findings: - Invalid event triggers - Undefined workflow inputs - Shell syntax errors in `run:` blocks - Invalid action version references ### zizmor - Workflow Security Audit Finds security issues in GitHub Actions workflows: excessive permissions, injection risks, untrusted inputs. ```yaml - repo: https://github.com/zizmorcore/zizmor-pre-commit rev: <latest> # https://github.com/zizmorcore/zizmor-pre-commit/releases hooks: - id: zizmor args: [--persona=regular, --min-severity=medium, --min-confidence=medium] ``` Run manually: ```bash zizmor .github/workflows/ ``` **Fixing `excessive-permissions`:** By default, workflows get `write` access to everything. Lock down with explicit permissions: ```yaml # Read-only workflows (lint, test, audit) permissions: contents: read # Workflows that push or create releases permissions: contents: write # Workflows that comment on PRs permissions: contents: read pull-requests: write ``` Common findings: - `excessive-permissions`: No `permissions:` block - `template-injection`: Using `${{ github.event.* }}` unsafely - `unpinned-action`: Actions not pinned to SHA - `dangerous-triggers`: `pull_request_target` with checkout ## Dependency Security ### pip-audit - Vulnerability Scanning Checks installed packages against the Python Advisory Database (PyPA) for known CVEs. **Setup:** ```toml # pyproject.toml [dependency-groups] audit = ["pip-audit"] ``` **Usage:** ```bash # Audit current environment uv run pip-audit # Audit without installing (faster for CI) uv run pip-audit . # Fix automatically (upgrades vulnerable packages) uv run pip-audit --fix ``` **In CI:** ```yaml - name: Security audit run: uv run pip-audit . ``` **When vulnerabilities found:** 1. Check if the CVE affects your usage (many are in unused code paths) 2. Update the package: `uv add <package>@latest` 3. If no fix available: evaluate risk, consider alternatives, or add to ignore list ### Dependabot - Automated Updates Automatically creates PRs for outdated dependencies. Copy [templates/dependabot.yml](../templates/dependabot.yml) to `.github/dependabot.yml`. **How pip-audit and Dependabot work together:** | Tool | Trigger | Scope | |------|---------|-------| | pip-audit | Every CI run | Known CVEs in current deps | | Dependabot | Weekly schedule | All outdated deps, security + non-security | - **pip-audit** catches: "You have a vulnerable version right now" - **Dependabot** prevents: "You'll fall behind and accumulate vulnerabilities" The 7-day cooldown protects against attackers publishing malicious updates and hoping for quick adoption before detection. See [dependabot.md](./dependabot.md) for advanced configuration. See [prek.md](./prek.md) for complete pre-commit hook configuration including security hooks. -
testing.md 5.1 KB
# Testing with pytest Configuration and best practices for pytest with coverage enforcement. ## Setup Add test dependencies: ```bash uv add --group test pytest pytest-cov hypothesis ``` ## pyproject.toml Configuration ```toml [tool.pytest] testpaths = ["tests"] pythonpath = ["src"] addopts = [ "-ra", # Show summary of all test outcomes "--strict-markers", # Error on unknown markers "--strict-config", # Error on config issues "--cov=myproject", # Coverage for package "--cov-report=term-missing", # Show missing lines "--cov-fail-under=80", # Minimum coverage ] markers = [ "slow: marks tests as slow", "integration: marks integration tests", ] filterwarnings = [ "error", # Treat warnings as errors "ignore::DeprecationWarning:third_party.*", ] [tool.coverage.run] branch = true source = ["src/myproject"] omit = [ "*/__main__.py", "*/conftest.py", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", "if __name__ == .__main__.:", "raise NotImplementedError", "@abstractmethod", ] fail_under = 80 show_missing = true ``` ## Project Structure ``` myproject/ ├── src/ │ └── myproject/ │ ├── __init__.py │ └── core.py ├── tests/ │ ├── __init__.py │ ├── conftest.py # Shared fixtures │ ├── test_core.py │ └── integration/ │ └── test_api.py └── pyproject.toml ``` ## Running Tests ```bash # Run all tests uv run pytest # Run with verbose output uv run pytest -v # Run specific file uv run pytest tests/test_core.py # Run specific test uv run pytest tests/test_core.py::test_function_name # Run tests matching pattern uv run pytest -k "test_parse" # Run marked tests uv run pytest -m "not slow" # Stop on first failure uv run pytest -x # Run last failed uv run pytest --lf ``` ## Coverage Commands ```bash # Run with coverage uv run pytest --cov=myproject # Generate HTML report uv run pytest --cov=myproject --cov-report=html open htmlcov/index.html # Coverage without running tests (use existing data) uv run coverage report uv run coverage html ``` ## Writing Tests ### Basic Test ```python # tests/test_core.py from myproject.core import add_numbers def test_add_numbers(): assert add_numbers(2, 3) == 5 def test_add_negative(): assert add_numbers(-1, 1) == 0 ``` ### Using Fixtures ```python # tests/conftest.py import pytest from myproject.db import Database @pytest.fixture def db(): """Provide a test database.""" database = Database(":memory:") database.init() yield database database.close() @pytest.fixture def sample_data(db): """Populate database with sample data.""" db.insert({"name": "test"}) return db ``` ```python # tests/test_db.py def test_query(sample_data): result = sample_data.query("test") assert result is not None ``` ### Parametrized Tests ```python import pytest @pytest.mark.parametrize("input,expected", [ ("hello", 5), ("", 0), ("test", 4), ]) def test_string_length(input, expected): assert len(input) == expected ``` ### Testing Exceptions ```python import pytest from myproject.core import divide def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): divide(1, 0) def test_divide_by_zero_message(): with pytest.raises(ZeroDivisionError, match="division by zero"): divide(1, 0) ``` ### Async Tests ```bash uv add --group test pytest-asyncio ``` ```python import pytest @pytest.mark.asyncio async def test_async_function(): result = await fetch_data() assert result is not None ``` ## Property-Based Testing with Hypothesis ```bash uv add --group test hypothesis ``` ```python from hypothesis import given, strategies as st from myproject.core import reverse_string @given(st.text()) def test_reverse_is_reversible(s): assert reverse_string(reverse_string(s)) == s @given(st.integers(), st.integers()) def test_add_commutative(a, b): assert add(a, b) == add(b, a) ``` ## Markers ```python import pytest @pytest.mark.slow def test_slow_operation(): # Long running test pass @pytest.mark.integration def test_api_call(): # Requires external service pass @pytest.mark.skip(reason="Not implemented yet") def test_future_feature(): pass @pytest.mark.skipif(sys.platform == "win32", reason="Unix only") def test_unix_feature(): pass ``` ## CI Configuration ```yaml # GitHub Actions - name: Checkout uses: actions/checkout@<sha> # <latest> https://github.com/actions/checkout/releases - name: Run tests run: | uv sync --group test uv run pytest --cov-report=xml - name: Security audit run: | uv sync --group audit uv run pip-audit - name: Upload coverage uses: codecov/codecov-action@<sha> # <latest> https://github.com/codecov/codecov-action/releases with: files: ./coverage.xml ``` ## Makefile Target ```makefile .PHONY: test test: uv run pytest test-cov: uv run pytest --cov-report=html open htmlcov/index.html test-fast: uv run pytest -x -q --no-cov ``` -
uv-commands.md 4.5 KB
# uv Command Reference `uv` is an extremely fast Python package and project manager written in Rust. It replaces pip, virtualenv, pip-tools, pipx, and pyenv. **Key principle:** Always use `uv run` to execute commands. Never manually activate virtual environments. ## Installation ```bash # macOS/Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # Homebrew brew install uv # pipx pipx install uv ``` ## Project Commands ### Initialize Projects | Command | Description | |---------|-------------| | `uv init` | Create new project (application) | | `uv init --package` | Create distributable package with src/ layout | | `uv init --lib` | Create library package | | `uv init --script file.py` | Create script with PEP 723 metadata | ### Dependency Management | Command | Description | |---------|-------------| | `uv add <pkg>` | Add dependency to project | | `uv add <pkg> --group dev` | Add to dependency group | | `uv add <pkg> --optional feature` | Add to optional dependency | | `uv remove <pkg>` | Remove dependency | | `uv lock` | Update lock file without installing | ### Environment Management uv manages virtual environments automatically. Do not manually create or activate venvs. | Command | Description | |---------|-------------| | `uv sync` | Install dependencies (creates venv if needed) | | `uv sync --all-groups` | Install all dependency groups | | `uv sync --group dev` | Install specific group | | `uv sync --frozen` | Install from lock file exactly | ### Running Code | Command | Description | |---------|-------------| | `uv run <cmd>` | Run command in project venv | | `uv run python script.py` | Run Python script | | `uv run pytest` | Run pytest | | `uv run --with pkg cmd` | Run with temporary dependency | ### Building & Publishing | Command | Description | |---------|-------------| | `uv build` | Build wheel and sdist | | `uv build --wheel` | Build wheel only | | `uv build --sdist` | Build sdist only | | `uv publish` | Publish to PyPI | | `uv publish --token $TOKEN` | Publish with API token | ## Tool Commands Run Python tools without installing globally: ```bash # Run any tool uv tool run ruff check . uvx ruff check . # shorthand # Install tool globally uv tool install ruff # List installed tools uv tool list # Upgrade tool uv tool upgrade ruff ``` ## Python Version Management ```bash # Install Python version uv python install 3.12 # List available versions uv python list # Pin project to Python version uv python pin 3.12 # Use specific version uv run --python 3.11 pytest ``` ## Script Commands (PEP 723) ```bash # Create script with inline metadata uv init --script myscript.py # Add dependency to script uv add --script myscript.py requests # Run script (auto-installs deps) uv run myscript.py ``` ## Common Workflows ### New Application Project ```bash uv init myapp cd myapp uv add fastapi uvicorn uv add --group dev ruff pytest uv sync --all-groups uv run uvicorn myapp:app ``` ### New Library Package ```bash uv init --package mylib cd mylib uv add --group dev ruff pytest pytest-cov uv add --group docs sphinx uv sync --all-groups uv run pytest uv build ``` ### Add Tool to Existing Project ```bash cd existing-project uv add --group dev ruff uv run ruff check . ``` ### One-off Script Execution ```bash # Run script with dependencies (no project needed) uv run --with requests --with rich script.py # Or use PEP 723 inline metadata uv run script_with_metadata.py ``` ## Environment Variables | Variable | Description | |----------|-------------| | `UV_CACHE_DIR` | Cache directory location | | `UV_NO_CACHE` | Disable caching | | `UV_PYTHON` | Default Python version | | `UV_PROJECT` | Project directory path | | `UV_PROJECT_ENVIRONMENT` | Custom venv directory (e.g., `.venv-dev`) | | `UV_SYSTEM_PYTHON` | Use system Python | ## Container/Host Development When developing on a host machine while also running in containers, you can use separate venvs to avoid rebuilding on each context switch: ```bash # On host machine (add to shell profile or .envrc) export UV_PROJECT_ENVIRONMENT=.venv-dev # Now host uses .venv-dev, containers use default .venv uv sync # creates .venv-dev on host ``` Add both to `.gitignore`: ``` .venv/ .venv-dev/ ``` This avoids rebuilding the venv when switching between host and container (different OS, Python versions, or native dependencies). ## Performance Tips - uv caches aggressively; first install may be slower - Use `uv sync --frozen` in CI for reproducible builds - Use `uv cache clean` if cache grows too large
-
-
templates
-
dependabot.yml 958 B
# Dependabot configuration for Python projects # Copy to .github/dependabot.yml # # See references/dependabot.md for advanced configuration. version: 2 updates: # Python dependencies (pyproject.toml, requirements.txt) - package-ecosystem: pip directory: / schedule: interval: weekly # 7-day cooldown: don't update packages published < 7 days ago # Mitigates supply chain attacks via malicious updates cooldown: default-days: 7 # Group minor/patch updates to reduce PR noise groups: dev-dependencies: dependency-type: development update-types: [minor, patch] production-dependencies: dependency-type: production update-types: [patch] # GitHub Actions - package-ecosystem: github-actions directory: / schedule: interval: weekly cooldown: default-days: 7 groups: actions: patterns: ["*"] update-types: [minor, patch] -
pre-commit-config.yaml 2.2 KB
# Pre-commit configuration for Python projects # Copy to your repo root as .pre-commit-config.yaml # # IMPORTANT: Replace all <latest> placeholders with actual versions. # Check each linked releases page for current stable versions. # # Usage: # prek install # Install git hooks # prek run --all-files # Run on all files # # See references/prek.md for more commands. default_language_version: python: python3.12 repos: # === Code Quality === # Ruff - linting and formatting (replaces black, isort, flake8, pylint) - repo: https://github.com/astral-sh/ruff-pre-commit rev: <latest> # https://github.com/astral-sh/ruff-pre-commit/releases hooks: - id: ruff args: [--fix] - id: ruff-format # === General File Checks === # prek builtin hooks - faster, no external deps - repo: builtin hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-toml - id: check-merge-conflict # === Security Hooks === # See references/security-setup.md for detailed guidance on each hook. # Shell script linting - catches unquoted vars, undefined vars, deprecated syntax - repo: https://github.com/koalaman/shellcheck-precommit rev: <latest> # https://github.com/koalaman/shellcheck-precommit/tags hooks: - id: shellcheck args: [--severity=error] # Secret detection - prevents committing API keys, passwords, tokens # Run `detect-secrets scan > .secrets.baseline` before first commit - repo: https://github.com/Yelp/detect-secrets rev: <latest> # https://github.com/Yelp/detect-secrets/releases hooks: - id: detect-secrets args: [--baseline, .secrets.baseline] # GitHub Actions linting - catches syntax errors, invalid refs - repo: https://github.com/rhysd/actionlint rev: <latest> # https://github.com/rhysd/actionlint/releases hooks: - id: actionlint # GitHub Actions security audit - excessive permissions, injection risks - repo: https://github.com/zizmorcore/zizmor-pre-commit rev: <latest> # https://github.com/zizmorcore/zizmor-pre-commit/releases hooks: - id: zizmor args: [--persona=regular, --min-severity=medium, --min-confidence=medium]
-
-
SKILL.md 9.8 KB
--- name: modern-python description: Configures Python projects with modern tooling (uv, ruff, ty). Use when creating projects, writing standalone scripts, or migrating from pip/Poetry/mypy/black. --- # Modern Python Guide for modern Python tooling and best practices, based on [trailofbits/cookiecutter-python](https://github.com/trailofbits/cookiecutter-python). ## When to Use This Skill - Creating a new Python project or package - Setting up `pyproject.toml` configuration - Configuring development tools (linting, formatting, testing) - Writing Python scripts with external dependencies - Migrating from legacy tools (when user requests it) ## When NOT to Use This Skill - **User wants to keep legacy tooling**: Respect existing workflows if explicitly requested - **Python < 3.11 required**: These tools target modern Python - **Non-Python projects**: Mixed codebases where Python isn't primary ## Anti-Patterns to Avoid | Avoid | Use Instead | |-------|-------------| | `[tool.ty]` python-version | `[tool.ty.environment]` python-version | | `uv pip install` | `uv add` and `uv sync` | | Editing pyproject.toml manually to add deps | `uv add <pkg>` / `uv remove <pkg>` | | `hatchling` build backend | `uv_build` (simpler, sufficient for most cases) | | Poetry | uv (faster, simpler, better ecosystem integration) | | requirements.txt | PEP 723 for scripts, pyproject.toml for projects | | mypy / pyright | ty (faster, from Astral team) | | `[project.optional-dependencies]` for dev tools | `[dependency-groups]` (PEP 735) | | Manual virtualenv activation (`source .venv/bin/activate`) | `uv run <cmd>` | | pre-commit | prek (faster, no Python runtime needed) | **Key principles:** - Always use `uv add` and `uv remove` to manage dependencies - Never manually activate or manage virtual environments—use `uv run` for all commands - Use `[dependency-groups]` for dev/test/docs dependencies, not `[project.optional-dependencies]` ## Decision Tree ``` What are you doing? │ ├─ Single-file script with dependencies? │ └─ Use PEP 723 inline metadata (./references/pep723-scripts.md) │ ├─ New multi-file project (not distributed)? │ └─ Minimal uv setup (see Quick Start below) │ ├─ New reusable package/library? │ └─ Full project setup (see Full Setup below) │ └─ Migrating existing project? └─ See Migration Guide below ``` ## Tool Overview | Tool | Purpose | Replaces | |------|---------|----------| | **uv** | Package/dependency management | pip, virtualenv, pip-tools, pipx, pyenv | | **ruff** | Linting AND formatting | flake8, black, isort, pyupgrade, pydocstyle | | **ty** | Type checking | mypy, pyright (faster alternative) | | **pytest** | Testing with coverage | unittest | | **prek** | Pre-commit hooks ([setup](./references/prek.md)) | pre-commit (faster, Rust-native) | ### Security Tools | Tool | Purpose | When It Runs | |------|---------|--------------| | **shellcheck** | Shell script linting | pre-commit | | **detect-secrets** | Secret detection | pre-commit | | **actionlint** | Workflow syntax validation | pre-commit, CI | | **zizmor** | Workflow security audit | pre-commit, CI | | **pip-audit** | Dependency vulnerability scanning | CI, manual | | **Dependabot** | Automated dependency updates | scheduled | See [security-setup.md](./references/security-setup.md) for configuration and usage. ## Quick Start: Minimal Project For simple multi-file projects not intended for distribution: ```bash # Create project with uv uv init myproject cd myproject # Add dependencies uv add requests rich # Add dev dependencies uv add --group dev pytest ruff ty # Run code uv run python src/myproject/main.py # Run tools uv run pytest uv run ruff check . ``` ## Full Project Setup If starting from scratch, ask the user if they prefer to use the Trail of Bits cookiecutter template to bootstrap a complete project with already preconfigured tooling. ```bash uvx cookiecutter gh:trailofbits/cookiecutter-python ``` ### 1. Create Project Structure ```bash uv init --package myproject cd myproject ``` This creates: ``` myproject/ ├── pyproject.toml ├── README.md ├── src/ │ └── myproject/ │ └── __init__.py └── .python-version ``` ### 2. Configure pyproject.toml See [pyproject.md](./references/pyproject.md) for complete configuration reference. Key sections: ```toml [project] name = "myproject" version = "0.1.0" requires-python = ">=3.11" dependencies = [] [dependency-groups] dev = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}] lint = ["ruff", "ty"] test = ["pytest", "pytest-cov"] audit = ["pip-audit"] [tool.ruff] line-length = 100 target-version = "py311" [tool.ruff.lint] select = ["ALL"] ignore = ["D", "COM812", "ISC001"] [tool.pytest] addopts = ["--cov=myproject", "--cov-fail-under=80"] [tool.ty.terminal] error-on-warning = true [tool.ty.environment] python-version = "3.11" [tool.ty.rules] # Strict from day 1 for new projects possibly-unresolved-reference = "error" unused-ignore-comment = "warn" ``` ### 3. Install Dependencies ```bash # Install all dependency groups uv sync --all-groups # Or install specific groups uv sync --group dev ``` ### 4. Add Makefile ```makefile .PHONY: dev lint format test build dev: uv sync --all-groups lint: uv run ruff format --check && uv run ruff check && uv run ty check src/ format: uv run ruff format . test: uv run pytest build: uv build ``` ## Migration Guide When a user requests migration from legacy tooling: ### From requirements.txt + pip First, determine the nature of the code: **For standalone scripts**: Convert to PEP 723 inline metadata (see [pep723-scripts.md](./references/pep723-scripts.md)) **For projects**: ```bash # Initialize uv in existing project uv init --bare # Add dependencies using uv (not by editing pyproject.toml) uv add requests rich # add each package # Or import from requirements.txt (review each package before adding) # Note: Complex version specifiers may need manual handling grep -v '^#' requirements.txt | grep -v '^-' | grep -v '^\s*$' | while read -r pkg; do uv add "$pkg" || echo "Failed to add: $pkg" done uv sync ``` Then: 1. Delete `requirements.txt`, `requirements-dev.txt` 2. Delete virtual environment (`venv/`, `.venv/`) 3. Add `uv.lock` to version control ### From setup.py / setup.cfg 1. Run `uv init --bare` to create pyproject.toml 2. Use `uv add` to add each dependency from `install_requires` 3. Use `uv add --group dev` for dev dependencies 4. Copy non-dependency metadata (name, version, description, etc.) to `[project]` 5. Delete `setup.py`, `setup.cfg`, `MANIFEST.in` ### From flake8 + black + isort 1. Remove flake8, black, isort via `uv remove` 2. Delete `.flake8`, `pyproject.toml [tool.black]`, `[tool.isort]` configs 3. Add ruff: `uv add --group dev ruff` 4. Add ruff configuration (see [ruff-config.md](./references/ruff-config.md)) 5. Run `uv run ruff check --fix .` to apply fixes 6. Run `uv run ruff format .` to format ### From mypy / pyright 1. Remove mypy/pyright via `uv remove` 2. Delete `mypy.ini`, `pyrightconfig.json`, or `[tool.mypy]`/`[tool.pyright]` sections 3. Add ty: `uv add --group dev ty` 4. Run `uv run ty check src/` ## Quick Reference: uv Commands | Command | Description | |---------|-------------| | `uv init` | Create new project | | `uv init --package` | Create distributable package | | `uv add <pkg>` | Add dependency | | `uv add --group dev <pkg>` | Add to dependency group | | `uv remove <pkg>` | Remove dependency | | `uv sync` | Install dependencies | | `uv sync --all-groups` | Install all dependency groups | | `uv run <cmd>` | Run command in venv | | `uv run --with <pkg> <cmd>` | Run with temporary dependency | | `uv build` | Build package | | `uv publish` | Publish to PyPI | ### Ad-hoc Dependencies with `--with` Use `uv run --with` for one-off commands that need packages not in your project: ```bash # Run Python with a temporary package uv run --with requests python -c "import requests; print(requests.get('https://httpbin.org/ip').json())" # Run a module with temporary deps uv run --with rich python -m rich.progress # Multiple packages uv run --with requests --with rich python script.py # Combine with project deps (adds to existing venv) uv run --with httpx pytest # project deps + httpx ``` **When to use `--with` vs `uv add`:** - `uv add`: Package is a project dependency (goes in pyproject.toml/uv.lock) - `--with`: One-off usage, testing, or scripts outside a project context See [uv-commands.md](./references/uv-commands.md) for complete reference. ## Quick Reference: Dependency Groups ```toml [dependency-groups] dev = ["ruff", "ty"] test = ["pytest", "pytest-cov", "hypothesis"] docs = ["sphinx", "myst-parser"] ``` Install with: `uv sync --group dev --group test` ## Best Practices Checklist - [ ] Use `src/` layout for packages - [ ] Set `requires-python = ">=3.11"` - [ ] Configure ruff with `select = ["ALL"]` and explicit ignores - [ ] Use ty for type checking - [ ] Enforce test coverage minimum (80%+) - [ ] Use dependency groups instead of extras for dev tools - [ ] Add `uv.lock` to version control - [ ] Use PEP 723 for standalone scripts ## Read Next - [migration-checklist.md](./references/migration-checklist.md) - Step-by-step migration cleanup - [pyproject.md](./references/pyproject.md) - Complete pyproject.toml reference - [uv-commands.md](./references/uv-commands.md) - uv command reference - [ruff-config.md](./references/ruff-config.md) - Ruff linting/formatting configuration - [testing.md](./references/testing.md) - pytest and coverage setup - [pep723-scripts.md](./references/pep723-scripts.md) - PEP 723 inline script metadata - [prek.md](./references/prek.md) - Fast pre-commit hooks with prek - [security-setup.md](./references/security-setup.md) - Security hooks and dependency scanning - [dependabot.md](./references/dependabot.md) - Automated dependency updates
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.