python Setup.py और पैकेज के साथ पैकेज संस्करण साझा करने का सही तरीका क्या है?




setuptools distutils (7)

setuptools , setuptools , आदि के साथ setup.py में एक पैकेज संस्करण निर्दिष्ट है:

# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)

मैं पैकेज के भीतर से उसी संस्करण संख्या तक पहुंचने में सक्षम होना चाहता हूं:

>>> import foobar
>>> foobar.__version__
'1.0.0'

मैं अपने पैकेज के __init__.py में __version__ = '1.0.0' जोड़ सकता हूं, लेकिन पैकेज में सरलीकृत इंटरफेस बनाने के लिए मैं अपने पैकेज में अतिरिक्त आयात भी शामिल करना चाहता हूं:

# file: __init__.py

from foobar import foo
from foobar.bar import Bar

__version__ = '1.0.0'

तथा

# file: setup.py

from foobar import __version__
...
setup(
name='foobar',
version=__version__,
# other attributes
)

हालांकि, इन अतिरिक्त आयातों से foobar की स्थापना विफल हो सकती है अगर वे अन्य संकुल आयात करते हैं जो अभी तक स्थापित नहीं हैं। Setup.py और पैकेज के साथ पैकेज संस्करण साझा करने का सही तरीका क्या है?

https://code.i-harness.com


केवल setup.py में संस्करण सेट करें, और pkg_resources साथ अपना स्वयं का संस्करण पढ़ें, प्रभावी ढंग से setuptools मेटाडेटा से पूछताछ करें:

फ़ाइल: setup.py

setup(
    name='foobar',
    version='1.0.0',
    # other attributes
)

फ़ाइल: __init__.py

from pkg_resources import get_distribution

__version__ = get_distribution('foobar').version

इस काम को सभी मामलों में करने के लिए, जहां आप इसे इंस्टॉल किए बिना इसे चला सकते हैं, DistributionNotFound और वितरण स्थान के लिए परीक्षण करें:

from pkg_resources import get_distribution, DistributionNotFound
import os.path

try:
    _dist = get_distribution('foobar')
    # Normalize case for Windows systems
    dist_loc = os.path.normcase(_dist.location)
    here = os.path.normcase(__file__)
    if not here.startswith(os.path.join(dist_loc, 'foobar')):
        # not installed, but there is another version that *is*
        raise DistributionNotFound
except DistributionNotFound:
    __version__ = 'Please install this project with setup.py'
else:
    __version__ = _dist.version

मुझे विश्वास नहीं है कि इसका एक कैननिक जवाब है, लेकिन मेरी विधि (या तो मैंने जो कुछ अन्य स्थानों में देखा है उससे सीधे कॉपी या थोड़ा tweaked) निम्नानुसार है:

फ़ोल्डर विरासत (केवल प्रासंगिक फाइलें):

package_root/
 |- main_package/
 |   |- __init__.py
 |   `- _version.py
 `- setup.py

main_package/_version.py :

"""Version information."""

# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"

main_package/__init__.py :

"""Something nice and descriptive."""

from main_package.some_module import some_function_or_class
# ... etc.
from main_package._version import __version__

__all__ = (
    some_function_or_class,
    # ... etc.
)

setup.py :

from setuptools import setup

setup(
    version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
    # ... etc.
)

... जो पाप के रूप में बदसूरत है ... लेकिन यह काम करता है, और मैंने इसे देखा है या ऐसे लोगों द्वारा वितरित पैकेजों में ऐसा कुछ है जो मुझे एक बेहतर तरीका जानने की उम्मीद करेंगे यदि कोई था।


मैं @ stefano-m के दर्शन के बारे में सहमत हूं:

स्रोत में संस्करण = "xyz" होने और setup.py के भीतर इसे पार्स करना निश्चित रूप से सही समाधान है, IMHO। रन टाइम जादू पर निर्भर (चारों ओर दूसरी तरफ) से काफी बेहतर है।

और यह उत्तर @ शून्य-पीरियस के answer । पूरा बिंदु है "setup.py में आयात का उपयोग न करें, इसके बजाय, फ़ाइल से संस्करण पढ़ें"।

मैं __version__ को पार्स करने के लिए रेगेक्स का उपयोग करता __version__ ताकि इसे समर्पित फ़ाइल की अंतिम पंक्ति होने की आवश्यकता न हो। असल में, मैंने अभी भी अपने प्रोजेक्ट के __init__.py अंदर एकल-स्रोत-सत्य __version__ है।

फ़ोल्डर विरासत (केवल प्रासंगिक फाइलें):

package_root/
 |- main_package/
 |   `- __init__.py
 `- setup.py

main_package/__init__.py :

# You can have other dependency if you really need to
from main_package.some_module import some_function_or_class

# Define your version number in the way you mother told you,
# which is so straightforward that even your grandma will understand.
__version__ = "1.2.3"

__all__ = (
    some_function_or_class,
    # ... etc.
)

