Ссылка require.txt для файла install_requires kwarg в файле setuptools setup.py


279

У меня есть requirements.txtфайл, который я использую с Travis-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
install_requiresиспользуется для объявления зависимостей от пакетов, которые требуются для работы пакета и используются разработчиком пакета, в то время requirements.txtкак используется для автоматизации установки сред, что позволяет устанавливать дополнительное программное обеспечение и выполнять закрепление версии, и используются системными администраторами, внедряющими пакет. Их роль и целевая аудитория существенно различаются, поэтому попытка объединить их, как пожелания ОП, является настоящей ошибкой дизайна.
Зарт

7
Мои 2 цента. Не используйте файл require.txt в вашем файле setup.py. Цели разные, ared caremad.io/2013/07/setup-vs-requirement
Филипп Омбредан

3
Я вижу много сложных ответов. Что не так с простым старым [line.strip() for line in open("requirements.txt").readlines()]?
Фелипе Шнайдер

Не рекомендуется делать это. Но если это действительно необходимо, то это просто: у setuptools уже есть все необходимоеpkg_resources.parse_requirements()
sinoroc

Ответы:


246

Вы можете перевернуть его вокруг и список зависимостей в setup.pyи имеют один символ - это точка .- в requirements.txtвместо этого.


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

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

Это не фильтрует маркеры среды, хотя.


В старых версиях pip, а точнее старше 6.0 , есть открытый API, который может быть использован для достижения этой цели. Файл требований может содержать комментарии ( #) и может включать некоторые другие файлы ( --requirementили -r). Таким образом, если вы действительно хотите разобрать a, 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
)

26
Что делать, если у пользователя не установлен pip? Ка-бум?
Гринго Суаве

83
@GringoSuave Если у пользователя не установлен pip, он должен сначала установить его.
Геттли

7
Вам также необходимо указать URL-адреса в файле требований, если есть какие-либо строки -e или -f («редактируемые» git repo), указывающие на пакеты не-pypi. Используйте это:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
Конфорки

91
Вы действительно не хотите этого делать. Говоря как pip, сопровождающий pip, вообще не поддерживает его как API. Фактически пункт 1.6 (следующая версия в настоящее время) перемещает эту функцию.
Дональд Штффт

26
Это больше не должно быть принятым ответом, если это когда-либо должно было иметь. Это явно сломано. Даже когда это работает, это явно не нужно. Поскольку по pipумолчанию для разбора зависимости от setup.pyв отсутствии requirements.txt, то простой ответ проницательно отметил Tobu ниже, чтобы получить список всех зависимостей в setup.pyи удалить requirements.txt. Для приложений, требующих и того, и другого, просто уменьшите список зависимостей requirements.txtдо простого .символа. Готово.
Сесил Карри

195

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

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

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

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

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

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

Итак, вынос:

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

Ссылки:


10
Это одно из лучших объяснений, которое позволило мне навести порядок в этом беспорядке, называемом установкой пакета! :)
Кунави

6
Мне до сих пор не ясно, почему разработчик должен держать под контролем версию requirements.txtвместе с источником пакета, который содержит конкретные / замороженные требования для установки или тестирования. Наверняка setup.pyможно использовать для этого в рамках самого проекта? Я могу только представить себе использование такого файла для инструментов, используемых для поддержки управления проектом (например, рефакторинг, создание релизов и т. Д.).
Сэм Брайтман

2
@samBrightman Я полностью согласен, я не думаю, что библиотечные пакеты или пакеты приложений должны фиксировать свой файл require.txt в хранилище с кодом. Я думаю, что это должен быть артефакт, сгенерированный во время тестирования сборки, а затем используемый для документирования манифеста сборки и, в конечном итоге, создания артефакта развертывания.
Джонатан Хансон,

6
То есть, вы говорите, что requirements.txtесть больше документации о состоянии мира, в котором была создана данная сборка, даже если она обычно не используется в самом процессе сборки? В этом есть смысл. Тем не менее, похоже, что несколько систем полагаются на дублирование: Travis устанавливает некоторые стандартные (старые) пакеты в вашем virtualenv и говорит использовать requirements.txt. Если я спрашиваю, как обеспечить использование зависимостей setup.py, люди настаивают на том, чтобы я использовал их requirements.txt.
Сэм Брайтман,

