使用distutils
,setuptools
等,在setup.py
中指定了包版本:
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
我希望能够从包中访问相同的版本号:
>>> import foobar
>>> foobar.__version__
'1.0.0'
我可以将__version__ = '1.0.0'
添加到我的包的__init__.py中,但我还想在我的包中包含其他导入来创建包的简化界面:
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = '1.0.0'
和
# file: setup.py
from foobar import __version__
...
setup(
name='foobar',
version=__version__,
# other attributes
)
但是,如果导入其他尚未安装的软件包,这些额外的导入可能会导致foobar
的安装失败。使用setup.py和软件包共享软件包版本的正确方法是什么?
答案 0 :(得分:71)
仅在setup.py
中设置版本,并使用pkg_resources
阅读您自己的版本,有效查询setuptools
元数据:
档案:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
档案:__init__.py
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
要在所有情况下都能正常运行,您最终可以在未安装的情况下运行此功能,请测试DistributionNotFound
和分发位置:
from pkg_resources import get_distribution, DistributionNotFound
import os.path
try:
_dist = get_distribution('foobar')
# Normalize case for Windows systems
dist_loc = os.path.normcase(_dist.location)
here = os.path.normcase(__file__)
if not here.startswith(os.path.join(dist_loc, 'foobar')):
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = 'Please install this project with setup.py'
else:
__version__ = _dist.version
答案 1 :(得分:18)
我不相信这有一个规范的答案,但我的方法(直接复制或稍微调整我在其他地方看到的)如下:
文件夹层次结构(仅限相关文件):
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
<强> main_package/_version.py
强>
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
<强> main_package/__init__.py
强>
"""Something nice and descriptive."""
from main_package.some_module import some_function_or_class
# ... etc.
from main_package._version import __version__
__all__ = (
some_function_or_class,
# ... etc.
)
<强> setup.py
强>
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
......这就像罪一样丑陋......但它确实有效,而且我已经在人们分发的软件包中看到它或类似的东西,如果有的话,我希望知道更好的方式。
答案 2 :(得分:11)
我同意@stefano-m 's philosophy关于:
拥有版本 =&#34; x.y.z&#34;在源代码中解析它 setup.py绝对是正确的解决方案,恕我直言。好多了 (反过来说)依靠运行时魔术。
这个答案来自@ zero-piraeus&#39; s answer。重点是不要在setup.py中使用导入,而是从文件中读取版本&#34;。
我使用正则表达式来解析__version__
,这样它根本不需要是专用文件的最后一行。事实上,我仍然将单一事实来源__version__
放在我的项目__init__.py
中。
文件夹层次结构(仅限相关文件):
package_root/
|- main_package/
| `- __init__.py
`- setup.py
<强> main_package/__init__.py
强>
# You can have other dependency if you really need to
from main_package.some_module import some_function_or_class
# Define your version number in the way you mother told you,
# which is so straightforward that even your grandma will understand.
__version__ = "1.2.3"
__all__ = (
some_function_or_class,
# ... etc.
)
<强> setup.py
强>
from setuptools import setup
import re, io
__version__ = re.search(
r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too
io.open('main_package/__init__.py', encoding='utf_8_sig').read()
).group(1)
# The beautiful part is, I don't even need to check exceptions here.
# If something messes up, let the build process fail noisy, BEFORE my release!
setup(
version=__version__,
# ... etc.
)
......这仍然不理想......但它确实有效。
顺便说一句,此时你可以用这种方式测试你的新玩具:
python setup.py --version
1.2.3
PS:此official Python packaging document(及其mirror)描述了更多选项。它的第一个选择也是使用正则表达式。 (取决于你使用的确切正则表达式,它可能会或可能不会处理版本字符串中的引号。虽然通常不是一个大问题。)
PPS:fix in ADAL Python现已被移植到此答案中。
答案 3 :(得分:3)
将__version__
放入your_pkg/__init__.py
,然后使用setup.py
解析ast
:
import ast
import importlib.util
from pkg_resources import safe_name
PKG_DIR = 'my_pkg'
def find_version():
"""Return value of __version__.
Reference: https://stackoverflow.com/a/42269185/
"""
file_path = importlib.util.find_spec(PKG_DIR).origin
with open(file_path) as file_obj:
root_node = ast.parse(file_obj.read())
for node in ast.walk(root_node):
if isinstance(node, ast.Assign):
if len(node.targets) == 1 and node.targets[0].id == "__version__":
return node.value.s
raise RuntimeError("Unable to find version string.")
setup(name=safe_name(PKG_DIR),
version=find_version(),
packages=[PKG_DIR],
...
)
如果使用Python&lt; 3.4,请注意importlib.util.find_spec
不可用。此外,importlib
当然不能依赖setup.py
的任何后退。{1}}。在这种情况下,请使用:
import os
file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, '__init__.py')
答案 4 :(得分:2)
基于accepted answer和评论,这就是我最终做的事情:
档案:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
档案:__init__.py
from pkg_resources import get_distribution, DistributionNotFound
__project__ = 'foobar'
__version__ = None # required for initial installation
try:
__version__ = get_distribution(__project__).version
except DistributionNotFound:
VERSION = __project__ + '-' + '(local)'
else:
VERSION = __project__ + '-' + __version__
from foobar import foo
from foobar.bar import Bar
说明:
__project__
是要安装的项目的名称
不同于包的名称
VERSION
是我在命令行界面中显示的内容
请求--version
仅限附加导入(对于简化的包接口) 如果项目实际已安装
答案 5 :(得分:2)
/// Initialize the MIDI Sampler
public override init() {
super.init()
enableMIDI()
}
/// Enable MIDI input from a given MIDI client
/// This is not in the init function because it must be called AFTER you start AudioKit
///
/// [snipped parameter comments for brevity]
open func enableMIDI(_ midiClient: MIDIClientRef = AKMIDI().client, name: String = "MIDI Sampler")
上提出了几种方法。
答案 6 :(得分:1)
setuptools 46.4.0 添加了基本的抽象语法树分析支持,因此 setup.cfg attr: directive 无需导入包的依赖项即可工作。这使得拥有包版本的单一真实来源成为可能,从而使 setupstools 46.4.0 发布之前发布的先前答案中的许多解决方案过时。
现在可以避免将版本传递给 setup.py 中的 setuptools.setup 函数,如果 __version__ 在 yourpackage.__init__.py 中初始化并且以下元数据添加到包的 setup.cfg 文件中。使用此配置,setuptools.setup 函数将自动从 yourpackage.__init__.py 解析包版本,您可以在应用程序中需要的地方自由导入 __version__.py。
setup.py 没有传递给 setup 的版本
from setuptools import setup
setup(
name="yourpackage"
)
你的包.____init__.py
__version__ = 0.2.0
setup.cfg
[metadata]
version = attr: package.__version__
应用中的某个模块
from yourpackage import __version__ as expected_version
from pkg_distribution import get_distribution
installed_version = get_distribution("yourpackage").version
assert expected_version != installed_version
答案 7 :(得分:0)
接受的答案要求已安装软件包。就我而言,我需要从源__version__
中提取安装参数(包括setup.py
)。在查看tests of the setuptools package时,我找到了一个直接而简单的解决方案。要查找有关_setup_stop_after
属性的更多信息,请引导我an old mailing list post提及distutils.core.run_setup
,这会引导我the actual docs needed。毕竟,这是一个简单的解决方案:
档案setup.py
:
from setuptools import setup
setup(name='funniest',
version='0.1',
description='The funniest joke in the world',
url='http://github.com/storborg/funniest',
author='Flying Circus',
author_email='flyingcircus@example.com',
license='MIT',
packages=['funniest'],
zip_safe=False)
档案extract.py
:
from distutils.core import run_setup
dist = run_setup('./setup.py', stop_after='init')
dist.get_version()
答案 8 :(得分:-1)
很晚,我知道。但这对我有用。
module / version.py:
__version__ = "1.0.2"
if __name__ == "__main__":
print(__version__)
模块/ __ init __。py:
from . import version
__version__ = version.__version__
setup.py:
import subprocess
out = subprocess.Popen(['python', 'module/version.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout,stderr = out.communicate()
version = str(stdout)
对我来说,主要优点是不需要手工解析或正则表达式或manifest.in条目。它也是相当Pythonic的,似乎在所有情况下都可以工作(pip -e等),并且可以通过在version.py中使用argparse轻松扩展以共享文档字符串等。谁能看到这种方法的问题?