Справочные требования.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
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 vs Requirements files из руководства пользователя по упаковке Python.
он не может взять дескриптор файла. Элемент
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чтобы заморозить весь пакет, установленный на известные рабочие версии. Если вам не нужны дополнительные ограничения, используйте только asetup.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