- name
- modern-python-toolchain
- description
- Modern Python project setup with uv, ruff, and pyright. Use when initializing a new Python project, configuring the Python environment, setting up linting/formatting, or when a project needs uv (the fast Python package manager). Trigger on: 'set up Python', 'new Python project', 'configure uv', 'install uv', 'ruff', 'pyright', 'Python linting', 'Python formatting', or when a task requires Python and no pyproject.toml exists yet.
# Modern Python Toolchain
A guide for setting up Python projects with modern, fast tooling: **uv** (package/project manager), **ruff** (linter/formatter), and **pyright** (type checker).
## Installing uv
uv is an extremely fast Python package and project manager. It replaces pip, pip-tools, pipx, pyenv, virtualenv, poetry, etc.
```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# Homebrew (macOS)
brew install uv
```
After installation, restart your shell or run `source $HOME/.local/bin/env` (the installer prints the exact command).
For detailed information: https://docs.astral.sh/uv/
---
## uv basics
### Python version
Pin a single Python minor version. The recommended default is 3.12 (broadest ecosystem support — PyTorch, CUDA images, downstream libraries). Python 3.14 is the latest stable; prefer it for new projects unless you depend on packages that haven't added 3.14 support yet.
```toml
# pyproject.toml
requires-python = "==3.12.*"
```
Install Python via uv (no system Python needed):
```bash
uv python install 3.12
```
### Creating a new project
```bash
uv init # Create new project with pyproject.toml
uv init -p 3.12 # Specify Python version
```
### Common commands
```bash
uv add requests # Add dependency
uv add --dev ruff "pyright[nodejs]" # Add dev dependencies
uv remove requests # Remove dependency
uv sync # Install from lockfile
uv run COMMAND # Run command in project environment
uv run script.py # Run a script
uv run python -c "..." # Run Python one-liner
uvx TOOL ARGS # Run a tool without installing it
```
### Rules
- **Never use `pip`** in uv projects — always `uv add` for packages.
- **Never run `python script.py` directly** — always `uv run script.py` to ensure the correct environment. For one-liners use `uv run python -c "..."`.
- **Don't manually manage environments** with `python -m venv` or `source .venv/bin/activate` — uv handles this automatically.
- `uvx` runs tools from PyPI by package name without installing them permanently.
### Project types
For **library** projects (`uv init --lib`) or packaged apps (`uv init --package`), `uv_build` is used as the default build backend automatically:
```toml
[build-system]
# auto-generated by uv init; version bound tracks your installed uv (here: 0.11.28)
requires = ["uv_build>=0.11.28,<0.12.0"]
build-backend = "uv_build"
```
For **application** projects with an entry point:
```toml
[project.scripts]
myapp = "myapp.__main__:main"
```
If the project does not use src layout, just run `uv run main.py`.
---
## ruff
Ruff is an extremely fast Python linter and code formatter. It replaces Flake8, isort, Black, pyupgrade, autoflake, and more.
For detailed information: https://docs.astral.sh/ruff/
### When to use
Always use ruff for Python linting and formatting. Prefer `uv run ruff` when ruff is a dev dependency; otherwise fall back to `uvx ruff`.
### Configuration
Add to `pyproject.toml`:
```toml
[tool.ruff.lint]
extend-select = [
"UP", # pyupgrade
"I", # isort
]
```
Do not enable the full `E` category or other formatter-conflicting rules (E1xx, E501, W191, Q, COM); `ruff format` owns layout.
### Post-edit workflow
After modifying Python code, run both:
```bash
uv run ruff check --fix path/to/changed_file.py
uv run ruff format path/to/changed_file.py
```
Use `--diff` to preview changes without applying.
---
## pyright
Pyright is a fast type checker for Python. Only use it when the project lists it as a dev dependency or explicitly uses type checking.
Install with the `nodejs` extra so Node.js is bundled automatically (no system `node` required):
```bash
uv add --dev "pyright[nodejs]"
```
Run type checking:
```bash
uv run pyright path/to/changed_file.py # check specific files
uv run pyright src/ # check all code
```
Usually only check the files you modified. For broad changes (base classes, shared types), check the full tree.
---
## Coding style
### Type annotations
Use modern Python 3.12+ syntax:
```python
# Good — builtin generics, union syntax
def fetch(url: str, timeout: float = 30.0) -> list[dict[str, str | None]]:
...
# Bad — legacy typing imports
from typing import List, Dict, Optional
def fetch(url: str, timeout: float = 30.0) -> List[Dict[str, Optional[str]]]:
...
```
Always annotate function parameters. Local variables can rely on inference unless the type is ambiguous:
```python
items: list[tuple[str, int]] = [] # annotate — empty literal
config: dict[str, Any] = {} # annotate — empty literal
result = some_api() # inference is fine
```
### pydantic v2
Use the modern class-based API:
- `model_config = ConfigDict(...)` at class body level, not `class Config`.
- `RootModel` with `root: SomeType` for single-root schemas.
### typer (CLI)
Recommended for CLI entry points over `argparse`:
```python
import typer
from typing import Annotated
cli = typer.Typer(add_completion=False)
@cli.command()
def main(name: Annotated[str, typer.Argument(help="Your name")]) -> None:
typer.echo(f"Hello {name}")
if __name__ == "__main__":
cli()
```
View on GitHub