Pip: Novo resolvedor: lançamento, loops de feedback e fluxo de desenvolvimento

Criado em 25 mai. 2019  ·  83Comentários  ·  Fonte: pypa/pip

Estive pensando um pouco sobre o nº 988 (duh!) -- especificamente como implementá-lo para minimizar a quebra e maximizar a oportunidade de obter feedback útil dos usuários.

Arquivando este problema agora que finalmente tenho os dois polegares + tempo à mão para fazê-lo. Obviamente, tudo o que se segue está em discussão. :)


Meu plano atual para implantar o novo resolvedor é baseado em expor o novo resolvedor por trás de um sinalizador. O fluxo seria não documentá-lo inicialmente e adicionar grandes avisos sobre o uso do sinalizador. Uma vez que seja menos experimental e mais beta, podemos começar a convidar usuários para jogar com o novo resolvedor. Isso envolveria CTAs para os usuários pedirem para experimentá-lo e fornecer feedback. Essas informações também podem ser impressas quando executadas com o sinalizador.

Em termos de gerenciamento de feedback, estou pensando em solicitar feedback sobre o rastreador de problemas de um repositório diferente. O raciocínio por trás de colocar problemas em um rastreador de problemas diferente é minimizar o ruído aqui + permitir discussões/investigações mais focadas. Eu borbulharia qualquer coisa que fosse mais do que um "bug na resolução" para o rastreador de problemas principal (este).

Em termos de transição, acho que quando houver confiança suficiente na nova lógica de resolução, podemos analisar como queremos lidar com a transição. Tendo colocado isso atrás de um sinalizador, teremos duas opções - alternar diretamente em uma versão ou "estabilizar" o novo resolvedor e fazer um (talvez multi-lançamento?) "período de transição". Acho que podemos fazer o planejamento da transição mais tarde, quando tivermos uma melhor compreensão das trocas exatas envolvidas.

Em termos de git/GitHub, esta é provavelmente a primeira implementação de recurso "experimental" dentro do pip. FWIW, estou planejando fazer experimentos etc no meu fork e regularmente mesclar o progresso para o próprio repositório principal do pip (somente código, em pip._internal.resolution). Não quero ser barulhento no repositório principal, mas quero manter master em sincronia com o trabalho sobre isso.


Observe que estou colocando #5051 como um bloqueador para este trabalho por causa de quão doloroso foi lidar com a lógica de construção ao construir o protótipo.

dependency resolution maintenance

Comentários muito úteis

Eu sou jovem, burro e otimista

:-) E às vezes sou muito velho, cansado e cínico. Vamos com sua filosofia, soa muito melhor :-)

Todos 83 comentários

Eu não sei como você planejou isso, mas um comentário é que eu encorajo você a tentar compartilhar o código o máximo possível entre o novo código e o código atual e refatorar o código atual enquanto você está trabalhando permitem mais compartilhamento entre os caminhos de código novos e atuais.

Uma razão é que, se você estiver compartilhando mais código, haverá menos chance de quebra ao ativar e desativar o novo comportamento, porque você estará exercitando esse código compartilhado em ambos os estados e não terá como muitas diferenças potenciais de comportamento para enfrentar.

Isso envolveria CTAs para os usuários, pedindo-lhes para experimentá-lo e fornecer feedback

Nosso histórico de obter feedback antecipado sobre novos recursos tem sido muito ruim. Tentamos versões beta, lançando novos recursos com sinalizadores de "exclusão" que as pessoas podem usar se encontrarem problemas, grandes campanhas publicitárias para mudanças importantes, e nenhum deles parece ter funcionado.

Meu sentimento pessoal é que "disponibilize e peça feedback" é uma variação interessante do que tentamos anteriormente, mas no final das contas não fará muita diferença. Muitas pessoas usam o pip mais recente com opções padrão em seus pipelines de compilação automatizados e não testam antes de mudar para uma nova versão do pip (vimos isso com o PEP 517).

Eu me pergunto - poderíamos obter uma concessão do PSF para obter recursos para fazer um grande exercício de teste "no mundo real" para esse recurso ou (melhor ainda) desenvolver uma infraestrutura de teste para nós? Esse projeto pode incluir uma chamada para projetos para nos informar seus fluxos de trabalho e configurações, para que possamos configurar caminhos de teste que garantam que novas versões de pip não os quebrem. Ou até mesmo usar uma concessão para obter alguém experiente no aspecto de comunicação de obter testadores beta para novos recursos para nos ajudar a configurar um programa de teste de usuário melhor?

Em termos de git/GitHub, esta é provavelmente a primeira implementação de recurso "experimental" no pip

Não tenho 100% de certeza do que você quer dizer com isso. Certamente tivemos novos recursos no passado que foram adicionados enquanto a "maneira antiga" ainda estava presente. Não costumamos deixá-los "desativados por padrão, habilite para experimentá-los", se é isso que você quer dizer, mas isso é principalmente porque nunca encontramos uma boa maneira de obter feedback (veja acima).

Passei ~60 minutos (re-re-re-re-re-) escrevendo este post, então agora vou dar uma olhada em lugares em Nova York! Se não vir uma resposta rápida minha, é porque estarei em modo turista.


Recomendo que você tente compartilhar o código o máximo possível entre o novo código e o código atual e refatore o código atual à medida que estiver trabalhando para permitir mais compartilhamento entre os caminhos de código novo e atual.

Com certeza! Isso é 80% do motivo pelo qual estou colocando o nº 5051 à frente disso -- pretendo pagar muito da dívida técnica que acumulamos em nossa lógica de construção para que fique mais fácil reutilizá-la (toda?). Um monte de código terá que ser :fire: e eu concordo que o resto definitivamente deve ser reutilizado tanto quanto for razoável.

Não costumamos deixá-los "desativados por padrão, habilite para experimentá-los", se é isso que você quer dizer

Sim, de fato. Também estou sugerindo o fluxo de desenvolvimento aqui - IMO, não há problema em mesclar infraestrutura vazia (classes com vários métodos que são apenas raise NotImplementedError() que serão desenvolvidos em PRs subsequentes) ou não cubra todos os casos (implementação incompleta) na ramificação master , desde que seja usado apenas atrás do sinalizador que é explicitamente indicado como "experimental/alpha".

re: comentários

Sou jovem, burro e otimista - quero tornar este lançamento um opt-in, para obter feedback proativo e agir de acordo com ele. Por "proativo", quero dizer de pessoas que estão dispostas a dedicar um tempo extra para experimentar a funcionalidade alfa/beta e nos informar sobre como ela é. Acho que se fizermos barulho suficiente e estrategicamente segmentar/alcançar as pessoas, podemos obter um bom feedback "proativo" de pessoas que têm tempo e energia para experimentar novas funcionalidades para ajudar a resolver os detalhes/problemas.

Observando nossas mudanças "principais" recentes, acho que a maioria dos comentários que recebemos foi reativa - de usuários que perceberam problemas com seus fluxos de trabalho quando eles quebraram e, em seguida, entraram em contato para nos informar sobre isso. Muitos deles podem não ter tido tempo de ajudar a resolver os detalhes da nova funcionalidade, o que causa muito atrito. Isso também nos custou muito do nosso "orçamento de rotatividade" [1], que eu não quero gastar mais, já que o Python Packaging não tem muito sobrando de qualquer maneira [2].

FWIW, pretendo pegar emprestadas algumas ideias do lançamento do PyPI, como fazer postagens de blog em locais bastante visíveis (ou seja, não no meu blog pessoal), possivelmente ir a podcasts, e-mails acionáveis ​​oportunos etc. Também estou procurando mais arte anterior /avenues para se comunicar via. Uma das (muitas!) coisas que aprendi na PyCon, foi que existem canais que não usamos, que vão ajudar a divulgar informações, mas não vão procurar checar se temos algum para divulgar.

Para ser claro, não estou criticando a abordagem de lançamento que adotamos para o PEP 517, acho que está indo bem, especialmente pelo fato de sermos todos voluntários. Estou tentando ver o que podemos aprender e itens acionáveis ​​para tentar evitar os problemas que tivemos. A maioria desses itens envolve mais trabalho dos mantenedores e a principal razão pela qual estou gastando todo esse tempo pensando nisso é porque vejo isso como um divertido exercício de aprendizado de como fazer o gerenciamento de mudanças.

re: subsídios

Sim, acho que definitivamente podemos usar uma pessoa mais experiente para nos ajudar a descobrir a comunicação, os lançamentos e a infraestrutura de testes. Isso, no entanto, precisa de alguém para fazer o trabalho de redação de subsídios e descobrir planos mais concretos do que posso fazer agora, já que não tenho um número de horas / semana mais estável que possa garantir.

FWIW, a PSF tem um contrato em andamento para ajudar a descobrir a comunicação relacionada ao PyPA/Packaging com a Changeset Consulting, então talvez possamos aproveitar isso?


Não estou @-mencionando intencionalmente as pessoas, já que isso é bastante cedo no estado de planejamento para adicionar mais pessoas à conversa.

Notas de rodapé:

  1. Um termo muito legal que @pganssle usou e que com certeza vou usar.
  2. É por isso que eu coloquei o #3164 em segundo plano, apesar de ter uma implementação do pacote "pip-cli" proposto lá e ter um consenso razoável sobre como queremos que o lançamento se pareça.

Eu sou jovem, burro e otimista

:-) E às vezes sou muito velho, cansado e cínico. Vamos com sua filosofia, soa muito melhor :-)

