Справочные требования.txt для установки требуется кварг в setuptools setup.py файл?



у меня есть requirements.txt файл, который я использую с Travis-CI. Кажется глупым дублировать требования в обоих requirements.txt и setup.py, поэтому я надеялся передать дескриптор файла install_requires kwarg в setuptools.setup.



это возможно? Если да, то как мне это сделать?



вот мой requirements.txt file:



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

16 ответов:

вы можете перевернуть его и перечислить зависимости в setup.py и есть один символ-точка . - in .


альтернативно, даже если не рекомендуется, все еще можно разобрать requirements.txt файл (если он не ссылается на какие-либо внешние требования по URL) со следующим взломом (проверено с помощью pip 9.0.1):

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

это не фильтрует [маркеры среды] (https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers) хотя.


в старых версиях 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
)

на первый взгляд, кажется, что requirements.txt и setup.py глупые дубликаты, но важно понимать, что в то время как форма похожа, предполагаемая функция очень отличается.

цель автора пакета при указании зависимостей состоит в том, чтобы сказать: "везде, где вы устанавливаете этот пакет, это другие пакеты, которые вам нужны, чтобы этот пакет работал."

напротив, автор развертывания (который может быть одним и тем же человеком в другом time) имеет другую работу, в которой они говорят: "Вот список пакетов, которые мы собрали вместе и протестировали, и которые мне теперь нужно установить".

автор пакета пишет для широкого спектра сценариев, потому что они помещают свою работу там, чтобы использовать ее способами, о которых они могут не знать, и не имеют возможности узнать, какие пакеты будут установлены рядом с их пакетом. Чтобы быть хорошим соседом и избежать конфликтов версий зависимостей с другими пакетами, им нужно чтобы указать как можно более широкий диапазон версий зависимостей. Вот что install_requires на setup.py делает.

автор развертывания пишет для совершенно другой, очень конкретной цели: один экземпляр установленного приложения или службы, установленного на определенном компьютере. Чтобы точно контролировать развертывание и быть уверенным в том, что правильные пакеты протестированы и развернуты, автор развертывания должен указать точную версию и Исходное расположение каждого пакета установлен, включая зависимости и зависимости зависимостей. С помощью этой спецификации развертывание может быть повторно применено к нескольким машинам или протестировано на тестовой машине, и автор развертывания может быть уверен, что каждый раз развертываются одни и те же пакеты. Вот что такое requirements.txt делает.

так что вы можете видеть, что, хотя они оба выглядят как большой список пакетов и версий, эти две вещи очень разные работы. И это, безусловно, легко перепутать это и получить его неправильно! Но правильный способ думать об этом заключается в том, что requirements.txt Это " ответ "на" вопрос", поставленный требованиями во всех различных setup.py файлы пакета. Вместо того, чтобы писать его вручную, он часто генерируется, говоря Пип, чтобы посмотреть на все setup.py файлы в наборе нужных пакетов, найти набор пакетов, который, по его мнению, соответствует всем требованиям, а затем, после их установки, "заморозить" этот список пакетов в текстовый файл (вот где pip freeze название происходит от).

Итак, вывод:

  • setup.py следует объявить самые свободные возможные версии зависимостей, которые все еще работоспособны. Его задача-сказать, с чем может работать конкретный пакет.
  • requirements.txt - это манифест развертывания, который определяет все задание установки и не должен рассматриваться как привязанный к какому-либо одному пакету. Его задача-объявить исчерпывающий список всех необходимых пакетов для выполнения развертывания.
  • потому что эти две вещи имеют такое разное содержание и причины для существования, что невозможно просто скопировать одну в другую.

ссылки:

он не может взять дескриптор файла. Элемент install_requires аргумент can только строка или список строк.

вы можете, конечно, прочитать свой файл в скрипте установки и передать его в виде списка строк в install_requires.

import os
from setuptools import setup

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

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

файлы требований используют расширенный формат pip, который полезен только в том случае, если вам нужно дополнить ваш setup.py С более сильными ограничениями, например, указывая точные URL-адреса, из которых должны исходить некоторые зависимости, или вывод pip freeze чтобы заморозить весь пакет, установленный на известные рабочие версии. Если вам не нужны дополнительные ограничения, используйте только a setup.py. Если вы чувствуете, что вам действительно нужно отправить requirements.txt в любом случае, вы можете сделать это в одну строку:

.

он будет действительны и относятся именно к содержимому setup.py то есть в том же каталоге.

хотя это не точный ответ на вопрос, я рекомендую сообщение в блоге Дональда Стаффта по адресу https://caremad.io/2013/07/setup-vs-requirement/ для хорошего подхода к этой проблеме. Я использую его с большим успехом.

короче, requirements.txt не setup.py альтернатива, но дополнение развертывания. Сохраните соответствующую абстракцию зависимостей пакетов в setup.py. Набор requirements.txt или более из них, чтобы получить определенные версии зависимостей пакетов для разработки, тестирования или производства.

например, с пакетами, включенными в репо под deps/:

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

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

pip выполняет пакет setup.py и устанавливает конкретные версии зависимостей объявлен в install_requires. Там нет двуличия и цель обоих артефактов сохраняется.

большинство других ответов выше не работают с текущей версией API pip. Вот правильный * способ сделать это с текущей версией 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 действительно не поддерживает API.

используя parse_requirements проблематично, потому что API pip не является публично документированным и поддерживаемым. В pip 1.6 эта функция фактически перемещается, поэтому существующее ее использование, скорее всего, сломается.

более надежный способ устранения дублирования между setup.py и requirements.txt для конкретных зависимостей в setup.py и потом поставить -e . в своем . Некоторая информация от одного из pip разработчиков о том, почему это лучший способ, чтобы здесь: https://caremad.io/blog/setup-vs-requirement/

установите текущий пакет в Travis. Это позволяет избежать использования . Например:

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

Если вы не хотите заставлять своих пользователей устанавливать 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'))
)

from pip.req import parse_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,
    ...
)

ОСТОРОЖНО 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']

Я создал отдельную функцию для этого. Он фактически анализирует весь каталог файлов требований и устанавливает их в 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

еще одно возможное решение...

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

еще один 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, так и двоичные диски.

как утверждают другие,parse_requirements имеет несколько недостатков, так что это не то, что вы должны делать на общественных проектах, но это может быть достаточно для внутренних/личных проектов.

следующий интерфейс стал устаревшим в 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 != ''
    ]

вот полный Хак (проверено с 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)

Comments

    Ничего не найдено.