web-dev-qa-db-ja.com

setuptools setup.pyファイル内のinstall_requires kwargのrequirements.txtを参照してください。

Travis-CIで使用しているrequirements.txtファイルがあります。 requirements.txtsetup.pyの両方で要件を複製するのは馬鹿げているようです。そのため、install_requiressetuptools.setup 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
244
blz

ひっくり返して、依存関係をsetup.pyにリストし、代わりに.に単一の文字(ドットrequirements.txt)を含めることができます。


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

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
)
215
Romain Hardouin

一見すると、requirements.txtsetup.pyは馬鹿げた重複であるように見えますが、フォームは似ていますが、目的の関数は非常に異なることを理解することが重要です。

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

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

パッケージ作成者は、さまざまなシナリオを作成します。なぜなら、彼らは自分の仕事を知らない方法で使用するためにそこに置き、パッケージと一緒にインストールされるパッケージを知る方法がないからです。良い隣人になり、他のパッケージとの依存関係のバージョンの競合を避けるために、彼らはおそらく動作可能な範囲の依存関係のバージョンの広い範囲を指定する必要があります。これは、install_requiressetup.pyが行うことです。

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

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

持ち帰り:

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

参照:

137
Jonathan Hanson

ファイルハンドルを取得できません。 install_requires引数には 文字列または文字列のリストのみ を指定できます。

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

import os
from setuptools import setup

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

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

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

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

例えば。 deps/の下のレポにパッケージが含まれている場合:

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

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

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

37
famousgarkin

上記の他の回答のほとんどは、現在のバージョンの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を維持していないので、おそらくそれを行うための最良の方法ではありません。

19
fabianvf

parse_requirementsの使用には問題があります。これは、pip APIが公に文書化されておらず、サポートされていないためです。 pip 1.6では、その機能は実際に動いているため、既存の機能の使用は中断される可能性があります。

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

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

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

ユーザーに強制的に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'))
)
5
reubano

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,
    ...
)
4
Diego Navarro

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']
3
MikeTwo

このために再利用可能な関数を作成しました。実際には、要件ファイルのディレクトリ全体を解析し、それらをextras_requireに設定します。

最新の情報は常にここで入手できます: https://Gist.github.com/akatrevorjay/293c26fefa24a7b812f5

import glob
import itertools
import os

from setuptools import find_packages, setup

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:

    >>> _conf = dict(
    ...     name='mainline',
    ...     version='0.0.1',
    ...     description='Mainline',
    ...     author='Trevor Joynson <[email protected],io>',
    ...     url='https://trevor.joynson.io',
    ...     namespace_packages=['mainline'],
    ...     packages=find_packages(),
    ...     Zip_safe=False,
    ...     include_package_data=True,
    ... )
    >>> _conf.update(setup_requirements())
    >>> 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
2
trevorj

次のインターフェースは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.strip(' \n') for line in f
        ] if not s.startswith('#') and s != ''
    ]
2
Dmitriy Sintsov

別の可能な解決策...

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,
    ...
)
1
Brian Bruggeman

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 __== '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requriements],
        ...
    )

次に、プロジェクトルートディレクトリの下のrequirements.txtの下にすべての要件を追加します。

0
Scrotch

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

D.S。 ごとに、requirements.txtは特定のバージョン番号で具体的な要件を文書化できますが、setup.pyは緩やかなバージョン範囲で抽象的な要件を文書化できます。

以下は、私のsetup.pyの抜粋です。

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/
    requirements = (Path(__file__).parent / filename).read_text().strip().split('\n')
    requirements = [r.strip() for r in requirements]
    requirements = [r for r in sorted(requirements) if r and not r.startswith('#')]
    return requirements

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

私の経験では、要件ファイルにバンドルするために特別な手順を実行する必要はないようです。

0
A-B-B