Setuptools setup.py dosyasındaki install_requires kwarg için başvuru gereksinimleri.txt


279

Bir var requirements.txtTravis-CI ile kullanıyorum o dosyayı. Her ikisinde de gereksinimleri çoğaltmak aptalca görünüyor requirements.txtve setup.pybu yüzden install_requireskwarg'a bir dosya tanıtıcısı geçirmeyi umuyordum setuptools.setup.

Mümkün mü? Öyleyse, bunu nasıl yapmalıyım?

İşte benim requirements.txtdosya:

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_requirespaketin çalışması için gerekli olan ve paketin geliştiricisi tarafından kullanılan paketlere bağımlılıkları bildirmek için kullanılırken requirements.txt, ekstra yazılım yüklemeye izin veren ve sürüm sabitlemeyi sağlayan ve sürümü dağıtan sistem yöneticileri tarafından kullanılan ortamların yüklenmesini otomatikleştirmek için kullanılır. paketlemek. Rolleri ve hedef kitleleri önemli ölçüde farklılık gösterir, bu yüzden onları OP'nin istediği gibi birleştirmeye çalışmak gerçek bir tasarım hatası imho'dur.
Zart

7
Benim 2 sentim. Setup.py dosyasında terms.txt dosyasını kullanmayın. Amaçlar
Philippe

3
Çok karmaşık cevaplar görüyorum. Sade eski ile ilgili sorun nedir [line.strip() for line in open("requirements.txt").readlines()]?
Felipe SS Schneider

Bunu yapmanız önerilmez. Ama gerçekten gerekirse, o zaman anlaşılır: setuptools'un kendisi zaten gerekli her şeye sahippkg_resources.parse_requirements()
sinoroc

Yanıtlar:


246

Bunu etrafında çevirmek ve liste içinde bağımlılıkları olabilir setup.pyve tek bir karaktere sahiptir - bir nokta .içinde - requirements.txtyerine.


Alternatif olarak, tavsiye edilmese bile, requirements.txtdosyayı (URL ile herhangi bir harici gereksinimi belirtmiyorsa) aşağıdaki saldırı ile (test edilmiş pip 9.0.1) ayrıştırmak hala mümkündür :

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

Bu, ortam belirteçlerini filtrelemez .


