Modern Python Skill
Modern Python tooling skill adapted from Trail of Bits coding standards. Mandates the use of uv (package management), ruff (linting and formatting), ty (type checking), and pytest (testing) as the standard Python toolchain. Based on patterns from trailofbits/cookiecutter-python for consistent, high-quality Python projects.
- Project initialization with modern Python toolchain (uv + ruff + ty + pytest)
- Migration from legacy tools (pip/Poetry/pipenv to uv, black/flake8/isort to ruff, mypy to ty)
- pyproject.toml configuration for all tools (single source of truth)
- Dependency management with uv (lock files, dependency groups, virtual environments)
- Linting and formatting with ruff (replaces flake8, isort, black, pyflakes, pycodestyle)
- Type checking with ty (Rust-based, faster than mypy)
- Testing with pytest, pytest-cov, and hypothesis
- CI/CD configuration with GitHub Actions
- Dependabot setup for automated dependency updates
- Pre-commit hook configuration
Overview
This skill implements Trail of Bits' modern Python coding standards for the agent-studio framework. The core philosophy is: use Rust-based tools for faster feedback loops, especially when working with AI agents. Every tool in this stack (uv, ruff, ty) is written in Rust and provides sub-second execution times, enabling tight iteration cycles.
Source repository: https://github.com/trailofbits/skills
Template: https://github.com/trailofbits/cookiecutter-python
License: CC-BY-SA-4.0
When to Use
- When creating new Python projects from scratch
- When migrating Python projects from legacy tooling
- When setting up CI/CD pipelines for Python projects
- When standardizing Python tooling across a team
- When writing standalone Python scripts that need proper structure
- When an AI agent needs fast feedback from Python tooling
Iron Laws
- ALWAYS configure all Python tooling in
pyproject.toml -- no separate config files (setup.cfg, .flake8, mypy.ini, black.toml) are permitted.
- ALWAYS use
uv add/uv remove for dependency management -- never use bare pip install in projects managed by uv.
- NEVER commit
venv/, .venv/, or pip-generated requirements.txt -- commit uv.lock for reproducible builds.
- ALWAYS use
uv run to execute tools and scripts -- this ensures the correct virtual environment and dependency resolution.
- NEVER use legacy linting/formatting tools (flake8, black, isort, mypy) when ruff and ty are available -- consolidate to the Rust-based stack for speed and consistency.
Anti-Patterns
| Anti-Pattern | Why It Fails | Correct Approach |
|---|
Using pip install directly in a uv-managed project | Bypasses lockfile and dependency resolution; creates reproducibility drift | Use uv add <pkg> to add dependencies and uv sync to install |
Maintaining .flake8, mypy.ini, or black.toml config files | Fragments configuration across multiple files; hard to maintain and audit | Consolidate all tool config into pyproject.toml under [tool.ruff] and [tool.ty] |
Running python script.py instead of uv run python script.py | Uses system Python instead of project venv; dependency mismatches | Always use uv run to execute within the managed environment |
Globally installing CLI tools with pip install --user | Pollutes global environment; version conflicts across projects | Use uv tool run <tool> or uvx <tool> for one-off tool execution |
Ignoring ruff security rules (S select) | Misses bandit-equivalent security checks like hardcoded passwords and SQL injection | Enable select = ["S"] in [tool.ruff.lint] for security linting |
The Modern Python Stack
| Tool | Replaces | Purpose | Speed |
|---|
| uv | pip, Poetry, pipenv, pip-tools | Package & project management | 10-100x faster |
| ruff | flake8, isort, black, pyflakes, pycodestyle, pydocstyle | Linting + formatting | 10-100x faster |
| ty | mypy, pyright, pytype | Type checking | 5-10x faster |
| pytest | unittest | Testing | -- |
| hypothesis | (manual property tests) | Property-based testing | -- |
Project Setup
New Project
uv init my-project
cd my-project
uv add --group dev ruff ty
uv add --group test pytest pytest-cov hypothesis
uv add --group docs sphinx myst-parser
uv sync --all-groups
pyproject.toml Configuration
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = ["ruff", "ty"]
test = ["pytest", "pytest-cov", "hypothesis"]
docs = ["sphinx", "myst-parser"]
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = [
"E",
"W",
"F",
"I",
"N",
"UP",
"B",
"A",
"C4",
"SIM",
"S",
"TCH",
"RUF",
]
= []
=
=
= []
= [
,
,
,
]
= []
=
=
=
= [
,
,
]
Daily Workflow Commands
Package Management (uv)
uv add requests
uv add --group dev ipdb
uv remove requests
uv lock --upgrade
uv lock --upgrade-package requests
uv run python script.py
uv run --with httpie http GET https://api.example.com
Linting and Formatting (ruff)
uv run ruff check .
uv run ruff check --fix .
uv run ruff format .
uv run ruff format --check .
uv run ruff check --select S .
Type Checking (ty)
uv run ty check
uv run ty check src/main.py
Testing (pytest)
uv run pytest
uv run pytest --cov
uv run pytest tests/test_auth.py
uv run pytest -v
uv run pytest -x
Migration Guide
From pip/requirements.txt
curl -LsSf https://astral.sh/uv/install.sh | sh
uv init
uv add $(cat requirements.txt | grep -v '^#' | grep -v '^$')
rm requirements.txt requirements-dev.txt
From Poetry
uv init
uv sync
From flake8/black/isort to ruff
uv remove flake8 black isort pyflakes pycodestyle
uv add --group dev ruff
rm .flake8 .isort.cfg pyproject.toml.bak
From mypy to ty
uv remove mypy
uv add --group dev ty
CI/CD Configuration
GitHub Actions
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Install dependencies
run: uv sync --all-groups
- name: Lint
run: uv run ruff check .
- name: Format check
run: uv run ruff format --check .
- name: Type check
run: uv run
Dependabot Configuration
version: 2
updates:
- package-ecosystem: 'uv'
directory: '/'
schedule:
interval: 'weekly'
groups:
all:
patterns:
- '*'
Pre-commit Hooks
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
Code Patterns
Type Annotations
from __future__ import annotations
from collections.abc import Sequence
from typing import TypeAlias
type Vector = list[float]
def process_items(items: Sequence[str], *, limit: int = 10) -> list[str]:
"""Process items with a limit."""
return [item.strip() for item in items[:limit]]
def maybe_int(value: str) -> int | None:
try:
return int(value)
except ValueError:
return None
Project Structure
my-project/
pyproject.toml # Single config file for all tools
uv.lock # Locked dependencies (commit this)
src/
my_project/
__init__.py
main.py
models.py
utils.py
tests/
__init__.py
test_main.py
test_models.py
conftest.py # Shared fixtures
.github/
workflows/
ci.yml
dependabot.yml
.pre-commit-config.yaml
Common Pitfalls
- Using pip directly: Always use
uv add / uv remove / uv run. Never pip install.
- Separate config files: All configuration goes in
pyproject.toml. Delete .flake8, mypy.ini, black.toml.
- Global installs: Use
uv run or uv tool run instead of globally installing CLI tools.
- Missing lock file: Always commit
uv.lock for reproducible builds.
- Old Python syntax: Use
ruff --select UP to auto-upgrade to modern syntax (match statements, | unions, etc.).
- Ignoring security rules: Enable
S (bandit) rules in ruff to catch security issues.
Integration with Agent-Studio
Recommended Workflow
- Use
modern-python to set up or migrate Python projects
- Use
python-backend-expert for framework-specific patterns (Django, FastAPI)
- Use
tdd skill for test-driven development workflow
- Use
comprehensive-unit-testing-with-pytest for test strategy
Complementary Skills
| Skill | Relationship |
|---|
python-backend-expert | Framework-specific patterns (Django, FastAPI, Flask) |
comprehensive-unit-testing-with-pytest | Testing strategies and patterns |
comprehensive-type-annotations | Type annotation best practices |
prioritize-python-3-10-features | Modern Python language features |
tdd | Test-driven development methodology |
property-based-testing | Hypothesis-based testing patterns |
Memory Protocol
Before starting: Check if the project already has Python tooling configured. Identify which legacy tools need migration.
During setup: Write configuration incrementally, verifying each tool works before moving to the next. Run ruff check, ruff format --check, and uv run pytest at each step.
After completion: Record the toolchain versions and any migration issues to .claude/context/memory/learnings.md for future reference.