web-dev-qa-db-ja.com

pythonパッケージにバージョンを埋め込む標準的な方法は?

バージョン文字列をpythonパッケージに関連付ける標準的な方法はありますか?

import foo
print foo.version

マイナー/メジャー文字列は既にsetup.pyで指定されているため、追加のハードコーディングなしでそのデータを取得する方法があると想像します。私が見つけた代替ソリューションは、import __version__foo/__init__.pyを含めてから、__version__.pyによってsetup.pyを生成することでした。

228
Dimitri Tcaciuc

あなたの質問に対する直接的な答えではありませんが、versionではなく、__version__と命名することを検討すべきです。

これはほぼ準標準です。標準ライブラリの多くのモジュールは__version__を使用し、これはサードパーティモジュールの lots でも使用されているため、準標準です。

通常、__version__は文字列ですが、フロートまたはタプルでもある場合があります。

編集:S.Lott(Thank you!)が述べたように、 PEP 8 は明示的に言っています:

バージョン簿記

ソースファイルにSubversion、CVS、またはRCS crudが必要な場合は、次のようにします。

    __version__ = "$Revision: 63990 $"
    # $Source$

これらの行は、モジュールのdocstringの後、他のコードの前に、上下の空白行で区切って含める必要があります。

また、バージョン番号が PEP 44PEP 386 この標準の以前のバージョン)で説明されている形式に準拠していることを確認する必要があります。

115
oefe

単一の_version.pyファイルを "once cannonical place"として使用して、バージョン情報を保存します。

  1. __version__属性を提供します。

  2. 標準のメタデータバージョンを提供します。したがって、pkg_resourcesまたはパッケージメタデータを解析する他のツール(Egg-INFOおよび/またはPKG-INFO、PEP 0345)によって検出されます。

  3. パッケージをビルドするときにパッケージ(またはその他)をインポートしないため、状況によっては問題が発生する可能性があります。 (これが引き起こす可能性のある問題については、以下のコメントを参照してください。)

  4. バージョン番号が書き留められる場所は1つだけなので、バージョン番号が変更されたときに変更する場所は1つだけであり、バージョンの不整合が発生する可能性は低くなります。

仕組みは次のとおりです。バージョン番号を格納する「1つの正規の場所」は、Pythonパッケージ(myniftyapp/_version.pyなど)にある "_version.py"という名前の.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を満たすために、次のようにmyniftyapp/__init__.pyから_versionファイルをインポートすることができます(セットアップ時ではなく、実行時)。

from _version import __version__

これが この手法の例 私が長年使ってきたものです。

その例のコードはもう少し複雑ですが、このコメントに書いた簡単な例は完全な実装です。

バージョンのインポートのコード例 です。

このアプローチに何か問題がある場合は、お知らせください。

101
Zooko

書き換えられた2017-05

10年以上Pythonコードを記述し、さまざまなパッケージを管理した後、DIYはおそらく最良のアプローチではないという結論に達しました。

パッケージのバージョン管理を扱うために、pbrパッケージを使い始めました。 SCMとしてgitを使用している場合、これは魔法のようなワークフローに適合し、数週間の作業を節約します(問題がどれほど複雑になるか驚くでしょう)。

今日の時点で pbrは最もよく使用されるpythonパッケージのランク#11 で、このレベルに到達するのに汚いトリックは含まれていませんでした。仕方。

pbrは、パッケージメンテナンスの負担を増やすことができ、バージョン管理に限定されませんが、すべての利点を強制的に採用することはありません。

したがって、1回のコミットでpbrを採用する方法についてのアイデアを得るには、外観を確認してください パッケージングをpbrに切り替える

おそらく、バージョンがリポジトリにまったく保存されていないことに気付くでしょう。 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__,
    ...

より良いと思われる別のアプローチを知っている場合はお知らせください。

93
sorin

遅延 PEP 396(モジュールバージョン番号) に従って、これを行う方法が提案されています。それは、従うべきモジュールのための(確かにオプションの)標準を理論的根拠とともに説明します。これがスニペットです。

