setuptools setup.py 파일의 install_requires kwarg에 대한 참조 requirements.txt


279

requirements.txtTravis-CI와 함께 사용 중인 파일이 있습니다. 모두의 요구 사항을 복제하는 어리석은 것 requirements.txtsetup.py난에 파일 핸들을 전달하기 위해 기대했다, 그래서 install_requires에서 kwarg setuptools.setup.

이게 가능해? 그렇다면 어떻게해야합니까?

requirements.txt파일 은 다음과 같습니다 .

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_requires는 패키지가 작동하는 데 필요하고 패키지 개발자가 사용하는 패키지에 대한 종속성을 선언하는 데 사용되는 반면 requirements.txt환경의 설치를 자동화하는 데 사용되며 추가 소프트웨어를 설치하고 버전 고정을 수행하고 sysadmins가 배포하는 데 사용됩니다. 꾸러미. 그들의 역할과 대상 고객은 크게 다르므로 OP 소원과 같이 결합하는 것은 진정한 디자인 실수입니다.
Zart

7
내 2 센트 setup.py에서 requirements.txt를 사용하지 마십시오. 목적은 다르다, caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne

3
복잡한 답변이 많이 있습니다. 평범한 옛날에 무엇이 문제입니까 [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider

이 작업은 권장하지 않습니다. 그러나 실제로 필요한 경우 간단합니다. setuptools 자체에 이미 필요한 모든 것이 있습니다pkg_resources.parse_requirements()
sinoroc

답변:


246

당신은 그것을 주위를 뒤집어 목록에서 종속성을 수 setup.py및 단일 문자가 - 점을 .에 - requirements.txt대신.


또는 권장 requirements.txt하지 않더라도 (다음으로 테스트 한 pip 9.0.1) 해킹을 사용 하여 파일 (URL로 외부 요구 사항을 참조하지 않는 경우) 을 파싱 할 수 있습니다 .

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

그러나 환경 마커 는 필터링하지 않습니다 .


이전 버전의 pip, 특히 6.0보다 오래된 버전에서는 이를 달성하는 데 사용할 수있는 공개 API가 있습니다. 요구 사항 파일은 주석 ( #)을 포함 할 수 있으며 다른 파일 ( --requirement또는 -r)을 포함 할 수 있습니다 . 따라서 정말로 파싱 requirements.txt하려면 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
)

26
사용자에게 pip가 설치되어 있지 않으면 어떻게합니까? 카붐?
Gringo Suave

82
@GringoSuave 사용자가 pip를 설치하지 않은 경우 먼저 설치해야합니다.
guettli

7
비파이 패키지를 가리키는 -e 또는 -f ( "편집 가능"git repo) 행이있는 경우 요구 사항 파일에 URL을 제공해야합니다. 이 옵션을 사용합니다 :setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
호브

91
당신은 정말로 이것을하고 싶지 않습니다. pip 관리자로 말하기 pip는 이와 같은 API로 호출되는 것을 전혀 지원하지 않습니다. 실제로 pip 1.6 (현재 다음 버전)은이 기능을 이동시킵니다.
Donald Stufft

26
더 이상 허용되는 답변이 아니어야합니다. 끔찍하게 고장났다. 그것이 작동했을 때조차도 끔찍하게 불필요합니다. 이후 pip에서 종속성을 분석 기본값 setup.py의 부재에서 requirements.txt간단한 대답 예리하게 지적한 토부 아래는 것입니다 모든 종속성을 나열 setup.py하고 제거 requirements.txt. 둘 다 필요한 응용 프로그램의 경우 단순히 종속성 목록 requirements.txt을 단순히 .문자로 줄이십시오. 끝난.
Cecil Curry

194

그것의 얼굴에, 그 보이지 않는 requirements.txtsetup.py바보 중복, 그러나 그것은 형태가 유사한 반면, 의도 한 기능이 매우 다르다는 것을 이해하는 것이 중요합니다.

종속성을 지정할 때 패키지 작성자의 목표는 "이 패키지를 설치할 때마다이 패키지가 작동하기 위해 필요한 다른 패키지입니다"라고 말하는 것입니다.

반대로 배포 작성자 (다른 시간에 같은 사람 일 수 있음)는 "우리가 함께 모아서 테스트하여 설치해야하는 패키지 목록이 있습니다"라는 점에서 다른 역할을합니다.

패키지 작성자는 다양한 시나리오에 대해 작성합니다. 그들이 알지 못하는 방식으로 작업을 수행하고 패키지와 함께 어떤 패키지가 설치 될지 알 수 없기 때문입니다. 좋은 이웃이되고 다른 패키지와의 종속성 버전 충돌을 피하려면 가능한 한 다양한 종속성 버전을 지정해야합니다. 이것이하는 일 install_requires입니다 setup.py.

배포 작성자는 매우 다른 매우 구체적인 목표, 즉 특정 컴퓨터에 설치된 설치된 응용 프로그램 또는 서비스의 단일 인스턴스를 작성합니다. 배포를 정확하게 제어하고 올바른 패키지를 테스트하고 배포하려면 배포 작성자가 종속성 및 종속성의 종속성을 포함하여 설치할 모든 패키지의 정확한 버전과 소스 위치를 지정해야합니다. 이 사양을 사용하면 배포를 여러 컴퓨터에 반복적으로 적용하거나 테스트 컴퓨터에서 테스트 할 수 있으며 배포 작성자는 매번 동일한 패키지가 배포 될 것을 확신 할 수 있습니다. 이것이 무엇을 하는가 requirements.txt입니다.

패키지와 버전의 큰 목록처럼 보이지만이 두 가지 작업은 매우 다릅니다. 그리고 이것을 섞어서 틀리게 만드는 것은 확실히 쉽습니다! 그러나 이것을 생각하는 올바른 방법 requirements.txt은 모든 다양한 setup.py패키지 파일 의 요구 사항에 의해 제기 된 "질문"에 대한 "답변" 입니다. 직접 작성하는 대신, pip에게 setup.py원하는 패키지 세트의 모든 파일 을보고, 모든 요구 사항에 맞는 패키지 세트를 찾은 다음, 설치 후 "고정"으로 생성하여 생성됩니다. "해당 패키지 목록을 텍스트 파일로 저장합니다 (여기서 pip freeze이름이 나오는 곳).

따라서 테이크 아웃 :

  • setup.py여전히 실행 가능한 가장 느슨한 종속성 버전을 선언해야합니다. 그 작업은 특정 패키지가 작동 할 수있는 것을 말하는 것입니다.
  • requirements.txt전체 설치 작업을 정의하는 배포 매니페스트이며 어떤 패키지에 묶여 있다고 생각해서는 안됩니다. 배포 작업을 수행하는 데 필요한 모든 패키지의 전체 목록을 선언해야합니다.
  • 이 두 가지 내용은 기존과 다른 내용과 이유를 가지고 있기 때문에 서로를 단순히 복사하는 것은 불가능합니다.

참고 문헌 :


10
이것은 패키지 설치라는 혼란에 순서를 둘 수있는 가장 좋은 설명 중 하나입니다! :)
Kounavi

