使用“setup.cfg”而不使用“setup.py”配置setuptools时,从文件中设置模块的“__version__”

Jim*_*iff 7 setuptools python-3.x

I\xe2\x80\x99m 遵循PyPA \xe2\x80\x99s 当前指南,仅在构建分发包时使用setup.cfg而不配置元数据。setup.py(\xe2\x80\x9c应首选静态元数据 ( )。动态元数据 ( ) 仅应在绝对必要时用作逃生舱口。过去是必需的,但对于较新版本的和可以省略。setup.cfgsetup.pysetup.pysetuptoolspip \xe2\x80 \x9d)

\n

具体来说,I\xe2\x80\x99m 使用以下目录/文件结构:

\n
my-project/\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 LICENSE\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 pyproject.toml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 README.md\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 setup.cfg\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src/\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 my_package/\n\xe2\x94\x82       \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82       \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 my_module.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 tests/\n
Run Code Online (Sandbox Code Playgroud)\n

(如果你\xe2\x80\x99不熟悉这个结构,它有一个src/介于项目目录和导入包目录之间的目录,它\xe2\x80\x99是包装Python项目教程中给出的结构,并由Ionel争论Cristian M\xc4\x83rie\xc8\x99Mark Smith等人。另请参阅使用文件配置 setuptools中的 \xc2\xa7 \xe2\x80\x9c使用src/布局\xe2\x80\x9d 。)setup.cfg

\n

我已经成功地指定了版本号setup.cfg

\n
[metadata]\nversion = "0.0.1"\n
Run Code Online (Sandbox Code Playgroud)\n

然而,PyPA 等人。已经讨论了 \xe2\x80\x9c单一来源软件包版本的概念,\xe2\x80\x9d 基本上意味着 AFAICT 将版本的定义移到外部setup.pysetup.cfg(a) 使其更广泛地被其他工具访问,并且(b) 将版本的定义放入导入包目录(即my-project/src/my_package)中的\xe2\x80\x99s 文件中,因此与代码一起安装。(诚​​然,我的用例非常简单,将定义移到外部可能没有什么好处setup.cfg,但我\xe2\x80\x99m 倾向于探索最佳实践\xe2\x80\x94,也许这是不合理的。)

\n

特别是,许多讨论包括定义一个单独的文件,例如__version__.py,,,version.py甚至VERSION,来保存版本信息。此外,此版本文件将位于 (a) 导入包的根目录,例如my_package

\n
path-to/project-directory/src/my_package/__version__.py\n
Run Code Online (Sandbox Code Playgroud)\n

project-directory而不是 (b) 所在位置的根setup.cfg。(我故意project-directory使用连字符并使用下划线来my_package强调 PyPI将项目名称中的下划线等规范化为连字符,而这些规范化的名称作为 Python 包的名称是非法的。)

\n

关于单一来源版本号的绝大多数讨论都是在动态使用setup.py而不是仅静态使用setup.cfg. 讨论中涉及使用静态元数据(即setup.cfg不使用静态元数据setup.py)(例如,使用 setup.cfg 文件配置 setuptools)的问题与它所回答的问题一样多。特别是,它\xe2\x80\x99 没有提及如何配置单独的版本指定文件以及所涉及的各个文件如何相互关联。

\n

当您使用 静态指定项目元数据时,有哪些选项以及如何精确配置适当的文件来指定包/模块的版本setup.cfg

\n

Jim*_*iff 18

在提出上述问题的过程中,我逐步解决了这个问题,找到了三种有效的方法:

\n
    \n
  • 两种方法使用attr:特殊指令setup.cfg。其中:\n
      \n
    • 一将版本号直接放在package\xe2\x80\x99s中__init__.py文件中。
    • \n
    • 另一种将版本号放在一个单独的文件(__version__.py)中,然后__init__.py从该单独的文件导入版本字符串。
    • \n
    \n
  • \n
  • 第三种方法使用file:特殊指令setup.cfg。\n
      \n
    • 这会直接读取单独的版本指定文件 ( VERSION),并且\xe2\x80\x99t 不涉及包\xe2\x80\x99s__init__.py ),并且根本
    • \n
    \n
  • \n
