Referenzanforderungen.txt für install_requires kwarg in der Datei setuptools setup.py

279

Ich habe eine requirements.txtDatei, die ich mit Travis-CI verwende. Es scheint albern, die Anforderungen in beiden zu duplizieren, requirements.txtund setup.pyso hatte ich gehofft, ein install_requiresDateihandle an das kwarg in zu übergeben setuptools.setup.

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

Hier ist meine requirements.txtDatei:

guessit>=0.5.2
tvdb_api>=1.8.2
hachoir-metadata>=1.3.3
hachoir-core>=1.3.3
hachoir-parser>=1.3.4
blz
quelle
4
install_requireswird verwendet, um Abhängigkeiten von Paketen zu deklarieren, die für das Funktionieren des Pakets erforderlich sind und vom Entwickler des Pakets verwendet werden, während requirements.txtdie Installation von Umgebungen automatisiert wird. Dies ermöglicht die Installation zusätzlicher Software und das Fixieren der Version und wird von Systemadministratoren verwendet, die das Paket bereitstellen Paket. Ihre Rolle und Zielgruppe unterscheiden sich erheblich, daher ist der Versuch, sie wie OP-Wünsche zu kombinieren, imho ein echter Designfehler.
Zart
7
Meine 2 Cent. Verwenden Sie in Ihrer setup.py nicht die Datei require.txt. Die Zwecke sind unterschiedlich, ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne
3
Ich sehe viele komplizierte Antworten. Was ist los mit einfach alt [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider
Es wird nicht empfohlen, dies zu tun. Aber wenn es wirklich gebraucht wird, ist es einfach: setuptools selbst hat bereits alles Notwendigepkg_resources.parse_requirements()
sinoroc

Antworten:

246

Sie können es umdrehen und die Abhängigkeiten setup.pyauflisten .und requirements.txtstattdessen ein einzelnes Zeichen - einen Punkt - einfügen.


Alternativ ist es auch dann möglich, die requirements.txtDatei mit dem folgenden Hack (getestet mit pip 9.0.1) zu analysieren, wenn dies nicht empfohlen wird (wenn keine externen Anforderungen per URL angegeben werden ):

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

Dies filtert jedoch keine Umgebungsmarkierungen .


In alten Versionen von pip, insbesondere älter als 6.0 , gibt es eine öffentliche API, mit der dies erreicht werden kann. Eine Anforderungsdatei kann Kommentare ( #) und einige andere Dateien ( --requirementoder -r) enthalten. Wenn Sie also wirklich a analysieren möchten, requirements.txtkö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
)
Romain Hardouin
quelle
26
Was ist, wenn der Benutzer kein Pip installiert hat? Ka-Boom?
Gringo Suave
82
@GringoSuave Wenn der Benutzer kein Pip installiert hat, muss er es zuerst installieren.
Guettli
7
Sie müssen auch die URLs in Ihrer Anforderungsdatei angeben, falls Zeilen -e oder -f ("bearbeitbare" Git-Repo-Zeilen) auf Nicht-Pypi-Pakete verweisen. Verwenden Sie diese:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
Kochfelder
91
Das willst du wirklich nicht. Als Pip-Betreuer sprechen Pip unterstützt es überhaupt nicht, als solche API aufgerufen zu werden. Tatsächlich verschiebt Pip 1.6 (nächste Version zu diesem Zeitpunkt) diese Funktion.
Donald Stufft
26
Dies sollte nicht länger die akzeptierte Antwort sein, falls dies jemals der Fall sein sollte. Es ist offensichtlich kaputt. Selbst wenn es funktioniert hat, ist es offensichtlich unnötig. Da pipstandardmäßig Abhängigkeiten von setup.pyin Abwesenheit von analysiert werden requirements.txt, besteht die einfache Antwort , die Tobu unten scharfsinnig notiert, darin, alle Abhängigkeitensetup.pyrequirements.txt aufzulisten und zu entfernen . Reduzieren Sie für Anwendungen, die beides erfordern, einfach die Abhängigkeitsliste requirements.txtauf das .Zeichen. Erledigt.
Cecil Curry
194

Auf den ersten Blick scheint es so, requirements.txtals setup.pywären es alberne Duplikate, aber es ist wichtig zu verstehen, dass die beabsichtigte Funktion sehr unterschiedlich ist, obwohl die Form ähnlich ist.

Das Ziel eines Paketautors bei der Angabe von Abhängigkeiten besteht darin, 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 zu einem anderen Zeitpunkt dieselbe Person sein kann) einen anderen Job, da er sagt: "Hier ist die Liste der Pakete, die wir zusammengestellt und getestet haben und die ich jetzt installieren muss."

Der Paketautor schreibt für eine Vielzahl von Szenarien, da sie ihre Arbeit dort einsetzen, um sie auf eine Weise zu verwenden, von der sie möglicherweise nichts wissen, und nicht wissen können, welche Pakete neben ihrem Paket installiert werden. Um ein guter Nachbar zu sein und Konflikte mit Abhängigkeitsversionen mit anderen Paketen zu vermeiden, müssen sie eine möglichst große Anzahl von Abhängigkeitsversionen angeben, die möglicherweise funktionieren. Dies ist, was install_requiresin setup.pytut.

Der Bereitstellungsautor schreibt für ein ganz anderes, sehr spezifisches Ziel: eine einzelne Instanz einer installierten Anwendung oder eines installierten Dienstes, die auf einem bestimmten Computer installiert ist. 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 sicher sein, dass jedes Mal dieselben Pakete bereitgestellt werden. Das macht ein requirements.txt.

Sie sehen also, dass beide Dinge wie eine große Liste von Paketen und Versionen aussehen, diese beiden Dinge jedoch sehr unterschiedliche Aufgaben haben. Und es ist definitiv einfach, dies zu verwechseln und falsch zu verstehen! Der richtige Weg, darüber nachzudenken, requirements.txtist jedoch eine "Antwort" auf die "Frage", die sich aus den Anforderungen in allen verschiedenen setup.pyPaketdateien ergibt. Anstatt es von Hand zu schreiben, wird es häufig generiert, indem pip angewiesen wird, alle setup.pyDateien in einer Reihe gewünschter Pakete zu betrachten, eine Reihe von Paketen zu finden, von denen es glaubt, dass sie alle Anforderungen erfüllen, und dann, nachdem sie installiert wurden, "einzufrieren" "diese Liste von Paketen in eine Textdatei (hier kommt der pip freezeName her).

Also zum Mitnehmen:

  • setup.pysollte die lockersten möglichen Abhängigkeitsversionen deklarieren, die noch funktionsfähig sind. Seine Aufgabe ist es zu sagen, womit ein bestimmtes Paket funktionieren kann.
  • requirements.txtist ein Bereitstellungsmanifest, das einen gesamten Installationsjob definiert und nicht als an ein Paket gebunden angesehen werden 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 für ihre Existenz haben, ist es nicht möglich, sie einfach ineinander zu kopieren.

Verweise:

Jonathan Hanson
quelle
10
Dies ist eine der besten Erklärungen, mit denen ich Ordnung in das Chaos der Paketinstallation bringen kann! :)
Kounavi
6
Mir ist immer noch nicht klar, warum ein Entwickler eine Versionskontrolle requirements.txtzusammen mit der Quelle des Pakets beibehalten würde, das die konkreten / eingefrorenen Anforderungen für die Installation oder den Test enthält. Sicherlich setup.pykann sich im Rahmen des Projekts für diesen Zweck verwendet werden? Ich kann nur eine solche Datei für Werkzeuge zur Unterstützung verwendet imagine mit der Verwaltung des Projekts (zB Refactoring, so dass Meldungen etc.).
Sam Brightman
2
@samBrightman Ich stimme voll und ganz zu. Ich denke nicht, dass Bibliothekspakete oder Anwendungspakete ihre Datei require.txt mit dem Code in das Repository übertragen sollten. Ich denke, das sollte ein Artefakt sein, das während des Build-Tests generiert und dann verwendet wird, um ein Build-Manifest zu dokumentieren und letztendlich ein Bereitstellungsartefakt zu generieren.
Jonathan Hanson
6
Sie sagen also, requirements.txtist mehr Dokumentation für den Zustand der Welt, der einen bestimmten Build erstellt hat, obwohl er normalerweise nicht im Build-Prozess selbst verwendet wird? Das macht Sinn. Es sieht jedoch so aus, als ob mehrere Systeme auf Duplizierung angewiesen sind: Travis installiert einige (alte) Standardpakete in Ihrer virtuellen Umgebung und sagt, dass sie verwendet werden sollen requirements.txt. Wenn ich frage, wie sichergestellt werden kann, dass Abhängigkeiten spätestens verwendet werden setup.py, bestehen die Leute darauf, dass ich sie verwenden sollte requirements.txt.
Sam Brightman
2
Der beste Rat, den Sie daraus ziehen können, ist, ein Modell zu finden, das für Sie funktioniert, es gut zu dokumentieren und sicherzustellen, dass jeder, mit dem Sie arbeiten, es versteht. Überlegen Sie, warum Sie die einzelnen Schritte ausführen und ob dies für Ihren Anwendungsfall wirklich sinnvoll ist. Und versuchen Sie, so gut wie möglich über den aktuellen Stand des Erstellens, Verpackens und Publizierens in Python informiert zu bleiben, für den Fall, dass es besser wird. Aber halten Sie nicht den Atem an.
Jonathan Hanson
89

Es kann kein Dateihandle aufnehmen. Das install_requiresArgument 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 übergeben install_requires.

import os
from setuptools import setup

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

setup(...
install_requires=required,
...)
Fredrick Brennan
quelle
5
Obwohl dies nützlich ist, ändert sich die Spezifikation der Anforderungen von deklarativ zu imperativ. Dies macht es für einige Tools unmöglich, Ihre Anforderungen herauszufinden. Zum Beispiel bietet PyCharm die automatische Installation aller in install_requires. Es funktioniert jedoch nicht, wenn Sie keine deklarative Syntax verwenden.
Piotr Dobrogost
55
@PiotrDobrogost Vielleicht sollte der PyCharm-Entwickler dann sein Programm reparieren. setup.pyist ein Programm, das ausgeführt werden soll, keine Datendatei, die analysiert werden soll. Das macht diese Antwort nicht schlimmer.
Fredrick Brennan
5
Ich weise nur auf mögliche Probleme hin; Diese Antwort ist vollkommen in Ordnung. Es ist nicht nur PyCharm, das Probleme damit hat, dass Informationen hinter Code "versteckt" werden. Dies ist ein universelles Problem, und daher gibt es einen allgemeinen Trend zur deklarativen Spezifikation von Metadaten in Python-Paketen.
Piotr Dobrogost
32
Funktioniert gut, solange Sie setzen include requirements.txtin Ihrem MANIFEST.inoder Sie werden nicht in der Lage sein , Ihre Bibliothek aus einer Quelldistribution zu installieren.
Pankrat
4
Ich weiß, dass dies eine alte Frage ist, aber Sie können PyCharm zumindest heutzutage so konfigurieren, dass eine Anforderungsdatei unter Einstellungen-> Tools-> Integrierte Python-Tools->
Paketanforderungsdatei
64

Anforderungsdateien verwenden ein erweitertes Pip-Format, das nur dann nützlich ist, wenn Sie Ihre setup.pymit stärkeren Einschränkungen ergänzen müssen , z. B. indem Sie die genauen URLs angeben, von denen einige der Abhängigkeiten stammen müssen, oder die Ausgabe von pip freeze, um das gesamte Paketset auf bekanntes Funktionieren einzufrieren Versionen. Wenn Sie die zusätzlichen Einschränkungen nicht benötigen, verwenden Sie nur a setup.py. Wenn Sie das Gefühl haben, dass Sie wirklich eine requirements.txtversenden müssen, können Sie daraus eine einzelne Zeile machen:

.

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