6
개발자가 requirements.txt설치 또는 테스트를위한 구체적 / 동결 한 요구 사항이 포함 된 패키지 소스와 함께 버전 제어 를 유지해야하는 이유는 여전히 명확하지 않습니다 . 확실히 setup.py프로젝트 자체 내에서이 목적으로 사용될 수 있습니까? 프로젝트 관리 를 지원 하는 도구 (예 : 리팩토링, 릴리스 등)에 이러한 파일을 사용한다고 상상할 수 있습니다 .
Sam Brightman

2
@samBrightman 전적으로 동의합니다. 라이브러리 패키지 또는 응용 프로그램 패키지가 requirements.txt 파일을 코드를 사용하여 저장소에 커밋해야 한다고 생각하지 않습니다 . 빌드 테스트 중에 생성 된 아티팩트 여야한다고 생각하고 빌드 매니페스트를 문서화하고 궁극적으로 배포 아티팩트를 생성하는 데 사용됩니다.
Jonathan Hanson

6
따라서 requirements.txt빌드 프로세스 자체에서 일반적으로 사용되지는 않지만 주어진 빌드를 생성 한 세계의 상태에 대한 더 많은 문서가 있습니까? 말이 되네요 그러나 여러 시스템이 복제에 의존하는 것처럼 보입니다. Travis는 virtualenv에 일부 기본 (이전) 패키지를 설치하고 사용한다고 말합니다 requirements.txt. 의존성을 최신 상태로 유지하는 방법을 묻는다면 setup.py사람들은 내가 사용해야한다고 주장한다 requirements.txt.
Sam Brightman

