在setuptools setup.py文件中引用installation_requires kwarg的requirements.txt

blz*_*blz 255 python pip setuptools requirements.txt

我有一个requirements.txt文件,我正在使用Travis-CI.看来愚蠢复制中都要求requirements.txtsetup.py,所以我希望到一个文件句柄传递给install_requires在kwarg setuptools.setup.

这可能吗?如果是这样,我该怎么做呢?

这是我的requirements.txt档案:

guessit>=0.5.2
tvdb_api>=1.8.2
hachoir-metadata>=1.3.3
hachoir-core>=1.3.3
hachoir-parser>=1.3.4
Run Code Online (Sandbox Code Playgroud)

Rom*_*uin 231

您可以翻转过来,并列出依赖关系setup.py,并有单字符-点.-在requirements.txt代替.


或者,即使不建议,仍然可以requirements.txt使用以下hack 解析文件(如果它没有通过URL引用任何外部要求)(测试时pip 9.0.1):

install_reqs = parse_requirements('requirements.txt', session='hack')
Run Code Online (Sandbox Code Playgroud)

但这并不过滤环境标记.


在旧版本的pip中,更具体地说,早于6.0版本,有一个公共API可用于实现此目的.需求文件可以包含comments(#),并且可以包含一些其他文件(--requirement-r).因此,如果你真的想解析一个,requirements.txt你可以使用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
)
Run Code Online (Sandbox Code Playgroud)

  • 你真的不想这样做.作为一个pip维护者pip,它不支持被称为这样的API.事实上,pip 1.6(此时的下一个版本)会移动此功能. (86认同)
  • @GringoSuave如果用户没有安装pip,他需要先安装它. (61认同)
  • 如果用户没有安装pip怎么办?嘉潮? (25认同)
  • 这应该不再是接受的答案,如果它应该有的话.它被彻底打破了.即使它工作,它显然是不必要的.由于`pip`默认在没有`requirements.txt`的情况下从`setup.py`解析依赖关系,[Tobu]敏锐地注意到[简单答案](/sf/answers/1335688791/) (/sf/users/16082741/)下面是**列出`setup.py`中的所有依赖项并删除`requirements.txt`.**对于需要两者的应用程序,只需减少依赖列表`requirements.txt`仅仅是`.`字符._Done._ (24认同)
  • 您还需要在需求文件中提供URL,以防有任何-e或-f("可编辑"git repo)行指向非pypi包.使用:`setup(...,dependency_links = [str(req_line.url)for parse_requirements(<requirements_path>)中的req_line],...)` (7认同)
  • 该护理文章现在在https://caremad.io/2013/07/setup-vs-requirement/ (6认同)
  • ``parse_requirements``函数被移动到pip 1.6中的不同位置.另外``--process-dependency-links``将在pip 1.6中完全消失.从github安装东西应该使用需求文件来完成.请参阅https://caremad.io/blog/setup-vs-requirement/ (5认同)
  • git要求怎么样? (4认同)
  • 我相信这就像'pip` 10一样被打破了. (3认同)
  • PIP 安装上的“没有这样的文件 requirements.txt”。不好的建议。 (2认同)

Jon*_*son 156

在它面前,它似乎是requirements.txtsetup.py是愚蠢的重复,而是要明白,虽然形式是相似的,预定的功能有很大的不同是很重要的.

在指定依赖关系时,程序包作者的目标是"无论您在何处安装此程序包,这些都是您需要的其他程序包,以便此程序包能够正常运行."

相比之下,部署作者(可能在不同时间可能是同一个人)有不同的工作,因为他们说"这是我们收集在一起测试的包的列表,我现在需要安装".

软件包作者为各种各样的场景写作,因为他们将他们的工作放在那里,以他们可能不知道的方式使用,并且无法知道将在他们的软件包旁边安装哪些软件包.为了成为一个好邻居并避免与其他包的依赖版本冲突,他们需要指定尽可能广泛的依赖版本.这是做什么install_requiressetup.py.

部署作者编写了一个非常不同的,非常具体的目标:安装在特定计算机上的已安装应用程序或服务的单个实例.为了精确控制部署,并确保测试和部署正确的软件包,部署作者必须指定要安装的每个软件包的确切版本和源位置,包括依赖项和依赖项的依赖项.使用此规范,部署可以重复应用于多台计算机,或在测试计算机上进行测试,部署作者可以确信每次都部署相同的程序包.这就是一个requirements.txt.

所以你可以看到,虽然它们看起来像一个包和版本的大列表,但这两个东西有着截然不同的工作.并且很容易将它混合起来并弄错!但正确的思考方式是requirements.txt对所有各种setup.py包文件中的要求所带来的"问题"的"回答" .而不是手工编写,它通常是通过告诉pip查看setup.py一组所需包中的所有文件,找到它认为符合所有要求的一组包,然后在安装完成后"冻结"而生成的. "将包列表放入文本文件中(这是pip freeze名称的来源).

