Requisitos de referência.txt para o arquivo install_requires kwarg no arquivo setuptools setup.py


279

Eu tenho um requirements.txtarquivo que estou usando com o Travis-CI. Parece bobagem duplicar os requisitos em ambos requirements.txte setup.py, portanto, eu esperava passar um identificador de arquivo para o install_requireskwarg setuptools.setup.

Isso é possível? Se sim, como devo proceder?

Aqui está o meu requirements.txtarquivo:

guessit>=0.5.2
tvdb_api>=1.8.2
hachoir-metadata>=1.3.3
hachoir-core>=1.3.3
hachoir-parser>=1.3.4

4
install_requiresé usado para declarar dependências nos pacotes que são necessários para o pacote funcionar e são usados ​​pelo desenvolvedor do pacote, enquanto requirements.txté usado para automatizar a instalação de ambientes, o que permite a instalação de software extra e a fixação da versão e são usados ​​pelos administradores de sistemas que implementam o pacote. pacote. Seu papel e público-alvo diferem significativamente; portanto, tentar combiná-los como os desejos do OP é um verdadeiro erro de design.
Zart 02/07

7
Meus 2 centavos. Não use requirements.txt em seu setup.py. Os efeitos são diferentes, ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombrédanne

3
Eu vejo muitas respostas complicadas. O que há de errado com a velha planície [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider

Não é recomendável fazer isso. Mas se realmente necessário, então é simples: o setuptools já possui tudo o necessáriopkg_resources.parse_requirements()
sinoroc 29/01

Respostas:


246

Você pode inverter e listar as dependências setup.pye ter um único caractere - um ponto .- em seu requirements.txtlugar.


Como alternativa, mesmo que não seja recomendado, ainda é possível analisar o requirements.txtarquivo (se não referir nenhum requisito externo pela URL) com o seguinte hack (testado com pip 9.0.1):

install_reqs = parse_requirements('requirements.txt', session='hack')

Isso não filtra os marcadores de ambiente .


Nas versões antigas do pip, mais especificamente anteriores à 6.0 , há uma API pública que pode ser usada para isso. Um arquivo de requisitos pode conter comments ( #) e pode incluir alguns outros arquivos ( --requirementou -r). Portanto, se você realmente deseja analisar um, requirements.txtpode usar o analisador de pip:

from pip.req import parse_requirements

# parse_requirements() returns generator of pip.req.InstallRequirement objects
install_reqs = parse_requirements(<requirements_path>)

# reqs is a list of requirement
# e.g. ['django==1.5.1', 'mezzanine==1.4.6']
reqs = [str(ir.req) for ir in install_reqs]

setup(
    ...
    install_requires=reqs
)

26
E se o usuário não tiver o pip instalado? Ka-boom?
Gringo Suave

82
@GringoSuave Se o usuário não possui o pip instalado, ele precisa instalá-lo primeiro.
guettli

7
Você também precisa fornecer os URLs no seu arquivo de requisitos, caso existam linhas -e ou -f ("editable" git repo) apontando para pacotes não-pypi. Use isto:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
hobs

91
Você realmente não quer fazer isso. Falando como mantenedor de pip, o pip não suporta ser chamado como uma API como esta. De fato, o pip 1.6 (próxima versão no momento) move essa função.
Donald Stufft 26/03

26
Essa não deve mais ser a resposta aceita, se é que deveria. Está descaradamente quebrado. Mesmo quando funcionou, é flagrantemente desnecessário. Como o pippadrão é analisar as dependências setup.pyna ausência de requirements.txt, a resposta simples observada astutamente por Tobu abaixo é listar todas as dependências setup.pye remover requirements.txt. Para aplicativos que exigem os dois, basta reduzir a lista de dependências requirements.txtapenas para o .caractere. Feito.
Cecil Curry

194

Em face disso, parece que requirements.txte setup.pysão duplicatas tolas, mas é importante entender que enquanto a forma é semelhante, a função pretendida é muito diferente.

O objetivo de um autor de pacote, ao especificar dependências, é dizer "onde quer que você instale este pacote, esses são os outros pacotes necessários para que este pacote funcione".

Por outro lado, o autor da implantação (que pode ser a mesma pessoa em um momento diferente) tem um trabalho diferente, pois diz "aqui está a lista de pacotes que reunimos e testamos e que agora preciso instalar".

O autor do pacote escreve para uma ampla variedade de cenários, porque eles estão enviando seu trabalho para serem usados ​​de maneiras que talvez não conheçam e não têm como saber quais pacotes serão instalados junto com o pacote. Para ser um bom vizinho e evitar conflitos de versão de dependência com outros pacotes, eles precisam especificar o maior número de versões de dependência possível. Isto é o que install_requiresnos setup.pyfaz.

O autor da implantação grava para um objetivo muito diferente e muito específico: uma única instância de um aplicativo ou serviço instalado, instalado em um computador específico. Para controlar com precisão uma implantação e garantir que os pacotes corretos sejam testados e implantados, o autor da implantação deve especificar a versão exata e o local de origem de cada pacote a ser instalado, incluindo dependências e dependências. Com essa especificação, uma implantação pode ser aplicada repetidamente em várias máquinas ou testada em uma máquina de teste, e o autor da implantação pode ter certeza de que os mesmos pacotes são implantados sempre. Isto é o que a requirements.txtfaz.

Então você pode ver que, embora ambos pareçam uma grande lista de pacotes e versões, essas duas coisas têm tarefas muito diferentes. E é definitivamente fácil misturar tudo e errar! Mas a maneira correta de pensar sobre isso é que requirements.txté uma "resposta" à "pergunta" feita pelos requisitos em todos os vários setup.pyarquivos de pacotes. Em vez de escrevê-lo manualmente, geralmente é gerado dizendo ao pip para olhar para todos os setup.pyarquivos em um conjunto de pacotes desejados, encontrar um conjunto de pacotes que julgue adequado a todos os requisitos e, depois de instalados, "congelar" "essa lista de pacotes em um arquivo de texto (é daí que o pip freezenome vem).

Então, a dica:

  • setup.pydeve declarar as versões de dependência mais baixas possíveis que ainda sejam viáveis. Seu trabalho é dizer com o que um pacote específico pode funcionar.
  • requirements.txté um manifesto de implantação que define todo um trabalho de instalação e não deve ser considerado vinculado a nenhum pacote. Seu trabalho é declarar uma lista exaustiva de todos os pacotes necessários para fazer uma implantação funcionar.
  • Como essas duas coisas têm conteúdo e motivos de existência tão diferentes, não é possível simplesmente copiar uma na outra.

Referências:


10
Esta é uma das melhores explicações que me permitem colocar alguma ordem nessa bagunça chamada instalação de pacotes! :)
Kounavi 26/11/2015

6
Ainda não está claro para mim por que um desenvolvedor manteria uma versão controlada requirements.txtjuntamente com a fonte do pacote que contém os requisitos concretos / congelados para instalação ou teste. Certamente setup.pypode ser usado para esse fim dentro do próprio projeto? Só posso imaginar usar esse arquivo para ferramentas usadas para dar suporte ao gerenciamento do projeto (por exemplo, refatoração, lançamento de lançamentos etc.).
Sam Brightman

2
@samBrightman Concordo inteiramente, não acho que os pacotes de bibliotecas ou aplicativos devam comprometer o arquivo requirements.txt para o repositório com o código. Eu acho que deveria ser um artefato gerado durante o teste de construção e, em seguida, usado para documentar um manifesto de construção e, finalmente, gerar um artefato de implantação.
27616 Jonathan

6
Então você está dizendo que requirements.txthá mais documentação para o estado do mundo que produziu uma determinada compilação, mesmo que ela não seja normalmente usada no próprio processo de compilação? Isso faz sentido. No entanto, parece que vários sistemas dependem de duplicação: O Travis instala alguns pacotes (antigos) padrão no seu virtualenv e diz para usá-lo requirements.txt. Se eu perguntar como garantir que as dependências sejam setup.pyusadas o mais tardar , as pessoas insistem que eu deveria usar requirements.txt.
Sam Brightman

2
O melhor conselho que você pode obter disso é encontrar um modelo que funcione para você, documentá-lo bem e garantir que todos com quem você trabalha o entendam. Pense por que você está fazendo cada bit e se realmente faz sentido para o seu caso de uso. E tente ficar o mais bem-lido possível sobre o estado atual de criação, empacotamento e publicação no Python, caso as coisas melhorem. Mas não prenda a respiração.
Jonathan Hanson

89

Não é possível lidar com um arquivo. O install_requiresargumento pode ser apenas uma string ou uma lista de strings .

Obviamente, você pode ler seu arquivo no script de instalação e passá-lo como uma lista de strings para install_requires.

import os
from setuptools import setup

with open('requirements.txt') as f:
    required = f.read().splitlines()

setup(...
install_requires=required,
...)

5
Embora útil, isso altera a especificação de requisitos de declarativa para imperativa. Isso torna impossível para algumas ferramentas descobrir quais são seus requisitos. Por exemplo, o PyCharm oferece instalação automática de todos os requisitos especificados em install_requires. No entanto, não funcionará se você não usar sintaxe declarativa.
Piotr Dobrogost

55
@PiotrDobrogost Talvez o desenvolvedor do PyCharm deva consertar o programa. setup.pyé um programa que deve ser executado, não um arquivo de dados que deve ser analisado. Isso não piora a resposta.
Fredrick Brennan

5
Estou apenas apontando possíveis problemas; esta resposta está perfeitamente bem. Não é apenas o PyCharm que tem problemas com a informação "oculta" por trás do código. Esse é um problema universal e, portanto, há uma mudança geral em direção à especificação declarativa de metadados no pacote Python.
Piotr Dobrogost

32
Funciona bem desde que você coloque include requirements.txtno seu MANIFEST.inou você não poderá instalar sua biblioteca a partir de uma distribuição de origem.
Pankrat

4
Eu sei que essa é uma pergunta antiga, mas você pode pelo menos hoje em dia configurar o PyCharm para analisar um arquivo de requisitos em Preferências-> Ferramentas-> Ferramentas integradas do Python-> arquivo de requisitos do pacote
lekksi

64

Os arquivos de requisitos usam um formato de pip expandido, que só é útil se você precisar complementá-lo setup.pycom restrições mais fortes, por exemplo, especificando os URLs exatos dos quais algumas dependências devem provir ou a saída de pip freezepara congelar todo o pacote definido como conhecido. versões. Se você não precisar de restrições extras, use apenas a setup.py. Se você sentir que realmente precisa enviar um de requirements.txtqualquer maneira, crie uma única linha:

.

Será válido e se referirá exatamente ao conteúdo do setup.pyque está no mesmo diretório.


9
Mas, nesse caso, ele também tentaria instalar o meu aplicativo. E se eu não precisar e quiser instalar install_requires?
Ffeast

2
Para elaborar o que o @ffeast está perguntando, se os requisitos existem apenas no setup.py, existe uma maneira de instalar os requisitos (equivalente a pip install -r requirements.txt ) sem instalar o próprio pacote?
haridsv

1
@ffeast @haridsv -e .deve ser suficiente. Verifique esta página: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter ele ainda tenta instalar o aplicativo em si. Este não é o que queremos
ffeast

38

Embora não seja uma resposta exata para a pergunta, recomendo a publicação no blog de Donald Stufft em https://caremad.io/2013/07/setup-vs-requirement/ para uma boa compreensão desse problema. Eu tenho usado com muito sucesso.

Em suma, requirements.txtnão é uma setup.pyalternativa, mas um complemento de implantação. Mantenha uma abstração apropriada das dependências do pacote em setup.py. Defina um requirements.txtou mais deles para buscar versões específicas de dependências de pacotes para desenvolvimento, teste ou produção.

Por exemplo, com pacotes incluídos no repositório em deps/:

# fetch specific dependencies
--no-index
--find-links deps/

# install package
# NOTE: -e . for editable mode
.

O pip executa os pacotes setup.pye instala as versões específicas das dependências declaradas em install_requires. Não há duplicidade e o objetivo de ambos os artefatos é preservado.


7
Isso não funciona quando você deseja fornecer um pacote para outras pessoas instalarem via pip install my-package. Se as dependências do my-package não estiverem listadas em my-package / setup.py, elas não serão instaladas por pip install my-package. Não consegui determinar como fornecer um pacote para outras pessoas que inclua dependências sem explicitamente explicá-las em setup.py. Gostaria de saber se alguém descobriu como mantê-lo seco enquanto permite que outros instalem dependências my-package + sem baixar o arquivo de requisitos e chamar manualmente pip install -r my-package/requirements.txt.
Malina16

2
@Malina O pacote aqui é perfeitamente instalável sem requirements.txt. Esse é o ponto. Atualizada a pergunta para tornar as coisas mais claras. Também foi atualizado o link obsoleto da postagem do blog.
Famosasfamily6

portanto, ao executar o setup.py, ele chamará requirements.txt para versões específicas dos arquivos listados em stup.py?
Dtracers

É o contrário de @dtracers. requirements.txt aponta para o próprio pacote, onde as dependências do setup.py podem ser coletadas. Então, ao instalar usando requisitos, ele funciona e quando instalar através de pip, ele funciona muito - em ambos os casos usando dependecies de setup.py, mas também permitindo instalar mais coisas ao usar requirements.txt
smido

20

O uso parse_requirementsé problemático porque a API do pip não é documentada e suportada publicamente. No pip 1.6, essa função está realmente em movimento, portanto, os usos existentes dela provavelmente serão interrompidos.

Uma maneira mais confiável de eliminar a duplicação entre setup.pye requirements.txté especificar suas dependências setup.pye colocá -e .- las no requirements.txtarquivo. Algumas informações de um dos pipdesenvolvedores sobre por que esse é o melhor caminho estão disponíveis aqui: https://caremad.io/blog/setup-vs-requirement/


@ Tommy Experimente o seguinte: caremad.io/2013/07/setup-vs-requirement Este é o mesmo link postado em outra resposta.
Amit 11/16

18

A maioria das outras respostas acima não funciona com a versão atual da API do pip. Aqui está a maneira correta * de fazer isso com a versão atual do pip (6.0.8 no momento da redação, também trabalhada no 7.1.2. Você pode verificar sua versão com o pip -V).

from pip.req import parse_requirements
from pip.download import PipSession

install_reqs = parse_requirements(<requirements_path>, session=PipSession())

reqs = [str(ir.req) for ir in install_reqs]

setup(
    ...
    install_requires=reqs
    ....
)

* Correto, pois é a maneira de usar parse_requirements com o pip atual. Provavelmente ainda não é a melhor maneira de fazê-lo, pois, como os pôsteres acima disseram, o pip realmente não mantém uma API.


14

Instale o pacote atual no Travis. Isso evita o uso de um requirements.txtarquivo. Por exemplo:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py

2
Essa é de longe a melhor combinação de "correto" e "prático". Eu acrescentaria que se, após a aprovação dos testes, você conseguir que o Travis gere um requirements.txt pip freezee exporte esse arquivo para algum lugar como um artefato (como S3 ou algo assim), você terá uma ótima maneira de instalar repetidamente exatamente o que você testado.
Jonathan Hanson

4

from pip.req import parse_requirements não funcionou para mim e acho que é para as linhas em branco nos meus requisitos.txt, mas essa função funciona

def parse_requirements(requirements):
    with open(requirements) as f:
        return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#')]

reqs = parse_requirements(<requirements_path>)

setup(
    ...
    install_requires=reqs,
    ...
)

4

Se você não deseja forçar seus usuários a instalar o pip, você pode emular seu comportamento com isso:

import sys

from os import path as p

try:
    from setuptools import setup, find_packages
except ImportError:
    from distutils.core import setup, find_packages


def read(filename, parent=None):
    parent = (parent or __file__)

    try:
        with open(p.join(p.dirname(parent), filename)) as f:
            return f.read()
    except IOError:
        return ''


def parse_requirements(filename, parent=None):
    parent = (parent or __file__)
    filepath = p.join(p.dirname(parent), filename)
    content = read(filename, parent)

    for line_number, line in enumerate(content.splitlines(), 1):
        candidate = line.strip()

        if candidate.startswith('-r'):
            for item in parse_requirements(candidate[2:].strip(), filepath):
                yield item
        else:
            yield candidate

setup(
...
    install_requires=list(parse_requirements('requirements.txt'))
)

4

A seguinte interface ficou obsoleta no pip 10:

from pip.req import parse_requirements
from pip.download import PipSession

Então, eu mudei apenas para a análise de texto simples:

with open('requirements.txt', 'r') as f:
    install_reqs = [
        s for s in [
            line.split('#', 1)[0].strip(' \t\n') for line in f
        ] if s != ''
    ]

Essa abordagem simples funciona mais de 90% do tempo. Para aqueles que usam Python 3.6+, escrevi uma resposta que é uma pathlibvariação dela.
Acumenus 15/01/19

3

Essa abordagem simples lê o arquivo de requisitos em setup.py. É uma variação da resposta de Dmitiry S .. Esta resposta é compatível apenas com Python 3.6+.

Por DS , requirements.txtpode documentar requisitos concretos com números de versão específicos, enquanto setup.pypode documentar requisitos abstratos com intervalos de versões soltas.

Abaixo está um trecho do meu setup.py.

import distutils.text_file
from pathlib import Path
from typing import List

def _parse_requirements(filename: str) -> List[str]:
    """Return requirements from requirements file."""
    # Ref: https://stackoverflow.com/a/42033122/
    return distutils.text_file.TextFile(filename=str(Path(__file__).with_name(filename))).readlines()

setup(...
      install_requires=_parse_requirements('requirements.txt'),
   ...)

Observe que distutils.text_file.TextFileisso removerá os comentários. Além disso, pela minha experiência, você aparentemente não precisa executar nenhuma etapa especial para agrupar o arquivo de requisitos.


2

Cuidado com o parse_requirementscomportamento!

Observe que pip.req.parse_requirementsos sublinhados serão alterados para traços. Isso me enfureceu por alguns dias antes que eu o descobrisse. Exemplo demonstrando:

from pip.req import parse_requirements  # tested with v.1.4.1

reqs = '''
example_with_underscores
example-with-dashes
'''

with open('requirements.txt', 'w') as f:
    f.write(reqs)

req_deps = parse_requirements('requirements.txt')
result = [str(ir.req) for ir in req_deps if ir.req is not None]
print result

produz

['example-with-underscores', 'example-with-dashes']

1
Use unsafe_name para obter a versão dos sublinhados:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds

5
Como apontado em outro lugar, o PIP é um aplicativo, não uma biblioteca. Ele não possui uma API acordada publicamente e a importação para o seu código não é um caso de uso suportado. Não é de surpreender que tenha um comportamento inesperado; suas funções internas nunca foram destinadas a serem usadas dessa maneira.
Jonathan Hanson

1

Eu criei uma função reutilizável para isso. Na verdade, ele analisa um diretório inteiro de arquivos de requisitos e os define como extras_require.

Últimas sempre disponíveis aqui: https://gist.github.com/akatrevorjay/293c26fefa24a7b812f5

import glob
import itertools
import os

# This is getting ridiculous
try:
    from pip._internal.req import parse_requirements
    from pip._internal.network.session import PipSession
except ImportError:
    try:
        from pip._internal.req import parse_requirements
        from pip._internal.download import PipSession
    except ImportError:
        from pip.req import parse_requirements
        from pip.download import PipSession


def setup_requirements(
        patterns=[
            'requirements.txt', 'requirements/*.txt', 'requirements/*.pip'
        ],
        combine=True):
    """
    Parse a glob of requirements and return a dictionary of setup() options.
    Create a dictionary that holds your options to setup() and update it using this.
    Pass that as kwargs into setup(), viola

    Any files that are not a standard option name (ie install, tests, setup) are added to extras_require with their
    basename minus ext. An extra key is added to extras_require: 'all', that contains all distinct reqs combined.

    Keep in mind all literally contains `all` packages in your extras.
    This means if you have conflicting packages across your extras, then you're going to have a bad time.
    (don't use all in these cases.)

    If you're running this for a Docker build, set `combine=True`.
    This will set `install_requires` to all distinct reqs combined.

    Example:

    >>> import setuptools
    >>> _conf = dict(
    ...     name='mainline',
    ...     version='0.0.1',
    ...     description='Mainline',
    ...     author='Trevor Joynson <github@trevor.joynson,io>',
    ...     url='https://trevor.joynson.io',
    ...     namespace_packages=['mainline'],
    ...     packages=setuptools.find_packages(),
    ...     zip_safe=False,
    ...     include_package_data=True,
    ... )
    >>> _conf.update(setup_requirements())
    >>> # setuptools.setup(**_conf)

    :param str pattern: Glob pattern to find requirements files
    :param bool combine: Set True to set install_requires to extras_require['all']
    :return dict: Dictionary of parsed setup() options
    """
    session = PipSession()

    # Handle setuptools insanity
    key_map = {
        'requirements': 'install_requires',
        'install': 'install_requires',
        'tests': 'tests_require',
        'setup': 'setup_requires',
    }
    ret = {v: set() for v in key_map.values()}
    extras = ret['extras_require'] = {}
    all_reqs = set()

    files = [glob.glob(pat) for pat in patterns]
    files = itertools.chain(*files)

    for full_fn in files:
        # Parse
        reqs = {
            str(r.req)
            for r in parse_requirements(full_fn, session=session)
            # Must match env marker, eg:
            #   yarl ; python_version >= '3.0'
            if r.match_markers()
        }
        all_reqs.update(reqs)

        # Add in the right section
        fn = os.path.basename(full_fn)
        barefn, _ = os.path.splitext(fn)
        key = key_map.get(barefn)

        if key:
            ret[key].update(reqs)
            extras[key] = reqs

        extras[barefn] = reqs

    if 'all' not in extras:
        extras['all'] = list(all_reqs)

    if combine:
        extras['install'] = ret['install_requires']
        ret['install_requires'] = list(all_reqs)

    def _listify(dikt):
        ret = {}

        for k, v in dikt.items():
            if isinstance(v, set):
                v = list(v)
            elif isinstance(v, dict):
                v = _listify(v)
            ret[k] = v

        return ret

    ret = _listify(ret)

    return ret


__all__ = ['setup_requirements']

if __name__ == '__main__':
    reqs = setup_requirements()
    print(reqs)

muito agradável! ainda lida com requisitos recursiva com mais recente pip :)
amohr

@amohr Obrigado! Eu o atualizei recentemente para um pip ainda mais tarde. Não sei por que eles estão agindo dessa maneira, movendo as coisas para pip._internal.. Se você não fornece uma API externa utilizável, não deve quebrar tudo isso que estão usando tudo o que você fornece.
trevorj

0

Outra solução possível ...

def gather_requirements(top_path=None):
    """Captures requirements from repo.

    Expected file format is: requirements[-_]<optional-extras>.txt

    For example:

        pip install -e .[foo]

    Would require:

        requirements-foo.txt

        or

        requirements_foo.txt

    """
    from pip.download import PipSession
    from pip.req import parse_requirements
    import re

    session = PipSession()
    top_path = top_path or os.path.realpath(os.getcwd())
    extras = {}
    for filepath in tree(top_path):
        filename = os.path.basename(filepath)
        basename, ext = os.path.splitext(filename)
        if ext == '.txt' and basename.startswith('requirements'):
            if filename == 'requirements.txt':
                extra_name = 'requirements'
            else:
                _, extra_name = re.split(r'[-_]', basename, 1)
            if extra_name:
                reqs = [str(ir.req) for ir in parse_requirements(filepath, session=session)]
                extras.setdefault(extra_name, []).extend(reqs)
    all_reqs = set()
    for key, values in extras.items():
        all_reqs.update(values)
    extras['all'] = list(all_reqs)
    return extras

