373

requirements.txtTravis-CIで使用しているファイルがあります。requirements.txtとの両方で要件を複製するのはばかげているように思われるsetup.pyので、ファイルハンドルをのinstall_requireskwargに渡すことを望んでいました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
4

20 に答える 20

305

それを裏返して依存関係を一覧表示し、代わりにsetup.py1つの文字(ドット)を含めることができます。.requirements.txt


または、アドバイスrequirements.txtがない場合でも、次のハック(でテスト済み)を使用してファイルを解析することができます(URLで外部要件を参照していない場合pip 9.0.1)。

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

ただし、これは環境マーカーをフィルタリングしません。


古いバージョンのpip、より具体的には6.0より古いバージョンには、これを実現するために使用できるパブリックAPIがあります。要件ファイルにはコメント(#)を含めることができ、他のファイル(--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
)
于 2013-05-18T13:18:24.863 に答える
294

requirements.txt一見すると、それはばかげた複製のように見えsetup.pyますが、形式は似ていますが、意図された機能は非常に異なることを理解することが重要です。

依存関係を指定するときのパッケージ作成者の目標は、「このパッケージをインストールする場所はどこでも、このパッケージが機能するために必要な他のパッケージです」と言うことです。

対照的に、展開の作成者(異なる時間に同じ人物である可能性があります)は、「これが私たちが集めてテストしたパッケージのリストであり、今インストールする必要がある」という点で異なる仕事をしています。

パッケージの作者はさまざまなシナリオについて書いています。なぜなら、彼らは自分たちの仕事を彼らが知らないかもしれない方法で使用するためにそこに置いており、どのパッケージが彼らのパッケージと一緒にインストールされるかを知る方法がないからです。良好なネイバーになり、依存関係のバージョンが他のパッケージと競合しないようにするには、可能な限り広範囲の依存関係のバージョンを指定する必要があります。これが何であるかinstall_requiresですsetup.py

展開の作成者は、特定のコンピューターにインストールされた、インストールされたアプリケーションまたはサービスの単一インスタンスという、非常に異なる、非常に具体的な目標について記述します。展開を正確に制御し、適切なパッケージがテストおよび展開されていることを確認するには、展開の作成者は、依存関係と依存関係の依存関係を含め、インストールするすべてのパッケージの正確なバージョンとソースの場所を指定する必要があります。この仕様を使用すると、展開を複数のマシンに繰り返し適用したり、テストマシンでテストしたりできます。展開の作成者は、同じパッケージが毎回展開されることを確信できます。これは何をするかrequirements.txtです。

つまり、どちらもパッケージとバージョンの大きなリストのように見えますが、これら2つの機能は大きく異なります。そして、これを混同して間違えるのは間違いなく簡単です!しかし、これについて考える正しい方法は、さまざまなパッケージファイルrequirements.txtすべての要件によって提起される「質問」に対する「答え」であるということです。setup.py手作業で書くのではなく、多くの場合、pipにsetup.py、目的のパッケージのセット内のすべてのファイルを調べ、すべての要件に適合すると思われるパッケージのセットを見つけて、インストール後に「フリーズ」するように指示することによって生成されます。 "パッケージのリストをテキストファイルにまとめます(これがpip freeze名前の由来です)。

だから持ち帰り:

  • setup.pyまだ機能している可能な限り緩い依存関係バージョンを宣言する必要があります。その仕事は、特定のパッケージが何で機能するかを言うことです。
  • requirements.txtは、インストールジョブ全体を定義する展開マニフェストであり、1つのパッケージに関連付けられていると考えるべきではありません。その仕事は、展開を機能させるために必要なすべてのパッケージの完全なリストを宣言することです。
  • これら2つのものは、内容と存在理由が非常に異なるため、単に一方を他方にコピーすることは現実的ではありません。

参照:

  • install_requiresとPythonパッケージングユーザーガイドの要件ファイル。
于 2015-11-13T04:21:24.610 に答える
125

ファイルハンドルを取ることはできません。install_requires引数は、文字列または文字列のリストのみにすることができます。

もちろん、セットアップスクリプトでファイルを読み取り、それを文字列のリストとしてに渡すことができますinstall_requires

import os
from setuptools import setup

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

setup(...
install_requires=required,
...)
于 2013-01-18T13:14:47.607 に答える
66

要件ファイルは拡張pip形式を使用します。これは、依存関係の一部を取得する必要がある正確なURLを指定したり、パッケージセット全体を既知の動作にフリーズするためsetup.pyの出力など、より強力な制約で補完する必要がある場合にのみ役立ちます。pip freezeバージョン。追加の制約が必要ない場合は、。のみを使用してsetup.pyください。requirements.txtとにかく本当に出荷する必要があると感じた場合は、それを1行にすることができます。

.

これは有効でありsetup.py、同じディレクトリにあるの内容を正確に参照します。

于 2013-09-29T17:48:42.143 に答える
39

質問に対する正確な答えではありませんが、この問題をうまく理解するために、Donald Stufftのブログ投稿(https://caremad.io/2013/07/setup-vs-requirement/ )をお勧めします。私はそれを大成功に使用してきました。

つまり、requirements.txtこれはsetup.py代替手段ではなく、展開を補完するものです。パッケージの依存関係を適切に抽象化してくださいsetup.pyrequirements.txt'em以上を設定して、開発、テスト、または本番用のパッケージ依存関係の特定のバージョンをフェッチします。

たとえば、以下のリポジトリに含まれるパッケージを使用しますdeps/

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

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

pipはパッケージを実行setup.pyし、で宣言された特定のバージョンの依存関係をインストールしますinstall_requires。重複はなく、両方のアーティファクトの目的は保持されます。

于 2014-04-06T18:02:07.993 に答える
22

parse_requirementspip APIは公に文書化およびサポートされていないため、使用には問題があります。pip 1.6では、その関数は実際に動いているため、既存の使用法は機能しなくなる可能性があります。

setup.pyとの間の重複を排除するためのより信頼性の高い方法requirements.txtは、依存関係を特定してからファイルsetup.pyに入れる-e .ことです。なぜそれがより良い方法であるかについての開発者requirements.txtの1人からのいくつかの情報はここで利用可能です: https ://caremad.io/blog/setup-vs-requirement/pip

于 2014-03-26T01:31:31.973 に答える
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
    ....
)

*正解です。これは、現在のpipでparse_requirementsを使用する方法です。上記のポスターが述べたように、pipは実際にはAPIを維持していないため、それはおそらくそれを行うための最良の方法ではありません。

于 2015-04-15T16:33:09.957 に答える
13

現在のパッケージをTravisにインストールします。これにより、ファイルの使用が回避されrequirements.txtます。例えば:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py
于 2013-08-21T16:23:17.003 に答える
10

私はそのようなことをすることをお勧めしません。何度も述べたように、間違いなく同じリストであるとは限りませんinstall_requiresしかし、 piprequirements.txtのプライベート内部APIに関連する誤解を招く答えがたくさんあるので、より賢明な代替案を検討する価値があるかもしれません...

setuptoolsスクリプトからファイルを解析するためにpipは必要ありません。setuptoolsプロジェクトには、最上位のパッケージに必要なすべてのツールがすでに含まれています。requirements.txt setup.pypkg_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,
)

注意の言葉

すでに述べたように、これはお勧めできません。ファイルと「インストールの依存関係」のrequirements.txtリストは2つの異なる概念であり、互換性はありません。

setup.pyただし、読み取るインストールスクリプトを作成する場合は、ファイルが「ソースディストリビューション」(sdistrequirements.txt )に含まれていることを確認してください。含まれていない場合、インストールは明らかに失敗します。requirements.txt


于 2020-01-29T16:25:41.980 に答える
6

この単純なアプローチは、から要件ファイルを読み取りますsetup.pyDmitirySによる回答のバリエーションです。この回答はPython3.6以降とのみ互換性があります。

DSごとに、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コメントを削除することに注意してください。また、私の経験によれば、要件ファイルにバンドルするために特別な手順を実行する必要はないようです。

于 2017-02-03T21:15:40.487 に答える
5

次のインターフェイスは、pip10で非推奨になりました。

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 != ''
    ]
于 2018-05-16T10:28:31.243 に答える
4

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,
    ...
)
于 2013-11-08T19:18:45.327 に答える
4

ユーザーに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'))
)
于 2014-08-08T10:33:52.750 に答える
2

行動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']
于 2014-07-26T18:44:20.577 に答える
1

このために再利用可能な関数を作成しました。実際には、要件ファイルのディレクトリ全体を解析し、それらを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)
于 2015-12-23T02:12:47.227 に答える
0

別の可能な解決策...

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,
    ...
)
于 2017-07-20T19:12:41.007 に答える
-1

このSOの質問からの私の答えをクロスポストして、別の単純な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次に、プロジェクトのルートディレクトリの下にすべての要件を入力します。

于 2019-07-24T21:53:49.710 に答える
-2

環境マーカーを次のように解析するさらに別のparse_requirementsハック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とbinarydistの両方をサポートする必要があります。

他の人が述べているように、parse_requirementsいくつかの欠点があるので、これは公開プロジェクトで行うべきことではありませんが、内部/個人プロジェクトには十分かもしれません。

于 2016-12-15T19:42:19.937 に答える
-2

これは私がしました:

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]}')
于 2020-03-24T03:38:46.483 に答える
-4

これは、現在の環境マーカーに従って解析およびフィルタリングするRomainの回答pip 9.0.1に基づく完全なハック(でテスト済み)です。requirements.txt

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)
于 2016-11-10T13:24:56.410 に答える