webentwicklung-frage-antwort-db.com.de

Verweisen Sie auf die Datei requirements.txt für install_requires kwarg in der Datei setuptools setup.py

Ich habe eine requirements.txt - Datei, die ich mit Travis-CI verwende. Es erscheint unsinnig, die Anforderungen sowohl in requirements.txt Als auch in setup.py Zu duplizieren, und ich hatte gehofft, ein Datei-Handle an das install_requires - Kwarg in setuptools.setup Zu übergeben.

Ist das möglich? Wenn ja, wie soll ich vorgehen?

Hier ist meine requirements.txt Datei:

guessit>=0.5.2
tvdb_api>=1.8.2
hachoir-metadata>=1.3.3
hachoir-core>=1.3.3
hachoir-parser>=1.3.4
244
blz

Sie können es umdrehen und die Abhängigkeiten in setup.py Auflisten und stattdessen ein einzelnes Zeichen - einen Punkt . - in requirements.txt Eingeben.


Alternativ kann die Datei requirements.txt (Falls sie keine externen Anforderungen per URL enthält) mit folgendem Hack (getestet mit pip 9.0.1) Analysiert werden, auch wenn dies nicht empfohlen wird:

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

Dies filtert jedoch nicht mgebungsmarkierungen .


In alten Versionen von pip, genauer gesagt älter als 6. , gibt es eine öffentliche API, mit der dies erreicht werden kann. Eine Anforderungsdatei kann Kommentare (#) Und einige andere Dateien (--requirement Oder -r) Enthalten. Wenn Sie also wirklich einen requirements.txt Analysieren möchten, können Sie den Pip-Parser verwenden:

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

Auf den ersten Blick scheint es, dass requirements.txt und setup.py sind alberne Duplikate, aber es ist wichtig zu verstehen, dass die beabsichtigte Funktion sehr unterschiedlich ist, obwohl die Form ähnlich ist.

Das Ziel eines Paketautors ist es, bei der Angabe von Abhängigkeiten zu sagen: "Wo immer Sie dieses Paket installieren, sind dies die anderen Pakete, die Sie benötigen, damit dieses Paket funktioniert."

Im Gegensatz dazu hat der Bereitstellungsautor (der möglicherweise dieselbe Person zu einem anderen Zeitpunkt ist) einen anderen Job: "Hier ist die Liste der Pakete, die wir zusammengestellt und getestet haben und die ich jetzt installieren muss".

Der Autor des Pakets schreibt für eine Vielzahl von Szenarien, da sie ihre Arbeit so einsetzen, dass sie sie möglicherweise nicht kennen und nicht wissen, welche Pakete neben ihrem Paket installiert werden. Um ein guter Nachbar zu sein und Konflikte zwischen Abhängigkeitsversionen und anderen Paketen zu vermeiden, müssen sie einen möglichst großen Bereich von Abhängigkeitsversionen angeben, die möglicherweise funktionieren. Das ist was install_requires im setup.py tut.

Der Bereitstellungsautor schreibt für ein ganz anderes, ganz bestimmtes Ziel: eine einzelne Instanz einer installierten Anwendung oder eines installierten Dienstes auf einem bestimmten Computer. Um eine Bereitstellung genau zu steuern und sicherzustellen, dass die richtigen Pakete getestet und bereitgestellt werden, muss der Bereitstellungsautor die genaue Version und den Quellspeicherort jedes zu installierenden Pakets angeben, einschließlich der Abhängigkeiten und Abhängigkeiten der Abhängigkeiten. Mit dieser Spezifikation kann eine Bereitstellung wiederholt auf mehrere Computer angewendet oder auf einem Testcomputer getestet werden, und der Bereitstellungsautor kann sich darauf verlassen, dass jedes Mal dieselben Pakete bereitgestellt werden. Das ist was für ein requirements.txt tut.

Sie sehen also, dass beide Pakete und Versionen zwar eine große Liste von Paketen und Versionen darstellen, diese beiden Dinge jedoch sehr unterschiedliche Aufgaben haben. Und es ist definitiv einfach, dies zu verwechseln und falsch zu verstehen! Aber der richtige Weg, darüber nachzudenken, ist, dass requirements.txt ist eine "Antwort" auf die "Frage", die die Anforderungen in allen verschiedenen setup.py Paketdateien. Anstatt es von Hand zu schreiben, wird es oft erzeugt, indem Pip angewiesen wird, sich alle setup.py -Dateien in einer Reihe gewünschter Pakete finden eine Reihe von Paketen, von denen angenommen wird, dass sie alle Anforderungen erfüllen, und dann, nachdem sie installiert wurden, diese Liste von Paketen in einer Textdatei "einfrieren" (hier wird pip freeze Name kommt von).

Also das Mitnehmen:

  • setup.py sollte die lockersten möglichen Abhängigkeitsversionen deklarieren, die noch funktionieren. Ihre Aufgabe ist es zu sagen, womit ein bestimmtes Paket arbeiten kann.
  • requirements.txt ist ein Bereitstellungsmanifest, das einen gesamten Installationsjob definiert und nicht an ein einzelnes Paket gebunden sein sollte. Seine Aufgabe ist es, eine vollständige Liste aller erforderlichen Pakete zu deklarieren, damit eine Bereitstellung funktioniert.
  • Da diese beiden Dinge so unterschiedliche Inhalte und Gründe haben, ist es nicht möglich, einfach eine in die andere zu kopieren.

Verweise:

145
Jonathan Hanson

Es kann keine Dateizugriffsnummer annehmen. Das Argument install_requires Kann nur eine Zeichenfolge oder eine Liste von Zeichenfolgen sein.

Sie können Ihre Datei natürlich im Setup-Skript lesen und als Liste von Zeichenfolgen an install_requires Übergeben.

import os
from setuptools import setup

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

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

Anforderungsdateien verwenden ein erweitertes Pip-Format. Dies ist nur nützlich, wenn Sie Ihren setup.py Durch stärkere Einschränkungen ergänzen müssen, z. B. die genauen URLs angeben, von denen einige Abhängigkeiten stammen müssen, oder die Ausgabe von pip freeze, um den gesamten Paketsatz auf bekannte Arbeitsversionen einzufrieren. Wenn Sie die zusätzlichen Einschränkungen nicht benötigen, verwenden Sie nur einen setup.py. Wenn Sie das Gefühl haben, dass Sie wirklich einen requirements.txt Versenden müssen, können Sie eine einzelne Zeile erstellen:

.

Es ist gültig und bezieht sich genau auf den Inhalt von setup.py, Der sich im selben Verzeichnis befindet.

61
Tobu

Obwohl ich keine genaue Antwort auf die Frage habe, empfehle ich Donald Stuffts Blog-Post unter https://caremad.io/2013/07/setup-vs-requirement/ , um dieses Problem gut zu lösen. Ich habe es mit großem Erfolg eingesetzt.

Kurz gesagt, requirements.txt Ist keine setup.py - Alternative, sondern eine Bereitstellungsergänzung. Behalten Sie eine angemessene Abstraktion der Paketabhängigkeiten in setup.py. Setzen Sie requirements.txt Oder mehr von ihnen, um bestimmte Versionen von Paketabhängigkeiten für Entwicklung, Test oder Produktion abzurufen.

Z.B. mit im Repo enthaltenen Paketen unter deps/:

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

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

pip führt das Paket setup.py aus und installiert die spezifischen Versionen der in install_requires deklarierten Abhängigkeiten. Es gibt keine Duplizität und der Zweck beider Artefakte bleibt erhalten.

37
famousgarkin

Die meisten anderen Antworten funktionieren mit der aktuellen Version der API von pip nicht. Hier ist der richtige * Weg, dies mit der aktuellen Version von pip zu tun (6.0.8 zum Zeitpunkt des Schreibens, funktionierte auch in 7.1.2. Sie können Ihre Version mit pip -V überprüfen).

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

* Richtig, da es die Art ist, parse_requirements mit dem aktuellen pip zu verwenden. Es ist wahrscheinlich immer noch nicht der beste Weg, dies zu tun, da pip, wie oben bereits erwähnt, keine API unterhält.

19
fabianvf

Die Verwendung von parse_requirements Ist problematisch, da die Pip-API nicht öffentlich dokumentiert und unterstützt wird. In Pip 1.6 ist diese Funktion tatsächlich in Bewegung, sodass vorhandene Verwendungen wahrscheinlich nicht mehr funktionieren.

Eine zuverlässigere Methode, um Doppelungen zwischen setup.py Und requirements.txt Zu vermeiden, besteht darin, Ihre Abhängigkeiten in setup.py Anzugeben und dann -e . In requirements.txt Einzufügen. Datei. Einige Informationen von einem der pip Entwickler darüber, warum dies ein besserer Weg ist, finden Sie hier: https://caremad.io/blog/setup-vs-requirement/

Installieren Sie das aktuelle Paket in Travis. Dies vermeidet die Verwendung eines requirements.txt Datei. Zum Beispiel:

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

Wenn Sie Ihre Benutzer nicht zwingen möchten, pip zu installieren, können Sie das Verhalten folgendermaßen emulieren:

import sys

from os import path as p

try:
    from setuptools import setup, find_packages
except ImportError:
    from distutils.core import setup, find_packages


def read(filename, parent=None):
    parent = (parent or __file__)

    try:
        with open(p.join(p.dirname(parent), filename)) as f:
            return f.read()
    except IOError:
        return ''


def parse_requirements(filename, parent=None):
    parent = (parent or __file__)
    filepath = p.join(p.dirname(parent), filename)
    content = read(filename, parent)

    for line_number, line in enumerate(content.splitlines(), 1):
        candidate = line.strip()

        if candidate.startswith('-r'):
            for item in parse_requirements(candidate[2:].strip(), filepath):
                yield item
        else:
            yield candidate

setup(
...
    install_requires=list(parse_requirements('requirements.txt'))
)
5
reubano

from pip.req import parse_requirements hat bei mir nicht funktioniert und ich denke, es ist für die Leerzeilen in meiner requirements.txt, aber diese Funktion funktioniert

def parse_requirements(requirements):
    with open(requirements) as f:
        return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#')]

reqs = parse_requirements(<requirements_path>)

setup(
    ...
    install_requires=reqs,
    ...
)
4
Diego Navarro

HÜTE DICH VOR parse_requirements VERHALTEN!

Bitte beachte, dass pip.req.parse_requirements ändert Unterstriche in Bindestriche. Das machte mich ein paar Tage wütend, bevor ich es entdeckte. Beispiel zur Veranschaulichung:

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

produziert

['example-with-underscores', 'example-with-dashes']
3
MikeTwo

Die folgende Schnittstelle ist in Pip 10 veraltet:

from pip.req import parse_requirements
from pip.download import PipSession

Also habe ich es einfach auf die einfache Textanalyse umgestellt:

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 != ''
    ]
