[python] 파이썬 패키지에 버전을 포함시키는 표준 방법?

다음을 수행 할 수있는 방식으로 버전 문자열을 파이썬 패키지와 연결하는 표준 방법이 있습니까?

import foo
print foo.version

작은 / 주요 문자열이 setup.py이미 지정되어 있으므로 추가 하드 코딩없이 해당 데이터를 검색 할 수있는 방법이 있다고 생각합니다 . 내가 발견 한 대체 솔루션은 것이 었 import __version__내에서 foo/__init__.py다음 한 __version__.py에 의해 생성 setup.py.



답변

귀하의 질문에 대한 직접적인 대답은 아니지만 __version__, 이름을 고려해야합니다 version.

이것은 거의 준 표준입니다. 표준 라이브러리의 많은 모듈이을 __version__사용하며 많은 타사 모듈 에서도 사용 되므로 준 표준입니다.

일반적으로 __version__문자열이지만 때로는 float 또는 tuple이기도합니다.

편집 : S.Lott (감사합니다!)가 언급했듯이 PEP 8 은 다음과 같이 명시 적으로 말합니다.

모듈 레벨 던더 이름

같은 (이 선도하고 두 후행 밑줄 즉, 이름) 모듈 수준 “dunders”는 __all__, __author__, __version__등 모듈의 문서화 문자열 뒤에 있지만에서 제외 import 문 앞에 배치해야 __future__가져옵니다.

또한 버전 번호가 PEP 440 ( 이 표준의 이전 버전 인 PEP 386)에 설명 된 형식을 준수하는지 확인해야합니다 .


답변

_version.py버전 정보를 저장 하기 위해 단일 파일을 “한 번 정식 위치”로 사용합니다.

  1. __version__속성을 제공 합니다.

  2. 표준 메타 데이터 버전을 제공합니다. 따라서 pkg_resources패키지 메타 데이터 (EGG-INFO 및 / 또는 PKG-INFO, PEP 0345)를 구문 분석하는 도구 또는 기타 도구에 의해 감지됩니다 .

  3. 패키지를 만들 때 패키지 또는 다른 것을 가져 오지 않으므로 일부 상황에서 문제가 발생할 수 있습니다. (이로 인해 발생할 수있는 문제에 대해서는 아래 주석을 참조하십시오.)

  4. 버전 번호가 기록 된 장소는 하나뿐이므로 버전 번호가 변경 될 때 버전 번호를 변경할 장소는 하나 뿐이며 버전이 일치하지 않을 가능성이 적습니다.

작동 방식은 다음과 같습니다. 버전 번호를 저장하는 “한 곳의 정식 장소”는 Python 패키지에있는 “_version.py”라는 .py 파일입니다 (예 🙂 myniftyapp/_version.py. 이 파일은 Python 모듈이지만 setup.py에서 가져 오지 않습니다! (이것은 기능 3을 물리치게됩니다.) 대신 setup.py는이 파일의 내용이 매우 단순하다는 것을 알고 있습니다.

__version__ = "3.6.5"

따라서 setup.py는 파일을 열고 다음과 같은 코드로 파싱합니다.

import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
    verstr = mo.group(1)
else:
    raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))

그런 다음 setup.py는 해당 문자열을 “version”인수의 값으로로 전달 setup()하여 기능 2를 만족시킵니다.

기능 1을 충족시키기 위해 패키지를 설정 시간이 아닌 런타임에 _version 파일을 다음 myniftyapp/__init__.py과 같이 가져올 수 있습니다 .

from _version import __version__

다음은 몇 년 동안 사용해온 이 기술의 예입니다 .

이 예제의 코드는 조금 더 복잡하지만이 주석에 쓴 간단한 예제는 완전한 구현이어야합니다.

다음은 버전을 가져 오는 예제 코드입니다 .

이 방법에 문제가 있으면 알려주십시오.


답변

2017-05 재 작성

파이썬 코드를 작성하고 다양한 패키지를 관리한지 10 년이 지난 후에 저는 DIY가 최선의 방법이 아니라는 결론에 도달했습니다.

pbr패키지의 버전 관리를 위해 패키지를 사용하기 시작했습니다 . git을 SCM으로 사용하는 경우 마술처럼 워크 플로에 적합하여 몇 주 동안의 작업 시간을 절약 할 수 있습니다 (문제가 얼마나 복잡 할 수 있는지에 놀랄 것입니다).

현재 pbr은 11 번째로 가장 많이 사용되는 파이썬 패키지로 순위가 매겨졌으며이 수준에 도달 하는 데는 더티 트릭이 포함되지 않았습니다.

pbr 패키지 유지 관리 부담을 더 많이 할 수 있으며 버전 관리에만 국한되지는 않지만 모든 이점을 채택하도록 강요하지는 않습니다.

따라서 한 번의 커밋에서 pbr을 채택하는 방법에 대한 아이디어를 얻으려면 pbr에 swiching 포장을하십시오.

아마도 버전이 저장소에 전혀 저장되지 않았 음을 알 수 있습니다. PBR은 Git 브랜치 및 태그에서이를 감지합니다.

pbr은 응용 프로그램을 패키지하거나 설치할 때 버전을 “컴파일”하고 캐시하므로 git 저장소가 없을 때 발생하는 상황에 대해 걱정할 필요가 없으므로 git에 런타임 종속성이 없습니다.

오래된 솔루션

지금까지 내가 본 최고의 솔루션은 다음과 같은 이유도 설명합니다.

내부 yourpackage/version.py:

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'

내부 yourpackage/__init__.py:

from .version import __version__

내부 setup.py:

exec(open('yourpackage/version.py').read())
setup(
    ...
    version=__version__,
    ...

더 나은 것으로 보이는 다른 접근법을 알고 있다면 알려주십시오.


답변

지연된 PEP 396 (모듈 버전 번호) 에 따라 제안 된 방법이 있습니다. 여기에는 이론적으로 모듈이 따라야 할 (선택적으로 선택적인) 표준이 설명되어 있습니다. 스 니펫은 다음과 같습니다.

3) 모듈 (또는 패키지)에 버전 번호가 포함 된 경우, __version__속성 에서 버전을 사용할 수 있어야 합니다.

4) 네임 스페이스 패키지 내에 존재하는 모듈의 경우, 모듈은 __version__속성을 포함해야 한다. 네임 스페이스 패키지 자체에는 자체 __version__속성이 포함되어서는 안된다 (SHOULD NOT) .

5) __version__속성 값은 문자열이어야한다.


답변

이것이 너무 늦었지만 이전 답변에 대한 약간 더 간단한 대안이 있습니다.

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(그리고 버전 번호의 자동 증가 부분을 문자열을 사용하여 변환하는 것은 매우 간단합니다 str().)

물론 내가 본 것에서 사람들은을 사용할 때 이전에 언급 한 버전과 같은 것을 사용하는 경향이 __version_info__있으며, 따라서 튜플의 정수로 저장합니다. 그러나 호기심이나 자동 증가 (그리고 심지어는, 어떤 경우에도 버전 번호의 일부에 대한 뺄셈과 같은 수학적 연산을 수행 할 상황이 의심 스럽기 때문에 그렇게하는 요점을 알지 못합니다. int()str()) 매우 쉽게 사용할 수 있습니다. (다른 한편으로, 다른 사람의 코드가 문자열 튜플이 아닌 숫자 튜플을 기대하여 실패 할 가능성이 있습니다.)

이것은 물론 내 자신의 견해이며, 숫자 튜플 사용에 대한 다른 사람들의 의견을 기쁘게 생각합니다.


shezi가 상기 한 것처럼, 숫자 문자열에 대한 (어휘) 비교는 직접적인 수치 비교와 반드시 같은 결과를 가질 필요는 없습니다. 이를 위해서는 선행 제로가 필요합니다. 따라서 결국 __version_info__정수 값의 튜플로 저장 하거나 호출하면 더 효율적인 버전 비교가 가능합니다.


답변

이 솔루션 중 다수는 git버전 태그를 무시 하므로 여전히 여러 위치에서 버전을 추적해야합니다 (나쁜). 나는 다음 목표로 이것에 접근했다.

  • gitrepo 의 태그에서 모든 Python 버전 참조를 파생하십시오.
  • 자동화 git tag/ pushsetup.py upload어떤 입력을지지 않습니다 하나의 명령으로 단계.

작동 방식 :

  1. A로부터 make release명령의 자식의 repo의 마지막 태그 버전을 찾아 증가합니다. 태그가로 푸시됩니다 origin.

  2. Makefile버전은 버전을 src/_version.py읽고 setup.py릴리스에 포함 할 버전을 저장합니다 . 소스 컨트롤을 확인하지 마십시오 _version.py!

  3. setup.py명령은에서 새 버전 문자열을 읽습니다 package.__version__.

세부:

메이크 파일

# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver      = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))

.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
    echo '__version__ = "$(call git_describe_ver)"' > $@

.PHONY: release
release: test lint mypy
    git tag -a $(call next_patch_ver)
    $(MAKE) ${MODULE}/_version.py
    python setup.py check sdist upload # (legacy "upload" method)
    # twine upload dist/*  (preferred method)
    git push origin master --tags

release목표는 항상 3 버전 숫자를 증가,하지만 당신은 사용할 수 있습니다 next_minor_ver또는 next_major_ver다른 숫자를 증가 할 수 있습니다. 명령은 versionbump.py리포지토리의 루트에 체크인 된 스크립트 에 의존합니다.

versionbump.py

"""An auto-increment tool for version strings."""

import sys
import unittest

import click
from click.testing import CliRunner  # type: ignore

__version__ = '0.1'

MIN_DIGITS = 2
MAX_DIGITS = 3


@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
    """Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
    optional 'v' prefix is allowed and will be included in the output if found."""
    prefix = version[0] if version[0].isalpha() else ''
    digits = version.lower().lstrip('v').split('.')

    if len(digits) > MAX_DIGITS:
        click.secho('ERROR: Too many digits', fg='red', err=True)
        sys.exit(1)

    digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS]  # Extend total digits to max.
    digits[bump_idx] = str(int(digits[bump_idx]) + 1)  # Increment the desired digit.

    # Zero rightmost digits after bump position.
    for i in range(bump_idx + 1, MAX_DIGITS):
        digits[i] = '0'
    digits = digits[:max(MIN_DIGITS, bump_idx + 1)]  # Trim rightmost digits.
    click.echo(prefix + '.'.join(digits), nl=False)


if __name__ == '__main__':
    cli()  # pylint: disable=no-value-for-parameter

이것은에서 버전 번호를 처리하고 증가시키는 방법을 많이 들어 올립니다 git.

__init__.py

my_module/_version.py파일로 가져옵니다 my_module/__init__.py. 모듈과 함께 배포하려는 정적 설치 구성을 여기에 넣으십시오.

from ._version import __version__
__author__ = ''
__email__ = ''

setup.py

마지막 단계는 my_module모듈 에서 버전 정보를 읽는 것 입니다.

from setuptools import setup, find_packages

pkg_vars  = {}

with open("{MODULE}/_version.py") as fp:
    exec(fp.read(), pkg_vars)

setup(
    version=pkg_vars['__version__'],
    ...
    ...
)

물론이 모든 기능이 작동하려면 리포지토리에 버전 태그가 하나 이상 있어야 시작할 수 있습니다.

git tag -a v0.0.1


답변

패키지 디렉토리에 JSON 파일을 사용합니다. 이것은 Zooko의 요구 사항에 맞습니다.

내부 pkg_dir/pkg_info.json:

{"version": "0.1.0"}

내부 setup.py:

from distutils.core import setup
import json

with open('pkg_dir/pkg_info.json') as fp:
    _info = json.load(fp)

setup(
    version=_info['version'],
    ...
    )

내부 pkg_dir/__init__.py:

import json
from os.path import dirname

with open(dirname(__file__) + '/pkg_info.json') as fp:
    _info = json.load(fp)

__version__ = _info['version']

pkg_info.json저자와 같은 다른 정보도 넣었습니다 . 메타 데이터 관리를 자동화 할 수 있기 때문에 JSON을 사용하고 싶습니다.