2
이 중 가장 유용한 조언은 자신에게 적합한 모델을 찾고 문서를 잘 작성하며 함께 작업하는 모든 사람이 이해하도록하는 것입니다. 각 비트를 수행하는 이유와 사용 사례에 적합한 지 여부를 생각하십시오. 그리고 상황이 나아질 때를 대비하여 파이썬에서 현재의 빌드, 패키징 및 게시 상태에 대해 가능한 한 잘 읽어보십시오. 그러나 숨을 참지 마십시오.
Jonathan Hanson

89

파일 핸들을 사용할 수 없습니다. install_requires인수는 수 만 문자열이나 문자열 목록 수 .

물론 설정 스크립트에서 파일을 읽고 문자열 목록으로 전달할 수 install_requires있습니다.

import os
from setuptools import setup

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

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

5
이것은 유용하지만 요구 사항 명세를 선언에서 명령으로 바꾼다. 따라서 일부 도구에서 요구 사항을 파악할 수 없습니다. 예를 들어 PyCharm은에 지정된 모든 요구 사항을 자동으로 설치합니다 install_requires. 그러나 선언적 구문을 사용하지 않으면 작동하지 않습니다.
Piotr Dobrogost

55
@PiotrDobrogost 아마도 PyCharm 개발자는 프로그램을 수정해야합니다. setup.py구문 분석해야하는 데이터 파일이 아니라 실행되어야하는 프로그램입니다. 그렇다고이 답변이 더 나빠지지는 않습니다.
프레드릭 브레넌

5
가능한 문제를 지적하고 있습니다. 이 답변은 완벽합니다. 코드 뒤에 "숨겨진"정보에 문제가있는 것은 PyCharm만이 아닙니다. 이것은 보편적 인 문제이므로 파이썬 패키징에서 메타 데이터의 선언적 사양으로의 일반적인 이동이 있습니다.
Piotr Dobrogost

33
당신이 넣어 같이 잘 작동 include requirements.txt당신에 MANIFEST.in또는 소스 배포판에서 라이브러리를 설치할 수 없습니다.
Pankrat

4
나는이 오래된 질문 알아요,하지만 당신은 적어도 현재 구성 PyCharm 환경 설정 -> 도구 -에서 요구 사항 파일을 구문 분석 할 수 있습니다> 파이썬은 도구 -> 패키지 요구 사항 파일 통합
lekksi

64

요구 사항 파일은 확장 된 pip 형식을 사용합니다. setup.py예를 들어 일부 종속 항목의 정확한 URL을 지정하거나 pip freeze전체 패키지 세트를 알려진 작업으로 고정하는 출력을 지정하는 등보다 강력한 제한 조건 으로 보완해야하는 경우에만 유용 합니다. 버전. 추가 제약이 필요하지 않은 경우을 사용하십시오 setup.py. requirements.txt어쨌든 실제로 배송해야 한다고 생각되면 한 줄로 만들 수 있습니다.

.

유효하며 setup.py동일한 디렉토리에있는 내용을 정확하게 참조하십시오 .


