Python启动配置:.site.toml文件规范

PEP 829 – 通过 .site.toml 文件实现结构化启动配置

摘要

本PEP提出一种基于TOML的配置文件格式,用于替代解释器启动期间 site.py 所使用的 .pth 文件机制。新格式使用名为 <package>.site.toml 的文件,为扩展 sys.path 和执行包初始化代码提供结构化配置,取代了当前将路径配置与任意代码执行混为一谈的临时 .pth 格式。

动机

Python的 .pth 文件(启动时由 Lib/site.py 处理)支持两个功能:

  • 扩展 sys.path – 文件中不以 #import 开头的行,指定要追加到 sys.path 的目录。相对路径隐式地以 site-packages 目录为基准。
  • 执行代码 – 以 import(或 import\t)开头的行,通过将源字符串传递给 exec() 立即执行。

此设计存在若干问题:

  • 代码执行是实现带来的副作用。以 import 开头的行可通过分号分隔多个语句进行扩展。只要待执行代码位于同一行,在处理 .pth 文件时就会全部执行。
  • .pth 文件本质上是非结构化的,导致内容难以推理或验证,通常甚至难以阅读。它将两个可能有用的功能混杂在一起,却带有不同的安全约束,且无法分离这些关注点。
  • 由于 .pth 文件缺乏结构,也就无法表达元数据,无法使格式具备未来兼容性,也没有定义内容的执行或处理顺序。
  • 在解释器启动期间对文件内容使用 exec() 会暴露很大的攻击面。
  • 没有显式的入口点概念,而入口点已是 Python 打包领域中一种公认的模式。那些需要在启动时执行代码和初始化的包滥用了 import 行,而不是显式声明入口点。

规范

本PEP定义了一种名为 <package>.site.toml 的新文件格式,以解决 .pth 文件存在的所有问题。与 .pth 文件类似,<package>.site.toml 文件会在 Python 启动时由 site.py 模块处理,这意味着禁用 site.py-S 选项同样会禁用 <package>.site.toml 文件。标准库的 tomllib 包用于读取和处理 <package>.site.toml 文件。

<package>.site.toml 文件的存在会取代并行的 <package>.pth 文件。这既提供了轻松的迁移路径,也支持与旧版 Python 并存。

任何解析错误都会导致整个 <package>.site.toml 文件被忽略且不被处理(但它仍然会取代任何并行的 <package>.pth 文件)。导入入口点模块或调用入口点函数时发生的任何错误都会被报告,但不会中止 Python 可执行文件。

文件命名与发现

  • .pth 文件一样,包可以选择性地安装单个 <package>.site.toml 文件,这与当前的 .pth 文件约定一致。
  • 文件命名格式为 <package>.site.toml。其中的 .site 标记用于将其与 site-packages 中可能存在的其他 TOML 文件区分开,并描述该文件的用途(由 site.py 处理)。
  • <package> 前缀应当与包名匹配,但与 .pth 文件一样,解释器并不强制这一点。构建后端和安装工具可以自行选择施加更严格的约束。
  • 包名(即 <package> 前缀)必须遵循标准的名称规范化规则。
  • <package>.site.toml 文件位于与当前 .pth 文件相同的 site-packages 目录中。
  • <package>.site.toml 文件的发现规则与当前 .pth 文件相同。以单个 . 开头的文件名(如 .site.toml)以及具有操作系统级隐藏属性(UF_HIDDENFILE_ATTRIBUTE_HIDDEN)的文件将被排除。
  • 处理顺序按文件名字母顺序排列,与 .pth 行为一致。
  • 如果 <package>.site.toml<package>.pth 共存于同一目录,则只处理 <package>.site.toml 文件。换句话说,<package>.site.toml 文件的存在会取代并行的 <package.pth> 文件,即使 TOML 文件的格式无效也是如此。

处理模型

在发生任何处理(即路径扩展或入口点执行)之前,会先读取并解析指定 site-packages 目录中的所有 <package>.site.toml 文件到一个中间数据结构。这种两阶段方法(先读取后处理)能够实现:

  • 未来的策略机制可以在执行前检查并修改收集到的数据(例如,禁用特定包的入口点或强制执行路径限制)。注意:此类策略框架明确不属于本PEP的范围。
  • 未来对路径扩展和入口点执行处理进行更细粒度的控制。例如,可以设想特殊的 -X 选项、环境变量或其他类型的配置,只允许路径扩展,或者显式管理入口点的允许或拒绝列表。注意:此类配置选项明确不属于本PEP的范围。
  • 更好的错误报告。所有解析、格式和数据类型错误都可以在任何处理发生之前呈现出来。

