Tham chiếu yêu cầu.txt cho tệp kwarg cài đặt trong tệp setuptools setup.py


279

Tôi có một requirements.txttệp mà tôi đang sử dụng với Travis-CI. Có vẻ ngớ ngẩn khi sao chép các yêu cầu trong cả hai requirements.txtsetup.py, vì vậy tôi đã hy vọng chuyển một xử lý tệp cho install_requireskwarg trong setuptools.setup.

Điều này có thể không? Nếu vậy, tôi nên làm thế nào về nó?

Đây là requirements.txttập tin của tôi :

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được sử dụng để khai báo các phụ thuộc vào các gói được yêu cầu để gói hoạt động và được nhà phát triển gói requirements.txtsử dụng , trong khi được sử dụng để tự động cài đặt các môi trường, cho phép cài đặt phần mềm bổ sung và thực hiện ghim phiên bản và được sử dụng bởi các sysadins triển khai gói. Vai trò và đối tượng mục tiêu của họ khác nhau đáng kể, vì vậy cố gắng kết hợp chúng như mong muốn của OP là một lỗi thiết kế chính hãng.
Zart

7
2 xu của tôi. Không sử dụng tests.txt trong setup.py của bạn. Các mục đích là khác nhau, ared caremad.io/2013/07/setup-vs-requirement
Philippe Ombredanne

