“Uma ferramenta só, escrita em Rust, faz o trabalho de Flake8, Black e isort — e faz mais rápido.”
Ruff — Python linter & formatter
Ruff é um linter de código Python desenvolvido em Rust, criado para substituir ferramentas como Flake8 e Black com um desempenho muito superior. Ele resolve dois problemas distintos: o check valida imports não utilizados, variáveis em desuso e boas práticas gerais de código, enquanto o format cuida apenas da aparência visual — indentação, uso de aspas, quebra de linha. O ideal é usar os dois em conjunto, já que resolvem problemas complementares.
Instalação
A instalação do Ruff pode ser feita via pip, dentro de um ambiente virtual do projeto, ou de forma global usando o uv como gerenciador de ferramentas. Para confirmar que a instalação funcionou, basta verificar a versão instalada.
pip install ruff# ou, instalando globalmente com uvuv tool install ruffruff --version
Formas de executar o Ruff
Existem três formas de rodar o Ruff, cada uma com um propósito. O comando direto usa a instalação global da ferramenta para analisar o diretório atual. Prefixado com uvx, o Ruff é instalado temporariamente, executa o linter completo e é removido em seguida — o equivalente ao npx do Node. Já uv run executa o Ruff dentro do ambiente virtual do projeto, respeitando a versão específica fixada no pyproject.toml.
ruff check . # instalação global, analisa o diretório atualuvx ruff check . # instalação temporária (como npx do Node)uv run ruff check . # executa dentro do venv, versão do pyproject.toml
Formatação com ruff format
O format mexe apenas com validações visuais do código: indentação, uso de aspas, quebra de linha e afins. Ele não altera lógica nem remove nada do seu código — só padroniza a forma como ele é escrito.
Verificando o que precisa ser formatado
Esse comando verifica, dentro da pasta informada, todos os arquivos que precisam de alterações, sem alterá-los de fato. Lembrando que o último argumento é o nome da pasta/diretório ou arquivo que você quer que o Ruff execute.
ruff format --check backend/# Would reformat: backend/assets/models.py
Visualizando o diff antes de aplicar
Para ver exatamente a diferença que o Ruff aplicaria, usamos a flag --diff.
ruff format --diff backend/
Aplicando a formatação
Para executar o formatter de fato, ajustando os arquivos de acordo com as regras do Ruff, basta rodar o comando apontando para a pasta ou arquivo desejado.
ruff format backend/
Usando o check do ruff
O check valida imports não utilizados, variáveis em desuso e boas práticas gerais de código — problemas de lógica, não de estilo visual.
Visão geral com --statistics
Antes de sair corrigindo erro por erro, vale rodar o check com a flag --statistics. Ela varre o projeto inteiro e monta um relatório agrupado por tipo de erro, dando um escopo geral dos problemas antes de começar a mexer em qualquer coisa — por isso costuma ser o primeiro comando a rodar num projeto novo.
uv run ruff check --statistics backend/# 17 I001 [*] unsorted-imports# 6 E501 [ ] line-too-long# 4 F401 [*] unused-import# 1 F811 [*] redefined-while-unused# Found 28 errors.# [*] 22 fixable with the --fix option.
Resumindo os códigos desse relatório: I001 é bloco de import desorganizado, E501 é linha longa demais, F401 é import não utilizado e F811 é uma redefinição de algo que já tinha sido definido antes e nunca chegou a ser usado. A letra no início do código (E, F, I) indica a categoria da regra, e o número identifica a regra específica dentro dela.
Encontrando problemas no código
Uma resposta possível do check é essa abaixo, sobre uma importação sem nenhum uso em um arquivo models.py.
ruff check backend/# F401 [*] `os` imported but unused# --> backend/assets/models.py:2:8# |# 1 | from django.db import models# 2 | import os# | ^^# 3 | from costumers.models import CustomUser# |# help: Remove unused import: `os`## Found 1 error.# [*] 1 fixable with the --fix option.
Visualizando a correção antes de aplicar
Para vermos a diferença que o Ruff faria caso corrigíssemos automaticamente, combinamos --fix com --diff.
ruff check --fix --diff backend/# --- backend/assets/models.py# +++ backend/assets/models.py# @@ -1,5 +1,4 @@# from django.db import models# -import os# from costumers.models import CustomUser## Would fix 1 error.
Aplicando a correção automaticamente
Para corrigirmos automaticamente, como a própria ferramenta sugere, basta rodar --fix.
ruff check --fix backend/
Organizando imports automaticamente
Com a regra I (isort), o Ruff também identifica blocos de import desorganizados ou mal formatados — comum quando vários desenvolvedores editam o mesmo arquivo. O erro I001 indica que o bloco precisa ser reorganizado, e pode ser corrigido com --fix.
uv run ruff check --select I backend/assets/models.py# I001 [*] Import block is un-sorted or un-formatted# --> backend/assets/models.py:1:1# |# 1 | / from django.db import models# 2 | | from costumers.models import CustomUser# 3 | | import os# 4 | | import json# 5 | | from django.db import models# 6 | | import sys# 7 | | from django.utils import timezone# | |_________________________________^# 8 |# 9 | class TypeAsset(models.TextChoices):# |# help: Organize imports## Found 1 error.# [*] 1 fixable with the --fix option.
Correções inseguras com --unsafe-fixes
O Ruff distingue entre correções seguras e inseguras. Sempre que rodamos --fix sozinho, estamos aplicando apenas correções seguras — que não mudam a lógica do código nem geram erros novos. Já as correções inseguras podem, em expressões mais complexas, desencadear bugs lógicos. Por isso a recomendação é sempre rodar primeiro com --diff, para ver o que seria alterado antes de aplicar de fato, e depois conferir manualmente se a correção não introduziu nenhum erro de lógica.
# visualizando o que as correções inseguras fariam, antes de aplicaruv run ruff check --fix --unsafe-fixes --diff backend/# aplicando de fatouv run ruff check --fix --unsafe-fixes backend/
Como ignorar erros
Nem todo erro apontado pelo Ruff precisa ser corrigido. Às vezes uma importação não é usada diretamente no arquivo, mas ainda assim é necessária — ou uma regra simplesmente não faz sentido para um trecho específico do projeto. Para esses casos, existem duas formas de ignorar: de forma pontual, direto no código, ou de forma persistente, configurando o pyproject.toml.
Ignorando uma linha específica com noqa
Às vezes precisamos de uma importação que não é necessariamente utilizada em algum lugar do arquivo, mas que ainda assim é importante para o código. Nesse caso, devemos ignorar apenas esse erro específico com um comentário noqa — F401 se refere ao erro de import não utilizado. Também é possível ignorar de forma mais genérica, omitindo o código do erro, mas aí qualquer regra que o Ruff aponte naquela linha é silenciada.
import os # noqa: F401# de forma mais genérica: ignora qualquer erro nessa linhaimport os # noqa
Também dá pra ignorar mais de uma regra na mesma linha, separando os códigos por vírgula: # noqa: F401, E501.
Ignorando o arquivo inteiro
Quando o problema não é uma linha específica, mas o arquivo inteiro, o comentário # ruff: noqa no topo do arquivo ignora todas as regras naquele arquivo. Do mesmo jeito que o noqa de linha, também dá pra restringir a uma regra específica em vez de ignorar tudo.
# opção 1 — ignora todas as regras nesse arquivo inteiro# ruff: noqa# opção 2 — ignora apenas uma regra específica nesse arquivo inteiro# ruff: noqa: F401
Ignorando uma regra em todo o projeto
A outra forma de ignorar é customizando o pyproject.toml. Para ignorar uma regra de forma global — nesse exemplo, o F401 (imports não usados) — o linter do Ruff passa a ignorar essa regra em todos os lugares do projeto.
[tool.ruff.lint]ignore = ["F401"]
Ignorando uma regra em uma pasta específica
Também é possível ignorar uma regra específica apenas dentro de uma pasta específica do projeto — como as migrations, por exemplo.
[tool.ruff.lint.per-file-ignores]"**/migrations/*.py" = ["E501", "F401"]
Excluindo pastas inteiras da análise
O per-file-ignores ainda analisa o arquivo, só ignora regras específicas nele. Já o extend-exclude tira a pasta inteira do escopo do Ruff — ele nem chega a abrir esses arquivos. Faz sentido pra pastas de migrations, vendor ou qualquer código gerado automaticamente que não faz sentido lintar. Vale usar extend-exclude em vez de exclude: o extend-exclude soma à lista padrão de exclusões do Ruff (como .git e __pycache__), enquanto exclude substitui essa lista inteira.
[tool.ruff]extend-exclude = ["**/migrations/*.py", "vendor/"]
Definindo comprimento de linha e regras habilitadas
Também podemos dizer o limite de caracteres por linha que queremos, junto com algumas configurações adicionais. Por padrão, o Ruff usa 88 como o número limite de caracteres dentro de uma linha, mas isso pode ser customizado. O select, por padrão, habilita o E (estilos: espaçamento, linhas em branco, comprimento de linha...) e o F (formatações lógicas: import não usado, variável definida mas não lida, nome não definido...). Aqui habilitamos também o I (imports: ordem errada, mistura de pacotes externos com internos de forma bagunçada, imports duplicados...) e ignoramos de forma global o erro E501.
[tool.ruff]line-length = 88[tool.ruff.lint]select = ["E", "F", "I"]ignore = ["E501"]
Ruff no CI/CD
Aqui configuramos um step de linter dentro do CI: durante um job do GitHub Actions, o Ruff roda à procura de erros no código e, caso encontre algum, quebra o step para que o desenvolvedor corrija e envie o código formatado corretamente, de acordo com as regras configuradas.
Step de linter no GitHub Actions
O primeiro comando instala o uv de forma silenciosa, com a flag --quiet. Em seguida rodamos uvx ruff check backend/ — usamos o uvx para que, no momento do CI, o uv instale o Ruff de forma temporária, sem deixar nada permanente no runner. Se o check encontrar algum problema, o step quebra ali mesmo: como o GitHub Actions roda cada step de run com bash -e por padrão, se o check falhar o format --check nem chega a rodar naquela execução. Só quando o check passa é que o format --check roda, validando a formatação. Se os dois comandos passarem, o código está de acordo com as regras do Ruff e o step é aprovado.
- name: Ruffrun: |pip install uv --quietuvx ruff check backend/uvx ruff format --check backend/
