Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 662 – 通过虚拟轮镜像实现可编辑安装

作者:
Bernát Gábor <gaborjbernat at gmail.com>
发起人:
Brett Cannon <brett at python.org>
讨论至:
Discourse 帖子
状态:
已拒绝
类型:
标准跟踪
主题:
打包
创建日期:
2021 年 5 月 28 日
发布历史:

决议:
Discourse 帖子

目录

摘要

本文档描述了对构建后端和前端通信的扩展(由 PEP 517 引入),以允许项目通过引入虚拟轮镜像以可编辑模式安装。

动机

在开发过程中,许多 Python 用户倾向于安装他们的库,以便对底层源代码和资源的更改能够自动反映在后续的解释器调用中,而无需额外的安装步骤。这种模式通常被称为“开发模式”或“可编辑安装”。目前,没有标准化的方法可以实现这一点,因为 PEP 517 明确排除了这一点,因为它涉及实际观察到的行为的复杂性。

目前,用户要实现此行为,通常会执行以下操作之一:

  • 仅通过将相关的源目录添加到 sys.path 来处理 Python 代码(可通过 PYTHONPATH 环境变量从命令行界面进行配置)。请注意,在这种情况下,用户必须自行安装项目依赖项,并且不会生成入口点或项目元数据。
  • setuptools 提供了 setup.py develop 机制:这会安装一个 pth 文件,该文件在解释器启动时将项目根目录注入 sys.path,生成项目元数据,并安装项目依赖项。 pip 通过 pip install -e 命令行界面公开调用此机制。
  • flit 提供了 flit install –symlink 命令,该命令将项目文件符号链接到解释器的 purelib 文件夹,生成项目元数据,并安装依赖项。请注意,这允许支持资源文件。

正如这些示例所示,可编辑安装可以通过多种方式实现,目前没有标准的方法。此外,实现和定义可编辑安装由谁负责尚不清楚。

  1. 允许构建后端定义和实现它,
  2. 允许构建前端定义和实现它,
  3. 明确定义和标准化一种方法。

本文档的作者认为,这里没有一种方法适合所有情况,实现可编辑效果的每种方法都有其优点和缺点。因此,本文档拒绝第三种选择,因为它不太可能获得社区的认同。此外,前端或构建后端谁应承担此责任的问题仍然存在。 PEP 660 提议由构建后端负责,而本文档则主要提议由前端负责,但如果后端愿意,也可以让后端控制。

基本原理

PEP 517 推迟了“可编辑安装”,因为它会进一步延迟其采用,并且在如何实现可编辑安装方面没有达成一致。由于 setuptoolspip 项目的流行,现状得以维持,后端可以通过提供 setup.py develop 实现来实现可编辑模式,用户可以通过 pip install -e 触发。通过定义构建后端和前端之间的可编辑接口,我们可以消除 setup.py 文件及其当前的通信方法。

术语和目标

本文档旨在明确区分前端和后端的角色,并赋予它们最大程度地为用户提供有价值功能的能力。在此提议中,后端的角色是为可编辑安装准备项目,然后向前端提供足够的信息,以便前端可以实现和强制执行可编辑安装。

后端提供给前端的信息是一个遵循 PEP 427 中现有规范的轮镜像。轮镜像关于存档本身的元数据({distribution}-{version}.dist-info/WHEEL)还必须包含 Editable 键,其值为 true

然而,它不提供轮镜像内的项目文件,而是必须提供一个 editable.json 文件(位于轮镜像的根级别),该文件定义了要由前端公开的文件。此文件的内容被构造成将绝对源树路径映射到目标解释器中某个方案映射下的相对目标目录路径。

满足前两段的轮镜像称为虚拟轮镜像。前端的职责是获取虚拟轮镜像并将项目以可编辑模式安装。它实现这一目标的方式完全取决于前端,并被视为实现细节。

可编辑安装模式意味着正在安装的项目的源代码可以在本地目录中找到。一旦项目以可编辑模式安装,对本地源树中的项目代码所做的某些更改将在后续导入包时生效,而无需新的安装步骤。至少,在安装时存在的非生成文件的文本更改应在后续导入包时反映出来。

某些类型的更改,例如添加或修改入口点或新依赖项,需要新的安装步骤才能生效。这些更改通常在构建后端配置文件(例如 pyproject.toml)中进行。此要求与用户普遍期望此类修改仅在重新安装后才生效的期望一致。

虽然用户期望可编辑安装的行为与标准安装完全相同,但这可能并非总是可能,并且可能与其他用户期望相冲突。根据前端实现可编辑模式的方式,可能会显示一些差异,例如(与典型安装相比)附加文件的存在,这些文件可能位于源树或解释器的安装路径中。

前端应尽量减少可编辑安装和标准安装行为之间的差异,并记录已知的差异。