2
Лучший совет, который вы можете получить из всего этого, - найти подходящую модель, хорошо ее документировать и убедиться, что все, с кем вы работаете, это понимают. Подумайте, почему вы делаете каждый бит и имеет ли это смысл для вашего случая использования. И старайтесь как можно лучше читать о текущем состоянии сборки, упаковки и публикации в Python, на случай, если дела пойдут лучше. Но не задерживай дыхание.
Джонатан Хэнсон

90

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

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

import os
from setuptools import setup

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

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

5
Хотя это полезно, спецификация требований меняется с декларативной на императивную. Это делает невозможным для некоторых инструментов выяснить, каковы ваши требования. Например, PyCharm предлагает автоматическую установку всех требований, указанных в install_requires. Однако это не работает, если вы не используете декларативный синтаксис.
Петр Доброгост

55
@PiotrDobrogost Возможно, разработчик PyCharm должен исправить свою программу тогда. setup.pyэто программа, которую нужно запустить, а не файл данных, который нужно проанализировать. Это не делает этот ответ хуже.
Фредрик Бреннан

5
Я просто указываю на возможные проблемы; этот ответ прекрасно. Проблема не только в том, что PyCharm «скрывает» информацию за кодом. Это универсальная проблема, и поэтому существует общий переход к декларативной спецификации метаданных в упаковке Python.
Петр Доброгост

33
Работает нормально, если вы положили include requirements.txtв свой, MANIFEST.inиначе вы не сможете установить свою библиотеку из исходного дистрибутива.
Панкрат

4
Я знаю, что это старый вопрос, но вы можете, по крайней мере, в настоящее время настроить PyCharm для анализа файла требований в Предпочтения-> Инструменты-> Инструменты Python Integrated-> Файл требований пакета
lekksi

64

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

.

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


9
Но в этом случае он также попытается установить мое приложение тоже. Что, если мне это не нужно и я хочу только установить install_requires?
Праздник

2
Чтобы уточнить, что спрашивает @ffeast, если требования существуют только в setup.py, есть ли способ установить требования (эквивалент pip install -r requirements.txt ) без установки самого пакета?
haridsv

1
@ffeast @haridsv -e .должно быть достаточно. Проверьте эту страницу: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter все еще пытается установить само приложение. Это не то, что мы хотим
праздник

38

Хотя это и не точный ответ на этот вопрос, я рекомендую пост в блоге Дональда Стиффта на 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. Там нет двуличия, и цель обоих артефактов сохраняется.


7
Это не работает, если вы хотите предоставить пакет для установки другими пользователями 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.
Малина

2
@Malina Пакет здесь отлично устанавливается без requirements.txt. В этом весь смысл. Обновлен вопрос, чтобы сделать вещи более понятными. Также обновлена ​​устаревшая ссылка на пост в блоге.
знаменитый Гаркин

поэтому при запуске setup.py он будет вызывать require.txt для определенных версий файлов, перечисленных в stup.py?
dtracers

Это наоборот @dtracers. require.txt указывает на сам пакет, где могут быть найдены зависимости setup.py. Таким образом, при установке с использованием требований, он работает и при установке через pip, он тоже работает - в обоих случаях с использованием зависимостей setup.py, но также позволяет устанавливать больше вещей при использовании
require.txt

20

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

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


@ Tommy Попробуйте это: caremad.io/2013/07/setup-vs-requirement Это та же ссылка, что и в другом ответе.
amit

18

Большинство других ответов выше не работают с текущей версией 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.


14

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

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

2
На сегодняшний день это лучшее сочетание «правильного» и «практического». Я бы добавил, что если после прохождения тестов вы сможете заставить Travis сгенерировать pip freezeфайл require.txt и экспортировать этот файл куда-нибудь как артефакт (например, S3 или что-то еще), то у вас будет отличный способ повторной установки именно того, что вы испытания.
Джонатан Хэнсон

4

from pip.req import parse_requirements не работает для меня, и я думаю, что это для пустых строк в моем require.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

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

4

В пипе 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 != ''
    ]

Этот простой подход работает более 90% времени. Для тех, кто использует Python 3.6+, я написал ответ, который является его pathlibвариантом .
Acumenus

3

Этот простой подход читает файл требований из setup.py. Это вариант ответа Дмитрия Сергеевича. . Этот ответ совместим только с Python 3.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будут лишены комментариев. Кроме того, по моему опыту, вам, очевидно, не нужно предпринимать каких-либо особых шагов для включения в файл требований.


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

1
Используйте unsafe_name, чтобы получить версию с подчеркиванием:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds

5
Как указано в другом месте, PIP - это приложение, а не библиотека. Он не имеет публично согласованного API, и его импорт в ваш код не поддерживается. Не удивительно, что у него неожиданное поведение; его внутренние функции никогда не предназначались для использования таким образом.
Джонатан Хэнсон

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)

очень хорошо! даже обрабатывает рекурсивные требования с последней версией :)
пунктами

@amohr Спасибо! Я недавно обновил его для еще более позднего пипса, я не уверен, почему они действуют так, как они есть, перемещая вещи в pip._internal... Если вы не предоставляете работающий внешний API, то вы не должны ломать все эти которые используют все, что вы предоставляете.
Trevorj

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

откуда treeвзялась?
Франческо Бой

@FrancescoBoi, если вы простите меня за то, что я не представляю полностью работающее решение ... tree - это всего лишь сканирование локальной файловой системы (очень похоже на команду "tree" в linux). Кроме того, мое решение, приведенное выше, может не работать полностью на этом этапе, потому что pip постоянно обновляется, и я использовал внутренние компоненты pip.
Брайан Брюггеман

0

Я бы не рекомендовал делать такие вещи. Как уже упоминалось несколько раз install_requiresи requirements.txtопределенно не должны быть в одном списке. Но так как есть много вводящих в заблуждение ответов, связанных с частными внутренними API pip , возможно, стоит поискать более разумные альтернативы ...

Там нет необходимости для пипа , чтобы разобрать requirements.txtфайл из Setuptools 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,
)

Если вы не знали, причина, по которой многие (включая меня) использовали pipсинтаксический анализ, а не pkg_resourcesдо 2015 года, это ошибки, такие как github.com/pypa/setuptools/issues/470 . Это точное исправление в настоящее время, но я все еще немного боюсь его использовать, поскольку обе реализации, похоже, разрабатываются отдельно.
Trevorj

@trevorj Спасибо за указание, я не знал. Факт в настоящее время это работает, и вовлечение пипса кажется мне нелепой идеей (особенно таким образом). Посмотрите на другие ответы, большинство из которых выглядят как небольшие вариации той же опрометчивой идеи, без какого-либо предупреждения. И новички могут просто следовать этой тенденции. Надеемся, что такие инициативы, как PEP517 и PEP518, отвлекут сообщество от этого безумия.
sinoroc

-1

Перекрестная публикация моего ответа из этого ТАКОГО вопроса для другого простого, 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корневой каталог проекта.


-1

Я сделал это:

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

-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, так и бинарные диски.

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


пип 20.1 изменил свой API, и маркеры больше не доступны через parse_requirements(), так что теперь это не получается.
Туукка Мустонен

-3

Вот полный взлом (проверенный 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)

1
Это только частично правда. Если вы звоните, r.match_markers()вы на самом деле оцениваете маркеры, что правильно сделать для sdist. Однако, если вы создаете двоичный дистрибутив (например, wheel), пакет будет перечислять только те библиотеки, которые соответствуют вашей среде сборки.
Туукка Мустонен

@TuukkaMustonen, так где найти это wheel environment(если это то, что человек пытается сделать), чтобы оценить маркеры против него?
анатолий техтоник

См. Stackoverflow.com/a/41172125/165629, который также должен поддерживать bdist_wheel. Он не оценивает маркеры, он просто добавляет их к extras_require.
Туукка Мустонен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.