所以外卖:

  • setup.py应该声明仍然可行的最松散的依赖版本.它的工作是说一个特定的包可以使用什么.
  • requirements.txt是一个定义整个安装作业的部署清单,不应该被认为是绑定到任何一个包.它的工作是声明所有必要包的详尽列表,以使部署工作.
  • 因为这两件事具有不同的内容和存在的原因,所以简单地将一个复制到另一个中是不可行的.

参考文献:

  • 这是最好的解释之一,让我在包装安装的混乱中加入一些订单!:) (8认同)
  • 所以你说`requirements.txt`是生成给定构建的世界状态的更多文档,即使它通常不在构建过程中使用吗?那讲得通.但是,看起来有几个系统依赖于复制:Travis在你的virtualenv中安装了一些默认(旧)软件包,并说要使用`requirements.txt`.如果我问如何使用`setup.py`来确保最新的依赖关系,人们坚持认为我应该使用`requirements.txt`. (5认同)
  • 我仍然不清楚为什么开发人员会保留版本控制的"requirements.txt"以及包含安装或测试的具体/冻结要求的包的来源.当然`setup.py`可以在项目中用于此目的吗?我只能想象将这样一个文件用于支持*管理*项目的工具(例如重构,发布等). (4认同)
  • @samBrightman我完全同意,我不认为库包_or_应用程序包应该使用代码将其requirements.txt文件提交到存储库.我认为这应该是在构建测试期间生成的工件,然后用于记录构建清单并最终生成部署工件. (2认同)
  • 您可以从中获得的最佳建议是找到一个适合您的模型,对其进行良好的记录,并确保与您合作的每个人都理解它。想一想为什么要做每件事,以及对您的用例是否真的有意义。并尽量保持对Python的构建,打包和发布的当前状态的了解,以防万一情况变得更好。但是,不要屏住呼吸。 (2认同)

Fre*_*nan 86

它不能采用文件句柄.该install_requires参数可以仅仅是一个字符串或字符串列表.

当然,您可以在设置脚本中读取文件并将其作为字符串列表传递给install_requires.

import os
from setuptools import setup

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

setup(...
install_requires=required,
...)
Run Code Online (Sandbox Code Playgroud)

  • @PiotrDobrogost也许PyCharm开发人员应该修复他们的程序.`setup.py`是一个应该运行的程序,而不是应该解析的数据文件.这并没有使这个答案更糟. (53认同)
  • 只要你将`include requirements.txt`放入你的`MANIFEST.in`就可以正常工作,否则你将无法从源代码发行版中安装你的库. (29认同)
  • 尽管有用,但它将需求规范从声明变为命令.这使得某些工具无法找出您的要求.例如,PyCharm提供了`install_requires`中指定的所有要求的自动安装.但是,如果不使用声明性语法,则它不起作用. (5认同)
  • 我只是指出可能存在的问题; 这个答案非常好.PyCharm不仅存在信息"隐藏"在代码背后的问题.这是普遍存在的问题,因此在Python包装中通常采用元数据的声明性规范. (5认同)
  • 我知道这是一个老问题,但至少你现在可以配置PyCharm解析一个需求文件在Preferences-> Tools-> Python集成工具 - >包需求文件 (4认同)
  • 这绝对是可怕的.由于`pip`默认安装`setup.py`列出的所有依赖项而没有`requirements.txt`,理智的解决方案是**列出`setup.py`中的所有依赖项并完全删除`requirements.txt` .**另见[famousgarkin](/sf/users/47724981/)和[Tobu](/sf/users/16082741/)的解释性答案. (4认同)

Tob*_*obu 60

需求文件使用扩展的pip格式,只有在需要补充setup.py更强的约束时才有用,例如指定一些依赖项必须来自的确切URL,或者pip freeze将整个包集冻结为已知工作的输出版本.如果您不需要额外的约束,请仅使用a setup.py.如果你觉得你真的需要发货requirements.txt,你可以把它变成一条线:

.
Run Code Online (Sandbox Code Playgroud)

它将是有效的,并且完全引用setup.py同一目录中的内容.

  • 但在这种情况下,它也会尝试安装我的应用程序.如果我不需要它并且只想安装install_requires怎么办? (9认同)
  • @ DexD.Hunter仍然尝试自行安装应用程序。这不是我们想要的 (4认同)
  • 要详细说明@ffeast的问题,如果需求仅存在于setup.py中,是否可以在不安装软件包本身的情况下安装需求(相当于`pip install -r requirements.txt`)? (2认同)

