Referensi persyaratan.txt untuk install_requires kwarg di file setup.tools setup.py

279

Saya memiliki requirements.txtfile yang saya gunakan dengan Travis-CI. Tampaknya konyol untuk menduplikasi persyaratan di keduanya requirements.txtdan setup.py, jadi saya berharap untuk menyerahkan file handle ke install_requireskwarg di setuptools.setup.

Apakah ini mungkin? Jika demikian, bagaimana cara saya melakukannya?

Ini requirements.txtfile saya :

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
sumber
4
install_requiresdigunakan untuk mendeklarasikan dependensi pada paket yang diperlukan agar paket bekerja dan digunakan oleh pengembang paket, sementara requirements.txtdigunakan untuk mengotomatiskan penginstalan lingkungan, yang memungkinkan menginstal perangkat lunak tambahan dan melakukan penginstalan versi dan digunakan oleh sysadmin yang menyebarkan paket. Peran dan target audiens mereka berbeda secara signifikan, jadi mencoba menggabungkannya seperti keinginan OP adalah kesalahan desain yang sebenarnya.
Zart
7
2 sen saya. Jangan gunakan persyaratan.txt di setup.py Anda. Tujuannya
Philippe Ombredanne
3
Saya melihat banyak jawaban yang rumit. Apa yang salah dengan orang tua biasa [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider
Tidak disarankan untuk melakukan ini. Tetapi jika benar-benar diperlukan maka itu mudah: setuptools sendiri sudah memiliki semua yang diperlukanpkg_resources.parse_requirements()
sinoroc

Jawaban:

246

Anda dapat membalikkannya dan mendaftar dependensi setup.pydan memiliki karakter tunggal - titik .- requirements.txtsebagai gantinya.


Atau, bahkan jika tidak disarankan, masih mungkin untuk mengurai requirements.txtfile (jika tidak merujuk persyaratan eksternal dengan URL) dengan hack berikut (diuji dengan pip 9.0.1):

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

Ini tidak memfilter penanda lingkungan .


Di versi lama pip, lebih khusus lebih tua dari 6.0 , ada API publik yang dapat digunakan untuk mencapai hal ini. File persyaratan dapat berisi komentar ( #) dan dapat menyertakan beberapa file lainnya ( --requirementatau -r). Jadi, jika Anda benar-benar ingin mem-parsing, requirements.txtAnda dapat menggunakan pip parser:

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
sumber
26
Bagaimana jika pengguna tidak menginstal pip? Ka-booming?
Gringo Suave
83
@GringoSuave Jika pengguna tidak menginstal pip, ia harus menginstalnya terlebih dahulu.
guettli
7
Anda juga perlu menyediakan url dalam file persyaratan Anda, jika ada baris -e atau -f ("edit" git repo) yang menunjuk ke paket non-pypi. Gunakan ini:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
hobs
91
Anda benar-benar tidak ingin melakukan ini. Berbicara sebagai pemelihara pip, pip tidak mendukung dipanggil sebagai API seperti ini sama sekali. Bahkan pip 1.6 (versi berikutnya saat ini) memindahkan fungsi ini.
Donald Stufft
26
Ini seharusnya tidak lagi menjadi jawaban yang diterima, jika memang seharusnya ada. Ini benar-benar rusak. Bahkan ketika itu berhasil, itu jelas tidak perlu. Karena pipdefault untuk mem-parsing dependensi dari setup.pyketiadaan requirements.txt, jawaban sederhana yang dicatat oleh Tobu di bawah ini adalah daftar semua dependensi di dalam setup.pydan hapus requirements.txt. Untuk aplikasi yang membutuhkan keduanya, cukup kurangi daftar dependensi requirements.txtmenjadi hanya .karakter. Selesai
Cecil Curry
195

Di muka itu, tampaknya itu requirements.txtdansetup.py merupakan duplikat yang konyol, tetapi penting untuk dipahami bahwa walaupun bentuknya serupa, fungsi yang dimaksud sangat berbeda.

Tujuan dari pembuat paket, ketika menentukan dependensi, adalah untuk mengatakan "di mana pun Anda menginstal paket ini, ini adalah paket lain yang Anda butuhkan, agar paket ini berfungsi."

Sebaliknya, penulis penyebaran (yang mungkin orang yang sama pada waktu yang berbeda) memiliki pekerjaan yang berbeda, di mana mereka mengatakan "inilah daftar paket yang telah kami kumpulkan dan uji coba dan sekarang saya perlu menginstal".

Penulis paket menulis untuk berbagai skenario, karena mereka meletakkan pekerjaan mereka di luar sana untuk digunakan dengan cara yang mungkin tidak mereka ketahui, dan tidak memiliki cara untuk mengetahui paket apa yang akan diinstal bersama paket mereka. Untuk menjadi tetangga yang baik dan menghindari konflik versi dependensi dengan paket lain, mereka perlu menentukan berbagai versi dependensi yang mungkin dapat berfungsi. Inilah yang install_requiresdi setup.pylakukan.

Penulis penyebaran menulis untuk tujuan yang sangat berbeda, sangat spesifik: satu contoh aplikasi atau layanan yang diinstal, diinstal pada komputer tertentu. Untuk mengontrol penyebaran secara tepat, dan memastikan bahwa paket yang tepat diuji dan digunakan, penulis penyebaran harus menentukan versi yang tepat dan lokasi-sumber dari setiap paket yang akan diinstal, termasuk dependensi dan dependensi dependensi. Dengan spek ini, penyebaran dapat diulangi pada beberapa mesin, atau diuji pada mesin uji, dan penulis pemasangan dapat yakin bahwa paket yang sama digunakan setiap waktu. Inilah yang dilakukan seorang requirements.txt.

Jadi Anda dapat melihat bahwa, walaupun keduanya terlihat seperti daftar besar paket dan versi, kedua hal ini memiliki pekerjaan yang sangat berbeda. Dan tentu saja mudah untuk mencampur ini dan membuatnya salah! Tetapi cara yang tepat untuk memikirkan hal ini adalah itu requirements.txtadalah "jawaban" untuk "pertanyaan" yang diajukan oleh persyaratan di semua setup.pyfile paket yang beragam . Alih-alih menulisnya dengan tangan, sering dihasilkan dengan memberi tahu pip untuk melihat semua setup.pyfile dalam satu set paket yang diinginkan, menemukan satu set paket yang dianggapnya sesuai dengan semua persyaratan, dan kemudian, setelah diinstal, "membekukan "daftar paket itu menjadi file teks (ini adalah asal pip freezenama itu).

Jadi takeaway:

  • setup.pyharus mendeklarasikan versi dependensi yang paling longgar yang masih bisa diterapkan. Tugasnya adalah untuk mengatakan apa yang bisa digunakan paket tertentu.
  • requirements.txtadalah manifes penyebaran yang mendefinisikan seluruh pekerjaan instalasi, dan tidak boleh dianggap terikat pada satu paket. Tugasnya adalah untuk mendeklarasikan daftar lengkap dari semua paket yang diperlukan untuk melakukan penyebaran.
  • Karena kedua hal ini memiliki konten dan alasan yang berbeda untuk hal tersebut, tidak layak hanya menyalin satu ke yang lain.

Referensi:

Jonathan Hanson
sumber
10
Ini adalah salah satu penjelasan terbaik yang membuat saya memesan kekacauan yang disebut instalasi paket! :)
Kounavi
6
Masih belum jelas bagi saya mengapa pengembang akan menjaga versi terkontrol requirements.txtbersama dengan sumber paket yang berisi persyaratan beton / beku untuk instalasi atau pengujian. Tentunya setup.pydapat digunakan untuk tujuan ini dalam proyek itu sendiri? Saya hanya bisa membayangkan menggunakan file seperti itu untuk alat-alat yang digunakan untuk mendukung mengelola proyek (misalnya refactoring, membuat rilis, dll.).
Sam Brightman
2
@ samBrightman Saya setuju sepenuhnya, saya tidak berpikir paket pustaka atau paket aplikasi harus melakukan file requirement.txt mereka ke repositori dengan kode. Saya pikir itu harus menjadi artefak yang dihasilkan selama pengujian build, dan kemudian digunakan untuk mendokumentasikan manifes build dan akhirnya menghasilkan artefak penyebaran.
Jonathan Hanson
6
Jadi maksud Anda requirements.txtadalah lebih banyak dokumentasi untuk keadaan dunia yang menghasilkan bangunan tertentu, meskipun biasanya tidak digunakan dalam proses pembangunan itu sendiri? Itu masuk akal. Namun, sepertinya beberapa sistem bergantung pada duplikasi: Travis menginstal beberapa paket default (lama) di virtualenv Anda dan mengatakan untuk menggunakannya requirements.txt. Jika saya bertanya bagaimana memastikan dependensi menggunakan terbaru setup.py, orang bersikeras bahwa saya harus menggunakan requirements.txt.
Sam Brightman
2
Saran terbaik yang dapat Anda peroleh dari semua ini adalah menemukan model yang sesuai untuk Anda, mendokumentasikannya dengan baik, dan memastikan semua orang yang bekerja dengan Anda memahaminya. Pikirkan mengapa Anda melakukan setiap bit dan apakah itu benar-benar masuk akal untuk use case Anda. Dan cobalah untuk tetap membaca sebanyak yang Anda bisa tentang keadaan saat ini bangunan, pengemasan, dan penerbitan dengan Python, kalau-kalau hal menjadi lebih baik. Tapi jangan menahan nafas.
Jonathan Hanson
90

Tidak dapat menangani file. The install_requiresArgumen dapat hanya berupa string atau daftar string .

Anda dapat, tentu saja, membaca file Anda di skrip setup dan meneruskannya sebagai daftar string install_requires.

import os
from setuptools import setup

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

setup(...
install_requires=required,
...)
Fredrick Brennan
sumber
5
Meskipun bermanfaat, ini mengubah spesifikasi persyaratan dari yang bersifat deklaratif ke imperatif. Ini membuat beberapa alat tidak mungkin mengetahui apa kebutuhan Anda. Sebagai contoh, PyCharm menawarkan instalasi otomatis semua persyaratan yang ditentukan dalam install_requires. Namun, itu tidak berfungsi jika Anda tidak menggunakan sintaks deklaratif.
Piotr Dobrogost
55
@PiotrDobrogost Mungkin pengembang PyCharm harus memperbaiki program mereka kemudian. setup.pyadalah program yang harus dijalankan, bukan file data yang harus diuraikan. Itu tidak membuat jawaban ini lebih buruk.
Fredrick Brennan
5
Saya hanya menunjukkan kemungkinan masalah; jawaban ini baik-baik saja. Bukan hanya PyCharm yang bermasalah dengan informasi yang "tersembunyi" di balik kode. Ini adalah masalah universal dan karenanya ada langkah umum menuju spesifikasi dekadatif metadata dalam kemasan Python.
Piotr Dobrogost
33
Berfungsi dengan baik selama Anda memasukkannya include requirements.txtke dalam MANIFEST.inatau Anda tidak akan dapat menginstal perpustakaan Anda dari distribusi sumber.
Pankrat
4
Saya tahu ini adalah pertanyaan lama, tetapi Anda setidaknya dapat mengkonfigurasi PyCharm untuk mengurai file persyaratan di Preferences-> Tools-> Python integrated tools-> File persyaratan paket
lekksi
64

File persyaratan menggunakan format pip yang diperluas, yang hanya berguna jika Anda perlu melengkapi Anda setup.pydengan kendala yang lebih kuat, misalnya menentukan url yang tepat dari mana beberapa dependensi harus berasal, atau output pip freezeuntuk membekukan seluruh paket yang disetel untuk diketahui bekerja versi. Jika Anda tidak membutuhkan kendala tambahan, gunakan hanya a setup.py. Jika Anda merasa perlu mengirim barang requirements.txt, Anda bisa membuatnya menjadi satu baris:

.

Itu akan valid dan merujuk tepat ke konten setup.pyyang ada di direktori yang sama.

Tobu
sumber
9
Tetapi dalam hal ini juga akan mencoba untuk menginstal aplikasi saya juga. Bagaimana jika saya tidak membutuhkannya dan hanya ingin install_requires diinstal?
ffeast
2
Untuk menguraikan tentang apa yang diminta @feast, jika persyaratan hanya ada di setup.py, apakah ada cara untuk menginstal persyaratan (setara dengan pip install -r requirements.txt ) tanpa menginstal paket itu sendiri?
haridsv
1
@feast @haridsv -e .sudah cukup. Periksa halaman ini: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter
4
@ DexD.Hunter masih mencoba menginstal aplikasi itu sendiri. Ini bukan yang kita inginkan
ffeast
38

Meskipun bukan jawaban yang tepat untuk pertanyaan ini, saya merekomendasikan posting blog Donald Stufft di https://caremad.io/2013/07/setup-vs-requirement/ untuk mendapatkan jawaban yang bagus tentang masalah ini. Saya telah menggunakannya untuk kesuksesan besar.

Singkatnya, requirements.txtbukanlah setup.pyalternatif, tetapi pelengkap penempatan. Simpan abstraksi yang sesuai dari dependensi paket setup.py. Tetapkan requirements.txtatau lebih dari mereka untuk mengambil versi tertentu dari dependensi paket untuk pengembangan, pengujian, atau produksi.

Misalnya dengan paket yang termasuk dalam repo di bawah deps/:

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

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

pip mengeksekusi paket setup.pydan menginstal versi dependensi spesifik yang dideklarasikan pada install_requires. Tidak ada duplikasi dan tujuan kedua artefak dipertahankan.

gin terkenal
sumber
7
Ini tidak berfungsi ketika Anda ingin menyediakan paket untuk diinstal oleh orang lain pip install my-package. Jika dependensi untuk paket saya tidak terdaftar di paket saya / setup.py, mereka tidak diinstal oleh pip install my-package. Saya tidak dapat menentukan cara menyediakan paket untuk orang lain yang menyertakan dependensi tanpa secara eksplisit menyatakannya di setup.py. Ingin tahu apakah seseorang telah menemukan cara untuk tetap KERING sambil memungkinkan orang lain untuk menginstal dependensi paket-saya + tanpa mengunduh file persyaratan dan menelepon secara manual pip install -r my-package/requirements.txt.
Malina
2
@Malina Paket di sini benar-benar dapat diinstal tanpa requirements.txt. Itulah intinya. Memperbarui pertanyaan untuk memperjelas segalanya. Juga memperbarui tautan posting blog yang sudah usang.
famousgarkin
jadi ketika menjalankan setup.py ia akan memanggil requirement.txt untuk versi spesifik dari file yang terdaftar di stup.py?
dtracers
Ini sebaliknya @dtracers. requirement.txt menunjuk ke paket itu sendiri, di mana dependecies setup.py dapat diambil. Jadi ketika menginstal menggunakan persyaratan, ia bekerja dan ketika menginstal melalui pip, ia bekerja juga - dalam kedua kasus menggunakan dependecies setup.py, tetapi juga memungkinkan untuk menginstal lebih banyak hal ketika menggunakan persyaratan.txt
smido
20

Penggunaan parse_requirementsbermasalah karena API pipa tidak didokumentasikan dan didukung secara publik. Di pip 1.6, fungsi itu sebenarnya bergerak, jadi penggunaan yang ada kemungkinan akan rusak.

Cara yang lebih dapat diandalkan untuk menghilangkan duplikasi antara setup.pydan requirements.txtuntuk menentukan dependensi Anda di setup.pydan kemudian dimasukkan -e .ke dalam requirements.txtfile Anda . Beberapa informasi dari salah satu pippengembang tentang mengapa itu cara yang lebih baik tersedia di sini: https://caremad.io/blog/setup-vs-requirement/

Wilfredo Sánchez Vega
sumber
@Tommy Coba ini: caremad.io/2013/07/setup-vs-requirement Ini adalah tautan yang sama dengan yang dikirim dalam jawaban lain.
amit
18

Sebagian besar jawaban lain di atas tidak berfungsi dengan versi API pip saat ini. Berikut adalah cara yang benar * untuk melakukannya dengan versi pip saat ini (6.0.8 pada saat penulisan, juga bekerja di 7.1.2. Anda dapat memeriksa versi Anda dengan 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
    ....
)

* Benar, karena ini adalah cara menggunakan parse_requirements dengan pip saat ini. Mungkin masih bukan cara terbaik untuk melakukannya, karena, seperti yang dikatakan poster di atas, pip tidak benar-benar memelihara API.

fabianvf
sumber
14

Instal paket saat ini di Travis. Ini menghindari penggunaan requirements.txtfile. Sebagai contoh:

language: python
python:
  - "2.7"
  - "2.6"
install:
  - pip install -q -e .
script:
  - python runtests.py
vdboor
sumber
2
Sejauh ini ini merupakan kombinasi terbaik dari "benar" dan "praktis". Saya akan menambahkan bahwa jika setelah tes lulus, Anda dapat membuat Travis menghasilkan persyaratan.txt dengan pip freezedan mengekspor file tersebut di suatu tempat sebagai artefak (seperti S3 atau sesuatu), maka Anda akan memiliki cara yang bagus untuk menginstal ulang secara tepat apa yang Anda inginkan. diuji.
Jonathan Hanson
4

from pip.req import parse_requirements tidak bekerja untuk saya dan saya pikir itu untuk baris kosong di requirement.txt saya, tetapi fungsi ini berfungsi

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

Jika Anda tidak ingin memaksa pengguna Anda untuk menginstal pip, Anda dapat meniru perilakunya dengan ini:

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'))
)
Ruben
sumber
4

