Pipenv: Proposta: padrões `pipenv` e antipadrões para projeto de biblioteca python

Criado em 5 abr. 2018  ·  74Comentários  ·  Fonte: pypa/pipenv

Hackeando maya Eu aprendi algumas lições que resultaram na minha seguinte proposta de uso recomendado de pipenv em bibliotecas python. Espero que outras pessoas analisem a proposta e, se chegarmos a um acordo, o texto (atualizado) pode acabar em pipenv docs.

pipenv padrões e antipadrões para projeto de biblioteca python

EDITAR
A seguir, é mais aplicável para bibliotecas python gerais (principalmente de código aberto), que devem ser executadas em diferentes versões e sistemas operacionais de python. Bibliotecas desenvolvidas em ambiente corporativo estrito podem ser casos diferentes (certifique-se de revisar todas as seções de Problemas de qualquer maneira).

FIM DA EDIÇÃO

TL; DR : Adicionar pipenv arquivos ao projeto de biblioteca Python provavelmente introduzirá complexidade extra e pode ocultar alguns erros sem adicionar nada à segurança da biblioteca. Por esta razão, mantenha Pipfile , Pipfile.lock e .env fora do controle de origem da biblioteca.

Você poderá usar a potência total de pipenv independentemente de seus arquivos residirem em .gitignore .

Biblioteca Python versus aplicativo Python

Por biblioteca python setup.py , sendo direcionado para distribuição e uso em várias plataformas que diferem na versão python e / ou sistema operacional.

Os exemplos são maya , requests , flask etc.

Por outro lado (não a biblioteca python), existem aplicativos direcionados para um interpretador python específico, sistema operacional e muitas vezes sendo implantados em um ambiente estritamente consistente.

pipfile descreve essas diferenças muito bem em seu Pipfile vs setup.py .

O que é pipenv (ferramenta de implantação)

Eu concordo totalmente com a declaração de que pipenv é uma ferramenta de implantação , pois permite:

  • definir requisitos rígidos ( Pipfile.lock ) para implantação de ambiente virtual
  • aplicar esses requisitos estritos de maneira reproduzível em máquinas diferentes

Ajuda quando é necessário implantar um aplicativo ou desenvolver em um ambiente python muito consistente entre vários desenvolvedores.

Chamar pipenv ferramenta de empacotamento é enganoso se alguém espera que ela crie bibliotecas python ou esteja profundamente envolvido na criação delas. Sim, pipenv pode ajudar muito (no desenvolvimento local de bibliotecas), mas pode possivelmente prejudicar (geralmente em testes de CI quando usado sem reflexão mais profunda).

Aplicar "razões de segurança" no contexto errado

TL; DR : pipenv fornece ambiente seguro por meio da aplicação de dependências concretas aprovadas descritas em Pipfile.lock file e a biblioteca python só tem permissão para definir dependências abstratas (portanto, não pode fornecer Pipfile.lock ).

pipenv brilha em cenários de implantação seguindo estas etapas:

  • definir dependências abstratas (via Pipfile )
  • gerar dependências concretas dele resultando em Pipfile.lock
  • criar ambiente python (virtual) refletindo essas dependências concretas
  • execute testes para ter certeza de que determinado ambiente funciona conforme o esperado e é seguro
  • libere o testado "dourado" Pipfile.lock como definição de ambiente Python aprovado
  • outros podem usar pipenv sync para aplicar "o ouro" Pipfile.lock outro lugar, obtendo um ambiente python idêntico.

Com o desenvolvimento da biblioteca python, não se pode alcançar tal segurança, porque as bibliotecas não devem definir dependências concretas . Quebrar essa regra (portanto, tentar declarar dependências concretas pela biblioteca Python) resulta em problemas como:

  • problemas para encontrar uma versão satisfatória de bibliotecas compartilhadas (cada pacote estrito define a versão exata da biblioteca compartilhada e é muito provável que as versões sejam diferentes e evitem encontrar a versão comumente aceitável)
  • dependências concretas podem depender da versão do python, sistema operacional ou outros marcadores de ambiente e tentar instalar o pacote em um contexto diferente pode facilmente falhar em satisfazer algumas das regras definidas nas dependências abstratas originais.

Problema: ocultando dependências definidas de setup.py quebradas

setup.py deve definir todas as dependências abstratas via install_requires .

Se Pipfile definir essas dependências também, pode facilmente ocultar problemas como:

  • falta de dependência em install_requires
  • Pipfile define regras específicas (faixas de versão, etc.) para uma dependência e install_requires não.

Para evitar isso, siga estas regras:

  • as dependências definidas pela biblioteca não devem aparecer em Pipfile
  • a seção [packages] em Pipfile deve estar vazia ou definir apenas uma única dependência da própria biblioteca.

Problema: Pipfile.lock no repositório

Manter Pipfile.lock (normalmente por "razões de segurança") no repositório da biblioteca é errado, porque:

  • dependências descritas são provavelmente inválidas para diferentes versões de python ou em outro sistema operacional
  • os desenvolvedores são forçados a atualizar o arquivo não apenas quando adicionam / removem alguma dependência, mas também quando outras bibliotecas são atualizadas e podem ser utilizadas dentro da biblioteca.

Para evitá-lo, deve-se:

  • remova Pipfile.lock do repositório e adicione-o em .gitignore

Problema: competindo com tox (escondendo usedevelop )

Se tox.ini contiver em sua commands section entradas como:

  • pipenv install
  • pipenv install --dev
  • pipenv lock

muitas vezes é um problema, porque:

  • pipenv install deve instalar apenas a própria biblioteca, e tox está (por padrão) fazendo isso também. Além da duplicidade, também evita usedevelop=True e usedevelop=False em tox.ini porque Pipenv é capaz de expressá-lo apenas em uma variante (e tox.ini permite diferenças em ambientes diferentes).

Para evitá-lo, deve-se:

Problema: quebrando compilações, se pipenv falhar

pipenv está sob forte desenvolvimento e alguma coisa quebrará algum dia. Se tal problema interromper sua construção de CI, há uma falha que poderia ser evitada não usando pipenv e usando ferramentas tradicionais (que geralmente são um pouco mais maduras).

Para evitá-lo, deve-se:

  • pense duas vezes antes de adicionar pipenv em um script de construção de CI, tox.ini ou lugar semelhante. Você sabe o valor que obtém ao adicioná-lo? O trabalho poderia ser feito com o ferramental existente?
  • não adicione "apenas por razões de segurança" ou porque "todo mundo o faz".

Resumo

As principais questões relacionadas ao papel de pipenv no desenvolvimento da biblioteca Python são:

  • Que valor pipenv realmente traz? R: Ferramenta de gerenciamento Virtualenv.
  • Qual é o caso de uso relevante para pipenv ? R: Gerenciar virtualenv.
  • Ele deve aparecer no repositório da biblioteca? R: NÃO.

Seguem-se mais alguns detalhes e truques.

pipenv não adicionará nenhuma segurança ao seu pacote

Não empurre para o projeto apenas porque todo mundo faz isso ou porque você espera segurança extra. Isso vai te decepcionar.

A proteção usando dependências concretas (e aprovadas) deve ocorrer em uma fase posterior no aplicativo que vai usar sua biblioteca.

Mantenha Pipfile , Pipfile.lock e .env arquivos fora do repositório

Coloque os arquivos em .gitignore .

Pipfile é fácil de recriar conforme demonstrado abaixo, pois a maioria ou todos os requisitos já estão definidos em seu setup.py . E o arquivo .env provavelmente contém informações privadas, que não devem ser compartilhadas.

Manter esses arquivos fora do repositório evitará todos os problemas, que podem acontecer com compilações de CI ao usar pipenv em situações que não são apropriadas.

pipenv como caixa de ferramentas privada do desenvolvedor

pipenv pode simplificar o trabalho do desenvolvedor como ferramenta de gerenciamento virtualenv.

O truque é aprender a recriar rapidamente seus arquivos (privados) pipenv relacionados, por exemplo:

$ cd <project_repository>
$ # your library will bring the dependencies (via install_requires in setup.py)
$ pipenv install -e .   
$ # add more dev tools you preffer 
$ pipenv install --dev ipython pdbpp
$ # start hacking
$ pipenv shell
...

Use .env file se precisar de um método conveniente para configurar variáveis ​​de ambiente.

Lembre-se: mantenha o uso de pipenv fora de seus builds de CI e sua vida ficará mais simples.

Truque: use a capacidade de setup.py para declarar dependências extras

Em sua setup.py use a seção extras_requires :

from setuptools import setup

setup(
    name='mypackage',
    ....,
    install_requires=["jinja2", "simplejson"],
    extras_require={
        'tests': ['pytest', 'pyyaml'],
        'pg': ['psycopg2'],
    },
    ....
)

Para instalar todas as dependências declaradas para tests extra:

$ pipenv install -e .[tests]

Observe que ele sempre incluirá as dependências install_requires .

Este método não permite dividir dependências em seções padrão e dev, mas isso não deve ser um problema real nos cenários esperados.

Discussion Type

Comentários muito úteis

@ Moritz90 Várias listas de discussão do Python seriam bons locais para manter essa discussão.

pypa-dev é o mais definitivo para discussões centradas no empacotamento do Python e no ecossistema ao seu redor. Eu provavelmente começaria aqui se fosse postar uma discussão semelhante.

python-ideas é um lugar para discutir ideias e tem grande visibilidade para toda a comunidade Python. Também seria um bom ponto de partida se você quiser levar isso para o nível PEP (eventualmente você o faria, eu acho).

Todos 74 comentários

Isso é muito impressionante, muito obrigado por compilar. Com certeza revisarei com mais detalhes daqui a pouco

/ cc @uranusjr @jtratner @ncoghlan

Algumas referências a maya questões:

  • kennethreitz / maya # 138 (RemovePipfile.lock do repositório)
  • kennethreitz / maya # 139 (Pular execução de pipenv em tox.ini ...)
  • kennethreitz / maya # 145 (correção pendulum>=1.0 em setup.py: a versão estava no Pipfile, mas faltava em setup.py)
  • kennethreitz / maya # 143 (RP mostrando como o problema de pipenv interrompeu toda a execução do Travis)
  • kennethreitz / maya # 144 (PR Refactor pipenv uso de acordo com as melhores práticas semi-oficiais)

Eu amo isso também Talvez devêssemos adicionar isso à documentação do Pipenv, ou mesmo ao Guia do Usuário de Empacotamento do

O corolário do conselho acima parece ser "renunciar a construções de CI determinísticas / reproduzíveis", o que me parece um antipadrão muito amplo.

O que você está propondo como alternativa que ainda permitiria o determinismo?

@ tsiq-oliverc Construções determinísticas têm seu lugar no momento, um aplicativo está para ser construído.

Imagine a seguinte tentativa de realizar compilações realmente determinísticas da biblioteca Python:

  • as compilações devem ser baseadas em Pipfile.lock
  • cada contexto de execução (combinação de cada python e variante do sistema operacional) pode ter Pipfile.lock resultante das dependências abstratas da biblioteca definidas em Pipfile
  • O repositório teria que fornecer instâncias Pipfile.lock separadas definidas no repositório. Observe que construir Pipfile.lock automaticamente durante a construção de CI não adiciona nenhum determinismo

É um grande esforço extra. E o que você obtém é uma biblioteca, que será instalada em um contexto diferente (por exemplo, uma semana depois a instalação padrão irá pegar uma ou duas dependências atualizadas) e que não terá nada com o fato de que você usou Pipfile.lock , que está obsoleto no momento.

O conflito está no fato de que a biblioteca nunca deve definir dependências estritas internamente.

Se você pensa, existe outra alternativa para obter compilações determinísticas para a biblioteca python - descreva-a.

@vlcinsky - Se um consumidor de sua biblioteca usa diferentes versões de dependências, etc., então isso está fora de seu controle. Portanto, concordo que não há maneira viável de um mantenedor de biblioteca gerenciar isso.

Mas o objetivo aqui é presumivelmente um escopo muito menor. Em particular, eu veria os objetivos de um mantenedor de biblioteca como os seguintes (que são aproximadamente equivalências):

  1. Se você executar seu CI duas vezes, terá a garantia de obter o mesmo resultado (apesar dos problemas de rede!).
  2. Você pode recriar localmente (e, portanto, depurar) o comportamento observado no CI, mesmo que isso signifique executar o Docker / etc. localmente.
  3. Você pode dizer com segurança "Minha biblioteca se comporta conforme o esperado com versões de dependência X, Y, Z" para seus consumidores.

Se alguma dessas três coisas não funcionar, parece-me a antítese do controle de qualidade.

Então, sim, eu diria que se você garante suporte às variantes Python A, B e C para o seu consumidor, e elas se comportam de maneira diferente o suficiente para que um arquivo de bloqueio (etc.) não resolva, então você deve ter três arquivos de bloqueio (ou qualquer que seja).

Eu não usei o Pipenv o suficiente para saber como isso seria fácil na prática, no entanto.

No momento, estou pensando em adicionar Pipfile s também a alguns projetos de biblioteca para o sistema de CI.

Eu absolutamente preciso do bloqueio de dependência (+ hashing) para cumprir as diretrizes de segurança de toda a empresa e atualmente não preciso testar com diferentes versões do Python, já que há apenas uma que é oficialmente suportada. E o fato de que o pipenv simplifica a configuração de um ambiente de desenvolvimento local, incluindo o virtualenv, é um bom efeito colateral.

E o que você obtém é uma biblioteca, que será instalada em um contexto diferente (por exemplo, uma semana depois a instalação padrão pegará uma ou duas dependências atualizadas) e que não obterá nada com o fato de que você usou Pipfile.lock , que está obsoleto no momento.

Isso não é universalmente verdadeiro. No mundo do software corporativo, você ainda tem ambientes muito específicos que são oficialmente suportados e um problema de segurança em uma dependência resulta na atualização do seu produto, em vez de o cliente atualizar a dependência por conta própria.

(Sim, estou falando de uma biblioteca, não de um aplicativo aqui ...)

@ Moritz90 seu cenário é para a biblioteca python em ambiente corporativo e pipenv pode ajudar porque é um ambiente muito mais determinístico.

Minha descrição visa bibliotecas python gerais, como flask , request , maya etc. onde o contexto é muito mais variável. Tentando consertar algumas coisas em maya , fiquei frustrado ao aprender que, em muitos casos, o uso de pipenv introduzia problemas reais (normalmente ocultando problemas que seriam normalmente detectados), embora não fornecesse muito ou nenhum acréscimo valor.

Obter compilações determinísticas é bom, mas incorre em custos. E se feito errado, você pode pagar a mais por um resultado de qualidade inferior - e isso é o que eu queria evitar.

Eu diria que esta é uma das instâncias em que não queremos que as compilações sejam absolutamente determinísticas. Se você não fixar suas dependências com == , você está se comprometendo a manter o suporte para várias versões por padrão e deve projetar a biblioteca dessa forma. Uma atualização de dependência interrompendo o build no CI é na verdade uma coisa boa porque expõe um bug na biblioteca. Dependências completamente determinísticas (como gerenciadas pelo Pipenv) mascarariam isso. Ainda assim, seria benéfico ser capaz de ser determinítico quando você quiser, isso geralmente não é o melhor.

@uranusjr - Claro. Concordo que, se o desejo for "construções não determinísticas", o conselho acima pode muito bem fazer sentido. Na verdade, é quase uma equivalência lógica e poderia ser declarada de forma muito mais sucinta: "Se você não quer compilações determinísticas, não use uma ferramenta ( pipenv ) cujo objetivo é garantir compilações determinísticas" 😄.

Mas essa certamente não é uma meta desejável em geral.

@ tsiq-oliverc boa definição de escopo - suporta discussão focada. Eu acrescentaria mais um requisito: O determinismo CI não deve ocultar possíveis problemas na biblioteca testada.

Se usarmos Pipenv.lock , criar virtualenv com base nisso e executar o teste de CI da biblioteca, fizemos parte da funcionalidade que a biblioteca deveria fazer - instalando dependências adequadas. Se a biblioteca estiver de alguma forma danificada a este respeito - o ambiente pré-instalado esconderia este problema.

Para mim, parece mais importante detectar problemas em uma biblioteca do que executar CI de maneira determinística. Se houver uma maneira de fazer as duas coisas (por exemplo, executando o teste por trás do índice pypi privado, que também pode oferecer suporte ao determinismo), não tenho nenhum problema, mas se houver um conflito, eu tenho minhas prioridades.

Não me leve a mal: não há desejo de executar compilações não determinísticas, meu desejo é executar compilações de CI, que detectarão o máximo de problemas possível.

@vlcinsky Claro, eu só queria compartilhar minha experiência para ter certeza de que a documentação atualizada reflete isso também. A documentação atual faz um ótimo trabalho ao explicar as vantagens e desvantagens:

Para bibliotecas, defina dependências abstratas por meio de install_requires em setup.py. [...]
Para aplicativos, defina as dependências e onde obtê-las no Pipfile e use esse arquivo para atualizar o conjunto de dependências concretas em Pipfile.lock. [...]
Obviamente, Pipfile e pipenv ainda são úteis para desenvolvedores de bibliotecas, pois podem ser usados ​​para definir um ambiente de desenvolvimento ou teste.
E, é claro, existem projetos para os quais a distinção entre biblioteca e aplicativo não é tão clara.

(Destacou a parte que se aplica ao meu caso.)

Só quero ter certeza de que continuará assim. Acho que sua postagem original contém muitas declarações gerais sem uma isenção de responsabilidade de que você está falando sobre um projeto de código aberto que será publicado no PyPI.

@ Moritz90 Concordo totalmente. Eu estava tentando destacar esse foco, mas posso torná-lo ainda mais visível.

@ Moritz90 Eu adicionei uma nota introdutória refletindo seu comentário.

@vlcinsky - Isso faz sentido. Eu entendo que você não fizer isso explicitamente quer não determinística constrói, mas eu acho que é inevitavelmente equivalente ao que você quer (ou seja, para questões de captura quando as suas dependências a montante atualização).

Pensando em voz alta, qual é a melhor maneira de resolver esses dois objetivos conflitantes? Uma possibilidade é ter um processo de CI de duas fases:

  1. A fase determinística. Aproveita um Pipfile.lock em seu repo, então é totalmente reproduzível.
  2. A fase não determinística. Executa pipenv update e, em seguida, executa os testes, para que obtenha a última de todas as suas dependências (que é basicamente o mesmo que o comportamento sem lockfile, eu acho?).

@ tsiq-oliverc Para obter compilações determinísticas, eu pensaria na seguinte configuração:

  • construir trabalho de cache pypi: executado uma vez, produzindo alguma forma de cache de índice pypi (como diretório de arquivos ou algo semelhante)
  • trabalho de teste de biblioteca: usando o cache pypi, mas evitando pipenv

Usar pipenv para fazer a instalação é semelhante ao que a própria biblioteca deve fazer, mas é definitivamente diferente porque é um código diferente que faz o trabalho.

construir trabalho de cache pypi

$ git clone <repo_url> <project_dir>
$ cd <project_dir>
$ pip install pipenv
$ $ # clean pypi cache and make it ready to cache somehow - not described here
$ pipenv install -e .[test]
$ # if we need extra testing packages in pipenv
$ pipenv install <extra_test_packages>
$ # record current requirements expressed in `Pipfile.lock`
$ pipenv lock
$ # if needed, record the `Pipfile.lock` somewhere

Os resultados desse trabalho são:

  • Pipfile.lock como dependências registradas (pode ajudar os desenvolvedores a reproduzir o ambiente facilmente)
  • cache local pypi pré-preenchido

trabalho de teste de biblioteca

existem fases:

  • configure o ambiente para forçar tox , pip etc. usando apenas nosso cache local pypi
  • execute os testes de CI (evite usar pipenv )

o que nós temos

  • biblioteca é testada em ambiente determinístico
  • biblioteca é testada incl. é a capacidade de se instalar por conta própria
  • Pipfile.lock registra pacotes pypi que foram usados ​​para instalar a biblioteca. Ele pode ser usado para reproduzir o ambiente no site do desenvolvedor.
  • a adaptação para pacotes atualizados em (possivelmente externo) pypi é simples (execute novamente o "trabalho de cache de construção de pypi) e é feita de maneira controlada (o conteúdo de pypi é registrado incluindo hashes)

Outra vantagem é que esta configuração não requer que os desenvolvedores mantenham Pipfile ou Pipfile.lock . Também executar os testes em contextos diferentes é sempre igual ( Pipfile.lock é sempre reconstruído em determinado contexto).

O que ainda falta (e pode ser feito)

O cache pypi é a parte que precisa de alguma pesquisa. Eu acho que um diretório simples seria suficiente e talvez pipenv já esteja pronto para ajudar com isso. Talvez a edição nº 1731 seja a parte que falta.

Como um pacote que faz a resolução de dependências, muitos de nossos próprios testes dependem de compilações determinísticas - isto é, pegando coisas conhecidas e esperando um gráfico resolvido. Usamos pytest-pypi para isso.

Adoro a discussão animada sobre este tópico. Acho que a nuance é importante e você deve sempre testar as dependências conhecidas e também as não fixadas

você deve sempre testar as dependências conhecidas, bem como as não fixadas

Eu apoio esta sugestão. É uma boa ideia sempre ter um "estado bom conhecido" explícito para compilações reproduzíveis e simplificar a depuração no caso de uma atualização quebrar algo, além de garantir que as versões menores / de correção de bug também funcionem.

(Na minha opinião pessoal, a situação ideal seria que o gerenciador de pacotes instale as versões secundárias mais recentes por padrão para que as bibliotecas possam sempre especificar as versões de dependência concretas com as quais foram testadas, mas eu percebo que é uma opinião altamente controversa e requer todos para seguir sempre.)

@ Moritz90 @techalchemy @uranusjr @ tsiq-oliverc

Aqui está meu resumo da discussão anterior.

Problemas particulares e soluções propostas

Muitos contextos de execução - quem deve manter Pipfile.lock arquivo (s)?

Cada sistema operacional e interpretador python com suporte contribuem para a matriz de contextos de execução possíveis.

Por exemplo, suporte para Flask (pelo menos material CI visível no repositório):

  • SO Windows (python 2.7 e Python 3.6)
  • Linux (python 2.7, 3.4, 3.5, 3.6, noturno, pypi)
  • OSX (py - não tenho certeza se há mais versões)

Faz 9 contextos de execução diferentes que podem ser diferentes.

Cada contexto de execução pode ter Pipfile.lock .

Quem deve mantê-los?

As opções são:

  • Deixe os desenvolvedores fazerem isso manualmente (SEM MANEIRA)
  • Mantenha apenas um Pipfile.lock para a plataforma de desenvolvimento principal (qual plataforma gosta de ser ignorada?)
  • Criação automática via CI (SIM)

Proposta: Deixe o CI gerar o arquivo por pipenv install -e . . Não o inclua no repo, ajude os desenvolvedores a escolher Pipfile.lock adequados como resultado de compilações automatizadas.

Os desenvolvedores precisam de um ambiente previsível

Ao corrigir um problema, que pode ser causado por alterações de dependências no pypi, o desenvolvedor pode precisar de meios simples para reproduzir o ambiente do teste com falha.

Proposta:

  • para muitos pacotes, as mudanças nas dependências pypi são tão raras que não são um problema real
  • para consertar o ambiente por conta própria, o desenvolvedor pode gerar Pipfile.lock por pipenv install -e . seguido por pipenv lock .
  • para replicar o ambiente do teste com falha, o desenvolvedor escolhe Pipfile.lock do teste com falha.
  • todo: mostre exemplos de como aplicar Pipfile.lock em tox.ini .

O CI deve revelar setup.py quebrado

A biblioteca setup.py pode estar quebrada (falta de dependência em install_requires , falta de especificador de versão, etc.) e o teste de CI não deve ocultar tal problema (pré-instalando dependências omitidas por conta própria).

Proposta:

  • confie em pipenv install -e . para fornecer o mesmo resultado da instalação simples (atualmente existem alguns problemas com isso).
  • execute o teste de instalação simples (sem pipenv ) e possivelmente compare que a saída pip freeze resultante é um subconjunto do que é instalado por pipenv .

Dependências pypi atualizadas podem quebrar coisas, o CI deve detectar tais perseguições

Alguma atualização de dependência pode quebrar a biblioteca usando-a. O CI deve detectar a falha em tal problema.

Proposta:

  • pelo menos um teste deve ser executado na versão não fixada
  • se o CI sempre gerar um novo Pipfile.lock , isso não é problema (já que executamos no modo não fixado de qualquer maneira)

Modos CI para diferentes tipos de biblioteca

Em todos os modos propostos, tentei evitar manter pipenv arquivos no repositório, evitando que os desenvolvedores mantenham essas coisas realmente complexas (automação !!!).

Em contraste com meu texto original, o segundo e terceiro modos usam pipenv em scripts de CI.

Modo: Corra, Forrest, Corra!

Pacote simples com menor número de dependências que não mudam com frequência.

Simplesmente execute como antes da era pipenv e mantenha as coisas simples para nós.

Casos raros em que dependências farão problemas são fáceis de corrigir e não justificam tornar a CI mais complexa.

Modo: Gerar e selar

Cada vez que o teste de CI é executado, gere um novo Pipfile.lock que descreve completamente o ambiente usado no momento.

O Pipfile.lock se tornará um artefato de CI.

Se as coisas derem errado, o desenvolvedor pode escolher Pipfile.lock da compilação quebrada, aplicá-la localmente e fazer o teste e a correção.

Se alguém quiser implantar, Pipfile.lock da última compilação bem-sucedida pode ser usado.

Modo: Idade do Gelo

Quando a mudança de dependências for um problema real, o CI deve criar Pipfile.lock uma vez e mantê-lo usando por determinado período (um mês?).

Isso torna a configuração do CI mais difícil, pois deve haver pelo menos dois jobs diferentes (um gerando Pipfile.lock , o outro aplicando e usando em testes).

Aviso: Pipfile.lock deve ser atualizado também no momento, setup.py muda dependências.

Observe que a Idade do Gelo requer Scrat, o tipo de teste do esquilo que ignora o status congelado e verifica as versões não fixadas.

Discurso de encerramento

Como visto, o determinismo e a complexidade crescem de modo a modo.

Minha proposta seria:

  • iniciar simples ("Executar, Forrest, Executar"). Você ganha eficiência e velocidade.
  • se as coisas ficarem muito complexas devido à alteração das dependências, vá para "Gerar e selar". Você ganha repetibilidade no ambiente local.
  • se as coisas estiverem realmente ruins, vá para o modo "Idade do Gelo". Você ganha determinismo (temporário).

Todos os ganhos custam alguma coisa.

Se o objetivo aqui é atualizar o conselho nos documentos, então honestamente parece irresponsável dizer algo dramaticamente diferente para "Siga as melhores práticas (compilações reproduzíveis) por padrão, até que você não tenha escolha."

@vlcinsky Sob o título "Modo: Gerar e selar", pode fazer sentido mencionar que o último Pipfile.lock bem-sucedido deve sempre ser mantido por perto, por exemplo, declarando-o como um artefato Jenkins. Com essa mudança, seria bom recomendar essa configuração para a maioria dos projetos. Como @ tsiq-oliverc, eu não recomendaria o primeiro modo, nunca.

Quanto mais penso nisso, mais sinto que esta documentação se tornará uma seção sobre por que usar pipenv para compilações de CI é uma ótima ideia, mesmo se você estiver desenvolvendo uma biblioteca.

@ tsiq-oliverc a grande maioria dos pacotes python gerais estão no modo "Executar, Forrest, Executar". Eu ajudei alguns desses pacotes com a introdução de tox e pytest , porque senti que isso contribuiria para a qualidade de determinado pacote e porque eu tinha uma ideia bastante clara de como isso poderia ser bem feito.

Agora, há outra grande ferramenta e eu me pergunto como usar pipenv corretamente em projetos Python gerais para contribuir com sua qualidade. Quero encontrar uma ou duas receitas que funcionem bem, que sejam justificadas e fáceis de seguir.

O que eu diria ao projeto Flask?

  1. Siga as melhores práticas (compilações reproduzíveis) por padrão, até que você não tenha escolha?
  2. Adicionar 9 Pipfile.lock arquivos e configurar a política para atualizá-los?
  3. Refatorar scripts de CI para Travis e Appveyor para trabalhar em duas fases seguindo o modo da Idade do Gelo?
  4. Modificar scripts de CI para Travis e Appveyor para gerar Pipfile.lock artefato para casos em que alguém precisa reproduzir o teste com falha em seu próprio computador?
  5. sem comentários, além de "Muito obrigado pelo Flask."

O objetivo é encontrar um estilo de trabalho funcional. Se terminar em doc, bom, se não, não tem problema.

@vlcinsky Eu diria que (1) e (4) devem ser as recomendações para tais projetos. Embora sem um Pipfile.lock pré-existente, você não saberá as versões usadas na compilação com antecedência (o que é bom fora de ambientes corporativos), você ainda obterá um resultado reproduzível se gerar e arquivar o arquivo de bloqueio durante a construção.

Edit : A versão tl; dr da minha recomendação seria:

  • Sempre certifique-se de que suas compilações sejam reproduzíveis, independentemente de estar desenvolvendo uma biblioteca ou um aplicativo. pipenv pode ajudá-lo a atingir esse objetivo.
  • Se você estiver desenvolvendo um aplicativo, comprometa Pipfile.lock em seu repositório e use-o para implantação. (Isso já está coberto pela documentação existente.)
  • Se você estiver desenvolvendo uma biblioteca de código aberto, gere Pipfile.lock on-the-fly em seu build de CI e arquive-o para mais tarde.
  • Se você estiver desenvolvendo uma biblioteca e trabalhando em um ambiente corporativo restritivo, mantenha o número apropriado de arquivos de bloqueio e use-os em seus builds de CI.

(Claro, a documentação real deve ter um pouco mais de detalhes e exemplos.)

@ Moritz90 Modifiquei "Gerar e Selar" conforme você propôs.

Re (1): fácil de dizer, impossível de executar sem ser mais específico.

Re (4): sim, eu também acho que "Gerar e Selar" é o modo mais viável. Mas no caso do Flask não ousarei (pelo menos não no momento).

Pipfile.lock re preexistente no ambiente empresarial: deve ser criado de alguma forma, (semi) manualmente ou automaticamente. Eu acho que no ambiente corporativo você não instala diretamente do pypi público, mas usa algum privado e que fornece apenas pacotes aprovados ( devpi-server fornece um ótimo serviço nisso - índices múltiplos, volatilidade controlada de pacotes publicados, aprovações para externos pacotes etc.) Se o processo de construção de Pipfile.lock rodar em tal ambiente, ele só pode usar o que for aprovado, então se uma nova versão deve aparecer lá, alguém tem que se levantar e torná-la aprovada. Seguir a construção do CI testará se ele não quebra as coisas. E com pipenv check teste de problemas de segurança também pode ser automatizado.

Acho que esse fluxo de trabalho seria mais seguro em comparação com alguém criando-o (semi) manualmente. Mas meu conhecimento do ambiente corporativo é muito limitado.

Olá equipe pipenv. Eu compartilho muito do que é dito neste texto, ele ajuda muito qualquer desenvolvedor a entender melhor as limitações do Pipfile / pipenv ao desenvolver uma biblioteca. Eu quero ver este texto ou parte dele integrado na documentação oficial do pipenv.

Tenho a seguinte alteração que gostaria de discutir:

Para nosso pacote python interno, totalmente reutilizável, publicado em nosso pypi interno, etc, e até mesmo para meus próprios pacotes python (ex: cfgtree , txrwlock , pipenv-to-requirements ), eu uso um pacote que alguns já podem conhecer ou mesmo usar , que abstrai esses detalhes e torna a vida do desenvolvedor Python mais fácil: PBR.
O PBR basicamente lê requirements.txt encontrado na pasta raiz de um pacote de distribuição e o injeta em install_requires de setup.py . O desenvolvedor simplesmente precisa manter um requirements.txt com declarações de dependência soltas. Até que o suporte de Pipfile seja integrado oficialmente dentro do PBR, eu tenho que usar pipenv-to-requirements que gera requirements.txt automaticamente de Pipfile para que ambos sejam sincronizados e ambos confirmados no código-fonte, e o PBR faz a injeção corretamente após o pacote de distribuição ter sido construído. Eu acho que alguém poderia usar pipenv para gerar este requirements.txt

Eu trabalho em um suporte do Pipfile para PBR, de modo que ele possa ler o Pipfile (e não o arquivo de bloqueio) e injetá-lo em install_requires como faz com requirements.txt .

Não sei se existem outros pacotes semelhantes, porque ele também faz outras coisas que as pessoas podem não querer (versão do histórico do git, geração automática de AUTHORS e ChangLog).

Mas no final, eu realmente sinto que é tão mais fácil escrever, manter e lidar com o controle de versão de uma biblioteca Python, eu ficaria triste se não compartilhasse essa experiência. Estou promovendo-o como a maneira "recomendada" de escrever bibliotecas Python modernas em minha empresa.

Eu reconheço que é como "trapacear" em todas as dificuldades sobre biblioteca e pipenv, mas no final o trabalho está feito e os desenvolvedores estão felizes em usá-lo até agora. Parte do treinamento de python, que estou dando para o novo desenvolvedor de python em minha empresa, envolve, primeiro, escrever uma biblioteca python mantendo install_requires manualmente e, em seguida, mudar para PBR para ver como fica mais fácil ( e, francamente, eu sou um fã do recurso de

Parte do motivo para declarar as dependências das bibliotecas usando um arquivo dedicado também para bibliotecas é poder usar ferramentas como readthedocs ou pyup (mesmo se o pyup fizer mais sentido quando vinculado a um aplicativo).

Não quero necessariamente promover este método como a maneira "padrão" de fazer o pacote python, na verdade é a maneira "OpenStack", mas gostaria de compartilhar minha experiência, e se outros tiverem experiências semelhantes ou contraditórias, estarei feliz em ouvi-los e atualizar meu ponto de vista.

Equipe, o que você acha de uma espécie de seção de "comunidade" na documentação? Para que usuários como eu possam compartilhar sua experiência sobre como ele usa o pipenv, sem necessariamente o endosso total da equipe do pipenv?

PS: Posso mover isso para um problema dedicado, se você não quiser poluir este tópico

@vlcinsky (1) é muito fácil de executar - coloque seu lockfile em seu repo.

Acho que o que você quer dizer é: é impossível dar conselhos específicos, uma vez que essa estratégia básica não é mais suficiente. Isso certamente é verdade, mas isso ocorre porque o problema específico provavelmente difere caso a caso.

Ou, dito de outra forma, a solução depende de quais garantias adicionais você deseja que seu fluxo de trabalho de CI forneça.

@gsemet você sabe o quê? Todos os meus pacotes python criados nos últimos dois anos são baseados em pbr - é realmente ótimo. E eu sigo suas tentativas de apoiar Pipfile no pbr sempre que posso (alguns polegares para cima, votos etc).

No caso deste problema (procurando por pipenv padrões e antipadrões para bibliotecas python gerais), omiti pbr intencionalmente por dois motivos:

  • tornaria a discussão conceitual mais complexa
  • algumas pessoas não gostam de pbr por outros motivos (você os mencionou) e isso provavelmente desviaria a discussão

Por outro lado, estou ansioso por uma receita sua para os amantes do pbr. Eu lerei isto.

@ tsiq-oliverc, você acertou o prego: coloque seu arquivo de bloqueio em seu repo

Este é exatamente o problema que me motivou a iniciar esta edição. Se você reler o início deste problema, encontrará a descrição de alguns casos, em que adicionar Pipfile.lock pode interromper seus testes de CI (interrompendo a execução da compilação ou ocultando problemas, que de outra forma seriam detectados, ou instalando dependências erradas para determinado contexto ...).

Se você me mostrar um repositório, onde isso é feito corretamente (biblioteca python geral), eu ficaria feliz. Ou eu demonstraria quais riscos existem ou quais coisas estão inacabadas.

Legal ! Eu também mantenho este cortador de cozinha :)

@vlcinsky Certo, então vamos enumerar os problemas específicos e encontrar soluções para eles 😄 (não conheço nenhuma biblioteca de alta qualidade que use o Pipenv, mas principalmente porque não procurei.)

Tanto quanto posso dizer, estes são os sintomas específicos em sua postagem original:

  • Ocultando dependências de setup.py quebradas. Isso soa como um problema - pipenv install -e . , certo?
  • É provável que as dependências sejam inválidas para diferentes versões do python ou em outro sistema operacional. Posso ver que isso pode ser um problema, mas você poderia fornecer um exemplo concreto de onde isso é importante na prática? (no contexto de um arquivo de bloqueio)
  • Os desenvolvedores são forçados a atualizar ... quando outras bibliotecas são atualizadas e podem ser usadas na biblioteca. Eles não são forçados a fazer isso. Eles fazem isso se quiserem fornecer uma garantia de que sua biblioteca funciona com a versão n + 1 em vez da versão n de sua dependência. Mas observe que eu já propus uma alternativa que fornece o melhor dos dois mundos.
  • Competindo com o Tox. Eu realmente não sei nada sobre Tox. Mas sim, usar duas ferramentas simultaneamente para gerenciar suas dependências parece uma receita para o desastre. Eu diria que use o que for superior para essa tarefa específica.
  • Pipenv falha. Isso soa como outro não-problema - você pode apenas fixar a versão do Pipenv (essa é minha solução atual, assim como fixo minha imagem do Docker, minha versão do Pip, etc.)

@ tsiq-oliverc Devo dizer que seus comentários me inspiraram e sei que eles contribuíram para um maior nível de reprodutibilidade da solução proposta.

A seguir está relacionado à sua proposta de colocar o arquivo de bloqueio ( Pipfile.lock ) no repositório para garantir a repetibilidade:

re Escondendo dependências de setup.py quebradas. . O pipenv install -e . segue o que proponho, mas note que este não é o uso de Pipfile.lock , é um método para (re) criá-lo. Se alguém mantiver Pipenv.lock e usá-lo para criar o virtualenv antes de instalar o pacote, o problema está presente.

É provável que as dependências re doit instalado para Python 2.7 deve ser uma versão mais antiga, pois a mais recente abandonou o suporte para Python 2.x. watchdog dependency requer bibliotecas dependentes de plataforma: inotify no Linux, algo mais no Windows, algo mais no OSX. Meu ex-cliente costumava dizer "Isso nunca vai acontecer" e em 50% das situações aconteceu em 2 semanas. Essa não é a prática recomendada para scripts de CI.

Os desenvolvedores são forçados a atualizar .. Imagine biblioteca de código aberto com 15 colaboradores. É tão fácil esquecer a regeneração de Pipfile.lock por um desenvolvedor de núcleo recém-chegado ou cansado. Por exemplo, no pacote maya , fui solicitado a regenerar o Pipfile.lock pois uma nova dependência foi adicionada a setup.py . Isso foi necessário? Eu atualizei corretamente? Eu atualizei para todos os contextos de execução suportados? As respostas são não, não tenho certeza, não. De qualquer forma, obrigado pela sua proposta (me inspirou para a solução descrita ao lado do seu comentário).

re Competindo com tox : Tox permite a criação de múltiplos virtuaisenvs e automação da execução de testes dentro deles. tox.ini típico define virtualenvs diferentes para python 2.7, 3.4, 3.5, 3.6 e qualquer outro que você precisa, e permite instalar o pacote lá e executar o conjunto de testes. É uma ferramenta elétrica estabelecida para testadores sérios. pipenv não é a ferramenta para este propósito, mas pode interferir na instalação das coisas necessárias. De certa forma, segui seu conselho e propus usar uma ferramenta superior (tox) acima de pipenv onde possível.

re Pipenv falha. Isso é realmente lamentável. Eu tive o teste de CI (baseado em tox) que rodou bem no localhost, mas quando executado via Travis, ele falhou devido ao problema de pipenv . Se eu quiser usar agora, a fixação não ajudará até que a correção seja lançada. Mas é assim que acontece - vou esperar.

Observe que algumas partes do meu post original terão que ser atualizadas como parece, usando pipenv em scripts de CI em seu lugar justificado ("selando" a configuração do virtualenv para possível uso posterior).

@ tsiq-oliverc Embora inicialmente tenha gostado da sua sugestão de testar tanto o "bom conhecido" quanto as versões mais recentes, acho cada vez mais difícil justificar o esforço quanto mais penso a respeito. Acho que você deve decidir fazer um ou outro, não ambos.

A única coisa que você ganha é que saberá imediatamente se uma falha foi causada por uma atualização de dependência ou alteração de código. Mas você pode conseguir o mesmo simplesmente fazendo commits separados (ao atualizar manualmente as dependências bloqueadas) ou tentando reproduzir o bug com o arquivo de bloqueio mais recente produzido por uma compilação bem-sucedida (sempre usando as versões mais recentes). E em ambientes restritos, você não pode "apenas atualizar" de qualquer maneira ...

@vlcinsky Embora eu concorde com seu ponto geral sobre as diferenças entre ambientes, o argumento "um arquivo de bloqueio por configuração" soa como um espantalho para mim. Na prática, você poderá compartilhar os arquivos de bloqueio entre pelo menos alguns dos ambientes.

Uma questão em aberto que ainda não foi respondida é como lidar com o caso em que vocês dois precisam testar em ambientes diferentes e bloquear suas dependências. Tenho que admitir que não sei nada sobre tox além de sua existência, mas parece que há necessidade de algum tipo de cola entre tox e pipenv isso resolve esse problema de alguma forma.

@ Moritz90

Conhecendo o espantalho

Em relação a muitas variantes de Pipfile.lock servindo como espantalho (para manter os outros fora do meu campo):

Frasco

Peguei o projeto flask (considerando-o muito maduro) e executei testes toxicológicos:

Aqui você vê a lista de variantes testadas (apenas localmente no Linux, multiplique por 3 como o Windows e o OSX fará o mesmo conjunto de testes, mas pode resultar em ambientes diferentes).

Existem 16 execuções de teste diferentes em um sistema operacional, 5 deles falharam porque eu não os tenho instalados (tudo bem), um está lidando com a construção de documentos (requer biblioteca importante) e outro com cobertura (que também requer biblioteca importável ):

  coverage-report: commands succeeded
  docs-html: commands succeeded
  py27-devel: commands succeeded
  py27-lowest: commands succeeded
  py27-simplejson: commands succeeded
  py27: commands succeeded
  py35: commands succeeded
  py36-devel: commands succeeded
  py36-lowest: commands succeeded
  py36-simplejson: commands succeeded
  py36: commands succeeded
ERROR:   py34: InterpreterNotFound: python3.4
ERROR:   pypy-devel: InterpreterNotFound: pypy
ERROR:   pypy-lowest: InterpreterNotFound: pypy
ERROR:   pypy-simplejson: InterpreterNotFound: pypy
ERROR:   pypy: InterpreterNotFound: pypy

Para cada um dos virtuaisenvs criados, criei requirements.txt arquivo por pip freeze > {venv_name}.txt

Em seguida, hashes calculados para os arquivos, classificados de acordo com os valores de hash, para que todos sejam agrupados. Aí vem o espantalho:

b231a4cc8f30e3fd1ca0bfb0397c4918f5ab5ec3e56575c15920809705eb815e  py35.txt
b231a4cc8f30e3fd1ca0bfb0397c4918f5ab5ec3e56575c15920809705eb815e  py36.txt
cdf69aa2a87ffd0291ea65265a7714cc8c417805d613701af7b22c8ff2b5c0e4  py27-devel.txt
dfe27df6451f10a825f4a82dfe5bd58bd91c7e515240e1b102ffe46b4c358cdf  py36-simplejson.txt
e48cd24ea944fc9d8472d989ef0094bf42eb55cc28d7b59ee00ddcbee66ea69f  py36-lowest.txt
f8c745d16a20390873d146ccb50cf5689deb01aad6d157b77be203b407e6195d  py36-devel.txt
053e107ac856bc8845a1c8095aff6737dfb5d7718b081432f7a67f2125dc87ef  docs-html.txt
45b90aa0885182b883b16cb61091f754b2d889036c94eae0f49953aa6435ece5  py27-simplejson.txt
48bd0f6e66a6374a56b9c306e1c14217d224f9d42490328076993ebf490d61b5  coverage-report.txt
564580dad87c793c207a7cc6692554133e21a65fd4dd6fc964e5f819f9ab249c  py27.txt
8b8ff4633af0897652630903ba7155feee543a823e09ced63a14959b653a7340  py27-lowest.txt

Assustador, não é? De todos os testes, apenas dois compartilham as mesmas dependências congeladas.

Esta é a realidade da biblioteca python geral com um bom conjunto de testes. Você provavelmente vai admitir que isso é algo bem diferente da biblioteca python testada em ambiente corporativo.

Jinja2

Verificando jinja2 , que parece ser uma besta muito mais simples:

  coverage-report: commands succeeded
  py26: commands succeeded
  py27: commands succeeded
  py33: commands succeeded
  py35: commands succeeded
  py36: commands succeeded
ERROR:   docs-html: commands failed
ERROR:   py34: InterpreterNotFound: python3.4
ERROR:   pypy: InterpreterNotFound: pypy

Ao ver as somas de verificação, fico surpreso, pois py27.txt e py26.txt são diferentes:

047a880804009107999888a3198f319e5bbba2fa461b74cfdfdc81384499864e  py26.txt
047a880804009107999888a3198f319e5bbba2fa461b74cfdfdc81384499864e  py33.txt
047a880804009107999888a3198f319e5bbba2fa461b74cfdfdc81384499864e  py35.txt
047a880804009107999888a3198f319e5bbba2fa461b74cfdfdc81384499864e  py36.txt
48bd0f6e66a6374a56b9c306e1c14217d224f9d42490328076993ebf490d61b5  coverage-report.txt
743ad9e4b59d19e97284e9a5be7839e39e5c46f0b9653c39ef8ca89c7b0bc417  py27.txt

@vlcinsky Isso é realmente assustador. Estou me perguntando se o Flask é um caso especial ou se isso é realmente a norma, mas você definitivamente provou que estou errado.

Agora espero que nossa biblioteca Python não sofra do mesmo problema algum dia e que as diferenças sejam mais administráveis ​​lá.

@ Moritz90 Sua biblioteca interna está servindo a um público completamente diferente, então você pode manter o contexto de execução muito mais restrito.

Bibliotecas python gerais são frequentemente flexíveis e configuráveis, por exemplo, Flask permite que analisadores json alternativos sejam instalados e usados ​​o que é coberto por uma execução de teste separada.

Pode-se aprender muito sobre testes e toxidade com o tox.ini do Flask

as variantes de teste mais baixas devem ter o cuidado de testar a versão de dependência mais antiga.

devel está testando em relação à versão de desenvolvimento das dependências principais.

Eu diria que o Flask está no nível mais alto de complexidade e exibe um conjunto de testes cuidadoso.

O tox.ini da pirâmide mostra um número semelhante de ambientes (eles também visam a 100% de cobertura de código).

O tox.ini de maya é muito fresco (2 dias) e simples, mesmo aqui existem 4 ambientes diferentes e py27 difere em requisitos de congelados de py35 e py36.

@ Moritz90
Em relação à cola entre pipenv e tox

  • pipenv --man mostra algumas instruções, como usar pipenv dentro de tox.ini comandos
  • tox-pipenv está tentando fornecer alguma integração extra, mas está me confundindo no momento.

tox.ini file permite a execução de comandos arbitrários, então isso inclui pipenv .

pipenv tem um ótimo recurso, que quando executado no virtualenv já ativado (o que é o caso no teste baseado em tox), ele instala em um determinado ambiente virtual. Isso é muito bom.

Como provavelmente precisamos de Pipfile.lock gerados, algum esforço extra deve ser feito para obtê-lo e mover para o lugar apropriado (ag em .tox/py36/Pipfile.lock para evitar sobrescrever seguindo o teste. Isso deve ser possível, mas com alguma simplificação seria bem-vindo. Talvez algum truque com a variável ambiental para a localização de Pipfile tornasse isso ainda mais simples.

@vlcinsky

  • setup.py - Ainda não tenho certeza se entendi o problema. Você executa pipenv install -e . uma vez para que setup.py agora seja rastreado por meio de seu arquivo de bloqueio. E então execute pipenv install sempre que adicionar novos pacotes ao setup.py.
  • Desenvolvedores que se esquecem de atualizar o arquivo de bloqueio - pipenv --deploy foi projetado para detectar isso. Execute no seu CI!
  • Pipenv falha - concordou, se houver bugs na ferramenta, isso é uma merda. Mas os bugs geralmente são corrigidos. Isso não é motivo para jogar fora toda uma filosofia 😞
  • Tox

    • Se o Tox é bom para o gerenciamento de testes, ótimo. Se também for ótimo para gerenciar pacotes e compilações determinísticas , isso é ainda melhor.

    • Mas se isso fosse verdade, não haveria uma razão para a existência de Pipenv. Portanto, só posso presumir que haja algum tipo de limitação com o Tox.

    • Da mesma forma que acima, o estado do mundo hoje (a falta de boa interoperabilidade) não parece uma razão para rejeitar a filosofia.

  • Múltiplos envs

    • É claro que existem pelo menos alguns casos especiais aqui, como Flask.

    • Não tenho uma sugestão melhor de vários arquivos de bloqueio (embora talvez haja um recurso futuro para o Pipenv a esse respeito?)

    • Mas, mesmo neste caso, ainda não estou convencido de que gerenciar vários arquivos de bloqueio seja um problema na prática. Na pior das hipóteses, parece que você poderia criar um script simples update-all-lockfiles.sh localmente e executar pipenv --deploy em seu CI para detectar erros.


@ Moritz90 - Concordo, a abordagem das "duas fases" pode ser exagerada na maioria dos casos. Em particular, se você estiver fazendo atualizações "manuais" deliberadas / intencionais em seu arquivo de bloqueio, isso é completamente desnecessário.


De maneira mais geral, seria bom garantir que essa "proposta" se concentre nas coisas que são realmente problemas difíceis (na minha opinião, isso é (A) servindo vários envs, (B) querendo pegar mudanças nas dependências upstream). Não deve ser baseado em coisas transitórias (bugs no Pipenv) ou potenciais mal-entendidos de como a ferramenta deve ser usada.

Mas mesmo para aqueles problemas "difíceis", o enquadramento deve ser como "em alguns casos extremos complexos, você pode achar que um fluxo de trabalho básico do Pipenv é insuficiente, então aqui estão algumas coisas para se pensar". IMO, não deve ser enquadrado como a abordagem padrão (porque a maioria das pessoas não terá essas preocupações).

O exemplo de documentação fornecido por @vlcinsky ficaria mais simples e menos confuso se o Pipenv / Pipfile permitisse o manuseio de lib-dependencies , app-dependencies e dev-dependencies . Os documentos podem ser parecidos com isto:

Use a opção lib-dependencies se o pacote for uma biblioteca compartilhada. Exemplo Pipfile :

[lib-dependencies]
some-lib=="*"
another-lib=="*"
yet-another-one==">=1.0"

[dev-dependencies]
some-dev-tool=="1.1"

Para bibliotecas compartilhadas, é importante manter os intervalos de versão abaixo de [lib-dependencies] tão amplos quanto possível, para evitar conflitos de versão no sistema do consumidor.

Se o seu pacote for um aplicativo (destinado a ser instalado por pipenv no sistema de destino) que requer versões de dependência exatas, você deve usar a opção [app-dependencies] . Exemplo Pipfile :

[app-dependencies]
some-lib=="1.0.12"
another-lib=="1.*"
yet-another-one=="2.0"

[dev-dependencies]
some-dev-tool=="1.1"

/ Exemplo de fim de documento

Outra abordagem poderia ser Pipfile.lib e Pipfile.app .

Acho que algo assim omitiria a necessidade de um pedaço de seções antipadrão e ferramentas de terceiros para preencher a lacuna.

Chamar a ferramenta de empacotamento pipenv é enganoso se alguém espera que ela crie bibliotecas Python ou esteja profundamente envolvido na criação delas.

Acho que este é um problema real, o que leva a muita confusão. Especialmente entre pessoas que estão acostumadas com gerenciadores de pacotes em outras linguagens de programação (por exemplo, JS, Rust, Elm). Levei vários meses e leituras ocasionais dos problemas do GIthub, até que percebi que estava usando o Pipenv e o setup.py da maneira errada.

@feluxe

Sua [lib-dependencies] ou Pipfile.lib é o que temos hoje em Pipfile (como dependências abstratas - sendo as mais amplas possíveis).

Seu [app-dependencies] ou Pipfile.app é o que temos em Pipfile.lock (como dependências específicas).

pipenv e seus arquivos podem ser usados ​​em duas situações diferentes - desenvolver uma biblioteca ou preparar uma implementação de aplicativo, mas provavelmente não para ambos ao mesmo tempo. Por esta razão, não vejo fortes motivos para adicionar seções extras em Pipenv . É responsabilidade do desenvolvedor saber a que tipo de propósito o Pipfile vai servir.

Acho que este é um problema real, o que leva a muita confusão. Especialmente entre pessoas que estão acostumadas com gerenciadores de pacotes em outras linguagens de programação (por exemplo, JS, Rust, Elm). Levei vários meses e leituras ocasionais dos problemas do GIthub, até que percebi que estava usando o Pipenv e o setup.py da maneira errada.

Concordou. A solução de três seções também é uma solução muito interessante que nunca considerei, e parece ser correta e (surpreendentemente!) Simples.

Vindo de experiência em Python, sempre achei que o package.json do Node está fazendo isso errado (Rust é melhor porque tem um compilador e um vinculador e pode resolver isso em um estágio posterior). Tratar dependências de aplicativo e lib da mesma maneira simplesmente não funcionará para uma linguagem de script como Python, pelo menos em um sentido abstrato - ou seja, pode funcionar para você, mas uma ferramenta genérica como Pipenv não pode fazer isso porque precisa ser genérico.

Embora eu goste do conceito da solução de três seções, ainda é uma mudança bastante incompatível com o ecossistema existente. Já há setup.py, setup.cfg e (potencialmente) pyproject.toml preenchendo este espaço. Se o Pipenv (Pipfile, para ser exato) quiser se mover para o espaço, ele precisa consolidar com projetos relacionados, como pip (o suporte à biblioteca deve ser idealmente suportado diretamente por ele) e flit .

Como mencionei em outras questões relacionadas ao tratamento de dependência lib / app, esta discussão precisa ser escalada para pypa-dev (a lista de discussão) e / ou o processo PEP, para que possa ser melhor ouvida por outras partes e pessoas relevantes, antes do Pipenv (Pipfile) pode se mover em qualquer direção.

@vlcinsky

Seu [lib-dependencies] ou Pipfile.lib é o que temos hoje no Pipfile (como dependências abstratas - sendo o mais amplo possível).

Desculpe se não ficou claro. Meus lib-dependencies devem ser o que as pessoas atualmente colocam em setup.py / install_requires . Talvez pypi-dependencies fosse um nome melhor para o que eu quis dizer.

@uranusjr

Já há setup.py, setup.cfg e (potencialmente) pyproject.toml preenchendo este espaço.

Pipenv (a ferramenta de linha de comando) pode fazer a interface setup.py . Apenas a seção de dependência de setup.py teria que ser movida para o Pipfile. Pelo menos na minha imaginação :)

Como mencionei em outras questões relacionadas ao tratamento de dependência lib / app, esta discussão precisa ser escalada para pypa-dev (a lista de discussão) e / ou o processo PEP, para que possa ser melhor ouvida por outras partes e pessoas relevantes, antes do Pipenv (Pipfile) pode se mover em qualquer direção.

Ok, desculpe incomodar;) Se eu encontrar algum tempo, vou escrever algo para a lista de mailling.