3
Tôi thấy rất nhiều câu trả lời phức tạp. Có gì sai với đồng bằng cũ [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider

Nó không được khuyến khích để làm điều này. Nhưng nếu thực sự cần thiết thì thật đơn giản: bản thân setuptools đã có mọi thứ cần thiếtpkg_resources.parse_requirements()
sinoroc

Câu trả lời:


246

Bạn có thể lật nó xung quanh và liệt kê các phụ thuộc vào setup.pyvà có một ký tự duy nhất - một dấu chấm .- requirements.txtthay vào đó.


Ngoài ra, ngay cả khi không được thông báo, vẫn có thể phân tích requirements.txttệp (nếu nó không đưa ra bất kỳ yêu cầu bên ngoài nào bằng URL) với bản hack sau (đã được thử nghiệm pip 9.0.1):

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

Điều này không lọc các dấu môi trường mặc dù.


Trong các phiên bản cũ của pip, cụ thể hơn cũ hơn 6.0 , có một API công khai có thể được sử dụng để đạt được điều này. Một tệp yêu cầu có thể chứa ý kiến ​​( #) và có thể bao gồm một số tệp khác ( --requirementhoặc -r). Vì vậy, nếu bạn thực sự muốn phân tích cú pháp, requirements.txtbạn có thể sử dụng trình phân tích cú pháp 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
Nếu người dùng không cài đặt pip thì sao? Ka-boom?
Gringo Suave

82
@GringoSuave Nếu người dùng chưa cài đặt pip, anh ta cần cài đặt nó trước.
guettli

7
Bạn cũng cần cung cấp các url trong tệp yêu cầu của mình, trong trường hợp có bất kỳ dòng -e hoặc -f ("có thể chỉnh sửa" git repo) nào trỏ đến các gói không phải pypi. Sử dụng cái này:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
hobs

91
Bạn thực sự không muốn làm điều này. Nói như một pip duy trì pip không hỗ trợ được gọi là API như thế này. Trong thực tế pip 1.6 (phiên bản tiếp theo tại thời điểm này) di chuyển chức năng này.
Donald Stufft

26
Đây không còn là câu trả lời được chấp nhận, nếu nó cần phải có. Nó bị phá vỡ một cách trắng trợn. Ngay cả khi nó hoạt động, nó vẫn không cần thiết. Vì pipmặc định để phân tích các phụ thuộc từ setup.pykhi không có requirements.txt, câu trả lời đơn giản được Tobu lưu ý dưới đây là liệt kê tất cả các phụ thuộc trong setup.pyvà loại bỏ requirements.txt. Đối với các ứng dụng yêu cầu cả hai, chỉ cần giảm danh sách phụ thuộc vào requirements.txtchỉ đơn thuần là .ký tự. Làm xong.
Cecil Curry

194

Trên khuôn mặt của nó, nó dường như đó requirements.txtsetup.pylà bản sao ngớ ngẩn, nhưng điều quan trọng là phải hiểu rằng trong khi các hình thức tương tự, chức năng dự định là rất khác nhau.

Mục tiêu của một tác giả gói, khi chỉ định các phụ thuộc, là nói "bất cứ nơi nào bạn cài đặt gói này, đây là các gói khác bạn cần, để gói này hoạt động."

Ngược lại, tác giả triển khai (có thể là cùng một người vào một thời điểm khác nhau) có một công việc khác, trong đó họ nói "đây là danh sách các gói chúng tôi đã tập hợp lại và thử nghiệm và bây giờ tôi cần cài đặt".

Tác giả gói viết cho nhiều tình huống khác nhau, bởi vì họ đang đưa tác phẩm của mình ra ngoài để sử dụng theo những cách họ có thể không biết và không có cách nào biết gói nào sẽ được cài đặt cùng với gói của họ. Để trở thành hàng xóm tốt và tránh xung đột phiên bản phụ thuộc với các gói khác, họ cần chỉ định phạm vi phiên bản phụ thuộc càng rộng càng tốt. Đây là những gì install_requirestrong setup.pylàm.

Tác giả triển khai viết cho một mục tiêu rất khác, rất cụ thể: một phiên bản duy nhất của một ứng dụng hoặc dịch vụ được cài đặt, được cài đặt trên một máy tính cụ thể. Để kiểm soát chính xác việc triển khai và chắc chắn rằng các gói phù hợp đã được kiểm tra và triển khai, tác giả triển khai phải chỉ định phiên bản chính xác và vị trí nguồn của mỗi gói sẽ được cài đặt, bao gồm cả phụ thuộc và phụ thuộc. Với thông số kỹ thuật này, việc triển khai có thể được áp dụng lặp lại cho một số máy hoặc được thử nghiệm trên máy thử nghiệm và tác giả triển khai có thể tự tin rằng các gói tương tự được triển khai mỗi lần. Đây là những gì a requirements.txtlàm.

Vì vậy, bạn có thể thấy rằng, trong khi cả hai đều trông giống như một danh sách lớn các gói và phiên bản, hai thứ này có công việc rất khác nhau. Và thật dễ dàng để trộn nó lên và hiểu sai! Nhưng cách đúng đắn để suy nghĩ về điều này là requirements.txt"câu trả lời" cho "câu hỏi" được đặt ra bởi các yêu cầu trong tất cả các setup.pytệp gói khác nhau . Thay vì viết nó bằng tay, nó thường được tạo bằng cách yêu cầu pip xem tất cả các setup.pytệp trong một tập các gói mong muốn, tìm một tập các gói mà nó cho là phù hợp với tất cả các yêu cầu, và sau đó, sau khi chúng được cài đặt, "đóng băng "Danh sách các gói đó thành một tệp văn bản (đây là pip freezetên của nguồn gốc).

Vì vậy, các takeaway:

  • setup.pynên khai báo các phiên bản phụ thuộc lỏng lẻo nhất có thể vẫn hoạt động được. Công việc của nó là nói những gì một gói cụ thể có thể làm việc với.
  • requirements.txtlà một bảng kê khai triển khai xác định toàn bộ công việc cài đặt và không nên được coi là gắn liền với bất kỳ một gói nào. Công việc của nó là khai báo một danh sách đầy đủ tất cả các gói cần thiết để thực hiện công việc triển khai.
  • Bởi vì hai điều này có nội dung và lý do khác nhau như vậy, nên việc sao chép cái này sang cái kia là không khả thi.

Người giới thiệu:


10
Đây là một trong những lời giải thích tốt nhất cho phép tôi đặt một số thứ tự trong mớ hỗn độn đó gọi là cài đặt gói! :)
Kounavi

6
Tôi vẫn chưa rõ lý do tại sao một nhà phát triển sẽ giữ một phiên bản được kiểm soát requirements.txtcùng với nguồn của gói chứa các yêu cầu cụ thể / đông lạnh để cài đặt hoặc thử nghiệm. Chắc chắn setup.pycó thể được sử dụng cho mục đích này trong chính dự án? Tôi chỉ có thể tưởng tượng sử dụng một tệp như vậy cho các công cụ được sử dụng để hỗ trợ quản lý dự án (ví dụ: tái cấu trúc, tạo bản phát hành, v.v.).
Sam Brightman

2
@samBrightman Tôi hoàn toàn đồng ý, tôi không nghĩ các gói thư viện hoặc gói ứng dụng nên cam kết tệp yêu cầu của họ với kho lưu trữ bằng mã. Tôi nghĩ rằng đó phải là một tạo phẩm được tạo ra trong quá trình thử nghiệm bản dựng, và sau đó được sử dụng để ghi lại một bản kê khai xây dựng và cuối cùng tạo ra một tạo phẩm triển khai.
Jonathan Hanson

6
Vì vậy, bạn đang nói requirements.txtlà nhiều tài liệu hơn cho tình trạng của thế giới đã tạo ra một bản dựng nhất định, mặc dù nó thường không được sử dụng trong chính quá trình xây dựng? Điều đó có ý nghĩa. Tuy nhiên, có vẻ như một số hệ thống dựa trên sự trùng lặp: Travis cài đặt một số gói mặc định (cũ) trong virtualenv của bạn và nói sẽ sử dụng requirements.txt. Nếu tôi hỏi làm thế nào để đảm bảo sự phụ thuộc được sử dụng muộn nhất setup.py, mọi người khăng khăng rằng tôi nên sử dụng requirements.txt.
Sam Brightman

2
Lời khuyên tốt nhất bạn có thể rút ra từ bất kỳ điều nào trong số này là tìm một mô hình phù hợp với bạn, ghi chép tốt và đảm bảo mọi người bạn làm việc đều hiểu nó. Hãy suy nghĩ về lý do tại sao bạn làm từng bit và liệu nó có thực sự có ý nghĩa đối với trường hợp sử dụng của bạn hay không. Và cố gắng đọc tốt như bạn có thể về tình trạng xây dựng, đóng gói và xuất bản hiện tại bằng Python, trong trường hợp mọi thứ trở nên tốt hơn. Nhưng đừng nín thở.
Jonathan Hanson

89

Nó không thể xử lý tập tin. Đối install_requiressố chỉ có thể là một chuỗi hoặc một danh sách các chuỗi .

Tất nhiên, bạn có thể đọc tệp của mình trong tập lệnh thiết lập và chuyển nó dưới dạng danh sách các chuỗi install_requires.

import os
from setuptools import setup

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

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

5
Mặc dù hữu ích, điều này thay đổi đặc điểm kỹ thuật của các yêu cầu từ khai báo sang mệnh lệnh. Điều này khiến một số công cụ không thể tìm ra yêu cầu của bạn là gì. Chẳng hạn, PyCharm cung cấp cài đặt tự động tất cả các yêu cầu được chỉ định trong install_requires. Tuy nhiên, nó không hoạt động nếu bạn không sử dụng cú pháp khai báo.
Piotr Dobrogost

55
@PiotrDobrogost Có lẽ nhà phát triển PyCharm nên sửa chương trình của họ sau đó. setup.pylà một chương trình nên được chạy, không phải là một tệp dữ liệu nên được phân tích cú pháp. Điều đó không làm cho câu trả lời này tồi tệ hơn.
Fredrick Brennan

5
Tôi chỉ chỉ ra những vấn đề có thể xảy ra; Câu trả lời này là hoàn toàn tốt. Không chỉ PyCharm có vấn đề với thông tin bị "ẩn" đằng sau mã. Đây là vấn đề phổ biến và do đó có động thái chung hướng tới đặc tả khai báo của siêu dữ liệu trong bao bì Python.
Piotr Dobrogost

32
Hoạt động tốt miễn là bạn đặt include requirements.txtvào MANIFEST.inhoặc bạn sẽ không thể cài đặt thư viện của mình từ bản phân phối nguồn.
Pankrat

4
Tôi biết đây là một câu hỏi cũ, nhưng bạn có thể ít nhất là hiện nay cấu hình PyCharm để phân tích một tập tin yêu cầu tại Preferences-> Tools-> Python tích hợp công cụ-> Gói yêu cầu tập tin
lekksi

64

Các tệp yêu cầu sử dụng định dạng pip mở rộng, chỉ hữu ích nếu bạn cần bổ sung cho các setup.pyràng buộc mạnh hơn, ví dụ: chỉ định các url chính xác mà một số phụ thuộc phải xuất phát hoặc đầu ra pip freezeđể đóng băng toàn bộ gói được đặt thành hoạt động đã biết phiên bản. Nếu bạn không cần các ràng buộc thêm, chỉ sử dụng a setup.py. Nếu bạn cảm thấy như bạn thực sự cần phải gửi một requirements.txtdù sao, bạn có thể làm cho nó một dòng duy nhất:

.

Nó sẽ hợp lệ và tham khảo chính xác các nội dung setup.pytrong cùng một thư mục.


9
Nhưng trong trường hợp này, nó cũng sẽ cố gắng cài đặt ứng dụng của tôi. Điều gì sẽ xảy ra nếu tôi không cần nó và chỉ muốn cài đặt cài đặt?
lễ

2
Để giải thích những gì @ffeast đang hỏi, nếu các yêu cầu chỉ tồn tại trong setup.py, có cách nào để cài đặt các yêu cầu (tương đương pip install -r requirements.txt ) mà không cần cài đặt gói không?
haridsv

1
@ffeast @haridsv -e .là đủ. Kiểm tra trang này: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter nó vẫn cố gắng tự cài đặt ứng dụng. Đây không phải là những gì chúng ta muốn
lễ

38

Mặc dù không có câu trả lời chính xác cho câu hỏi, tôi khuyên bạn nên đăng bài viết trên blog của Donald Stufft tại https://caremad.io/2013/07/setup-vs-requirement/ để giải quyết vấn đề này. Tôi đã sử dụng nó để thành công lớn.

Nói tóm lại, requirements.txtkhông phải là một setup.pysự thay thế, mà là một bổ sung triển khai. Giữ một sự trừu tượng thích hợp của các phụ thuộc gói trong setup.py. Đặt requirements.txthoặc nhiều hơn để tìm nạp các phiên bản cụ thể của các phụ thuộc gói để phát triển, thử nghiệm hoặc sản xuất.

Ví dụ: với các gói được bao gồm trong repo dưới deps/:

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

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

Pip thực thi gói setup.pyvà cài đặt các phiên bản cụ thể của các phụ thuộc được khai báo install_requires. Không có sự trùng lặp và mục đích của cả hai hiện vật được bảo tồn.


7
Điều này không hoạt động khi bạn muốn cung cấp một gói cho người khác cài đặt qua pip install my-package. Nếu các phụ thuộc cho gói của tôi không được liệt kê trong gói của tôi / setup.py, thì chúng không được cài đặt bởi pip install my-package. Tôi đã không thể xác định cách cung cấp gói cho những người khác bao gồm các phụ thuộc mà không nêu rõ chúng trong setup.py. Rất muốn biết nếu ai đó đã tìm ra cách giữ DRY trong khi cho phép người khác cài đặt gói phụ thuộc của tôi mà không cần tải xuống tệp yêu cầu và gọi thủ công pip install -r my-package/requirements.txt.
Malina

2
@Malina Gói ở đây hoàn toàn có thể cài đặt mà không cần requirements.txt. Đó là toàn bộ vấn đề. Cập nhật câu hỏi để làm cho mọi thứ rõ ràng hơn. Cũng cập nhật liên kết bài viết blog lỗi thời.
famousgarkin 6/03/2015

Vì vậy, khi chạy setup.py, nó sẽ gọi tests.txt cho các phiên bản cụ thể của các tệp được liệt kê trong stup.py?
dtracers

Đó là cách khác xung quanh @dtracers. Yêu cầu trỏ đến gói tự nó, nơi các phụ thuộc của setup.txt có thể được chọn. Vì vậy, khi cài đặt bằng cách sử dụng các yêu cầu, nó hoạt động và khi cài đặt qua pip, nó cũng hoạt động - trong cả hai trường hợp sử dụng các phụ thuộc của setup.txt, nhưng cũng cho phép cài đặt nhiều thứ hơn khi sử dụng
tests.txt

20

Việc sử dụng parse_requirementslà có vấn đề vì API pip không được ghi lại và hỗ trợ công khai. Trong pip 1.6, chức năng đó thực sự đang di chuyển, vì vậy việc sử dụng hiện tại của nó có khả năng bị phá vỡ.

Một cách đáng tin cậy hơn để loại bỏ sự trùng lặp giữa setup.pyrequirements.txtlà cụ thể các phụ thuộc của bạn setup.pyvà sau đó đưa -e .vào requirements.txttệp của bạn . Một số thông tin từ một trong những pipnhà phát triển về lý do tại sao đó là cách tốt hơn để có sẵn tại đây: https://caremad.io/blog/setup-vs-requirement/


@ Mẹ Hãy thử điều này: caremad.io/2013/07/setup-vs-requirement Đây là liên kết tương tự như được đăng trong một câu trả lời khác.
amit

18

Hầu hết các câu trả lời khác ở trên không hoạt động với phiên bản API hiện tại của pip. Đây là cách * chính xác để làm điều đó với phiên bản hiện tại của pip (6.0.8 tại thời điểm viết, cũng hoạt động trong 7.1.2. Bạn có thể kiểm tra phiên bản của mình với 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
    ....
)

* Chính xác, trong đó là cách sử dụng parse numquirements với pip hiện tại. Đây có lẽ vẫn chưa phải là cách tốt nhất để làm điều đó, vì, như những người đăng ở trên đã nói, pip không thực sự duy trì API.


14

Cài đặt gói hiện tại trong Travis. Điều này tránh việc sử dụng một requirements.txttập tin. Ví dụ:

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

2
Đây là sự kết hợp tốt nhất giữa "chính xác" và "thực tế". Tôi nói thêm rằng nếu sau khi các bài kiểm tra vượt qua, bạn có thể yêu cầu Travis tạo một tệp.txt.txt pip freezevà xuất tệp đó ở đâu đó dưới dạng một tạo phẩm (như S3 hoặc một cái gì đó), thì bạn sẽ có một cách tuyệt vời để cài đặt lại chính xác những gì bạn thử nghiệm.
Jonathan Hanson

4

from pip.req import parse_requirements không hoạt động với tôi và tôi nghĩ rằng nó dành cho các dòng trống trong tệp.txt của tôi, nhưng chức năng này không hoạt động

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

Nếu bạn không muốn buộc người dùng của mình cài đặt pip, bạn có thể mô phỏng hành vi của nó bằng cách này:

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

Giao diện sau đây không được dùng trong pip 10:

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

Vì vậy, tôi chuyển nó chỉ để phân tích cú pháp văn bản đơn giản:

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

Cách tiếp cận đơn giản này hoạt động hơn 90% thời gian. Đối với những người sử dụng Python 3.6+, tôi đã viết một câu trả lời là một pathlibbiến thể của nó.
Acumenus

3

Cách tiếp cận đơn giản này đọc các tập tin yêu cầu từ setup.py. Đó là một biến thể của câu trả lời của Dmitiry S .. Câu trả lời này chỉ tương thích với Python 3.6+.

Trên mỗi DS , requirements.txtcó thể ghi lại các yêu cầu cụ thể với số phiên bản cụ thể, trong khi setup.pycó thể ghi lại các yêu cầu trừu tượng với phạm vi phiên bản lỏng lẻo.

Dưới đây là một đoạn trích của tôi 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'),
   ...)