Antarmuka berikut menjadi usang di pip 10:

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

Jadi saya beralih hanya untuk parsing teks sederhana:

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
sumber
Pendekatan sederhana ini bekerja lebih dari 90% dari waktu. Bagi mereka yang menggunakan Python 3.6+, saya telah menulis jawaban yang merupakan pathlibvariasi dari itu.
Acumenus
3

Pendekatan sederhana ini membaca file persyaratan dari setup.py. Ini adalah variasi dari jawaban oleh Dmitiry S .. Jawaban ini hanya kompatibel dengan Python 3.6+.

Per DS , requirements.txtdapat mendokumentasikan persyaratan konkret dengan nomor versi tertentu, sedangkan setup.pydapat mendokumentasikan persyaratan abstrak dengan rentang versi longgar.

Di bawah ini adalah kutipan dari blog saya 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'),
   ...)

Perhatikan bahwa distutils.text_file.TextFileakan menghapus komentar. Selain itu, menurut pengalaman saya, Anda tampaknya tidak perlu mengambil langkah khusus untuk membundel dalam file persyaratan.

Acumenus
sumber
2

WASPADALAH TERHADAP parse_requirementsPERILAKU!

Harap dicatat bahwa pip.req.parse_requirementsakan mengubah garis bawah menjadi garis putus-putus. Ini membuat saya marah selama beberapa hari sebelum saya menemukannya. Contoh menunjukkan:

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