No escopo desta proposta, no entanto, eu sugeriria que ela se concentrasse nas melhores práticas atualmente possíveis, em vez de entrar na toca do coelho de elaborar um novo fluxo de trabalho para toda a comunidade de pacotes Python. Seria mais produtivo propor uma prática recomendada dentro das restrições atuais e , em

@uranusjr - Eu venho de um background "compilado", então estou curioso para

Tratar dependências de aplicativo e lib da mesma maneira simplesmente não funcionará para uma linguagem de script como Python

@ tsiq-oliverc Como a melhor prática do aplicativo requer que você fixe suas dependências, as bibliotecas também começariam a fixar as suas, se usassem a mesma fonte de arquivos de requisitos. Isso levaria a problemas na resolução de dependências.

Digamos que meu aplicativo tenha duas dependências A e B, ambas dependem de C, mas A fixa v1, enquanto B fixa v2. As linguagens compiladas permitem que a cadeia de ferramentas detecte isso em tempo de compilação e resolva de várias maneiras. Rust, por exemplo, faz isso durante o tempo de vinculação - O executável final conteria duas cópias de C (v1 e v2), com A e B vinculando cada uma delas. No terreno do C ++, isso seria resolvido com bibliotecas dinâmicas; a pesquisa de símbolo é feita ainda mais tarde (em tempo de execução), mas a ideia é a mesma - o compilador sabe o que você precisa (a partir da interface que você usa) e pode agir de acordo.