e depois usar ...

reqs = gather_requirements()
install_reqs = reqs.pop('requirements', [])
test_reqs = reqs.pop('test', [])
...
setup(
    ...
    'install_requires': install_reqs,
    'test_requires': test_reqs,
    'extras_require': reqs,
    ...
)

de onde treevem?
Francesco Boi

@FrancescoBoi se você me perdoa um pouco por não apresentar uma solução totalmente funcional ... a árvore é realmente apenas uma varredura do sistema de arquivos local (muito semelhante ao comando "árvore" no linux). Além disso, minha solução acima pode não funcionar totalmente neste momento, porque o pip está sendo atualizado constantemente e eu usei os internos do pip.
Brian Bruggeman

0

Eu não recomendaria fazer isso. Como mencionado várias vezes install_requirese requirements.txtdefinitivamente não deveria ser a mesma lista. Mas como existem muitas respostas enganosas envolvendo APIs internas privadas de pip , pode valer a pena procurar alternativas mais saudáveis ​​...

Não há necessidade de o pip analisar um requirements.txtarquivo a partir de um script setuptools setup.py . O projeto setuptools já contém todas as ferramentas necessárias em seu pacote de nível superiorpkg_resources .

Poderia ser mais ou menos assim:

#!/usr/bin/env python3

