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中被明确排除在外,因为实际观察到的行为的复杂性。
目前,用户要获得此行为,可以执行以下操作之一
- 对于仅包含 Python 代码的情况,可以通过将相关的源目录添加到
sys.path
(可以通过PYTHONPATH
环境变量从命令行界面配置)来实现。请注意,在这种情况下,用户必须自己安装项目依赖项,并且不会生成入口点或项目元数据。 - setuptools 提供了setup.py develop 机制:它安装一个
pth
文件,该文件在解释器启动时将项目根目录注入到sys.path
中,生成项目元数据,并安装项目依赖项。pip 通过pip install -e命令行界面公开了调用此机制的方法。 - flit 提供了flit install –symlink 命令,该命令将项目文件符号链接到解释器的
purelib
文件夹中,生成项目元数据,并安装依赖项。请注意,这还允许支持资源文件。
正如这些示例所示,可编辑安装可以通过多种方式实现,目前还没有标准的方法。此外,目前尚不清楚谁应该负责实现和定义可编辑安装的含义
- 允许构建后端定义和实现它,
- 允许构建前端定义和实现它,
- 明确定义和标准化可能选项中的一个方法。
本 PEP 的作者认为这里没有一刀切的解决方案,每种实现可编辑效果的方法都有其优缺点。因此,本 PEP 拒绝了选项三,因为社区不太可能就一个解决方案达成一致。此外,问题仍然存在于前端还是构建后端应该承担此责任。 PEP 660 提出由构建后端负责,而本 PEP 则主要提出由前端负责,但如果后端希望这样做,它仍然允许后端接管控制权。
基本原理
PEP 517 推迟了“可编辑安装”,因为这会延迟其进一步采用,并且当时还没有就如何实现可编辑安装达成一致。由于setuptools和pip项目的普及,现状得以保留,后端可以通过提供setup.py develop
实现来实现可编辑模式,用户可以通过pip install -e触发。通过定义构建后端和前端之间的可编辑接口,我们可以消除setup.py
文件及其当前的通信方法。
术语和目标
本 PEP 的目标是明确界定前端和后端的角色,并赋予每个开发人员最大能力为其用户提供有价值的功能。在本提案中,后端的角色是为可编辑安装准备项目,然后向前端提供足够的信息,以便前端能够实现和执行可编辑安装。
后端提供给前端的信息是一个符合PEP 427中现有规范的轮包。关于归档本身的轮包元数据({distribution}-{version}.dist-info/WHEEL
)还必须包含键Editable
,其值为true
。
但是,它不应在轮包中提供项目文件,而应提供一个editable.json
文件(位于轮包的根级别),该文件定义了前端要公开的文件。此文件的内容被表述为源代码树绝对路径到方案映射中解释器目标路径的相对路径的映射。
满足前两段描述的轮包即为虚拟轮包。前端的角色是获取虚拟轮包并以可编辑模式安装项目。它实现此目标的方式完全取决于前端,并被视为实现细节。
可编辑安装模式意味着要安装的项目的源代码在本地目录中可用。一旦项目以可编辑模式安装,对本地源代码树中项目代码的一些更改将无需新的安装步骤即可生效。至少,对安装时存在的非生成文件的文本的更改应该在随后导入包时反映出来。
某些类型的更改,例如添加或修改入口点或新的依赖项,需要新的安装步骤才能生效。这些更改通常在构建后端配置文件(例如pyproject.toml
)中进行。此要求与用户普遍期望一致,即此类修改仅在重新安装后才会生效。
虽然用户期望可编辑安装的行为与标准安装相同,但这并不总是可能的,并且可能与其他用户期望相冲突。根据前端实现可编辑模式的方式,可能会出现一些差异,例如在源代码树或解释器的安装路径中存在其他文件(与典型安装相比)。
前端应尽量减少可编辑安装和标准安装行为之间的差异,并记录已知的差异。
作为参考,非可编辑安装的工作原理如下
机制
本 PEP 向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_editable
的metadata_directory
参数,或者在将其设置为除None
以外的任何值时引发异常。
源目录可能是只读的,在这种情况下,后端可能会引发一个错误,前端可以将其显示给用户。后端可以在缓存位置或临时目录中存储中间工件。任何缓存的存在或不存在都不应对构建的最终结果产生重大影响。
editable.json
的内容**必须**通过以下 JSON 模式验证
{
"$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": ""}
,这意味着在目标解释器中purelib
路径的根目录下公开/me/project/src
中的所有文件和模块。
构建前端依赖项
构建前端负责为构建后端生成虚拟轮子设置环境。此处也适用于PEP 517对构建轮子钩子的所有建议。
前端依赖项
前端必须完全按照PEP 427中定义的安装虚拟轮子。此外,还负责安装editable.json
文件中定义的文件。它执行的方式留给前端决定,并鼓励前端与用户沟通所选择的方法以及该解决方案将具有的限制。
前端必须在已安装分发的.dist-info
目录中创建一个direct_url.json
文件,符合PEP 610。url
值必须是指向项目目录(即包含pyproject.toml
的目录)的file://
URL,dir_info
值必须为{'editable': true}
。
前端可以在以可编辑模式安装时依赖于prepare_metadata_for_build_editable
钩子。
如果前端得出结论,它无法使用构建后端提供的信息实现可编辑安装,则应失败并引发错误以向用户说明原因。
前端可能会实现一种或多种可编辑安装机制,并可以由用户选择最适合其用例的一种。例如,pip 可以添加一个可编辑模式标志,并允许用户在pth
文件或符号链接之间进行选择(pip install -e . --editable-mode=pth
与pip install -e . --editable-mode=symlink
)。
可编辑实现示例
为了展示如何使用此 PEP,我们现在将介绍一些案例研究。请注意,提供的解决方案仅用于说明目的,对于前端/后端不具有规范性。
将源代码树原样添加到解释器
这是最简单的实现之一,它会将源树按原样添加到解释器的方案路径中,虚拟轮子中的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.py
和B.py
。它们是源目录中的两个独立文件;但是,在构建轮子时,它们会被合并到一个大型文件中project.py
。在这种情况下,使用此 PEP,后端可以生成一个导入钩子,在导入时读取源文件并在内存中合并它们,然后再将其具体化为模块。 - 自动更新过时的 C 扩展:后端可以生成一个导入钩子,检查 C 扩展源文件的最后修改时间戳。如果它大于当前的 C 扩展二进制文件,则在导入前通过调用编译器触发更新。
被拒绝的想法
此 PEP 与PEP 660存在竞争关系,并拒绝该提案,因为我们认为实现可编辑安装的机制应该在前端而不是构建后端中。此外,此方法允许生态系统使用替代方法来实现可编辑安装效果(例如,在sys.path
上插入路径或符号链接,而不仅仅是暗示该 PEP 所描述的后端中的松散轮子模式)。
最重要的是,PEP 660不允许使用符号链接来公开代码和数据文件,而不扩展轮子文件标准以支持符号链接。目前尚不清楚如何扩展轮子格式以支持引用非轮子本身内的文件,而是仅在本地磁盘上可用的文件。需要注意的是,后端本身(或后端生成的代码)不得生成这些符号链接(例如,在解释器启动时),因为这会与前端对需要卸载的文件的记录冲突。
最后,PEP 660仅添加了对purelib
和platlib
文件的支持。它有意避免支持轮子格式支持的其他类型的信息:include
、data
和scripts
。通过此路径,前端可以通过符号链接机制以尽力而为的方式支持这些文件(尽管此功能并非普遍可用——在 Windows 上需要启用)。我们认为,添加对这些文件类型的尽力而为的支持,而不是完全排除支持它们的可能性,是有益的。
版权
本文档置于公共领域或根据 CC0-1.0-Universal 许可证,以更具许可性的为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0662.rst
上次修改时间:2023-09-09 17:39:29 GMT