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未将版本传递给安装程序

      来自