setup.py :

from setuptools import setup
import re, io

__version__ = re.search(
    r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]',  # It excludes inline comment too
    io.open('main_package/__init__.py', encoding='utf_8_sig').read()
    ).group(1)
# The beautiful part is, I don't even need to check exceptions here.
# If something messes up, let the build process fail noisy, BEFORE my release!

setup(
    version=__version__,
    # ... etc.
)

... जो अभी भी आदर्श नहीं है ... लेकिन यह काम करता है।

और वैसे, इस बिंदु पर आप अपने नए खिलौने का इस तरह से परीक्षण कर सकते हैं:

python setup.py --version
1.2.3

पीएस: यह आधिकारिक पायथन पैकेजिंग दस्तावेज़ (और इसका mirror ) अधिक विकल्पों का वर्णन करता है। इसका पहला विकल्प रेगेक्स का भी उपयोग कर रहा है। (आपके द्वारा उपयोग किए जाने वाले सटीक रेगेक्स पर निर्भर करता है, यह संस्करण स्ट्रिंग के अंदर उद्धरण चिह्नों को संभाल सकता है या नहीं। आम तौर पर हालांकि कोई बड़ी समस्या नहीं है।)

पीपीएस: एडीएएल पायथन में फिक्स अब इस जवाब में बैकपोर्ट किया गया है।


स्वीकृत उत्तर की आवश्यकता है कि पैकेज स्थापित किया गया है। मेरे मामले में, मुझे स्रोत setup.py से __version__ ( __version__ सहित) निकालने की आवश्यकता थी। Setuptools पैकेज के परीक्षणों को देखते हुए मुझे प्रत्यक्ष और सरल समाधान मिला। _setup_stop_after विशेषता पर अधिक जानकारी के लिए _setup_stop_after मुझे पुरानी मेलिंग सूची पोस्ट में ले जाता है जिसमें _setup_stop_after उल्लेख किया गया है, जो मुझे आवश्यक वास्तविक दस्तावेज़ों में ले जाता है । उसके बाद, यहां सरल समाधान है:

फ़ाइल setup.py :

from setuptools import setup

setup(name='funniest',
      version='0.1',
      description='The funniest joke in the world',
      url='http://github.com/storborg/funniest',
      author='Flying Circus',
      author_email='[email protected]',
      license='MIT',
      packages=['funniest'],
      zip_safe=False)

फ़ाइल extract.py :

from distutils.core import run_setup
dist = run_setup('./setup.py', stop_after='init')
dist.get_version()


your_pkg/__init__.py __version__ में your_pkg/__init__.py , और your_pkg/__init__.py का उपयोग करके setup.py में पार्स करें:

import ast
import importlib.util

from pkg_resources import safe_name

PKG_DIR = 'my_pkg'

def find_version():
    """Return value of __version__.

    Reference: https://.com/a/42269185/
    """
    file_path = importlib.util.find_spec(PKG_DIR).origin
    with open(file_path) as file_obj:
        root_node = ast.parse(file_obj.read())
    for node in ast.walk(root_node):
        if isinstance(node, ast.Assign):
            if len(node.targets) == 1 and node.targets[0].id == "__version__":
                return node.value.s
    raise RuntimeError("Unable to find version string.")

setup(name=safe_name(PKG_DIR),
      version=find_version(),
      packages=[PKG_DIR],
      ...
      )

यदि पायथन <3.4 का उपयोग करते हैं, तो ध्यान दें कि importlib.util.find_spec उपलब्ध नहीं है। इसके अलावा, setup.py लिए उपलब्ध होने पर पाठ्यक्रम के importlib के किसी भी बैकपोर्ट पर निर्भर नहीं किया जा सकता है। इस मामले में, उपयोग करें:

import os

file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, '__init__.py')

स्वीकार्य उत्तर और टिप्पणियों के आधार पर, मैं यही कर रहा हूं:

फ़ाइल: setup.py

setup(
    name='foobar',
    version='1.0.0',
    # other attributes
)

फ़ाइल: __init__.py

from pkg_resources import get_distribution, DistributionNotFound

__project__ = 'foobar'
__version__ = None  # required for initial installation

try:
    __version__ = get_distribution(__project__).version
except DistributionNotFound:
    VERSION = __project__ + '-' + '(local)'
else:
    VERSION = __project__ + '-' + __version__
    from foobar import foo
    from foobar.bar import Bar

स्पष्टीकरण:

  • __project__ स्थापित करने के लिए प्रोजेक्ट का नाम है जो पैकेज के नाम से अलग हो सकता है

  • VERSION है जो मैं अपने कमांड लाइन इंटरफेस में प्रदर्शित करता हूं जब - --version से अनुरोध किया जाता है

  • अतिरिक्त आयात (सरलीकृत पैकेज इंटरफ़ेस के लिए) केवल तब होता है जब प्रोजेक्ट वास्तव में स्थापित किया गया हो





distutils