Skip to content

    Chapter 50, Advanced

    Quality and automation

    What is not checked by a machine is forgotten by a human. This chapter sets up the minimum set of checks that I put in any project.

    Code for this chapter: avancado/cap50_qualidade.py

    Linting and formatting with ruff

    The ruff combines linter and formatter into a single, very fast tool. The linter finds errors and suspicious patterns. The formatter standardizes the style, which ends discussions about commas in code review. See code with real issues:

    exemplos/qualidade/antes.py
    import os, sys
    
    
    def adicionar(item, lista=[]):
        try:
            lista.append(item)
        except:
            pass
        return lista
    
    Terminal
    uv run ruff check exemplos/qualidade/antes.py --output-format concise
    
    Output
    exemplos/qualidade/antes.py:1:1: E401 [*] Multiple imports on one line
    exemplos/qualidade/antes.py:1:1: I001 [*] Import block is un-sorted or un-formatted
    exemplos/qualidade/antes.py:1:8: F401 [*] `os` imported but unused
    exemplos/qualidade/antes.py:1:12: F401 [*] `sys` imported but unused
    exemplos/qualidade/antes.py:4:27: B006 Do not use mutable data structures for argument defaults
    exemplos/qualidade/antes.py:7:5: E722 Do not use bare `except`
    Found 6 errors.
    [*] 4 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
    

    Ruff pointed out six findings: two unused imports, imports on the same line and out of order, a mutable default value (the bug from chapter 18), and a except that swallows any error. The four marked with [*] it corrects automatically with --fix. The other two, the mutable value and the except empty one, require a decision from you, because they change behavior. The corrected version:

    avancado/cap50_qualidade.pylinhas 10 a 19
    def adicionar(item, lista=None):
        if lista is None:
            lista = []
        lista.append(item)
        return lista
    
    
    assert adicionar(1) == [1]
    assert adicionar(2) == [2]
    print("sem os problemas apontados")
    
    Output
    sem os problemas apontados
    

    Configuration in one place

    I explicitly choose the rule groups, so that the configuration is a decision and not the tool's default. The mypy stays in the same file:

    exemplos/qualidade/pyproject.toml
    [tool.ruff]
    line-length = 100
    target-version = "py312"
    
    [tool.ruff.lint]
    select = ["E", "F", "I", "B", "UP"]
    
    [tool.mypy]
    python_version = "3.12"
    strict = true
    files = ["src"]
    
    GroupWhat it checks
    EStyle and syntax errors (pycodestyle)
    FSimple logical errors, such as unused names (pyflakes)
    IOrder of imports
    BProbable bug traps (flake8-bugbear)
    UPOld syntax that can be modernized

    Before the commit: pre-commit

    The pre-commit runs the checks at commit time, and prevents the problem from reaching the repository. Install with uv tool install pre-commit and activate with pre-commit install:

    exemplos/qualidade/.pre-commit-config.yaml
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.12.0   # atualize com: pre-commit autoupdate
        hooks:
          - id: ruff-check
            args: [--fix]
          - id: ruff-format
    

    After the push: continuous integration

    The pre-commit is a local convenience and can be skipped. The guarantee is the CI, which runs in a clean environment on every push and pull request. This GitHub Actions flow uses uv and tests three versions of Python:

    exemplos/qualidade/ci.yml
    name: ci
    
    on:
      push:
        branches: [main]
      pull_request:
    
    jobs:
      qualidade:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            python: ["3.12", "3.13", "3.14"]
        steps:
          - uses: actions/checkout@v4
          - uses: astral-sh/setup-uv@v6
            with:
              python-version: ${{ matrix.python }}
          - run: uv sync --locked
          - run: uv run ruff check .
          - run: uv run ruff format --check .
          - run: uv run mypy
          - run: uv run pytest -q
    

    Check the versions of the actions

    The versions of the actions (actions/checkout, astral-sh/setup-uv) evolve. Before copying this file, check the latest version of each one on the action's own page. The flow format changes little, but the tags change.

    Coverage

    Coverage measures which lines the tests execute. It tells what does not is tested, and not that what is tested is correct. I use it to discover gaps, and not as a numerical goal:

    Terminal
    uv add --dev pytest-cov
    uv run pytest --cov=calc_notes --cov-report=term-missing
    

    Exercise 1

    Regression test

    For the function adicionar corrected, write a test that would fail in the version with a mutable default value.