Pip'in eski sürümlerinde, özellikle 6.0'dan daha eski olanlarda , bunu başarmak için kullanılabilecek bir genel API vardır. Bir gereksinim dosyası yorum ( #) içerebilir ve diğer bazı dosyaları ( --requirementveya -r) içerebilir . Böylece, gerçekten ayrıştırmak requirements.txtistiyorsanız pip ayrıştırıcıyı kullanabilirsiniz:

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
Kullanıcıda pip yüklü değilse ne olur? Ka-boom?
Gringo Suave

83
@GringoSuave Kullanıcıda pip yüklü değilse, önce yüklemesi gerekir.
guettli

7
Ayrıca, pypi olmayan paketlere işaret eden herhangi bir -e veya -f ("düzenlenebilir" git repo) satırı olması durumunda, gereksinimler dosyasında URL'leri sağlamanız gerekir. Bunu kullanın:setup(..., dependency_links=[str(req_line.url) for req_line in parse_requirements(<requirements_path>)], ...)
ocaklar

91
Bunu gerçekten yapmak istemiyorsun. Pip sürdürücü pip olarak konuşmak, bunun gibi bir API olarak adlandırılmayı desteklemez. Aslında pip 1.6 (şu anda bir sonraki sürüm) bu işlevi taşır.
Donald Stufft

26
Eğer varsa, bu artık kabul edilen cevap olmamalıdır. Açıkça kırılmış. Çalıştığında bile, gereksiz yere gereksizdir. Yana pipgelen bağımlılıkları ayrıştırma için varsayılan setup.pyyokluğunda requirements.txt, basit bir cevabı zekice tarafından da belirtildiği Tobu aşağıda etmektir tüm bağımlılıkları listelemek setup.pyve kaldır requirements.txt. Her ikisini de gerektiren uygulamalar için, bağımlılık listesini requirements.txtyalnızca .karaktere indirgeyin . Bitti.
Cecil Curry

195

Yüzünde, öyle görünüyor requirements.txtve setup.pyaptalca kopyalar, ancak form benzer olsa da, amaçlanan fonksiyonun çok farklı olduğunu anlamak önemlidir.

Bir paket yazarının amacı, bağımlılıkları belirtirken, "bu paketi her nereye yüklerseniz yükleyin, bu paketin çalışması için ihtiyacınız olan diğer paketlerdir" demektir.

Buna karşılık, dağıtım yazarı (farklı bir zamanda aynı kişi olabilir) farklı bir işe sahiptir, çünkü "burada topladığımız ve test ettiğimiz ve şimdi yüklemem gereken paketler listesi".

Paket yazarı çok çeşitli senaryolar için yazıyor, çünkü çalışmalarını bilmedikleri yollarla kullanılmak üzere ortaya koyuyorlar ve paketlerinin yanında hangi paketlerin kurulacağını bilmenin bir yolu yok. İyi bir komşu olmak ve diğer paketlerle bağımlılık sürümü çakışmalarını önlemek için, muhtemelen çalışabilecek kadar geniş bir bağımlılık sürümleri yelpazesi belirtmeleri gerekir. Bu ne install_requiresde setup.pyyapar.

Dağıtım yazarı çok farklı ve çok özel bir amaç için yazar: yüklü bir uygulamanın veya hizmetin belirli bir bilgisayara yüklenen tek bir örneği. Bir dağıtımı tam olarak denetlemek ve doğru paketlerin sınandığından ve dağıtıldığından emin olmak için, dağıtım yazarının, bağımlılıklar ve bağımlılık bağımlılıkları da dahil olmak üzere, yüklenecek her paketin tam sürümünü ve kaynak konumunu belirtmesi gerekir. Bu spesifikasyonla, bir dağıtım birkaç makineye tekrarlanabilir şekilde uygulanabilir veya bir test makinesinde test edilebilir ve dağıtım yazarı her seferinde aynı paketlerin konuşlandırıldığından emin olabilir. Bu, a'nın requirements.txtyaptığı şeydir .

Gördüğünüz gibi, ikisi de büyük bir paket ve sürüm listesi gibi görünse de, bu iki şeyin çok farklı işleri var. Ve bunu karıştırmak ve yanlış yapmak kesinlikle kolay! Ancak bunu düşünmenin doğru yolu requirements.txt, çeşitli setup.pypaket dosyalarındaki gereksinimlerin ortaya koyduğu "soru" nun "cevabı" dır . Elle yazmak yerine, genellikle pip'e setup.py, istenen paketlerdeki tüm dosyalara bakmasını, tüm gereksinimlere uyduğunu düşündüğü bir paket kümesi bulmasını ve ardından kurulduktan sonra "dondurmasını söyleyerek üretilir. msgstr "bu paketlerin listesini bir metin dosyasına ( pip freezeadın geldiği yer).

Yani paket servisi olan restoran:

  • setup.pyhala uygulanabilir olan en gevşek bağımlılık sürümlerini bildirmelidir. Görevi, belirli bir paketin neyle çalışabileceğini söylemek.
  • requirements.txttüm kurulum işini tanımlayan ve herhangi bir pakete bağlı olduğu düşünülmemesi gereken bir dağıtım bildirimidir. Görevi, bir dağıtım işini yapmak için gerekli tüm paketlerin kapsamlı bir listesini bildirmektir.
  • Bu iki şeyin çok farklı içeriği ve mevcut nedenleri olduğundan, birini diğerine kopyalamak mümkün değildir.

Referanslar:


10
Bu, paket kurulumu adı verilen karışıklığa bir miktar sipariş vermeme izin veren en iyi açıklamalardan biri! :)
Kounavi

6
Bir geliştiricinin neden requirements.txtkurulum veya test için somut / dondurulmuş gereksinimleri içeren paketin kaynağı ile birlikte sürüm kontrolünü koruyacağı hala net değil . Elbette setup.pybu amaç için projenin kendisinde kullanılabilir mi? Böyle bir dosyayı sadece projenin yönetimini desteklemek için kullanılan araçlar için kullanmayı hayal edebiliyorum (örneğin yeniden düzenleme, sürüm çıkarma vb.).
Sam Brightman

2
@samBrightman Tamamen katılıyorum, kütüphane paketleri veya uygulama paketlerinin kodlarıyla depoya kendi istekleri.txt dosyasını işlemesi gerektiğini düşünmüyorum . Ben bunun yapı testi sırasında üretilen ve daha sonra bir yapı manifest belgelemek ve sonuçta bir dağıtım yapı oluşturmak için kullanılan bir eser olması gerektiğini düşünüyorum.
Jonathan Hanson

6
Yani requirements.txt, derleme süreci içinde kullanılmasa da, belirli bir yapı üreten dünyanın durumu için daha fazla belge mi diyorsunuz ? Mantıklı. Bununla birlikte, birkaç sistem çoğaltmaya dayanıyor gibi görünüyor: Travis, sanal ağınıza bazı varsayılan (eski) paketleri yükler ve kullanmayı söyler requirements.txt. Bağımlılıkların en azından nasıl kullanıldığını nasıl sorarsam setup.py, insanlar kullanmam gerektiğinde ısrar ediyorlar requirements.txt.
Sam Brightman

2
Bunlardan herhangi birinden alabileceğiniz en iyi tavsiye, sizin için çalışan bir model bulmak, iyi belgelemek ve birlikte çalıştığınız herkesin bunu anladığından emin olmaktır. Neden her bir biti yaptığınızı ve kullanım durumunuz için gerçekten anlamlı olup olmadığını düşünün. Ve her şey yolunda giderse, Python'daki mevcut yapım, paketleme ve yayınlama durumu hakkında olabildiğince iyi okumaya çalışın. Ama nefesini tutma.
Jonathan Hanson

90

Bir dosya tanıtıcısı alamaz. Bağımsız install_requiresdeğişken yalnızca bir dize veya bir dize listesi olabilir .

Elbette dosyanızı kurulum komut dosyasında okuyabilir ve dizelerin bir listesi olarak iletebilirsiniz install_requires.

import os
from setuptools import setup

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

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

5
Her ne kadar yararlı olsa da, bu şartnamelerin şartnamesini bildirmeden zorunlu hale getirmektedir. Bu, bazı araçların gereksinimlerinizin ne olduğunu bulmasını imkansız hale getirir. Örneğin, PyCharm içinde belirtilen tüm gereksinimlerin otomatik kurulumunu sunar install_requires. Ancak, bildirimli sözdizimi kullanmazsanız çalışmaz.
Piotr Dobrogost

55
@PiotrDobrogost Belki PyCharm geliştiricisi programlarını düzeltmelidir. setup.pyayrıştırılması gereken bir veri dosyası değil, çalıştırılması gereken bir programdır. Bu, bu cevabı daha da kötüleştirmez.
Fredrick Brennan

5
Sadece olası sorunlara işaret ediyorum; bu cevap gayet iyi. Bilginin kodun arkasında "gizli" olması sorunu olan sadece PyCharm değil. Bu evrensel bir sorundur ve bu nedenle Python paketlemesinde meta verilerin beyan edici spesifikasyonuna doğru genel bir hareket vardır.
Piotr Dobrogost

33
Zamandır koymak gibi cezayı Works include requirements.txtSİZİN içine MANIFEST.inveya bir kaynak dağıtımından kütüphanenizi yüklemek mümkün olmayacaktır.
Pankrat

4
Bunun eski bir soru olduğunu biliyorum, ancak en azından bugünlerde PyCharm'ı Tercihler-> Araçlar-> Python entegre araçları-> Paket gereksinimleri dosyası
lekksi

64

Gereksinim dosyaları, yalnızca setup.pydaha güçlü kısıtlamalarla tamamlamanız gerektiğinde yararlı olan genişletilmiş bir pip biçimi kullanır ; örneğin, bazı bağımlılıkların gelmesi gereken kesin URL'leri veya pip freezetüm paket setini bilinen çalışana dondurmak için çıktısını belirtme sürümleri. Ekstra kısıtlamalara ihtiyacınız yoksa yalnızca a kullanın setup.py. Gerçekten bir şekilde göndermeniz gerektiğini düşünüyorsanız requirements.txt, bunu tek bir hat haline getirebilirsiniz:

.

Geçerli olacak ve tam olarak setup.pyaynı dizindeki içeriğe atıfta bulunacaktır .


9
Ancak bu durumda uygulamamı da yüklemeye çalışır. İhtiyacım yoksa ve yalnızca install_requires yüklüyse ne olur?
ffeast

2
@ Feast'in ne istediğini açıklamak için, yalnızca setup.py dosyasında gereksinimler varsa pip install -r requirements.txt , paketin kendisini yüklemeden gereksinimleri (eşdeğeri ) kurmanın bir yolu var mı?
haridsv

1
@ffeast @haridsv -e .yeterli olmalı. Bu sayfayı kontrol edin: caremad.io/posts/2013/07/setup-vs-requirement
dexhunter

4
@ DexD.Hunter hala uygulamanın kendisini yüklemeye çalışıyor. İstediğimiz bu değil
ffeast

38

Soruya kesin bir cevap olmasa da , bu sorunu iyi bir şekilde ele almak için https://caremad.io/2013/07/setup-vs-requirement/ adresinde Donald Stufft'ın blog gönderisini öneriyorum . Büyük başarı için kullanıyorum.

Kısacası, requirements.txtbir setup.pyalternatif değil , bir dağıtım tamamlayıcısıdır. İçindeki paket bağımlılıklarını uygun bir şekilde saklayın setup.py. requirements.txtGeliştirme, test veya üretim için paket bağımlılıklarının belirli sürümlerini getirecek şekilde ayarlayın .

Örneğin aşağıdaki repoya dahil olan paketler deps/:

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

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

pip, paketleri yürütür setup.pyve bildirilen bağımlılıkların belirli sürümlerini yükler install_requires. Hiçbir kopya yoktur ve her iki eserin amacı korunur.


7
Başkalarının yüklemesi için bir paket sağlamak istediğinizde bu çalışmaz pip install my-package. Paketim için bağımlılıklar paketim / setup.py dosyasında listelenmemişse, tarafından yüklenmez pip install my-package. Ben açıkça setup.py bunları belirtmeden bağımlılıkları içeren diğerleri için bir paket sağlamak nasıl belirleyemedim. Başkalarının gereksinimler dosyasını indirmeden ve manuel olarak aramadan paketimi + bağımlılıkları yüklemesine izin verirken bir kişinin DRY'yi nasıl tutacağını anladığını bilmek isterim pip install -r my-package/requirements.txt.
Malina

2
@Malina Buradaki paket olmadan mükemmel bir şekilde kurulabilir requirements.txt. Bütün mesele bu. Her şeyi daha net hale getirmek için soru güncellendi. Ayrıca eski blog yazı bağlantısı güncellendi.
famousgarkin

setup.py çalıştırılırken stup.py dosyasında listelenen dosyaların belirli sürümleri için gereksinimler.txt olarak adlandırılır.
vagonlar

@ Dtracers'ın tam tersi. gereksinimleri.txt, setup.py'nin bağımlılıklarının alınabileceği pakete kendini gösterir. Yüklerken gereksinimleri kullanırken Yani, işler ve pip yoluyla yüklerken, çok çalışır - her iki durumda da requirements.txt kullanırken fazla şeyler yüklemek için izin de setup.py en dependecies kullanarak, ama
KOSGEB

20

Kullanılması parse_requirementspip API genel olarak belgelenen ve desteklenmediği için problemlidir. Pip 1.6'da, bu fonksiyon aslında hareket ediyor, bu yüzden mevcut kullanımları kırılacak.

Daha güvenilir yolu arasındaki tekrarını ortadan kaldırmak setup.pyve requirements.txtspesifik etmektir bağımlılıkları içinde setup.pyve daha sonra koyun -e .senin içine requirements.txtdosyası. pipGeliştiricilerin birinden, bunun neden daha iyi bir yol olduğu hakkında bazı bilgileri burada bulabilirsiniz: https://caremad.io/blog/setup-vs-requirement/


@Tommy Bunu deneyin: caremad.io/2013/07/setup-vs-requirement Bu, başka bir cevapta yayınlanan bağlantıyla aynı.
amit