供参考,非可编辑安装的工作方式如下:

  1. 开发人员正在使用一个我们称之为前端的工具来驱动项目开发(例如,pip)。当用户想要触发项目构建和安装时,他们将与前端进行通信。
  2. 前端使用构建前端来触发轮镜像的构建(例如,build)。构建前端使用 PEP 517构建后端(例如 setuptools)进行通信——构建后端安装在 PEP 518 环境中。调用后,后端返回一个轮镜像。
  3. 前端获取轮镜像并将其提供给安装程序(例如,installer)以将轮镜像安装到目标 Python 解释器中。

机制

本文档在 PEP 517 后端接口中添加了两个可选钩子。其中一个钩子用于指定可编辑安装的构建依赖项。另一个钩子通过构建前端返回前端创建可编辑安装所需的信息。

get_requires_for_build_editable

def get_requires_for_build_editable(config_settings=None):
    ...

此钩子必须返回一个额外的字符串序列,其中包含 PEP 508 依赖项规范,这些依赖项超出 pyproject.toml 文件中指定的依赖项。前端必须确保在调用 build_editable 钩子的构建环境中可用这些依赖项。

如果未定义,默认实现等同于返回 []

prepare_metadata_for_build_editable

def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
    ...

必须在指定的 metadata_directory 中创建一个包含轮镜像元数据的 .dist-info 目录(即,创建一个类似 {metadata_directory}/{package}-{version}.dist-info/ 的目录)。此目录必须是轮镜像规范中定义的有效 .dist-info 目录,但可以不包含 RECORD 或签名。钩子还可以在此目录中创建其他文件,构建前端必须保留但否则忽略这些文件;此处的意思是,在元数据依赖于构建时决策的情况下,构建后端可能需要将这些决策记录在某种便捷格式中,以供实际轮镜像构建步骤重用。

它必须返回所创建 .dist-info 目录的基本名称(而非完整路径),作为 Unicode 字符串。

如果构建前端需要此信息但未定义该方法,则它应调用 build_editable 并直接查看由此产生的元数据。

build_editable

def build_editable(self, wheel_directory, config_settings=None,
                    metadata_directory=None):
    ...

必须构建一个 .whl 文件,并将其放置在指定的 wheel_directory 中。它必须返回创建的 .whl 文件的基本名称(不是完整路径),作为 Unicode 字符串。轮镜像文件必须是虚拟轮镜像类型,如术语部分所定义。

如果构建前端先前调用了 prepare_metadata_for_build_editable 并且依赖于此调用生成的轮镜像具有与先前调用匹配的元数据,则它应将创建的 .dist-info 目录的路径作为 metadata_directory 参数提供。如果提供了此参数,则 build_editable 必须生成具有相同元数据的轮镜像。构建前端提供的目录必须与 prepare_metadata_for_build_editable 创建的目录相同,包括它创建的任何未识别的文件。

不提供 prepare_metadata_for_build_editable 钩子的后端可以默默忽略 build_editablemetadata_directory 参数,或者当该参数设置为 None 以外的值时引发异常。

源目录可以是只读的,在这种情况下,后端可能会引发一个前端可以向用户显示的错误。后端可以将中间构件存储在缓存位置或临时目录中。任何缓存的存在或不存在都不应对构建的最终结果产生实质性影响。

editable.json 的内容必须通过以下 JSON schema 验证:

{
  "$schema": "https://json-schema.fullstack.org.cn/draft-07/schema",
  "$id": "http://pypa.io/editables.json",
  "type": "object",
  "title": "Virtual wheel editable schema.",
  "required": ["version", "scheme"],
  "properties": {
    "version": {
      "$id": "#/properties/version",
      "type": "integer",
      "minimum": 1,
      "maximum": 1,
      "title": "The version of the schema."
    },
    "scheme": {
      "$id": "#/properties/scheme",
      "type": "object",
      "title": "Files to expose.",
      "required": ["purelib", "platlib", "data", "headers", "scripts"],
      "properties": {
        "purelib": { "$ref": "#/$defs/mapping" },
        "platlib": { "$ref": "#/$defs/mapping" },
        "data": { "$ref": "#/$defs/mapping" },
        "headers": { "$ref": "#/$defs/mapping" },
        "scripts": { "$ref": "#/$defs/mapping" }
      },
      "additionalProperties": true
    }
  },
  "additionalProperties": true,
  "$defs": {
    "mapping": {
      "type": "object",
      "description": "A mapping of source to target paths. The source is absolute path, the destination is relative path.",
      "additionalProperties": true
    }
  }
}

例如

{
   "version": 1,
   "scheme": {
      "purelib": {"/src/tree/a.py": "tree/a.py"},
      "platlib": {},
      "data": {"/src/tree/py.typed": "tree/py.typed"},
      "headers": {},
      "scripts": {}
   }
}

方案路径将项目源绝对路径映射到目标目录的相对路径。我们允许后端通过使用映射来更改项目布局,从项目源目录到解释器将看到的内容。

例如,如果后端返回 "purelib": {"/me/project/src": ""},则意味着将 /me/project/src 内的所有文件和模块暴露在目标解释器 purelib 路径的根目录下。

构建前端要求

构建前端负责为构建后端设置生成虚拟轮镜像的环境。 PEP 517 中关于构建轮镜像钩子的所有建议也适用于此。

前端要求