3
Dmitriy Sintsov

Ich habe dafür eine wiederverwendbare Funktion erstellt. Tatsächlich wird ein ganzes Verzeichnis von Anforderungsdateien analysiert und auf extras_require gesetzt.

Neueste immer verfügbar hier: https://Gist.github.com/akatrevorjay/293c26fefa24a7b812f5

import glob
import itertools
import os

from setuptools import find_packages, setup

try:
    from pip._internal.req import parse_requirements
    from pip._internal.download import PipSession
except ImportError:
    from pip.req import parse_requirements
    from pip.download import PipSession


def setup_requirements(
        patterns=[
            'requirements.txt', 'requirements/*.txt', 'requirements/*.pip'
        ],
        combine=True,
):
    """
    Parse a glob of requirements and return a dictionary of setup() options.
    Create a dictionary that holds your options to setup() and update it using this.
    Pass that as kwargs into setup(), viola

    Any files that are not a standard option name (ie install, tests, setup) are added to extras_require with their
    basename minus ext. An extra key is added to extras_require: 'all', that contains all distinct reqs combined.

    Keep in mind all literally contains `all` packages in your extras.
    This means if you have conflicting packages across your extras, then you're going to have a bad time.
    (don't use all in these cases.)

    If you're running this for a Docker build, set `combine=True`.
    This will set `install_requires` to all distinct reqs combined.

    Example:

    >>> _conf = dict(
    ...     name='mainline',
    ...     version='0.0.1',
    ...     description='Mainline',
    ...     author='Trevor Joynson <[email protected],io>',
    ...     url='https://trevor.joynson.io',
    ...     namespace_packages=['mainline'],
    ...     packages=find_packages(),
    ...     Zip_safe=False,
    ...     include_package_data=True,
    ... )
    >>> _conf.update(setup_requirements())
    >>> setup(**_conf)

    :param str pattern: Glob pattern to find requirements files
    :param bool combine: Set True to set install_requires to extras_require['all']
    :return dict: Dictionary of parsed setup() options
    """
    session = PipSession()

    # Handle setuptools insanity
    key_map = {
        'requirements': 'install_requires',
        'install': 'install_requires',
        'tests': 'tests_require',
        'setup': 'setup_requires',
    }
    ret = {v: set() for v in key_map.values()}
    extras = ret['extras_require'] = {}
    all_reqs = set()

    files = [glob.glob(pat) for pat in patterns]
    files = itertools.chain(*files)

    for full_fn in files:
        # Parse
        reqs = {
            str(r.req)
            for r in parse_requirements(full_fn, session=session)
            # Must match env marker, eg:
            #   yarl ; python_version >= '3.0'
            if r.match_markers()
        }
        all_reqs.update(reqs)

        # Add in the right section
        fn = os.path.basename(full_fn)
        barefn, _ = os.path.splitext(fn)
        key = key_map.get(barefn)

        if key:
            ret[key].update(reqs)
            extras[key] = reqs

        extras[barefn] = reqs

    if 'all' not in extras:
        extras['all'] = list(all_reqs)

    if combine:
        extras['install'] = ret['install_requires']
        ret['install_requires'] = list(all_reqs)

    def _listify(dikt):
        ret = {}

        for k, v in dikt.items():
            if isinstance(v, set):
                v = list(v)
            Elif isinstance(v, dict):
                v = _listify(v)
            ret[k] = v

        return ret

    ret = _listify(ret)

    return ret
