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

发布于 2024-07-13 00:02:04 字数 286 浏览 17 评论 0 原文

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

import foo
print(foo.version)

我想有某种方法可以检索该数据而无需任何额外的硬编码,因为次要/主要字符串已在 setup.py 中指定。 我发现的替代解决方案是在我的 foo/__init__.py 中导入 __version__ ,然后由 setup 生成 __version__.py .py。

Is there a standard way to associate version string with a Python package in such way that I could do the following?

import foo
print(foo.version)

I would imagine there's some way to retrieve that data without any extra hardcoding, since minor/major strings are specified in setup.py already. Alternative solution that I found was to have import __version__ in my foo/__init__.py and then have __version__.py generated by setup.py.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(25

清旖 2024-07-20 00:02:05

对于其他一些答案,有一个稍微简单的替代方案:(

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

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

当然,从什么我发现,人们在使用 __version_info__ 时倾向于使用类似前面提到的版本,并将其存储为整数元组; 但是,我不太明白这样做的意义,因为我怀疑在某些情况下,除了出于好奇或自动增量之外,您还会出于任何目的对版本号的部分执行加法和减法等数学运算(即使如此, int()str() 可以相当容易地使用)。 (另一方面,其他人的代码可能需要数字元组而不是字符串元组,从而失败。)

当然,这是我自己的观点,我很乐意接受其他人关于使用数字元组的输入元组。


正如 shezi 提醒我的那样,数字字符串的(词法)比较不一定与直接数字比较具有相同的结果; 为此需要前导零。 因此最终,将 __version_info__ (或任何名称)存储为整数值元组将允许更有效的版本比较。

There is a slightly simpler alternative to some of the other answers:

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

(And it would be fairly simple to convert auto-incrementing portions of version numbers to a string using str().)

Of course, from what I've seen, people tend to use something like the previously-mentioned version when using __version_info__, and as such store it as a tuple of ints; however, I don't quite see the point in doing so, as I doubt there are situations where you would perform mathematical operations such as addition and subtraction on portions of version numbers for any purpose besides curiosity or auto-incrementation (and even then, int() and str() can be used fairly easily). (On the other hand, there is the possibility of someone else's code expecting a numerical tuple rather than a string tuple and thus failing.)

This is, of course, my own view, and I would gladly like others' input on using a numerical tuple.


As shezi reminded me, (lexical) comparisons of number strings do not necessarily have the same result as direct numerical comparisons; leading zeroes would be required to provide for that. So in the end, storing __version_info__ (or whatever it would be called) as a tuple of integer values would allow for more efficient version comparisons.

终遇你 2024-07-20 00:02:05

这里的许多解决方案都忽略了 git 版本标签,这仍然意味着您必须在多个位置跟踪版本(不好)。 我实现了以下目标:

  • git 存储库中的标记派生所有 python 版本引用
  • 自动化 git tag/push setup.py upload 步骤使用不带任何输入的单个命令。

工作原理:

  1. 通过 make release 命令,可以找到 git 存储库中最后一个标记的版本并递增。 标签被推回到origin

  2. Makefile 将版本存储在 src/_version.py 中,setup.py 会读取该版本,并包含在版本中。 不要将_version.py签入源代码管理!

  3. 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 脚本

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

Many of these solutions here ignore git version tags which still means you have to track version in multiple places (bad). I approached this with the following goals:

  • Derive all python version references from a tag in the git repo
  • Automate git tag/push and setup.py upload steps with a single command that takes no inputs.

How it works:

  1. From a make release command, the last tagged version in the git repo is found and incremented. The tag is pushed back to origin.

  2. The Makefile stores the version in src/_version.py where it will be read by setup.py and also included in the release. Do not check _version.py into source control!

  3. setup.py command reads the new version string from package.__version__.

Details:

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

The release target always increments the 3rd version digit, but you can use the next_minor_ver or next_major_ver to increment the other digits. The commands rely on the versionbump.py script that is checked into the root of the 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

This does the heavy lifting how to process and increment the version number from git.

__init__.py

The my_module/_version.py file is imported into my_module/__init__.py. Put any static install config here that you want distributed with your module.

from ._version import __version__
__author__ = ''
__email__ = ''

setup.py

The last step is to read the version info from the my_module 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__'],
    ...
    ...
)

Of course, for all of this to work you'll have to have at least one version tag in your repo to start.

git tag -a v0.0.1
℡Ms空城旧梦 2024-07-20 00:02:05

我在包目录中使用 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,因为我可以自动管理元数据。

I use a JSON file in the package dir. This fits Zooko's requirements.

Inside pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Inside 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'],
    ...
    )

