IT박스

패키지 버전을 setup.py 및 패키지와 공유하는 올바른 방법은 무엇입니까?

itboxs 2020. 12. 8. 07:52
반응형

패키지 버전을 setup.py 및 패키지와 공유하는 올바른 방법은 무엇입니까?


distutils, setuptools등의 패키지 버전이 지정됩니다 setup.py:

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

패키지 내에서 동일한 버전 번호에 액세스하고 싶습니다.

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

__version__ = '1.0.0'패키지의 __init__.py에 추가 있지만 패키지에 대한 단순화 된 인터페이스를 생성하기 위해 패키지에 추가 가져 오기를 포함하고 싶습니다.

# 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 및 패키지와 공유하는 올바른 방법은 무엇입니까?


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

나는 이것에 대한 정식 답변이 있다고 생각하지 않지만 내 방법 (직접 복사하거나 다른 여러 곳에서 보았던 것에서 약간 조정)은 다음과 같습니다.

폴더 계층 (관련 파일 만 해당) :

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 내에서 구문 분석하는 것은, 확실히 이럴 올바른 솔루션입니다. 런타임 매직에 의존하는 것보다 훨씬 낫습니다.

그리고이 대답은 @ zero-piraeus의 대답 에서 파생되었습니다 . 요점은 "setup.py에서 가져 오기를 사용하지 말고 대신 파일에서 버전을 읽습니다"입니다.

정규식을 사용하여 구문 분석을 수행 __version__하므로 전용 파일의 마지막 줄이 될 필요가 없습니다. 사실, 나는 여전히 __version__내 프로젝트의 __init__.py.

폴더 계층 (관련 파일 만 해당) :

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

PS: This official Python packaging document (and its mirror) describes more options. Its first option is also using regex. (Depends on the exact regex you use, it may or may not handle quotation marks inside version string. Generally not a big issue though.)

PPS: The fix in ADAL Python is now backported into this answer.


Put __version__ in your_pkg/__init__.py, and parse in setup.py using ast:

import ast
import importlib.util

from pkg_resources import safe_name

PKG_DIR = 'my_pkg'

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

    Reference: https://stackoverflow.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],
      ...
      )

If using Python < 3.4, note that importlib.util.find_spec is not available. Moreover, any backport of importlib of course cannot be relied upon to be available to setup.py. In this case, use:

import os

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

Based on the accepted answer and comments, this is what I ended up doing:

file: setup.py

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

file: __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

Explanation:

  • __project__ is the name of the project to install which may be different than the name of the package

  • VERSION is what I display in my command-line interfaces when --version is requested

  • the additional imports (for the simplified package interface) only occur if the project has actually been installed


The accepted answer requires that the package has been installed. In my case, I needed to extract the installation params (including __version__) from the source setup.py. I found a direct and simple solution while looking through the tests of the setuptools package. Looking for more info on the _setup_stop_after attribute lead me to an old mailing list post which mentioned distutils.core.run_setup, which lead me to the actual docs needed. After all that, here's the simple solution:

file 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='flyingcircus@example.com',
      license='MIT',
      packages=['funniest'],
      zip_safe=False)

file extract.py:

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

참고URL : https://stackoverflow.com/questions/17583443/what-is-the-correct-way-to-share-package-version-with-setup-py-and-the-package

반응형