fam*_*kin 37

虽然不是这个问题的确切答案,但我建议Donald Stufft的博客文章https://caremad.io/2013/07/setup-vs-requirement/以便很好地解决这个问题.我一直在用它取得巨大成功.

简而言之,requirements.txt不是setup.py替代方案,而是部署的补充.保持适当的包依赖性抽象setup.py.设置requirements.txt或更多的'em来获取开发,测试或生产的特定版本的包依赖项.

例如,包含在回购下的包裹deps/:

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

# install package
# NOTE: -e . for editable mode
.
Run Code Online (Sandbox Code Playgroud)

pip执行package setup.py并安装声明的依赖项的特定版本install_requires.没有两面性,两个工件的目的都得以保留.

  • 当您想通过`pip install my-package`提供其他人安装包时,这不起作用.如果my-package/setup.py中没有列出my-package的依赖项,则不会通过`pip install my-package`安装它们.我一直无法确定如何为包含依赖项的其他人提供包,而无需在setup.py中明确说明它们.很想知道是否有人已经想出如何保持干燥,同时允许其他人安装my-package +依赖项而不下载需求文件并手动调用`pip install -r my-package/requirements.txt`. (7认同)
  • @Malina这里的包完全可以安装,不需要`requirements.txt`.这就是重点.更新了问题以使事情更加清晰.还更新了过时的博客帖子链接. (2认同)

fab*_*nvf 19

上面的大多数其他答案都不适用于当前版本的pip的API.这是使用当前版本的pip(在编写本文时为6.0.8,也在7.1.2中工作)的正确方法.您可以使用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
    ....
)
Run Code Online (Sandbox Code Playgroud)

*正确,因为它是使用当前pip的parse_requirements的方法.它仍然可能不是最好的方法,因为,正如上面的海报所说,pip并没有真正维护API.


Wil*_*ega 18

使用parse_requirements是有问题的,因为pip API未公开记录和支持.在pip 1.6中,该函数实际上正在移动,因此它的现有用途可能会中断.

一种更可靠的方法来消除之间的重复,setup.py并将requirements.txt特定于您的依赖项setup.py,然后放入-e .您的requirements.txt文件.其中一位pip开发人员提供了有关为什么这是一种更好的方法的信息,请访问:https://caremad.io/blog/setup-vs-requirement/


vdb*_*oor 14

在Travis中安装当前包.这样可以避免使用requirements.txt文件.例如:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py
Run Code Online (Sandbox Code Playgroud)

  • 这是迄今为止"正确"和"实用"的最佳组合.我想补充一点,如果在测试通过后你可以让Travis生成一个带有`pip freeze`的requirements.txt并将该文件作为工件导出(如S3或其他东西),那么你有一个很好的方法可以重复安装完全你测试的. (2认同)

Die*_*rro 7

from pip.req import parse_requirements对我不起作用,我认为它适用于我的requirements.txt中的空白行,但这个函数确实有效

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,
    ...
)
Run Code Online (Sandbox Code Playgroud)


Acu*_*nus 6

这种简单的方法从setup.py. 这是Dmitiry S.答案的变体。此答案仅与 Python 3.6+ 兼容。

DSrequirements.txt可以记录与特定的版本号具体要求,而setup.py可以记录与宽松版范围抽象的要求。

下面是我的摘录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: /sf/answers/2942318571/
    return distutils.text_file.TextFile(filename=str(Path(__file__).with_name(filename))).readlines()

setup(...
      install_requires=_parse_requirements('requirements.txt'),
   ...)
Run Code Online (Sandbox Code Playgroud)

请注意,这distutils.text_file.TextFile将删除注释。此外,根据我的经验,您显然不需要采取任何特殊步骤来捆绑需求文件。


sin*_*roc 6

我不建议做这样的事情。正如多次提到的install_requiresrequirements.txt绝对不应该是同一个列表。但是,由于涉及pip 的私有内部 API 有很多误导性的答案,因此可能值得寻找更明智的替代方案......

pip不需要requirements.txtsetuptools setup.py脚本解析文件。该setuptools的项目已经包含在了所有必要的工具顶层pkg_resources

它或多或少看起来像这样:

#!/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,
)
Run Code Online (Sandbox Code Playgroud)

注意事项


reu*_*ano 5

如果您不想强迫用户安装pip,可以使用以下方法模拟其行为:

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'))
)
Run Code Online (Sandbox Code Playgroud)


Dmi*_*sov 5

以下接口在 pip 10 中已弃用:

from pip.req import parse_requirements
from pip.download import PipSession
Run Code Online (Sandbox Code Playgroud)

所以我把它切换到简单的文本解析:

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 != ''
    ]
Run Code Online (Sandbox Code Playgroud)