Como manter as dependências de bibliotecas garantindo as correções de bugs

Publicado em 2016-02-26 por Vinicius Assef

Quando usamos bibliotecas de terceiros em nossos projetos, corremos o risco de não instalarmos correções. Como fazer isso sem ficar mexendo no arquivo requirements.txt?

Versão TL;DR: administre manualmente as dependências, fixe as versões dos pacotes de forma a garantir updates de correções, mas mantenha a compatibilidade.

Trabalhando com Python é normal instalar pacotes que já fazem alguma tarefa que eu preciso e é comum projetos com mais de uma dezena de dependências externas. Com essa quantidade, surge a dificuldade de administrar todas as dependências do projeto.

O utilitário pip veio para ajudar, permitindo que o desenvolvedor guarde a lista de dependências em um arquivo texto com as versões dos pacotes. Esse arquivo pode ter qualquer nome: requirements.pip, pip-requirements.txt ou ainda dependencias.txt. Nesse artigo vamos usar a convenção mais comum: requirements.txt.

O básico

Se você já sabe usar o pip, sugiro pular para a próxima seção.

Nota: use Python sempre com virtualenv. O Python 3 já traz o módulo venv embutido. No Python antigo (Python 2) você precisava instalar o pacote virtualenv.

Vou criar um virtualenv, instalar um pacote com pip e listar o que está instalado:

$ python3 -m venv ~/virtualenvs/meuvenv  # cria o virtualenv
$ source ~/virtualenvs/meuvenv/bin/activate  # ativa o virtualenv
(meuvenv) $ pip install django
Collecting django
... # várias mensagens do pip ...
Successfully installed django-1.9.2
(meuvenv) $ pip freeze
Django==1.9.2

O comando pip freeze lista o que está instalado. Muito prático.

Agora eu posso usar o recurso de redirecionamento do shell para criar a lista de dependências automaticamente:

(meuvenv) $ pip freeze > requirements.txt

Em uma próxima instalação, eu posso fazer assim:

(meuvenv) $ pip install -r requirements.txt

O pip freeze lista tudo

Se eu instalar mais um pacote, veja o resultado:

(meuvenv) $ pip install django-post-office
Collecting django-post-office
... # várias mensagens do pip ...
Successfully installed django-post-office-2.0.5 jsonfield-1.0.3
(meuvenv) $ pip freeze
Django==1.9.2
django-post-office==2.0.5
jsonfield==1.0.3

Note que o pacote jsonfield aparece na lista, apesar de eu não tê-lo instalado. Isso acontece porque o django-post-office depende dele.

Aqui é possível perceber que o pip freeze pode não ser a melhor solução para administrar as dependências de pacotes porque ele lista tudo o que está instalado, e não apenas o que eu mandei instalar.

Solução: edite o arquivo requirements.txt e deixe nele apenas os pacotes instalados explicitamente.

Fixar versões dos pacotes

O pip freeze é bastante útil porque ele lista os pacotes com as versões instaladas.

Antes de analisar as vantagens e desvantagens dessa abordagem, é preciso entender que o pip trabalha geralmente com versionamento semântico. Para ilustrar, vou usar o Django como exemplo:

  1. Toda versão de Django 1.8.x é compatível com as 1.8 anteriores. Essas versões geralmente trazem correção de bugs ou de segurança. Exemplo: Django 1.8.9 é compatível com Django 1.8.0.
  2. Django 1.9.x pode quebrar a compatibilidade ou interromper alguns recursos das versões 1.8.x.
  3. A versão 2.0 vai quebrar compatibilidade com as versões 1.x.

Conhecendo essa estrutura, percebo que o requirements.txt gerado com o pip freeze garante o funcionamento da minha aplicação com uma versão específica (a mais específica possível) de um pacote.

Vantagens:

  • Existe a garantia que minha aplicação vai funcionar, porque eu testei com determinada versão das dependências.
  • Não existe o risco de instalar um pacote em uma versão mais recente, incompatível com minha aplicação.

Por isso, fixar a versão do pacote instalado é uma boa ideia.

Desvantagem: correções e atualizações de segurança em versões compatíveis das dependências não serão instaladas.

Fixar com flexibilidade

Se fixar completamente a versão de um pacote impede que as novas versões de correção sejam instaladas, preciso de uma alternativa.

O que eu acho que vai funcionar

Se eu fixar Django==1.9.2 ficarei preso a essa versão. Então, como $ pip install django instala a última versão disponível, um $ pip install django==1.9 vai instalar a última versão da série 1.9 disponível, certo?

Errado! O pip não funciona assim.

Se você fixar a versão em Django==1.9 o pip vai instalar a versão 1.9, que é a primeira da série, ao invés da última disponível da série.

Se você fixar a versão em Django==1.9.* você vai perder a versão 1.9 porque não tem um ponto depois do nove.

O que realmente funciona

O comando correto para instalar a última versão da série 1.9, mesmo se ainda não houve nenhum patch de correção, é:

$ pip install 'django>=1.9, <1.10'

Ou

$ pip install 'django<1.10'

O nome do pacote e as versões precisam estar entre aspas porque os sinais "menor que" e "maior que" são símbolos de redirecionamento de entrada e saída padrão no Linux.

Com uma dessas duas alternativas eu fixo a versão prevendo updates de correção sem o risco de ter problemas de compatibilidade entre versões.

Conclusão

Boa prática: Fixar a versão dos pacotes instalados prevendo correção de bugs e atualizações de segurança.

O pip freeze é útil para ajudar a fixar versões de pacotes, mas não use-o cegamente.

Para garantir atualizações de segurança e correção de bugs, eu preciso:

  1. Usar o pip freeze para gerar uma lista inicial de pacotes instalados;

  2. Manter o arquivo requirements.txt manualmente, com o auxílio do pip freeze, deixando nele apenas os pacotes instalados explicitamente;

  3. Fixar as versões dos pacotes para garantir as correções de bug e atualizações de segurança.

Vinicius Assef

Eu sou apaixonado por Python e shell script.

Aprenda com seus erros e dê nome certo às coisas.