Requerimientos de referencia.txt para install_requires kwarg en el archivo setuptools setup.py

279

Tengo un requirements.txtarchivo que estoy usando con Travis-CI. Parece tonto duplicar los requisitos en ambos requirements.txty setup.py, por lo tanto, esperaba pasar un identificador de archivo al install_requireskwarg setuptools.setup.

es posible? Si es así, ¿cómo debo hacerlo?

Aquí está mi requirements.txtarchivo:

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
fuente
44
install_requiresse usa para declarar dependencias en los paquetes que son necesarios para que el paquete funcione y los usa el desarrollador del paquete, mientras que requirements.txtse usa para automatizar la instalación de entornos, lo que permite instalar software adicional y anclar la versión y los administradores de sistemas los usan paquete. Su rol y público objetivo difieren significativamente, por lo que tratar de combinarlos como lo desea OP es un verdadero error de diseño en mi humilde opinión.
Zart
77
Mis 2 centavos No use requerimientos.txt en su setup.py. Los propósitos son diferentes, ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne
3
Veo muchas respuestas complicadas. ¿Qué hay de malo en lo viejo [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider
No se recomienda hacer esto. Pero si realmente se necesita, entonces es sencillo: setuptools ya tiene todo lo necesariopkg_resources.parse_requirements()
sinoroc

Respuestas:

246

Puede darle la vuelta y enumerar las dependencias setup.pyy tener un solo carácter, un punto ., en su requirements.txtlugar.


Alternativamente, incluso si no se recomienda, aún es posible analizar el requirements.txtarchivo (si no hace referencia a ningún requisito externo por URL) con el siguiente truco (probado con pip 9.0.1):

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

Sin embargo, esto no filtra los marcadores del entorno .


En versiones antiguas de pip, más específicamente anteriores a 6.0 , hay una API pública que se puede utilizar para lograr esto. Un archivo de requisitos puede contener comentarios ( #) y puede incluir algunos otros archivos ( --requiremento -r). Por lo tanto, si realmente desea analizar un requirements.txtpuede usar el analizador de 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
)
Romain Hardouin
fuente
26
¿Qué pasa si el usuario no tiene pip instalado? Ka-boom?
Gringo Suave
83
@GringoSuave Si el usuario no tiene pip instalado, primero debe instalarlo.
guettli
77
También debe proporcionar las URL en su archivo de requisitos, en caso de que haya alguna línea -e o -f ("editable" git repo) que apunte a paquetes que no sean pypi. Use esto:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
placas de
9191
Realmente no quieres hacer esto. Hablando como mantenedor de pip, pip no es compatible con ser llamado como una API como esta. De hecho, pip 1.6 (próxima versión en este momento) mueve esta función.
Donald Stufft
26
Esta ya no debería ser la respuesta aceptada, si alguna vez debería haberlo hecho. Está roturamente roto. Incluso cuando funcionó, es descaradamente innecesario. Dado que, por pipdefecto, se analizan las dependencias setup.pyen ausencia de requirements.txt, la respuesta simple que Tobu señala astutamente a continuación es enumerar todas las dependencias setup.pyy eliminarlas requirements.txt. Para las aplicaciones que requieren ambos, simplemente reduzca la lista de dependencias requirements.txtsimplemente al .carácter. Hecho.
Cecil Curry
195

En vista de ello, parece que requirements.txty setup.pyson duplicados tontas, pero es importante entender que mientras que la forma es similar, la función prevista es muy diferente.

El objetivo del autor de un paquete, al especificar dependencias, es decir "donde sea que instale este paquete, estos son los otros paquetes que necesita para que este paquete funcione".

Por el contrario, el autor de la implementación (que puede ser la misma persona en un momento diferente) tiene un trabajo diferente, ya que dice "aquí está la lista de paquetes que hemos reunido y probado y que ahora necesito instalar".

El autor del paquete escribe para una amplia variedad de escenarios, porque están poniendo su trabajo ahí fuera para que lo usen de formas que quizás no conozcan, y no tienen forma de saber qué paquetes se instalarán junto con su paquete. Para ser un buen vecino y evitar conflictos de versiones de dependencia con otros paquetes, necesitan especificar una gama tan amplia de versiones de dependencia como pueda funcionar. Esto es lo que install_requiresen setup.pysí.

El autor de la implementación escribe para un objetivo muy diferente y muy específico: una única instancia de una aplicación o servicio instalado, instalado en una computadora en particular. Para controlar con precisión una implementación y asegurarse de que los paquetes correctos se prueben e implementen, el autor de la implementación debe especificar la versión exacta y la ubicación de origen de cada paquete que se instalará, incluidas las dependencias y las dependencias de la dependencia. Con esta especificación, una implementación se puede aplicar repetidamente a varias máquinas, o probarse en una máquina de prueba, y el autor de la implementación puede estar seguro de que los mismos paquetes se implementan cada vez. Esto es lo que requirements.txthace.

Entonces puede ver que, si bien ambos parecen una gran lista de paquetes y versiones, estas dos cosas tienen trabajos muy diferentes. ¡Y definitivamente es fácil mezclar esto y equivocarse! Pero la forma correcta de pensar en esto es que requirements.txtes una "respuesta" a la "pregunta" planteada por los requisitos en todos los diversos setup.pyarchivos de paquete. En lugar de escribirlo a mano, a menudo se genera diciéndole a pip que mire todos los setup.pyarchivos en un conjunto de paquetes deseados, encuentre un conjunto de paquetes que considere que cumple con todos los requisitos y luego, una vez instalados, "congele "esa lista de paquetes en un archivo de texto (de aquí pip freezeproviene el nombre).

Entonces la comida para llevar:

  • setup.pydebería declarar las versiones de dependencia más flojas posibles que aún sean viables. Su trabajo es decir con qué puede funcionar un paquete en particular.
  • requirements.txtes un manifiesto de implementación que define un trabajo de instalación completo y no debe considerarse vinculado a ningún paquete. Su trabajo es declarar una lista exhaustiva de todos los paquetes necesarios para que una implementación funcione.
  • Debido a que estas dos cosas tienen un contenido y razones tan diferentes para existir, no es factible simplemente copiar una en la otra.

Referencias

Jonathan Hanson
fuente
10
¡Esta es una de las mejores explicaciones que me permiten poner un poco de orden en ese desastre llamado instalación de paquetes! :)
Kounavi el
66
Todavía no me queda claro por qué un desarrollador mantendría una versión controlada requirements.txtjunto con la fuente del paquete que contiene los requisitos concretos / congelados para la instalación o prueba. ¿Seguramente setup.pypuede usarse para este propósito dentro del proyecto mismo? Solo puedo imaginar el uso de un archivo de este tipo para herramientas utilizadas para apoyar la gestión del proyecto (por ejemplo, refactorización, lanzamiento, etc.).
Sam Brightman
2
@samBrightman Estoy completamente de acuerdo, no creo que los paquetes de la biblioteca o los paquetes de la aplicación deban comprometer su archivo require.txt al repositorio con el código. Creo que debería ser un artefacto generado durante las pruebas de compilación, y luego utilizado para documentar un manifiesto de compilación y, en última instancia, generar un artefacto de implementación.
Jonathan Hanson el
66
Entonces, ¿está diciendo que requirements.txthay más documentación para el estado del mundo que produjo una compilación determinada, a pesar de que generalmente no se usa en el proceso de compilación en sí? Eso tiene sentido. Sin embargo, parece que varios sistemas dependen de la duplicación: Travis instala algunos paquetes predeterminados (antiguos) en su virtualenv y dice que los use requirements.txt. Si pregunto cómo garantizar que las dependencias se utilicen a más tardar setup.py, la gente insiste en que debería usarlas requirements.txt.
Sam Brightman
2
El mejor consejo que puede obtener de todo esto es encontrar un modelo que funcione para usted, documentarlo bien y asegurarse de que todas las personas con las que trabaja lo entiendan. Piense detenidamente por qué está haciendo cada parte y si realmente tiene sentido para su caso de uso. Y trate de mantenerse lo más leído posible sobre el estado actual de construcción, empaque y publicación en Python, en caso de que las cosas mejoren. Pero no aguantes la respiración.
Jonathan Hanson
90

