【问题标题】:Reference requirements.txt for the install_requires kwarg in setuptools setup.py filesetuptools setup.py 文件中 install_requires kwarg 的参考 requirements.txt
【发布时间】:2013-01-02 04:47:11
【问题描述】:

我有一个与 Travis-CI 一起使用的 requirements.txt 文件。在requirements.txtsetup.py 中重复要求似乎很愚蠢,所以我希望将文件句柄传递给setuptools.setup 中的install_requires kwarg。

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

这是我的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

【问题讨论】:

  • install_requires 用于声明对包运行所需的包的依赖关系,并由包的开发人员使用,而requirements.txt 用于自动安装环境,允许安装额外的软件并进行版本固定,并由部署软件包的系统管理员使用。它们的角色和目标受众差异很大,因此试图将它们像 OP 所希望的那样结合起来是一个真正的设计错误恕我直言。
  • 我的 2 美分。不要在 setup.py 中使用 requirements.txt。目的不一样,aredcaremad.io/2013/07/setup-vs-requirement
  • 我看到了很多复杂的答案。普通的旧 [line.strip() for line in open("requirements.txt").readlines()] 有什么问题?
  • 不建议这样做。但如果真的需要,那么它很简单:setuptools 本身已经拥有一切必要的东西pkg_resources.parse_requirements()

标签: python pip setuptools requirements.txt


【解决方案1】:

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

pip 不需要从 setuptools setup.py 脚本解析 requirements.txt 文件。 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,
)

注意事项

【讨论】:

  • 如果您不知道,自 2015 年之前很多人(包括我自己)一直使用 pip 的解析而不是 pkg_resources 的原因是诸如 github.com/pypa/setuptools/issues/470 之类的错误.这个确切的一个现在已经修复了,但我仍然有点害怕使用它,因为这两种实现似乎是分开开发的。
  • @trevorj 感谢您指出这一点,我不知道。事实是现在它起作用了,让 pip 参与对我来说似乎是一个荒谬的想法(尤其是在这种方式下)。看看其他答案,大多数似乎都是同一个不明智的想法的轻微变化,几乎没有任何警告通知。新来者可能只是跟随这一趋势。希望 PEP517 和 PEP518 等举措能够引导社区远离这种疯狂。
  • @sinoroc 谢谢!你的答案应该是最多的,因为它是最干净的。理想情况下,用户会在此处加载 requirements.in 而不是 requirements.txt。 (pip-tools 使用的 requirements.in,可能是我们正在寻找的精确 install_requirements '真相')
  • 或多或少..?
【解决方案2】:

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

根据D.S.requirements.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: 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'),
   ...)

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

