Довідкові вимоги.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 копійки. Не використовуйте вимоги.txt у вашій setup.py. Цілі різні, ared caremad.io/2013/07/setup-vs-requirement
Philippe

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? Ка-бум?
Gringo Suave

82
@GringoSuave Якщо користувач не встановив pip, йому потрібно спочатку встановити його.
guettli

7
Вам також потрібно надати URL-адреси у вашому файлі вимог, якщо є рядки -e або -f ("редаговані" git repo), що вказують на пакети non-pypi. Використовуйте це:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
варильні панелі

91
Ти справді не хочеш цього робити. Якщо говорити як сервіс, який підтримує піп, то файл pip взагалі не може називатися таким, як API. Фактично pip 1.6 (наступна версія на даний момент) переміщує цю функцію.
Donald Stufft

26
Це більше не повинно бути прийнятою відповіддю, якщо воно коли-небудь повинно бути. Це кричуще зламано. Навіть коли це спрацювало, це явно непотрібно. Оскільки pipза замовчуванням для розбору залежностей setup.pyвідсутній requirements.txt, простий відповідь, влучно зазначений Тобу нижче, - перерахувати всі залежності setup.pyта усунути їх requirements.txt. Для додатків, які вимагають обох, просто зменшіть список залежностей requirements.txtдо лише .символу. Зроблено.
Сесіль Карі

194

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

Мета автора пакета, визначаючи залежності, - сказати, "де б ви не встановили цей пакет, це інші потрібні вам пакети, щоб цей пакет працював".

На відміну від цього, автор розгортання (який може бути однаковою людиною в різний час) має іншу роботу, оскільки вони говорять "ось список пакетів, які ми зібрали разом і протестували, і які мені зараз потрібно встановити".

Автор пакета пише для найрізноманітніших сценаріїв, тому що вони розміщують свої роботи там, щоб вони використовувались способами, про які вони можуть не знати, і не мають можливості знати, які пакунки будуть встановлені поряд із їх пакетом. Щоб бути добрим сусідом і уникати конфліктів версій залежності з іншими пакетами, їм потрібно вказати якомога ширший діапазон версій залежностей, наскільки це можливо. Це те , що install_requiresв setup.pyробить.

Автор розгортання пише для зовсім іншої, дуже конкретної мети: одиничного екземпляра встановленого додатка чи послуги, встановленого на певному комп'ютері. Щоб точно контролювати розгортання та бути впевненим, що правильні пакети перевірені та розгорнуті, автор розгортання повинен вказати точну версію та джерело розташування кожного встановленого пакету, включаючи залежності та залежності залежностей. За допомогою цієї специфікації розгортання можна повторно застосувати до декількох машин або протестувати на тестовій машині, і автор розгортання може бути впевнений, що одні й ті ж пакети розгортаються щоразу. Це те, що requirements.txtробить.

Тож ви можете бачити, що в той час як вони обидва виглядають як великий список пакетів та версій, у цих двох речей є дуже різні завдання. І це, безумовно, легко змішати це і зрозуміти неправильно! Але правильний спосіб подумати над цим - requirements.txtце "відповідь" на "питання", поставлене вимогами у всіх різних setup.pyфайлах пакунків. Замість того, щоб писати це вручну, це часто генерується, повідомляючи pip переглянути всі setup.pyфайли в наборі потрібних пакетів, знайти набір пакетів, які, на його думку, відповідають усім вимогам, а потім, після їх встановлення, "заморожують" "цей список пакетів у текстовий файл ( pip freezeзвідси походить назва).

Тож винос:

  • setup.pyслід оголосити найбільш слабкі можливі версії залежностей, які досі працюють. Його завдання - сказати, з чим може працювати певний пакет.
  • requirements.txtце маніфест розгортання, який визначає всю роботу з монтажу, і не слід вважати його прив'язаним до якогось одного пакету. Його завдання - оголосити вичерпний перелік усіх необхідних пакетів, щоб зробити роботу розгортання.
  • Оскільки ці дві речі мають такий різний зміст та причини, що існують, просто неможливо просто скопіювати одну в іншу.

Список літератури:


10
Це одне з найкращих пояснень, яке дозволило мені навести певний порядок у тому безладі, який називається встановлення пакета! :)
Kounavi

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

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

6
Отже, ви говорите, requirements.txtчи більше документації для стану світу, який створив певну збірку, навіть якщо вона зазвичай не використовується в самому процесі збирання? Що має сенс. Однак схоже, що кілька систем покладаються на дублювання: Travis встановлює деякі (старі) пакети у вашому virtualenv і каже використовувати requirements.txt. Якщо я запитую, як забезпечити використання залежностей якнайшвидше setup.py, люди наполягають на тому, щоб я використовував requirements.txt.
Сем Брайтман