menghasilkan

['example-with-underscores', 'example-with-dashes']
MikeTwo
sumber
1
Gunakan unsafe_name untuk mendapatkan versi garis bawah:[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds
5
Seperti yang ditunjukkan di tempat lain, PIP adalah aplikasi, bukan perpustakaan. Itu tidak memiliki API yang disepakati secara publik, dan mengimpornya ke kode Anda bukan kasus penggunaan yang didukung. Tidak mengherankan bahwa ia memiliki perilaku yang tidak terduga; fungsi internalnya tidak pernah dimaksudkan untuk digunakan dengan cara ini.
Jonathan Hanson
1

Saya membuat fungsi yang dapat digunakan kembali untuk ini. Ini sebenarnya mem-parsing seluruh direktori file persyaratan dan menetapkannya ke extras_require.

Terbaru selalu tersedia di sini: 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
sumber
sangat bagus! bahkan menangani persyaratan rekursif dengan pip terbaru :)
amohr
@ amohr, terima kasih! Saya baru-baru ini memperbaruinya untuk pip yang lebih baru, saya tidak yakin mengapa mereka bertindak seperti itu, dengan memindahkan sesuatu ke pip._internal.. Jika Anda tidak menyediakan API eksternal yang dapat digunakan, maka Anda tidak boleh merusak semua itu yang menggunakan semua yang Anda berikan.
trevorj
0