在每个 site-packages 目录内,处理顺序如下:

  1. 发现并解析所有 <package>.site.toml 文件,按字母顺序排序。
  2. 处理解析后的 TOML 文件中的所有 [paths] 条目。
  3. 执行解析后的 TOML 文件中的所有 [entrypoints] 条目。
  4. 处理任何未被 <package>.site.toml 文件取代的剩余 <package>.pth 文件。

这确保了在任何入口点代码运行之前,路径扩展已经就位,并且 <package>.site.toml 声明的路径对入口点导入和 <package>.pthimport 行都可用。

TOML 文件模式

一个 <package>.site.toml 文件被定义为包含三个部分,所有部分都是可选的:

[metadata]
schema_version = 1

[paths]
dirs = ["../lib", "/opt/mylib", "{sitedir}/extra"]

[entrypoints]
init = ["foo.startup:initialize", "foo.plugins"]
[metadata] 部分

此部分包含包和/或文件的元数据。唯一定义的键是可选的 schema_version 键。

  • schema_version (整数,推荐):TOML 文件模式的版本号。对于本规范,必须为整数 1。如果存在,Python 会保证向前兼容的处理方式:未来版本将根据声明的模式处理文件,或者在给出清晰诊断信息后跳过它。如果 schema_version 存在但值不被支持,则跳过整个文件。如果省略 schema_version,文件将在尽力而为的基础上处理,不提供向前兼容性保证。

允许并保留额外的键,但为了本PEP的目的,它们将被忽略。

[paths] 部分

定义的键:

  • dirs:一个字符串列表,指定要追加到 sys.path 的目录。

路径条目使用混合解析方案:

  • 相对路径以 site-packages 目录(sitedir)为基准,与当前 .pth 行为一致。例如,在 /usr/lib/python3.15/site-packages/ 下的文件中出现 ../lib,将解析为 /usr/lib/python3.15/lib
  • 绝对路径保持不变。例如,/opt/mylib 完全按原样使用。
  • 支持使用 {name} 语法的占位符变量。占位符 {sitedir} 会展开为找到 <package>.site.toml 文件所在的 site-packages 目录。因此,{sitedir}/relpathrelpath 解析为相同的路径,而带占位符的版本是相对路径形式的显式(且推荐)方式。

虽然本PEP只定义了 {sitedir},但未来的PEP可能会定义额外的占位符变量(例如 {prefix}{exec_prefix}{userbase})。

如果 dirs 不是字符串列表,则会发出警告(在 -v 下可见),并跳过该部分。

文件系统上不存在的目录会被静默跳过,与 <package>.pth 行为一致。路径会去重,也与 <package>.pth 行为一致。

[entrypoints] 部分
  • init:一个字符串列表,指定要在启动时执行的入口点引用。每个条目使用标准的 Python 入口点语法:package.module:callable

:callable 部分是可选的。如果省略(例如 package.module),则通过 importlib.import_module() 导入模块,但不会调用任何内容。这涵盖了常见的 <package>.pth 模式 import foo(用于副作用)。

可调用对象在调用时不带参数。

条目按列表中的顺序执行。

打包入口点规范中的 [extras] 语法不被支持;它是安装工具的元数据,在解释器启动时没有意义。

通用模式规则

  • 所有三个部分都是可选的。空的 <package>.site.toml 文件是有效的无操作文件。
  • 未知的表会被静默忽略,为未来的扩展提供向前兼容性。
  • [paths] 总是在 [entrypoints] 之前处理,无论这些部分在 TOML 文件中出现的顺序如何。

错误处理

错误的处理方式因阶段而异:

  • 阶段1:读取与解析:如果 <package>.site.toml 文件无法打开、解码或解析为有效的 TOML,则跳过该文件并继续处理下一个文件。仅在给出 -v(详细模式)时报告错误。重要的是,解析失败的 <package>.site.toml 文件仍然会取代其对应的 <package>.pth 文件。<package>.site.toml 文件的存在足以抑制 <package>.pth 的处理,无论 TOML 文件是否成功解析。这可以防止混淆的双重执行场景,并确保损坏的 <package>.site.toml 能够被注意到,而不是被静默地回退到 <package>.pth 文件所掩盖。
  • 阶段2:执行:如果路径条目或入口点在处理过程中引发异常,则会向 sys.stderr 打印回溯,跳过失败的条目,并继续处理该文件及后续文件中的其余条目。