18

Yukarıdaki diğer cevapların çoğu pip'in API'sinin mevcut sürümü ile çalışmaz. İşte pip'in geçerli sürümü ile yapmanın doğru * yolu (yazma sırasında 6.0.8, 7.1.2'de de çalıştı. Pip-V ile sürümünüzü kontrol edebilirsiniz).

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

* Doğru, geçerli pip ile parse_requirements kullanmanın yolu olmasıdır. Yine de muhtemelen bunu yapmanın en iyi yolu değil, çünkü yukarıdaki posterlerin söylediği gibi pip gerçekten bir API tutmuyor.


14

Geçerli paketi Travis'e yükleyin. Bu bir requirements.txtdosyanın kullanılmasını önler . Örneğin:

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

2
Bu, "doğru" ve "pratik" in en iyi kombinasyonudur. Testler geçtikten sonra Travis'in bir gereksinim.txt oluşturmasını pip freezeve bu dosyayı bir eser olarak (S3 gibi bir şey olarak) dışa aktarabileceğinizi varsa, o zaman tam olarak ne yaptığınızı tekrar yüklemek için harika bir yolunuz olacağını ekleyebilirim. test etti.
Jonathan Hanson

4

from pip.req import parse_requirements benim için çalışmadı ve bence gereksinimlerimdeki boş satırlar için. txt, ancak bu işlev işe yarıyor

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

Kullanıcılarınızı pip kurmaya zorlamak istemiyorsanız, davranışını şu şekilde taklit edebilirsiniz:

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

Aşağıdaki arabirim 10. pip'te kullanımdan kaldırıldı:

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

Bu yüzden sadece basit metin ayrıştırmaya geçtim:

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

Bu basit yaklaşım zamanın% 90'ından fazlasında işe yarar. Python 3.6+ kullananlar için , bunun bir pathlibvaryasyonu olan bir cevap yazdım .
Acumenus

3

Bu basit yaklaşım, ihtiyaçlar dosyasından okur setup.py. Bu cevap Dmitiry S. tarafından bir varyasyon . Bu cevap yalnızca Python 3.6+ ile uyumludur.

Başına DS , requirements.txtoysa belirli sürüm numaralarıyla somut şartları belgelenebilir setup.pygevşek versiyonu aralıklarına sahip soyut gereklerini karşılamak için gerekli olabilir.

Aşağıda benim bir alıntı 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.TextFileYorumların kaldırılacağını unutmayın . Ayrıca, tecrübelerime göre, görünüşe göre gereksinimler dosyasında paketlemek için herhangi bir özel adım atmanıza gerek yok.


2

parse_requirementsDAVRANIŞ DİKKAT EDİN !

pip.req.parse_requirementsAlt çizgileri tire işareti olarak değiştireceğinizi lütfen unutmayın . Bu onu keşfetmeden önce birkaç gün beni çok etkiledi. Gösteren örnek:

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

üretir

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

1
Alt çizgi sürümünü almak için güvenli olmayan_adı kullanın :[ir.req.unsafe_name for ir in req_deps if ir.req is not None]
alanjds

5
Başka bir yerde belirtildiği gibi, PIP bir kütüphane değil bir uygulamadır. Herkese açık olarak kabul edilmiş bir API'sı yoktur ve kodunuza içe aktarılması desteklenen bir kullanım örneği değildir. Beklenmedik bir davranışa sahip olması şaşırtıcı değil; iç fonksiyonları hiçbir zaman bu şekilde kullanılmak üzere tasarlanmamıştır.
Jonathan Hanson

1

Bunun için yeniden kullanılabilir bir fonksiyon oluşturdum. Aslında gereksinim dosyalarının tüm dizinini ayrıştırır ve extras_require olarak ayarlar.

En son her zaman burada bulunabilir: 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)

çok hoş! hatta en son pip ile özyinelemeli gereksinimleri ele alır :)
am

@amohr Teşekkürler! Son zamanlarda daha sonra bir pip için güncelledim, bir şeyleri taşıyarak neden oldukları gibi davrandıklarından emin değilim. pip._internalKullanılabilir bir harici API sağlamazsanız, tüm bunları kırmamalısınız. sağladığınız her şeyi kullanıyor.
trevorj