Solusi lain yang mungkin ...

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

dan kemudian menggunakan ...

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
sumber
mana treeberasal?
Francesco Boi
@FrancescoBoi jika Anda memaafkan saya sedikit karena tidak menghadirkan solusi yang berfungsi penuh ... tree benar-benar hanya pemindaian sistem file lokal (sangat mirip dengan perintah "tree" di linux). Juga, solusi saya di atas mungkin tidak berfungsi sepenuhnya pada saat ini karena pip terus diperbarui dan saya menggunakan pip internal.
Brian Bruggeman
0

Saya tidak akan merekomendasikan melakukan hal seperti itu. Seperti yang disebutkan beberapa kali install_requiresdan requirements.txtjelas tidak seharusnya menjadi daftar yang sama. Tetapi karena ada banyak jawaban menyesatkan yang melibatkan API internal swasta dari pip , mungkin ada baiknya mencari alternatif yang lebih waras ...

Tidak perlu pip untuk mem-parsing requirements.txtfile dari skrip setuptools setup.py . Proyek setuptools sudah berisi semua alat yang diperlukan dalam paket tingkat ataspkg_resources .

Ini bisa kurang lebih terlihat seperti ini:

#!/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
sumber
Jika Anda tidak sadar, alasan mengapa banyak (termasuk saya) telah menggunakan pipparsing dan bukan pkg_resourcessejak sebelum 2015 adalah bug seperti github.com/pypa/setuptools/issues/470 . Yang tepat ini sudah diperbaiki saat ini, tapi saya masih agak takut untuk menggunakannya, karena kedua implementasi tampaknya dikembangkan secara terpisah.
trevorj
@ trvorj Terima kasih telah menunjukkan ini, saya tidak tahu. Faktanya saat ini berfungsi dan melibatkan pip sepertinya merupakan ide yang konyol bagi saya (khususnya dalam mode ini). Lihatlah jawaban-jawaban lain, sebagian besar tampak seperti sedikit variasi dari gagasan keliru yang sama, tanpa pemberitahuan peringatan. Dan pendatang baru mungkin hanya mengikuti tren ini. Semoga inisiatif seperti PEP517 dan PEP518 akan menjauhkan masyarakat dari kegilaan ini.
sinoroc
-1

