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

import foo
print(foo.version)

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


当前回答

我在这里描述了一种标准的现代方法,它依赖于setuptools_scm。 在过去的几年中,这种模式已经成功地应用于许多已发布的包,因此我强烈推荐它。

注意,您不需要getversion包来实现这个模式。getversion文档恰好包含这个技巧。

其他回答

使用setuptools和pbr

没有管理版本的标准方法,但是管理包的标准方法是setuptools。

总的来说,我发现最好的版本管理解决方案是使用setuptools和pbr扩展。这是我现在管理版本的标准方式。

对于简单的项目来说,为完整的打包设置项目可能有些过分,但是如果您需要管理版本,那么您可能处于设置所有内容的正确级别。这样做还可以使您的包在PyPi上发布,以便每个人都可以下载并与Pip一起使用它。

PBR moves most metadata out of the setup.py tools and into a setup.cfg file that is then used as a source for most metadata, which can include version. This allows the metadata to be packaged into an executable using something like pyinstaller if needed (if so, you will probably need this info), and separates the metadata from the other package management/setup scripts. You can directly update the version string in setup.cfg manually, and it will be pulled into the *.egg-info folder when building your package releases. Your scripts can then access the version from the metadata using various methods (these processes are outlined in sections below).

当在VCS/SCM中使用Git时,这种设置甚至更好,因为它将从Git中引入大量元数据,这样你的回购就可以成为一些元数据的主要真实来源,包括版本、作者、更改日志等。对于version,它将基于repo中的git标记为当前提交创建一个版本字符串。

PyPA -用SetupTools打包Python包-教程 PBR最新的构建使用文档-如何用元数据设置一个8行的setup.py和setup.cfg文件。

由于PBR会直接从你的git repo中提取版本、作者、更新日志和其他信息,所以setup.cfg中的一些元数据可以被省略,并在为你的包创建发行版时自动生成(使用setup.py)。



实时获取当前版本

Setuptools将使用setup.py实时提取最新信息:

python setup.py --version

这将从setup.cfg文件或git repo中提取最新版本,基于最近的提交和repo中存在的标记。但是,该命令不会更新发行版中的版本。



更新版本元数据

当你使用setup.py(例如py setup.py sdist)创建发行版时,所有当前信息都会被提取并存储在发行版中。这实际上是运行setup.py——version命令,然后将版本信息存储到包中。Egg-info文件夹中的一组文件,用于存储分发元数据。

Note on process to update version meta-data: If you are not using pbr to pull version data from git, then just update your setup.cfg directly with new version info (easy enough, but make sure this is a standard part of your release process). If you are using git, and you don't need to create a source or binary distribution (using python setup.py sdist or one of the python setup.py bdist_xxx commands) the simplest way to update the git repo info into your <mypackage>.egg-info metadata folder is to just run the python setup.py install command. This will run all the PBR functions related to pulling metadata from the git repo and update your local .egg-info folder, install script executables for any entry-points you have defined, and other functions you can see from the output when you run this command. Note that the .egg-info folder is generally excluded from being stored in the git repo itself in standard Python .gitignore files (such as from Gitignore.IO), as it can be generated from your source. If it is excluded, make sure you have a standard "release process" to get the metadata updated locally before release, and any package you upload to PyPi.org or otherwise distribute must include this data to have the correct version. If you want the Git repo to contain this info, you can exclude specific files from being ignored (i.e. add !*.egg-info/PKG_INFO to .gitignore)



从脚本访问版本

您可以在包本身的Python脚本中从当前构建中访问元数据。以版本为例,到目前为止,我发现有几种方法可以做到这一点:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib.metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

你可以把其中一个直接放在__init__.py中,让包提取版本信息,如下所示,类似于其他一些答案:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version

我使用一个单独的_version.py文件作为“曾经正确的位置”来存储版本信息:

It provides a __version__ attribute. It provides the standard metadata version. Therefore it will be detected by pkg_resources or other tools that parse the package metadata (EGG-INFO and/or PKG-INFO, PEP 0345). It doesn't import your package (or anything else) when building your package, which can cause problems in some situations. (See the comments below about what problems this can cause.) There is only one place that the version number is written down, so there is only one place to change it when the version number changes, and there is less chance of inconsistent versions.

它是这样工作的:存储版本号的“一个规范的地方”是一个.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__

这是我多年来一直在使用的一个技巧的例子。

这个例子中的代码有点复杂,但是我写在这个注释中的简化的例子应该是一个完整的实现。

下面是导入版本的示例代码。

如果你发现这种方法有任何问题,请告诉我。

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

这几乎是一个准标准。标准库中的许多模块使用__version__,这也在许多第三方模块中使用,因此它是准标准的。

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

正如S.Lott所提到的(谢谢!),PEP 8明确表示:

模块级别Dunder名称 模块级别的“dunders”(即名称有两个前导和两个后导 下划线),例如__all__, __author__, __version__等。 应该放在模块文档字符串之后,但在任何导入之前 除了__future__导入之外的语句。

您还应该确保版本号符合PEP 440 (PEP 386是此标准的以前版本)中描述的格式。

只使用VERSION .py文件,文件中有__version__ = <VERSION>参数。在setup.py文件中导入__version__参数,并将其值放在setup.py文件中,如下所示: version = __version__ 另一种方法是只使用version=<CURRENT_VERSION>的setup.py文件- CURRENT_VERSION是硬编码的。

因为我们不希望每次创建一个新标记(准备发布一个新的包版本)时都手动更改文件中的版本,所以我们可以使用以下..

我强烈推荐凸版包。多年来我一直在用它来撞一个版本。

首先在setup.py文件中添加version=< version >,如果你还没有的话。

你应该在每次碰到一个版本时使用这样一个简短的脚本:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

然后为每个repo添加一个名为.bumpversion.cfg的文件:

[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]

注意:

你可以像在其他文章中建议的那样,在version.py文件下使用__version__参数,并像这样更新bumpversion文件: [bumpversion:文件:< RELATIVE_PATH_TO_VERSION_FILE >) 你必须git提交或git重置你的回购中的所有东西,否则你会得到一个肮脏的回购错误。 确保你的虚拟环境包含了bump版本的包,没有它它将无法工作。

我更喜欢从安装环境中读取包版本。 这是我的src/foo/_version.py:

from pkg_resources import get_distribution                                        
                                                                                  
__version__ = get_distribution('foo').version

确保foo总是已经安装,这就是为什么需要src/ layer来防止在没有安装的情况下导入foo。

在setup.py中,我使用setuptools-scm自动生成版本。


2022.7.5更新:

还有另一种方法,这是我现在最喜欢的。使用setuptools-scm生成_version.py文件。

setup(
    ...
    use_scm_version={
        'write_to':
        'src/foo/_version.py',
        'write_to_template':
        '"""Generated version file."""\n'
        '__version__ = "{version}"\n',
    },
)