Asi modern-python

Modern Python best practices. Use when creating new Python projects, and writing Python scripts, or migrating existing projects from legacy tools.

install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/modern-python" ~/.claude/skills/plurigrid-asi-modern-python && rm -rf "$T"
manifest: skills/modern-python/SKILL.md
source content

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.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

AvoidUse 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)
Poetryuv (faster, simpler, better ecosystem integration)
requirements.txtPEP 723 for scripts, pyproject.toml for projects
mypy / pyrightty (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-commitprek (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

ToolPurposeReplaces
uvPackage/dependency managementpip, virtualenv, pip-tools, pipx, pyenv
ruffLinting AND formattingflake8, black, isort, pyupgrade, pydocstyle
tyType checkingmypy, pyright (faster alternative)
pytestTesting with coverageunittest
prekPre-commit hooks (setup)pre-commit (faster, Rust-native)

Security Tools

ToolPurposeWhen It Runs
shellcheckShell script lintingpre-commit
detect-secretsSecret detectionpre-commit
actionlintWorkflow syntax validationpre-commit, CI
zizmorWorkflow security auditpre-commit, CI
pip-auditDependency vulnerability scanningCI, manual
DependabotAutomated dependency updatesscheduled

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:

  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)
  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

CommandDescription
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.lock
    to version control
  • Use PEP 723 for standalone scripts

Read Next