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

import foo
print(foo.version)

我认为有一些方法可以在没有任何额外硬编码的情况下检索数据,因为minor/major字符串已经在setup.py中指定了。我发现的替代解决方案是在我的foo/__init__.py中导入__version__,然后由setup.py生成__version__.py。


当前回答

自从第一次提出这个问题以来,已经完成了许多关于统一版本和支持约定的工作。可接受的选项现在在Python打包用户指南中详细介绍。同样值得注意的是,版本号方案在Python PEP 440中是相对严格的,因此如果您的包将被发布到Cheese Shop,那么保持正常是至关重要的。

以下是版本控制选项的简短分类:

Read the file in setup.py (setuptools) and get the version. Use an external build tool (to update both __init__.py as well as source control), e.g. bump2version, changes or zest.releaser. Set the value to a __version__ global variable in a specific module. Place the value in a simple VERSION text file for both setup.py and code to read. Set the value via a setup.py release, and use importlib.metadata to pick it up at runtime. (Warning, there are pre-3.8 and post-3.8 versions.) Set the value to __version__ in sample/__init__.py and import sample in setup.py. Use setuptools_scm to extract versioning from source control so that it's the canonical reference, not code.

注意(7)可能是最现代的方法(构建元数据独立于代码,由自动化发布)。还要注意,如果setup用于包发布,那么简单的python3 setup.py——version将直接报告版本。

其他回答

重写2017 - 05

在写了13年以上的Python代码和管理各种包之后,我得出的结论是,DIY可能不是最好的方法。

我开始使用pbr包来处理包中的版本控制。如果您正在使用git作为您的SCM,这将像魔法一样适合您的工作流,节省您数周的工作时间(您将惊讶于问题的复杂程度)。

截至目前,pbr的月下载量为1200万次,达到这一水平并不需要任何肮脏的手段。这只是一件事——用非常简单的方法解决一个常见的包装问题。

PBR可以承担更多的包维护负担,而且不局限于版本控制,但它不会强迫您采用它的所有好处。

所以为了给你一个关于如何在一次提交中采用策略br的想法,看看切换包装到策略br

您可能会注意到版本根本没有存储在存储库中。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__,
    ...

如果你知道其他更好的方法,请告诉我。

经过几个小时的努力,我找到了最简单可靠的解决方案,以下是其中的几个部分:

在你的包"/mypackage"文件夹中创建一个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__ = '1.2.7'

在setup . py:

exec(open('mypackage/version.py').read())
setup(
    name='mypackage',
    version=__version__,

在主文件夹init.py中:

from .version import __version__

exec()函数在任何导入之外运行脚本,因为在导入模块之前运行setup.py。您仍然只需要在一个地方的一个文件中管理版本号,但不幸的是,它不在setup.py中。(这是缺点,但没有导入错误是优点)

在与__init__.py相同的文件夹中创建一个名为_version.txt的文件,并将version写成一行:

0.8.2

从__init__.py中的_version.txt文件中读取以下信息:

    import os 
    def get_version():
        with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), "_version.txt")) as f:
            return f.read().strip() 
    __version__ = get_version()

带有bump2版本的策略路由

这个解决方案来自本文

用例- python GUI包通过PyInstaller分发。需要显示版本信息。

这里是项目包的结构

packagex
├── packagex
│   ├── __init__.py
│   ├── main.py
│   └── _version.py
├── packagex.spec
├── LICENSE
├── README.md
├── .bumpversion.cfg
├── requirements.txt
├── setup.cfg
└── setup.py

setup.py在哪里

# setup.py
import os

import setuptools

about = {}
with open("packagex/_version.py") as f:
    exec(f.read(), about)

os.environ["PBR_VERSION"] = about["__version__"]

setuptools.setup(
    setup_requires=["pbr"],
    pbr=True,
    version=about["__version__"],
)

Packagex /_version.py包含just

__version__ = "0.0.1"

和packagex / __init__ . py

from ._version import __version__

对于.bumpversion.cfg

[bumpversion]
current_version = 0.0.1
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)(?P<build>\d+))?
serialize = 
    {major}.{minor}.{patch}-{release}{build}
    {major}.{minor}.{patch}

[bumpversion:part:release]
optional_value = prod
first_value = dev
values = 
    dev
    prod

[bumpversion:file:packagex/_version.py]

《绿箭侠》以一种有趣的方式处理它。

现在(2e5031b起)

在箭头/ __init__ . py:

__version__ = 'x.y.z'

在setup . py:

from arrow import __version__

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

之前

在箭头/ __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__'),
    # [...]
)