这是对 .pth 行为的有意改进,后者在遇到第一个错误时会中止处理文件的其余部分。

原理

  • TOML 作为配置格式:TOML 已被 pyproject.toml 使用,并为 Python 打包生态系统所熟悉。它是一种易于人类读写、有助于验证和审计的格式。TOML 文件是结构化和带类型的,并且易于推理。TOML 文件允许轻松的未来扩展性。自 Python 3.11 起,tomllib 模块已在标准库中可用。
  • <package>.site.toml 命名约定:双扩展名清晰地传达了用途:.site 标记表明这是一个站点启动配置文件,而 .toml 表明其格式。这避免了与现在或将来可能存在于 site-packages 中的其他 TOML 文件产生歧义。包名前缀保留了当前每个包一个启动文件(<package>.pth)的约定。
  • 混合路径解析:隐式相对路径连接(与 <package>.pth 行为匹配)提供了平滑的迁移路径,而 {sitedir} 和未来的占位符变量则提供了显式、可扩展的替代方案。与 <package>.pth 文件一样,绝对路径被保留并按原样使用。
  • 使用 importlib.import_module() 替代 exec():使用标准导入机制比 exec() 更具可预测性和可审计性。它与导入系统的钩子和日志记录集成,并且 package.module:callable 语法在 Python 打包生态系统(例如 console_scripts)中已经非常成熟。允许可选的 :callable 语法保留了 <package>.pth 文件的导入副作用功能,使迁移更加容易。
  • 两阶段处理:在执行任何配置之前读取所有配置,为未来的策略机制提供了一个自然的扩展点,并使错误报告更具可预测性。
  • 字母顺序处理,无优先级机制:包是独立安装的,没有外部优先级仲裁者。按字母顺序处理与 <package>.pth 行为匹配,并且易于推理。优先级问题可以通过未来的站点级策略配置来解决。
  • schema_version 为推荐而非必需:要求 schema_version 会使最简单的有效文件变得更冗长。将其设为推荐状态达到了平衡:包含它的文件获得向前兼容性保证,而省略它的简单文件仍然可以在尽力而为的基础上工作。
  • 出错时继续而非中止.pth 在第一个错误时中止处理文件其余部分的行为过于苛刻。如果一个包声明了三个入口点而其中一个失败,其余两个仍应运行。

向后兼容性

  • <package>.pth 文件的处理不会被弃用或移除。在每个 site-packages 目录中,<package>.pth<package>.site.toml 文件会被并行发现。这保持了所有现有(迁移前)包的向后兼容性。弃用 <package>.pth 文件不属于本PEP的范围。
  • <package>.site.toml<package>.pth 并存时,<package>.site.toml 具有更高优先级,<package>.pth 文件被跳过,这提供了一条自然的迁移路径,并且易于与不了解 <package>.site.toml 文件的旧版 Python 兼容。
  • site-packages 目录内,所有 <package>.site.toml 文件(路径和入口点)会在任何剩余的 <package>.pth 文件之前被完整处理。
  • 公共 API site.addsitedir() 保持其现有签名,并继续接受 known_paths

安全影响

本PEP改善了解释器启动的安全态势:

  • <package>.site.toml 文件用 importlib.import_module() 和显式的 getattr() 调用取代了 exec(),这些操作更加受约束且可审计。
  • 使用 io.open_code() 读取 <package>.site.toml 文件,确保审计钩子(PEP 578)可以监控文件访问。
  • 两阶段处理模型创建了一个自然点,未来的策略机制可以在该点检查并限制执行内容。
  • package.module:callable 语法将执行限制为可导入的模块及其属性,与可以运行任意代码的 exec() 不同。

整体攻击面并未消除——恶意包仍然可以通过 init 入口点导致任意代码执行——但本PEP提出的机制更加结构化、可审计,并且适用于未来的策略控制。

如何教授

对于包作者

如果包当前提供了 <package>.pth 文件,可以迁移到 <package>.site.toml 文件。

包含目录名的 <package>.pth 文件的等效形式是:

[paths]
dirs = ["my_directory"]

包含 import my_package<package>.pth 文件的等效形式是:

[entrypoints]
init = ["my_package"]

如果 <package>.pth 文件调用特定函数,请使用 module:callable 语法:

[entrypoints]
init = ["my_package.startup:initialize"]

如果 <package>.pth 文件包含任意代码,请将该代码放入启动函数中,并使用 module:callable 语法。

在迁移期间,<package>.pth<package>.site.toml 可以共存。如果同一包同时存在两个文件,则仅处理 <package>.site.toml。因此,建议与旧版 Python 兼容的包同时提供这两个文件。

对于工具开发者

构建后端和安装工具应生成 <package>.site.toml 文件,作为 <package>.pth 文件的补充或替代,具体取决于包的 Python 支持矩阵。TOML 格式易于使用 tomllib(用于读取)或字符串格式化(用于写入,因为模式很简单)以编程方式生成。

构建后端应该确保 <package> 前缀与包名匹配。

安装工具可以验证或强制 <package> 前缀与包名匹配。

参考实现

参考实现作为对 Lib/site.py 的修改提供,增加了以下内容:

  • _SiteTOMLData – 一个 __slots__ 类,用于保存单个 <package>.site.toml 文件的解析数据(metadatadirsinit)。
  • _read_site_toml(sitedir, name) – 读取并解析单个 <package>.site.toml 文件,验证类型,并返回 _SiteTOMLData 实例,出错时返回 None
  • _process_site_toml_paths(toml_data_list, known_paths) – 处理所有已解析文件中的 [paths].dirs,展开占位符并适当地将目录添加到 sys.path
  • _process_site_toml_entrypoints(toml_data_list) – 执行所有已解析文件中的 [entrypoints].init
  • 修改后的 addsitedir() – 编排三阶段流程:发现并解析 <package>.site.toml 文件,处理路径和入口点,然后处理剩余的 <package>.pth 文件。

测试在 Lib/test/test_site.pySiteTomlTests 类中提供。

被拒绝的想法

  • 单个站点级配置文件而非每个包一个文件:曾考虑过但被拒绝,因为它需要独立安装的包之间进行协调,并且无法反映工具已经理解的 <package>.pth 约定。
  • JSON 替代 TOML:JSON 缺乏注释,对人不友好。TOML 已通过 pyproject.toml 成为 Python 生态系统中的标准配置格式。
  • YAML 替代 TOML:标准库中没有标准的 YAML 解析器。
  • Python 替代 TOML:Python 是命令式的,TOML 是声明式的。因此 TOML 文件更容易验证和推理。
  • $schema URL 引用:与 JSON 不同,TOML 没有标准的 $schema 约定。一个简单的整数 schema_version 就足够了,并且是自包含的。
  • 必需的 schema_version:要求 schema_version 会使最简单的有效文件更冗长,却没有显著好处。推荐但可选的方法在简洁性和未来兼容性之间取得了平衡。
  • [entrypoints] 中分离 loadexecute:曾考虑将仅导入和可调用入口点拆分为单独的列表,但由于会使执行顺序复杂化而被拒绝。一个包含两种形式的单一 init 列表保持了顺序的明确性。
  • 用于处理顺序的优先级或权重字段:由于包是独立安装的,没有优先级仲裁者。字母顺序处理与 <package>.pth 行为匹配。优先级问题可以通过未来的站点级策略配置文件来解决,而不是每个包的元数据。
  • 向可调用对象传递参数:为了简单和与现有 <package>.pth 导入行为对等,可调用对象在调用时不带参数。未来的PEP可能会定义一个可选的上下文参数(例如,解析后的 TOML 数据或站点信息对象)。

待解决问题

  • <package>.pth<package>.site.toml 共存时是否应该发出警告?
  • 未来的 -X 选项是否应该提供对错误报告、未知表警告和入口点执行的细粒度控制?
  • 可调用对象是否应该接收上下文(例如,<package>.site.toml 文件的路径、解析后的 TOML 数据或站点信息对象)?
  • 除了 {sitedir},还应支持哪些额外的占位符变量?候选包括 {prefix}{exec_prefix}{userbase}

变更历史

暂无。

版权

本文档已置于公共领域,或在更许可的条件下采用 CC0-1.0 通用许可证。FINISHED
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)或者 我的个人博客 https://blog.qife122.com/
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值