Inside 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']

I also put other information in pkg_info.json, like author. I
like to use JSON because I can automate management of metadata.

强者自强 2024-07-20 00:02:05

自从首次提出这个问题以来,关于统一版本控制和支持约定的大量工作已经完成。。 Python 打包用户指南中现在详细提供了可口的选项。 另外值得注意的是,根据 PEP 440,Python 中的版本号方案相对严格,因此,如果您的包裹将被发布到 Cheese Shop,保持理智至关重要。

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

  1. 阅读 setup.py 中的文件 (setuptools) 并获取版本。
  2. 使用外部构建工具(更新 __init__.py 以及源代码控制),例如 bump2version更改zest.releaser
  3. 将值设置为特定模块中的 __version__ 全局变量。
  4. 将值放在一个简单的 VERSION 文本文件中,以便 setup.py 和代码读取。
  5. 通过 setup.py 版本设置值,并使用 importlib.metadata 在运行时拾取它。 (警告,有3.8之前和3.8之后的版本。)
  6. sample/__init__.py中将值设置为__version__,并在setup.py中导入sample
  7. 使用 setuptools_scm 从源代码管理中提取版本控制,使其成为规范参考,而不是代码。

注意(7)可能是最现代的方法(构建元数据独立于代码,由自动化发布)。 另外注意,如果安装程序用于软件包发布,则简单的python3 setup.py --version将直接报告版本。

Lots of work toward uniform versioning and in support of conventions has been completed since this question was first asked. Palatable options are now detailed in the Python Packaging User Guide. Also noteworthy is that version number schemes are relatively strict in Python per PEP 440, and so keeping things sane is critical if your package will be released to the Cheese Shop.

Here's a shortened breakdown of versioning options:

  1. Read the file in setup.py (setuptools) and get the version.
  2. Use an external build tool (to update both __init__.py as well as source control), e.g. bump2version, changes or zest.releaser.
  3. Set the value to a __version__ global variable in a specific module.
  4. Place the value in a simple VERSION text file for both setup.py and code to read.
  5. 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.)
  6. Set the value to __version__ in sample/__init__.py and import sample in setup.py.
  7. Use setuptools_scm to extract versioning from source control so that it's the canonical reference, not code.

NOTE that (7) might be the most modern approach (build metadata is independent of code, published by automation). Also NOTE that if setup is used for package release that a simple python3 setup.py --version will report the version directly.

天气好吗我好吗 2024-07-20 00:02:05

另外值得注意的是,__version__ 是一个半标准。 在 python 中, __version_info__ 也是一个元组,在简单的情况下,您可以执行以下操作:

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

...并且您可以从文件中获取 __version__ 字符串,或者任何。

Also worth noting is that as well as __version__ being a semi-std. in python so is __version_info__ which is a tuple, in the simple cases you can just do something like:

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

...and you can get the __version__ string from a file, or whatever.

仙气飘飘 2024-07-20 00:02:05

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

  1. 将版本嵌入 setup.py 并让 setup.py 生成一个模块(例如 < code>version.py) 仅包含由您的包导入的版本信息,或者

  2. 相反:将版本信息放入您的包本身,然后导入 thatsetup.py

    中设置版本

There doesn't seem to be a standard way to embed a version string in a python package. Most packages I've seen use some variant of your solution, i.e. eitner

  1. Embed the version in setup.py and have setup.py generate a module (e.g. version.py) containing only version info, that's imported by your package, or

  2. The reverse: put the version info in your package itself, and import that to set the version in setup.py

北方的韩爷 2024-07-20 00:02:05

arrow 以一种有趣的方式处理它。

现在(自2e5031b开始)

在<代码>arrow/__init__.py:

__version__ = 'x.y.z'

setup.py中:

from arrow import __version__

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

之前

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

arrow handles it in an interesting way.

Now (since 2e5031b)

In arrow/__init__.py:

__version__ = 'x.y.z'

In setup.py:

from arrow import __version__

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

Before

In arrow/__init__.py:

__version__ = 'x.y.z'
VERSION = __version__

In 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__'),
    # [...]
)
┊风居住的梦幻卍 2024-07-20 00:02:05

我还看到了另一种风格:

>>> django.VERSION
(1, 1, 0, 'final', 0)

I also saw another style:

>>> django.VERSION
(1, 1, 0, 'final', 0)
岁月苍老的讽刺 2024-07-20 00:02:05

使用 setuptoolspbr

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

我发现的管理版本的最佳解决方案是使用 setuptoolspbr 扩展。 现在这是我管理版本的标准方法。

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

