如何获取包中 setup.py (setuptools) 中定义的版本?
-
20-09-2019 - |
题
我怎样才能获得定义的版本 setup.py
从我的包裹中(对于 --version
, ,或其他目的)?
解决方案
讯问已经安装的分布的版本字符串
要检索您的包内运行时版本(显示你的问题实际上被询问),您可以使用:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
存储版本字符串,在使用过程中安装
如果你想要去的其他方式“轮(这似乎是什么出现在这里的其他答案的作者已经以为你是问),把版本字符串在一个单独的文件和读取setup.py
该文件的内容。
您可以做一个version.py在你的包有__version__
线,然后用execfile('mypackage/version.py')
从setup.py读它,使它集__version__
在setup.py命名空间。
如果你想有一个,将与可能需要访问版本字符串的所有Python版本,甚至非Python这样的语言的工作更简单的方式:
存储的版本的字符串作为一个纯文本文件的唯一内容,例如命名VERSION
和setup.py
期间读取该文件。
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
同样VERSION
文件将工作完全以及其他任何程序,甚至非的Python的人,而你只需要改变的版本字符串在一个地方的所有程序。
警告关于种族条件期间安装
顺便说一句,不要从你的setup.py在这里另一个答案建议导入您的包:它似乎为你工作(因为你已经安装了软件包的依赖关系),但它会在肆虐新用户浩劫的程序包,因为它们将不能够无需手动首先安装的依赖关系来安装程序包。
其他提示
实例研究:mymodule
设想这样的配置:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
然后想象,你必须依赖和setup.py
看起来有些平常的情景:
setup(...
install_requires=['dep1','dep2', ...]
...)
和示例__init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
和例如myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
问题#1:在安装过程中导入mymodule
如果您setup.py
进口安装过程中mymodule
然后点击 您最有可能得到一个ImportError
。这是一个非常常见的错误时,你的包具有相关性。如果包装上没有其它的依赖比建宏,你可能是安全的;然而,这是不是一个好的做法。其原因是,它是不是经得起未来考验;明天再说你的代码需要消耗一些其他的依赖性。
问题2:这里是我的__version__
?
如果您在__version__
硬编码setup.py
那么它可能不匹配,你将你的模块中附带的版本。为了保持一致,你把它放在一个地方,当你需要它从同一个地方阅读。使用import
您可能会收到问题#1。
的解决方案:点菜setuptools
您会使用open
,exec
的组合,并且提供一个字典用于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__'],
...)
和在mymodule/version.py
暴露版本:
__version__ = 'some.semantic.version'
这样,该版本附带的模块,并设置要导入已缺少的依赖关系模块中没有问题(尚未安装)。
最好的方法是在你的产品代码来定义__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__,
...
)
更新(2017年):coverage.py不再进口本身获得的版本。导入自己的代码可以把它卸载的,因为你的产品代码会试图导入的依赖,这是尚未安装,因为setup.py就是安装它们。
您的问题有点模糊,但我想你问的是如何规定的。
您需要定义像这样__version__
:
__version__ = '1.4.4'
然后你就可以确认setup.py知道您刚才指定的版本:
% ./setup.py --version
1.4.4
我很不满意这些答案......不想要求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
如果您希望要格外小心,并使用一个真正的解析器:
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
setup.py有点暴殄天物模块的这样不是一个问题,如果它是有点难看。
在源树中创建一个文件,例如在 yourbasedir/yourpackage/_version.py 中。让该文件仅包含一行代码,如下所示:
__version__ = "1.1.0-r4704"
然后在 setup.py 中,打开该文件并解析出版本号,如下所示:
verstr = "unknown" try: verstrline = open('yourpackage/_version.py', "rt").read() except EnvironmentError: pass # Okay, there is no version file. else: VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) else: raise RuntimeError("unable to find version in yourpackage/_version.py")
最后,在 yourbasedir/yourpackage/__init__.py
像这样导入_version:
__version__ = "unknown" try: from _version import __version__ except ImportError: # We're running in a tree that doesn't have a _version.py, so we don't know what our version is. pass
执行此操作的代码示例是我维护的“pyutil”包。(请参阅 PyPI 或谷歌搜索 - 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
此也应该工作,使用正则表达式,并根据所述元数据字段有一个这样的格式:
__fieldname__ = 'value'
使用下面你setup.py的开头:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
在这之后,你可以使用元数据在你的脚本是这样的:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
通过这样的结构:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
其中的 version.py 强>包含:
__version__ = 'version_string'
您可以做到这一点的 setup.py
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
这会不会导致你在你的mymodule中的任何相关性的任何问题/ __ __初始化。PY
要避免导入一个文件(并因此执行其代码)人们可以分析它并恢复从语法树的version
属性:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
此代码试图找到在模块返回的顶层的__version__
或VERSION
分配是字符串值。右侧可以是一个字符串或一元组。
有一个一千种皮肤猫 - 这里是我的:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
这@清理 https://stackoverflow.com/a/12413800 外国佬-和蔼:
from itertools import ifilter
from os import path
from ast import parse
with open(path.join('package_name', '__init__.py')) as f:
__version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
f))).body[0].value.s
我们希望把我们在pypackagery
包__init__.py
元信息,但不能因为它有第三方的依赖关系PJ伊比已经指出(见他的回答和关于竞争条件的警告)。
我们通过创建包含一个单独的模块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'
然后导入的元信息中packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
和最后用它在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'],
)
您必须包括pypackagery_meta
到您的包py_modules
设置参数。否则,你不能导入它在安装时因为包装的分布会缺少它。
现在,这是严重和需要一些精炼(甚至有可能出现的,我错过了通过pkg_resources未覆盖的成员通话),但我只是不明白为什么这不工作,也不知道为什么没有人就建议日期(周围的Googling并没有把这个了)......注意,这是Python的2.x和需要,需要通过pkg_resources(叹气):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
简单和直接,创建一个文件叫 source/package_name/version.py
与以下内容:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
然后,在你的文件 source/package_name/__init__.py
, 你进口的版本对其他人使用:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
现在,你可以穿上这个 setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
测试这与蟒蛇 2.7
, 3.3
, 3.4
, 3.5
, 3.6
和 3.7
在Linux上,窗户和Mac OS。我用我的包,其中有整合和单元测试的所有论文的平台。你可以看到的结果 .travis.yml
和 appveyor.yml
在这里:
- https://travis-ci.org/evandrocoan/debugtools/builds/527110800
- https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446
另一个版本使用方面管理:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
你也可以使用 codecs
模块来处理unicode错误两个月蟒蛇 2.7
和 3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
如果你正在写的一Python模块100%在C/C++使用Python C扩展,可以做同样的事情,但使用C++而不是蟒蛇。
在这种情况下,创建以下 setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
哪读的版本的文件 version.h
:
const char* __version__ = "1.0.12";
但是,不要忘记创建的 MANIFEST.in
包括 version.h
文件:
include README.md
include LICENSE.txt
recursive-include source *.h
它是纳入主要的应用程序:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
参考文献: