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

9.2 KiB
Raw Blame History

Tooling — Ruff-Only Setup and Code Quality

Table of Contents

  1. Use Only Ruff (Replaces black, isort, flake8)
  2. Ruff Configuration in pyproject.toml
  3. mypy Configuration
  4. pre-commit Configuration
  5. pytest and Coverage Configuration
  6. Dev Dependencies in pyproject.toml
  7. CI Lint Job — Ruff Only
  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

[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

# 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

[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

# src layout:
mypy src/your_package/

# flat layout:
mypy your_package/

In CI, detect layout dynamically:

- name: Run mypy
  run: |
    if [ -d "src" ]; then
        mypy src/
    else
        mypy your_package/
    fi

4. pre-commit Configuration

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

# 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

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

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

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

[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:

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.

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

# 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