No puede tomar un identificador de archivo. El install_requiresargumento solo puede ser una cadena o una lista de cadenas .

Por supuesto, puede leer su archivo en el script de configuración y pasarlo como una lista de cadenas a install_requires.

import os
from setuptools import setup

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

setup(...
install_requires=required,
...)
Fredrick Brennan
fuente
55
Aunque útil, esto cambia la especificación de los requisitos de ser declarativa a imperativa. Esto hace imposible que algunas herramientas descubran cuáles son sus requisitos. Por ejemplo, PyCharm ofrece instalación automática de todos los requisitos especificados en install_requires. Sin embargo, no funciona si no utiliza la sintaxis declarativa.
Piotr Dobrogost
55
@PiotrDobrogost Quizás el desarrollador de PyCharm debería arreglar su programa entonces. setup.pyes un programa que debería ejecutarse, no un archivo de datos que debería analizarse. Eso no empeora esta respuesta.
Fredrick Brennan
55
Solo estoy señalando posibles problemas; Esta respuesta está perfectamente bien. No solo PyCharm tiene problemas con la información "oculta" detrás del código. Este es un problema universal y, por lo tanto, hay un movimiento general hacia la especificación declarativa de metadatos en el empaquetado de Python.
Piotr Dobrogost
33
Funciona bien siempre que lo coloque include requirements.txten su MANIFEST.ino no podrá instalar su biblioteca desde una distribución fuente.
Pankrat
44
Sé que esta es una pregunta antigua, pero al menos hoy en día puede configurar PyCharm para analizar un archivo de requisitos en Preferencias-> Herramientas-> Herramientas integradas de Python-> Archivo de requisitos del paquete
lekksi
64