Com certeza! Isso é 80% do motivo pelo qual estou colocando o nº 5051 à frente disso -- pretendo pagar muito da dívida técnica que acumulamos em nossa lógica de construção para que fique mais fácil reutilizá-la (toda?).

Excelente!

Do IRC agora mesmo :

[sumanah] pradyunsg: há algo que a comunidade de pip & packaging possa fazer para ajudá-lo a fazer mais trabalho mais rápido no resolvedor?
....
[pradyunsg] Na verdade, agora, as entradas em https://github.com/pypa/pip/issues/6536 provavelmente me ajudariam a descobrir como abordar o trabalho / obter feedback das pessoas etc.
....
[sumanah] pradyunsg: re: Novo Resolvedor: Rollout, Feedback Loops e Development Flow #6536 -- a entrada que você quer é algo como: a abordagem do sinalizador de recurso é uma boa ideia? é uma boa ideia obter feedback por meio de algum mecanismo diferente dos problemas do pip no GitHub? é uma boa ideia obter uma concessão ou similar para obter testes manuais no mundo real e uma infraestrutura de testes robusta construída e/ou comunicações proativas?
...
[pradyunsg] Sim -- se as idéias que estou sugerindo são boas. Além disso, quaisquer ideias/abordagens/pensamentos adicionais que possam ajudar a implementação + feedback a serem mais suaves seriam incríveis.

Assim:

A abordagem do sinalizador de recurso é uma boa ideia? sim.

É uma boa ideia obter feedback por meio de algum mecanismo diferente dos problemas do pip no GitHub? sim. Devemos encontrar maneiras automatizadas de aceitar relatórios de bugs menos estruturados de usuários menos experientes.

Uma infraestrutura de teste mais robusta ajudaria? Sim, muito, e este é um lugar onde nossos patrocinadores podem nos ajudar.

O Changeset (eu), sob o contrato existente com a PSF para ajudar na coordenação/comunicações do PyPA, poderia ajudar a pip com comunicações proativas para obter testes manuais mais sistemáticos no mundo real? Supondo que eu tenha horas restantes no meu contrato até o momento em que queremos iniciar este lançamento, sim.

é uma boa ideia obter um subsídio ou algo semelhante para obter mais ajuda com a experiência do usuário, comunicações/publicidade e testes? sim. As doações do PSF seriam potencialmente de interesse , assim como as doações da NLNet (para solicitações abaixo de 30.000 euros ), potencialmente o software de código aberto essencial de Chan Zuckerberg para bolsas de ciência e o MOSS da Mozilla . O GT de Embalagens pode ser o requerente do registro. Se @pradyunsg ou @pfmoore quiserem dar um aceno "sim, isso parece interessante", posso começar a investigar essas possibilidades com o GT.

Se @pradyunsg ou @pfmoore quiserem dar um aceno "sim, isso parece interessante",

Definitivamente parece interessante para mim :-)

@pradyunsg ou @pfmoore quer dar um aceno "sim, isso parece interessante"

_acena_ sim, isso parece interessante

Uma infraestrutura de teste mais robusta ajudaria? Sim, muito, e este é um lugar onde nossos patrocinadores podem nos ajudar.

@brainwane Também relevante aqui é https://github.com/pypa/integration-test. Acho que configurar isso é outra área potencial para financiamento - devemos adicionar isso a https://wiki.python.org/psf/Fundable%20Packaging%20Improvements.

OK! Comecei a conversar com o PSF e com o pessoal da Iniciativa Chan Zuckerberg sobre a solicitação de uma doação CZI por meio do Grupo de Trabalho de Embalagens. Adicionei alguns detalhes à página Fundable Packaging Improvements sobre por que o novo resolvedor de pip é importante e adicionei o projeto integration-test a essa lista. E comecei a reunir nomes de especialistas em experiência do usuário que têm a capacidade de pesquisar nossa complexa cadeia de ferramentas de instalação/distribuição de pacotes de linha de comando, conversar com usuários para entender seu modelo mental do que está acontecendo e o que deve acontecer e aconselhar os mantenedores.

Se conseguirmos dinheiro por meio de doações da MOSS, CZI ou NLNET, acho que conseguiremos o dinheiro... no mínimo em outubro, provavelmente. Uma doação diretamente do PSF provavelmente seria mais rápida, mas "Nosso foco atual são workshops de Python, conferências (especialmente para ajuda financeira) e esforços de diversidade/inclusão em Python".

Uma consideração é que eu sei que Brett e o pessoal do conselho diretor estão falando sobre investir em gerenciamento de projetos e procurando algum tipo de recurso pago para gerenciar esses projetos (triagem, gerenciamento de projetos etc.) e eles estão conversando com o PSF diretamente. Pode valer a pena entrar em contato e descobrir o que eles estão fazendo ou pensando, já que ouvi algumas conversas sobre sustentabilidade de longo prazo e seria bom estar envolvido nelas.

Os sinalizadores de recursos são bons, os opt-ins são bons. Uma coisa que você pode considerar é se você pode solicitar aleatoriamente aos usuários que experimentem o resolvedor (como, muito, muito raramente e apenas para uma instalação de cada vez, ou seja, não forçá-los a ligá-lo permanentemente). Em seguida, você pode indicar como o resolvedor foi útil (por exemplo, o que ele fez por eles? quais conflitos ele encontrou e resolveu?)

As pessoas que vêm de javascript ou ferrugem, por exemplo, também esperam algum tipo de arquivo de bloqueio, então isso pode ser algo a ser considerado ...

Desculpe entrar, fico feliz em ver isso acontecendo!

Meu sentimento pessoal é que "disponibilize e peça feedback" é uma variação interessante do que tentamos anteriormente, mas no final das contas não fará muita diferença. Muitas pessoas usam o pip mais recente com opções padrão em seus pipelines de compilação automatizados e não testam antes de mudar para uma nova versão do pip (vimos isso com o PEP 517).

Como uma das pessoas que foram mordidas por alguns problemas do PEP 517 por esse motivo, eu adoraria ver uma maneira opt-in de testar as coisas. Mas eu só sei sobre esse tipo de coisa porque eu assinei todas as fontes de notícias de empacotamento python que pude após o problema do sinalizador --no-use-pep517 . O que estou dizendo é que espalhar esse tipo de notícia é difícil e provavelmente é por isso que é difícil obter feedback.

Acho que mais pessoas estariam interessadas nisso se a informação pudesse ser melhor divulgada. É isso que os recursos que você está procurando permitiriam?

Para continuar com o que jriddy está dizendo, também acho que será muito difícil fazer com que as pessoas testem vários sinalizadores de recursos se tiverem que saber sobre eles, fazer alterações na configuração de CI para cada novo sinalizador etc.

O que parece muito mais factível, no entanto, é se houver apenas _um_ sinalizador de recurso para conhecer, para testar "o que está por vir" em termos de alterações que precisam ser testadas. Então, pessoas e empresas podem configurar seu CI para executar isso também (sem falhas nas compilações por erros). Estou pensando em algo semelhante ao Rust, onde esses tipos de alterações ocorrem no canal "beta" da cadeia de ferramentas, e é fácil configurar outro canal CI para executar coisas na cadeia de ferramentas beta e enviar erros para alguém.

O principal é que essa configuração precisa ser aprendida e feita _apenas uma vez_, em vez de ter que aprender continuamente sobre novos sinalizadores de recursos individuais, modificar configurações de CI ou testá-las manualmente.

O que parece muito mais factível, no entanto, é se houver apenas _um_ sinalizador de recurso para conhecer,

De certa forma, isso já não existe na forma de --pre ? O canal de lançamento beta para pip poderia ser apenas uma questão de executar pip install --upgrade --pre pip ?

Desculpe entrar, fico feliz em ver isso acontecendo!

@techalchemy , por favor, de todas as pessoas, _você_ definitivamente não precisa se desculpar por entrar nessa discussão.

É isso que os recursos que você está procurando permitiriam?

Até certo ponto, sim.

reg: versões beta/"canal" para pip

Obrigado por entrar em contato com @jriddy e @chrish42. Embora eu ache que geralmente essa é definitivamente uma conversa útil/importante, também sinto que é um pouco OT para esse problema. No entanto, responderei aqui uma vez; se quisermos discutir isso mais, vamos abrir uma nova questão.

Nós tentamos isso no passado - mais recentemente com o pip 10 - mas não funcionou bem. Estou um pouco cético sobre o quão bem isso pode funcionar daqui para frente também, mas também posso imaginar que algumas mudanças em nosso processo podem resultar em que isso funcione sem problemas para nós. Talvez pudéssemos fazer um conjunto de recursos "somente beta" ou algo assim? Eu imaginei -X all como uma sintaxe para isso em #5727. Talvez pudéssemos pegar isso como parte deste plano de lançamento? Eu não sei. Precisamos investir tempo e energia para descobrir isso. :)

Conforme mencionado em https://github.com/pypa/packaging-problems/issues/25#issuecomment -520167480, acho importante ter uma explicação consolidada de como um solver altera a experiência do pip. Muitas pessoas ficarão frustradas com a mudança para um sistema mais rígido (mesmo que as coisas devam ser mais confiáveis ​​no geral, elas serão bloqueadas em lugares onde não estão sendo bloqueadas no momento.

Ter uma explicação central de como as coisas mudaram e por que é uma boa mudança tornará a resposta a essas pessoas irritadas muito mais simples. Poste um link e veja se eles têm mais alguma dúvida.

O pré-lançamento é uma boa ideia. Em conda, temos um canal de pré-lançamento, conda-canary. Incentivamos as pessoas a configurar um trabalho de CI para ser executado no canary de uma maneira que as ajude a ver se as alterações no conda vão quebrá-las. Idealmente, eles nos informam antes de lançarmos essa versão. Esse canal foi um fracasso bastante triste. A única vez que as pessoas realmente parecem usá-lo é quando querem obter a versão mais recente para corrigir algum bug com o qual estão lutando. Não recebemos muitos relatórios de nossos primeiros adeptos pretendidos. Eu ainda acho que o pré-lançamento é uma boa ideia, porque quando um lançamento vai mal e as pessoas ficam bravas com você por quebrar seus 700 nós gerenciados, você pode dizer "bem, ele estava disponível por uma semana antes de lançarmos. Por que não estão você está testando essas coisas antes de lançá-las para 700 nós?" Você está dando às pessoas uma oportunidade de fazer as coisas funcionarem melhor. Ajude-os a perceber que passar essa oportunidade significa mais dor para eles no futuro. É um investimento que vale a pena para eles e, se o fizerem como parte de seu IC, não lhes custará tempo além da configuração.

Em relação ao sinalizador: acho melhor ter uma opção de configuração (talvez além de um sinalizador). Eu não gostaria de passar uma bandeira o tempo todo. Não tenho certeza se o pip tem essa capacidade - talvez você diga às pessoas que desejam um switch mais permanente para usar o env var correspondente?

Sobre a bandeira:

As opções de CLI do pip são mapeadas automaticamente para uma opção de arquivo de configuração e uma variável de ambiente, com os nomes apropriados.

@msarahan Obrigado por entrar na conversa, muito apreciado! :)

Em relação à opção "deixe-me fazer o que eu quero" para ignorar dependências quebradas, acho que seria desejável estruturar o sinalizador de recurso de forma que ele também pudesse servir como opt-out após o resolvedor ser ativado por padrão (por exemplo, start com --avoid-conflicts como opt-in, eventualmente mude para --no-avoid-conflicts como opt-out, mas aceite ambas as opções desde o início)

Você também vai querer considerar como --ignore-installed interage com o solver - quando ele for aprovado, você provavelmente deve ignorar todos os requisitos para pacotes já instalados.

Além disso, lidar com as coisas como patches de refatoração menores para facilitar a integração do resolvedor é um excelente caminho a seguir (essa é a abordagem que tornou possível a nova API de configuração para CPython: muita refatoração privada que acabou estável o suficiente para tornar pública)

@ncoghlan O que significa "desativar" o resolvedor? Evitar completamente a resolução de dependência (e, portanto, o resolvedor) é --no-deps . Eu entendo que há uma necessidade de "ignorar conflitos de versão neste pacote" ou algo nesse sentido.

Pessoalmente, não vejo sentido em manter a lógica de resolução "manter visto primeiro" por mais de um período de transição para um novo resolvedor.

No entanto, se houver casos de uso que essas duas opções não cobrem, eu realmente gostaria de saber sobre eles. :)


Mais amplamente, se houver fluxos de trabalho que tenham problemas com o comportamento de um resolvedor estrito, estou curioso para saber como são, o mais cedo possível, para poder descobrir se/como apoiá-los.

Pessoalmente, não vejo sentido em manter a lógica de resolução "manter visto primeiro" por mais de um período de transição para um novo resolvedor.

IDK, eu uso esse "recurso" para fazer algumas coisas bem loucas com compilações, como ...

# install just the packages I've built specifically
pip install --no-index --no-deps --find-links=/path/to/my/local/build/cache -r local-reqs.txt

# ...snip to later in a dockerfile, etc...

# install the deps from public PyPI
pip install -r local-reqs.txt

Nesse caso, estou pedindo para resolver minhas dependências depois de instalar alguns pacotes muito pré-determinados de uma casa do leme local. Suponho que eu poderia ler minhas versões exatas nesse arquivo local-reqs para deixar um resolvedor feliz, mas na verdade achei o comportamento atual do pip bastante útil para permitir esses tipos de etapas de injeções de compilação arbitrárias. Pode ser um caso do fluxo de trabalho de aquecimento da barra de espaço, admito.

Mas talvez o comportamento de "resolução ingênua" ainda tenha um uso.

Concordo com @pradyunsg. Não acho viável manter o código existente e um novo resolvedor indefinidamente. Certamente, como mantenedor de pip, não tenho interesse em fazer isso.

De um ponto de vista do usuário final, aceito que pode haver cenários estranhos em que o novo resolvedor pode não fazer a coisa certa. E ter um sinalizador de emergência "devolva-me o comportamento antigo" é um mecanismo de transição importante (embora seja discutível se "reverter temporariamente para a versão anterior do pip" não é tão bom - mesmo que coisas como o uso comum de CI que usa automaticamente o pip mais recente, tornando a defesa dessa opção problemática). Mas a longo prazo, por que precisaríamos manter o comportamento atual? Posso imaginar as seguintes situações principais:

  1. Solucionador de bugs. Possibilidade óbvia, correção fácil - corrija o bug na próxima versão do pip.
  2. Casos em que o resolvedor antigo está errado (gera resultados que não satisfazem as restrições). Não pretendemos apoiar isso daqui para frente, certo? (Pelo menos não através de algo menos extremo do que o usuário fixar o que deseja e usar --no-deps para desligar o resolvedor).
  3. Casos em que os resolvedores antigos e novos fornecem resultados diferentes, ambos satisfazendo as restrições fornecidas. Os usuários podem adicionar restrições para forçar o resultado antigo (se não puderem, isso nos coloca de volta em (2)). Devemos dar a eles tempo para fazer isso, mas depois descartar o resolvedor antigo, assim como qualquer outra funcionalidade obsoleta.
  4. Um caso extremo que consideramos muito complexo/estranho para suportar. Isso é como (3), mas onde não estamos afirmando que o novo resolvedor fornece o resultado "certo". Os usuários ainda podem modificar as restrições para evitar o caso estranho ou fixar e usar --no-deps . Mas, em última análise, estamos dizendo "não faça isso" e, se os usuários ignorarem essa mensagem, novamente em algum momento removeremos o resolvedor antigo dizendo "nós avisamos".

Existem outros que eu perdi? Em particular, onde não é possível depreciar e remover o resolvedor antigo?

A propósito, qual é o melhor lugar para postar cenários "aqui está um caso extremo que eu pensei" para que eles não se percam? Eu acho que seria útil coletar o máximo de situações estranhas que pudermos com antecedência, apenas para que possamos começar cedo a escrever casos de teste :-)

PS Devemos provavelmente também, como parte do trabalho de preparação para o novo resolvedor, pesquisar quais são os problemas de restrição "típicos" (com base no que está no PyPI). De minha parte, é muito raro que eu tenha algo mais complexo do que "pip install". Seria uma pena ficarmos tão atolados nos casos complexos que perdemos de vista a grande maioria dos mais simples.

  1. resolver é muito lento (veja conda). Se eu tiver que escolher entre um resolvedor de mais de 20 minutos ou o comportamento atual, geralmente quero o comportamento atual (ou pelo menos tentar; em muitos casos, ele dará um resultado bom).

  2. metadados errados. não é um problema tão grande hoje, mas é fácil imaginar casos que deveriam ser solucionáveis, mas não são. Os metadados PyPI estão em pior forma do que os metadados conda/conda-forge, e já é um problema para o conda. se estiver errado e, como usuário, não conseguir uma solução, gostaria de obter algum opt-out.

@rgommers Para 6, a opção de estilo "ignorar conflitos de versão neste pacote" pode funcionar, certo?

Obrigado, @rgommers - esses são bons pontos.

O resolvedor é muito lento, eu contaria como um bug do resolvedor. Se ele não pode fornecer resultados com desempenho suficiente em casos simples, isso não é adequado para o propósito, na minha opinião. Se, por outro lado, você tem uma rede de restrições massivamente complexa que leva mais tempo com um resolvedor completo (espero que 20 minutos seja um exagero, não considero isso aceitável sob nenhuma circunstância!), então estamos entrando em "coisas que considero muito complexo para suportar" território. Dito de outra forma, alguém já tentou pedir ao conda para fornecer um resolvedor "rápido e sujo" impreciso, mas rápido? Se eles não vão fazer isso (e eu tenho certeza que eles não fariam), então por que é razoável esperar que o pip faça isso?

Metadados ruins são definitivamente algo que eu consideraria como "nós não suportaríamos isso" (lembre-se, estou falando de "após um período de descontinuação" aqui!). Dar aos usuários tempo para corrigir os metadados e fornecer uma opção de cláusula de escape "ignorar conflitos de versão no pacote X" é IMO suficiente, não devemos esperar que retenhamos todo o maquinário antigo apenas porque algumas pessoas não corrigirão seus metadados.

Mas sim, divulgar o fato de que precisamos de bons metadados porque um resolvedor preciso segue a regra "garbage in, garbage out" e monitorar como as pessoas respondem a essa mensagem faz parte do processo de implantação.

estou pedindo para resolver minhas dependências depois de instalar alguns pacotes muito pré-determinados de uma casa do leme local.

@jriddy A estratégia de resolução de "usar a instalação existente se compatível" funcionará para isso.

onde é o melhor lugar para postar cenários "aqui está um caso extremo que eu pensei" para que eles não se percam?

https://github.com/pradyunsg/zazo/

Para 6, a opção de estilo "ignorar conflitos de versão neste pacote" pode funcionar, certo?

sim, parece a opção certa