0

Başka bir olası çözüm ...

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

ve sonra kullanmak için ...

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

nereden treegeliyor
Francesco Boi

@FrancescoBoi, tamamen çalışan bir çözüm sunmadığım için beni biraz affederseniz ... ağaç gerçekten sadece yerel dosya sisteminin bir taramasıdır (linux'daki "tree" komutuna çok benzer). Ayrıca, yukarıdaki çözümüm bu noktada tamamen çalışmayabilir, çünkü pip sürekli güncelleniyor ve pip içlerini kullandım.
Brian Bruggeman

0

Ben böyle bir şey yapmayı tavsiye etmem. Birden fazla kez belirtildiği gibi install_requiresve requirements.txtkesinlikle aynı liste olması gerekiyordu. Ancak, pip'in dahili dahili API'lerini içeren birçok yanıltıcı cevap olduğu için olduğu için, daha sağlıklı alternatiflere bakmaya değer olabilir ...

Gerek yoktur pip bir ayrıştırmak için requirements.txtbir dosyayı Setuptools setup.py senaryo. Setuptools zaten proje onun gerekli tüm araçları içerir üst düzey paketinde pkg_resources.

Aşağı yukarı böyle görünebilir:

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

Farkında olmadığınız takdirde, 2015'ten bu yana pek çok kişinin (kendim dahil) pip's ayrıştırma yöntemini kullanmasının nedeni github.com/pypa/setuptools/issues/470pkg_resources gibi hatalardır . Bu kesin olanı bugünlerde düzeltildi, ancak her iki uygulamanın da ayrı olarak geliştirildiği göründüğünden hala kullanmaktan biraz korkuyorum.
trevorj

@trevorj Bunu işaret ettiğiniz için teşekkürler, bilmiyordum. Gerçek şu anda işe yarıyor ve pip dahil olmak benim için saçma bir fikir gibi görünüyor (özellikle bu şekilde). Diğer cevaplara bir göz atın, çoğu aynı kötü tavsiye edilen fikrin ufak tefek değişikliklerine benziyor. Ve yeni gelenler sadece bu trendi takip edebilirler. Umarım PEP517 ve PEP518 gibi girişimler toplumu bu delilikten uzaklaştıracaktır.
sinoroc

-1

Başka bir basit, pip sürüm kanıtı çözümü için bu SO soruya cevabımı çapraz gönderme .

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

Ardından, tüm gereksinimlerinizi requirements.txtproje kök dizini altına girin.


-1

Bunu ben yaptım:

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Çevre belirteçlerini de ayrıştıran bir başka saldırı 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
)

Hem sdist hem de ikili dağıtımları desteklemelidir.

Başkaları tarafından belirtildiği gibi parse_requirements, bazı eksiklikler vardır, bu yüzden kamu projelerinde yapmanız gerekenler değildir, ancak iç / kişisel projeler için yeterli olabilir.


pip 20.1 API'lerini değiştirdi ve işaretleyiciler parse_requirements()artık kullanılamıyor, bu yüzden şimdi başarısız oluyor.
Tuukka Mustonen

-3

Romain'in cevabını mevcut ortam belirteçlerine göre ayrıştıran ve filtreleyen cevabınapip 9.0.1 dayanan tam bir saldırı (test edildi ) :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
Bu sadece kısmen doğrudur. Eğer ararsanız r.match_markers(), aslında bir sdist için yapılacak doğru olan belirteçleri değerlendiriyorsunuz. Eğer bir ikili dist (örneğin tekerlek) yapıyolar Ancak, paket yalnızca eşleşti olanlar kütüphaneleri listelemek istiyorum senin build zamanı ortamı.
Tuukka Mustonen

@TuukkaMustonen, öyleyse bunu bulmak için nerede wheel environment(eğer kişinin yapmaya çalıştığı şeyse) işaretçileri değerlendirmek için?
anatoly techtonik

Ayrıca desteklemesi gereken stackoverflow.com/a/41172125/165629 adresine bakın bdist_wheel. İşaretçileri değerlendirmez, sadece ekler extras_require.
Tuukka Mustonen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.