2
trevorj

Eine andere mögliche Lösung ...

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

und dann zu benutzen ...

reqs = gather_requirements()
install_reqs = reqs.pop('requirements', [])
test_reqs = reqs.pop('test', [])
...
setup(
    ...
    'install_requires': install_reqs,
    'test_requires': test_reqs,
    'extras_require': reqs,
    ...
)
1
Brian Bruggeman

Dieser einfache Ansatz liest die Anforderungsdatei aus setup.py. Es ist eine Variation der Antwort von Dmitiry S. . Diese Antwort ist nur mit Python 3.6+ kompatibel.

Per D.S. , requirements.txt kann konkrete Anforderungen mit bestimmten Versionsnummern dokumentieren, während setup.py kann abstrakte Anforderungen mit losen Versionsbereichen dokumentieren.

Unten ist ein Auszug aus meinem 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=Path(__file__).with_name(filename)).readlines()

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

Beachten Sie, dass distutils.text_file.TextFile entfernt Kommentare. Meiner Erfahrung nach müssen Sie anscheinend auch keine besonderen Schritte unternehmen, um die Anforderungsdatei zu bündeln.

1
Acumenus

Cross-Posting meiner Antwort von this SO question für eine andere einfache, Pip-Version Proof-Lösung.

try:  # for pip >= 10
    from pip._internal.req import parse_requirements
    from pip._internal.download import PipSession
except ImportError:  # for pip <= 9.0.3
    from pip.req import parse_requirements
    from pip.download import PipSession

requirements = parse_requirements(os.path.join(os.path.dirname(__file__), 'requirements.txt'), session=PipSession())

if __== '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requriements],
        ...
    )

Dann werfen Sie einfach alle Ihre Anforderungen unter requirements.txt unter Projektstammverzeichnis.

0
Scrotch