2
Найкраща порада, яку ви можете отримати з будь-якого з цього, - це знайти модель, яка працює для вас, добре задокументувати її та переконатися, що її розуміють усі, з ким ви працюєте. Подумайте, чому ви робите кожен шматочок і чи справді це має сенс для вашого випадку використання. І постарайтеся залишатись настільки добре прочитаними про сучасний стан будівництва, упаковки та публікації на Python, на всякий випадок, якщо все покращиться. Але не затримуй дихання.
Джонатан Гансон

89

Він не може прийняти обробку файлу. 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-

64

Файли з вимогами використовують розширений формат pip, який корисний лише у тому випадку, якщо вам потрібно доповнити ваші 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 він все ще намагається встановити додаток сам. Це не те, чого ми хочемо
ffeast

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 / setup.py, вони не встановлюються pip install my-package. Мені не вдалося визначити, як надати пакет для інших, що включає залежності, без явного зазначення їх у setup.py. Хочеться дізнатися, чи хтось придумав, як утримати НУМУ, дозволяючи іншим встановлювати мій пакунок + залежності, не завантажуючи файл вимог і не викликаючи вручну pip install -r my-package/requirements.txt.
Маліна

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

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

Це навпаки @dtracers. needs.txt вказує на сам пакет, де можна було взяти залежності від setup.py. Отже, при встановленні з використанням вимог він працює, а також при встановленні через pip - це працює і в обох випадках, використовуючи залежності setup.py, але також дозволяє встановлювати більше речей при використанні
вимог.txt

20

Використовувати parse_requirementsпроблематично, оскільки API pip не публікується та не підтримується. У 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 Це те саме посилання, що було розміщено в іншій відповіді.
Аміт

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. Це, мабуть, не найкращий спосіб зробити це, оскільки, як говорили вище плакати, pip насправді не підтримує API.


14

Встановіть поточний пакет у Travis. Це дозволяє уникнути використання requirements.txtфайлу. Наприклад:

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

2
Це, безумовно, найкраще поєднання "правильного" та "практичного". Я додам, що якщо після проходження тестів ви зможете Travis генерувати вимоги.txt pip freezeта експортувати цей файл кудись як артефакт (наприклад, S3 чи щось подібне), то у вас буде чудовий спосіб повторно встановити саме те, що ви перевірений.
Джонатан Гансон

4

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

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

Наступний інтерфейс став застарілим у 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.split('#', 1)[0].strip(' \t\n') for line in f
        ] if s != ''
    ]

Цей простий підхід працює понад 90% часу. Для тих, хто використовує Python 3.6+, я написав відповідь, що є його pathlibваріантом .
Акумен

3

Цей простий підхід читає файл вимог setup.py. Ця зміна відповіді по Dmitiry S. . Ця відповідь сумісна лише з 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, якщо ви пробачите мені трохи за те, що не представив повністю працюючого рішення ... дерево насправді є лише скануванням локальної файлової системи (дуже схоже на команду "дерево" в Linux). Крім того, моє рішення вище може не працювати повністю в даний момент, оскільки pip постійно оновлюється, і я використовував внутрішні протоколи pip.
Брайан Брюггман

0

Я б не рекомендував робити таке. Як уже згадувалося неодноразово install_requiresі requirements.txtнапевно не повинно бути одного і того ж списку. Але оскільки існує багато оманливих відповідей навколо залучення приватних внутрішніх API- файлів pip , можливо, варто поглянути на більш безпечні альтернативи ...

Немає потреби в 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/isissue/470 . Це саме зараз виправлено, але я все ще трохи боюся його використовувати, оскільки, як видається, обидві реалізації розробляються окремо.
trevorj

@trevorj Дякую за вказівку на це, я не знав. Факт нині працює, і залучення до цього моменту здається смішною ідеєю (особливо таким чином). Подивіться на інші відповіді, більшість здається незначними варіаціями тієї ж необдуманої ідеї без ледь-якого попередження. І новачки можуть просто слідувати цій тенденції. Сподіваємось, такі ініціативи, як PEP517 та PEP518, відвернуть громаду від цього божевілля.
sinoroc

-1

Перехресне опублікування моєї відповіді з цього питання SO для іншого простого рішення, що підтверджує версію файлу.

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нього є кілька недоліків, тож це не те, що слід робити в громадських проектах, але це може бути достатньо для внутрішніх / особистих проектів.


pip 20.1 змінив API і маркери вже недоступні через parse_requirements(), тому це тепер не вдається.
Tuukka Mustonen

-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. Однак, якщо ви будуєте бінарний dist (наприклад, колесо), пакет містить лише ті бібліотеки, які відповідають вашому середовищу збирання.
Tuukka Mustonen

@TuukkaMustonen, тож де це знайти wheel environment(якщо це намагається зробити людина), щоб оцінити маркери проти цього?
anatoly techtonik

Див. Stackoverflow.com/a/41172125/165629, який також повинен підтримувати bdist_wheel. Він не оцінює маркери, він просто додає їх extras_require.
Tuukka Mustonen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.