Python 与setup.py和软件包共享软件包版本的正确方法是什么?
使用Python 与setup.py和软件包共享软件包版本的正确方法是什么?,python,setuptools,distutils,Python,Setuptools,Distutils,使用distutils,setuptools等。包版本在setup.py中指定: # file: setup.py ... setup( name='foobar', version='1.0.0', # other attributes ) 我希望能够从软件包中访问相同的版本号: >>> import foobar >>> foobar.__version__ '1.0.0' # file: __init__.py from foobar import
distutils
,setuptools
等。包版本在setup.py
中指定:
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
我希望能够从软件包中访问相同的版本号:
>>> import foobar
>>> foobar.__version__
'1.0.0'
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = '1.0.0'
我可以将
\uuuuu version\uuuuu='1.0.0'
添加到我的包的\uuuu init\uuuuuuuuuuuuy.py中,但我还希望在包中包含其他导入,以创建包的简化接口:
>>> import foobar
>>> foobar.__version__
'1.0.0'
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = '1.0.0'
及
但是,如果这些额外导入导入了其他尚未安装的软件包,则可能会导致安装
foobar
失败。与setup.py和软件包共享软件包版本的正确方法是什么?我不相信有一个规范的答案,但我的方法(直接复制或稍微调整我在其他地方看到的)如下所示:
文件夹继承人权限(仅限相关文件):
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
main\u-package/\u-version.py
:
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
main\u-package/\uuu-init\uuuuuuuuuuuy.py
:
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
setup.py
:
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
。。。这就像罪恶一样丑陋。。。但是它是有效的,我在一些人分发的软件包中看到过它或类似的东西,我希望他们知道一种更好的方法,如果有的话。只在
setup.py
中设置版本,并使用它阅读自己的版本,有效地查询setuptools
元数据:
文件:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
setup(
name='foobar',
version='1.0.0',
# other attributes
)
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
文件:\uuuu init\uuuu.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
setup(
name='foobar',
version='1.0.0',
# other attributes
)
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
要使此功能在所有情况下都能正常工作,如果您可以在未安装的情况下运行此功能,请测试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
根据调查和评论,我最终做了以下工作:
文件:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
setup(
name='foobar',
version='1.0.0',
# other attributes
)
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
文件:\uuuu init\uuuu.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
setup(
name='foobar',
version='1.0.0',
# other attributes
)
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
说明:
是要安装的项目的名称,可能是 与包的名称不同\uuuu project\uuuu
是当VERSION
--请求版本
- 附加导入(仅适用于简化包接口) 如果项目已实际安装,则发生
- 我同意以下观点:
在源代码中具有版本=“x.y.z”,并在
setup.py绝对是正确的解决方案,IMHO。比
(另一种方式)依赖运行时魔术
这个答案来源于比雷埃夫斯的“零”。要点是“不要在setup.py中使用导入,而是从文件中读取版本”
我使用regex来解析
\uuuuuuuuuuuuuuuu版本
,因此它根本不需要是专用文件的最后一行。事实上,我仍然在我的项目的\uu init\uuuuuuuuy.py
中加入了真相的单一来源\uuuuuuuuu版本
文件夹继承人权限(仅限相关文件):
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
main\u-package/\uuu-init\uuuuuuuuuuuy.py
:
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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.
)
setup.py
:
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
"""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.
)
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"),
# ... etc.
)
package_root/
|- main_package/
| `- __init__.py
`- setup.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.
)
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:这(及其)描述了更多选项。它的第一个选项也是使用正则表达式。(取决于您使用的确切正则表达式,它可能处理版本字符串中的引号,也可能不处理。不过一般来说这不是什么大问题。)
PPS:现在将返回到这个答案中。将
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
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<3.4,请注意这是不可用的。此外,importlib
的任何后端口当然不能依赖于setup.py
。在这种情况下,请使用:
import os
file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, '__init__.py')
接受的答案要求已安装软件包。在我的例子中,我需要从源代码setup.py
中提取安装参数(包括\uuuuuu version\uuuu
)。我在查看过程中发现了一个直接而简单的解决方案。有关\u setup\u stop\u after
属性的更多信息,请引导我到前面提到的distutils.core.run\u setup
,这将引导我到。在所有这些之后,下面是一个简单的解决方案:
文件setup.py
:
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
从设置工具导入设置
设置(name='funniest',
version='0.1',
description='世界上最有趣的笑话',
url='1〕http://github.com/storborg/funniest',
作者:《飞行马戏团》,
作者(电邮地址)flyingcircus@example.com',
许可证,
软件包=['funniest'],
zip_safe=False)
文件extract.py
:
很晚了,我知道。但这对我来说很有效
模块/version.py:
__version__ = "1.0.2"
if __name__ == "__main__":
print(__version__)
模块/\uuuu初始化\uuuuu.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条目。它也是相当python的,似乎在所有情况下都能工作(pip-e等),并且可以通过在version.py中使用argparse轻松地扩展到共享docstring等。任何人都能看到这种方法的问题吗?setuptools 46.4.0添加了基本的抽象语法树分析支持,这样就不必导入包的依赖项。这使得软件包版本有一个单一的真实来源成为可能,从而使setupstools 46.4.0发布之前发布的先前答案中的许多解决方案过时
如果在包中初始化了_version _uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。通过此配置,setuptools.setup函数将自动解析包中的包版本。\uuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
例子
setup.py未将版本传递给安装程序
来自