it-swarm.dev

Odwołaj się do wymagania.txt dla pliku install_requires kwarg w pliku setup.py setuptools

Mam requirements.txt plik, którego używam z Travis-CI. Głupio wydaje się powielanie wymagań w obu requirements.txt i setup.py, więc miałem nadzieję przekazać uchwyt pliku do install_requires kwarg w setuptools.setup.

Czy to możliwe? Jeśli tak, jak powinienem to zrobić?

Tutaj jest mój requirements.txt plik:

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

Możesz go odwrócić i wymienić zależności w setup.py i mieć pojedynczy znak - kropkę . - w requirements.txt zamiast.


Alternatywnie, nawet jeśli nie jest to zalecane, nadal można przeanalizować requirements.txt plik (jeśli nie odwołuje żadnych zewnętrznych wymagań przez URL) z następującym włamaniem (testowany z pip 9.0.1):

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

To jednak nie filtruje znaczników środowiska .


W starych wersjach pipa, a dokładniej starszych niż 6. , istnieje publiczny interfejs API, który można wykorzystać do osiągnięcia tego. Plik wymagań może zawierać komentarze (#) i może zawierać inne pliki (--requirement lub -r). Zatem jeśli naprawdę chcesz przeanalizować requirements.txt możesz użyć parsera 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
)
224
Romain Hardouin

Na pierwszy rzut oka wydaje się, że requirements.txt i setup.py są głupimi duplikatami, ale ważne jest, aby zrozumieć, że chociaż forma jest podobna, zamierzona funkcja jest zupełnie inna.

Celem autora pakietu, określając zależności, jest powiedzenie „gdziekolwiek instalujesz ten pakiet, są to inne pakiety, których potrzebujesz, aby ten pakiet działał”.

W przeciwieństwie do tego autor wdrażania (którym może być ta sama osoba w innym czasie) ma inne zadanie, mówiąc: „Oto lista pakietów, które zebraliśmy i przetestowaliśmy i które muszę teraz zainstalować”.

Autor pakietu pisze o wielu różnych scenariuszach, ponieważ udostępniają swoją pracę w sposób, o którym mogą nie wiedzieć, i nie mają możliwości dowiedzenia się, jakie pakiety zostaną zainstalowane wraz z pakietem. Aby być dobrym sąsiadem i uniknąć konfliktów wersji zależności z innymi pakietami, muszą określić możliwie szeroki zakres wersji zależności. Co to jest install_requires w setup.py robi.

Autor wdrożenia pisze o bardzo innym, bardzo konkretnym celu: pojedynczej instancji zainstalowanej aplikacji lub usługi zainstalowanej na określonym komputerze. Aby dokładnie kontrolować wdrożenie i upewnić się, że odpowiednie pakiety zostały przetestowane i wdrożone, autor wdrażania musi określić dokładną wersję i lokalizację źródłową każdego pakietu do zainstalowania, w tym zależności i zależności zależności. Dzięki tej specyfikacji wdrożenie może być wielokrotnie stosowane na kilku komputerach lub testowane na maszynie testowej, a autor wdrażania może być pewny, że za każdym razem wdrażane są te same pakiety. Oto co requirements.txt robi.

Widać więc, że chociaż oba wyglądają jak duża lista pakietów i wersji, te dwie rzeczy mają bardzo różne zadania. Zdecydowanie łatwo to wymieszać i zrobić to źle! Ale właściwym sposobem myślenia o tym jest to, że requirements.txt jest „odpowiedzią” na „pytanie” wynikające z wymagań we wszystkich różnych setup.py pliki pakietu. Zamiast pisać ręcznie, często jest generowany przez polecenie pipowi, aby sprawdził wszystkie setup.py plików w zestawie żądanych pakietów, znajdź zestaw pakietów, który jego zdaniem spełnia wszystkie wymagania, a następnie, po ich zainstalowaniu, „zamrozić” tę listę pakietów do pliku tekstowego (w tym miejscu pip freeze nazwa pochodzi od).

Więc na wynos:

  • setup.py powinien zadeklarować najlżejsze możliwe wersje zależności, które nadal są wykonalne. Jego zadaniem jest określenie, z czym konkretny pakiet może współpracować.
  • requirements.txt jest manifestem wdrażania, który definiuje całe zadanie instalacyjne i nie należy go uważać za powiązany z żadnym pakietem. Jego zadaniem jest zadeklarowanie wyczerpującej listy wszystkich niezbędnych pakietów do wdrożenia.
  • Ponieważ te dwie rzeczy mają tak odmienną treść i powody istnienia, nie można po prostu skopiować jednej do drugiej.

Referencje:

145
Jonathan Hanson

Nie może obsłużyć pliku. install_requires argument może może być tylko ciągiem lub listą ciągów .

Możesz oczywiście odczytać plik w skrypcie instalacyjnym i przekazać go jako listę ciągów do install_requires.

import os
from setuptools import setup

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

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

Pliki wymagań używają rozszerzonego formatu pip, który jest użyteczny tylko wtedy, gdy trzeba uzupełnić swoje setup.py z silniejszymi ograniczeniami, na przykład określając dokładne adresy URL, z których niektóre zależności muszą pochodzić, lub wynik pip freeze, aby zamrozić cały zestaw pakietów do znanych wersji. Jeśli nie potrzebujesz dodatkowych ograniczeń, użyj tylko setup.py. Jeśli masz wrażenie, że naprawdę musisz wysłać requirements.txt tak czy inaczej, możesz zrobić z tego jedną linię:

.