As linguagens de script não podem fazer isso porque não sabem o que você realmente deseja fazer até que realmente alcance a chamada. O Node contorna isso sempre assumindo que as dependências são incompatíveis (A e B sempre obtêm seu próprio C, mesmo se as duas cópias forem idênticas), mas isso leva a uma nova classe de problemas e resulta em hacks estranhos, como dependências de pares que todos (Espero?) Concorda são terríveis. O Python provavelmente não quer fazer isso (não pode, de qualquer maneira, já que isso provavelmente interromperia todas as instalações existentes do Python).

Outra maneira de contornar isso é fazer algo inteligente nas ferramentas de empacotamento que “desencadeia” a versão de dependência. Bundler (de Ruby) meio que faz isso, recomendando às pessoas que não incluam o arquivo de bloqueio no gem, para que o Bundler possa usar as versões não fixadas em Gemfile, em vez de versões fixadas em Gemfile.lock. Mas as pessoas tendem a ignorar os conselhos e fazer o que quiserem, então você ainda tem versões fixas em todos os lugares.

Provavelmente fui um pouco forte demais para dizer que simplesmente não funcionaria . Mas pelo menos todas as tentativas anteriores falharam, e muitos dos que tentaram são pessoas muito inteligentes, muito mais inteligentes do que eu. Não acho que isso possa ser feito, pessoalmente, e continuaria pensando dessa forma até ver a proposta muito brilhante que realmente faz isso.

@ tsiq-oliverc Pieter Hintjens escreveu em algum lugar o conceito de "Comentários são bem-vindos na forma de solicitação pull"