(Espero que 20 minutos seja um exagero, não considero isso aceitável sob nenhuma circunstância!), então estamos entrando no território de "coisas que consideramos muito complexas para suportar". Dito de outra forma, alguém já tentou pedir ao conda para fornecer um resolvedor "rápido e sujo" impreciso, mas rápido? Se eles não vão fazer isso (e eu tenho certeza que eles não fariam), então por que é razoável esperar que o pip faça isso?

Há muitas pessoas que estão reclamando do desempenho de conda e estão ouvindo - mas é muito trabalho, veja seus posts recentes no blog. Não sei se haverá uma opção de conda rápida e suja. No entanto, existem soluções relacionadas (hacks) como conda-metachannel que permitem que você reduza o gráfico de dependência (incluir/excluir pacotes manualmente) para obter uma solução mais rapidamente. Então eu acho que é na mesma linha.

No entanto, lembre-se de que isso será _definitivamente_ necessário, a menos que você:

  • fazer um trabalho muito melhor do que conda imediatamente (não muito provável, não é como se esses caras não soubessem o que estão fazendo - é apenas um problema muito cabeludo).
  • tem apenas problemas menores para resolver (espero que não seja verdade, o PyPI é grande e com bons metadados os problemas devem ser grandes)

Metadados ruins são definitivamente algo que eu consideraria como "nós não suportaríamos isso"

É justo. Toda a postura sobre "nós não aplicamos metadados corretos" não está ajudando aqui. A menos que isso mudou recentemente? (e eu sei, é uma coisa de PyPI, não uma coisa de pip - mas está relacionado).

Eu não tenho certeza se você realmente tem as opções que você acha que tem.

O que um resolvedor rápido e sujo faz de diferente de um preciso? O que cai para ser mais rápido? Restrições são restrições - elas não vêm em notas. Ou você os satisfaz ou não. Talvez você possa satisfazê-los apenas por nome para começar, depois por versão, etc.

Quando as pessoas estão chateadas com a lentidão do Conda, é essencialmente sempre por causa de metadados ruins. Tenha em mente que você será culpado por qualquer lentidão percebida, independentemente da causa raiz. Metadados ruins às vezes são uma restrição incorreta, mas mais frequentemente é a falta de uma restrição que permite a consideração de uma opção muito mais antiga e indesejável. Conda melhorou muito recentemente fazendo uma pequena coisa: removendo nossa coleção mais antiga de software que tinha restrições inadequadas (principalmente muito abertas). Essas restrições abertas fizeram com que o conda explorasse soluções realmente ruins, exigindo muitos downgrades. Este é o lugar onde a resolução levou literalmente horas. Os downgrades são operações muito, muito caras devido à maneira como podem ser cascatas, com cada etapa se tornando menos restrita e mais cara.

O problema com a mentalidade de "entrar lixo, sair lixo" é que, como mantenedores do solucionador, você está segurando o saco. A menos que você tenha uma heurística muito boa para o que é lixo, você é impotente. Você acaba sendo aquele que tem que investigar por que o solver é lento, isolar o pacote do problema e então pedir à fonte do problema para consertar as coisas. Não é uma boa posição para se estar, confie em mim. Gastamos muito tempo tentando explicar às pessoas por que o conda está demorando muito ou não funciona com alguma mistura de conda-forge, bioconda ou outros canais de terceiros. Acabamos tendo que fazer o trabalho de detetive e dizer a esses canais de terceiros o que eles precisam consertar. É uma merda.

Qualquer comparação com o conda precisa considerar que o conda está adotando uma abordagem muito diferente do problema. O Conda tem uma fonte gigante de metadados e resolve tudo de uma vez, mas o pip está resolvendo e otimizando recursivamente cada coisa de cada vez (retrocedendo conforme necessário). Isso pode proporcionar diferentes caminhos de otimização.

@wolfv recentemente explorou usando libsolv para conda. Ele ficou frustrado por não conseguir dar as mesmas respostas que Conda. Tudo se resumia a essa diferença entre as abordagens. Libsolv é um solucionador de retrocesso. Pode servir como um complemento compilado opcional para pip para acelerar as coisas, embora eu saiba que você é sensível a não incluir código compilado diretamente com pip.

( @pradyunsg acabou de postar sua atualização de agosto em seu blog .)

Algumas das perguntas e necessidades que as pessoas estão levantando agora são coisas que precisamos começar a reunir agora, como casos de teste.

Mas também: qual cronograma estamos achando realista para esse lançamento? Isso depende muito da saúde e do tempo livre de Pradyun, e da disponibilidade de revisão de código de outros mantenedores de pip e se recebemos algumas concessões para as quais estamos solicitando, mas acho que a sequência é algo como:

  • build logic refactor: em andamento, feito em algum momento de dezembro-fevereiro
  • Pesquisa e design de UX, construção de infraestrutura de teste, conversa com downstreams e usuários sobre sinalizadores de configuração e cronogramas de transição: precisamos de financiamento para isso; o início mais cedo é provavelmente dezembro, levará de 2 a 3 meses
  • introduzir as abstrações definidas em resolvelib/zazo ao fazer o teste alfa: levará alguns meses, então, estimando conservadoramente, maio de 2020?
  • adotando melhor resolução de dependência e fazer testes beta: ?

Isto está certo? o que estou perdendo?

Pergunto porque parte do trabalho de coleta de informações é algo que um gerente de projeto e/ou pesquisador de UX deve fazer, na minha opinião, e porque algum progresso em https://github.com/pypa/packaging-problems/issues/264 e outras questões podem ajudar com as preocupações que as pessoas levantaram aqui.

Metadados ruins às vezes são uma restrição incorreta, mas mais frequentemente é a falta de uma restrição que permite a consideração de uma opção muito mais antiga e indesejável.

Como usar dependências irrestritas ou >= some_old_version é a regra e não a exceção para setup.py / pyproject.toml , isso será um problema. Eu realmente não me importo se é uma "restrição incorreta" ou se o solucionador precisa fazer escolhas diferentes - esse é o estado dos metadados no PyPI.

Essas restrições abertas fizeram com que o conda explorasse soluções realmente ruins, exigindo muitos downgrades.

Você é o especialista aqui, mas não há soluções pragmáticas para isso? Na maioria dos casos, nenhum downgrade é necessário, então tente apenas isso primeiro. Em seguida, faça o downgrade de apenas 1 versão para cada pacote. Se você precisar fazer o downgrade ainda mais, quase sempre é a solução errada (pode ser devido a metadados ruins ou outra coisa).

Na verdade, não tenho certeza se pip _pode_ até mesmo rebaixar as coisas. Primeiro tinha uma estratégia muito agressiva de "atualizar tudo", agora "atualizar conforme necessário" funciona muito bem. Eu nunca o vi fazer downgrade de nada e, na prática, funciona muito bem para uso regular.

Um exemplo de downgrade que o conda deve enfrentar, mas o pip não: usuários do anaconda ou miniconda com python 3 começam com python 3.7. Os usuários às vezes precisam instalar algo que está disponível apenas para python 3.6. O solver deve fazer o downgrade do python e de todos os outros pacotes não noarch. Este é um caso especial que talvez possa ser otimizado por ter um comportamento especial para alterações de versão do python, mas ilustra o ponto de como as alterações em um pacote podem exigir downgrades para outro. "Funcionou até agora" é pura sorte, não o que realmente funciona o tempo todo.

Quanto à restrição de versões, você não pode aplicar isso na resolução em si. Você tem suas restrições, e qualquer tipo de "abertura por uma versão" não pode esperar ser geral o suficiente, porque é diferente para cada pacote. No entanto, você pode cortar metadados por versão anterior ao solver. Isso é o que é o "current_repodata.json" do conda. Apenas a versão mais recente. Faz as coisas correrem muito rápido quando funciona, mas as pessoas ficariam muito bravas se esse fosse o único repodata. As coisas não seriam reproduzíveis, e eles ficariam frustrados porque especificações que funcionam um dia podem não funcionar no dia seguinte. Fornecemos um substituto para os repodata completos e também planejamos introduzir subconjuntos baseados em tempo, com apenas as versões mais recentes disponíveis em determinados momentos. A abertura incremental dos dados de índice disponíveis pode ser um conceito mais útil com o solucionador de retrocesso.

pip _pode_ até rebaixar as coisas.

Pode - para pip, o downgrade é apenas uma etapa de desinstalação e instalação, como a atualização. E faz downgrade quando vê uma restrição que foi solicitada a satisfazer.

O seguinte faz o downgrade das ferramentas de configuração - desde que seja a primeira coisa que o pip veja:

pip install "setuptools < 20.0"

onde é o melhor lugar para postar cenários "aqui está um caso extremo que eu pensei" para que eles não se percam?

https://github.com/pradyunsg/zazo/

Obrigado, vou manter isso em mente. Embora repensando, meu "caso patológico" não é realmente tão patológico. É principalmente apenas um caso extremo do fato de que, para conhecer os metadados de dependência de um pacote, você precisa baixar e descompactar o pacote e, em certos casos, isso pode desencadear o download de muitos pacotes apenas para rejeitá-los. Essa pode ser uma limitação importante do protocolo de "índice simples" que precisamos resolver, mas não é diretamente um problema de resolução.

Um exemplo de downgrade que o conda deve enfrentar, mas o pip não: usuários do anaconda ou miniconda com python 3 começam com python 3.7. Os usuários às vezes precisam instalar algo que está disponível apenas para python 3.6. O solver deve fazer o downgrade do python e de todos os outros pacotes não noarch. Este é um caso especial que talvez possa ser otimizado por ter um comportamento especial para alterações de versão do python, mas ilustra o ponto de como as alterações em um pacote podem exigir downgrades para outro. "Funcionou até agora" é pura sorte, não o que realmente funciona o tempo todo.