Będzie ważny i będzie odnosił się dokładnie do treści setup.py, który znajduje się w tym samym katalogu.

61
Tobu

Chociaż nie jest to dokładna odpowiedź na pytanie, polecam post na blogu Donalda Stuffta pod adresem https://caremad.io/2013/07/setup-vs-requirement/ , aby dobrze zająć się tym problemem. Użyłem go do wielkiego sukcesu.

W skrócie, requirements.txt nie jest setup.py alternatywa, ale uzupełnienie wdrożenia. Zachowaj odpowiednią abstrakcję zależności pakietów w setup.py. Ustaw requirements.txt lub więcej z nich, aby pobrać określone wersje zależności pakietów na potrzeby programowania, testowania lub produkcji.

Na przykład. z pakietami zawartymi w repozytorium pod deps/:

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

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

pip wykonuje pakiet setup.py i instaluje określone wersje zależności zadeklarowane w install_requires. Nie ma duplikatu, a cel obu artefaktów zostaje zachowany.

37
famousgarkin

Większość pozostałych odpowiedzi powyżej nie działa z bieżącą wersją interfejsu API pipa. Oto poprawny * sposób na zrobienie tego z bieżącą wersją pip (6.0.8 w momencie pisania, działał także w 7.1.2. Możesz sprawdzić swoją wersję za pomocą 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
    ....
)

* Prawidłowo, ponieważ jest to sposób na użycie parse_requirements z bieżącym pipem. Prawdopodobnie nie jest to najlepszy sposób, aby to zrobić, ponieważ, jak wspomniano powyżej, pip tak naprawdę nie utrzymuje interfejsu API.

19
fabianvf

Za pomocą parse_requirements jest problematyczny, ponieważ interfejs API pip nie jest publicznie udokumentowany i obsługiwany. W pipie 1.6 ta funkcja faktycznie się porusza, więc istniejące jej zastosowania prawdopodobnie się zepsują.

Bardziej niezawodny sposób na wyeliminowanie powielania między setup.py i requirements.txt określa konkretne zależności w setup.py, a następnie umieść -e . do twojego requirements.txt plik. Niektóre informacje od jednego z programistów pip o tym, dlaczego jest to lepszy sposób, są dostępne tutaj: https://caremad.io/blog/setup-vs-requirement/

18

Zainstaluj bieżący pakiet w Travis. Pozwala to uniknąć użycia requirements.txt plik. Na przykład:

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

Jeśli nie chcesz zmuszać użytkowników do instalowania pip, możesz naśladować jego zachowanie za pomocą:

import sys

from os import path as p

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


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

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


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

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

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

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

from pip.req import parse_requirements nie działało dla mnie i myślę, że dotyczy to pustych wierszy w pliku wymagania.txt, ale ta funkcja działa

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

reqs = parse_requirements(<requirements_path>)

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

WAŻAĆ NA parse_requirements ZACHOWANIE!

Proszę to zanotować pip.req.parse_requirements zmieni podkreślenia na myślniki. Rozwścieczyło mnie to przez kilka dni, zanim to odkryłem. Przykład demonstrujący:

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

produkuje

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

Następujący interfejs stał się przestarzały w pip 10:

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

Więc przełączyłem to na proste parsowanie tekstu:

with open('requirements.txt', 'r') as f:
    install_reqs = [
        s for s in [
            line.strip(' \n') for line in f
        ] if not s.startswith('#') and s != ''
    ]
3
Dmitriy Sintsov

Stworzyłem do tego funkcję wielokrotnego użytku. W rzeczywistości analizuje cały katalog plików wymagań i ustawia je na dodatkowe wymagania.

Najnowsze zawsze dostępne tutaj: https://Gist.github.com/akatrevorjay/293c26fefa24a7b812f5

import glob
import itertools
import os

from setuptools import find_packages, setup

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


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

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

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

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

    Example:

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

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

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

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

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

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

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

        extras[barefn] = reqs

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

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

    def _listify(dikt):
        ret = {}

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

        return ret

    ret = _listify(ret)

    return ret
2
trevorj

Inne możliwe rozwiązanie ...

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

a następnie użyć ...

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

To proste podejście odczytuje plik wymagań z setup.py. Jest to wariant odpowiedzi Dmitiry S. . Ta odpowiedź jest kompatybilna tylko z Python 3.6+.

Per D.S. , requirements.txt może dokumentować konkretne wymagania za pomocą określonych numerów wersji, natomiast setup.py może dokumentować abstrakcyjne wymagania za pomocą luźnych zakresów wersji.

Poniżej znajduje się fragment mojego setup.py.

import distutils.text_file
from pathlib import Path
from typing import List

def parse_requirements(filename: str) -> List[str]:
    """Return requirements from requirements file."""
    # Ref: https://stackoverflow.com/a/42033122/
    return distutils.text_file.TextFile(filename=Path(__file__).with_name(filename)).readlines()

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

Pamiętaj, że distutils.text_file.TextFile usunie komentarze. Z mojego doświadczenia wynika, że ​​najwyraźniej nie musisz podejmować żadnych specjalnych kroków w celu dołączenia pliku wymagań.

1
Acumenus

Cross wysyłając moją odpowiedź od this SO pytanie dla innego prostego rozwiązania, sprawdzonego w wersji 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 __== '__main__':
    setup(
        ...
        install_requires=[str(requirement.req) for requirement in requriements],
        ...
    )

Następnie po prostu wprowadź wszystkie swoje wymagania w requirements.txt w katalogu głównym projektu.

0
Scrotch