PBR 将大多数元数据从 setup.py 工具中移出并放入 setup.cfg 文件中,然后将该文件用作大多数元数据(可以包括版本)的源。 如果需要的话,这允许使用诸如 pyinstaller 之类的东西将元数据打包到可执行文件中(如果是这样,您可能会需要此信息),并将元数据与其他包管理/设置脚本分开。 您可以直接手动更新 setup.cfg 中的版本字符串,在构建软件包版本时,它将被拉入 *.egg-info 文件夹中。 然后,您的脚本可以使用各种方法从元数据访问该版本(这些过程将在下面的部分中概述)。

当使用 Git 进行 VCS/SCM 时,此设置甚至更好,因为它将从 Git 中提取大量元数据,以便您的存储库可以成为某些元数据的主要真实来源,包括版本、作者、更改日志、具体来说,对于版本,它将根据存储库中的 git 标签为当前提交创建版本字符串。

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


实时获取当前版本

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

python setup.py --version

这将根据最新提交以及存储库中存在的标签,从 setup.cfg 文件或 git 存储库中提取最新版本。 不过,此命令不会更新发行版中的版本。


更新版本元数据

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

更新版本元数据的过程注意事项:

如果您不使用 pbr 从 git 提取版本数据,则只需使用新版本信息直接更新 setup.cfg(很简单,但请确保这是发布过程的标准部分)。

如果您使用 git,并且不需要创建源代码或二进制发行版(使用 python setup.py sdistpython setup.py bdist_xxx 之一) code> 命令)将 git repo 信息更新到 .egg-info 元数据文件夹的最简单方法是运行 python setup.py install 命令。 这将运行与从 git 存储库中提取元数据相关的所有 PBR 功能,并更新本地 .egg-info 文件夹,为您定义的任何入口点安装脚本可执行文件,以及您可以看到的其他功能从运行此命令时的输出中。

请注意,.egg-info 文件夹通常不会存储在标准 Python .gitignore 文件中的 git 存储库本身中(例如来自 Gitignore.IO),因为它可以从您的源生成。 如果被排除,请确保您有一个标准的“发布流程”来在发布之前在本地更新元数据,并且您上传到 PyPi.org 或以其他方式分发的任何包都必须包含此数据以获得正确的版本。 如果您希望 Git 存储库包含此信息,您可以排除特定文件被忽略(即添加 !*.egg-info/PKG_INFO.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

Using setuptools and pbr

There is not a standard way to manage version, but the standard way to manage your packages is setuptools.

The best solution I've found overall for managing version is to use setuptools with the pbr extension. This is now my standard way of managing version.

Setting up your project for full packaging may be overkill for simple projects, but if you need to manage version, you are probably at the right level to just set everything up. Doing so also makes your package releasable at PyPi so everyone can download and use it with 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).

When using Git for VCS/SCM, this setup is even better, as it will pull in a lot of the metadata from Git so that your repo can be your primary source of truth for some of the metadata, including version, authors, changelogs, etc. For version specifically, it will create a version string for the current commit based on git tags in the repo.

As PBR will pull version, author, changelog and other info directly from your git repo, so some of the metadata in setup.cfg can be left out and auto generated whenever a distribution is created for your package (using setup.py)


Get the current version in real-time

setuptools will pull the latest info in real-time using setup.py:

python setup.py --version

This will pull the latest version either from the setup.cfg file, or from the git repo, based on the latest commit that was made and tags that exist in the repo. This command doesn't update the version in a distribution though.


Updating the version metadata

When you create a distribution with setup.py (i.e. py setup.py sdist, for example), then all the current info will be extracted and stored in the distribution. This essentially runs the setup.py --version command and then stores that version info into the package.egg-info folder in a set of files that store distribution metadata.

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)


Accessing the version from a script

You can access the metadata from the current build within Python scripts in the package itself. For version, for example, there are several ways to do this I have found so far:

## 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))

You can put one of these directly in your __init__.py for the package to extract the version info as follows, similar to some other answers:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
牵你的手,一向走下去 2024-07-20 00:02:05

使用 setuptools 和 pyproject.toml

setuptools 现在提供了一种动态获取版本的方法在 pyproject.toml

重现此处的示例,您可以在 pyproject.toml 中创建类似以下内容的内容

# ...
[project]
name = "my_package"
dynamic = ["version"]
# ...
[tool.setuptools.dynamic]
version = {attr = "my_package.__version__"}

Using setuptools and pyproject.toml

Setuptools now offers a way to dynamically get version in pyproject.toml