É também um caso em que você normalmente _não quer fazer o downgrade_. Como usuário, prefiro receber uma exceção informando que o pacote não está disponível e me deixar instalar explicitamente o 3.6 se for isso que eu quero.

Outro exemplo é a inconsistência entre os canais. Exemplo recente: estávamos no Python 3.7.3, então base obteve 3.7.4 . Eu não me importo com essas diferenças de versão, e a maioria dos usuários não. Um padrão "não fazer nada" seria muito melhor do que "hey .4 > .3 , vamos atualizar isso e depois mudar os canais de outros pacotes para base se for necessário (mesmo que isso os reduza)".

Fornecemos um fallback para os repodata completos e também planejamos introduzir subconjuntos baseados em tempo, com apenas as versões mais recentes disponíveis em determinados momentos

Isso soa como uma melhoria muito útil.

O problema com a mentalidade de "entrar lixo, sair lixo" é que, como mantenedores do solucionador, você está segurando o saco. A menos que você tenha uma heurística muito boa para o que é lixo, você é impotente.

Sim, é verdade. Eu acho que todo IDE, distribuição ou "interface comum para um monte de coisas" tem esse problema.

O seguinte rebaixa setuptools

Esse é um downgrade solicitado pelo usuário. Um indireto é o que eu quis dizer (por exemplo setuptools<20.0 em pyproject.toml`). Isso funcionaria bem, eu acho, mas é raro na prática.

O problema com a mentalidade de "entrar lixo, sair lixo" é que, como mantenedores do solucionador, você está segurando o saco.

100% de acordo. É algo que deve ser tratado com muito cuidado. Mas, por outro lado, tentar descobrir um comportamento sensato para qualquer lixo antigo não é viável - temos que traçar uma linha em algum lugar .

Apenas como um lembrete, esta discussão foi desencadeada pela questão de saber se precisaríamos manter o resolvedor existente indefinidamente. Não tenho certeza se algum desses pontos realmente afeta essa questão - o resolvedor existente não está certo, o melhor que você pode dizer é que é um comportamento quebrado com o qual as pessoas estão familiarizadas. Portanto, mantenho o que disse acima, não há motivo para manter o antigo resolvedor além de um período de transição inicial.

E, na verdade, para uma grande parte dos casos de uso, os resolvedores antigos e novos provavelmente darão os mesmos resultados de qualquer maneira.

"Certo" não importa para os usuários quando eles são prejudicados por suas alterações. Qualquer mudança de comportamento vai enfurecer alguns usuários. Dizer a eles que seu fluxo de trabalho está errado e que eles precisam mudar não foi uma estratégia muito eficaz para mim. Acho que você precisa tocar de ouvido. Eu certamente não gostaria de manter o resolvedor antigo para sempre, mas você provavelmente precisa dar às pessoas um caminho para mantê-lo - como torná-lo um plugin que outra pessoa adota e mantém, e as pessoas podem instalá-lo separadamente do pip.

tentar descobrir um comportamento sensato para qualquer lixo velho não é viável - temos que traçar uma linha em algum lugar.

Sim, mas o que é "lixo velho"? O que o distingue? E quanto ao lixo ruim vs lixo bom (tendo em mente que o mais novo nem sempre é melhor)? Meu conselho é gastar muito tempo fazendo com que o solver seja muito depurável. Facilite para os usuários (não apenas você como especialista) rastrear onde as coisas vão para o lado para facilitar a identificação de quando metadados ruins são o problema e quais são esses metadados ruins. Esta é uma habilidade que a maioria dos usuários atualmente não tem e, honestamente, a maioria dos usuários que eu vi não quer ter essa habilidade. Eu não acho que você (ou conda para esse assunto) jamais será capaz de ter uma heurística automática completamente precisa para ruim versus bom.

É também um caso em que você normalmente não deseja fazer o downgrade. Como usuário, prefiro receber uma exceção informando que o pacote não está disponível e me deixar instalar explicitamente o 3.6 se for isso que eu quero.

@rgommers , conda 4.7 movido para isso - exigindo a especificação explícita do python para alterar as versões secundárias. As pessoas têm odiado. Eu não tenho idéia de qual fração da população são os vocais, mas muitas pessoas realmente, realmente não gostam que eles costumavam ser capazes de conda install algo, e agora eles não podem. Eles não se importam muito com o motivo, e ficam apaziguados com a resposta, mas ainda temos que lidar com a hostilidade enquanto isso. Apenas mais um exemplo de

"Certo" não importa para os usuários quando eles são prejudicados por suas alterações.

Outro exemplo é a inconsistência entre os canais. Exemplo recente: estávamos no Python 3.7.3, então a base obteve o 3.7.4. Eu não me importo com essas diferenças de versão, e a maioria dos usuários não. Um padrão "não fazer nada" seria muito melhor do que "hey .4 > .3, vamos atualizar isso e depois mudar os canais de outros pacotes para base se for necessário (mesmo que isso os reduza)".

Este é um ponto muito mais complicado. Você está basicamente propondo critérios de otimização diferentes. Você pode ter visto as 11 etapas atuais em nossa postagem no blog em https://www.anaconda.com/understanding-and-improving-condas-performance/

Estes são extremamente complicados e não existe um ótimo global que satisfaça todas as situações. Dada a compatibilidade binária e os canais como ilhas dessa compatibilidade, a forte prioridade dos canais é bastante essencial. As compilações do Python não importam, você está certo, mas atualmente não há como expressar uma restrição de compatibilidade binária versus uma restrição de versão simples. Você precisaria disso para pensar em sua ideia de simplesmente deixar as coisas em paz. Caso contrário, você rapidamente estaria no inferno da incompatibilidade binária.

Olá, obrigado @msarahan por me mencionar nesta edição. Eu queria entrar na conversa mais cedo, mas não encontrei tempo.

Na verdade, experimentei bastante o uso de libsolv como um solucionador para especificações de conda. E funciona -- a diferença restante agora é que conda não se importa muito com o número de compilação, mas libsolv na forma como é codificado (e com o solucionador de retrocesso) sim. Mesmo ao usar o repodata completo, libsolv é muito rápido - eu diria que a velocidade do libsolv é rápida o suficiente para não ser irritante :)

Minha grande contribuição para a libsolv foi torná-la compatível com várias plataformas para que agora compile no Windows, OS X e Linux.

Eu defenderia totalmente o uso de libsolv para um novo resolvedor, mesmo que seja um blob de código compilado (pelo menos é rápido) e @mlschroe pode estar disponível para ajudar - ele ajudou muito com o suporte libsolv para o conda matchspec.

Por outro lado, não tenho certeza em que estágio está o desenvolvimento do resolvedor e se já é tarde demais agora e se o código compilado é aceitável ou não.

Você pode ter visto as 11 etapas atuais em nossa postagem no blog em https://www.anaconda.com/understanding-and-improving-condas-performance/

Na verdade eu fiz. Essa foi uma bela postagem no blog.

Você está basicamente propondo critérios de otimização diferentes.

Na verdade não. Eu acho que sua _"restrição de compatibilidade binária vs. uma restrição de versão simples"_ apenas aponta para algo que estou perdendo provavelmente. O que eu esperaria é que nenhum pacote deveria ter python >= 3.7.4 ou == 3.7.4 em seus metadados, é sempre == 3.7 (apenas verificado para scipy, meta.yaml diz python e conda_forge.yml diz max_py_ver: '37' - faz sentido). Portanto, introduzir um 3.7.4 não deve fazer nada - o resolvedor escolhendo 3.7.3 e não alterando mais nada é muito mais barato (e válido de acordo com suas 11 etapas) do que forçar 3.7.4 e acionar um cadeia de subidas/descidas.

Acho que esta parte está ficando fora do tópico para planos de lançamento de pip , tão feliz em levar isso para outro lugar.

Meu conselho é gastar muito tempo fazendo com que o solver seja muito depurável.

+1

Além disso: faça (mantenha) o mais reparável possível. Essa é a coisa legal com pip agora, se isso atrapalhar, geralmente pode-se fazer cd site-packages && rm -rf troublesome_package (possivelmente seguido de reinstalar com --no-deps ) e as coisas funcionam novamente. Os gostos de conda , apt e amigos são muito mais difíceis de reparar dessa maneira.

Você está basicamente propondo critérios de otimização diferentes.

Eu não acho que você esteja considerando o conceito de canais em seu pensamento o suficiente. Eu não sei o quão relevante é pip. Definitivamente muito menos do que conda, mas não tenho certeza se é totalmente irrelevante. As pessoas ainda podem coletar pacotes de mais de um índice por vez, certo?

Os pacotes não têm python >=3.7.4, nem ==3.7.4. O padrão na embalagem conda é ter um limite superior e inferior. Estes são geralmente determinados automaticamente pelo conda-build usando informações fornecidas pelo autor da receita sobre quantos lugares da versão considerar um intervalo compatível. Os pacotes têm restrições como >=3.7.2,<3.8.0a0, com a estranheza 0a0 sendo responsável pelo fato de que as versões de pré-lançamento estão abaixo das versões .0 e, portanto, corresponderiam a uma especificação <3.8.0 onde as pessoas não realmente espera.

Os pacotes também têm um canal associado a eles. Este canal é efetivamente parte da otimização da versão: https://github.com/conda/conda/blob/4.6.7/conda/resolve.py#L1074 - o canal é como uma super-versão, um lugar à frente do versão principal do pacote. Se uma solução não mudar o python, a especificação do python não pode ser python ==3.7 - isso é um intervalo e o canal afetará esse intervalo. Especificamente, ter uma especificação python ==3.7 e começar com uma instalação do canal defaults , então adicionar o canal conda-forge resultará em muita rotatividade, porque você introduziu novos pacotes python que são mais altos em "versão" (incluindo canal) e sua especificação python é permissiva a essa alteração.

O Conda 4.7 introduziu um "congelamento" muito mais agressivo de especificações, e tenho certeza de que esse comportamento é o que você procura. Isso é bem complicado, no entanto. Tudo se resume a apenas congelar coisas que não entram em conflito com suas especificações explícitas. Como você determina "conflito" é a parte difícil. Achamos que é melhor não congelar coisas que impeçam o solver de fornecer ao usuário os pacotes mais recentes que fazem parte do gráfico de dependências desse pacote. Vale a pena mencionar esse congelamento porque pode ser feito de acordo com as especificações do pip de uma maneira que o conda não pode. Eu acho que pode ser uma ótima otimização para um solucionador de retrocesso.

Além disso: faça (mantenha) o mais reparável possível. Essa é a coisa legal com o pip agora, se ele estragar, geralmente pode-se fazer cd site-packages && rm -rf troublesome_package (possivelmente seguido de reinstalação com --no-deps) e as coisas funcionam novamente. Os gostos de conda, apt e friends são muito mais difíceis de reparar dessa maneira.

Sim, isso é muito importante. Acho que o pip fez um bom trabalho ao vender coisas criteriosamente para que seja mais difícil de quebrar. Isso é muito sábio, e Conda está aprendendo com isso. Se você acabar usando qualquer código compilado, certifique-se de que ele esteja vinculado estaticamente ou de outra forma impossível para o carregamento dinâmico causar problemas.

(tangente libsolv: quando eu trabalhava para RH, peguei https://pypi.org/project/solv/ para fechar a brecha de segurança "pip install solv" no Fedora, já que o processo de compilação libsolv não gera uma sdist ou wheel archive no momento, quanto mais publicá-lo no PyPI. Feliz em conversar com qualquer pessoa que possa estar interessada em fazer essas ligações de biblioteca reais com uma cópia de pacote de libsolv, em vez do espaço reservado inócuo que é agora)

Em relação ao meu comentário de "exclusão", não quero dizer "voltar à lógica de instalação antiga", quero dizer "fornecer uma opção para pular a instalação de pacotes que causariam violações de restrições, em vez de falhar em toda a solicitação de instalação".

O Yum/DNF oferece isso por meio de sua opção --skip-broken (no DNF esse sinalizador é um alias para --setopt=strict=0 ), e acho que o resolvedor pip deve oferecer uma opção semelhante.

@ncoghlan Ah certo. Isso faz sentido.

opção de estilo "ignorar conflitos de versão neste pacote"

Eu já havia mencionado que faríamos isso, e é por isso que fiquei confuso com seu comentário.

Estamos na mesma página então. :)

@ncoghlan respondeu à minha linha do tempo proposta em distutils-sig e disse que parece razoável.

@pradyunsg - ansioso pela sua próxima atualização mensal!

Passei algum tempo dando uma olhada nisso novamente e arquivei #7317.

Acho que estamos quase lá com as abstrações - graças a muito trabalho na interação do índice do pip, resolução de dependência + separação de lógica de compilação e um monte de limpeza geral.

Acabei de fechar o nº 7317. Até onde eu sei, a resolução de dependência agora está desacoplada (o suficiente) da lógica de compilação de metadados. O refator da lógica de construção progrediu bem e não é mais um bloqueador para mais progressos agora.

Agora podemos começar a trabalhar na implementação das abstrações [resolvelib] no pip, tomando referência de [passa] e do resolvedor de poesia quando apropriado. :)

@pradyunsg Estou planejando extrair o resolvedor base (baseado no PubGrub) da base de código Poetry (consulte https://github.com/sdispater/poetry/tree/master/poetry/mixology). É principalmente dissociado do resto do código, mas ainda há referências a partes internas que preciso abstrair.

Se você estiver interessado em ajudar com isso, por favor me avise. A ideia é ter uma implementação autônoma do algoritmo PubGrub que pode ser usada por terceiros e será colocada em https://pypi.org/project/mixology/ que atualmente contém o código do resolvedor antigo.

@sdispater Definitivamente! Não sei se posso ajudar diretamente (restrições de tempo), mas seria incrível se você pudesse desacoplar a porta PubGrub do resto da poesia!

Uma das coisas que seria muito legal seria ter uma camada de abstração consistente, tal que pip, poesia e pipenv usem as mesmas abstrações. No momento, temos zazo (meu), mixology (poesia) e resolvelib (pipenv) -- todos definem algum tipo de camada de abstração e são ligeiramente diferentes, mas (ridiculamente!) semelhantes. Se você está aberto a isso, deixe-nos saber!

Para sua informação, nós ( @wolfv e a equipe @QuantStack em geral) respondemos ao RfP para o resolvedor de dependência de pip.

A abordagem proposta é adotar a biblioteca C libsolv e contribuir com o suporte para o formato de restrições de versão do pip para libsolv. Exporíamos a biblioteca C por meio de novas ligações do Python.

Libsolv é uma biblioteca reforçada por batalhas subjacente ao ecossistema RPM e, portanto, já usada em escala industrial.

  • Libsolv é distribuído sob a licença BSD-3-Clause.
  • Libsolv suporta vários pacotes e formatos de repositório, como rpm , deb ,
    haiku , conda , arch . É importante ressaltar que a variedade de formatos e formas de expressar
    restrições de dependência mostra que é um sistema plugável que deve ser capaz de
    para acomodar a sintaxe do pip para restrições em versões de dependência.
  • Usando libsolv em vez do solver do conda no wrapper do mamba fino, estávamos
    capaz de melhorar significativamente o desempenho do conda. (a lentidão do conda na resolução de pacotes com canais grandes foi nossa principal motivação para trabalhar no mamba).
  • Funciona multiplataforma no Windows, OS X e Linux. ( @wolfv fez a porta do Windows de libsolv)
  • Ele executa a solução completa de SAT para encontrar combinações de dependência ideais e, se não for bem-sucedida, retornará dicas acionáveis ​​para resolução de conflitos.

A abordagem proposta é adotar a biblioteca C libsolv

Seria necessário haver um fallback para plataformas que a libsolv não suporta (por exemplo, o suporte do AIX no pip está sendo trabalhado ativamente e você não mencionou o BSD). Portanto, libsolv como uma opção de desempenho, quando disponível, é plausível para mim, mas não estamos realmente em condições de usá-lo apenas . (Existe uma versão Python pura do libsolve, ou seja, algo que dê os mesmos resultados, apenas mais lento?)

Além disso, como o get-pip.py funcionaria? Teríamos que incluir binários para libsolv para todas as plataformas possíveis? Novamente, eu diria que não, usaríamos um fallback puro-python.

Não poder usar o código C externo tem sido um incômodo para o pip há muito tempo. Eu gostaria de ver uma boa solução para isso, mas (a) não tenho certeza de que exista uma (a não ser alguma forma de solução bootstrapper "mini-pip" que nos permita de-vendor completamente) e (b) é um trabalho grande o suficiente para que eu odeie o novo resolvedor depender dele.

Olá @pfmoore

Acho que o suporte de plataforma adicional não deve ser tão difícil de alcançar, pois libsolv é um código C bastante direto. Tenho certeza de que não existe uma versão Python pura de libsolv (o que entendo ser uma desvantagem, mas também não existe uma versão Python pura de Python, ou a lib padrão Python, então, em minha mente, não deve ser uma bloqueador).

Eu acho que para bootstrapping, pode-se ter um pip Python puro que usa o mecanismo atual para resolver, que instala a lib resolvedora necessária com base em libsolv. Por exemplo, pode-se fixar o pacote exato de ligações Python específicas de libsolv + pip e instalá-las a partir do boostrap-pip como você descreve. Parece totalmente factível para mim, mas você pode saber melhor o que estaria envolvido ...

Acho que o suporte adicional à plataforma não deve ser tão difícil de alcançar

Para ser claro, não tenho interesse nas plataformas mais de nicho, apenas acho que temos que ser muito claros se estamos impactando em quais plataformas o pip é suportado (que no momento é basicamente "qualquer coisa que possa executar o Python" ). Há também a questão de implantação de como enviamos uma extensão C (já que pip é atualmente enviado como uma roda "universal", e isso é importante para alguns casos de uso como get-pip.py )

Eu acho que para bootstrapping, pode-se ter um pip Python puro que usa o mecanismo atual para resolver

Eu argumentei acima, e vou repetir aqui, eu realmente não quero ter que manter dois resolvedores em pip indefinidamente.

Mas eu não quero ser muito negativo sobre a proposta - eu só queria sinalizar algumas das razões pelas quais não permitimos atualmente dependências em extensões C, caso você não esteja ciente delas.

A discussão provavelmente é mais adequada em outro lugar e pode ser totalmente redundante quando/se mais detalhes forem revelados, mas eu realmente quero deixar minhas perguntas agora.

Do meu entendimento, libsolv usa um solucionador totalmente SAT para resolução de dependência e requer o carregamento de informações de dependência antes de começar a resolver. Mas o PyPI a partir de agora armazena metadados de dependência por pacote. Mesmo se você ignorar a natureza dependente do tempo de execução de setup.py , seria difícil buscar eficientemente as informações necessárias para o solucionador SAT.

Como você planeja lidar com esse problema? Você planeja implementar a infraestrutura (e propor especificações adicionais para implementações de repositórios de terceiros) para gerar arquivos .solv quando os pacotes forem carregados? Ou você tem algumas estratégias na manga, gerando dados solucionáveis ​​apropriados à medida que o solucionador avança, e talvez implemente alguns retrocessos à medida que novos dados de dependência chegam?

Não tenho conhecimento de nenhum trabalho existente a esse respeito, e a maioria dos recursos que posso encontrar sugere que o cenário de empacotamento do Python precisa de algo mais/mais do que um solucionador SAT direto. Então, estou muito interessado em quaisquer possibilidades aqui.

Esses arquivos .solv são apenas para armazenamento em cache, a libsolv não precisa deles para resolvê-los. Mas concordo que a natureza dinâmica das dependências do PyPI dificulta o uso de um solucionador SAT.

(Consulte https://docs.google.com/document/d/1x_VrNtXCup75qA3glDd2fQOB2TakldwjKZ6pXaAjAfg/edit para obter mais informações)

(Observe que um solucionador SAT é apenas um solucionador de retrocesso que também faz aprendizado de cláusula se ocorrer um conflito, então acho que é possível usar um solucionador SAT para PyPI. Mas precisa ser um solucionador que permita adicionar cláusulas dinamicamente durante a resolução.)

Os solucionadores por satélite têm alguns recursos significativos atualmente, incluindo adições dinâmicas, reinicializações, retrocessos, reinicializações aleatórias etc. Mas acho que os desafios aqui serão parcialmente técnicos relacionados à necessidade de oferecer suporte a plataformas onde não há garantia de que você possa construir um solucionador baseado em C.

Estou no aeroporto agora, então não posso responder aos pontos que foram levantados agora, mas... tenho certeza de que não devemos discutir as escolhas técnicas/compensações aqui - isso é mais abrangente para como comunicamos e gerenciamos o lançamento versus o que implementamos. :)

Eu arquivei # 7406 para uma discussão mais aprofundada sobre as compensações técnicas - @sdispater , @techalchemy , @uranusjr , @wolfv Eu apreciaria se pudéssemos ter uma discussão mais aprofundada sobre as várias opções para o design do resolvedor.

Para definir as expectativas com antecedência, estarei viajando pelas próximas 2 semanas e espero poder acompanhar toda a discussão em ~ 9 de dezembro.

Atualização de status: o PSF conseguiu algum financiamento do Mozilla Open Source Support e da Chan Zuckerberg Initiative para contratar empreiteiros para trabalhar no resolvedor de pip e problemas relacionados à experiência do usuário . Você pode ver nosso roteiro (que eu preciso aprimorar) e postagens de blog e fórum e lista de discussão e notas de reuniões recentes para se manter informado. Estarei postando algo sobre isso em breve no distutils-sig e no fórum de empacotamento na instância Discourse do Python .

Nosso objetivo é ter o recurso de resolvedor do pip preparado para lançamento no pip 20.2 em julho. (De acordo com a cadência de lançamento trimestral para pip, dificuldades imprevistas podem atrasar até 20,3 no próximo trimestre.)

@uranusjr :

Do meu entendimento, libsolv usa um solucionador totalmente SAT para resolução de dependência e requer o carregamento de informações de dependência antes de começar a resolver. Mas o PyPI a partir de agora armazena metadados de dependência por pacote. Mesmo se você ignorar a natureza dependente do tempo de execução do setup.py, seria difícil buscar com eficiência as informações necessárias para o solucionador SAT.

O comando prototype pip resolve em #7819 usa duas técnicas para obter essas informações de maneira eficiente (consulte esse problema para obter detalhes):

  1. > Extrair o conteúdo do arquivo METADATA de uma url para um wheel sem realmente baixar o wheel.
  2. > Armazenando em cache o resultado de cada chamada self._resolve_one() em um arquivo json persistente.

A técnica usada para (1) é capaz de converter uma URL de roda em uma lista de strings de requisitos das quais ela depende muito rapidamente, enquanto baixa apenas alguns KB do próprio conteúdo da roda. O protótipo em #7819 garante que req.populate_link() seja chamado em cada um dos requisitos dependentes retornados por self._resolve_one() e armazena o mapeamento de (==Requirement, url) -> [list of (==Requirement, url) non-transitive dependencies] em um arquivo de cache json persistente. (1) obtém novas informações rapidamente, (2) torna as informações antigas mais rápidas de serem consultadas.

Embora eu ainda não esteja familiarizado com a libsolv, acredito que as entradas desse mapeamento da URL do requisito para as dependências e suas URLs podem ser exatamente a entrada atômica exigida por um solucionador SAT. Conforme demonstrado em #7189, o arquivo de cache de dependência json persistente fez com que as invocações pip resolve se tornassem completas sem operações após a primeira execução, levando 800-900ms na linha de comando a partir de então. Se as técnicas de (1) e (2) forem usadas, acredito que seja possível deixar um solucionador SAT executar até a conclusão toda vez que o pip for invocado sem esperar um tempo incrivelmente longo. Provavelmente não seria muito difícil tentar hackear libsolv em cima do protótipo em #7189 para tornar esse número mais concreto.

@techalchemy :

Os solucionadores por satélite têm alguns recursos significativos atualmente, incluindo adições dinâmicas, reinicializações, retrocessos, reinicializações aleatórias etc. Mas acho que os desafios aqui serão parcialmente técnicos relacionados à necessidade de oferecer suporte a plataformas onde não há garantia de que você possa construir um solucionador baseado em C.

pants costumava ter algum código que tornava mais fácil expor um compilador e um linker para um projeto baseado em setup.py (#6273), mas depois removemos isso (consulte #7016) para possibilitar a construção C/C++ em calças sem usar setup.py , o que era apropriado para nosso caso de uso dentro do Twitter, que só precisava construir uma biblioteca compartilhada para operadores personalizados do TensorFlow . Hospedamos binários pré-construídos para arquivos GCC e binutils vinculados estaticamente em nosso s3 para OSX e Linux (consulte https://github.com/pantsbuild/binaries/) para que os usuários de calças não precisem instalar nada além de python e um JDK usar calças em tudo.

Eu estaria interessado em ajudar a debater e/ou desenvolver qualquer tipo de ferramenta para construir C e C++ de forma portátil que possa permitir que o pip dependa de forma confiável do libsolv em todas as plataformas suportadas.

@cosmicexplorer

pants costumava ter algum código que tornava mais fácil expor um compilador e um linker para um projeto baseado em setup.py (#6273), mas depois removemos isso (consulte #7016) para possibilitar a construção de C/C++ in pants sem usar setup.py, que era apropriado para nosso caso de uso dentro do Twitter, que precisava apenas criar uma biblioteca compartilhada para operadores personalizados do TensorFlow. Hospedamos binários pré-construídos para arquivos GCC e binutils vinculados estaticamente em nosso s3 para OSX e Linux (consulte pantsbuild/binários) para que os usuários de calças não precisem instalar nada além de python e um JDK para usar calças.

O trabalho do compilador/linker é muito interessante! Há também uma discussão sobre desacoplar o compilador do setup.py (ou um backend de compilação PEP 517 em geral). Não está realmente relacionado ao resolvedor (pelo menos não diretamente), mas você pode estar interessado: https://discuss.python.org/t/how-do-we-get-out-of-the-business-of- condução-c-compiladores/2591

@cosmicexplorer

Eu estaria interessado em ajudar a debater e/ou desenvolver qualquer tipo de ferramenta para construir C e C++ de forma portátil que possa permitir que o pip dependa de forma confiável do libsolv em todas as plataformas suportadas.

Eu e @wolfv estamos analisando isso para construir partes da pilha do gerenciador de pacotes DNF em todas as principais plataformas suportadas para mamba e meu próprio trabalho pessoal com DNF. Neste momento, o libsolv agora pode ser construído para Windows, Linux, macOS, BSD, Haiku OS, e estou vagamente ciente de que ele está sendo colocado em vários sistemas UNIX como parte do uso de DNF no UNIX. Eu sei que o @dralley também disponibilizou rodas binárias solv para ~principais plataformas suportadas por PyPI~ Linux usando manylinux2014 em PyPI .

Agora temos o pip 20.1b1, uma versão beta que inclui uma versão muito inicial (alfa) do novo resolvedor (consulte #8099 para obter o contexto e uma pesquisa onde as pessoas podem dar feedback). Aqui está o anúncio . E https://github.com/pypa/pip/issues/7951#issuecomment -617851381 lista alguns lugares em que divulgamos o beta até agora.

O pip 20.1 já está disponível e inclui a versão alfa do resolvedor.

Estamos discutindo a publicação de outro lançamento do pip em maio, que inclui um alfa mais avançado do resolvedor. E estamos descobrindo quando lançar a versão beta do novo resolvedor e fazer um push "por favor, teste isso".

Tivemos atrasos enquanto estávamos descobrindo o #8371, como exibir melhor certas mensagens de erro e lidando com um monte de outras coisas complicadas; veja https://github.com/pypa/pip/projects/6 e https://github.com/pypa/pip/projects/5 para saber mais sobre nosso progresso. #8206 é a discussão do próximo beta, pip 20.2b2, que espero que possamos publicar até o final de junho.

Publiquei nosso relatório semestral no blog do PSF . Uma coisa importante a saber: no final deste mês estaremos lançando o pip 20.2 que terá uma versão beta do novo resolvedor de dependências (pip 20.1 tinha uma versão alfa) disponível através de um sinalizador opcional " --use-feature=2020-resolver ". Estaremos divulgando muito o pip 20.2 e pedindo a muitos usuários que testem o novo resolvedor.

De acordo com #8511, agora lançamos o pip 20.2 . Esta versão inclui a versão beta do resolvedor de dependência de próxima geração . Ele é significativamente mais rígido e consistente quando recebe instruções incompatíveis e reduz o suporte para certos tipos de arquivos de restrições, portanto, algumas soluções alternativas e fluxos de trabalho podem ser interrompidos. Por favor, teste- o com o sinalizador --use-feature=2020-resolver . Consulte nosso guia sobre como testar e migrar e como relatar problemas . O novo resolvedor de dependência está desativado por padrão porque ainda não está pronto para uso diário .

Planejamos fazer a próxima versão trimestral do pip, 20.3, em outubro de 2020. Estamos nos preparando para alterar o comportamento de resolução de dependência padrão e tornar o novo resolvedor o padrão no pip 20.3.

Por favor, espalhe a notícia apontando para esta postagem no blog - espalhe a notícia no Hacker News, Reddit, Twitter, Facebook, Dev.to, Telegram, respostas relevantes do Stack Overflow e seus Slacks e Discords favoritos. A maioria das pessoas que isso afetará não acompanha as notícias de desenvolvedores específicas do Python. Ajude-os a avisar antes de outubro e nos ajude a obter seus relatórios de bugs.

(Copiando minha nota de # 988.)

@zooba perguntou :

Você acha que devemos atualizar a versão do pip empacotada com o Python 3.9 neste estágio (para o primeiro RC)?

Da mesma forma, há necessidade de atualizar o Python 3.8 para sua próxima versão?

Minha suposição é que, sim, após o lançamento do bugfix no início da próxima semana, sim, mas @pfmoore @xavfernandez @cjerdonek @uranusjr @pradyunsg @dstufft o que você acha?

Desculpe, clique em postar muito cedo. Meu raciocínio é que agrupar o pip 20.2 tornará muito mais fácil para desenvolvedores experientes testarem o novo resolvedor de dependência facilmente enquanto testam as versões mais recentes do Python. Mas não sei quanto trabalho é atualizar essa versão em pacote ou com que frequência você deseja fazê-lo.

Mesmo aqui, seria bom incluir um 20.2.x em 3.9 para facilitar o acesso ao novo resolvedor.

O que você acha?

Você está certo, esse é o plano. :)

OK, respondido em python-dev -- sim, a versão do pip empacotada no Python 3.8 e 3.9 deve ser atualizada para 20.2.x.

Separadamente, sobre publicidade, anoto aqui alguns trabalhos em andamento:

Nas próximas 6-8 semanas, estarei pressionando para obter ampla publicidade para que os usuários tentem usar o novo pip. Suspeito que o problema não seja tanto "este pacote individual não será instalado"; serão conflitos inesperados entre pacotes específicos, talvez dependentes do ambiente e dos arquivos de restrições particulares. Estamos tentando obter algum feedback inicial por meio da pesquisa para que possamos corrigir bugs, configurar mais testes automatizados, etc., e para que esses pacotes upstream possam receber alertas e enviar pacotes corrigidos antes do pip 20.3 : o problema TensorFlow/numpy/scipy em https://github.com/pypa/pip/issues/8076#issuecomment-666493069 ).

Não importa quanto esforço coloquemos nisso, haverá usuários lidando com inconsistências que tropeçarão com o 20.3, e eles ficarão frustrados, confusos e feridos, e isso causará uma carga de suporte para nós e para todos os seus upstreams. Nosso objetivo é reduzir isso fazendo com que os usuários testem e chamando a atenção dos upstreams.

Portanto, pretendo entrar em contato e alavancar os grupos que prestam atenção aos seus próprios cantos específicos de domínio - os cientistas de dados, os professores, os artistas, os especialistas em DevOps e assim por diante.

Eu suponho que uma maneira de chamar a atenção deles é através dos pacotes específicos nos quais eles confiam.

Ontem eu examinei algumas listas de pacotes amplamente usados ​​e mandei um e-mail manualmente para algumas pessoas e criei problemas em alguns repositórios para sugerir que eles pedissem a seus usuários para testar com a versão beta do novo resolvedor, para começar a rolar e experimentar alguns redação/abordagens para obter mais publicidade e testes. Isso levou à confusão em pelo menos um caso - veja https://github.com/sqlalchemy/mako/issues/322#issuecomment-667546739 - e assim que o lançamento do bugfix do 20.2 for lançado, ficarei um pouco mais sistemática e mais clara sobre

  • por que estamos entrando em contato
  • quem escolhemos entrar em contato/quando (o link para uma estratégia de mídia pública ajudará)
  • por que não podemos usar testes automatizados para encontrar esses problemas por conta própria

Recebemos um pouco de atenção no Twitter ( 1 , 2 ) e Reddit (alguém quer responder a esta pergunta sobre o financiamento do PyPA ?).

@zooba escreveu (sobre o pacote):

Obrigado. Parece que podemos fazer isso ainda esta semana e fazer a próxima rodada de lançamentos. Por favor, avise-nos o mais rápido possível se surgir algo que você não gostaria de ser liberado.

Acho que o --use-feature=2020-resolver para mim geralmente corrige mais problemas do que causa.

é tarde demais para sugerir um lançamento inicial por meio de:

pseudocódigo 20.3 proposto

try:
    _2020_resolver()
except:
    legacy_resolver()

o que significaria que pelo menos para meus projetos todos eles passariam sem modificação

depois disso, uma vez que o resolvedor legado está desabilitado por padrão, posso ter alguns projetos que funcionam no resolvedor 2020 e alguns não no resolvedor legado, gostaria de poder definir um sinalizador para habilitar um fallback:

pseudocódigo 20.4 proposto

try:
    _2020_resolver()
except:
    if not use_legacy_resolver:
        raise
    legacy_resolver()

Estamos planejando lançar o 20.3 ainda este mês. Prevemos que isso exigirá muito suporte ao usuário, pois usuários confusos nos fazem perguntas.

@di se ofereceu para ajudar a reunir alguns voluntários para ajudar na primeira resposta. Esses voluntários responderão a perguntas e ajudarão com a carga de suporte ao usuário assim que o novo pip for lançado - no Twitter, StackOverflow e GitHub - e encaminharão bugs reais para a atenção da equipe de mantenedores/colaboradores.

Dustin, acho que você tem um plano aproximado de como isso funcionaria - você se importaria de postar isso aqui e então obterei algum consenso de outros mantenedores de pip? Obrigado profundamente.

Aqui está o meu plano aproximado:

  • Inicie uma discussão em discuss.python.org solicitando suporte
  • Direcione as pessoas para um canal do Slack que possa servir como um canal de comunicação entre todos
  • Inicie um documento descrevendo algumas perguntas frequentes e nossas respostas
  • Incluir uma árvore de decisão para um novo problema -> problema triado
  • Compartilhe isso com o canal assim que tivermos uma data de lançamento conhecida
  • Tente agendar os voluntários para ficarem online e fazer a triagem nos dias seguintes ao lançamento

Obrigado, @di. Estamos esperando até amanhã para obter OK de outros mantenedores. E pretendemos lançar o pip 20.3 na quarta ou quinta-feira, 28 ou 29 de outubro.

@di vejo que seu plano foi aprovado. Por favor, vá em frente!

Caso eu não esteja disponível quando pudermos publicar 20.3 (que esperamos que seja na próxima semana), aqui está um plano de publicidade .

Como discuti em um comentário em outro lugar , decidimos adiar o lançamento um pouco, por causa de alguns problemas de CI e por causa de alguns fatores externos.

Na reunião da equipe de hoje , concordamos que a versão 20.3 provavelmente será amanhã ou sexta-feira. Você pode seguir #8936 para saber mais.


Eu não fiz tanta divulgação por pacote quanto sugeri em um comentário anterior. Mas isso não significa que não fizemos divulgação. Algumas das ações de divulgação que fizemos (algumas das quais estão catalogadas em #8511 ou nesta página wiki ):

Nos últimos meses, recebemos um fluxo constante de novos problemas de pessoas testando o novo resolvedor em 20.2 e 20.3 beta, pip 20.3b1 . Esses relatórios nos ajudaram a melhorar o resolvedor, corrigindo bugs e melhorando o UX de sua saída. Também melhoramos substancialmente o guia do usuário "o que está mudando" , parcialmente em resposta ao feedback beta.

Aqui está o meu plano aproximado:

* Start a discussion on discuss.python.org asking for support

* Direct folks to a Slack channel that could serve as a communication channel between everyone

* Start a document outlining some FAQ and our responses

* Include a decision tree for new issue -> triaged issue

* Share this with the channel once we have a known release date

* Try and roughly schedule volunteers to be online & triaging in the days following the release

@di Reconheço que a constante incerteza e atrasos provavelmente impediram você de fazer o agendamento. A nova data de lançamento é amanhã, segunda-feira, 30 de novembro. Se agora você tem um tópico de discussão e uma árvore de decisão para compartilhar, vá em frente e compartilhe-os!

O pip 20.3 foi lançado e possui o novo resolvedor por padrão! Aqui está o anúncio de lançamento no blog do PSF: https://blog.python.org/2020/11/pip-20-3-release-new-resolver.html

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