Lưu ý rằng distutils.text_file.TextFilesẽ tước bình luận. Ngoài ra, theo kinh nghiệm của tôi, rõ ràng bạn không cần thực hiện bất kỳ bước đặc biệt nào để gói trong tệp yêu cầu.


2

HÃY THƯỞNG THỨC parse_requirements!

Xin lưu ý rằng pip.req.parse_requirementssẽ thay đổi dấu gạch dưới thành dấu gạch ngang. Điều này đã làm tôi tức giận trong vài ngày trước khi tôi phát hiện ra nó. Ví dụ minh họa:

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

sản xuất

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

1
Sử dụng unsafe_name để có phiên bản gạch dưới:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds

5
Như đã chỉ ra ở nơi khác, PIP là một ứng dụng, không phải là một thư viện. Nó không có API theo thỏa thuận công khai và việc nhập nó vào mã của bạn không phải là trường hợp sử dụng được hỗ trợ. Không có gì đáng ngạc nhiên khi nó có hành vi bất ngờ; chức năng nội bộ của nó không bao giờ có ý định được sử dụng theo cách này.
Jonathan Hanson

1

Tôi đã tạo ra một chức năng tái sử dụng cho việc này. Nó thực sự phân tích toàn bộ thư mục của các tệp yêu cầu và đặt chúng thành ngoại lệ.