Reproducing the example here, you can create something like the following in your pyproject.toml

# ...
[project]
name = "my_package"
dynamic = ["version"]
# ...
[tool.setuptools.dynamic]
version = {attr = "my_package.__version__"}
浅唱々樱花落 2024-07-20 00:02:05

经过几个小时的尝试找到最简单可靠的解决方案后,以下是以下部分:

在包“/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中。 (这是缺点,但没有导入错误是优点)

After several hours of trying to find the simplest reliable solution, here are the parts:

create a version.py file INSIDE the folder of your package "/mypackage":

# 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'

in setup.py:

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

in the main folder init.py:

from .version import __version__

The exec() function runs the script outside of any imports, since setup.py is run before the module can be imported. You still only need to manage the version number in one file in one place, but unfortunately it is not in setup.py. (that's the downside, but having no import bugs is the upside)

农村范ル 2024-07-20 00:02:05

不,没有一种标准方法可以将版本字符串嵌入到 Python 包中,以便可以将其作为属性进行访问PEP 396 – 模块版本号中提出了一项标准,但该 PEP 已于 2021 年被拒绝。

标准化内容:

非标准方法的几个示例是 VERSION 元组:

>>> import django
>>> django.VERSION
(4, 2, 4, 'final', 0)

__version__ 字符串:

>>> import requests
>>> requests.__version__
'2.31.0'

越来越多地,您会发现许多项目根本不存储版本属性。。 无论项目是否将版本属性保留到源代码中,可靠地检索包版本的推荐方法是使用 stdlib importlib.metadata

>>> from importlib.metadata import version
>>> version("django")
'4.2.4'
>>> version("requests")
'2.31.0'

如果您曾经为您的包提供过版本属性,并且如果您希望删除它,但出于向后兼容性的原因需要允许一段弃用期,您可以使用 模块level __getattr__ 后备:

# myproj/__init__.py
import warnings
from importlib.metadata import version

def __getattr__(name: str): 
    if name == "__version__":
        warnings.warn(
            f"Accessing myproj.__version__ is deprecated and will be "
            "removed in a future release. Use importlib.metadata directly.",
            DeprecationWarning,
            stacklevel=2,
        )
        return version("myproj")
    raise AttributeError(f"module {__name__} has no attribute {name}")

这是 python-attrs 使用的方法 23.1.0 例如。

$ PYTHONWARNINGS=always python3 -c 'from attrs import __version__'
<string>:1: DeprecationWarning: Accessing attrs.__version__ is deprecated and will be removed in a future release. Use importlib.metadata directly to query for attrs's packaging metadata.

不要费心使用模块 __getattr__ 方法,除非您要弃用旧项目中的现有版本属性。 对于新项目,我的建议是首先不要在源代码中提供版本属性,以便版本字符串的唯一真实来源位于包元数据中。

No, there isn't a standard way to embed the version string in a Python package so that it's accessible as an attribute. A standard was proposed in PEP 396 – Module Version Numbers, but that PEP has been rejected in 2021.

What has been standardized:

A couple of examples of non-standard approaches are a VERSION tuple:

>>> import django
>>> django.VERSION
(4, 2, 4, 'final', 0)

or a __version__ string:

>>> import requests
>>> requests.__version__
'2.31.0'

Increasingly, you'll find that many projects don't store a version attribute at all. Regardless of whether a project keeps a version attribute burnt into the source code, the recommended way to retrieve a package version reliably is by using stdlib importlib.metadata:

>>> from importlib.metadata import version
>>> version("django")
'4.2.4'
>>> version("requests")
'2.31.0'

If you've historically provided a version attribute for your package, and you wish to remove it but need to allow a deprecation period for backwards-compatibility reasons, you may use a module level __getattr__ fallback:

# myproj/__init__.py
import warnings
from importlib.metadata import version

def __getattr__(name: str): 
    if name == "__version__":
        warnings.warn(
            f"Accessing myproj.__version__ is deprecated and will be "
            "removed in a future release. Use importlib.metadata directly.",
            DeprecationWarning,
            stacklevel=2,
        )
        return version("myproj")
    raise AttributeError(f"module {__name__} has no attribute {name}")

This is the approach used by python-attrs 23.1.0 for example.

$ PYTHONWARNINGS=always python3 -c 'from attrs import __version__'
<string>:1: DeprecationWarning: Accessing attrs.__version__ is deprecated and will be removed in a future release. Use importlib.metadata directly to query for attrs's packaging metadata.

Don't bother using the module __getattr__ approach unless you're deprecating an existing version attribute from an old project. For new projects, my recommendation is not to provide a version attribute in the source code in the first place, so that the single source of truth for the version string is in the package metadata.

岁月无声 2024-07-20 00:02:05

pbr 与凹凸2版本

此解决方案源自本文

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

这是项目 packagex 的结构,

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 只包含

__version__ = "0.0.1"

packagex/__init__.py

from ._version import __version__

.bumpversion.cfg

[bumpversion]
current_version = 0.0.1
commit = False
tag = False
message = Bump version to v{new_version}
tag_message = v{new_version}


[bumpversion:file:packagex/_version.py]

以及 这里 是一个 GitHub使用此设置的项目。

pbr with bump2version

This solution was derived from this article.

The use case - python GUI package distributed via PyInstaller. Needs to show version info.

Here is the structure of the project packagex

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

where setup.py is

# 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 contains just

__version__ = "0.0.1"

and packagex/__init__.py

from ._version import __version__

and for .bumpversion.cfg

[bumpversion]
current_version = 0.0.1
commit = False
tag = False
message = Bump version to v{new_version}
tag_message = v{new_version}


[bumpversion:file:packagex/_version.py]

And here is a GitHub project with this setup.

一页 2024-07-20 00:02:05

看到这么多答案清楚地表明没有标准方法。

所以这是另一个:
Poetry 和 importlib

我使用 poetry-dynamic-versioning用于设置poetry build版本的插件。

然后,在我的包的 __init__.py 中,我有:

from importlib.metadata import version

__version__ = version(__name__)

当然,这需要有正确的包结构和构建过程。

Seeing so many answers is a clean sign that there is no standard way.

So here's another:
Poetry and importlib

I use the poetry-dynamic-versioning plugin to set the version on poetry build.

Then in the __init__.py of my package I have:

from importlib.metadata import version

__version__ = version(__name__)

Of course this requires to have a proper package structure and build process.

荒岛晴空 2024-07-20 00:02:05

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

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

确保 foo 始终已安装,这就是为什么需要 src/ 层来防止 < code>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',
    },
)

I prefer to read the package version from installation environment.
This is my src/foo/_version.py:

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

Makesure foo is always already installed, that's why a src/ layer is required to prevent foo imported without installation.

In the setup.py, I use setuptools-scm to generate the version automatically.


Update in 2022.7.5:

There is another way, which is my faviourate now. Use setuptools-scm to generate a _version.py file.

setup(
    ...
    use_scm_version={
        'write_to':
        'src/foo/_version.py',
        'write_to_template':
        '"""Generated version file."""\n'
        '__version__ = "{version}"\n',
    },
)
不…忘初心 2024-07-20 00:02:05
  1. 在与 __init__.py 相同的文件夹中创建一个名为 _version.txt 的文件,并将版本写入单行:
0.8.2
  1. 从文件 _version.txt< 中读取此信息/code> 在 __init__.py 中:
    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()
  1. Create a file named by _version.txt in the same folder as __init__.py and write version as a single line:
0.8.2
  1. Read this infomation from file _version.txt in __init__.py:
    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()
∝单色的世界 2024-07-20 00:02:05

__version__="0.0.1" 添加到您的主 __init__.py 中:

import .submodule

__version__ = '0.0.1'

如果您的库名为 mylib,那么这是唯一的事实来源您可以通过mylib.__version__直接访问您的版本号。 但您仍然需要将其添加到您的 setup.py 文件中。

setup.py 中,您需要将 __init__.py 作为文本文件读取:

import re
from setuptools import setup, find_packages

with open("mylib/__init__.py") as f:
    version= re.findall("__version__.*(\d.\d.\d).*", f.read())[0]

setup(
        name="mylib",
        version=version,
        packages=find_packages()
    )

有多种方法可以从以下位置读取 __version__ 变量: __init__.py 作为文本文件。 以这个为例: 单一采购软件包版本

请记住,您需要避免以下几点:

  1. 不要从 __init__.py 导入 __version__ 因为这会加载所有子模块及其依赖项在设置时不可用并且会失败。
  2. 如果您打算从主目录(即 __init__.py< /code> 位于),因为它会自动触发 __init__.py 这将导致与第 1 点中提到的相同问题。

Add __version__="0.0.1" to your main __init__.py:

import .submodule

__version__ = '0.0.1'

If your library is called mylib, this is the single source of truth for your version number which you can directly access by mylib.__version__. But you still need to add it to your setup.py file.

In your setup.py, you need to read the __init__.py as a text file:

import re
from setuptools import setup, find_packages

with open("mylib/__init__.py") as f:
    version= re.findall("__version__.*(\d.\d.\d).*", f.read())[0]