Los archivos de requisitos usan un formato de pip expandido, que solo es útil si necesita complementarlo setup.pycon restricciones más fuertes, por ejemplo, especificando las URL exactas de las que deben proceder algunas de las dependencias, o la salida de pip freezecongelar todo el conjunto de paquetes a trabajo conocido versiones. Si no necesita restricciones adicionales, use solo a setup.py. Si siente que realmente necesita enviar un requirements.txtcorreo electrónico de todos modos, puede hacerlo en una sola línea:

.

Será válido y se referirá exactamente al contenido del setup.pyque está en el mismo directorio.

Tobu
fuente
9
Pero en este caso también intentaría instalar mi aplicación. ¿Qué sucede si no lo necesito y solo quiero instalar install_requires?
ffeast
2
Para profundizar en lo que @ffeast pregunta, si los requisitos solo existen en setup.py, ¿hay alguna forma de instalar los requisitos (equivalentes a pip install -r requirements.txt ) sin instalar el paquete en sí?
haridsv
1
@ffeast @haridsv -e .debería ser suficiente. Consulte esta página: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter
44
@ DexD.Hunter todavía intenta instalar la aplicación en sí. Esto no es lo que queremos
ffeast
38

Si bien no es una respuesta exacta a la pregunta, recomiendo la publicación del blog de Donald Stufft en https://caremad.io/2013/07/setup-vs-requirement/ para una buena interpretación de este problema. Lo he estado usando con gran éxito.

En resumen, requirements.txtno es una setup.pyalternativa, sino un complemento de implementación. Mantenga una abstracción adecuada de las dependencias del paquete en setup.py. Configure requirements.txto más de ellos para obtener versiones específicas de dependencias de paquetes para desarrollo, prueba o producción.

Por ejemplo, con los paquetes incluidos en el repositorio bajo deps/:

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

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

pip ejecuta paquetes setup.pye instala las versiones específicas de dependencias declaradas en install_requires. No hay duplicidad y se preserva el propósito de ambos artefactos.

calabaza famosa
fuente
77
Esto no funciona cuando desea proporcionar un paquete para que otros lo instalen a través de pip install my-package. Si las dependencias de my-package no se enumeran en my-package / setup.py, no las instala pip install my-package. No he podido determinar cómo proporcionar un paquete para otros que incluya dependencias sin indicarlo explícitamente en setup.py. Me encantaría saber si alguien ha descubierto cómo mantenerlo SECO mientras permite que otros instalen las dependencias my-package + sin descargar el archivo de requisitos y llamar manualmente pip install -r my-package/requirements.txt.
Malina
2
@Malina El paquete aquí es perfectamente instalable sin requirements.txt. Ese es todo el punto. Se actualizó la pregunta para aclarar las cosas. También se actualizó el enlace de publicación de blog obsoleto.
famousgarkin
entonces, cuando ejecute setup.py, ¿llamará a require.txt para versiones específicas de los archivos listados en stup.py?
Dtracers
Es al revés @dtracers. require.txt apunta al paquete en sí mismo, donde se podrían recoger las dependencias de setup.py. Entonces, cuando se instala usando los requisitos, funciona y cuando se instala a través de pip, también funciona, en ambos casos, usando las dependencias de setup.py, pero también permitiendo instalar más cosas cuando se usa
require.txt
20

El uso parse_requirementses problemático porque la API de pip no está documentada y soportada públicamente. En el pip 1.6, esa función en realidad se está moviendo, por lo que es probable que los usos existentes se rompan.

Una forma más confiable de eliminar la duplicación entre setup.pyy requirements.txtes especificar sus dependencias setup.pyy luego ponerlas -e .en su requirements.txtarchivo. pipAquí encontrará información de uno de los desarrolladores sobre por qué es una mejor manera de hacerlo: https://caremad.io/blog/setup-vs-requirement/

Wilfredo Sánchez Vega
fuente
@Tommy Pruebe esto: caremad.io/2013/07/setup-vs-requirement Este es el mismo enlace que se publicó en otra respuesta.
amit
18

La mayoría de las otras respuestas anteriores no funcionan con la versión actual de la API de pip. Aquí está la forma correcta * de hacerlo con la versión actual de pip (6.0.8 en el momento de la escritura, también funcionaba en 7.1.2. Puede verificar su versión 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
    ....
)

* Correcto, ya que es la forma de usar parse_requirements con el pip actual. Probablemente todavía no sea la mejor manera de hacerlo, ya que, como decían los carteles anteriores, pip realmente no mantiene una API.

fabianvf
fuente
14

Instale el paquete actual en Travis. Esto evita el uso de un requirements.txtarchivo. Por ejemplo:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py
vdboor
fuente
2
Esta es, con mucho, la mejor combinación de "correcto" y "práctico". Agregaría que si después de que pasan las pruebas puede hacer que Travis genere un require.txt pip freezey exporte ese archivo a algún lugar como un artefacto (como S3 o algo así), entonces tendría una excelente manera de instalar repetidamente exactamente lo que probado
Jonathan Hanson el
4

from pip.req import parse_requirements no funcionó para mí y creo que es para las líneas en blanco en mis requerimientos.txt, pero esta función sí funciona

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
fuente
4

Si no desea obligar a sus usuarios a instalar pip, puede emular su comportamiento con esto:

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
fuente
4

La siguiente interfaz quedó obsoleta en pip 10:

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

Así que lo cambié a un simple análisis de texto:

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
fuente
Este enfoque simple funciona más del 90% del tiempo. Para aquellos que usan Python 3.6+, he escrito una respuesta que es una pathlibvariación de la misma.
Acumenus
3

Este enfoque simple lee el archivo de requisitos de setup.py. Es una variación de la respuesta de Dmitiry S. .. Esta respuesta es compatible solo con Python 3.6+.

Por DS , requirements.txtpuede documentar requisitos concretos con números de versión específicos, mientras que setup.pypuede documentar requisitos abstractos con rangos de versiones sueltas.

A continuación hay un extracto de mi 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'),
   ...)

Tenga en cuenta que distutils.text_file.TextFileeliminará los comentarios. Además, según mi experiencia, aparentemente no es necesario que realice ningún paso especial para agrupar el archivo de requisitos.

Acumenus
fuente
2

¡CUIDADO CON EL parse_requirementsCOMPORTAMIENTO!

Tenga en cuenta que pip.req.parse_requirementscambiará los guiones bajos a guiones. Esto me enfureció durante unos días antes de descubrirlo. Ejemplo que demuestra:

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']
MikeTwo
fuente
1
Use unsafe_name para obtener la versión de guiones bajos:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds
55
Como se señaló en otra parte, PIP es una aplicación, no una biblioteca. No tiene una API acordada públicamente, e importarlo a su código no es un caso de uso compatible. No es sorprendente que tenga un comportamiento inesperado; sus funciones internas nunca fueron pensadas para ser utilizadas de esta manera.
Jonathan Hanson el
1

Creé una función reutilizable para esto. En realidad, analiza un directorio completo de archivos de requisitos y los establece en extras_require.

Lo último siempre disponible aquí: 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
fuente
¡muy agradable! incluso maneja los requisitos recursivos con el último pip :)
amohr
@amohr ¡Gracias! Recientemente lo actualicé para un pip aún más tarde, no estoy seguro de por qué están actuando de la forma en que lo hacen, moviendo las cosas a pip._internal... Si no proporciona una API externa utilizable, entonces no debería romper todos esos que están usando todo lo que proporcionas.
trevorj
0

Otra posible solución ...

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

y luego usar ...

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
fuente
de donde treeviene
Francesco Boi
@FrancescoBoi si me perdonas un poco por no presentar una solución que funcione completamente ... el árbol es realmente solo un escaneo del sistema de archivos local (muy similar a un comando "árbol" en Linux). Además, mi solución anterior puede no funcionar completamente en este punto porque pip se actualiza constantemente y usé pip internos.
Brian Bruggeman
0

No recomendaría hacer tal cosa. Como se mencionó varias veces install_requiresy requirements.txtdefinitivamente no se supone que sea la misma lista. Pero dado que hay muchas respuestas engañosas que involucran API privadas internas de pip , podría valer la pena buscar alternativas más sensatas ...

No es necesario que pip analice un requirements.txtarchivo desde un script setuptools setup.py . El proyecto setuptools ya contiene todas las herramientas necesarias en su paquete de nivel superiorpkg_resources .

Podría verse más o menos así:

#!/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
fuente
En caso de que no lo supiera, la razón por la cual muchos (incluido yo mismo) han estado usando pipel análisis y no pkg_resourcesdesde antes de 2015 son errores como github.com/pypa/setuptools/issues/470 . Esta exacta está arreglada hoy en día, pero todavía tengo un poco de miedo de usarla, ya que ambas implementaciones parecen estar desarrolladas por separado.
trevorj
@trevorj Gracias por señalar esto, no lo sabía. El hecho es que hoy en día funciona y participar pip me parece una idea ridícula (particularmente de esta manera). Eche un vistazo a las otras respuestas, la mayoría parecen ligeras variaciones de la misma idea desacertada, sin apenas aviso previo. Y los recién llegados podrían seguir esta tendencia. Esperemos que iniciativas como PEP517 y PEP518 alejen a la comunidad de esta locura.
sinoroc
-1

Cruce la publicación de mi respuesta de esta pregunta SO para otra solución simple de prueba de versión 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],
        ...
    )

Luego, solo incluya todos sus requisitos en el requirements.txtdirectorio raíz del proyecto.

Scrotch
fuente
-1

Hice esto:

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
fuente
-2

Otro parse_requirementstruco que también analiza los marcadores ambientales en 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
)

Debería admitir discos sdist y binarios.

Según lo declarado por otros, parse_requirementstiene varias deficiencias, por lo que esto no es lo que debe hacer en proyectos públicos, pero puede ser suficiente para proyectos internos / personales.

Tuukka Mustonen
fuente
pip 20.1 cambió su API y los marcadores ya no están disponibles parse_requirements(), por lo que ahora falla.
Tuukka Mustonen
-3

Aquí hay un truco completo (probado con pip 9.0.1) basado en la respuesta de Romain que lo analiza requirements.txty filtra de acuerdo con los marcadores actuales del entorno :

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
fuente
1
Esto sólo es parcialmente cierto. Si llama r.match_markers(), en realidad está evaluando los marcadores, que es lo correcto para un sdist. Sin embargo, si está compilando un dist binario (por ejemplo, wheel), el paquete solo enumerará las bibliotecas que coinciden con su entorno de tiempo de compilación.
Tuukka Mustonen
@TuukkaMustonen, entonces, ¿dónde encontrar esto wheel environment(si es lo que la persona intenta hacer) para evaluar los marcadores en su contra?
anatoly techtonik
Consulte stackoverflow.com/a/41172125/165629 que también debería ser compatible bdist_wheel. No evalúa los marcadores, solo los agrega extras_require.
Tuukka Mustonen