Mới nhất luôn có sẵn ở đây: 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)

rất đẹp! thậm chí xử lý các yêu cầu đệ quy với pip mới nhất :)
amohr

@amohr Cảm ơn! Gần đây tôi đã cập nhật nó cho một pip thậm chí muộn hơn, tôi không chắc tại sao họ lại hành động như vậy, bằng cách chuyển mọi thứ sang pip._internal.. Nếu bạn không cung cấp API bên ngoài có thể sử dụng được, thì bạn không nên phá vỡ tất cả những thứ đó đang sử dụng tất cả những gì bạn cung cấp.
trevorj

0

Một giải pháp khả thi khác ...

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

và sau đó sử dụng ...

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

không treeđến từ đâu
Francesco Boi

@FrancescoBoi nếu bạn tha thứ cho tôi một chút vì đã không trình bày một giải pháp hoạt động hoàn toàn ... cây thực sự chỉ là một bản quét của hệ thống tệp cục bộ (rất giống với lệnh "cây" trong linux). Ngoài ra, giải pháp của tôi ở trên có thể không hoạt động hoàn toàn vào thời điểm này vì pip liên tục được cập nhật và tôi đã sử dụng nội bộ pip.
Brian Bruggeman

0

Tôi không khuyên bạn nên làm một điều như vậy. Như đã đề cập nhiều lần install_requiresrequirements.txtchắc chắn không phải là cùng một danh sách. Nhưng vì có rất nhiều câu trả lời sai lệch xung quanh liên quan đến các API nội bộ riêng tư của pip , nên có thể đáng để xem xét các lựa chọn thay thế ...