3)モジュール(またはパッケージ)にバージョン番号が含まれている場合、バージョンは__version__属性で使用できる必要があります。

4)名前空間パッケージ内に存在するモジュールの場合、モジュールには__version__属性を含める必要があります。名前空間パッケージ自体には、独自の__version__属性を含めるべきではありません。

5)__version__属性の値は文字列である必要があります。

26
Oddthinking

これはおそらく遅すぎるかもしれませんが、前の回答のも​​う少しシンプルな代替案があります。

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

(そして、str()を使用して、バージョン番号の自動インクリメント部分を文字列に変換するのは非常に簡単です。)

もちろん、私が見たことから、人々は__version_info__を使用するときに前述のバージョンのようなものを使用する傾向があり、そのため、Tupleのintとして保存します。しかし、好奇心や自動インクリメント以外の目的でバージョン番号の一部に対して加算や減算などの数学演算を実行する状況があるとは思えないため、そうすることには意味がありません。 int()str()はかなり簡単に使用できます)。 (一方で、他の誰かのコードが文字列のタプルではなく数値のタプルを期待して失敗する可能性があります。)

もちろんこれは私自身の見解であり、数値タプルの使用に関する他の人の入力を喜んで望みます。


Sheziが思い出したように、数字の文字列の(字句的)比較は、必ずしも直接の数値比較と同じ結果になるわけではありません。そのためには、先行ゼロが必要です。そのため、最終的に、__version_info__(またはそれが呼び出されるもの)を整数値のタプルとして保存すると、より効率的なバージョン比較が可能になります。

22
JAB

パッケージdirで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を使用したいのは、メタデータの管理を自動化できるからです。

11
Andy Lee

ここでのこれらのソリューションの多くは、gitバージョンタグを無視しています。つまり、複数の場所でバージョンを追跡する必要があります(悪い)。私は次の目標でこれに取り組みました:

  • gitリポジトリ内のタグからすべてのpythonバージョン参照を取得します
  • git tag/Pushおよびsetup.py uploadステップを、入力を受け取らない単一のコマンドで自動化します。

使い方:

  1. make releaseコマンドから、gitリポジトリ内の最後のタグ付きバージョンが検出され、インクリメントされます。タグはOriginにプッシュバックされます。

  2. Makefilesrc/_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 __== '__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__'],
    ...
    ...
)

もちろん、これがすべて機能するためには、開始するためにリポジトリに少なくとも1つのバージョンタグが必要です。

git tag -a v0.0.1
10
cmcginty

また、__version__が準標準であることも注目に値します。 pythonには、タプルである__version_info__もあります。単純な場合は、次のようにできます。

__version__ = '1.2.3'
__version_info__ = Tuple([ int(num) for num in __version__.split('.')])

...また、ファイルから__version__文字列などを取得できます。

6
James Antill

pythonパッケージにバージョン文字列を埋め込む標準的な方法はないようです。私が見たほとんどのパッケージはあなたのソリューションのいくつかのバリアント、すなわちeitnerを使用しています

  1. バージョンをsetup.pyに埋め込み、setup.pyに、パッケージによってインポートされたバージョン情報のみを含むモジュール(たとえば、version.py)を生成させる、または

  2. 逆:パッケージ自体にバージョン情報を入れ、thatをインポートしてsetup.pyにバージョンを設定します

6
dF.

別のスタイルも見ました:

>>> Django.VERSION
(1, 1, 0, 'final', 0)
4
L1ker

矢印 は興味深い方法で処理します。

今( 2e5031bから

arrow/__init__.py ::

__version__ = 'x.y.z'

setup.py ::

from arrow import __version__

setup(
    name='arrow',
    version=__version__,
    # [...]
)

Before

arrow/__init__.py ::

__version__ = 'x.y.z'
VERSION = __version__

setup.py ::

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

file_text = read(fpath('arrow/__init__.py'))

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)
2
Anto

NumPy distutilsを使用している場合、numpy.distutils.misc_util.Configurationには、変数versionpackage.__svn_version__内にリビジョン番号を埋め込む make_svn_version_py() メソッドがあります。

0
Matt