Gerenciamento de ambientes e pacotes no Python
Diferente de outras linguagens de programação como Go (mod), Rust (Cargo), Elixir (Hex), Ruby (Bundler), PHP (Composer), Clojure (Lein), Nodejs (Npm) e outras tantas que tem seus sistemas de gerenciamento de pacotes e dependências “padronizados” ou nativos na linguagem, Python não tem um único “padrão”, longe disso: distutils, setuptools, easy_install, PyPI, twine, wheel, pip, requiments.txt, virtualenv, pipx, pipenv, Pipfile, pip-tools, flit, pdm, poetry, conda são algumas das ferramentas (e tem mais) que temos no ecossistema de gerenciamento de ambientes e pacotes no Python.
Como funciona o sistema de módulos
O módulo nativo sys fornece acesso a variáveis e funções que interagem com o interpretador. O sys.path
é uma variável dentro do módulo sys
que contém uma lista de diretórios que o interpretador irá buscar quando algum módulo por requerido nos imports
.
Exemplo do que temos dentro do sys.path:
Vou demonstrar dentro do projeto httpx para explicar o que cada índice da lista representa:
O que está demonstrado à direita da tela é o arquivo _main.py
e ao lado esquerdo está a árvore de diretórios do projeto.
Portanto, _main.py faz diversos imports, de arquivos locais, de pasta diferentes dentro do projeto, de módulos nativos do Python como functools, json e sys e de outros projetos como click, httpcore e pygments.
(Index 0) O primeiro índice é uma string em branco (‘ ’) e quer dizer que o Python irá buscar o arquivo do import no mesmo diretório que está sendo executado.
No exemplo _main.py
está importando a classe Client
do arquivo _client.py
que está no mesmo diretório, por isso, utiliza-se o ponto (.), se estivesse em outra pasta, o importe seria: from other._client import Client
.
(Index 1) O segundo índice do sys.path busca na biblioteca padrão (Stardard Library) de instalação, ou seja, irá buscar os módulos e funções nativas.
No mesmo exemplo, irá encontrar functools, json, sys e typing nesse diretório porque são funções nativas da linguagem.
(Index 2) O terceiro índice buscará na pasta do usuário, cada usuário tem o seu system path e é nele onde o usuário instala os programas para não causar conflitos com programas instalados por outros usuários do sistema operacional.
E por fim, se não encontrar o módulo nos diretórios anteriores, buscará no (Index 3) diretório de bibliotecas do sistema, onde, geralmente, se encontra a maioria dos programas feitos em Python que o sistema operacional utiliza.
Caso não encontre nesses diretórios de exemplo, o Python mostrará uma exceção dizendo que não encontrou o módulo.
É possível adicionar outros diretórios no sys.path, é possível controlar os diretórios com variáveis de ambiente ou exportar o PYTHONPATH. Modifique, somente se souber exatamente o que está fazendo!
O que precisamos
Precisamos que uma única solução, simples, rápida e eficiente, que faça:
- Faça o download de um projeto no PyPI.
- Instale o projeto e todas as suas dependências, sem que ocorra problemas de conflitos de versões das dependências.
- Tenha a verificação de hashes de cada pacote.
- Tenha um arquivo de configuração padronizado.
- Fazer build e upload de um pacote para o PyPI.
- Seja multiplataforma.
- Seja sustentável financeiramente. Sugestão: Financiada pela Python Software Foundation (PSF) (veja os patrocinadores da PSF aqui).
Evolução de 1991 a 2021
A linguagem tem seu primeiro release lançado oficialmente em 1991, e a primeira versão de uma ferramenta para distribuição de pacotes foi a distutils
, lançada em setembro de 2000 na versão 1.6. O Distutils permite que você estruture seu projeto para que tenha um setup.py
e, com isso, facilita a instalação e distribuição do projeto.
Nesse meio tempo, se quisesse instalar o código de outra pessoa, teria que baixar, instalar e adicioná-lo ao PYTHONPATH
manualmente. Em 2003 foi criado, o então cheeseshop
e agora, PyPI
(Python Package Index), que é um catálogo/repositório online de programas para qualquer pessoa baixar e instalar.
Com um repositório centralizado e milhares de projetos, muitos projetos começaram a ter mais e mais dependências e gerenciá-los era um novo desafio por haver a possibilidade de incompatibilidades e conflitos de versões.
Em 2004 e nos anos seguintes, a comunidade começou a desenvolver um conjunto de ferramentas para extender o distutils para gerenciamento de dependências complexas, reconhecimento automatizado de precedência de versão e uma ferramenta de instalação automática chamada easy_install.
Embora nunca tenha chegado à biblioteca padrão, setuptools
tornou-se o meio principal para criar e distribuir pacotes.
Mesmo com todas as ferramentas úteis feitas com o setuptools e easy_install, o problemas de compartilhamento de pacotes e o conflitos de versões ainda ocorriam. Em 2006 começou a ser discutido a possibilidade de construção de ambientes virtuais e em 2007 foi lançado o virtualenv
que permitia construir ambientes isolados (ou virtuais) de um projeto a partir de uma instalação de sistema central de Python, com isso, o easy_install instala as dependências do projeto somente naquele ambiente isolado.
Em 2008, o mesmo criador do virtualenv, em alternativa e correções ao easy_install, cria o pip
para fácil instalação de pacotes e dependências. O pip
utiliza o arquivo requirements.txt
para gerenciar as dependências do pacotes.
Em 2011, A Python Software Foundation cria um grupo de trabalho chamado Python Packaging Authority (PyPA) responsável por padronizar o meio de distribuição de pacotes, manter o pip e o virtualenv e também, por manter a infraestrutura do PyPI. A linha do tempo da história do PyPA encontra-se aqui.
Em 2012, a empresa Anaconda Inc. cria o gerenciador de pacote chamado Conda, voltado para a comunidade científica de desenvolvimento, criando uma infraestrutura totalmente separada do pip, virtualenv e PyPI.
Também em 2012, foi criado o pip-tools
que é um conjunto de ferramentas para gerenciar e manter compilações determinísticas especificando as dependências por meio de hash ( — require-hashes que o pip implementou em 2016).
Em 2013, com a PEP 427, define-se que o formato binário padrão para distribuição dos pacotes é o formato wheel
e é criado o twine
para interagir e realizar upload seguros de pacotes para o PyPI.
Em 2014, a PEP 440 descreve como identificar versões de pacotes com um novo schema de versionamento.
Em 2015, é aprovadas a PEP 503 e PEP 508 que descrevem padrões para pacotes e para distribuição no PyPI.
Ainda em 2015, começa o desenvolvimento do flit
, que pretendia ser o substituto de todo o ecossistema.
Em 2016 a PEP 518 especifica um novo arquivo de configuração de pacotes chamado pyproject.toml
que centraliza e padroniza todos os arquivos de configuração em um só.
Ainda em 2016, o pip
lança uma nova versão com a implementação do hashes
para cada dependência dentro do requirements.txt.
Em 2017 a PEP 517 aprova builds independentes
. Devido problemas antigos do distutils, e a tentativa de se fazer um distutils2, abrindo a possibilidade de não necessariamente utilizar distutils, ou seja, se você quiser usar distutils, ótimo; se você quiser usar outra coisa, isso deve ser fácil de fazer usando métodos padronizados.
Ainda em 2017 é lançado o pipx
, ferramenta para instalar e rodar programas sem causar conflitos de dependências com outros pacotes instalados no sistema.
Estamos em 2017, ou seja, 26 anos desde a criação da linguagem e ainda não temos uma solução única e completa para gerenciar e distribuir pacotes.
De acordo com a sátira do xkcd, como está nosso ambiente:
Pipenv nasce ainda em 2017 como um projeto que visa agregar o pip, virtualenv e o Pipfile em um único conjunto de ferramentas para gerenciar ambientes, dependências e pacotes, cria e verifica hashes de arquivo para garantir a conformidade com dependências com os arquivos Pipfile
e Pipfile.lock
.
Em 2018 nasce o Poetry com a proposta de substituir todas as ferramentas já existentes de empacotamento e distribuição existentes (mesmo que, por debaixo dos panos, utiliza o pip e o virtualenv para instalar e criar ambientes isolados), ou seja, supre a necessidade do distutils, setuptools, virtualenv, pip, faz builds determinísticos igual o pip-tools e pip faz, segue o padrão da PEP 440 sobre semântica de versões, segue os padrão do PyPI da PEP 503 e PEP 508, utiliza o padrão do pyproject.toml da PEP 518 e cria builds independentes da PEP 517.
Portanto, o Poetry (em 2021), é o único projeto que abrange todos as ferramentas do ecossistema e segue todos os padrões definidos nas PEPs.
Em 2020, é lançada uma nova versão do pip, com um novo resolvedor de dependências.
Ainda em 2020, é criado o projeto pdm
, que atende a PEP 582, ou seja, não há de se criar ambientes virtuais, cria-se um diretório local de pacotes python para cada projeto chamado __pypackages__
para incluir todos lá dentro. Seguindo a mesma ideia do node_modules do Nodejs.
Em 2021, ainda não temos uma ferramenta oficial, única e completa para todo o ecossistema (o poetry está perto disso) e que atenda todos os requisitos que escrevi no tópico logo acima chamado “O que precisamos”.
Conclusão
A organização PyPA no Github mantém a maioria dos projetos de empacotamento e distribuição, na minha humilde opinião
, são vários projetos muito parecidos e para um mesmo objetivo final que são propostos, não vale tal esforço humano e financeiro. Poderíamos aprender com as linguagens citadas na introdução.