Gosto disso porque muda o foco de conselhos filosóficos para coisas realmente tangíveis e práticas. E também limita o número de comentários porque um comentarista frequentemente aprende que a ideia está incompleta ou de alguma forma quebrada no uso real.

Eu pedi a você um exemplo de biblioteca python, onde pipenv é usado corretamente (ou pelo menos usado) e você não forneceu nenhum.

Você comenta sobre as qualidades de tox , mas admite que não está familiarizado com elas, ainda repetindo algo sobre as melhores práticas no mundo do desenvolvimento de pacotes python.

Você diz que Flask é possivelmente um caso especial. Então, pesquisei no Github por projetos python usando a palavra "biblioteca", classificados de acordo com o número de garfos (já que provavelmente reflete quantas pessoas estão fazendo algum desenvolvimento com ele), ignorei todas as "listas atualizadas de algo" e contei o número de ambientes para um SO (normalmente Linux):

O número real de ambientes nos quais os testes serão executados será 2 (+ Windows) ou 3 (+ OSX) vezes maior.

tox é usado em 2 de 3 projetos (não o comparo ao Travis ou Appveyor, pois eles fazem outro nível de teste ao lado).

O número de ambientes para testar é bastante alto, Flask definitivamente não é o mais selvagem.

O número de ambientes para os quais definir dependências fixas não é gerenciável manualmente.

Simplesmente soltar Pipfile.lock em um repositório é bastante fácil, mas não faz nenhuma melhora mágica (se sim, mostre-me o cenário real, quando isso vai melhorar a situação).

Talvez você conheça a regra de ouro do mundo "compilado" e sinta que o determinismo (ou repetibilidade) é uma obrigação para o Python também. Como você pode ver, muitos projetos Python vivem muito bem sem ele, então pode ser que a regra de ouro não se aplique tão estritamente aqui.

Ficarei feliz se encontrarmos o uso de pipenv para bibliotecas python, o que irá melhorar a situação. E quero evitar o uso, o que prejudicaria a qualidade geral.

Para alcançar esse objetivo, minha abordagem é iterar sobre as perguntas:

  • Devo usar a ferramenta?
  • Como?
  • Por que, qual valor eu recebi?
  • Introduziu alguns problemas? (trabalho extra, erros sendo ocultados ...)

@feluxe

Desculpe se não ficou claro. Minhas dependências de lib devem ser o que as pessoas atualmente colocam em setup.py / install_requires. Talvez dependências de pypi seja um nome melhor para o que eu quis dizer.

Veja pbr discussão nesta edição. É o esforço para oferecer suporte às dependências de biblioteca do Pipfile.

Acho que Pipfile não deve ser usado para dois propósitos (lib e app), essas coisas devem ser feitas separadamente. Se você sentir que é realmente necessário, você poderia descrever o propósito de um projeto usando-o? Normalmente tento manter os projetos de desenvolvimento e implantação de bibliotecas separados, pois eles têm usos bastante diferentes no tempo.

@vlcinsky Não tenho certeza de onde você quer levar isso (não tenho certeza de que tipo de relações públicas você está pedindo!), então vou sair desta conversa por enquanto.

Para reafirmar o TL; DR da minha posição:

  1. Construções determinísticas são altamente desejáveis, comuns em outros lugares na indústria de software e fáceis de alcançar com o Pipenv.
  2. Definitivamente, há alguns casos extremos, e alguém deve levar o que há de mais moderno para isso (por meio de soluções alternativas ou por meio de ferramentas melhores).
  3. Seria irresponsável que os documentos do Pipenv fizessem uma recomendação geral contra (1) simplesmente porque (2) afeta um pequeno subconjunto de casos.

@uranusjr Entendi. Embora eu não ache que haja nada específico de linguagem aqui, é simplesmente que diferentes comunidades estabeleceram diferentes heurísticas para lidar com um problema sem solução genérica - se você tiver conflitos de versão, você tem um problema.

Maven / Java (por exemplo) força você a pensar sobre isso no momento da construção. A maneira NPM significa que você terá problemas de tempo de execução se as versões incompatíveis cruzarem uma interface. Resolução de tempo de execução (por exemplo, Python, bibliotecas dinâmicas) significa que um dependente pode travar / etc. se a versão da dependência não for a esperada.

@vlcinsky

Veja a discussão do pbr nesta edição. É o esforço para oferecer suporte às dependências de biblioteca do Pipfile.

pbr parece bom e tudo, mas se enquadra na categoria que eu estava tentando abordar com isto:

Acho que algo assim omitiria a necessidade de um pedaço de seções antipadrão e ferramentas de terceiros para preencher a lacuna.

Acho que essas ferramentas não deveriam ser necessárias em primeiro lugar.

Se você sentir que é realmente necessário, você poderia descrever o propósito de um projeto usando-o? Normalmente tento manter os projetos de desenvolvimento e implantação de bibliotecas separados, pois eles têm usos bastante diferentes no tempo.

Quando se trata de pacotes pypi, acabei usando Pipenv para lidar com dev-dependências, Pipfile para descrever dev-dependências, setup.py para descrever dependências lib com install_requires e setuptools em setup.py para publicar meu pacote executando pipenv run python setup.py bdist_wheel upload . Isso é o que considero complicado.

Em outras linguagens modernas, tenho que aprender uma ferramenta de linha de comando (gerenciador de pacotes) mais um formato de arquivo de dependência. A documentação está em um lugar e é mais fácil de seguir e um recém-chegado resolverá tudo isso em algumas horas. É uma questão de npm init , npm install foo --dev , npm publish . Pipenv / Pipfile já pode fazer a maior parte disso, se pudesse fazer tudo, problemas como este não existiriam.

Reinterrupto minha convocação para uma espécie de seção / wiki de "comunidade" para esta discussão. Existem vários "padrões" que podem ser legítimos e alguns de nós podem querer compartilhá-los "maneira de fazer bibliotecas python", alguns como eu com pbr, e outros podem ter um padrão muito bom. Mas uma página dentro do documento pipenv, não tenho certeza se é uma boa ideia.

PS: para preparar a migração para o novo pypi, você deve usar twine e não python setup.py upload. Usar "upload" deve ser considerado um antipadrão.

Talvez o pipenv possa desenvolver comandos de "publicação"?

@feluxe Você pode querer dar uma olhada na poesia . Eu simplesmente tropecei nele e parece que é o que você está procurando.

Ele faz o que pipenv faz e muito mais e parece que eles fazem isso melhor, especialmente em relação ao gerenciamento de dependências (pelo menos é o que eles fingem). Ele faz gerenciamento de dependências, empacotando e publicando tudo em uma única ferramenta poetry .

Eu me pergunto se pipenv e poetry poderiam reunir esforços para finalmente dar ao Python um verdadeiro gerenciador de pacotes.

Quero me reiterar antes que essa discussão vá longe demais. O Pipenv não pode simplesmente desenvolver um comando publish ou fazer qualquer coisa que tente assumir a tarefa de empacotamento. Isso só fragmentaria mais o ecossistema, porque nem todo mundo faz dessa maneira, e com as dependências de app e lib teoricamente diferentes, você não pode dizer a alguém para mesclá-los novamente depois que a distinção for feita em seu fluxo de trabalho.

Pode parecer que quase todos concordam com essa mesclagem, mas a verdade é que há muito mais pessoas que não estão participando dessa discussão porque as coisas funcionam para elas e elas estão fazendo outra coisa. Eu já disse isso várias vezes: a discussão sobre como melhorar o design de conjuntos de ferramentas e formatos de arquivo deve acontecer em algum lugar superior na hierarquia de pacotes do Python, para que receba mais exposição de pessoas que projetam coisas mais fundamentais nas quais o Pipenv depende. Por favor, leve a discussão lá. Não adianta sugerir aqui, porque Pipenv não está em posição de alterá-lo.

Eu já disse várias vezes: A discussão sobre como melhorar o design de conjuntos de ferramentas e formatos de arquivo deve acontecer em algum lugar mais alto na hierarquia de pacotes do Python, para que receba mais exposição às pessoas que projetam coisas mais fundamentais nas quais o Pipenv depende.

Concordo que a discussão sobre esse bug está fora de controle agora que o empacotamento e a publicação surgiram (esse bug é apenas sobre gerenciamento de dependências!), Mas você poderia nos indicar o lugar certo para ter essa discussão? As pessoas estão tendo isso aqui porque o pipenv é visto como um passo muito necessário na direção certa, não porque eles querem impor responsabilidades adicionais aos mantenedores do pipenv.

Edit : Desculpe, devo ter perdido o post em que você fez exatamente isso ao ler os novos comentários pela primeira vez.

No escopo desta proposta, no entanto, eu sugeriria que ela se concentrasse nas melhores práticas atualmente possíveis, em vez de entrar na toca do coelho de elaborar um novo fluxo de trabalho para toda a comunidade de pacotes Python. Seria mais produtivo propor uma prática recomendada dentro das restrições atuais e, em seguida, iniciar a discussão para melhorias.

Eu concordo muito com isso. Devemos primeiro descobrir qual é o melhor fluxo de trabalho possível para os mantenedores de bibliotecas agora, antes de chegarmos a grandes planos. Portanto, vamos nos concentrar nisso novamente, como fizemos no início deste tópico. Acho que ainda não chegamos a uma conclusão.

Voltar ao tópico: Citando a postagem de

Outra maneira de contornar isso é fazer algo inteligente nas ferramentas de empacotamento que “desencadeia” a versão de dependência. Bundler (de Ruby) meio que faz isso, recomendando às pessoas que não incluam o arquivo de bloqueio no gem, para que o Bundler possa usar as versões não fixadas em Gemfile, em vez de versões fixadas em Gemfile.lock. Mas as pessoas tendem a ignorar os conselhos e fazer o que quiserem, então você ainda tem versões fixas em todos os lugares.

Provavelmente fui um pouco forte demais para dizer que simplesmente não funcionaria. Mas pelo menos todas as tentativas anteriores falharam

Ainda não vejo por que a recomendação oficial para bibliotecas por agora não pode ser usar pipenv para suas compilações de CI, mas manter Pipfile.lock fora do controle de origem. Visto que, como algumas pessoas apontaram, pipenv não tem nada a ver com o processo de empacotamento, não devemos nos deparar com o problema que você descreveu acima.

E também não vejo por que isso é um argumento contra a definição de suas dependências abstratas no mesmo arquivo que os aplicativos usam para definir suas dependências abstratas. Tudo bem se pipenv não quiser implementar uma solução elaborada para integrar Pipfile com setup.py , mas não vejo por que isso é uma má ideia em geral.

@vlcinsky

Eu acho que um Pipfile não deve ser usado para duas finalidades (lib e app), essas coisas devem ser feitas separadamente.

Veja meu post acima. Você poderia explicar por que você acha isso? Simplesmente não consigo ver nenhuma desvantagem em princípio. No momento, pode ser uma má ideia incluir Pipfile , já que você terá que definir as dependências da mesma maneira em dois arquivos diferentes, mas ainda não vi nenhum argumento que explique o porquê seria uma má ideia usar Pipfile para declarações de dependência em geral.

Observe que já concordei que Pipfile.lock não deve estar no controle de origem das bibliotecas, a menos que você esteja na mesma situação em que estou.

Editar : Além disso, se descobrir que pipenv si realmente precisa saber sobre a diferença, você pode apenas introduzir algo como o campo crate-type da carga antes de começar a introduzir app-dependencies e lib-dependencies - isso parece muito complicado.

@ Moritz90 Várias listas de discussão do Python seriam bons locais para manter essa discussão.

pypa-dev é o mais definitivo para discussões centradas no empacotamento do Python e no ecossistema ao seu redor. Eu provavelmente começaria aqui se fosse postar uma discussão semelhante.

python-ideas é um lugar para discutir ideias e tem grande visibilidade para toda a comunidade Python. Também seria um bom ponto de partida se você quiser levar isso para o nível PEP (eventualmente você o faria, eu acho).

@ tsiq-oliverc