import pathlib

import pkg_resources
import setuptools

with pathlib.Path('requirements.txt').open() as requirements_txt:
    install_requires = [
        str(requirement)
        for requirement
        in pkg_resources.parse_requirements(requirements_txt)
    ]

setuptools.setup(
    install_requires=install_requires,
)

Caso você não saiba, a razão pela qual muitos (inclusive eu) utilizamos pipa análise de e não pkg_resourcesdesde antes de 2015 são bugs, como github.com/pypa/setuptools/issues/470 . Esse exato é corrigido hoje em dia, mas ainda tenho um pouco de medo de usá-lo, pois as duas implementações parecem ser desenvolvidas separadamente.
trevorj 12/02

@trevorj Obrigado por apontar isso, eu não sabia. Atualmente, o fato é que funciona e me envolver com o pip parece uma idéia ridícula para mim (principalmente dessa maneira). Dê uma olhada nas outras respostas, a maioria parece ser pequenas variações da mesma ideia desagradável, sem quase nenhum aviso prévio. E os novatos podem seguir essa tendência. Esperançosamente, iniciativas como PEP517 e PEP518 afastarão a comunidade dessa loucura.
sinoroc

-1

Postando minha resposta dessa pergunta SO para outra solução simples e à prova de versão de pip.

try:  # for pip >= 10
    from pip._internal.req import parse_requirements
    from pip._internal.download import PipSession
