Riferimento requisito.txt per install_requires kwarg nel file setuptools setup.py


279

Ho un requirements.txtfile che sto usando con Travis-CI. Sembra sciocco duplicare i requisiti in entrambi requirements.txte setup.py, quindi speravo di passare un handle di file al install_requireskwarg in setuptools.setup.

È possibile? In tal caso, come devo fare per farlo?

Ecco il mio requirements.txtfile:

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_requiresviene utilizzato per dichiarare le dipendenze dai pacchetti richiesti per il funzionamento del pacchetto e utilizzati dallo sviluppatore del pacchetto, mentre requirements.txtviene utilizzato per automatizzare l'installazione di ambienti, che consente l'installazione di software aggiuntivo e l'esecuzione del pin di versione e vengono utilizzati dagli amministratori di sistema che distribuiscono il pacchetto. Il loro ruolo e il pubblico target differiscono in modo significativo, quindi cercare di combinarli come desideri di OP è un vero errore di progettazione.
Zart,

7
I miei 2 centesimi. Non utilizzare Requisiti.txt in setup.py. Gli scopi sono diversi, ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne

3
Vedo molte risposte complicate. Cosa c'è che non va nel semplice vecchio [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider,

Non è consigliabile farlo. Ma se davvero necessario, allora è semplice: setuptools stesso ha già tutto il necessariopkg_resources.parse_requirements()
sinoroc

Risposte:


246

Puoi capovolgerlo ed elencare le dipendenze setup.pye avere un solo carattere - un punto .- in requirements.txtinvece.


In alternativa, anche se non consigliato, è ancora possibile analizzare il requirements.txtfile (se non fa riferimento a requisiti esterni tramite URL) con il seguente hack (testato con pip 9.0.1):

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

Questo però non filtra i marker di ambiente .


Nelle vecchie versioni di pip, in particolare più vecchie di 6.0 , esiste un'API pubblica che può essere utilizzata per raggiungere questo obiettivo. Un file di requisiti può contenere commenti ( #) e può includere altri file ( --requiremento -r). Quindi, se vuoi davvero analizzare un requirements.txtpuoi usare il pars pars :

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
Cosa succede se l'utente non ha pip installato? Ka-boom?
Gringo Suave,

83
@GringoSuave Se l'utente non ha installato pip, deve prima installarlo.
Guettli,

7
È inoltre necessario fornire gli URL nel file dei requisiti, nel caso in cui vi siano righe -e o -f ("modificabili" git repo) che puntano a pacchetti non pypi. Utilizzare questo:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
piani cottura

91
Davvero non vuoi farlo. Parlare come pip manutentore pip non supporta affatto la chiamata come API come questa. In effetti pip 1.6 (la prossima versione in questo momento) sposta questa funzione.
Donald Stufft,

26
Questa non dovrebbe più essere la risposta accettata, se mai dovesse avere. È palesemente rotto. Anche quando ha funzionato, è palesemente inutile. Dato che per pipimpostazione predefinita l'analisi delle dipendenze è setup.pyin assenza di requirements.txt, la semplice risposta annotata in modo accurato da Tobu di seguito è quella di elencare setup.pye rimuovere tutte le dipendenze requirements.txt. Per le applicazioni che richiedono entrambi, è sufficiente ridurre l'elenco delle dipendenze requirements.txta solo il .carattere. Fatto.
Cecil Curry,

195

Sulla faccia di esso, sembra che requirements.txte setup.pysono duplicati stupide, ma è importante capire che mentre la forma è simile, la funzione prevista è molto diversa.

L'obiettivo di un autore di pacchetti, quando si specificano le dipendenze, è di dire "ovunque si installi questo pacchetto, questi sono gli altri pacchetti necessari, affinché questo pacchetto funzioni".

Al contrario, l'autore della distribuzione (che potrebbe essere la stessa persona in un momento diverso) ha un lavoro diverso, in quanto dicono "ecco l'elenco dei pacchetti che abbiamo raccolto e testato e che ora devo installare".

L'autore del pacchetto scrive per un'ampia varietà di scenari, perché stanno facendo del loro lavoro là fuori per essere utilizzati in modi che potrebbero non conoscere e non hanno modo di sapere quali pacchetti verranno installati insieme al loro pacchetto. Per essere un buon vicino ed evitare conflitti di versione di dipendenza con altri pacchetti, devono specificare una gamma di versioni di dipendenza quanto più ampia possibile. Questo è ciò che install_requiresin setup.pyfa.