Por RP, quero dizer: mostre um exemplo que prova a viabilidade de seu conceito.

Então, pegue alguma biblioteca existente, bifurque-a, aplique seu (1) - você diz que será fácil com pipenv e mostre-me. Eu tentei muito e tenho dificuldades.

Se seu (2) significar "outra pessoa tem que fazer o trabalho", seu RP não existirá.

Em (3), você fala sobre "pequeno subconjunto de casos" sem fornecer nenhum número real. Todas as principais bibliotecas que descrevi em relação ao número de virtualenvs são consideradas "pequeno subconjunto"?

Para concluir esta discussão, criei um breve resumo do que foi encontrado durante a discussão.

Foco: pipenv (anti) padrões para bibliotecas e aplicativos Python

Mudei um pouco o foco: ele fala não apenas sobre bibliotecas Python (gerais), mas também sobre aplicativos, pois foi bastante barato incluí-lo e demonstra bem as diferenças.

Excluí intencionalmente qualquer coisa que propusesse mudanças nas ferramentas existentes, como pipenv , tox etc.

O que é pipenv e o que não é

  • é uma ferramenta de implantação, permitindo definir e aplicar dependências concretas por meio de Pipfile.lock .
  • é uma ferramenta de gerenciamento virtualenv.
  • NÃO é uma ferramenta de empacotamento no sentido de gerar um pacote python.

Bibliotecas e aplicativos

O produto (software python) está pronto para ser usado em outro produto (portanto, biblioteca) ou é o aplicativo final pronto para ser executado.

Pessoalmente, acho que até mesmo "bibliotecas corporativas" se enquadram na categoria de biblioteca (as mesmas regras se aplicam, apenas o número de contextos de execução é menor).

Tipos de produtos de software

  • biblioteca: para ser usado em outro produto (biblioteca ou um aplicativo)
  • aplicativo: a ser implantado e executado

Métodos de instalação:

  • biblioteca: pipenv install <package> portanto, "coloque o pacote em ação (resolvendo versões para outras bibliotecas)"
  • aplicação: pipenv sync portanto, "aplique dependências concretas"

Dependências abstratas e concretas

dependências do produto de software

  • dependências abstratas: deve nomear as bibliotecas usadas, pode restringir versões ou uso, mas deve permanecer flexível o suficiente (não deve fixar versões)
  • dependências concretas: devem fixar versões, de preferência com hashes de bibliotecas usadas

    pipenv artefatos:

  • Pipfile : dependências abstratas

  • Pipfile.lock : dependências concretas (bloqueadas) "

Contextos de execução

Número típico de contextos de execução diferentes

  • biblioteca:

    • python virtualenvs em um sistema operacional: 3 a 9 (tornado usando 30)

    • número de sistemas operacionais: 1 a 3 (Linux, OSX, Windows)

    • número total: 3 a 18

  • aplicativo:

    • python virtualenvs em um sistema operacional: 1

    • número de SO: 1

    • número total: 1 (ou muito poucos)

Objetivos, prioridades e determinismo de CI

Meta de CI

  • biblioteca:

    • o código incl. suas dependências abstratas permitem a instalação e a função esperada em todas as variantes esperadas de contextos de execução.

    • quando (privado / público) pypi obtém atualização da biblioteca de dependência, falha, se isso afeta a instalação da biblioteca testada da função.

  • aplicativo:

    • quando instalado (usando dependências concretas / fixadas), todas as funcionalidades esperadas são fornecidas em um contexto de execução predefinido

Objetivos específicos de CI em relação à funcionalidade:

  • biblioteca:

    • dependências abstratas declaradas pela biblioteca são completas e incluem todas as restrições necessárias (somente onde necessário): a biblioteca se instala corretamente

    • todos os casos de uso esperados funcionam corretamente

  • aplicativo:

    • dependências concretas estão completas e todas fixadas, melhor incl. hashes: o aplicativo é instalado corretamente

    • todos os casos de uso esperados funcionam corretamente

Diferentes modos de teste de CI

  • Modo: "Executar, Forrest, Executar"

    • Hoje em dia, a grande maioria das bibliotecas Python são testadas dessa maneira.

    • use tox ou software de teste semelhante

    • Sem uso de pipenv e dependências concretas (pode ser adequado para bibliotecas)

  • Modo: "Gerar e selar"

    • Sem Pipfile no repositório

    • Pipfile.lock criado por pipenv install -e .

    • Pipfile.lock documenta (sela) o ambiente e permite a reprodução posterior do virtualenv para análise de problemas.

  • Modo: "Idade do Gelo"

    • teste de duas fases

      • quando dependências abstratas (definidas em setup.py install_requires ) mudança ou pacote dependente em pypi é atualizado, regenere Pipfile.lock por pipenv install -e .

    • execução de teste de função: executa quando o código da biblioteca é alterado. É executado dentro do virtualenv criado por pipenv sync Pipfile.lock

      Como e por quem Pipfile.lock pode ser criado

  • manualmente pelo desenvolvedor (pode funcionar para aplicativos)

  • automaticamente pela construção de CI (e passando em todos os testes, declara que foi verificado o artefato)

Prioridade de determinismo versus flexibilidade

  • biblioteca: flexibilidade (execute sempre que possível)
  • aplicação: determinismo (executado exatamente da mesma maneira dentro do contexto de execução selecionado)

    O que pode afetar o determinismo do produto instalado:

  • public pypi (baixo determinismo, pacotes são atualizados a qualquer momento)

  • pypi privada (determinismo superior, atualizações de pacote podem ser controladas)
  • requisitos abstratos dentro de bibliotecas (não devem ser usados ​​para determinismo)
  • requisitos concretos ( Pipfile.lock ): determinismo total

Diversos

Alguns casos de uso para Pipfile.lock :

  • (antipadrão) define dependências abstratas da biblioteca (porque deve ser abstrato)
  • (antipattern) configurar virtualenv para a biblioteca testada (pode ocultar dependências abstratas da biblioteca quebradas)
  • documentar o virtualenv exato ("selo"), onde um teste foi executado (assim, permitir que o desenvolvedor o recrie mais tarde para testes quebrados e experimentos nele)
  • configurar o virtualenv para o aplicativo testado
  • implantar aplicativo na produção

    Outras dicas

  • pbr biblioteca requirements.txt . A atualização de leitura Pipfile está a caminho.

  • poetry pacote pyenv

    Problemas comuns

  • "solte o arquivo de bloqueio no repo" e você obterá compilações determinísticas:

    • Deve funcionar para aplicativos.
    • Não funcionará para bibliotecas, já que existem muitos contextos de execução e cada um tem um bom potencial para resultar em Pipfile.lock . Sério: flask mostra em seus 11 envs virtuais diferentes (em um SO) 10 dependências bloqueadas diferentes. Quem os criará e os comprometerá?
    • Observe que, com o modo de CI "Gerar e selar", você ainda pode obter Pipfile.lock (mas gerado pelo script de CI), permitindo regenerar o virtualenv em outro lugar.
  • Pipfile.lock no repositório da biblioteca

    • se usado para criar virtualenv, pode ocultar definições quebradas de dependências de biblioteca em setup.py .

  • Pipfile no repositório da biblioteca

    • se ele repetir dependências abstratas (que devem ser definidas em setup.py ), ele pode ocultar a declaração de dependência setup.py quebrada.

    • recomendado: Gere Pipfile por pipenv install -e . ou pipenv install -e .[tests] se você também precisar testar dependências e elas forem declaradas como extras de "testes" em setup.py

  • adicionar pipenv install <something> em scripts de CI

    • não melhora muito o determinismo por si só

    • consulte o modo CI "Gerar e selar" (todas as instalações no virtualenv devem ser feitas por meio de pipenv ).

Conclusões

Bibliotecas Python (especialmente as gerais) exibem um número inesperadamente alto de contextos de execução. A razão é que, com as bibliotecas, o objetivo é a flexibilidade comprovada em diferentes condições. A flexibilidade parece mais importante sobre construções determinísticas. Para as pessoas que vêm do mundo da "compilação", isso pode parecer um antipadrão muito ruim. O fato é que a maioria (possivelmente todas) as bibliotecas Python não fornecem compilações determinísticas (se você estiver ciente de algumas, me avise) e Python ainda está indo muito bem. Os motivos pelos quais os aplicativos Python ainda estão vivos podem ser: o python como linguagem de script difere do mundo compilado. A outra razão poderia ser que o determinismo pode (deve) ser resolvido uma etapa depois, assim que um aplicativo (construir a partir de um conjunto de bibliotecas) deve resolver o requisito (natural e justificado) para o determinismo.

Para aplicações, a situação é exatamente oposta e aqui o determinismo é realmente fácil de alcançar com ferramentas como pipenv .

o que fazer a seguir?

  • Não terei tempo para lidar com isso nas próximas duas semanas.
  • Posso imaginar a criação de uma série de entradas de blog em algum lugar (não tenho certeza de onde). Se você conhece o lugar (de preferência permitindo alguma discussão), seria um lugar natural deixar o conteúdo ser referenciado, discutido e, finalmente, possivelmente (se ele sobreviver) colocado em algum lugar na pedra :-).
  • Proponho @uranusjr para assumir o controle deste problema (fechá-lo, decidir o que fazer a seguir, redirecionar as pessoas para outro lugar ou o que quer que pareça prático)

Obrigado a todos pela discussão tão inspiradora - sinto para mim como uma mensagem "Estou totalmente perdido neste tópico" refatorada três vezes - o que significa que naturalmente melhoramos.

@vlcinsky poetry não tem nada a ver com pyenv . É muito parecido com pipenv (mas com uma implementação muito melhor em relação ao gerenciamento de bibliotecas e aplicativos, IMO), mas com a parte de empacotamento e publicação.

Você tem um arquivo pyproject.toml que define seu projeto e suas dependências (dependências abstratas) e um pyproject.lock que descreve as dependências fixadas e são fixados para qualquer versão e plataforma python de pyproject.toml arquivo foi especificado para ter apenas um arquivo de bloqueio determinístico para evitar os problemas que pipenv está enfrentando. Apenas durante a instalação, poetry verificará quais pacotes instalar, comparando-os com o ambiente.

E ao empacotar sua biblioteca, ele usará as dependências abstratas (e não as fixas) para que você mantenha a flexibilidade ao distribuir seu pacote (via PyPI, por exemplo).

A vantagem disso é que ele usará dependências abstratas para bibliotecas e o arquivo de bloqueio para aplicativos. Esse é o melhor de ambos mundos.

A poesia fixas está literalmente

Essencialmente, gastamos muito tempo e esforço na resiliência em que pequenos projetos que fazem promessas grandiosas não precisam despender tanto esforço porque as pessoas não estão atingindo casos extremos. Se você realmente acredita que outra ferramenta oferece o melhor de todos os mundos, encorajo-o a usá-la - o pipenv em si não vai lidar com embalagens para você no curto prazo, ou nunca.

@techalchemy Não estou vendendo nada, na verdade, estou apenas direcionando para ideias que poderiam ser usadas em pipenv .

E poetry fixa dependências em pyproject.lock , assim como pipenv faz em Pipfile.lock . Então você tem uma reprodução exatamente como pipenv oferece. Se você tiver um arquivo de bloqueio, ele será usado e instale a dependência fixada e, se não me engano, é também o que pipenv faz.

A única vez que ele usa dependências abstratas é quando empacota o projeto para distribuição (basicamente para bibliotecas), pois neste caso você não quer dependências fixas.

@vlcinsky Ainda existem alguns pontos que precisam ser resolvidos, corrigidos ou expandidos, mas ainda estou muito interessado em que isso entre em forma de documentação, Pipenv ou outro. Você estaria interessado em enviar uma solicitação de pull? Eu ficaria mais do que feliz em ajudar a desenvolver o artigo.

