Como Começar um Projeto Python Perfeito
Começar um novo projeto Python não precisa ser um desafio porque as necessidades básicas são sempre iguais mesmo para tipos de projetos diferentes. Este artigo apresenta como criar uma base inicial perfeita que pode ser usada para qualquer projeto Python.
Definição da Base Inicial Perfeita
A base de um Python perfeito deve ter os seguintes recursos:
- Estrutura adequada de arquivos e diretórios para organizar o projeto, separando o código da aplicação, testes, documentação, configuração do projeto etc.
- Ambiente virtual para criar uma instalação isolada e independente do Python e das bibliotecas necessárias para o projeto, sem conflitos ou interferência do sistema ou de outros projetos.
- Ferramentas de linter para manter a qualidade e a consistência do projeto através da análise estática do código para identificar defeitos, problemas de formatação, otimização, segurança e outros problemas no início do estágio de desenvolvimento.
- Testes automatizados para garantir a qualidade do código e detectar defeitos precocemente. É importante ter uma suíte de testes que cubra todos os aspectos da aplicação, juntamente com relatórios que indiquem a porcentagem de código coberta pelos testes.
- Controle de versão para registrar as alterações no código-fonte e permitir a colaboração entre os desenvolvedores. É importante configurar devidamente o controle de versão para ignorar arquivos que não devem ser versionados, como arquivos temporários ou código compilado.
- Integração contínua (CI) para garantir a qualidade do código executando testes automatizados e realizando outras verificações no código sempre que ele é enviado para o servidor.
Tirando a estrutura de arquivos e diretórios que é única, todos os demais itens dependem de escolhas. E existem muitas opções. O gerenciamento de ambientes virtuais, por exemplo, podem ser feito com venv
, pipenv
, poetry
ou conda
. Há dezenas de ferramentas de linting tais como ruff
, flake8
, pylint
, mypy
etc. que são equivalentes ou complementares. No fim das contas, as escolhas que formam uma ou outra combinação dependem de decisões técnicas e pessoais.
Gerenciamento de Ambientes Virtuais
O gerenciamento de versões do Python, de ambiente virtuais e dependências será feito através da combinação pyenv + poetry
(leia o artigo anterior).
Estrutura Inicial de Diretórios
Para criar a estrutura inicial do seu projeto, use poetry new <nome_projeto>
:
O comando anterior cria a seguinte estrutura de diretórios:
projeto_x ├── projeto_x │ └── __init__.py ├── pyproject.toml ├── README.rst └── tests ├── __init__.py └── test_projeto_x.py
Esta é uma estrutura mínima de arquivos e diretórios muito boa porque separa claramente o código específico do projeto no subdiretório projeto_x
do código apenas relacionado com testes no diretório tests
, e dos arquivos de configuração e documentação do projeto (pyproject.toml
e README.rst
). Contudo, alguns ajustes são necessários:
- O arquivo
README.rst
vem vazio e você precisa completá-lo. A criação desse tipo de arquivo foge do escopo deste artigo, mas você encontra boas dicas e mais informações em 1 e 2. - Edite o arquivo
pyproject.toml
e mude as definições que foram criadas automaticamente paraname
,version
,description
eauthors
. - Verifique se a versão do Python definida na seção
[tool.poetry.dependencies]
empyproject.toml
é a versão desejada.poetry new
usa a versão do ambiente, mas você pode instalar e especificar outras versões do Python através do pyenv.
Ferramentas de Linting e Teste
O conjunto mínimo recomendado de ferramentas de teste é:
- pytest: ferramenta de teste para Python
-
pytest-cov: plugin do
pytest
para medir cobertura de código
Para linting, recomendo o uso de:
- mypy: ferramenta de análise estática de tipos
- pip-audit: ferramenta para escanear ambientes Python em busca de pacotes com vulnerabilidade conhecida
- ruff: Ferramenta de linting para projetos Python extremamente rápida (escrita em Rust). Substitui outras ferramentas tais como blue, black, flake8, isort, pep-naming, pyupgrade e bandit.
Instalação e Configuração
Todas as bibliotecas e ferramentas relacionadas a atividades de teste e linting são necessárias para o desenvolvimento do projeto, mas não para seu funcionamento em produção. Devem ser instaladas em uma seção separada em pyproject.toml
para não se misturar com as dependências essenciais. A instalação dessas bibliotecas e ferramentas deve ser feita através do comando poetry add --dev
:
Configuração
A configuração da maioria das dependências pode ser mantida no arquivo pyproject.toml
, em seções nomeadas seguindo o padrão [tool.<nome-da-ferramenta>]
:
Algumas observações:
- Linhas 2 e 12 alteram o tamanho da linha do valor padrão
79
para100
. -
mypy
possui diversas opções de configuração. A opçãoignore_missing_imports
suprime mensagens de erro sobre importações que não podem ser resolvidas (linha 12). A opçãodisallow_untyped_defs
não permite a definição de funções sem anotações de tipo ou com anotações de tipo incompletas (linha 13). -
ruff
é capaz de verificar as regras usadas por diversas outras ferramentas de linting.
Automação
Testes e linting devem ser fáceis de serem executados, sem a necessidade de lembrar cada comando e seus argumentos. Para isso, eu recomendo usar um Makefile
com as tarefas necessárias:
test: pytest --cov-report term-missing --cov-report html --cov-branch \ --cov projeto_x/ lint: ruff check --diff . @echo ruff format --diff . @echo mypy . format: ruff check --silent --exit-zero --fix . @echo ruff format . audit: pip-audit
E aí, é só usar o comando make
para executar as tarefas:
-
make test
executa os testes e gera relatórios de cobertura dos testes. -
make lint
executa o linting usando diversas ferramentas em sequência. -
make format
formata o código Python de acordo com os padrões usados porblue
eruff
. -
make audit
verifica se existem pacotes com vulnerabilidades conhecidas.
Podemos usar esses mesmos comandos nos scritps de hook do controle de versão e na configuração do sistema de integração contínua. Dessa forma, mantemos um único arquivo com os comandos e os demais usando esse arquivo.
Configuração do Sistema de Integração Contínua
A maioria dos sistemas de integração contínua modernos mantém sua configuração junto com o código-fonte. GitHub Actions, por exemplo, mantém sua configuração em arquivos no formato yaml
dentro do diretório .github/workflows
, na raiz do projeto.
Crie o arquivo .github/workflows/continuous_integration.yml
com o seguinte conteúdo:
Essa configuração funciona da seguinte forma:
- Esse fluxo será executado toda vez que o repositório receber um push (linha 2).
- O fluxo será executado em um sistema operacional Ubuntu, na última versão disponível (linha 5).
- Use a versão
3.10
do Python (linha 11). - Em seguida, instale
poetry
(linha 16) e configure-o para usar ambientes virtuais em diretórios.venv
(linha 19). - Para evitar ter de reinstalar as mesmas dependências toda vez, vamos criar uma política de cache para o diretório
.venv
(linha 25). A chave que identifica o cache é formado pela combinação da palavravenv
e o hash do conteúdo depoetry.lock
(linha 26). - As dependências são instaladas apenas se o cache não for encontrado (linhas 28 a 30). Caso contrário, o cache de
venv
é usado. - Executar a tarefa de lint, audit e teste (linhas 32 a 39)
Eventos de pre-commit
e pre-push
É uma boa prática fazer a verificação da qualidade do código localmente mesmo que a integração contínua refaça o processo no servidor. Isso economiza tempo porque o resultado é imediato e as correções podem ser feitas sem ter de passar por um ciclo de integração contínua.
A verificação local deve acontecer antes do compartilhamento das alterações com outros desenvolvedores ou com o repositório oficial do projeto. Podemos automatizar esse processo através de hooks do controle de versão. Os mais adequados são pre-commit
e pre-push
.
Configuração | pre-commit |
pre-push |
---|---|---|
1 | make lint |
make test && make audit |
2 | make lint && make test && make audit |
Na configuração 1
, a análise estática é feita antes de cada commit
. Os testes e a auditoria, por serem mais demorados, só são feitos antes do push
. A vantagem dessa distribuição é que toda revisão local é verificada e formatada. Por outro lado, executar make lint
antes de cada commit
pode ser meio irritante dependendo do seu fluxo de trabalho.
Na configuração 2
, o linting, os testes e a auditoria são feitos apenas antes do push
. O fluxo de trabalho é mais fluído porém as revisões locais podem ser inconsistentes. É necessário criar uma revisão adicional com os ajustes necessários em caso de falha na execução do pre-push
.
Para facilitar a vida do desenvolvedor, vamos adicionar a tarefa install_hooks
ao Makefile
, que chama o script scripts/install_hooks.sh
para criar os hooks na configuração 2
:
scripts/install_hooks.sh
contém:
Algumas explicações:
- No Git, hooks são arquivos executáveis nomeados de acordo com o evento desejado, localizados em
.git/hooks
. - No Mercurial, hooks são definidos na seção [hooks] no arquivo de configuração .hg/hgrc, em que cada hook pode ser comandos ou funções Python.
- Tanto os scripts em
bash
(linhas 3-6) quanto os comandos usados no Mercurial (linhas 8-10) fazem a mesma coisa: mudam o diretório corrente para a raiz do projeto, onde está localizadoMakefile
, e executa o comandopoetry run make <tarefa>
. Lembre-se quepoetry run <comando>
executa o comando dentro do contexto do ambiente virtual do projeto. - Se existir um diretório
.git
, então os hooks do Git são criados no diretório.git/hooks
(linhas 12-14). Caso contrário, os hooks do Mercurial são criados no diretório.hg/hgrc
(linhas 15-16). - O trecho de código apresentado atende tanto quem usa Mercurial (meu caso) quanto quem usa Git. Você pode retirar algumas partes se você e sua equipe usam apenas um ou outro.
Preparando o Controle de Versão
Para evitar que arquivos indesejados sejam adicionados por engano ao controle de versão, é preciso criar uma lista de filtros em um arquivo especial localizado na raiz do projeto, no mesmo nível do diretório .hg
ou .git
dependendo de qual ferramenta você usa. Se você usa o Mercurial, este arquivo deve-se chamar .hgignore
e deve conter:
syntax: glob .venv .env *~ *.py[cod] *.orig # Unit test / coverage reports .coverage htmlcov/ # cache __pycache__ .mypy_cache .pytest_cache
Se você usa Git
, o nome do arquivo deve ser .gitignore
e contém as mesmas linhas acima menos a primeira linha (syntax: glob
), que deve ser removida.
Com os filtros definidos, podemos iniciar o controle de versão. Para o Mercurial, os comandos são:
Para quem usa Git, execute:
$ git init . $ poetry run make install_hooks $ git add -A . $ git commit -m 'Estrutura inicial do projeto'
E tudo está pronto para enviar ao repositório oficial do projeto no GitHub.
Gabarito Pronto Para Uso no GitHub
É importante saber os passos para criar o projeto Python perfeito. Mas ao invés de executar esses mesmos passos a cada novo projeto, você pode simplesmente usar o gabarito que eu disponibilizei no Github. As instruções de uso estão no README.rst
.
Considerações Finais
A base de projeto apresentada neste artigo funciona muito bem e pode ser facilmente adaptada para outras ferramentas se você quiser tentar uma combinação diferente. O importante é manter a estrutura do projeto e as atividades de linting e teste automatizadas.
Referências
1 | Make a README |
---|
2 | READMEs on READMEs (and other README-related resources) |
---|
3 | How to set up a perfect Python project |
---|
Próximo artigo: Projeto FastAPI Mínimo
Artigo anterior: Gerenciamento de Versões, Ambiente Virtuais e Dependências com pyenv e poetry
Comentários
Comments powered by Disqus