当前位置:  开发笔记 > 编程语言 > 正文

将版本嵌入python包的标准方法?

如何解决《将版本嵌入python包的标准方法?》经验,为你挑选了9个好方法。

是否有一种标准方法可以将版本字符串与python包关联起来,以便我可以执行以下操作?

import foo
print foo.version

我想有一些方法可以在没有任何额外硬编码的情况下检索数据,因为setup.py已经指定了次要/主要字符串.我找到的替代解决方案是import __version__在我的foo/__init__.py,然后__version__.py生成setup.py.



1> oefe..:

不直接回答你的问题,但你应该考虑命名__version__,而不是version.

这几乎是准标准.标准库中的许多模块都使用__version__,这也用于许多第三方模块,因此它是准标准的.

通常,__version__是一个字符串,但有时它也是一个浮点数或元组.

编辑:正如S.Lott所说(谢谢!),PEP 8明确地说:

版本簿记

如果您的源文件中必须包含Subversion,CVS或RCS crud,请按以下步骤操作.

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

这些行应该包含在模块的docstring之后,在任何其他代码之前,由上面和下面的空行分隔.

您还应确保版本号符合PEP 440(PEP 386,此标准的先前版本)中所述的格式.


它应该是一个字符串,并为元组版本提供__version_info__.
对.似乎这些PEP相互矛盾.好吧,PEP 8说"if"和"crud",所以它并不支持使用VCS关键字扩展.此外,如果您切换到不同的VCS,您将丢失修订信息.因此,我建议使用嵌入在单个源文件中的PEP 386/440兼容版本信息,至少对于较大的项目.
你会把__version__放在哪里.考虑到这是可接受的版本,我很乐意在这里看到更多信息.

2> Zooko..:

我使用单个_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__

这是我多年来一直使用的这种技术的一个例子.

该示例中的代码有点复杂,但我在本评论中写的简化示例应该是一个完整的实现.

以下是导入版本的示例代码.

如果您发现此方法有任何问题,请告诉我们.


能否请您描述激发#3的问题?Glyph表示它与"setuptools喜欢在你的setup.py运行时假装你的代码不在系统中的任何地方"有关,但细节将有助于说服我和其他人.
类似的方法是从setup.py中的`execfile("myniftyapp/_version.py")`,而不是试图手动解析版本代码.建议在http://stackoverflow.com/a/2073599/647002 - 讨论中也可能有所帮助.
@Iva现在,该工具应该以什么顺序执行此操作?它不能(在今天的setuptools/pip/virtualenv系统中)甚至知道deps _are_是什么,直到它评估你的`setup.py`.此外,如果它尝试完全深度优先并在执行此操作之前完成所有deps,如果存在圆形deps,它将会卡住.但是如果它在安装依赖项之前尝试构建这个包,那么如果你从`setup.py`导入你的包,它就不一定能够导入它的deps或它的deps的正确版本.
你能从"setup.py"写*文件"version.py"而不是解析吗?这似乎更简单.
Jonathan Hartley:我同意你的"setup.py"编写"version.py"文件而不是解析它会稍微简单一点,但是当你编辑setup.py时会打开一个不一致的窗口.拥有新版本但尚未执行setup.py来更新version.py文件.将规范版本放在一个小的单独文件中的另一个原因是它使得其他*工具(例如读取修订控制状态的工具)可以轻松编写版本文件.
应该是`from ._version import __version__`从本地项目中的`_version.py`中引入`_version`模块而不是在你的环境中漂浮的另一个模块.

3> sorin..:

重写的美国

经过十多年编写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__,
    ...

如果您知道其他方法似乎更好,请告诉我.