except ImportError:  # for pip <= 9.0.3
    from pip.req import parse_requirements
    from pip.download import PipSession

requirements = parse_requirements(os.path.join(os.path.dirname(__file__), 'requirements.txt'), session=PipSession())

if __name__ == '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requirements],
        ...
    )

Em seguida, basta inserir todos os seus requisitos no requirements.txtdiretório raiz do projeto.


-1

Eu fiz isso:

import re

def requirements(filename):
    with open(filename) as f:
        ll = f.read().splitlines()
    d = {}
    for l in ll:
        k, v = re.split(r'==|>=', l)
        d[k] = v
    return d

def packageInfo():
    try:
        from pip._internal.operations import freeze
    except ImportError:
        from pip.operations import freeze

    d = {}
    for kv in freeze.freeze():
        k, v = re.split(r'==|>=', kv)
        d[k] = v
    return d

req = getpackver('requirements.txt')
pkginfo = packageInfo()

for k, v in req.items():
    print(f'{k:<16}: {v:<6} -> {pkginfo[k]}')

-2

Outro parse_requirementstruque que também analisa marcadores de ambiente em extras_require:

from collections import defaultdict
from pip.req import parse_requirements

requirements = []
extras = defaultdict(list)
for r in parse_requirements('requirements.txt', session='hack'):
    if r.markers:
        extras[':' + str(r.markers)].append(str(r.req))
    else:
        requirements.append(str(r.req))