【讨论】:

    【解决方案3】:

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

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

    所以我将它切换为简单的文本解析:

    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 != ''
        ]
    

    【讨论】:

    【解决方案4】:

    我这样做了:

    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]}')
    

    【讨论】:

      【解决方案5】:

      我为此创建了一个可重用的函数。它实际上解析了需求文件的整个目录并将它们设置为 extras_require。

      最新的总是在这里: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)
      

      【讨论】:

      • 非常好!甚至可以使用最新的 pip 处理递归需求 :)
      • @amohr 谢谢!我最近更新了它以供以后的点子使用,我不确定他们为什么会这样行事,将内容移至 pip._internal.. 如果您不提供可用的外部 API,那么您不应该打破所有使用你提供的一切的人。
      【解决方案6】:

      this SO question 交叉发布我的答案以获得另一个简单的 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],
              ...
          )
      
      

      然后在项目根目录下的requirements.txt 下输入所有需求。

      【讨论】:

        【解决方案7】:

        您可以翻转它并在 setup.py 中列出依赖关系,并在 requirements.txt 中使用单个字符 - 一个点 .


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

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

        这不会过滤environment markers


        在旧版本的 pip 中,更具体地说是older than 6.0,有一个公共 API 可用于实现此目的。需求文件可以包含 cmets (#) 并且可以包含一些其他文件 (--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
        )
        

        【讨论】:

        • 如果用户没有安装pip怎么办?卡繁荣?
        • @GringoSuave 如果用户没有安装pip,需要先安装。
        • 您还需要在需求文件中提供 url,以防有任何 -e 或 -f(“可编辑”git repo)行指向非 pypi 包。使用这个:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(&lt;requirements_path&gt;)], ...)
        • 你真的不想这样做。以 pip 维护者的身份说话 pip 根本不支持像这样的 API 被调用。事实上 pip 1.6(此时的下一个版本)移动了这个功能。
        • 这应该不再是公认的答案,如果它应该有的话。它被公然破坏了。即使它有效,它也是公然不必要的。由于pip 在没有requirements.txt 的情况下默认解析来自setup.py 的依赖关系,所以下面Tobu 敏锐地指出的simple answer列出setup.py 中的所有依赖关系并删除requirements.txt。 对于需要两者的应用程序,只需将requirements.txt 中的依赖关系列表减少为仅. 字符。 完成。
        【解决方案8】:

        另一种可能的解决方案...

        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
        

        然后使用...

        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,
            ...
        )
        

        【讨论】:

        • tree 来自哪里?
        • @FrancescoBoi 如果你原谅我没有提供一个完整的解决方案......树实际上只是对本地文件系统的扫描(非常类似于 linux 中的“树”命令)。此外,我上面的解决方案此时可能无法完全正常工作,因为 pip 会不断更新,并且我使用了 pip 内部结构。
        【解决方案9】:

        如果你不想强迫你的用户安装 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'))
        )
        

        【讨论】:

          【解决方案10】:

          另一个parse_requirements hack 也将环境标记解析为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
          )
          

          它应该支持 sdist 和二进制文件。

          正如其他人所说,parse_requirements 有几个缺点,所以这不是您在公共项目中应该做的,但对于内部/个人项目可能就足够了。

          【讨论】:

          • pip 20.1 更改了他们的 API,标记不再通过 parse_requirements() 可用,所以现在失败了。
          【解决方案11】:

          这是一个基于 Romain's answer 的完整 hack(使用 pip 9.0.1 测试),它解析 requirements.txt 并根据当前的 environment markers 对其进行过滤:

          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)
          

          【讨论】:

          • 这只是部分正确。如果您致电r.match_markers(),您实际上是在评估标记,这对于 sdist 来说是正确的做法。但是,如果您正在构建二进制 dist(例如 wheel),则该软件包将仅列出与 您的 构建时环境匹配的那些库。
          • @TuukkaMustonen,那么在哪里可以找到这个wheel environment(如果这是人们试图做的事情)来评估针对它的标记?
          • 参见stackoverflow.com/a/41172125/165629,它也应该支持bdist_wheel。它不评估标记,它只是将它们添加到extras_require
          【解决方案12】:

          从表面上看,requirements.txtsetup.py 似乎是愚蠢的重复,但重要的是要理解,虽然形式相似,但预期的功能却大不相同。

          在指定依赖项时,包作者的目标是说“无论您在何处安装此包,这些都是您需要的其他包,以使此包正常工作。”

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

          包作者针对各种各样的场景进行了编写,因为他们将自己的工作以他们可能不知道的方式使用,并且无法知道哪些包将与他们的包一起安装。为了成为一个好邻居并避免与其他包的依赖版本冲突,他们需要指定尽可能广泛的依赖版本。这就是install_requires in setup.py 所做的。

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

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

          所以要点:

          • setup.py 应该声明仍然可行的最松散的可能依赖版本。它的工作是说明特定包可以使用什么。
          • requirements.txt 是一个部署清单,它定义了整个安装作业,不应被认为与任何一个包相关联。它的工作是声明一份详尽的清单,列出所有必要的包以使部署工作。
          • 因为这两个东西有如此不同的内容和存在的原因,简单地把一个复制到另一个是行不通的。

          参考资料:

          【讨论】:

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

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

          简而言之,requirements.txt 不是setup.py 的替代品,而是部署的补充。在setup.py 中保留适当的包依赖抽象。设置 requirements.txt 或更多它们以获取特定版本的包依赖项以用于开发、测试或生产。

          例如包含在deps/ 下的 repo 中的软件包:

          # fetch specific dependencies
          --no-index
          --find-links deps/
          
          # install package
          # NOTE: -e . for editable mode
          .
          

          pip 执行包的setup.py 并安装在install_requires 中声明的特定版本的依赖项。没有重复性,并且保留了两个工件的用途。

          【讨论】:

          • 当您想通过pip install my-package 提供一个软件包供其他人安装时,这不起作用。如果 my-package 的依赖项未在 my-package/setup.py 中列出,则它们不是由 pip install my-package 安装的。如果没有在 setup.py 中明确说明它们,我无法确定如何为包含依赖项的其他人提供包。很想知道是否有人想出了如何让它保持干燥,同时允许其他人安装 my-package + 依赖项,而无需下载需求文件并手动调用pip install -r my-package/requirements.txt
          • @Malina 这里的包在没有requirements.txt 的情况下完全可以安装。这就是重点。更新了问题以使事情更清楚。还更新了过时的博客文章链接。
          • 所以在运行 setup.py 时它会调用 requirements.txt 以获得 stup.py 中列出的文件的特定版本?
          • 与@dtracers 正好相反。 requirements.txt 指向它自己的包,在这里可以获取 setup.py 的依赖项。因此,当使用 requirements 安装时,它可以工作,当通过 pip 安装时,它也可以工作 - 在这两种情况下都使用 setup.py 的依赖项,但也允许在使用 requirements.txt 时安装更多东西
          【解决方案14】:

          上面的大多数其他答案都不适用于当前版本的 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
              ....
          )
          

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

          【讨论】:

            【解决方案15】:

            提防parse_requirements行为!

            请注意pip.req.parse_requirements 会将下划线更改为破折号。在我发现它之前,这让我愤怒了几天。示例演示:

            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
            

            生产

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

            【讨论】:

            • 使用 unsafe_name 获取下划线版本:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
            • 正如其他地方所指出的,PIP 是一个应用程序,而不是一个库。它没有公开认可的 API,并且将其导入您的代码不是受支持的用例。它有意想不到的行为并不奇怪。它的内部功能从未打算以这种方式使用。
            【解决方案16】:

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

            消除setup.pyrequirements.txt 之间重复的更可靠方法是在setup.py 中指定您的依赖关系,然后将-e . 放入您的requirements.txt 文件中。来自pip 开发人员之一的一些关于为什么这是一种更好的方法的信息可在此处获得:https://caremad.io/blog/setup-vs-requirement/

            【讨论】:

            【解决方案17】:

            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,
                ...
            )
            

            【讨论】:

              【解决方案18】:

              需求文件使用扩展的 pip 格式,仅当您需要用更强的约束来补充您的 setup.py 时才有用,例如指定某些依赖项必须来自的确切 url,或 pip freeze 的输出到将整个包集冻结为已知工作版本。如果您不需要额外的约束,请仅使用setup.py。如果您觉得确实需要发送requirements.txt,您可以将其设置为一行:

              .
              

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

              【讨论】:

              • 但在这种情况下,它也会尝试安装我的应用程序。如果我不需要它而只想安装 install_requires 怎么办?
              • 要详细说明@ffeast 的要求,如果要求仅存在于 setup.py 中,有没有办法在不安装包本身的情况下安装要求(相当于 pip install -r requirements.txt )?
              • @feast @haridsv -e . 应该足够了。查看此页面:caremad.io/posts/2013/07/setup-vs-requirement
              • @DexD.Hunter 它仍然尝试安装应用程序本身。这不是我们想要的
              • setup.py 中的每个依赖项是否提供特定版本或版本范围?如果给定一个范围,你会自动测试每个可能的依赖组合吗?
              【解决方案19】:

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

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

              【讨论】:

              • 这是迄今为止“正确”和“实用”的最佳组合。我要补充一点,如果测试通过后,您可以让 Travis 使用 pip freeze 生成 requirements.txt 并将该文件导出为工件(如 S3 或其他东西),那么您将有一个很好的方法来重复安装正是你测试的。
              【解决方案20】:

              它不能接受文件句柄。 install_requires 参数可以only be a string or a list of strings

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

              import os
              from setuptools import setup
              
              with open('requirements.txt') as f:
                  required = f.read().splitlines()
              
              setup(...
              install_requires=required,
              ...)
              

              【讨论】:

              • 虽然这很有用,但它会将需求规范从声明性更改为命令性。这使得某些工具无法找出您的要求。例如,PyCharm 可以自动安装install_requires 中指定的所有要求。但是,如果您不使用声明性语法,它就不起作用。
              • @PiotrDobrogost 也许 PyCharm 开发人员应该修复他们的程序。 setup.py 是应该运行的程序,而不是应该解析的数据文件。这并没有使这个答案变得更糟。
              • 我只是指出可能的问题;这个答案很好。不仅 PyCharm 存在信息被“隐藏”在代码后面的问题。这是一个普遍的问题,因此 Python 打包中元数据的声明性规范普遍存在。
              • 只要将include requirements.txt 放入MANIFEST.in 就可以正常工作,否则您将无法从源代码分发版安装库。
              • 我知道这是一个老问题,但您至少现在可以在 Preferences->Tools->Python 集成工具->Package requirements file 配置 PyCharm 解析需求文件
              猜你喜欢
              • 2023-01-19
              • 1970-01-01
              • 1970-01-01
              • 2016-05-06
              • 1970-01-01
              • 1970-01-01
              • 2020-10-24
              • 1970-01-01
              相关资源
              最近更新 更多