呃,不.execfile()在Python 3中不存在,因此最好使用exec(open().read()).
为什么不在setup.py中来自.version导入__version__`?
@Aprillion因为当`setup.py`正在运行时没有加载包 - 试试看,你会得到一个错误(或者至少,我做了:-))
毫无疑问,**pbr**是一个很好的工具,但你没有解决这个问题.如何通过**bpr**访问当前版本或已安装的软件包.
指向pbr的链接导致网关错误。

4> Oddthinking..:

根据延迟的PEP 396(模块版本号),有一种建议的方法可以做到这一点.它有理由描述了模块的(公认的可选)标准.这是一个片段:

3)当模块(或包)包含版本号时,该__version__属性应该可以使用该版本.

4)对于存在于命名空间包内的模块,模块应该包含该__version__属性.命名空间包本身不应该包含自己的__version__属性.

5)__version__属性的值应该是一个字符串.


该PEP不被接受/标准化,但是被推迟(由于缺乏兴趣).因此,说明"*有一种标准方式*"是有点误导性的.
编辑注意它不是标准.现在我感到很尴尬,因为我已经提出要求他们遵循这个"标准"的项目的功能请求.

5> JAB..:

虽然这可能为时已晚,但有一个比上一个答案稍微简单的替代方案:

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

(使用str().将版本号的自动递增部分转换为字符串相当简单.)

当然,从我所看到的情况来看,人们在使用时倾向于使用类似于前面提到的版本的东西__version_info__,因此将其存储为整数元组; 但是,我没有完全看到这样做的重点,因为我怀疑在某些情况下你会为了除了好奇心或自动增量之外的任何目的在版本号的部分上执行数学运算,例如加法和减法(即便如此,int()并且str()可以相当容易地使用).(另一方面,有可能其他人的代码期望数字元组而不是字符串元组因此失败.)

当然,这是我自己的观点,我很乐意让其他人投入使用数字元组.


正如shezi提醒我的那样,(词汇)数字串的比较不一定与直接数字比较具有相同的结果; 领先的零将需要提供.因此,最后,存储__version_info__(或任何它将被称为)作为整数值的元组将允许更有效的版本比较.


我这样做很好,但略有调整,以解决整数..`__version_info__ =(0,1,0)= __version__ ''.加入(图(STR,__version_info __))`
很好(+1),但你不喜欢数字而不是字符串?例如`__version_info__ =(1,2,3)`
我认为这种方法的问题在于在哪里放置代码行来声明\ _\_ _ version__.如果它们在setup.py中,那么您的程序无法从其包中导入它们.也许这对你来说不是问题,在这种情况下,很好.如果它们进入你的程序,那么你的setup.py可以导入它们,但它不应该,因为setup.py在安装期间运行时尚未安装程序的依赖项(setup.py用于确定依赖项是什么) .)因此Zooko的答案是:手动解析产品源文件中的值,而不导入产品包
当版本号超过9时,字符串的比较会变得危险,因为例如'10'<'2'.

6> Andy Lee..:

我在包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,因为我可以自动管理元数据.



7> cmcginty..:

这里的许多解决方案都忽略了git版本标签,这仍然意味着你必须在多个地方跟踪版本(糟糕).我实现了以下目标:

gitrepo中的标记派生所有python版本引用

使用不带任何输入的单个命令自动执行git tag/ pushsetup.py upload步骤.

这个怎么运作:

    make release命令中,找到git repo中的最后一个标记版本并递增.标签被推回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目标总是递增第三版数字,但可以使用next_minor_vernext_major_ver递增其他数字.命令依赖于versionbump.py检入repo根目录的脚本

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



8> James Antill..:

另外值得注意的是,这也是__version__一个半标准.在python中所以__version_info__它是一个元组,在简单的情况下你可以做类似的事情:

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

...你可以__version__从文件或其他任何东西中获取字符串.


更容易在另一个方向上进行映射(`__version_info__ =(1,2,3)`和`__version__ ='.'.join(map(str,__ version_info __))`).
好吧,它与sys.version到sys.version_info的映射相同.所以:http://docs.python.org/library/sys.html#sys.version_info
@EOL - `__version__ ='.'.join(str(i)for i in __version_info __)` - 稍长但更pythonic.

9> dF...:

似乎没有一种标准方法可以在python包中嵌入版本字符串.我见过的大多数软件包都使用了解决方案的一些变体,即eitner

    将版本嵌入setup.pysetup.py生成一个模块(例如version.py),其中仅包含由您的软件包导入的版本信息,或者

    反过来:将版本信息放入包中,然后导入以设置版本 setup.py

推荐阅读
虎仔球妈_459
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有