Files
awesome-copilot/skills/python-pypi-package-builder/references/tooling-ruff.md
Patel Dhruv 49fd3f3faf Add new skill: Python PyPI Package Builder (#1302)
* Add python-pypi-package-builder skill for Python packaging

- Created `SKILL.md` defining decision-driven workflow for building, testing, versioning, and publishing Python packages.
- Added reference modules covering PyPA packaging, architecture patterns, CI/CD, testing, versioning strategy, and release governance.
- Implemented scaffold script to generate complete project structure with pyproject.toml, CI workflows, tests, and configuration.
- Included support for multiple build backends (setuptools_scm, hatchling, flit, poetry) with clear decision rules.
- Added secure release practices including tag-based versioning, branch protection, and OIDC Trusted Publishing.

* fix: correct spelling issues detected by codespell
2026-04-09 10:36:17 +10:00

345 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tooling — Ruff-Only Setup and Code Quality
## Table of Contents
1. [Use Only Ruff (Replaces black, isort, flake8)](#1-use-only-ruff-replaces-black-isort-flake8)
2. [Ruff Configuration in pyproject.toml](#2-ruff-configuration-in-pyprojecttoml)
3. [mypy Configuration](#3-mypy-configuration)
4. [pre-commit Configuration](#4-pre-commit-configuration)
5. [pytest and Coverage Configuration](#5-pytest-and-coverage-configuration)
6. [Dev Dependencies in pyproject.toml](#6-dev-dependencies-in-pyprojecttoml)
7. [CI Lint Job — Ruff Only](#7-ci-lint-job--ruff-only)
8. [Migration Guide — Removing black and isort](#8-migration-guide--removing-black-and-isort)
---
## 1. Use Only Ruff (Replaces black, isort, flake8)
**Decision:** Use `ruff` as the single linting and formatting tool. Remove `black` and `isort`.
| Old (avoid) | New (use) | What it does |
|---|---|---|
| `black` | `ruff format` | Code formatting |
| `isort` | `ruff check --select I` | Import sorting |
| `flake8` | `ruff check` | Style and error linting |
| `pyupgrade` | `ruff check --select UP` | Upgrade syntax to modern Python |
| `bandit` | `ruff check --select S` | Security linting |
| All of the above | `ruff` | One tool, one config section |
**Why ruff?**
- 10100× faster than the tools it replaces (written in Rust).
- Single config section in `pyproject.toml` — no `.flake8`, `.isort.cfg`, `pyproject.toml[tool.black]` sprawl.
- Actively maintained by Astral; follows the same rules as the tools it replaces.
- `ruff format` is black-compatible — existing black-formatted code passes without changes.
---
## 2. Ruff Configuration in pyproject.toml
```toml
[tool.ruff]
target-version = "py310" # Minimum supported Python version
line-length = 88 # black-compatible default
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear (opinionated but very useful)
"C4", # flake8-comprehensions
"UP", # pyupgrade (modernise syntax)
"SIM", # flake8-simplify
"TCH", # flake8-type-checking (move imports to TYPE_CHECKING block)
"ANN", # flake8-annotations (enforce type hints — remove if too strict)
"S", # flake8-bandit (security)
"N", # pep8-naming
]
ignore = [
"ANN101", # Missing type annotation for `self`
"ANN102", # Missing type annotation for `cls`
"S101", # Use of `assert` — necessary in tests
"S603", # subprocess without shell=True — often intentional
"B008", # Do not perform function calls in default arguments (false positives in FastAPI/Typer)
]
[tool.ruff.lint.isort]
known-first-party = ["your_package"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101", "ANN", "D"] # Allow assert and skip annotations/docstrings in tests
[tool.ruff.format]
quote-style = "double" # black-compatible
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
```
### Useful ruff commands
```bash
# Check for lint issues (no changes)
ruff check .
# Auto-fix fixable issues
ruff check --fix .
# Format code (replaces black)
ruff format .
# Check formatting without changing files (CI mode)
ruff format --check .
# Run both lint and format check in one command (for CI)
ruff check . && ruff format --check .
```
---
## 3. mypy Configuration
```toml
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
show_error_codes = true
# Ignore missing stubs for third-party packages that don't ship types
[[tool.mypy.overrides]]
module = ["redis.*", "pydantic_settings.*"]
ignore_missing_imports = true
```
### Running mypy — handle both src and flat layouts
```bash
# src layout:
mypy src/your_package/
# flat layout:
mypy your_package/
```
In CI, detect layout dynamically:
```yaml
- name: Run mypy
run: |
if [ -d "src" ]; then
mypy src/
else
mypy your_package/
fi
```
---
## 4. pre-commit Configuration
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4 # Pin to a specific release; update periodically with `pre-commit autoupdate`
hooks:
- id: ruff
args: [--fix] # Auto-fix what can be fixed
- id: ruff-format # Format (replaces black hook)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
additional_dependencies:
- types-requests
- types-redis
# Add stubs for any typed dependency used in your package
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-toml
- id: check-yaml
- id: check-merge-conflict
- id: check-added-large-files
args: ["--maxkb=500"]
```
### ❌ Remove these hooks (replaced by ruff)
```yaml
# DELETE or never add:
- repo: https://github.com/psf/black # replaced by ruff-format
- repo: https://github.com/PyCQA/isort # replaced by ruff lint I rules
- repo: https://github.com/PyCQA/flake8 # replaced by ruff check
- repo: https://github.com/PyCQA/autoflake # replaced by ruff check F401
```
### Setup
```bash
pip install pre-commit
pre-commit install # Installs git hook — runs on every commit
pre-commit run --all-files # Run manually on all files
pre-commit autoupdate # Update all hooks to latest pinned versions
```
---
## 5. pytest and Coverage Configuration
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra -q --strict-markers --cov=your_package --cov-report=term-missing"
asyncio_mode = "auto" # Enables async tests without @pytest.mark.asyncio decorator
[tool.coverage.run]
source = ["your_package"]
branch = true
omit = ["**/__main__.py", "**/cli.py"] # omit entry points from coverage
[tool.coverage.report]
show_missing = true
skip_covered = false
fail_under = 85 # Fail CI if coverage drops below 85%
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"raise NotImplementedError",
"@abstractmethod",
]
```
### asyncio_mode = "auto" — remove @pytest.mark.asyncio
With `asyncio_mode = "auto"` set in `pyproject.toml`, **do not** add `@pytest.mark.asyncio`
to test functions. The decorator is redundant and will raise a warning in modern pytest-asyncio.
```python
# WRONG — the decorator is deprecated when asyncio_mode = "auto":
@pytest.mark.asyncio
async def test_async_operation():
result = await my_async_func()
assert result == expected
# CORRECT — just use async def:
async def test_async_operation():
result = await my_async_func()
assert result == expected
```
---
## 6. Dev Dependencies in pyproject.toml
Declare all dev/test tools in an `[extras]` group named `dev`.
```toml
[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-asyncio>=0.23",
"pytest-cov>=5",
"ruff>=0.4",
"mypy>=1.10",
"pre-commit>=3.7",
"httpx>=0.27", # If testing HTTP transport
"respx>=0.21", # If mocking httpx in tests
]
redis = [
"redis>=5",
]
docs = [
"mkdocs-material>=9",
"mkdocstrings[python]>=0.25",
]
```
Install dev dependencies:
```bash
pip install -e ".[dev]"
pip install -e ".[dev,redis]" # Include optional extras
```
---
## 7. CI Lint Job — Ruff Only
Replace the separate `black`, `isort`, and `flake8` steps with a single `ruff` step.
```yaml
# .github/workflows/ci.yml — lint job
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dev dependencies
run: pip install -e ".[dev]"
# Single step: ruff replaces black + isort + flake8
- name: ruff lint
run: ruff check .
- name: ruff format check
run: ruff format --check .
- name: mypy
run: |
if [ -d "src" ]; then
mypy src/
else
mypy $(basename $(ls -d */))/ 2>/dev/null || mypy .
fi
```
---
## 8. Migration Guide — Removing black and isort
If you are converting an existing project that used `black` and `isort`:
```bash
# 1. Remove black and isort from dev dependencies
pip uninstall black isort
# 2. Remove black and isort config sections from pyproject.toml
# [tool.black] ← delete this section
# [tool.isort] ← delete this section
# 3. Add ruff to dev dependencies (see Section 2 for config)
# 4. Run ruff format to confirm existing code is already compatible
ruff format --check .
# ruff format is black-compatible; output should be identical
# 5. Update .pre-commit-config.yaml (see Section 4)
# Remove black and isort hooks; add ruff and ruff-format hooks
# 6. Update CI (see Section 7)
# Remove black, isort, flake8 steps; add ruff check + ruff format --check
# 7. Reinstall pre-commit hooks
pre-commit uninstall
pre-commit install
pre-commit run --all-files # Verify clean
```