L'autore della distribuzione scrive per un obiettivo molto diverso e molto specifico: una singola istanza di un'applicazione o servizio installata, installata su un determinato computer. Per controllare con precisione una distribuzione ed essere sicuri che i pacchetti giusti siano testati e distribuiti, l'autore della distribuzione deve specificare la versione esatta e la posizione di origine di ogni pacchetto da installare, comprese le dipendenze e le dipendenze delle dipendenze. Con questa specifica, una distribuzione può essere ripetutamente applicata a più macchine o testata su una macchina di prova e l'autore della distribuzione può essere sicuro che gli stessi pacchetti vengano distribuiti ogni volta. Questo è ciò che requirements.txtfa.

Quindi puoi vedere che, mentre entrambi sembrano un grande elenco di pacchetti e versioni, queste due cose hanno lavori molto diversi. Ed è sicuramente facile mescolarlo e sbagliarlo! Ma il modo giusto di pensare a questo è che requirements.txtè una "risposta" alla "domanda" posta dai requisiti in tutti i vari setup.pyfile di pacchetto. Piuttosto che scriverlo a mano, viene spesso generato dicendo a pip di guardare tutti i setup.pyfile in un set di pacchetti desiderati, trovare un set di pacchetti che ritiene corrisponda a tutti i requisiti e quindi, dopo l'installazione, "bloccare "quell'elenco di pacchetti in un file di testo (da qui deriva il pip freezenome).

Quindi il takeaway:

  • setup.pydovrebbe dichiarare le versioni di dipendenza più vaghe possibili che sono ancora praticabili. Il suo compito è quello di dire con cosa può funzionare un determinato pacchetto.
  • requirements.txtè un manifest di distribuzione che definisce un intero processo di installazione e non deve essere considerato come legato a un singolo pacchetto. Il suo compito è di dichiarare un elenco esaustivo di tutti i pacchetti necessari per far funzionare una distribuzione.
  • Poiché queste due cose hanno contenuti e ragioni di esistenza così diversi, non è possibile semplicemente copiarne una nell'altra.

Riferimenti:


10
Questa è una delle migliori spiegazioni che mi permettono di mettere un po 'di ordine in quel casino chiamato installazione di pacchetti! :)
Kounavi il

6
Non mi è ancora chiaro perché uno sviluppatore debba mantenere una versione controllata requirements.txtinsieme all'origine del pacchetto che contiene i requisiti concreti / congelati per l'installazione o il test. Sicuramente setup.pypuò essere utilizzato per questo scopo all'interno del progetto stesso? Posso solo immaginare di usare un simile file per gli strumenti usati per supportare la gestione del progetto (es. Refactoring, rilasci ecc.).
Sam Brightman,

2
@samBrightman Sono completamente d'accordo, non credo che i pacchetti di librerie o i pacchetti di applicazioni debbano eseguire il commit dei loro file Request.txt nel repository con il codice Penso che dovrebbe essere un artefatto generato durante il test di compilazione e quindi utilizzato per documentare un manifest di compilazione e, in definitiva, generare un artefatto di distribuzione.
Jonathan Hanson,

6
Quindi stai dicendo che requirements.txtc'è più documentazione per lo stato del mondo che ha prodotto una data build, anche se di solito non viene utilizzata nel processo di build stesso? Questo ha senso. Tuttavia, sembra che diversi sistemi facciano affidamento sulla duplicazione: Travis installa alcuni pacchetti (vecchi) predefiniti in virtualenv e dice di usarli requirements.txt. Se chiedo come garantire che le dipendenze vengano utilizzate al più tardi setup.py, le persone insistono sul fatto che dovrei usare requirements.txt.
Sam Brightman,

2
Il miglior consiglio che puoi ottenere da tutto ciò è trovare un modello che funzioni per te, documentarlo bene e assicurarsi che tutti quelli con cui lavori lo capiscano. Pensa al motivo per cui stai facendo ogni singolo bit e se ha davvero senso per il tuo caso d'uso. E cerca di rimanere il più informato possibile sullo stato attuale di costruzione, packaging e pubblicazione in Python, nel caso in cui le cose migliorino. Ma non trattenere il respiro.
Jonathan Hanson,

90

Non può richiedere un handle di file. L' install_requiresargomento può essere solo una stringa o un elenco di stringhe .

Ovviamente puoi leggere il tuo file nello script di installazione e passarlo come un elenco di stringhe a install_requires.

import os
from setuptools import setup

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

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

5
Sebbene utile, ciò cambia le specifiche dei requisiti da dichiarative a imperative. Questo rende impossibile per alcuni strumenti scoprire quali sono i tuoi requisiti. Ad esempio, PyCharm offre l'installazione automatica di tutti i requisiti specificati in install_requires. Tuttavia, non funziona se non si utilizza la sintassi dichiarativa.
Piotr Dobrogost,

55
@PiotrDobrogost Forse lo sviluppatore di PyCharm dovrebbe risolvere il suo programma allora. setup.pyè un programma che deve essere eseguito, non un file di dati che deve essere analizzato. Ciò non peggiora ulteriormente questa risposta.
Fredrick Brennan,

5
Sto solo sottolineando possibili problemi; questa risposta va benissimo. Non è solo PyCharm ad avere problemi con le informazioni "nascoste" dietro il codice. Questo è un problema universale e quindi c'è una tendenza generale verso la specifica dichiarativa dei metadati nella confezione di Python.
Piotr Dobrogost,

33
Funziona bene fintanto che metti include requirements.txtnel tuo MANIFEST.ino non sarai in grado di installare la tua libreria da una distribuzione di origine.
Pankrat,

4
So che questa è una vecchia domanda, ma almeno oggi puoi configurare PyCharm per analizzare un file dei requisiti in Preferenze-> Strumenti-> Strumenti integrati Python-> File dei requisiti del pacchetto
lekksi,

64

I file dei requisiti utilizzano un formato pip espanso, utile solo se è necessario integrare i setup.pyvincoli più forti, ad esempio specificando gli URL esatti da cui devono provenire alcune dipendenze o l'output di pip freezecongelare l'intero pacchetto impostato su noto funzionante versioni. Se non sono necessari ulteriori vincoli, utilizzare solo a setup.py. Se ritieni di dover davvero effettuare una spedizione in requirements.txtogni caso, puoi trasformarlo in un'unica linea:

.

Sarà valido e si riferirà esattamente al contenuto di quello setup.pyche si trova nella stessa directory.


9
Ma in questo caso proverebbe anche a installare anche la mia app. Cosa succede se non ne ho bisogno e voglio solo install_requires installato?
festa

2
Per approfondire cosa chiede @ffeast, se i requisiti esistono solo in setup.py, c'è un modo per installare i requisiti (equivalente di pip install -r requirements.txt ) senza installare il pacchetto stesso?
Haridsv,

1
@ffeast @haridsv -e .dovrebbe essere sufficiente. Controlla questa pagina: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter tenta ancora di installare l'app stessa. Questo non è ciò che vogliamo
festa

38

Pur non essendo una risposta esatta alla domanda, raccomando il post sul blog di Donald Stufft all'indirizzo https://caremad.io/2013/07/setup-vs-requirement/ per una buona questo problema. L'ho usato con grande successo.

In breve, requirements.txtnon è setup.pyun'alternativa, ma un complemento di distribuzione. Mantenere un'astrazione appropriata delle dipendenze dei pacchetti in setup.py. Impostatorequirements.txt o più di questi per recuperare versioni specifiche delle dipendenze dei pacchetti per lo sviluppo, i test o la produzione.

Ad esempio, con i pacchetti inclusi nel repository in deps/:

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

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

pip esegue i pacchetti setup.pye installa le versioni specifiche delle dipendenze dichiarate ininstall_requires . Non c'è duplicità e lo scopo di entrambi i manufatti viene preservato.


7
Questo non funziona quando si desidera fornire un pacchetto per l'installazione da parte di altri pip install my-package. Se le dipendenze per my-package non sono elencate in my-package / setup.py, non vengono installate da pip install my-package. Non sono stato in grado di determinare come fornire un pacchetto per altri che include dipendenze senza dichiararle esplicitamente in setup.py. Mi piacerebbe sapere se qualcuno ha capito come mantenerlo ASCIUTTO mentre consente ad altri di installare le dipendenze my-package + senza scaricare il file dei requisiti e chiamare manualmente pip install -r my-package/requirements.txt.
Malina,

