是否有一种标准方法可以将版本字符串与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明确地说:
版本簿记
如果您的源文件中必须包含Subversion,CVS或RCS crud,请按以下步骤操作.
__version__ = "$Revision: 63990 $" # $Source$这些行应该包含在模块的docstring之后,在任何其他代码之前,由上面和下面的空行分隔.
您还应确保版本号符合PEP 440(PEP 386,此标准的先前版本)中所述的格式.
我使用单个_version.py
文件作为存储版本信息的"one cannonical place":
它提供了一个__version__
属性.
它提供标准元数据版本.因此,它将通过pkg_resources
解析包元数据的其他工具(EGG-INFO和/或PKG-INFO,PEP 0345)进行检测.
在构建软件包时,它不会导入您的软件包(或其他任何东西),这可能会在某些情况下导致问题.(请参阅下面的评论,了解这可能导致的问题.)
版本号只写下一个地方,因此当版本号更改时只有一个地方可以更改它,并且版本不一致的可能性更小.
以下是它的工作原理:存储版本号的"一个规范位置"是一个.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将该字符串作为"version"参数的值传递给setup()
,从而满足功能2.
为了满足功能1,你可以拥有你的包(在运行时,而不是在设置时!)从myniftyapp/__init__.py
这里导入_version文件:
from _version import __version__
这是我多年来一直使用的这种技术的一个例子.
该示例中的代码有点复杂,但我在本评论中写的简化示例应该是一个完整的实现.
以下是导入版本的示例代码.
如果您发现此方法有任何问题,请告诉我们.
经过十多年编写Python代码和管理各种软件包后,我得出结论,DIY可能不是最好的方法.
我开始使用pbr
包来处理我的包中的版本控制.如果您使用git作为您的SCM,这将适合您的工作流程,如魔术,节省您的工作周(您会惊讶于问题的复杂程度).
截至今天,pbr排名第11位最常用的python包,达到这个级别并没有包含任何肮脏的技巧:只有一个:以一种非常简单的方式解决常见的包装问题.
pbr
可以做更多的包维护负担,不仅限于版本控制,但它不会强迫你采用它的所有好处.
因此,为了让您了解它在一次提交中采用pbr的样子,请查看pbr的swiching包装
可能你会发现版本根本没有存储在存储库中.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
版本标签,这仍然意味着你必须在多个地方跟踪版本(糟糕).我实现了以下目标:
从git
repo中的标记派生所有python版本引用
使用不带任何输入的单个命令自动执行git tag
/ push
和setup.py upload
步骤.
从make release
命令中,找到git repo中的最后一个标记版本并递增.标签被推回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
目标总是递增第三版数字,但可以使用next_minor_ver
或next_major_ver
递增其他数字.命令依赖于versionbump.py
检入repo根目录的脚本
"""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
.
该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__'],
...
...
)
当然,要使所有这些工作,你必须在你的仓库中至少有一个版本标签才能开始.
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
),其中仅包含由您的软件包导入的版本信息,或者
反过来:将版本信息放入包中,然后导入它以设置版本 setup.py