バージョン文字列をpythonパッケージに関連付ける標準的な方法はありますか?
import foo
print foo.version
マイナー/メジャー文字列は既にsetup.py
で指定されているため、追加のハードコーディングなしでそのデータを取得する方法があると想像します。私が見つけた代替ソリューションは、import __version__
にfoo/__init__.py
を含めてから、__version__.py
によってsetup.py
を生成することでした。
あなたの質問に対する直接的な答えではありませんが、version
ではなく、__version__
と命名することを検討すべきです。
これはほぼ準標準です。標準ライブラリの多くのモジュールは__version__
を使用し、これはサードパーティモジュールの lots でも使用されているため、準標準です。
通常、__version__
は文字列ですが、フロートまたはタプルでもある場合があります。
編集:S.Lott(Thank you!)が述べたように、 PEP 8 は明示的に言っています:
バージョン簿記
ソースファイルにSubversion、CVS、またはRCS crudが必要な場合は、次のようにします。
__version__ = "$Revision: 63990 $" # $Source$
これらの行は、モジュールのdocstringの後、他のコードの前に、上下の空白行で区切って含める必要があります。
また、バージョン番号が PEP 44 ( PEP 386 この標準の以前のバージョン)で説明されている形式に準拠していることを確認する必要があります。
単一の_version.py
ファイルを "once cannonical place"として使用して、バージョン情報を保存します。
__version__
属性を提供します。
標準のメタデータバージョンを提供します。したがって、pkg_resources
またはパッケージメタデータを解析する他のツール(Egg-INFOおよび/またはPKG-INFO、PEP 0345)によって検出されます。
パッケージをビルドするときにパッケージ(またはその他)をインポートしないため、状況によっては問題が発生する可能性があります。 (これが引き起こす可能性のある問題については、以下のコメントを参照してください。)
バージョン番号が書き留められる場所は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__
これが この手法の例 私が長年使ってきたものです。
その例のコードはもう少し複雑ですが、このコメントに書いた簡単な例は完全な実装です。
バージョンのインポートのコード例 です。
このアプローチに何か問題がある場合は、お知らせください。
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__,
...
より良いと思われる別のアプローチを知っている場合はお知らせください。
遅延 PEP 396(モジュールバージョン番号) に従って、これを行う方法が提案されています。それは、従うべきモジュールのための(確かにオプションの)標準を理論的根拠とともに説明します。これがスニペットです。
3)モジュール(またはパッケージ)にバージョン番号が含まれている場合、バージョンは
__version__
属性で使用できる必要があります。4)名前空間パッケージ内に存在するモジュールの場合、モジュールには
__version__
属性を含める必要があります。名前空間パッケージ自体には、独自の__version__
属性を含めるべきではありません。5)
__version__
属性の値は文字列である必要があります。
これはおそらく遅すぎるかもしれませんが、前の回答のもう少しシンプルな代替案があります。
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(そして、str()
を使用して、バージョン番号の自動インクリメント部分を文字列に変換するのは非常に簡単です。)
もちろん、私が見たことから、人々は__version_info__
を使用するときに前述のバージョンのようなものを使用する傾向があり、そのため、Tupleのintとして保存します。しかし、好奇心や自動インクリメント以外の目的でバージョン番号の一部に対して加算や減算などの数学演算を実行する状況があるとは思えないため、そうすることには意味がありません。 int()
とstr()
はかなり簡単に使用できます)。 (一方で、他の誰かのコードが文字列のタプルではなく数値のタプルを期待して失敗する可能性があります。)
もちろんこれは私自身の見解であり、数値タプルの使用に関する他の人の入力を喜んで望みます。
Sheziが思い出したように、数字の文字列の(字句的)比較は、必ずしも直接の数値比較と同じ結果になるわけではありません。そのためには、先行ゼロが必要です。そのため、最終的に、__version_info__
(またはそれが呼び出されるもの)を整数値のタプルとして保存すると、より効率的なバージョン比較が可能になります。
パッケージ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を使用したいのは、メタデータの管理を自動化できるからです。
ここでのこれらのソリューションの多くは、git
バージョンタグを無視しています。つまり、複数の場所でバージョンを追跡する必要があります(悪い)。私は次の目標でこれに取り組みました:
git
リポジトリ内のタグからすべてのpythonバージョン参照を取得しますgit tag
/Push
およびsetup.py upload
ステップを、入力を受け取らない単一のコマンドで自動化します。make release
コマンドから、gitリポジトリ内の最後のタグ付きバージョンが検出され、インクリメントされます。タグはOrigin
にプッシュバックされます。
Makefile
はsrc/_version.py
にバージョンを保存し、setup.py
によって読み取られ、リリースにも含まれます。 _version.py
をソース管理にチェックインしないでください!
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
スクリプトに依存します
"""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
からバージョン番号を処理およびインクリメントする方法の面倒な作業を行います。
my_module/_version.py
ファイルはmy_module/__init__.py
にインポートされます。モジュールと共に配布する静的インストール設定をここに配置します。
from ._version import __version__
__author__ = ''
__email__ = ''
最後のステップは、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
また、__version__
が準標準であることも注目に値します。 pythonには、タプルである__version_info__
もあります。単純な場合は、次のようにできます。
__version__ = '1.2.3'
__version_info__ = Tuple([ int(num) for num in __version__.split('.')])
...また、ファイルから__version__
文字列などを取得できます。
pythonパッケージにバージョン文字列を埋め込む標準的な方法はないようです。私が見たほとんどのパッケージはあなたのソリューションのいくつかのバリアント、すなわちeitnerを使用しています
バージョンをsetup.py
に埋め込み、setup.py
に、パッケージによってインポートされたバージョン情報のみを含むモジュール(たとえば、version.py
)を生成させる、または
逆:パッケージ自体にバージョン情報を入れ、thatをインポートしてsetup.py
にバージョンを設定します
別のスタイルも見ました:
>>> Django.VERSION
(1, 1, 0, 'final', 0)
矢印 は興味深い方法で処理します。
今( 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__'),
# [...]
)
NumPy distutilsを使用している場合、numpy.distutils.misc_util.Configuration
には、変数version
のpackage.__svn_version__
内にリビジョン番号を埋め込む make_svn_version_py()
メソッドがあります。