将版本嵌入到 python 包中的标准方法?
题
是否有一种标准方法将版本字符串与 python 包关联起来,以便我可以执行以下操作?
import foo
print foo.version
我想有某种方法可以检索该数据而无需任何额外的硬编码,因为次要/主要字符串是在 setup.py
已经。我发现的替代解决方案是 import __version__
在我的 foo/__init__.py
然后有 __version__.py
产生于 setup.py
.
解决方案
不是直接回答你的问题,但你应该考虑命名它 __version__
, , 不是 version
.
这几乎是一个准标准。标准库中的许多模块都使用 __version__
, ,这也用于 地段 第三方模块,所以它是准标准。
通常, __version__
是一个字符串,但有时它也是一个浮点数或元组。
编辑:正如 S.Lott 提到的(谢谢!), PEP 8 明确地说:
版本记账
如果您必须在源文件中有颠覆,简历或RCS CRUD,请按照以下方式进行操作。
__version__ = "$Revision: 63990 $" # $Source$
这些行应在模块的DocString之后,在任何其他代码之前,在上方和下方的空白行中分开。
其他提示
我使用一个单一的 _version.py
文件作为"一旦cannonical放"储存的版本信息:
它提供了一个
__version__
属性。它提供了标准元数据的版本。因此,它将通过检测
pkg_resources
或其他工具,分析包元数据(卵信息和/或包信息,PEP0345).它不能进您的包裹(或任何其他)当建立您的包裹,这可能会造成问题,在一些情况。(参见下面的意见,关于什么的问题,这可能会导致。)
只有一个地方的版本号码写下来,所以只有一个地方来改变它当版本数量的变化,并有较少机会的不一致的版本。
这里是它是如何工作:"一个规范的地位"存储的版本号是a.py文件,名为"_version.py"这是在你的Python包,例如在 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 通过这串的价值"版本"的论点来 setup()
, ,因此满足特征2.
为了满足特征的1,你可以有你的包(在运行时,不在设置时间!) 进口的_version文件 myniftyapp/__init__.py
是这样的:
from _version import __version__
这里是 这种技术的示例 我已经使用多年。
代码在那个例子是一个比较复杂,但简化的例子,我写成这一意见应该是一个完整的执行情况。
这里是 例代码版本进口.
如果你看到任何错误使用这种方法,请让我知道。
重写2017-05
在十多年的编写Python代码和管理我得出的结论认为DIY是也许不是最好的方法不同软件包。
我开始使用pbr
包带版本在我的包处理。如果您使用的git为您的供应链管理,这将适用于您的工作流程就像魔术,节约工作的周(你会惊讶关于这个问题的复杂程度)。
直至今日 PBR的排名是#11最常用的Python包并达到这个水平没”吨包括任何脏招数:只有一个:在一个非常简单的方式固定一个共同的包装问题
pbr
可以做更多的包维护负担,并不局限于版本,但它不强迫你通过其所有的好处。
所以,给你关于它看起来如何采取PBR在一个犯看看 swiching的想法包装到PBR
也许你会观察到的版本没有存储在所有在库中。 PBR确实从GIT中分支和标签检测到它。
没有必要担心,当你没有一个Git仓库,因为PBR不“编译”,当你打包或安装的应用程序缓存的版本,所以对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__
时,因此储存它作为整数的元组;不过,我不太明白这一点这样做,因为我怀疑有些情况下你会进行数学运算,如加法和减法上除了好奇心或自动增量(和版本号为任何目的的部分即使在当时的情况下, 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
版本的标记,这仍然意味着你必须跟踪版本在多个地方(糟糕).我走近这与以下目标:
- 获得所有python版本的参考资料的一个标签
git
repo - 自动化
git tag
/push
和setup.py upload
步骤,用一个单一命令,采取没有投入。
它是如何工作:
从一个
make release
命令,标记的最后版本的混帐回购协议是找到和递增。标记是推后到origin
.的
Makefile
商店中的版本src/_version.py
在那里它会被读取setup.py
并且还包括在释放。 不检查_version.py
入来源的控制!setup.py
命令读的新版的字符串package.__version__
.
详细信息:
Makefile
# 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
似乎没有一个标准的方法嵌入一个版本串在一python包。大多数软件包,我已经看到使用某些变的解决方案,即eitner
嵌入的版本
setup.py
和有setup.py
产生一个模块(例如version.py
)仅含有版本的信息,这就是进口的你的包装,或相反:把版本的信息在包本身,并导入 那 设置版本中的
setup.py
另外值得注意的是,以及__version__
是半STD。在python所以是__version_info__
这是一个元组,在简单的情况下,你可以这样做是这样的:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
...你可以从一个文件,或任何获得__version__
字符串。
我还看到了另一种风格:
>>> django.VERSION
(1, 1, 0, 'final', 0)
箭头处理它以有趣的方式。
在arrow/__init__.py
:
__version__ = '0.8.0'
VERSION = __version__
在setup.py
:
def grep(attrname):
pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
strval, = re.findall(pattern, file_text)
return strval
setup(
name='arrow',
version=grep('__version__'),
# [...]
)
有关它的价值,如果你使用NumPy的的distutils,numpy.distutils.misc_util.Configuration
拥有的 make_svn_version_py()
方法嵌入了修订号内package.__svn_version__
在可变version
。
- 使用
version.py
文件只与__version__ = <VERSION>
param在文件。在setup.py
文件导入__version__
param,并把它的价值setup.py
文件这样的:version=__version__
- 另一种方式是使用只是一个
setup.py
文件version=<CURRENT_VERSION>
-该CURRENT_VERSION是硬编码.
因为我们不要手动改变该版本在文件的每一次我们创建了一个新标记(准备释放一种新包装版本),我们可以使用以下..
我强烈推荐 bumpversion 包。我一直在使用它多年来碰一个版本。
开始加入 version=<VERSION>
你的 setup.py
文件如果你不用它了。
你应该用一个短脚本,这样每次你碰一个版本:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
然后添加一个文件中每个仓库被称为: .bumpversion.cfg
:
[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]
注:
- 你可以使用
__version__
参数下version.py
文件建议在其他员额和更新bumpversion文件是这样的:[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- 你的 必须
git commit
或git reset
一切都在你的回购,否则你会得到一个肮脏的仓库中的错误。 - 请确保您的虚拟环境包括包装的bumpversion,没有它,它将不会的工作。
如果您使用CVS(或RCS),并希望迅速解决,则可以使用:
__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])
(当然,修订号将由CVS取代为您服务。)
这给你一个打印的版本,并且您可以用它来检查你正在导入的模块至少具有预期的版本的版本信息:
import my_module
assert my_module.__version_info__ >= (1, 1)