前端必须完全按照 PEP 427 中的定义来安装虚拟轮镜像。此外,它还负责安装 editable.json 文件中定义的文件。实现方式留给前端自行决定,并鼓励前端与用户沟通所选方法的具体细节以及该解决方案的局限性。

前端必须在已安装分发的 .dist-info 目录中创建一个 direct_url.json 文件,该文件符合 PEP 610url 值必须是一个 file:// URL,指向项目目录(即包含 pyproject.toml 的目录),并且 dir_info 值必须是 {'editable': true}

前端可以在以可编辑模式安装时依赖 prepare_metadata_for_build_editable 钩子。

如果前端得出结论,它无法使用构建后端提供的信息实现可编辑安装,则应失败并引发错误,以向用户说明原因。

前端可以实现一种或多种可编辑安装机制,并允许用户选择最适合用户用例的一种。例如,pip 可以添加一个可编辑模式标志,并允许用户在 pth 文件或符号链接之间进行选择(pip install -e . --editable-mode=pthpip install -e . --editable-mode=symlink)。

可编辑实现的示例

为了说明本文档的用法,我们将展示一些案例研究。请注意,提供的解决方案仅用于说明目的,并非对前端/后端的规范。

将源代码树原样添加到解释器

这是最简单的实现之一,它将源代码树原样添加到解释器的方案路径中,虚拟轮镜像中的 editable.json 可能如下所示:

{
  {"version": 1, "scheme": {"purelib": {"<project dir>": "<project dir>"}}}
}

然后前端可以:

  • 在解释器启动期间将源目录添加到目标解释器的 sys.path。这是通过在目标解释器的 purelib 文件夹中创建一个 pth 文件来完成的。setuptools 目前就是这样做,并且 pip install -e 也翻译成这样。此解决方案速度快且跨平台兼容。但是,这会将整个源树暴露给系统,可能会暴露在标准安装情况下本不可用的模块。
  • 符号链接文件夹或其中的单个文件。此方法是 flit 通过其 flit install –symlink 实现的。此解决方案需要当前平台支持符号链接。尽管如此,它可能允许符号链接单个文件,这可以解决包含应从源树中排除的文件的问题。

使用自定义导入器

为了实现更健壮、更动态的构建后端与目标解释器之间的协作,我们可以利用导入系统允许注册自定义导入器。有关更多详细信息,请参阅 PEP 302,以及 editables 作为示例。后端可以在可编辑构建期间生成新的导入器(或将其作为附加依赖项安装),并通过添加 pth 文件在解释器启动时进行注册。

{
   "version": 1,
   "scheme": {
      "purelib": {
        "<project dir>/.editable/_register_importer.pth": "<project dir>/_register_importer.pth".
        "<project dir>/.editable/_editable_importer.py": "<project dir>/_editable_importer.py"
      }
    }
  }
}

后端在此注册了一个钩子,该钩子会在导入新模块时被调用,从而实现动态和按需的功能。这可能很有用的潜在用例:

  • 公开源文件夹,但遵守模块排除:后端可以生成一个导入钩子,该钩子在允许源文件加载器发现源目录中的文件之前,会先检查排除表。
  • 对于一个项目,假设有两个模块 A.pyB.py。这两个文件在源目录中是分开的;然而,在构建轮镜像时,它们被合并成一个主文件 project.py。在这种情况下,使用本文档,后端可以生成一个导入钩子,该钩子在导入时读取源文件,并在内存中将它们合并,然后再将其实现为一个模块。
  • 自动更新过期的 C 扩展:后端可以生成一个导入钩子,该钩子检查 C 扩展源文件的最后修改时间戳。如果它大于当前的 C 扩展二进制文件,则通过在导入前调用编译器来触发更新。

被拒绝的想法

本文档与 PEP 660 存在竞争关系,并拒绝了该提议,因为我们认为实现可编辑安装的机制应位于前端而不是构建后端。此外,这种方法允许生态系统使用替代方法来实现可编辑安装效果(例如,在 sys.path 中插入路径或使用符号链接,而不是像该 PEP 所描述的那样仅暗示后端的宽松轮镜像模式)。

突出的是,PEP 660 不允许使用符号链接来公开代码和数据文件,而无需扩展轮镜像文件标准以支持符号链接。不清楚轮镜像格式如何扩展以支持指向本地磁盘上的文件而不是轮镜像内文件的符号链接。需要注意的是,后端本身(或后端生成的代码)不得生成这些符号链接(例如,在解释器启动时),因为这会与前端对需要卸载的文件进行记账发生冲突。

最后,PEP 660 仅支持 purelibplatlib 文件。它故意避免支持轮镜像格式支持的其他类型的信息:includedatascripts。通过此路径,前端可以通过符号链接机制在尽力而为的基础上支持这些(尽管此功能并非普遍可用 - 在 Windows 上需要启用)。我们认为最好支持这些文件类型的尽力而为的支持,而不是完全排除支持的可能性。


来源:https://github.com/python/peps/blob/main/peps/pep-0662.rst

最后修改:2025-02-01 08:55:40 GMT