2
@Malina Il pacchetto qui è perfettamente installabile senza requirements.txt. Questo è il punto. Aggiornata la domanda per rendere le cose più chiare. Anche il link post blog obsoleto aggiornato.
famosogarkin

quindi quando esegui setup.py chiamerà Requisit.txt per versioni specifiche dei file elencati in stup.py?
Dtracers,

È il contrario di @dtracers. requisito.txt punta al pacchetto stesso, in cui è possibile prelevare le dipendenze di setup.py. Quindi, quando si installano usando i requisiti, funziona e quando si installa tramite pip, funziona anche - in entrambi i casi usando le dipendenze di setup.py, ma permettendo anche di installare più cose quando si usa Requisit.txt
smido

20

L'uso parse_requirementsè problematico perché l'API pip non è documentata e supportata pubblicamente. Nel pip 1.6, quella funzione si sta effettivamente spostando, quindi è probabile che gli usi esistenti di essa vengano interrotti.

Un modo più affidabile per eliminare la duplicazione tra setup.pye requirements.txtè quello di specificare le proprie dipendenze setup.pye quindi inserirle -e .nel requirements.txtfile. Alcune informazioni di uno degli pipsviluppatori sul perché sia ​​un modo migliore per andare sono disponibili qui: https://caremad.io/blog/setup-vs-requirement/


@ Tommy Prova questo: caremad.io/2013/07/setup-vs-requirement Questo è lo stesso link pubblicato in un'altra risposta.
Amit

18

La maggior parte delle altre risposte precedenti non funziona con la versione corrente dell'API di pip. Ecco il modo corretto * per farlo con la versione corrente di pip (6.0.8 al momento della scrittura, funzionava anche in 7.1.2. Puoi controllare la tua versione con 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
    ....
)

* Corretto, in quanto è il modo di usare parse_requirements con l'attuale pip. Probabilmente non è ancora il modo migliore per farlo, poiché, come dicevano i poster sopra, pip non mantiene davvero un'API.


14

Installa il pacchetto corrente in Travis. Questo evita l'uso di un requirements.txtfile. Per esempio:

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

2
Questa è di gran lunga la migliore combinazione di "corretto" e "pratico". Aggiungo che se dopo il superamento dei test riesci a far sì che Travis generi un requisito.txt con pip freezeed esporti quel file da qualche parte come un artefatto (come S3 o qualcosa del genere), allora avresti un ottimo modo per installare ripetutamente esattamente ciò che testato.
Jonathan Hanson,

4

from pip.req import parse_requirements non ha funzionato per me e penso che sia per le righe vuote nel mio requisito.txt, ma questa funzione funziona

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

Se non vuoi forzare gli utenti a installare pip, puoi emularne il comportamento con questo:

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

La seguente interfaccia è diventata obsoleta nel pip 10:

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

Quindi l'ho passato solo al semplice analisi del testo:

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

Questo semplice approccio funziona oltre il 90% delle volte. Per quelli che usano Python 3.6+, ho scritto una risposta che ne è una pathlibvariante .
Acumenus,

3

Questo semplice approccio legge il file dei requisiti da setup.py. È una variazione della risposta di Dmitiry S .. Questa risposta è compatibile solo con Python 3.6+.

Per DS , requirements.txtpuò documentare requisiti concreti con numeri di versione specifici, mentre setup.pypuò documentare requisiti astratti con intervalli di versioni non validi.

Di seguito è riportato un mio estratto 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'),
   ...)

Si noti che distutils.text_file.TextFileeliminerà i commenti. Inoltre, secondo la mia esperienza, apparentemente non è necessario fare alcun passo speciale per raggruppare il file dei requisiti.


2

ATTENZIONE AL parse_requirementsCOMPORTAMENTO!

Si noti che pip.req.parse_requirementscambierà i trattini bassi in trattini. Questo mi ha fatto infuriare per alcuni giorni prima che lo scoprissi. Esempio dimostrativo:

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

produce

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

1
Utilizzare unsafe_name per ottenere la versione di sottolineatura:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds,

5
Come sottolineato altrove, PIP è un'applicazione, non una libreria. Non ha un'API concordata pubblicamente e l'importazione nel codice non è un caso d'uso supportato. Non sorprende che abbia un comportamento inaspettato; le sue funzioni interne non furono mai pensate per essere utilizzate in questo modo.
Jonathan Hanson,