setup(
    ...,
    install_requires=requirements,
    extras_require=extras
)

Ele deve suportar discos sdist e binários.

Conforme declarado por outros, parse_requirementspossui várias deficiências, portanto, não é isso que você deve fazer em projetos públicos, mas pode ser suficiente para projetos internos / pessoais.


O pip 20.1 mudou sua API e os marcadores não estão mais disponíveis via parse_requirements(), então isso agora falha.
Tuukka Mustonen

-3

Aqui está um hack completo (testado com pip 9.0.1) baseado na resposta de Romain que o analisa requirements.txte filtra de acordo com os marcadores de ambiente atuais :

from pip.req import parse_requirements

requirements = []
for r in parse_requirements('requirements.txt', session='hack'):
    # check markers, such as
    #
    #     rope_py3k    ; python_version >= '3.0'
    #
    if r.match_markers():
        requirements.append(str(r.req))

print(requirements)

1
Isso é apenas parcialmente verdade. Se você ligar, r.match_markers()está realmente avaliando os marcadores, o que é a coisa certa a fazer para um sdist. No entanto, se você estiver construindo um dist binário (por exemplo, wheel), o pacote listará apenas as bibliotecas que correspondem ao seu ambiente de tempo de construção.
Tuukka Mustonen

@TuukkaMustonen, então onde encontrar isso wheel environment(se é o que a pessoa tenta fazer) para avaliar marcadores?
Anatoly techtonik

Consulte stackoverflow.com/a/41172125/165629 que também deve suportar bdist_wheel. Não avalia marcadores, apenas os adiciona extras_require.
Tuukka Mustonen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.