PEP 735 – pyproject.toml 中的依赖项组

猫勺猫勺 03-26 179 阅读 0 评论

抽象

PEP 指定了一种将包需求存储在文件中的机制,以便它们不包含在任何构建的 项目。pyproject.toml

这适用于创建命名的依赖项组,类似于文件,启动器、IDE 和其他工具可以找到这些依赖项和 按名称标识。requirements.txt

此处定义的功能称为“依赖项组”。

赋予动机

Python 社区没有两个主要用例 标准化答案:

  • 应该如何定义包的开发依赖关系?

  • 应该如何为不构建的项目定义依赖关系 发行版(非包项目)?

为了支持这两种需求,有两种相似的常见解决方案 对于此提案:

  • requirements.txt文件

  • 套餐附加功能

这两个文件都有限制,这 标准寻求克服。requirements.txtextras

请注意,上述两个用例描述了两种不同类型的项目 本 PEP 寻求支持:

  • Python 包,例如库

  • 非包项目,例如数据科学项目

文件的局限性

许多项目可以定义一个或多个 iles, 并且可以将它们安排在项目根目录(例如 和 ) 或在目录中(例如 和 )。但是,有 以这种方式使用需求文件的主要问题:requirements.txtrequirements.txttest-requirements.txtrequirements/base.txtrequirements/test.txt

没有标准化的命名约定,因此工具可以发现或 按名称使用这些文件。

requirements.txt文件不是标准化的,而是提供 选项设置为 。pip

因此,很难根据文件定义工具行为。发现或识别它们并非易事 名称,其内容可能包含包说明符和其他选项的组合。requirements.txtpip

缺乏内容标准也意味着它们是 不能移植到任何希望处理它们的替代工具。requirements.txtpip

此外,文件需要每个依赖项列表的文件。 对于某些用例,这使得依赖分组的边际成本很高, 相对于他们的利益。 更简短的声明有利于具有许多小组的项目 依赖。requirements.txt

与此相反,依赖项组是在众所周知的位置定义的 在具有完全标准化的内容中。他们不仅会有 立竿见影的效用,但它们也将作为未来的起点 标准。pyproject.toml

的局限性

extras是在表中声明的其他包元数据。它们为以下列表提供名称 作为包元数据的一部分发布的包说明符,以及 用户可以在该名称下请求,如 使用额外的安装。[project.optional-dependencies]pip install 'foo[bar]'foobar

因为是包元数据,所以当项目时它们不可用 不构建发行版(即,不是一个包)。extras

对于包的项目,是定义的通用解决方案 发展依赖性,但即使在这些情况下,它们也有 缺点:extras

  • 因为 an 定义了可选的附加依赖项,所以它不是 可以在不安装当前软件包的情况下安装 AN 并且 其依赖项。extraextra

  • 因为它们是用户可安装的,所以是公共接口的一部分 对于包。因为是 published,所以包开发人员经常是 关注确保他们的开发附加功能不会与 面向用户的附加功能。extrasextras

理由

此 PEP 定义了表中列表中的需求数据的存储。 选择此名称是为了匹配要素的规范名称 (“依赖组”)。[dependency-groups]

这种格式应该尽可能简单易学,具有格式 在许多情况下,与现有文件非常相似。每个列表 in 定义为包说明符的列表。为 例:requirements.txt[dependency-groups]

[dependency-groups]
test = ["pytest>7", "coverage"]

有许多文件的用例需要 不能用 PEP 508 依赖项说明符表示的数据。这样 字段在依赖项组中无效。包括许多数据和 支持的字段,例如索引服务器、哈希和路径 依赖关系,需要新的标准。该标准为新的标准留出了空间 标准和发展,但不试图支持所有有效内容。requirements.txtpiprequirements.txt

唯一的例外是文件的标志 用于将一个文件包含在另一个文件中。依赖项组支持“include” 含义相似的机制,允许一个依赖组扩展 另一个。-rrequirements.txt

依赖项组具有两个类似于文件的附加功能:requirements.txt

  • 它们不会作为任何构建发行版的一部分发布

  • 安装依赖项组并不意味着安装软件包的 依赖项或包本身

使用案例

以下用例被视为此 PEP 的重要目标。他们是 在用例附录中更详细地定义。

  • 通过非 Python 打包构建过程部署的 Web 应用程序

  • 具有未发布的开发依赖项组的库

  • 具有依赖项组但没有核心包的数据科学项目

  • 输入数据以生成锁文件(依赖项组通常不应 用作锁定依赖项数据的位置)

  • 将数据输入到环境管理器,例如 tox、Nox 或 Hatch

  • 可配置的 IDE 发现测试和 linter 要求