Cross memposting jawaban saya dari pertanyaan SO ini untuk solusi bukti versi pip sederhana lainnya.

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

Kemudian cukup masukkan semua persyaratan Anda di requirements.txtbawah direktori root proyek.

Scrotch
sumber
-1

Saya melakukan ini:

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

Namun parse_requirementsperetasan lain yang juga mem-parsing penanda lingkungan ke 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
)

Seharusnya mendukung distist sdist dan binary.

Seperti yang dinyatakan oleh orang lain, parse_requirementsmemiliki beberapa kekurangan, jadi ini bukan apa yang harus Anda lakukan pada proyek publik, tetapi mungkin cukup untuk proyek internal / pribadi.

Tuukka Mustonen
sumber
pip 20.1 mengubah API mereka dan marker tidak tersedia parse_requirements()lagi, jadi ini sekarang gagal.
Tuukka Mustonen
-3

Berikut ini adalah retasan lengkap (diuji dengan pip 9.0.1) berdasarkan jawaban Romain yang mem-parsing requirements.txtdan memfilternya sesuai dengan penanda lingkungan saat ini :

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)
techtonik anatoly
sumber
1
Ini hanya sebagian benar. Jika Anda menelepon, r.match_markers()Anda sebenarnya mengevaluasi marker, yang benar untuk dilakukan oleh seorang sdist. Namun, jika Anda sedang membangun dist biner (mis. Roda), paket hanya akan mencantumkan pustaka yang cocok dengan lingkungan build-time Anda.
Tuukka Mustonen
@ TuukkaMustonen, jadi di mana menemukan ini wheel environment (jika itu yang orang coba lakukan) untuk mengevaluasi spidol yang menentangnya?
anatoly techtonik
Lihat stackoverflow.com/a/41172125/165629 yang juga harus mendukung bdist_wheel. Itu tidak mengevaluasi spidol, itu hanya menambahkan mereka extras_require.
Tuukka Mustonen