setup(
        name="mylib",
        version=version,
        packages=find_packages()
    )

There are different ways to read the __version__ variable from __init__.py as a text file. Check this one as an example: Single-sourcing the package version

Please keep in mind that you need the avoid the following points:

  1. Do not import __version__ from __init__.py because this loads all sub-modules with their dependencies that are not available at the setup time and will fail.
  2. Do not store __version__ in a separate file (e.g. version.py) if you are going to import it from your main directory (i.e. where your __init__.py is located) because it will automatically trigger __init__.py which will lead to the same issue mentioned in point 1.
帅气称霸 2024-07-20 00:02:05
  1. 仅使用文件中带有 __version__ = 参数的 version.py 文件。 在 setup.py 文件中导入 __version__ 参数并将其值放入 setup.py 文件中,如下所示:
    version=__version__
  2. 另一种方法是仅使用带有 version=setup.py 文件 - CURRENT_VERSION 是硬编码的。

由于我们不想每次创建新标签(准备发布新的包版本)时都手动更改文件中的版本,因此我们可以使用以下内容。

我强烈推荐 bumpversion 包。 我多年来一直使用它来升级版本。

首先将 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.py 文件下添加 __version__ 参数,并像这样更新凹凸版本文件:
    [bumpversion:file:]
  • 必须 git commitgit Reset 存储库中的所有内容,否则你会得到一个肮脏的回购错误。
  • 确保你的虚拟环境包含bumpversion包,没有它就无法工作。
  1. Use a version.py file only with __version__ = <VERSION> param in the file. In the setup.py file import the __version__ param and put it's value in the setup.py file like this:
    version=__version__
  2. Another way is to use just a setup.py file with version=<CURRENT_VERSION> - the CURRENT_VERSION is hardcoded.

Since we don't want to manually change the version in the file every time we create a new tag (ready to release a new package version), we can use the following..

I highly recommend bumpversion package. I've been using it for years to bump a version.

start by adding version=<VERSION> to your setup.py file if you don't have it already.

You should use a short script like this every time you bump a version:

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

Then add one file per repo called: .bumpversion.cfg:

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

Note:

  • You can use __version__ parameter under version.py file like it was suggested in other posts and update the bumpversion file like this:
    [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • You must git commit or git reset everything in your repo, otherwise you'll get a dirty repo error.
  • Make sure that your virtual environment includes the package of bumpversion, without it it will not work.
海未深 2024-07-20 00:02:05

如果您的项目使用 git 或 Mercurial 作为其 SCM,我建议您执行以下操作:

  1. 如下所示配置您的 __init__.py,以便它始终设置 __version__ 属性正确 - 无论是在开发模式、非 pip 开发模式(由 IDE 更新的 python 路径)或生产模式(pip install)下

    尝试: 
          # -- 分发方式 -- 
          # 从发布期间 setuptools_scm 生成的 _version.py 导入 
          从 ._version 将版本导入为 __version__ 
      除了导入错误: 
          # -- 源模式 -- 
          # 使用 setuptools_scm 使用 git 从 src 获取当前版本 
          从 setuptools_scm 导入 get_version as _gv 
          从操作系统导入路径作为_path 
          __version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir)) 
      
  2. 然后,选择一种方式来生成生产中所需的 _version.py 文件(上面第 4 行导入的文件)。

    a. 我个人在 setup.py 中执行此操作:

    设置( 
        ... 
        use_scm_version={'write_to': '%s/_version.py' % } 
      ) 
      

    b. 或者,如果您更喜欢使用 pyproject.tomlsetup.cfg 文件配置项目,您可以查看 setuptools_scm 了解这是如何完成的。 例如,对于pyproject.toml,它是这样的:

    <前><代码>[tool.setuptools_scm]
    write_to = "/_version.py"

    c. 最后,另一种方法是当您希望创建版本时手动执行脚本。 该脚本必须在对项目进行 git 标记之后、发布之前运行。

    from setuptools_scm import get_version 
      get_version('.', write_to='/_version.py') 
      

    例如,您可以在持续集成过程中将其作为单个命令行运行:

    <代码>>   python -c“从 setuptools_scm 导入 get_version;get_version('.', write_to='/_version.py')” 
      

这种模式 (1+2a) 在过去几年中成功地用于我的数十个已发布的软件包(例如 makefun),所以我可以热烈推荐它。

注意:我最初在一个单独的网页 此处< /a>.

编辑:setuptools_scm文档指出write_to 现已弃用(但在撰写本文时仍受支持)。 他们建议改用 version_file

If your project uses git or mercurial as its SCM, I would recommend the following:

  1. Configure your __init__.py as shown below so that it always sets the __version__ attribute correctly - both in development mode, non-pip development mode (python path updated by IDE), or production mode (pip install)

    try:
        # -- Distribution mode --
        # import from _version.py generated by setuptools_scm during release
        from ._version import version as __version__
    except ImportError:
        # -- Source mode --
        # use setuptools_scm to get the current version from src using git
        from setuptools_scm import get_version as _gv
        from os import path as _path
        __version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir))
    
  2. then, choose a way to generate the _version.py file that is required in production (the one imported on line 4 above).

    a. I personally do this in setup.py:

    setup(
      ...
      use_scm_version={'write_to': '%s/_version.py' % <pkgname>}
    )
    

    b. Alternately if you prefer to configure your project using a pyproject.toml or setup.cfg file, you can check the documentation in setuptools_scm to find how this is done. For example with pyproject.toml it is like this:

    [tool.setuptools_scm]
    write_to = "<pkgname>/_version.py"
    

    c. Finally, an alternative is to execute a script manually when you wish to create releases. This script must run after git-tagging your project and before publishing it.

    from setuptools_scm import get_version
    get_version('.', write_to='<pkg_name>/_version.py')
    

    you can for example run this as a single commandline in your continuous integration process:

    > python -c "from setuptools_scm import get_version;get_version('.', write_to='<pkg_name>/_version.py')"
    

This pattern (1+2a) has worked successfully for me for dozens of published packages over the past years (for example makefun), so I can warmly recommend it.

Note: I originally provided the above tip in a separate web page here.

EDIT: setuptools_scm's documentation states that write_to is now deprecated (but still supported at the time of writing). They recommend to use version_file instead.

风柔一江水 2024-07-20 00:02:05

如果您使用 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)

If you use CVS (or RCS) and want a quick solution, you can use:

__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])

(Of course, the revision number will be substituted for you by CVS.)

This gives you a print-friendly version and a version info that you can use to check that the module you are importing has at least the expected version:

import my_module
assert my_module.__version_info__ >= (1, 1)
定格我的天空 2024-07-20 00:02:05

就其价值而言,如果您使用 NumPy distutils,numpy.distutils.misc_util.Configuration 有一个 make_svn_version_py() 方法,将修订号嵌入到 package.__svn_version__ 在变量 version 中。

For what it's worth, if you're using NumPy distutils, numpy.distutils.misc_util.Configuration has a make_svn_version_py() method that embeds the revision number inside package.__svn_version__ in the variable version .

靑春怀旧 2024-07-20 00:02:04

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

这几乎是一个准标准。 标准库中的许多模块都使用 __version__,这也用于 很多 3rd-party模块,所以它是准标准。

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

正如 S.Lott 提到的(谢谢!), PEP 8 明确指出:

模块级别 Dunder 名称

模块级别“dunders”(即具有两个前导和两个尾随的名称)
下划线),例如 __all____author____version__ 等。
应放置在模块文档字符串之后但在任何导入之前
除了来自 __future__ 导入的语句。

您还应该确保版本号符合 PEP 440PEP 386 该标准的先前版本)。

Not directly an answer to your question, but you should consider naming it __version__, not version.

This is almost a quasi-standard. Many modules in the standard library use __version__, and this is also used in lots of 3rd-party modules, so it's the quasi-standard.

Usually, __version__ is a string, but sometimes it's also a float or tuple.

As mentioned by S.Lott (Thank you!), PEP 8 says it explicitly:

Module Level Dunder Names

Module level "dunders" (i.e. names with two leading and two trailing
underscores) such as __all__, __author__, __version__, etc.
should be placed after the module docstring but before any import
statements except from __future__ imports.

You should also make sure that the version number conforms to the format described in PEP 440 (PEP 386 a previous version of this standard).

地狱即天堂 2024-07-20 00:02:04

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

  1. 它提供了一个 __version__ 属性。

  2. 它提供标准元数据版本。 因此,它将被 pkg_resources 或其他解析包元数据的工具(EGG-INFO 和/或 PKG-INFO,PEP 0345)检测到。

  3. 在构建包时,它不会导入您的包(或其他任何内容),这在某些情况下可能会导致问题。 (有关这可能导致什么问题,请参阅下面的评论。)

  4. 只有一处写下了版本号,因此当版本号发生变化时,只有一处可以更改它,并且版本号发生变化的机会较小。版本不一致。

它的工作原理如下:存储版本号的“一个规范位置”是一个名为“_version.py”的 .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() 的“version”参数的值,从而满足功能 2。