\n

鉴于这三种可能性,我重新呈现了目录/文件结构,并添加了两个版本指定文件__version__.pyVERSION

\n
my-project/\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 LICENSE\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 pyproject.toml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 README.md\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 setup.cfg\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src/\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 my_package/\n\xe2\x94\x82       \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82       \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __version__.py\n\xe2\x94\x82       \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 VERSION\n\xe2\x94\x82       \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 my_module.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 tests/\n
Run Code Online (Sandbox Code Playgroud)\n

当然,您\xe2\x80\x99d 至多拥有这两个文件之一,具体取决于您选择实施的三个解决方案中的哪一个。

\n

他们俩attr:特别指令解决方案

\n

setup.cfg在使用\xe2\x80\x99sattr:特殊指令的两个解决方案中,setup.cfg从导入包\xe2\x80\x99s__version__属性获取导入包\xe2\x80\x99s 版本。(当您导入some_package具有版本的版本时,使用dir(some_package),您\xe2\x80\x99将看到它具有一个__version__属性。)现在您可以看到这里之间的连接attr:属性。)现在您可以看到特殊指令的名称和我们的目标

\n

关键任务:如何将__version__属性分配给my_package分配给?

\n

我们__version__使用已经存在的 package\xe2\x80\x99s 文件直接或间接分配属性__init__.py(假设您有传统包而不是命名空间包),这超出了本答案的范围)。

\n

其中的代码片段setup.cfg在方法 A 和方法 B 中都很常见

\n

在这两个attr:特殊指令解决方案中,文件的配置setup.cfg是相同的,具有以下代码片段:

\n
[metadata]\nname = my-project\nversion = attr: my_package.__version__\n
Run Code Online (Sandbox Code Playgroud)\n

需要明确的是,这里.__version__引用的是属性,而不是文件、子包或其他任何东西。

\n

现在,我们根据版本信息是直接进入__init__.py还是进入其自己的文件进行分支。

\n

方法A:将作业放入package\xe2\x80\x99s__init__.py文件中

\n

此方法不使用单独的文件来指定版本号,而是将其插入到 packagexe2x80x99s 文件中__init__.py

\n
# path-to/my-project/src/my_package/__init__.py\n__version__ = \'0.0.2\'\n
Run Code Online (Sandbox Code Playgroud)\n

请注意作业的两个要素:

\n
    \n
  • 左侧 ( __version__) 对应于( )attr:中的行setup.cfgversion = attr: my_package.__version__
  • \n
  • 右侧的合法版本字符串是用引号括起来的字符串。
  • \n
\n

我们\xe2\x80\x99已经用方法A完成了。

\n

方法B:将作业放入__version__.py文件中并导入__init__.py

\n

创建__version__.py版本字符串并将其放入其中

\n

我们构造一个新的Python文件并将其定位在与导入包\xe2\x80\x99s__init__.py文件相同的级别。

\n

__version__我们插入与方法 A 中插入的指令完全相同的指令__init__.py

\n
# my-project/src/my_package/__version__.py\n__version__ = \'0.0.2\'\n
Run Code Online (Sandbox Code Playgroud)\n

从内部__init__.pyimport __version____version__.py

\n

在 中__init__.py,我们进行相对导入来访问__version__在单独文件中分配的 :

\n
# path-to/my-project/src/my_package/__init__.py\nfrom . __version__ import __version__\n
Run Code Online (Sandbox Code Playgroud)\n

稍微解压一下\xe2\x80\xa6