1

Ho creato una funzione riutilizzabile per questo. Effettivamente analizza un'intera directory di file dei requisiti e li imposta su extras_require.

Le ultime sempre disponibili qui: 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)

molto bella! gestisce anche i requisiti ricorsivi con l'ultimo pip :)
amohr

@amohr Grazie! L'ho aggiornato di recente per un pip anche più tardi, non sono sicuro del motivo per cui si comportano come sono, spostando le cose in pip._internal.. Se non fornisci un'API esterna utilizzabile, non dovresti interrompere tutte quelle che utilizzano tutto ciò che fornisci.
trevorj,

0

Un'altra possibile soluzione ...

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

e poi usare ...

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

da dove treeviene?
Francesco Boi,

@FrancescoBoi se mi perdoni un po 'per non aver presentato una soluzione completamente funzionante ... tree è davvero solo una scansione del file system locale (molto simile a un comando "tree" in linux). Inoltre, la mia soluzione sopra potrebbe non funzionare completamente a questo punto perché pip viene costantemente aggiornato e ho usato pip internals.
Brian Bruggeman,

0

Non consiglierei di fare una cosa del genere. Come accennato più volte install_requirese requirements.txtsicuramente non dovrebbero essere la stessa lista. Ma dal momento che ci sono molte risposte fuorvianti che coinvolgono API interne private di pip , potrebbe valere la pena cercare alternative più sane ...

Non è necessario che pip analizzi un requirements.txtfile da uno script setuptools setup.py . Il progetto setuptools contiene già tutti gli strumenti necessari nel suo pacchetto di livello superiorepkg_resources .

Potrebbe più o meno apparire così:

#!/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,
)

Nel caso in cui non fossi a conoscenza, il motivo per cui molti (me compreso) hanno utilizzato pipl'analisi e non pkg_resourcesda prima del 2015 sono bug come github.com/pypa/setuptools/issues/470 . Questo esatto è stato risolto al giorno d'oggi, ma ho ancora un po 'paura di usarlo, poiché entrambe le implementazioni sembrano essere sviluppate separatamente.
trevorj,

@trevorj Grazie per averlo indicato, non lo sapevo. Il fatto è che al giorno d'oggi funziona e coinvolgere il pip mi sembra un'idea ridicola (specialmente in questo modo). Dai un'occhiata alle altre risposte, molte sembrano lievi variazioni della stessa idea sconsiderata, senza alcun preavviso. E i nuovi arrivati ​​potrebbero seguire questa tendenza. Speriamo che iniziative come PEP517 e PEP518 allontanino la comunità da questa follia.
sinoroc

-1

Pubblicazione incrociata della mia risposta da questa domanda SO per un'altra soluzione semplice, a prova di versione pip.

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

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

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

Quindi inserisci tutti i requisiti nella requirements.txtdirectory principale del progetto.


-1

L'ho fatto:

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

Ancora un altro parse_requirementstrucco che analizza anche i marcatori di ambiente in 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
)

Dovrebbe supportare dists binarie e binarie.

Come affermato da altri, parse_requirementsha diverse carenze, quindi questo non è ciò che dovresti fare su progetti pubblici, ma potrebbe essere sufficiente per progetti interni / personali.


pip 20.1 ha cambiato la sua API e gli indicatori non sono più disponibili tramite parse_requirements(), quindi ora questo fallisce.
Tuukka Mustonen,

-3

Ecco un hack completo (testato con pip 9.0.1) basato sulla risposta di Romain che analizza requirements.txte filtra in base agli attuali marker di ambiente :

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
Questo è solo parzialmente vero. Se chiami r.match_markers(), stai effettivamente valutando i marker, che è una cosa corretta da fare per uno sdist. Tuttavia, se stai costruendo una dist binaria (es. Wheel), il pacchetto elencherebbe solo quelle librerie che corrispondono al tuo ambiente di build-time.
Tuukka Mustonen,

@TuukkaMustonen, quindi dove trovarlo wheel environment(se è la cosa che la persona cerca di fare) per valutare i marcatori rispetto ad esso?
Anatoly Techtonik,

Vedi stackoverflow.com/a/41172125/165629 che dovrebbe supportare anche bdist_wheel. Non valuta i marker, li aggiunge semplicemente a extras_require.
Tuukka Mustonen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.