Em relação à poesia, pessoalmente não sou um fã como um todo, mas ela faz muitas coisas corretas. Provavelmente, não deve ser mencionado nos documentos do Pipenv porque viola algumas das melhores práticas que os desenvolvedores do Pipenv desejam levar às pessoas, mas deve ser mencionado se a discussão for realizada em pypa-dev ou semelhante, para fornecer uma imagem completa de como a embalagem ecossistema atualmente é.

a poesia também pode usar mais atenção e contribuição. Isso seria o melhor para a comunidade, incluindo Pipenv. Com escolhas viáveis, as pessoas podem pesar em suas escolhas em vez de ir para o Pipenv de cabeça e reclamar de não fazer o que eles esperam. A boa competição entre as bibliotecas também pode estimular melhorias técnicas na resolução de dependências, o que tanto o Pipenv quanto a poesia fazem (e nenhum deles perfeitamente). Podemos aprender muito uns com os outros.

@uranusjr Sim, acho que poucas coisas foram esclarecidas e merecem ser compartilhadas com um público mais amplo. Sua ajuda é muito bem-vinda.

E quanto à "redação da documentação do par"? Acho que, neste momento, seria mais eficaz trabalhar nisso em pequena escala de apenas duas pessoas.

As coisas a fazer são (possivelmente com uma ou duas iterações):

  • onde exatamente poderíamos publicar isso
  • identificar itens de documentação (artigos, seções)
  • esclarecer o escopo e objetivo de cada item
  • concordar no esboço
  • identificar questões abertas
  • trabalhe-os
  • escreva o doc
  • publicar (e espero que seja aceito)

Se você quiser escrever por conta própria (com base no que foi discutido) e me tiver como revisor, não reclamaria.

Entrarei em contato com você por e-mail para combinar as próximas ações.

@vlcinsky Também estou disponível como @uranusjr no PySlackers (um espaço de trabalho do Slack) se você preferir interação em tempo real. Pipenv tem um canal lá ( #pipenv ).

@uranusjr Isso é o que quero dizer com reunir esforços. Python precisa desesperadamente de um bom gerenciador de pacotes como o cargo. O ecossistema Python empalidece em comparação com as outras linguagens devido à falta de uma maneira padrão de fazer as coisas. E pipenv não vai ajudar com isso, eu acho.

O que me incomoda é que pipenv anuncia como the officially recommended Python packaging tool embora não seja uma ferramenta de empacotamento, longe disso, o que é enganoso para os usuários. É apenas um gerenciador de dependências acoplado a um gerenciador virtualenv.

Além disso, você diz que foi inspirado por cargo, npm, yarn, que são ferramentas de empacotamento junto com gerenciadores de dependências, enquanto a tubulação não é.

E aqui está a falha de pipenv , apenas turva a água, pois as pessoas ainda cometerão os mesmos erros de antes com requirements.txt vs setup.py . Os projetos ainda serão mal empacotados com dependências mal definidas em seus setup.py por causa disso. Isso é o que projetos como carga fazem da maneira certa: eles lidam com todos os aspectos do desenvolvimento de projetos / aplicativos para garantir uma consistência, enquanto um projeto como pipenv não o faz.

E quando você diz:

que Pipenv e poesia fazem (e nenhuma perfeitamente)

O que você quer dizer? Pelo que tenho visto, seu gerenciador de dependência é muito mais resiliente do que aquele fornecido por pipenv . A única desvantagem é que eles usam a API JSON do PyPI, que às vezes não tem informações de dependência devido a pacotes publicados incorretamente.

De qualquer forma, acho, como você disse, que os dois projetos podem aprender um com o outro.

E, mais uma coisa, qual é o futuro do pipenv se, em última análise, o pip lida com Pipfile ? Será apenas um gerenciador virtualenv?

Se o gerenciador de dependência de poesia depende da API json, ele não só às vezes está errado devido a 'pacotes mal publicados', mas será muito limitado no que pode realmente resolver corretamente. A API json do warehouse posta as dependências _mais recentes_, mesmo se você estiver lidando com uma versão antiga, e isso se tiver essa informação. Costumávamos incorporar a API json também, era ótimo porque era rápida, mas a equipe de infraestrutura nos disse para não confiarmos nela. Parece um pouco insincero chamar algo de resiliente se ele depende de uma fonte não confiável para começar.

Em última análise, os desafios estão em torno da construção de um gráfico de dependência que executou um arquivo de configuração porque, atualmente, é assim que o empacotamento funciona. Simplesmente não há maneira de contornar isso. Um gráfico de dependência que resolve na minha máquina pode ser diferente daquele que resolve na sua máquina, mesmo para o mesmo pacote.

É fácil acenar com a mão e dizer 'bem, isso não torna pipenv um gerenciador virtualenv se pip pode ler um pipfile?' Não. Pipenv é um gerenciador de dependências. Ele gerencia ambientes idempotentes e gera um arquivo de bloqueio reproduzível. Sei que isso deve parecer trivial para você, porque você está descartando isso e reduzindo essa ferramenta a um gerenciador de virtualenv, mas não é. Resolvemos arquivos de bloqueio e incluímos marcadores para versões de python que você não tem, não está usando e mantemos isso disponível para que você possa implantar e reproduzir com precisão em plataformas e versões de python. Usamos vários métodos de resolução, incluindo manipulação de rodas e arquivos locais, repositórios vcs (resolvemos o gráfico lá também) artefatos remotos, pacotes pypi, índices privados, etc.

No final das contas, pip _will_ manipulará pipfiles, esse é o plano, tem sido o plano desde que o formato foi criado. Mas isso é o mesmo que perguntar 'mas e quando o pip puder lidar com os arquivos de requisitos?' A questão é basicamente idêntica. Pip pode instalar esse formato. Não é realmente relevante para nenhuma das funcionalidades que descrevi, exceto que também instalamos os arquivos (usando pip, a propósito).

@techalchemy

A API json do warehouse posta as dependências mais recentes, mesmo se você estiver lidando com uma versão antiga, e isso se tiver essa informação

Isso é simplesmente errado, você pode obter dependências de uma versão específica chamando https://pypi.org/pypi/{project}/{release}/json . Se você apenas chamar https://pypi.org/pypi/{project}/json certamente obterá apenas as últimas dependências, mas poderá obter o conjunto correto de dependências.

E a parte de empacotamento / publicação de projetos Python realmente precisa ser melhorada porque no final isso irá beneficiar a todos, já que tornará possível usar a API JSON de forma confiável.

Ele gerencia ambientes idempotentes e gera um arquivo de bloqueio reproduzível.
Resolvemos arquivos de bloqueio e incluímos marcadores para versões de python que você não tem, não está usando e mantemos isso disponível para que você possa implantar e reproduzir com precisão em plataformas e versões de python.

E o mesmo acontece com poetry . E você pode fazer com que ele não use a API JSON para fornecer o mesmo método de resolução de pipenv (usando pip-tools). Consulte https://github.com/sdispater/poetry/issues/37#issuecomment -379071989 e ainda será mais resiliente do que pipenv (https://github.com/sdispater/poetry#dependency-resolution )

@zface Eu direi isso uma última vez, por favor, leve isso para algum lugar mais alto na hierarquia. Pipenv não se autoproclama ser a ferramenta de empacotamento Python oficialmente recomendada; diz isso porque é . Se você achar que isso é inapropriado, diga aos funcionários que recomendam a Pipenv . Por favor, não coloque essas coisas no Pipenv dev. Este é o lugar errado para reclamar, e você não pode obter resoluções para suas reclamações aqui. Você também pode obter respostas melhores para as perguntas técnicas que possui. Este é um rastreador de problemas para Pipenv, não um fórum de discussão para ferramentas de empacotamento Python e como o empacotamento Python é feito.

Pipenv não depende apenas de ferramentas pip para resolução, por favor, pare de reduzir nosso software a uma linha que demonstra falta de compreensão. Sei muito bem como funciona a api PyPI, conversei diretamente com a equipe que a implementou.

Isso simplesmente errado,

Esse tipo de atitude não é bem-vindo aqui. Não presuma que não entendemos do que estamos falando. Por favor, pratique a cortesia.

ainda será mais resiliente do que pipenv (https://github.com/sdispater/poetry#dependency-resolution)

Atualmente, o Pipenv não simplifica os gráficos de dependência. Apontar para um problema específico em que uma árvore foi achatada e afirmar que a ferramenta inteira é, portanto, melhor e mais resistente é tolice, você está provando repetidamente que está aqui simplesmente para insultar pipenv e promover a poesia. Continue, esse comportamento não é bem-vindo.

Concordo que a discussão está fora do assunto, que estava tentando capitalizar as "boas práticas" espalhadas por pipenv.

Contudo,

[...] ainda cometerá os mesmos erros de antes com requirements.txt versus setup.py. Os projetos ainda serão mal empacotados com dependências mal definidas em seu setup.py por causa disso.

Eu compartilho essa opinião, fazer com que novos desenvolvedores empacotem com sucesso seu próprio código Python é na verdade complexo, muito complexo, requer a leitura de muita documentação online.
Mas não cabe ao pipenv ou a qualquer outra dependência de pacote lidar com isso inteiramente. Não podíamos reescrever a história. Nós, como comunidade, precisamos encontrar uma maneira de modernizar a cadeia de ferramentas Python, passo a passo.

E pipenv (e provavelmente poesia) é um bom passo em frente.

Ter que manter de um lado Pipfile para aplicação e setup.py para bibliotecas do outro lado é um acéfalo. Não importa o quão difícil seja explicar com muitas palavras e longos artigos e guias de boas práticas, é muito complexo para o que é. Concordo plenamente que é assim por enquanto, mas isso não deve nos impedir de imaginar um caminho melhor e mais seguro.
No final, como desenvolvedor, quero uma única ferramenta, talvez com dois modos diferentes, para me ajudar e tornar minha vida o mais fácil possível.

Deve ser uma maneira de extrair apenas a parte que faz o requirements.txt/Pipfile de bibliotecas como PBR para propor um tipo de 'setup.py fácil', um wrapper ciente do Pipfile próximo a install_requires , sem todo o comportamento indesejado que o pbr traz, e empacote isso em um wrapper de setuptools dedicado que só faz isso.

Assim, seríamos capazes de ter o melhor de cada mundo:

  • pipenv para manter Pipfile (com versão em bibliotecas e aplicativos)
  • pipenv deve manter Pipfile.lock (versão somente para aplicativos)
  • alguém usaria este pacote mágico ( pipfile_setuptools , install_requires_pipfile ?) que seria uma dependência de primeiro nível cujo trabalho é apenas injetar Pipfile em install_requires .

Este é outro projeto que não estaria relacionado a pipenv , mas ainda precisa de uma biblioteca de analisador Pipfile genérica. O que você acha?

@gsemet Pelo que entendi, o PyPA tem tentado preencher isso com pyproject.toml, liderado por flit . Você precisará falar com eles primeiro (em pypa-dev ou distutils-sig) sobre isso antes de continuar a usar o Pipfile como formato de origem. Quanto à análise do Pipfile (e do arquivo de bloqueio), isso é tratado em pypa / pipfile (cujos fornecedores do Pipenv fornecem a lógica de análise principal).


Edit: Por favor, mande-me uma mensagem se você decidir iniciar uma discussão sobre isso em qualquer uma das listas de e-mail. Tenho algumas ideias de como podemos reunir as duas partes da distribuição de pacotes Python.

Devo admitir que estou um pouco triste ao ver dependências declaradas em pyproject.toml (que assume as funções como setup.cfg feitas por PBR), enquanto PyPa também suporta Pipfile ....

Obrigado pelo ponteiro para flit e pipfile. Também existe o pipenvlib de Kennethreitz que parece mais leve.

O setup.cfg do PBR parece mais completo em comparação com a documentação oficial (ex: data_files ) e reutiliza um arquivo já compartilhado com várias ferramentas (flake8, pytest, ...) pode usar o mesmo arquivo, reduzindo o número de arquivo na raiz de um projeto Python)

Esta página foi útil?
0 / 5 - 0 avaliações