问题:如何获取软件包中setup.py(setuptools)中定义的版本?
如何setup.py
从软件包中定义版本(出于--version
或其他目的)?
How could I get the version defined in setup.py
from my package (for --version
, or other purposes)?
回答 0
询问已安装发行版的版本字符串
要在运行时从程序包内部检索版本(您的问题实际上是在问什么),可以使用:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
存储版本字符串以在安装期间使用
如果要沿另一方向走动(这似乎是其他答案作者似乎认为您正在询问的问题),请将版本字符串放在单独的文件中,然后在中读取该文件的内容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()
这样,同一个VERSION
文件在任何其他程序中都可以很好地工作,即使在非Python程序中也是如此,并且您只需要在一个位置更改所有程序的版本字符串即可。
安装过程中有关种族状况的警告
顺便说一句,请不要按照此处另一个答案的建议从setup.py导入软件包:它似乎对您有用(因为您已经安装了软件包的依赖项),但是会对软件包的新用户造成严重破坏,因为如果不先手动安装依赖项,他们将无法安装您的软件包。
Interrogate version string of already-installed distribution
To retrieve the version from inside your package at runtime (what your question appears to actually be asking), you can use:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Store version string for use during install
If you want to go the other way ’round (which appears to be what other answer authors here appear to have thought you were asking), put the version string in a separate file and read that file’s contents in setup.py
.
You could make a version.py in your package with a __version__
line, then read it from setup.py using execfile('mypackage/version.py')
, so that it sets __version__
in the setup.py namespace.
If you want a much simpler way that will work with all Python versions and even non-Python languages that may need access to the version string:
Store the version string as the sole contents of a plain text file, named e.g. VERSION
, and read that file during setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
The same VERSION
file will then work exactly as well in any other program, even non-Python ones, and you only need to change the version string in one place for all programs.
Warning about race condition during install
By the way, DO NOT import your package from your setup.py as suggested in another answer here: it will seem to work for you (because you already have your package’s dependencies installed), but it will wreak havoc upon new users of your package, as they will not be able to install your package without manually installing the dependencies first.
回答 1
示例研究: 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。
解决方案:à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__'],
...)
并mymodule/version.py
公开版本:
__version__ = 'some.semantic.version'
这样,该版本将随模块一起提供,并且在安装期间尝试导入缺少依赖项(尚未安装)的模块时不会出现问题。
example study: mymodule
Imagine this configuration:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Then imagine some usual scenario where you have dependencies and setup.py
looks like:
setup(...
install_requires=['dep1','dep2', ...]
...)
And an example __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
And for example myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
problem #1: importing mymodule
during setup
If your setup.py
imports mymodule
then during setup you would most likely get an ImportError
. This is a very common error when your package has dependencies. If your package does not have other dependencies than the builtins, you may be safe; however this isn’t a good practice. The reason for that is that it is not future-proof; say tomorrow your code needs to consume some other dependency.
problem #2: where’s my __version__
?
If you hardcode __version__
in setup.py
then it may not match the version that you would ship in your module. To be consistent, you would put it in one place and read it from the same place when you need it. Using import
you may get the problem #1.
solution: à la setuptools
You would use a combination of open
, exec
and provide a dict for exec
to add variables:
# 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__'],
...)
And in mymodule/version.py
expose the version:
__version__ = 'some.semantic.version'
This way, the version is shipped with the module, and you do not have issues during setup trying to import a module that has missing dependencies (yet to be installed).
回答 2
最好的技术是__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是安装它们的原因。
The best technique is to define __version__
in your product code, then import it into setup.py from there. This gives you a value you can read in your running module, and have only one place to define it.
The values in setup.py are not installed, and setup.py doesn’t stick around after installation.
What I did (for example) in coverage.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): coverage.py no longer imports itself to get the version. Importing your own code can make it uninstallable, because you product code will try to import dependencies, which aren’t installed yet, because setup.py is what installs them.
回答 3
您的问题有点模糊,但是我想您要问的是如何指定它。
您需要这样定义__version__
:
__version__ = '1.4.4'
然后,您可以确认setup.py知道您刚刚指定的版本:
% ./setup.py --version
1.4.4
Your question is a little vague, but I think what you are asking is how to specify it.
You need to define __version__
like so:
__version__ = '1.4.4'
And then you can confirm that setup.py knows about the version you just specified:
% ./setup.py --version
1.4.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在某种程度上是一个一次性模块,因此如果它有点难看,则不是问题。
更新:有趣的是,近年来,我已经摆脱了这一点,开始使用名为的软件包中的单独文件meta.py
。我在其中放置了很多可能需要经常更改的元数据。因此,不仅仅是为了一个价值。
I wasn’t happy with these answers… didn’t want to require setuptools, nor make a whole separate module for a single variable, so I came up with these.
For when you are sure the main module is in pep8 style and will stay that way:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
If you’d like to be extra careful and use a real parser:
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 is somewhat of a throwaway module so not an issue if it is a bit ugly.
Update: funny enough I’ve moved away from this in recent years and started using a separate file in the package called meta.py
. I put lots of meta data in there that I might want to change frequently. So, not just for one value.
回答 5
在源代码树中创建文件,例如在yourbasedir / yourpackage / _version.py中。让该文件仅包含一行代码,如下所示:
__version__ = "1.1.0-r4704"
然后在setup.py中,打开该文件并解析出如下版本号:
verstr =“未知”
尝试:
verstrline = open('yourpackage / _version.py',“ rt”)。read()
除了EnvironmentError:
通过#好的,没有版本文件。
其他:
VSRE = r“ ^ __ version__ = ['\”]([^ \\]] *)['\“]”
mo = re.search(VSRE,verstrline,re.M)
如果莫:
verstr = mo.group(1)
其他:
引发RuntimeError(“无法在您的package / _version.py中找到版本”)
最后,在yourbasedir/yourpackage/__init__.py
import _version中,如下所示:
__version__ =“未知”
尝试:
从_version导入__version__
除了ImportError:
#我们正在没有_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,distribution或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
Create a file in your source tree, e.g. in yourbasedir/yourpackage/_version.py . Let that file contain only a single line of code, like this:
__version__ = "1.1.0-r4704"
Then in your setup.py, open that file and parse out the version number like this:
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")
Finally, in yourbasedir/yourpackage/__init__.py
import _version like this:
__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
An example of code that does this is the “pyutil” package that I maintain. (See PyPI or google search — stackoverflow is disallowing me from including a hyperlink to it in this answer.)
@pjeby is right that you shouldn’t import your package from its own setup.py. That will work when you test it by creating a new Python interpreter and executing setup.py in it first thing: python setup.py
, but there are cases when it won’t work. That’s because import youpackage
doesn’t mean to read the current working directory for a directory named “yourpackage”, it means to look in the current sys.modules
for a key “yourpackage” and then to do various things if it isn’t there. So it always works when you do python setup.py
because you have a fresh, empty sys.modules
, but this doesn’t work in general.
For example, what if py2exe is executing your setup.py as part of the process of packaging up an application? I’ve seen a case like this where py2exe would put the wrong version number on a package because the package was getting its version number from import myownthing
in its setup.py, but a different version of that package had previously been imported during the py2exe run. Likewise, what if setuptools, easy_install, distribute, or distutils2 is trying to build your package as part of a process of installing a different package that depends on yours? Then whether your package is importable at the time that its setup.py is being evaluated, or whether there is already a version of your package that has been imported during this Python interpreter’s life, or whether importing your package requires other packages to be installed first, or has side-effects, can change the results. I’ve had several struggles with trying to re-use Python packages which caused problems for tools like py2exe and setuptools because their setup.py imports the package itself in order to find its version number.
By the way, this technique plays nicely with tools to automatically create the yourpackage/_version.py
file for you, for example by reading your revision control history and writing out a version number based on the most recent tag in revision control history. Here is a tool that does that for darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst and here is a code snippet which does the same thing for git: http://github.com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34
回答 6
使用正则表达式并根据元数据字段具有如下格式,这也应该起作用:
__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']
This should also work, using regular expressions and depending on the metadata fields to have a format like this:
__fieldname__ = 'value'
Use the following at the beginning of your setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
After that, you can use the metadata in your script like this:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
回答 7
具有这样的结构:
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 / __ init__.py中的任何依赖项造成任何问题
With a structure like this:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
where version.py contains:
__version__ = 'version_string'
You can do this in setup.py:
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
This won’t cause any problem with whatever dependencies you have in your mymodule/__init__.py
回答 8
为了避免导入文件(从而执行其代码),可以解析文件并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
返回字符串值。右侧可以是字符串或元组。
To avoid importing a file (and thus executing its code) one could parse it and recover the version
attribute from the syntax tree:
# 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
This code tries to find the __version__
or VERSION
assignment at the top level of the module return is string value. The right side can be either a string or a tuple.
回答 9
有上千种为猫皮的方法-这是我的:
# 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.")
There’s a thousand ways to skin a cat — here’s mine:
# 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.")
回答 10
从@ gringo-suave 清理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
Cleaning up https://stackoverflow.com/a/12413800 from @gringo-suave:
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
回答 11
现在这很麻烦,需要进行一些改进(我可能错过了pkg_resources中甚至可能有一个未发现的成员调用),但是我根本看不出为什么这行不通,也为什么至今没有人建议这样做(在Google周围搜索没有出现)…请注意,这是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
Now this is gross and needs some refining (there may even be an uncovered member call in pkg_resources that I missed), but I simply do not see why this doesn’t work, nor why no one has suggested it to date (Googling around has not turned this up)…note that this is Python 2.x, and would require requiring pkg_resources (sigh):
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
回答 12
我们希望将有关我们程序包的元信息放入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'
然后将元信息导入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
setup参数将其包含在包中。否则,您将无法在安装时导入它,因为打包的发行版将缺少它。
We wanted to put the meta information about our package pypackagery
in __init__.py
, but could not since it has third-party dependencies as PJ Eby already pointed out (see his answer and the warning regarding the race condition).
We solved it by creating a separate module pypackagery_meta.py
that contains only the meta information:
"""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'
then imported the meta information in packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
and finally used it in 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'],
)
You must include pypackagery_meta
into your package with py_modules
setup argument. Otherwise, you can not import it upon installation since the packaged distribution would lack it.
回答 13
简单明了,创建一个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()
与Python测试了这个2.7
,3.3
,3.4
,3.5
,3.6
和3.7
在Linux,Windows和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
模块在Python 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 C扩展在C / C ++中100%编写Python模块,则可以执行相同的操作,但要使用C / C ++而不是Python。
在这种情况下,创建以下内容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__ ) );
...
}
参考文献:
- python打开文件错误
- 通过C API在Python模块中定义全局变量
- 如何在setuptools / distribute中包含软件包数据?
- https://github.com/lark-parser/lark/blob/master/setup.py#L4
- 如何使用具有相同名称的setuptools软件包和ext_modules?
- 是否可以使用dist utils(setup.py)作为包数据的一部分包含子目录?
Simple and straight, create a file called source/package_name/version.py
with the following contents:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Then, on your file source/package_name/__init__.py
, you import the version for other people to use:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Now, you can put this on 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()
Tested this with Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
and 3.7
on Linux, Windows and Mac OS. I used on my package which has Integration and Unit Tests for all theses platforms. You can see the results from .travis.yml
and appveyor.yml
here:
- https://travis-ci.org/evandrocoan/debugtools/builds/527110800
- https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446
An alternate version is using context manager:
#!/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 ) )
You can also be using the codecs
module to handle unicode errors both on Python 2.7
and 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 ) )
If you are writing a Python module 100% in C/C++ using Python C Extensions, you can do the same thing, but using C/C++ instead of Python.
On this case, create the following 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'],
)
],
)
Which reads the version from the file version.h
:
const char* __version__ = "1.0.12";
But, do not forget to create the MANIFEST.in
to include the version.h
file:
include README.md
include LICENSE.txt
recursive-include source *.h
And it is integrated into the main application with:
#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__ ) );
...
}
References:
- python open file error
- Define a global in a Python module from a C API
- How to include package data with setuptools/distribute?
- https://github.com/lark-parser/lark/blob/master/setup.py#L4
- How to use setuptools packages and ext_modules with the same name?
- Is it possible to include subdirectories using dist utils (setup.py) as part of package data?
回答 14
将包部署到服务器和索引包的文件命名约定:
pip动态版本转换的示例:
赢得:
- test_pkg-1.0.0-cp36-cp36m-win_amd64.whl
- test_pkg-1.0.0-py3.6-win-amd64.egg
苹果电脑:
- test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.egg
- test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.whl
- Linux:
- test_pkg-1.0.0-cp36-cp36m-linux_x86_64.whl
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
找到示例setup.py从git commit调用匹配的动态pip版本
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
deploy package to server and file naming convention for indices packages :
example for pip dynamic version conversion:
win:
- test_pkg-1.0.0-cp36-cp36m-win_amd64.whl
- test_pkg-1.0.0-py3.6-win-amd64.egg
mac:
- test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.egg
- test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.whl
- linux:
- test_pkg-1.0.0-cp36-cp36m-linux_x86_64.whl
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
Find the sample setup.py calls the dynamic pip version matching from git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
回答 15
我正在使用以下环境变量
VERSION = 0.0.0 python setup.py sdist bdist_wheel
在setup.py中
import os
setup(
version=os.environ['VERSION'],
...
)
为了与打包程序版本进行一致性检查,我使用以下脚本。
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi
I am using an environment variable as below
VERSION=0.0.0 python setup.py sdist bdist_wheel
In setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
For consistency check with packer version, I am using below script.
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi