如何在我的包中获得setup.py(setuptools)中定义的版本?

elm*_*rco 139 python setuptools

我如何setup.py从我的包中获得定义的版本(用于--version或其他目的)?

PJ *_*Eby 228

已安装分发的询问版本字符串

要在运行时从包内部检索版本(您的问题似乎实际上是在询问),您可以使用:

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Run Code Online (Sandbox Code Playgroud)

存储版本字符串以在安装期间使用

如果你想转向其他方式(这似乎是其他答案作者在这里似乎认为你在问),请将版本字符串放在一个单独的文件中并读取该文件的内容setup.py.

您可以使用__version__一行在您的包中创建一个version.py ,然后使用setup.py从中读取它execfile('mypackage/version.py'),以便它__version__在setup.py命名空间中设置.

如果你想要一种更简单的方法,它可以用于所有Python版本,甚至可能需要访问版本字符串的非Python语言:

将版本字符串存储为纯文本文件的唯一内容(例如VERSION,名称),并在此期间读取该文件setup.py.

version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Run Code Online (Sandbox Code Playgroud)

然后,同一个VERSION文件在任何其他程序中都可以正常工作,甚至是非Python程序,您只需要在一个地方为所有程序更改版本字符串.

安装期间有关竞争条件的警告

顺便说一句,不要按照另一个答案中的建议从setup.py导入你的软件包:它似乎对你有用(因为你已经安装了你的软件包的依赖项),但它会对你的软件包的新用户造成严重破坏,因为他们将无法安装您的包,而无需先手动安装依赖项.

  • 由于Python 3.8现已推出...标准库中直接有一个解决方案 `__version__ = importlib.metadata.version('Example')` https://docs.python.org/3.8/library/importlib.metadata。 html (10认同)
  • ...好吧,对于Python 2*和*3使用`with open('mypackage/version.py')作为f:exec(f.read())`代替`execfile('mypackage/version.py') )`.(来自http://stackoverflow.com/a/437857/647002) (9认同)
  • 肆虐的部分不一定是真的......如果包中的__init__.py文件具有代码导入依赖性(直接或间接),它只会造成严重破坏. (4认同)
  • `execfile`工作得很好......但是(遗憾的是)不适用于Python 3. (2认同)
  • 似乎值得一提的是,django在他们的setup.py中执行了一个\ _ _ _ import _ _\__ call(https://github.com/django/django/blob/master/setup.py).'\ _ _ _ import\_\_''vs'import'是否安全?它似乎没有引起任何问题. (2认同)

dno*_*zay 30

示例研究: mymodule

想象一下这个配置:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py
Run Code Online (Sandbox Code Playgroud)

然后想象一下你有依赖关系的常见场景,setup.py看起来像:

setup(...
    install_requires=['dep1','dep2', ...]
    ...)
Run Code Online (Sandbox Code Playgroud)

一个例子__init__.py:

from mymodule.myclasses import *
from mymodule.version import __version__
Run Code Online (Sandbox Code Playgroud)

例如myclasses.py:

# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
Run Code Online (Sandbox Code Playgroud)

问题#1:mymodule在安装过程中导入

如果您在安装过程中进行了setup.py导入,mymodule则很可能会得到一个.当您的包具有依赖项时,这是一个非常常见的错误.如果你的包没有内置的其他依赖,你可能是安全的; 但这不是一个好习惯.原因是它不具备前瞻性; 明天你的代码需要消耗一些其他的依赖.ImportError

问题#2:哪里是我的__version__

如果您进行硬编码__version__,setup.py则可能与您在模块中发布的版本不匹配.为了保持一致,您可以将它放在一个地方,并在需要时从同一个地方阅读.使用import你可能会遇到问题#1.

解决方案:àla setuptools

您将使用组合open,exec并提供一个dict exec来添加变量:

# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path

main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
    exec(ver_file.read(), main_ns)

setup(...,
    version=main_ns['__version__'],
    ...)
Run Code Online (Sandbox Code Playgroud)

并在mymodule/version.py公开版本:

__version__ = 'some.semantic.version'
Run Code Online (Sandbox Code Playgroud)

这样,该版本随模块一起提供,在安装过程中尝试导入缺少依赖项(尚未安装)的模块时没有问题.

  • 这似乎是最合理的解决方案,因为它仅依赖于允许的依赖项。 (2认同)

jat*_*ism 14

你的问题有点模糊,但我认为你要问的是如何指定它.

你需要__version__像这样定义:

__version__ = '1.4.4'
Run Code Online (Sandbox Code Playgroud)

然后你可以确认setup.py知道你刚刚指定的版本:

% ./setup.py --version
1.4.4
Run Code Online (Sandbox Code Playgroud)


Ned*_*der 14

最好的技术是__version__在产品代码中定义,然后从那里将其导入setup.py.这为您提供了一个可以在运行模块中读取的值,并且只有一个位置可以定义它.

未安装setup.py中的值,安装后setup.py不会停留.

我在coverage.py中所做的(例如):

# coverage/__init__.py
__version__ = "3.2"


# setup.py
from coverage import __version__

setup(
    name = 'coverage',
    version = __version__,
    ...
    )
Run Code Online (Sandbox Code Playgroud)

更新(2017年):coverage.py不再导入自己以获取版本.导入您自己的代码可以使其无法安装,因为您的产品代码将尝试导入尚未安装的依赖项,因为setup.py是安装它们的原因.

  • 我为此感到沮丧,但@pjeby是完全正确的:"顺便说一句,不要按照另一个答案中的建议从setup.py导入你的软件包:它似乎对你有用(因为你已经安装了你的软件包的依赖项) ),但它会对你的软件包的新用户造成严重破坏,因为如果不首先手动安装依赖项,他们将无法安装你的软件包." 内德,你介意给你的回答添加一个警告吗? (22认同)
  • @Evan,对不起,你错了导入部分文件.尝试在长模块的末尾放置一个print语句,然后导入文件中定义的第一个变量.print语句将执行. (13认同)
  • @Evan:我不确定你所得到的是什么"只从源头上汲取这个价值".如果其中带有`__version__`的文件以某种方式被破坏,那么您的导入也将被破坏.Python不知道如何只解释你想要的语句.@pjeby是对的:如果你的模块需要导入其他模块,它们可能还没有安装,而且会很混乱.如果您小心导入不会注意其他导入的长链,则此方法有效. (4认同)
  • 这是一种反模式,因为它阻止“mypackage/__init__.py”导入任何依赖项。应该更新答案以显示如何正确执行此操作(例如,放置一个单独的“version.py”模块,然后从“__init__.py”导入该模块),以便人们不会错误地在此处复制过时的代码。 (3认同)

小智 9

许多其他答案都已过时,我相信从已安装的 python 3.10 包获取版本信息的标准方法是使用 importlib.metadata 自PEP-0566起

官方文档:https://docs.python.org/3.10/library/importlib.metadata.html

from importlib.metadata import version
VERSION_NUM = version("InstalledPackageName")
Run Code Online (Sandbox Code Playgroud)

这很简单,干净,没有大惊小怪。

如果您在软件包安装期间运行的脚本中执行一些奇怪的操作,但如果您所做的只是获取版本检查的版本号以通过 CLI --help 命令向用户显示,则此操作将不起作用,或者任何其他已经安装了软件包并且只需要安装的版本号的东西,这对我来说似乎是最好的解决方案。


Gri*_*ave 8

我对这些答案不满意......不想要求setuptools,也不想为单个变量创建一个完整的模块,所以我想出了这些.

当你确定主模块是pep8风格并保持这种方式时:

version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            _, _, version = line.replace("'", '').split()
            break
Run Code Online (Sandbox Code Playgroud)

如果您想要格外小心并使用真正的解析器:

import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            version = ast.parse(line).body[0].value.s
            break
Run Code Online (Sandbox Code Playgroud)

setup.py是一个一次性模块,所以如果它有点难看就不是问题.

version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            _, _, version = line.replace("'", '').split()
            break
Run Code Online (Sandbox Code Playgroud)


Jah*_*hid 7

具有这样的结构:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py
Run Code Online (Sandbox Code Playgroud)

其中version.py包含:

__version__ = 'version_string'
Run Code Online (Sandbox Code Playgroud)

您可以在setup.py中执行此操作:

import sys

sys.path[0:0] = ['mymodule']

from version import __version__
Run Code Online (Sandbox Code Playgroud)

这不会对 mymodule/__init__.py 中的任何依赖项造成任何问题


mar*_*tin 6

我们想将有关我们的包的元信息放入pypackagery__init__.py,但不能,因为它具有第三方依赖项,正如 PJ Eby 已经指出的那样(请参阅他的答案和有关竞争条件的警告)。

我们通过创建一个pypackagery_meta.py包含元信息的单独模块来解决这个问题:

"""Define meta information about pypackagery package."""

__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
                   'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
Run Code Online (Sandbox Code Playgroud)

然后将元信息导入packagery/__init__.py

# ...

from pypackagery_meta import __title__, __description__, __url__, \
    __version__, __author__, __author_email__, \
    __license__, __copyright__

# ...
Run Code Online (Sandbox Code Playgroud)

最后将其用于setup.py

import pypackagery_meta

setup(
    name=pypackagery_meta.__title__,
    version=pypackagery_meta.__version__,
    description=pypackagery_meta.__description__,
    long_description=long_description,
    url=pypackagery_meta.__url__,
    author=pypackagery_meta.__author__,
    author_email=pypackagery_meta.__author_email__,
    # ...
    py_modules=['packagery', 'pypackagery_meta'],
 )
Run Code Online (Sandbox Code Playgroud)

您必须pypackagery_meta使用 setup 参数将其包含在包中py_modules。否则,您无法在安装时导入它,因为打包的发行版将缺少它。


Zoo*_*oko 5

在您的源代码树中创建一个文件,例如在 yourbasedir/yourpackage/_version.py 中。让该文件只包含一行代码,如下所示:

__version__ = "1.1.0-r4704"

然后在您的 setup.py 中,打开该文件并像这样解析版本号:

verstr = "未知"
尝试:
    verstrline = open('yourpackage/_version.py', "rt").read()
除了环境错误:
    pass # 好的,没有版本文件。
别的:
    VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
    mo = re.search(VSRE, verstrline, re.M)
    如果莫:
        verstr = mo.group(1)
    别的:
        raise RuntimeError("无法在您的包/_version.py 中找到版本")

最后,在yourbasedir/yourpackage/__init__.pyimport _version 这样的:

__version__ = "未知"
尝试:
    从 _version 导入 __version__
除了导入错误:
    # 我们在没有_version.py 的树中运行,所以我们不知道我们的版本是什么。
    经过

执行此操作的代码示例是我维护的“pyutil”包。(请参阅 PyPI 或 google 搜索——stackoverflow 不允许我在此答案中包含指向它的超链接。)

@pjeby 是正确的,你不应该从它自己的 setup.py 导入你的包。当您通过创建一个新的 Python 解释器并首先在其中执行 setup.py 来测试它时,这将起作用:python setup.py,但在某些情况下它不起作用。那是因为import youpackage并不意味着读取名为“yourpackage”的目录的当前工作目录,它意味着在当前sys.modules查找关键“yourpackage”,然后在它不存在时执行各种操作。所以当你这样做时它总是有效,python setup.py因为你有一个新的、空的sys.modules,但这通常不起作用。

例如,如果 py2exe 正在执行您的 setup.py 作为打包应用程序过程的一部分怎么办?我见过这样的情况,其中 py2exe 会将错误的版本号放在包上,因为包是从import myownthing在它的 setup.py 中,但之前在 py2exe 运行期间导入了该包的不同版本。同样,如果 setuptools、easy_install、distribute 或 distutils2 试图构建您的软件包作为安装依赖于您的不同软件包的过程的一部分怎么办?然后你的包是否在它的 setup.py 被评估时是可导入的,或者在这个 Python 解释器的生命周期中是否已经导入了你的包的一个版本,或者导入你的包是否需要先安装其他包,或有副作用,可以改变结果。我在尝试重用 Python 包时遇到了几次困难,这导致了 py2exe 和 setuptools 等工具出现问题,因为它们的 setup.py 会导入包本身以查找其版本号。

顺便说一句,这种技术可以很好地与工具一起yourpackage/_version.py为您自动创建文件,例如通过读取您的修订控制历史记录并根据修订控制历史记录中的最新标记写出版本号。这是一个为 darcs 执行此操作的工具:http : //tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst,这是一个对 git 执行相同操作的代码片段:http://github .com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34


小智 5

这也应该有效,使用正则表达式并根据元数据字段具有如下格式:

__fieldname__ = 'value'
Run Code Online (Sandbox Code Playgroud)

在 setup.py 的开头使用以下内容:

import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Run Code Online (Sandbox Code Playgroud)

之后,您可以在脚本中使用元数据,如下所示:

print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Run Code Online (Sandbox Code Playgroud)

  • 不好的建议是slacy,当评估如此简单时应该避免。 (5认同)

归档时间:

查看次数:

64006 次

最近记录:

5 年,11 月 前