Tobu
quelle
9
In diesem Fall würde es aber auch versuchen, meine App zu installieren. Was ist, wenn ich es nicht brauche und nur install_requires installieren möchte?
Fest
2
Gibt es eine Möglichkeit, die Anforderungen (äquivalent zu pip install -r requirements.txt ) zu installieren, ohne das Paket selbst zu installieren, um zu erläutern, was @ffeast fragt, ob Anforderungen nur in setup.py vorhanden sind ?
Haridsv
1
@ffeast @haridsv -e .sollte ausreichen. Überprüfen Sie diese Seite: caremad.io/posts/2013/07/setup-vs-requirement
Dexhunter
4
@ DexD.Hunter versucht immer noch, die App selbst zu installieren. Das wollen wir nicht
Fest
38

Obwohl dies keine genaue Antwort auf die Frage ist, 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.txtist keine setup.pyAlternative, sondern eine Ergänzung zur Bereitstellung. Behalten Sie eine angemessene Abstraktion der Paketabhängigkeiten in bei setup.py. Legen Sie requirements.txtmindestens mehrere fest, um bestimmte Versionen von Paketabhängigkeiten für Entwicklung, Test oder Produktion abzurufen.

ZB mit Paketen im Repo unter deps/:

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

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

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

Berühmte Garkin
quelle
7
Dies funktioniert nicht, wenn Sie ein Paket bereitstellen möchten, über das andere installieren können pip install my-package. Wenn Abhängigkeiten für my-package nicht in my-package / setup.py aufgeführt sind, werden sie nicht von installiert pip install my-package. Ich konnte nicht feststellen, wie ein Paket für andere bereitgestellt werden soll, das Abhängigkeiten enthält, ohne sie explizit in setup.py anzugeben. Würde gerne wissen, ob jemand herausgefunden hat, wie man es trocken hält, während andere es erlauben, my-package + Abhängigkeiten zu installieren, ohne die Anforderungsdatei herunterzuladen und manuell aufzurufen pip install -r my-package/requirements.txt.
Malina
2
@ Malina Das Paket hier ist perfekt ohne installiert requirements.txt. Das ist der springende Punkt. Die Frage wurde aktualisiert, um die Dinge klarer zu machen. Außerdem wurde der Link zum veralteten Blog-Beitrag aktualisiert.
Berühmte Garkin
Wenn Sie setup.py ausführen, wird die Datei "resources.txt" für bestimmte Versionen der in stup.py aufgelisteten Dateien aufgerufen.
Dtracer
Es ist umgekehrt um @dtracers. require.txt verweist auf das Paket selbst, in dem die Abhängigkeiten von setup.py erfasst werden könnten. Wenn Sie also mit Anforderungen installieren, funktioniert es und wenn Sie über pip installieren, funktioniert es auch - in beiden Fällen unter Verwendung der Abhängigkeiten von setup.py, aber auch, um bei Verwendung von
require.txt
20

Die Verwendung parse_requirementsist problematisch, da die pip-API nicht öffentlich dokumentiert und unterstützt wird. In Pip 1.6 bewegt sich diese Funktion tatsächlich, sodass bestehende Verwendungen davon wahrscheinlich nicht mehr funktionieren.

Eine zuverlässigere Möglichkeit, Doppelarbeit zwischen setup.pyund requirements.txtzu vermeiden, besteht darin, Ihre Abhängigkeiten in Ihrer Datei anzugeben setup.pyund diese dann -e .in Ihre requirements.txtDatei einzufügen. Einige Informationen von einem der pipEntwickler darüber, warum dies ein besserer Weg ist, finden Sie hier: https://caremad.io/blog/setup-vs-requirement/

Wilfredo Sánchez Vega
quelle
@Tommy Versuchen Sie Folgendes: caremad.io/2013/07/setup-vs-requirement Dies ist derselbe Link wie in einer anderen Antwort.
Amit
18

Die meisten der oben genannten Antworten funktionieren nicht mit der aktuellen Version der Pip-API. Hier ist die richtige * Methode, um dies mit der aktuellen Version von pip zu tun (6.0.8 zum Zeitpunkt des Schreibens, ebenfalls 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 auf den Postern erwähnt, keine API verwaltet.

fabianvf
quelle
14

Installieren Sie das aktuelle Paket in Travis. Dies vermeidet die Verwendung einer requirements.txtDatei. Zum Beispiel:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py
vdboor
quelle
2
Dies ist bei weitem die beste Kombination aus "richtig" und "praktisch". Ich würde hinzufügen, dass wenn Travis nach dem Bestehen der Tests eine anforderungs.txt mit generieren pip freezeund diese Datei irgendwo als Artefakt exportieren kann (wie S3 oder so), Sie eine großartige Möglichkeit haben, genau das zu wiederholen, was Sie tun geprüft.
Jonathan Hanson
4

from pip.req import parse_requirements hat bei mir nicht funktioniert und ich denke, es ist für die Leerzeilen in meiner Anforderung.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,
    ...
)
Diego Navarro
quelle
4

Wenn Sie Ihre Benutzer nicht zwingen möchten, pip zu installieren, können Sie das folgende Verhalten 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'))
)
reubano
quelle
4

Die folgende Schnittstelle wurde in Pip 10 veraltet:

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

Also habe ich es einfach auf einfaches Parsen von Text umgestellt:

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 != ''
    ]
Dmitriy Sintsov
quelle
Dieser einfache Ansatz funktioniert in über 90% der Fälle. Für diejenigen, die Python 3.6+ verwenden, habe ich eine Antwort geschrieben, die eine pathlibVariation davon ist.
Acumenus
3

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.

Pro DS , requirements.txtkönnen konkrete Anforderungen mit spezifischen Versionsnummern dokumentieren, währendsetup.py abstrakte Anforderungen mit loser Version Bereichen dokumentieren.

Unten ist ein Auszug von mir 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'),
   ...)

Beachten Sie, dass distutils.text_file.TextFileKommentare entfernt werden. Nach meiner Erfahrung müssen Sie anscheinend keine besonderen Schritte unternehmen, um die Anforderungsdatei zu bündeln.

Scharfsinn
quelle
2

Hüten Sie sich vor dem parse_requirementsVerhalten!

Bitte beachten Sie, dass dadurch pip.req.parse_requirementsUnterstriche in Bindestriche umgewandelt werden. Das machte mich ein paar Tage lang wütend, bevor ich es entdeckte. Beispiel zur Demonstration:

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']
MikeTwo
quelle
1
Verwenden Sie unsafe_name , um die Unterstrichversion zu erhalten:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
Alanjds
5
Wie bereits an anderer Stelle erwähnt, ist PIP eine Anwendung, keine Bibliothek. Es gibt keine öffentlich vereinbarte API, und das Importieren in Ihren Code ist kein unterstützter Anwendungsfall. Es ist nicht überraschend, dass es unerwartetes Verhalten hat; Seine internen Funktionen sollten niemals auf diese Weise verwendet werden.
Jonathan Hanson
1

Ich habe dafür eine wiederverwendbare Funktion erstellt. Es analysiert tatsächlich ein ganzes Verzeichnis von Anforderungsdateien und setzt sie auf extras_require.

Neueste immer hier verfügbar: 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 <[email protected],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)
trevorj
quelle
Sehr schön! behandelt sogar rekursive Anforderungen mit dem neuesten Pip :)
Amohr
@amohr Danke! Ich habe es kürzlich für einen noch späteren Pip aktualisiert. Ich bin mir nicht sicher, warum sie sich so verhalten, wie sie sind, indem ich Dinge auf ... verschiebe pip._internal. Wenn Sie keine verwendbare externe API bereitstellen, sollten Sie nicht alle brechen die alles verwenden, was Sie zur Verfügung stellen.
Trevorj
0

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,
    ...
)
Brian Bruggeman
quelle
woher treekommen aus?
Francesco Boi
@FrancescoBoi, wenn Sie mir ein wenig verzeihen, dass ich keine voll funktionsfähige Lösung präsentiere ... Baum ist wirklich nur ein Scan des lokalen Dateisystems (sehr ähnlich einem "Baum" -Befehl unter Linux). Außerdem funktioniert meine obige Lösung zu diesem Zeitpunkt möglicherweise nicht vollständig, da pip ständig aktualisiert wird und ich Pip-Interna verwendet habe.
Brian Bruggeman
0

Ich würde so etwas nicht empfehlen. Wie mehrfach erwähnt install_requiresund requirements.txtsollen definitiv nicht die gleiche Liste sein. Aber da es viele irreführende Antworten gibt, die private interne APIs von pip betreffen betreffen, lohnt es sich möglicherweise, nach vernünftigeren Alternativen zu suchen ...

Es besteht keine Notwendigkeit für pip eine parsen requirements.txtDatei von einem Setuptool setup.py Skript. Das setuptools- Projekt enthält bereits alle erforderlichen Tools in seinem Top-Level- Paket pkg_resources.

Es könnte mehr oder weniger so aussehen:

#!/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,
)
Sinoroc
quelle
Falls Sie sich dessen nicht bewusst waren, sind Fehler wie github.com/pypa/setuptools/issues/470 der Grund, warum viele (ich selbst eingeschlossen) seit 2015 pipParsing verwenden und nicht . Dieses genaue Problem ist heutzutage behoben, aber ich habe immer noch ein bisschen Angst, es zu verwenden, da beide Implementierungen scheinbar separat entwickelt wurden. pkg_resources
Trevorj
@revorj Danke, dass du darauf hingewiesen hast, ich wusste es nicht. Tatsache ist, dass es heutzutage funktioniert und es für mich eine lächerliche Idee ist, Pip einzubeziehen (besonders auf diese Weise). Schauen Sie sich die anderen Antworten an, die meisten scheinen geringfügige Abweichungen derselben schlecht beratenen Idee zu sein, ohne dass es eines Warnhinweises bedarf. Und Neulinge könnten diesem Trend folgen. Hoffentlich lenken Initiativen wie PEP517 und PEP518 die Community von diesem Wahnsinn ab.
Sinoroc
-1

Cross-Posting meiner Antwort von dieser SO-Frage für eine andere einfache, pip-versionssichere 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 __name__ == '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requirements],
        ...
    )

Dann geben Sie einfach alle Ihre Anforderungen requirements.txtunter Projektstammverzeichnis ein.

Scrotch
quelle
-1

Ich war das:

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

Ein weiterer parse_requirementsHack, der auch Umgebungsmarkierungen analysiert 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
)

Es sollte sowohl sdist als auch binäre Dists unterstützen.

Wie von anderen angegeben, parse_requirementsweist es mehrere Mängel auf, sodass Sie dies bei öffentlichen Projekten nicht tun sollten, dies kann jedoch für interne / persönliche Projekte ausreichen.

Tuukka Mustonen
quelle
pip 20.1 hat seine API geändert und Marker sind nicht mehr über verfügbar parse_requirements(), daher schlägt dies jetzt fehl.
Tuukka Mustonen
-3

Hier ist ein vollständiger Hack (getestet mit pip 9.0.1) basierend auf Romains Antwort , der requirements.txtihn gemäß den aktuellen Umgebungsmarkierungen analysiert und filtert :

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)
anatoly techtonik
quelle
1
Dies ist nur teilweise richtig. Wenn Sie anrufen r.match_markers(), werten Sie tatsächlich die Marker aus, was für einen SDIST richtig ist. Wenn Sie jedoch eine binäre Dist (z. B. ein Rad) erstellen, listet das Paket nur die Bibliotheken auf, die Ihrer Build-Time-Umgebung entsprechen.
Tuukka Mustonen
@ TuukkaMustonen, wo kann man das finden wheel environment(wenn es das ist, was die Person versucht), um Marker dagegen zu bewerten?
Anatoly Techtonik
Siehe stackoverflow.com/a/41172125/165629 , das ebenfalls unterstützt werden sollte bdist_wheel. Marker werden nicht ausgewertet, sondern nur hinzugefügt extras_require.
Tuukka Mustonen