9
그러나이 경우 내 앱도 설치하려고합니다. 필요하지 않고 install_requires 만 설치하려면 어떻게해야합니까?
ffeast

2
요구 사항이 setup.py에만 존재하는 경우 @ffeast가 요구하는 사항을 자세히 설명 pip install -r requirements.txt 하기 위해 패키지 자체를 설치하지 않고 요구 사항을 설치하는 방법이 있습니까?
haridsv

1
@ffeast @haridsv -e .이면 충분합니다. 이 페이지를 확인하십시오 : caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter 여전히 앱 자체 설치를 시도합니다. 이것은 우리가 원하는 것이 아닙니다
ffeast at

38

이 질문에 대한 정확한 대답은 아니지만 이 문제를 잘 이해 하려면 Donald Stufft의 블로그 게시물 ( https://caremad.io/2013/07/setup-vs-requirement/) 을 추천합니다. 나는 그것을 큰 성공을 위해 사용하고 있습니다.

즉, requirements.txt아닌 setup.py다른,하지만 배포 보완. 에 패키지 종속성의 적절한 추상화를 유지하십시오 setup.py. requirements.txt개발, 테스트 또는 프로덕션을 위해 특정 버전의 패키지 종속성을 가져 오도록 설정하십시오 .

예를 들어 아래의 저장소에 패키지가 포함 된 경우 deps/:

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

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

pip는 패키지를 실행 setup.py하고에 선언 된 특정 버전의 종속성을 설치합니다 install_requires. 이중성이 없으며 두 아티팩트의 목적이 유지됩니다.


7
다른 사용자가을 통해 설치할 패키지를 제공하려는 경우에는 작동하지 않습니다 pip install my-package. my-package의 종속성이 my-package / setup.py에 나열되어 있지 않으면로 설치되지 않습니다 pip install my-package. setup.py에 명시 적으로 명시하지 않고 종속성을 포함하는 다른 패키지를 제공하는 방법을 결정할 수 없었습니다. 누군가가 요구 사항 파일을 다운로드하고 수동으로 호출하지 않고 다른 사람이 내 패키지 + 종속성을 설치할 수 있도록 DRY를 유지하는 방법을 알고 있는지 알고 싶습니다 pip install -r my-package/requirements.txt.
Malina

2
@Malina 여기 패키지는 완벽하게 설치 가능 requirements.txt합니다. 그게 요점입니다. 더 명확하게하기 위해 질문을 업데이트했습니다. 더 이상 사용되지 않는 블로그 게시물 링크도 업데이트되었습니다.
famousgarkin

따라서 setup.py를 실행할 때 stup.py에 나열된 특정 버전의 파일에 대해 requirements.txt를 호출합니까?
dtracers 2016 년

@dtracers와 다른 방법입니다. requirements.txt는 setup.py의 종속 항목을 가져올 수있는 자체 패키지 패키지를 가리 킵니다. 따라서 요구 사항을 사용하여 설치하면 작동하고 pip를 통해 설치할 때 setup.py의 종속 항목을 사용하는 두 경우 모두 작동하지만 requirements.txt를 사용할 때 더 많은 것을 설치할 수 있습니다.
smido

20

parse_requirementspip API가 공개적으로 문서화되고 지원되지 않기 때문에 사용 에 문제가 있습니다. pip 1.6에서는 해당 기능이 실제로 이동하므로 기존 기능을 사용하기가 어려울 수 있습니다.

더 신뢰할 수있는 방법 사이의 중복을 제거하기 setup.pyrequirements.txt특정에 당신의 종속성을에 setup.py다음 넣어 -e .당신에 requirements.txt파일. pip왜 더 좋은 방법인지 에 대한 개발자 중 한 사람의 정보는 https://caremad.io/blog/setup-vs-requirement/에서 확인할 수 있습니다.


@Tommy 이것을 시도하십시오 : caremad.io/2013/07/setup-vs-requirement 이것은 다른 답변에 게시 된 것과 동일한 링크입니다.
amit

18

위의 다른 답변 대부분은 현재 버전의 pip API에서 작동하지 않습니다. 현재 버전의 pip (작성 당시 6.0.8, 7.1.2에서 작동했습니다. 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
    ....
)

* 현재 pip와 함께 parse_requirements를 사용하는 방법이라는 점에서 정확합니다. 위의 포스터가 말했듯이 pip는 실제로 API를 유지 관리하지 않기 때문에 여전히 최선의 방법은 아닙니다.


14

Travis에 현재 패키지를 설치하십시오. 이것은 requirements.txt파일 사용을 피 합니다. 예를 들면 다음과 같습니다.

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

2
이것은 "올바른"과 "실제적인"의 가장 좋은 조합입니다. 테스트를 통과 한 후 Travis가 requirements.txt를 생성하고 pip freeze해당 파일을 S3 등의 아티팩트로 내보낼 수있는 경우, 원하는 내용을 반복적으로 정확하게 설치할 수있는 좋은 방법이 있습니다. 테스트.
Jonathan Hanson

4

from pip.req import parse_requirements 나를 위해 작동하지 않고 내 requirements.txt의 빈 줄에 대한 것이라고 생각하지만이 기능은 작동합니다

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

사용자가 pip를 설치하지 못하게하려면 다음과 같이 동작을 에뮬레이션 할 수 있습니다.

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

다음 인터페이스는 pip 10에서 더 이상 사용되지 않습니다.

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

그래서 간단한 텍스트 파싱으로 전환했습니다.

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

이 간단한 접근 방식은 90 % 이상 작동합니다. Python 3.6 이상을 사용하는 사람들에게는 변형답변을pathlib 작성 했습니다.
Acumenus

3

이 간단한 접근 방식은에서 요구 사항 파일을 읽습니다 setup.py. Dmitiry S 의 답변의 변형입니다 . . 이 답변은 Python 3.6 이상에서만 호환됩니다.

DS , requirements.txt반면, 특정 버전 번호와 구체적인 요구 사항을 문서화 할 수setup.py 문서화 할 수있는 느슨한 버전 범위로 추상 요구 사항을 문서화 할 수 있습니다.

아래는 내 발췌 한 것입니다 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'),
   ...)

주석 distutils.text_file.TextFile이 제거됩니다. 또한 내 경험에 따라 요구 사항 파일에 번들하기 위해 특별한 단계를 수행 할 필요는 없습니다.


2

parse_requirements행동 주의 !

그주의 사항 pip.req.parse_requirements대시에 밑줄 변경됩니다. 내가 그것을 발견하기 전에 며칠 동안 나를 화나게했다. 데모 예 :

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

생산

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

1
unsafe_name 을 사용 하여 밑줄 버전을 얻으십시오.[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds

5
다른 곳에서 지적했듯이 PIP는 라이브러리가 아닌 응용 프로그램입니다. 공개적으로 동의 한 API가 없으며 코드로 가져 오는 것은 지원되는 사용 사례가 아닙니다. 예상치 못한 동작이 있다는 것은 놀라운 일이 아닙니다. 내부 기능은 절대 이런 식으로 사용되지 않았습니다.
Jonathan Hanson

1

이를 위해 재사용 가능한 함수를 만들었습니다. 실제로 요구 사항 파일의 전체 디렉토리를 구문 분석하여 extras_require로 설정합니다.

여기에 항상 사용할 최신 : 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)

아주 좋아요! 최신 pip로 재귀 요구 사항도 처리합니다. :)
amohr

@amohr 감사합니다! 나는 최근에 나중에 핍을 위해 그것을 업데이트했다. 나는 그들이 물건을 pip._internal. 당신이 제공하는 모든 것을 사용하고 있습니다.
trevorj

0

또 다른 가능한 해결책은 ...

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

그리고 사용하려면 ...

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

어디에서 tree왔습니까?
Francesco Boi

@FrancescoBoi 당신이 완전히 작동하는 솔루션을 제시하지 않은 것에 대해 조금 용서한다면 트리는 실제로 로컬 파일 시스템의 스캔입니다 (리눅스의 "tree"명령과 매우 유사합니다). 또한 pip가 지속적으로 업데이트되고 pip internals를 사용했기 때문에 위의 솔루션이 현재 작동하지 않을 수 있습니다.
브라이언 브루 게먼

0

나는 그런 일을하지 않는 것이 좋습니다. 여러 번 언급 한 바와 같이 install_requires하고 requirements.txt확실히 동일한 목록 있어야되지 않습니다. 그러나 pip의 개인 내부 API와 관련하여 오해의 소지가 많은 답변이 있기 때문에 더 멋진 대안을 살펴볼 가치가 있습니다 ...

필요가 없습니다 piprequirements.txtsetuptools setup.py 스크립트 에서 파일 을 구문 분석 . setuptools에는 이미 프로젝트의에 필요한 모든 도구가 포함되어 최상위 패키지 pkg_resources.

다음과 같이 다소 보일 수 있습니다.

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

잘 모르는 경우 2015 년 이전에 많은 (자체 포함) pip파싱을 사용하고 파싱을 사용하지 않은 pkg_resources이유는 github.com/pypa/setuptools/issues/470 과 같은 버그 입니다. 이 정확한 것은 요즘 고정되어 있지만 두 가지 구현이 별도로 개발 된 것으로 보이므로 여전히 사용하기가 약간 무섭습니다.
trevorj

@trevorj 이것을 지적 해 주셔서 감사합니다. 사실 요즘은 효과가 있으며 핍을 관련시키는 것은 나에게 어리석은 생각처럼 보입니다 (특히이 방식으로). 다른 답변을 살펴보십시오. 대부분 경고 통지없이 같은 잘못 권고 된 아이디어의 약간의 변형처럼 보입니다. 그리고 새로 온 사람들은이 추세를 따를 것입니다. PEP517 및 PEP518과 같은 이니셔티브가 커뮤니티를이 광기로부터 멀어지게 할 것입니다.
sinoroc

-1

간단한 질문에 대한 답변을 교차 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],
        ...
    )

그런 다음 requirements.txt프로젝트 루트 디렉토리 아래에 모든 요구 사항을 입력하십시오 .


-1

나는 이걸했다:

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

parse_requirements환경 마커를 파싱하는 또 다른 해킹 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
)

sdist와 binary dists를 모두 지원해야합니다.

다른 사람들이 말했듯 parse_requirements이 몇 가지 단점이 있으므로 이것은 공공 프로젝트에서해야 할 일이 아니지만 내부 / 개인 프로젝트에는 충분할 수 있습니다.


pip 20.1은 API를 변경했으며 parse_requirements()더 이상 마커를 사용할 수 없으므로 이제 실패합니다.
Tuukka Mustonen

-3

다음은 현재 환경 마커 에 따라 구문 분석 하고 필터링하는 Romain의 답변pip 9.0.1기반으로 한 완전한 해킹 (으로 테스트 )입니다 .requirements.txt

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
이것은 부분적으로 만 사실입니다. 전화 r.match_markers()하면 실제로 마커를 평가하는 것입니다. 이것은 sdist에 대해 올바른 일입니다. 당신은 바이너리 DIST (예 : 휠)을 구축하는 경우 그러나 패키지는 일치하는 것을 그 라이브러리를 나열합니다 당신의 빌드 타임 환경을.
Tuukka Mustonen

@TuukkaMustonen, wheel environment마커를 평가 하기 위해 어디서 찾을 수 있습니까?
anatoly techtonik

또한 지원해야하는 stackoverflow.com/a/41172125/165629 를 참조하십시오 bdist_wheel. 마커를 평가하지 않고에 추가합니다 extras_require.
Tuukka Mustonen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.