\n
    \n
  • 我们\xe2\x80\x99正在做相对导入,所以我们必须使用语法from \xe2\x80\xa6 import \xe2\x80\xa6。(绝对导入可以使用 orimport <>语法from <> import <>,但相对导入只能使用第二种形式。)
  • \n
  • 表示. 相对导入,从当前包开始
  • \n
  • 第一次出现是__version__指 \xe2\x80\x9cmodule\xe2\x80\x9d__version__.py。\n
      \n
    • 此文件名不必是\xe2\x80\x99 __version__.py。那\xe2\x80\x99只是传统的。但是,无论文件名是什么,它都必须与后面的名称匹配from .(除非.pyfrom . import)。
    • \n
    \n
  • \n
  • 第二次出现的__version__指的是里面的赋值语句__version__.py。\n
      \n
    • I\xe2\x80\x99m 不确定这个字符串是否需要__version__但它肯定至少需要匹配赋值语句。
    • \n
    \n
  • \n
\n

我们\xe2\x80\x99已经用方法B完成了。

\n

方法 C:使用file:特殊指令

\n

单独的文件的填充方式不同

\n

在这个方法中,我们使用一个单独的文件作为版本号,就像方法B一样。与方法B不同的是,我们直接读取这个文件的内容,而不是导入它。

\n

为了防止混淆,我将这个文件简单地称为 .\xe2\x80\x99 VERSION__init__.py与方法 B\xe2\x80\x99s一样__version__.pyVERSION位于导入包的根级别。(参见目录/文件图。)(当然,在这种方法中,你不会有\xe2\x80\x99t __version__.py。)

\n

但是,该文件的内容VERSION与方法 B\xe2\x80\x99s 的内容有很大不同__version__.py

\n

这里\xe2\x80\x99s的内容my-project/src/my_package/VERSION

\n
0.0.2\n
Run Code Online (Sandbox Code Playgroud)\n

注意:

\n
    \n
  • 该文件只包含版本字符串本身的内容。特别是,不要将此字符串括在引号中!
  • \n
  • \xe2\x80\x99s 也没有赋值语法;There\xe2\x80\x99s 没有 \xe2\x80\x9c __version__ = \xe2\x80\x9d 赋值字符串的前导码。
  • \n
  • 这甚至不是一个 Python 文件,所以我没有包含带有文件路径的注释字符串,因为这足以给出错误VERSION does not comply with PEP 440: # comment line
  • \n
\n

setup.cfg与以前不同

\n

setup.cfg方法 C 与setup.cfg方法 A 和 B 共有的方法有两点不同。

\n

setup.cfg使用file:而不是attr:

\n

在方法 C 中,我们在 中使用不同的表述setup.cfg,替换掉attr:特殊指令并将其替换为file:特殊指令。新的片段是:

\n
[metadata]\nname = my-project\nversion = file: src/my_package/VERSION\n
Run Code Online (Sandbox Code Playgroud)\n

文件路径VERSION是相对于项目目录的

\n

VERSION请注意赋值语句中的路径: src/my_package/VERSION

\n

文件的相对文件路径是相对于项目目录VERSIONS的根目录的。这与方法 B 不同,方法 B 中的相对导入是相对于导入包根(即.my-projectmy_package

\n

我们\xe2\x80\x99已经用方法C完成了。

\n

优点和缺点

\n

方法 A 可能被认为具有不需要附加文件来设置版本的优点(因为,除了在setup.cfg任何情况下都需要的 之外,方法 A 仅使用__init__.py,它同样已经存在)。然而,使用单独的版本号文件有其优点,即版本号的设置位置显而易见。在方法 A 中,派某人去更改版本号,但他\xe2\x80\x99 并不知道版本号的存储位置,这可能需要一段时间;在 中查找\xe2\x80\x99 并不明显__init__.py

\n

方法 C 似乎比方法 B 更有优势,因为方法 B 需要修改两个文件(__init__.py__version__.py),而方法 C 只需要修改一个文件(VERSION)。方法 B 唯一可能的抵消优势是它__version__.py是一个允许嵌入注释的 Python 文件,方法 C\xe2\x80\x99sVERSION不允许。

\n

  • 非常详细,谢谢。可能值得一提的是,使用方法 A 和 B 在代码中使用版本号时,您可以使用 `from my-package.__init__ import __version__` (2认同)