Không cần pip để phân tích requirements.txttệp từ tập lệnh setuptools setup.py . Các setuptools dự án đã có chứa tất cả các công cụ cần thiết trong nó cấp cao nhất gói pkg_resources.

Nó có thể ít nhiều trông như thế này:

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

Trong trường hợp bạn không biết, lý do tại sao nhiều người (bao gồm cả tôi) đã sử dụng pipphân tích cú pháp và không phải pkg_resourcestừ trước năm 2015 là các lỗi như github.com/pypa/setuptools/issues/470 . Điều này chính xác đã được sửa chữa hiện nay, nhưng tôi vẫn hơi sợ sử dụng nó, vì cả hai triển khai dường như được phát triển riêng.
trevorj

@trevorj Cảm ơn bạn đã chỉ ra điều này, tôi không biết. Thực tế là ngày nay nó hoạt động và việc tham gia vào pip có vẻ như là một ý tưởng vô lý với tôi (đặc biệt là trong thời trang này). Hãy nhìn vào các câu trả lời khác, hầu hết có vẻ giống như các biến thể nhỏ của cùng một ý tưởng không đúng đắn, mà hầu như không có bất kỳ thông báo cảnh báo nào. Và những người mới đến có thể chỉ theo xu hướng này. Hy vọng các sáng kiến ​​như PEP517 và PEP518 sẽ giúp cộng đồng tránh xa sự điên rồ này.
sinoroc

-1

Đăng chéo câu trả lời của tôi từ câu hỏi SO này cho một giải pháp chứng minh phiên bản pip đơn giản khác.

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

Sau đó, chỉ cần ném vào tất cả các yêu cầu của bạn dưới requirements.txtthư mục gốc của dự án.


-1

Tôi đã làm điều này:

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

Một parse_requirementshack khác cũng phân tích các dấu môi trường thành 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
)

Nó sẽ hỗ trợ cả sdist và dists nhị phân.

Như đã nêu của những người khác, parse_requirementscó một số thiếu sót, vì vậy đây không phải là điều bạn nên làm trong các dự án công cộng, nhưng nó có thể đủ cho các dự án nội bộ / cá nhân.


pip 20.1 đã thay đổi API và các điểm đánh dấu của họ không còn khả dụng parse_requirements()nữa, vì vậy điều này hiện không thành công.
Tuukka Mustonen

-3

Đây là một bản hack hoàn chỉnh (đã được thử nghiệm pip 9.0.1) dựa trên câu trả lời của Romain để phân tích requirements.txtvà lọc nó theo các dấu môi trường hiện tại :

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
Điều này chỉ đúng một phần. Nếu bạn gọi r.match_markers()bạn thực sự đang đánh giá các điểm đánh dấu, đó là điều chính xác cần làm cho một người theo dõi. Tuy nhiên, nếu bạn đang xây dựng một dist nhị phân (ví dụ: bánh xe), gói sẽ chỉ liệt kê các thư viện phù hợp với môi trường thời gian xây dựng của bạn .
Tuukka Mustonen

@TuukkaMustonen, vậy thì tìm cái này ở đâu wheel environment(nếu đó là điều người cố gắng làm) để đánh giá các dấu hiệu chống lại nó?
anatoly techtonik

Xem stackoverflow.com/a/41172125/165629 cũng nên hỗ trợ bdist_wheel. Nó không đánh giá các điểm đánh dấu, nó chỉ thêm chúng vào extras_require.
Tuukka Mustonen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.