关于 Poetry 和 PDM 依赖组

现有的 Poetry 和 PDM 工具已经提供了一个功能,每个工具都调用了 “依赖组”。但是,没有任何指定集合的标准 的依赖关系,每个工具都以特定于工具的方式定义这些依赖项,在 表格的相关部分。[tool]

(PDM 还对某些依赖项组使用了附加功能,并且与该概念重叠 大量附加功能。

此 PEP 不支持 Poetry 和 PDM 的所有功能,它们与 的文件一样,支持多个非标准扩展 到常见的依赖项说明符。requirements.txtpip

此类工具应该可以使用标准化的依赖项组,例如 扩展自己的依赖组机制。 但是,定义一种新的数据格式来取代现有的 Poetry 和 PDM 解决方案不是目标。这样做需要标准化几个 这些支持的其他功能,例如路径依赖项 工具。

依赖项组不是隐藏的额外内容

依赖项组与未发布的额外内容非常相似。 但是,有两个主要特征将它们与附加功能区分开来 进一步:

  • 它们支持非包项目

  • 安装依赖项组并不意味着安装软件包的 依赖项(或包本身)

未来的兼容性和无效数据

依赖项组旨在在未来的 PEP 中可扩展。 但是,依赖项组也应可由多个工具使用 单个 Python 项目。 使用相同数据的多个工具,一个工具可能会实现 一个扩展依赖组的未来 PEP,而另一个则不扩展。

为了在这种情况下支持用户,此 PEP 定义并建议验证 工具仅检查它们正在使用的依赖项组的行为。 这允许使用多个工具,使用不同版本的依赖组数据, 在 中共享单个表。pyproject.toml

规范

此 PEP 在名为 的文件中定义一个新部分(表)。该表包含任意 用户定义键的数量,每个键的值都有一个列表 要求(定义见下文)。这些键必须是有效的非规范化名称, 并且必须在比较之前进行归一化。pyproject.tomldependency-groupsdependency-groups

工具应倾向于通过以下方式向用户显示原始的、非规范化的名称 违约。如果在规范化后遇到重复的名称,工具应 发出错误。

下面的要求列表可能包含字符串、表格 (Python 中的“dicts”),或字符串和表的混合。dependency-groups

要求列表中的字符串必须是有效的依赖项说明符, 如 PEP 508 中所定义。

需求列表中的表必须是有效的依赖对象说明符, 定义如下。

依赖项对象说明符

依赖项对象说明符是定义零个或多个依赖项的表。

此 PEP 仅标准化一种类型的依赖项对象说明符,即 “依赖组包括”。将来的标准中可能会添加其他类型。

依赖项组包括

Dependency Group Include 包括另一个 Dependency 的依赖项 当前依赖项组中的组。

include 被定义为一个表,只有一个键,其 value 是一个字符串,是另一个依赖项组的名称。"include"

例如,是一个 include 扩展为 依赖项组的内容。{include = "test"}test

包含被定义为与命名的内容完全等效 依赖项组,插入到当前组的包含位置。 例如,如果是一个组,并且是另一个组,则应该 评估到“依赖项组包括”展开的时间。foo = ["a", "b"]bar = ["c", {include = "foo"}, "d"]bar["c", "a", "b", "d"]

依赖项组包含可以多次指定同一包。工具 不应删除重复数据或以其他方式更改 包括。例如,给定下表:

[dependency-groups]
group-a = ["foo"]
group-b = ["foo>1.0"]
group-c = ["foo<1.0"]
all = ["foo", {include = "group-a"}, {include = "group-b"}, {include = "group-c"}]

SHOULD 的解析值为 。 工具应该完全像处理任何其他情况一样处理这样的列表。 他们被要求多次处理相同的要求 不同的版本约束。all["foo", "foo", "foo>1.0", "foo<1.0"]

依赖项组包括可能包括包含依赖项组的列表 包括,在这种情况下,这些包含也应扩展。屬地 组包含不得包含周期,如果出现以下情况,工具应报告错误 他们检测到一个循环。

Example Dependency Groups 表

下面是一个部分的示例,它使用它来 定义四个依赖项组:、、 和 :pyproject.tomltestdocstypingtyping-test

[dependency-groups]
test = ["pytest", "coverage"]
docs = ["sphinx", "sphinx-rtd-theme"]
typing = ["mypy", "types-requests"]
typing-test = [{include = "typing"}, {include = "test"}, "useful-types"]

请注意,这些依赖项组声明都没有隐式安装 当前包、其依赖项或任何可选依赖项。 使用依赖组(如测试包)需要 用户的配置或工具链也会安装 。例如test.

$TOOL install-dependency-group testpip 
install -e .

可以使用(假设是一个支持安装的工具 依赖组)来构建测试环境。$TOOL

这也允许在不使用 将项目安装为包:docs

$TOOL install-dependency-group docs

包构建

构建后端不得在构建的发行版中包含依赖组数据,因为 包元数据。这意味着 sdists 中的 PKG-INFO 和 wheels 中的 METADATA 不包含任何包含依赖项组的可引用字段。

在动态元数据的评估中使用依赖组是有效的,sdist 中包含的文件自然仍会包含该表。但是,表内容不是 已发布包的接口。pyproject.toml[dependency-groups]

安装依赖项组

支持依赖项组的工具有望提供新的选项和 允许用户从依赖项组安装的接口。

没有定义用于表示包的依赖组的语法,对于两个 原因:

  • 引用第三方的依赖组是无效的 来自 PyPI 的包(因为数据被定义为未发布)

  • 不保证有依赖组的当前包 - 部分 其目的是支持非包项目

例如,用于安装依赖项组的可能的 pip 接口 将:

pip install --dependency-groups=test,typing

请注意,这只是一个示例。此 PEP 未声明任何要求 了解工具如何支持依赖项组的安装。

验证和兼容性

支持依赖项组的工具可能需要在使用数据之前对其进行验证。 但是,实现此类验证行为的工具应小心允许 用于将来对此规范的扩展,以便它们不会不必要地发出 存在新语法时出现错误或警告。

工具在评估或处理无法识别的数据时应出错 依赖项组。

工具不应急切地验证所有依赖项的列表内容 组。

这意味着在存在以下数据的情况下,大多数工具将允许 要使用的组,并且仅在该组 使用:foobar

[dependency-groups]
foo = ["pyparsing"]
bar = [{set-phasers-to = "stun"}]

参考实现

以下参考实现打印依赖项的内容 组为 stdout,换行符分隔。 因此,输出是有效数据。requirements.txt

import re
import sys
import tomllib
from collections import defaultdict

from packaging.requirements import Requirement

def _normalize_name(name: str) -> str:
    return re.sub(r"[-_.]+", "-", name).lower()
    
def _normalize_group_names(dependency_groups: dict) -> dict:
    original_names = defaultdict(list)
    normalized_groups = {}

    for group_name, value in dependency_groups.items():
        normed_group_name = _normalize_name(group_name)
        original_names[normed_group_name].append(group_name)
        normalized_groups[normed_group_name] = value

    errors = []
    for normed_name, names in original_names.items():
        if len(names) > 1:
            errors.append(f"{normed_name} ({', '.join(names)})")
    if errors:
        raise ValueError(f"Duplicate dependency group names: {', '.join(errors)}")

    return normalized_groups
    
def _resolve_dependency_group(
    dependency_groups: dict, group: str, past_groups: tuple[str] = ()
) -> list[str]:
    if group in past_groups:
        raise ValueError(f"Cyclic dependency group include: {group} -> {past_groups}")

    if group not in dependency_groups:
        raise LookupError(f"Dependency group '{group}' not found")

    raw_group = dependency_groups[group]
    if not isinstance(raw_group, list):
        raise ValueError(f"Dependency group '{group}' is not a list")

    realized_group = []
    for item in raw_group:
        if isinstance(item, str):
            # packaging.requirements.Requirement parsing ensures that this is a valid
            # PEP 508 Dependency Specifier
            # raises InvalidRequirement on failure
            Requirement(item)
            realized_group.append(item)
        elif isinstance(item, dict):
            if tuple(item.keys()) != ("include",):
                raise ValueError(f"Invalid dependency group item: {item}")

            include_group = _normalize_name(next(iter(item.values())))
            realized_group.extend(
                _resolve_dependency_group(
                    dependency_groups, include_group, past_groups + (group,)
                )
            )
        else:
            raise ValueError(f"Invalid dependency group item: {item}")

    return realized_group
    
def resolve(dependency_groups: dict, group: str) -> list[str]:
    if not isinstance(dependency_groups, dict):
        raise TypeError("Dependency Groups table is not a dict")
    if not isinstance(group, str):
        raise TypeError("Dependency group name is not a str")
    return _resolve_dependency_group(dependency_groups, group)
    
if __name__ == "__main__":
    with open("pyproject.toml", "rb") as fp:
        pyproject = tomllib.load(fp)

    dependency_groups_raw = pyproject["dependency-groups"]
    dependency_groups = _normalize_group_names(dependency_groups_raw)
    print("\n".join(resolve(pyproject["dependency-groups"], sys.argv[1])))

向后兼容性

在撰写本文时,文件中的命名空间未使用。由于顶级命名空间是 仅供 packaging.python.org 规定的标准使用, 不应有直接的向后兼容性问题。dependency-groupspyproject.toml

安全隐患

此 PEP 引入了用于指定依赖关系的新语法和数据格式 项目中的信息。但是,它没有引入新指定的 用于处理或解决依赖关系的机制。

因此,除了任何 可能已用于安装依赖项的工具 - 即恶意的 可以在此处指定依赖项,就像在文件中指定依赖项一样。requirements.txt

如何教这个

此功能应使用其规范名称“依赖项组”来引用。

基本用法形式应作为典型数据的变体进行教学。标准依赖项说明符 (PEP 508) 可以是 已添加到命名列表。而不是要求 pip 从文件安装,而是安装 pip 或相关的工作流工具 从命名的依赖项组。requirements.txtrequirements.txt

对于新的 Python 用户,可以直接教他们创建一个包含其依赖组的部分,类似于他们如何 目前被教导使用文件。 这也允许新的 Python 用户了解文件 无需了解包构建。 只有表而没有其他表的文件 是有效的。pyproject.tomlrequirements.txtpyproject.tomlpyproject.toml[dependency-groups]

对于新用户和有经验的用户,依赖项组包含将需要 被解释。对于有使用经验的用户,这可以是 描述为 的类似物。对于新用户,应该告诉他们 include 允许一个依赖项组扩展另一个依赖项组。类似配置 接口和 Python 方法可用于解释 通过类比的想法。requirements.txt-rlist.extend

被拒绝的想法

为什么不将每个依赖组定义为一个表?

如果我们的目标是允许将来的扩展,那么定义每个依赖项 group作为一个子表,从而使我们能够将未来的键附加到每个组, 允许最大的未来灵活性。

然而,它也使结构嵌套得更深,因此更难 教和学。这个 PEP 的目标之一是成为一个简单的替代品 适用于许多用例。requirements.txt

为什么不定义一个特殊的字符串语法来扩展依赖项说明符呢?

该规范的早期草案定义了依赖关系的语法形式 组包含和路径依赖关系。

但是,这种方法存在三个主要问题:

  • 它使必须教授的字符串语法复杂化,超出了 PEP 508

  • 生成的字符串始终需要从 PEP 508 中消除歧义 说明符,使实现复杂化

为什么不允许更多非 PEP 508 依赖项说明符?

在讨论中出现了几个需要更具表现力的用例 比 PEP 508 可能实现的说明符。

“路径依赖关系”,指的是本地路径,并引用了[project.dependencies]

但是,这些功能没有现成的标准(除了 的实现细节的事实标准)。pip

因此,尝试在此 PEP 中包含这些功能会导致 范围显着扩大,以尝试标准化这些不同的功能 和行为。pip

特别注意尝试使 可编辑的装置,如 PEP 660 所表达的那样。 但是,尽管可编辑安装的创建是标准化的 后端,可编辑行为对于安装程序来说不是标准化的。 在此 PEP 中包含可编辑内容要求任何支持工具都允许 安装可编辑文件。pip install -e

因此,尽管 Poetry 和 PDM 为其中一些功能提供了语法, 目前,它们被认为不够标准化,无法纳入 依赖项组。

为什么表没有命名为 , , ...?

这个概念有很多可能的名称。 它必须与现有的 和 表一起存在,并且可能还有一个新的依赖项表(在撰写本文时,PEP 725,它 定义表,正在进行中)。[project.dependencies][project.optional-dependencies][external][external]

[run]是早期讨论中的主要提案,但其建议的用法 以一组运行时依赖项为中心。这个 PEP 明确 概述了多组依赖项,这使得 适当的拟合 – 这不仅仅是特定运行时的依赖项数据 context,但适用于多个上下文。[run]

[project.dependency-groups]会提供一个很好的平行于 和 ,但有 非一揽子项目的主要缺点。 需要定义多个键,例如 和 。使用此名称需要重新定义表以允许不存在这些键,否则将施加要求 在非包项目上定义和使用这些键。推而广之,它将 有效地要求任何非打包项目都允许将自己视为一个 包。[project.dependencies][project.optional-dependencies][project]nameversion[project]

为什么pip的计划实施不够充分?

PIP 目前在路线图上有一个功能,可以添加一个 –only-deps 标志。 此标志旨在允许用户安装包依赖项和附加功能 无需安装当前软件包。

它不满足非一揽子项目的需求,也不允许 安装一个没有包依赖项的额外内容。

为什么<环境管理器>不是一个解决方案?

tox、Nox 和 Hatch 等现有环境管理器已经具备 能够将内联依赖项列为其配置数据的一部分。 这满足了许多开发依赖需求,并且清楚地关联了依赖 具有可以运行的相关任务的组。 这些机制是好的,但还不够。

首先,它们没有满足非一揽子项目的需求。

其次,没有其他工具用于访问这些数据的标准。这 对 IDE 和 Dependabot 等高级工具有影响,这些工具无法支持 与这些依赖项组深度集成。(例如,在撰写本文时 Dependabot 不会标记固定在文件中的依赖项。tox.ini

未解决的问题

依赖项组是否可以包含,反之亦然?

一个争论的话题是依赖组应该如何(或是否)与 交互。[project.dependencies][project.optional-dependencies]

可以添加一个额外的依赖对象说明符,用于将 或 数据包含在 依赖项组。但是,此规范的目标是 依赖项组应始终可解析为包列表 无需使用构建后端。因此,包含或需要 要仔细定义动态依赖关系。[project.dependencies][project.optional-dependencies][project.dependencies][project.optional-dependencies]

以相反方向运行的包含 – 包含依赖项组引用的列表,可能重用依赖项 组包含对象作为机制 - 也是可能的,但存在 不同的挑战。这样的添加会在表中引入新的语法,起初并非所有工具都支持这种语法。[project.dependencies][project]

附录A:非Python语言的现有技术

本节主要提供信息,用于记录其他 语言生态系统也解决了类似的问题。

JavaScript 和

在 JavaScript 社区中,包包含规范配置和 data 文件,范围类似于 。pyproject.tomlpackage.json

控制依赖关系数据中的两个键:和 。的作用实际上是相同的 如 中 ,声明 包的直接依赖项。package.json"dependencies""devDependencies""dependencies"[project.dependencies]pyproject.toml

数据

依赖项数据声明为包名称的映射 到版本说明符。package.json

版本说明符支持可能的版本、范围和 其他值,类似于 Python 的 PEP 440 版本说明符。

例如,下面是一个部分文件,声明了一些 依赖:package.json

{    "dependencies": {    
       "@angular/compiler": "^17.0.2",        
       "camelcase": "8.0.0",        
       "diff": ">=5.1.0 <6.0.0"    
    }
}

符号的使用是声明包的范围 owner,用于组织拥有的包。 因此声明一个名为 grouped 的包 在所有权下。@"@angular/compiler"compilerangular

引用 URL 和本地路径的依赖关系

依赖项说明符支持 URL 和 Git 存储库的语法,类似于 到 Python 打包中的规定。

可以使用 URL 代替版本号。 使用时,它们隐式引用包源代码的压缩包。

Git 存储库也可以类似地使用,包括对 committish 的支持 说明符。

与 PEP 440 不同,NPM 允许使用本地路径来打包源代码 依赖项的目录。当这些数据添加到 via 标准命令,路径规范化为 相对路径,从包含 的目录中取出,并带有前缀 跟。例如,以下部分包含一个 对当前目录的同级目录的引用:package.jsonnpm install --savepackage.jsonfile:package.json

{  
  "dependencies": {     
     "my-package": "file:../foo" 
  }
}

官方 NPM 文档指出,本地路径依赖项“不应”发布到公共包中 存储库,但没有说明固有的有效性或无效性 已发布包中的此类依赖项数据。

数据

package.json允许包含名为 的第二节,其格式与 相同。 默认情况下,不会安装中声明的依赖项 当从软件包存储库安装软件包时(例如,作为 依赖项正在解析),但在 包含 ."devDependencies""dependencies""devDependencies"npm installpackage.json

正如支持 URL 和本地路径一样,."dependencies""devDependencies"

另外还有两个相关的部分,其中有 关联。package.json

"peerDependencies"以与 相同的格式声明依赖项列表,但表示这些依赖项是兼容性 声明。 例如,以下数据声明与包版本 2 兼容:"dependencies"foo

{  
  "peerDependencies": {   
       "foo": "2.x"  
  }
}

"optionalDependencies"声明一个依赖项列表,这些依赖项应为 如果可能,则安装,但如果是 不能利用的。它还使用与 相同的映射格式。"dependencies"

“peerDependenciesMeta”

"peerDependenciesMeta"是允许额外控制的部分 关于如何对待。"peerDependencies"

可以通过将包设置为本部分来禁用有关缺少依赖项的警告,如以下示例所示:optional

{  
  "peerDependencies": {    
      "foo": "2.x"   
  },    
  "peerDependenciesMeta": {  
        "foo": {      
              "optional": true       
        }    
   }
}

该命令支持两个选项,以及 它可以控制是否安装“prod”、“dev”、“optional”或“peer”依赖项。npm install--omit--include

“prod”名称是指 下列出的依赖项。"dependencies"

默认情况下,执行时将安装所有四个组 针对源代码树,但这些选项可用于控制安装 更准确地说是行为。 此外,这些值可以在文件中声明,允许 用于控制安装行为的每用户和每个项目配置。npm install.npmrc

红宝石和红宝石

Ruby 项目可能旨在也可能不打算在 Ruby 生态系统。事实上,人们的期望是该语言的大多数用户都这样做 不想生产宝石,也没有兴趣生产自己的包装。 许多教程没有涉及如何生成包,工具链也从未触及 需要为支持的用例打包用户代码。

Ruby 将需求规范拆分为两个单独的文件。

  • Gemfile:仅支持以下形式的需求数据的专用文件 依赖项组

  • <package>.gemspec:用于声明包 (gem) 元数据的专用文件

提供命令的工具是主界面 用于使用数据。bundlerbundleGemfile

该工具负责通过命令从数据构建 Gem。gem.gemspecgem build

Gem文件 & 捆绑包

Gemfile 是 Ruby 文件 包含包含在任意数量的声明中的指令。 指令也可以在声明之外使用,其中 case 它们形成一个隐式未命名的依赖项组。gemgroupgemgroup

例如,以下列表作为项目依赖项。 所有其他依赖项都列在组下:Gemfilerails

source '

gem 'rails'

group :test do
  gem 'rspec'
end

group :lint do
  gem 'rubocop'
end

group :docs do
  gem 'kramdown'  
  gem 'nokogiri'
end

如果用户使用这些数据执行,则所有组都 安装。用户可以通过创建或修改捆绑器配置来取消选择组 中 ,手动或通过 CLI 进行。例如。bundle install.bundle/configbundle config set --local without 'lint:docs'

根据上述数据,无法排除 gem 的顶级用途或按名称引用该隐含分组。'rails'

Gemspec 和打包的依赖项数据

gemspec 文件是 包含 Gem::Specification 实例声明的 ruby 文件。

一个中只有两个字段与包依赖项数据有关。 这些是 和 . 对象还提供用于添加依赖项的方法 动态地,包括(这会添加运行时依赖项)。Gem::Specificationadd_development_dependencyadd_runtime_dependencyGem::Specificationadd_dependency

这是该文件的变体,删除了许多字段或 缩短以简化:rails.gemspec

version = '7.1.2'

Gem::Specification.new do |s|
  s.platform    = Gem::Platform::RUBY  
  s.name        = "rails"  
  s.version     = version  
  s.summary     = "Full-stack web application framework."  
  
  s.license = "MIT"  
  s.author   = "David Heinemeier Hansson"
    
  s.files = ["README.md", "MIT-LICENSE"]
  
  # shortened from the real 'rails' project  
  s.add_dependency "activesupport", version  
  s.add_dependency "activerecord",  version  
  s.add_dependency "actionmailer",  version  
  s.add_dependency "activestorage", version  
  s.add_dependency "railties",      version
end

请注意,没有使用 . 其他一些主流的主要软件包(例如)不使用开发 其 gem 中的依赖关系。add_development_dependencyrubocop

其他项目也使用此功能。例如,利用 开发依赖项,在其中包含以下规范:kramdownRakefile

s.add_dependency "rexml"
s.add_development_dependency 'minitest', '~> 5.0'
s.add_development_dependency 'rouge', '~> 3.0', '>= 3.26.0'
s.add_development_dependency 'stringex', '~> 1.5.1'

开发依赖项的目的只是声明一个隐式组, 作为 的一部分,然后可以由 使用 。.gemspecbundler

有关完整的详细信息,请参阅 有关 Gemfiles 的文档中的指令。 但是,通过示例可以最好地理解开发依赖项和/使用之间的集成。gemspecbundler.gemspecGemfilebundle

Gemspec 开发依赖项示例

考虑以下以 和 的形式进行的简单项目。 文件:Gemfile.gemspeccool-gem.gemspec

Gem::Specification.new do |s|
  s.author = 'Stephen Rosen'  
  s.name = 'cool-gem'  
  s.version = '0.0.1'  
  s.summary = 'A very cool gem that does cool stuff'  
  s.license = 'MIT'  
  
  s.files = []  
  
  s.add_dependency 'rails'  
  s.add_development_dependency 'kramdown'
end

以及:Gemfile

source '
 
gemspec

指令 in 声明对本地的依赖 package, ,在本地可用文件中定义。它还隐式地将所有开发依赖项添加到依赖项中 名为 的组。gemspecGemfilecool-gemcool-gem.gemspecdevelopment

因此,在这种情况下,指令等价于 内容如下:gemspecGemfile

gem 'cool-gem', :path => '.'

group :development do
  gem 'kramdown'
end

附录 B:Python 中的现有技术

在没有任何依赖组的先前标准的情况下,两个已知的工作流 PDM 和 Poetry 等工具定义了自己的解决方案。

本节将主要关注这两种工具作为现有技术的案例 关于 Python 中依赖组的定义和使用。

项目是包

PDM 和 Poetry 都将它们支持的项目视为包。 这允许他们使用标准元数据并与之交互 满足他们的一些需求,并允许他们支持安装 “当前项目”,使用其构建后端进行构建和安装。pyproject.toml

实际上,这意味着 Poetry 和 PDM 都不支持非打包项目。

非标准依赖项说明符

PDM 和 Poetry 通过附加功能扩展了 PEP 508 依赖项说明符 不属于任何共享标准。 但是,这两种工具使用的方法略有不同。

PDM 支持通过以下语法指定本地路径和可编辑安装 看起来像一组参数。例如,以下内容 依赖项组包含一个处于可编辑模式的本地包:pip install

[tool.pdm.dev-dependencies]
mygroup = ["-e file:///${PROJECT_ROOT}/foo"]

这声明了一个依赖项组,其中包含一个本地可编辑项 从目录安装。mygroupfoo

Poetry 将依赖项组描述为表,将包名称映射到 说明符。例如,与上述示例相同的配置可能会在 Poetry 下显示如下:mygroup

[tool.poetry.group.mygroup]
foo = { path = "foo", editable = true }

PDM 将自己限制为字符串语法,而 Poetry 引入了 描述依赖关系。

安装和引用依赖项组

PDM 和 Poetry 都具有特定于工具的安装依赖项支持 组。由于这两个项目都支持自己的锁定文件格式,因此它们还 两者都能够透明地使用依赖项组名称来引用 添加到该组的锁定依赖项数据。

但是,这两个工具的依赖项组都不能从其他工具本机引用 、 或 . 例如,尝试在 下安装依赖项组需要 对 PDM 或 Poetry 的显式调用,以解析它们的依赖项数据并执行 相关安装步骤。toxnoxpiptox

附录 C:使用案例

Web 应用程序

Web 应用程序(例如 Django 或 Flask 应用程序)通常不需要构建一个 分发版,但将其源代码捆绑并交付到部署工具链。

例如,源代码存储库可以将 Python 打包元数据定义为 以及容器化或其他生成管道元数据 (, 等)。 Python 应用程序是通过将整个存储库复制到 构建上下文,安装依赖项,并将结果捆绑为计算机 图像或容器。Dockerfile

此类应用程序具有用于构建的依赖项组,但也具有用于 linting 的依赖项组, 测试等在实践中,今天,这些应用程序通常将自己定义为 包,以便能够使用打包工具和机制,例如 管理其依赖项组。但是,它们在概念上不是包, 用于以 SDIST 或轮式格式分发。extras

依赖项组允许这些应用程序定义其各种依赖项 不依赖打包元数据,也不试图表达他们的 包装方面的需求。

图书馆

库是构建发行版(sdist 和 wheel)的 Python 包,并且 将它们发布到 PyPI。

对于库,依赖项组表示 定义开发依赖关系组,具有重要优势 如上所述。extras

库可以定义允许测试和 类型检查,因此依赖于库自身的依赖项(如 在 中指定)。testtyping[project.dependencies]

其他开发需求可能根本不需要安装软件包。为 例如,依赖项组可能有效且安装速度更快,而无需 该库,因为它只安装 、 或 等工具。lintblackruffflake8

lint环境也可能是值得关注的宝贵位置 IDE 或编辑器支持。有关此类的更完整描述,请参阅下面的案例 用法。test

下面是一个示例 Dependency Groups 表,它可能适用于 图书馆:

[dependency-groups]
test = ["pytest<8", "coverage"]
typing = ["mypy==1.7.1", "types-requests"]
lint = ["black", "flake8"]
typing-test = [{include = "typing"}, "pytest<8"]

请注意,这些都没有隐式安装库本身。 因此,任何环境管理工具链都有责任 需要时安装适当的依赖项组和库, 就像 的情况一样。test

数据科学项目

数据科学项目通常采用逻辑集合的形式 用于处理和分析数据的脚本和实用程序,使用通用的 工具链。组件可以以 Jupyter Notebook 格式 (ipynb) 定义, 但依赖于相同的通用核心实用程序集。

在这样的项目中,没有要构建或安装的包。因此,目前不提供任何依赖解决方案 管理或声明。pyproject.toml

对于这样的项目来说,能够定义至少一个专业是很有价值的 依赖项的分组。例如:

[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]

但是,各种脚本可能还需要具有其他 支持工具。项目甚至可能具有冲突或不兼容的工具,或者 不同组件的工具版本,因为它们会随着时间的推移而变化。

请考虑以下更精细的配置:

[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]
scikit = [{include = "main"}, "scikit-learn==1.3.2"]
scikit-old = [{include = "main"}, "scikit-learn==0.24.2"]

这将 和 定义为 的两个相似变体 通用的依赖项套件,拉入不同版本以适应不同的脚本。scikitscikit-oldscikit-learn

此 PEP 仅定义这些数据。它没有正式确定任何机制。 数据科学项目(或任何其他类型的项目)来安装依赖项 进入已知环境或将这些环境与各种 脚本。这种数据组合留给工具作者来说是一个问题 解决,也许最终标准化。

锁文件生成

在 Python 生态系统中有许多工具可以生成锁文件 今天。PDM 和 Poetry 各自使用自己的锁文件格式和 pip-tools 生成具有版本引脚和哈希的文件。requirements.txt

依赖项组不是存储锁定文件的合适位置,因为它们缺乏 许多必要的功能。最值得注意的是,它们无法存储哈希值,这 大多数Lockfile用户认为是必不可少的。

但是,依赖项组是生成锁定文件的工具的有效输入。 此外,PDM 和 Poetry 都允许使用依赖组名称(在其 依赖组的概念)用于指代其锁定的变体。

因此,请考虑一个生成锁定文件的工具,此处称为 。 它可能按如下方式使用:$TOOL

$TOOL lock --dependency-group=test
$TOOL install --dependency-group=test --use-locked

这样的工具需要做的就是确保其锁文件数据记录 名称,以支持此类用法。test

不保证依赖组的相互兼容性。例如 上面的数据科学示例显示了 的冲突版本。 因此,串联安装多个锁定的依赖项组可能需要 该工具应用其他约束或生成其他锁定文件数据。 这些问题被认为超出了本 PEP 的范围。scikit-learn

举两个示例来说明如何锁定组合:

  • 工具可能要求为任何 组合被视为有效

  • Poetry 实现了所有依赖组相互的要求 兼容,并且仅生成一个锁定版本。(这意味着它找到一个 解决方案,而不是解决方案的集合或矩阵。

Environment Manager 输入

tox、Nox 和 Hatch 中的常见用法是将一组依赖项安装到 测试环境。

例如,在 下,可以定义类型检查依赖关系 内嵌:tox.ini

[testenv:typing]
deps =  
  pyright    
  useful-types
commands = pyright src/

这种组合在有限的范围内提供了理想的开发人员体验 上下文。在相关环境管理器下,依赖项是 测试环境所需的命令与需要的命令一起声明 这些依赖项。它们不会像那样在包元数据中发布,并且对于需要它们来构建 相关环境。extras

依赖项组通过有效地“提升”这些用法来应用此类用法 需求数据从工具特定位置转换为更广泛可用的位置 一。在上面的示例中,只能访问声明的 依赖。在支持依赖项组的实现下,相同 数据可能在依赖项组中可用:tox

[dependency-groups]
typing = ["pyright", "useful-types"]

然后,可以在多种工具下使用数据。例如,might 实现支持作为,替换上面的用法。toxdependency_groups = typingdeps

为了使依赖组成为 环境管理器,环境管理器将需要支持处理 依赖项组与它们支持内联依赖项声明的方式类似。

IDE 和编辑器对需求数据的使用

IDE 和编辑器集成可能受益于常规或可配置名称 用于集成的依赖项组的定义。

至少有两种已知的场景对编辑器或 IDE 能够发现项目未发布的依赖项:

  • 测试:VS Code 等 IDE 支持 GUI 界面,用于运行特定的 测试

  • linting:编辑器和 IDE 通常支持 linting 和自动格式化 突出显示或自动更正错误的集成

这些情况可以通过定义传统的组名(如 、 和 )或定义配置机制来处理 允许选择依赖项组。testlintfix

例如,以下声明上述三个 组:pyproject.toml

[dependency-groups]
test = ["pytest", "pytest-timeout"]
lint = ["flake8", "mypy"]
fix = ["black", "isort", "pyupgrade"]

本 PEP 不试图标准化此类名称或保留它们 使用。IDE 可以标准化或允许用户配置所使用的组名 用于各种目的。

此声明允许项目作者了解适当的工具 以便与该项目的所有编辑者共享项目。

版权

本文档位于公共领域或 CC0-1.0-通用许可证,以更宽松者为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/76.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,179人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码