为了满足功能 1,您可以拥有您的包(在运行时,而不是在安装时!)从 myniftyapp/__init__.py 导入 _version 文件,如下所示:

from _version import __version__

这里是 我多年来一直使用这种技术的示例

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

这是导入版本的示例代码< /a>.

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

I use a single _version.py file as the "once cannonical place" to store version information:

  1. It provides a __version__ attribute.

  2. 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).

  3. 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.)

  4. 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.

Here is how it works: the "one canonical place" to store the version number is a .py file, named "_version.py" which is in your Python package, for example in myniftyapp/_version.py. This file is a Python module, but your setup.py doesn't import it! (That would defeat feature 3.) Instead your setup.py knows that the contents of this file is very simple, something like:

__version__ = "3.6.5"

And so your setup.py opens the file and parses it, with code like:

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,))

Then your setup.py passes that string as the value of the "version" argument to setup(), thus satisfying feature 2.

To satisfy feature 1, you can have your package (at run-time, not at setup time!) import the _version file from myniftyapp/__init__.py like this:

from _version import __version__

Here is an example of this technique that I've been using for years.

The code in that example is a bit more complicated, but the simplified example that I wrote into this comment should be a complete implementation.

Here is example code of importing the version.

If you see anything wrong with this approach, please let me know.

巷雨优美回忆 2024-07-20 00:02:04

重写 2017-05

经过 13 年多的编写 Python 代码和管理各种包之后,我得出的结论是 DIY 可能不是最好的方法。

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

截至今天,pbr 每月下载量为 1200 万次,达到这个水平并不包含任何肮脏的伎俩。 这只是一件事——以一种非常简单的方式解决常见的包装问题。

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

因此,为了让您了解如何在一次提交中采用 pbr,请查看 将打包切换到 pbr

您可能会发现该版本根本没有存储在存储库中。 PBR 确实从 Git 分支和标签中检测到它。

无需担心没有 git 存储库时会发生什么,因为 pbr 在打包或安装应用程序时会“编译”并缓存版本,因此对 git 没有运行时依赖。

旧解决方案

这是迄今为止我见过的最好的解决方案,它也解释了原因:

Inside 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'

Inside yourpackage/__init__.py:

from .version import __version__

Inside setup.py

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

如果您知道另一种似乎更好的方法,请告诉我。

Rewritten 2017-05

After 13+ years of writing Python code and managing various packages, I came to the conclusion that DIY is maybe not the best approach.

I started using the pbr package for dealing with versioning in my packages. If you are using git as your SCM, this will fit into your workflow like magic, saving your weeks of work (you will be surprised about how complex the issue can be).

As of today, pbr has 12M mongthly downloads, and reaching this level didn't include any dirty tricks. It was only one thing -- fixing a common packaging problem in a very simple way.

pbr can do more of the package maintenance burden, and is not limited to versioning, but it does not force you to adopt all its benefits.

So to give you an idea about how it looks to adopt pbr in one commit have a look switching packaging to pbr

Probably you would observed that the version is not stored at all in the repository. PBR does detect it from Git branches and tags.

No need to worry about what happens when you do not have a git repository because pbr does "compile" and cache the version when you package or install the applications, so there is no runtime dependency on git.

Old solution

Here is the best solution I've seen so far and it also explains why:

Inside 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'

Inside yourpackage/__init__.py:

from .version import __version__

Inside setup.py:

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

If you know another approach that seems to be better let me know.

清风夜微凉 2024-07-20 00:02:04

根据推迟的[停止新闻:拒绝] PEP 396 (模块版本号),有一个建议的方法可以做到这一点。 它描述了模块要遵循的(诚然是可选的)标准,并给出了基本原理。 这是一个片段:

  • 当模块(或包)包含版本号时,版本应该在 __version__ 属性中可用。
  • 对于位于命名空间包内的模块,该模块应该包含 __version__ 属性。 命名空间包本身不应包含其自己的 __version__ 属性。
  • __version__ 属性的值应该是一个字符串。
  • Per the deferred [STOP PRESS: rejected] PEP 396 (Module Version Numbers), there is a proposed way to do this. It describes, with rationale, an (admittedly optional) standard for modules to follow. Here's a snippet:

    1. When a module (or package) includes a version number, the version SHOULD be available in the __version__ attribute.
    1. For modules which live inside a namespace package, the module SHOULD include the __version__ attribute. The namespace package itself SHOULD NOT include its own __version__ attribute.
    1. The __version__ attribute's value SHOULD be a string.
    ~没有更多了~
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文