Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 735 – pyproject.toml における依存関係グループ

Author:
Stephen Rosen <sirosen0 at gmail.com>
Sponsor:
Brett Cannon <brett at python.org>
PEP-Delegate:
Paul Moore <p.f.moore at gmail.com>
Discussions-To:
Discourse thread
Status:
Accepted
Type:
Standards Track
Topic:
Packaging
Created:
20-Nov-2023
Post-History:
14-Nov-2023, 20-Nov-2023
Resolution:
10-Oct-2024

Table of Contents

Abstract

この PEP は、プロジェクトのビルドされた配布物に含まれないように、パッケージの依存関係を pyproject.toml ファイルに保存するメカニズムを指定します。

これは、ランチャー、IDE、およびその他のツールが名前で見つけて識別できるように、requirements.txt ファイルに似た名前付きの依存関係グループを作成するのに適しています。

ここで定義される機能は「依存関係グループ」と呼ばれます。

Motivation

Python コミュニティには標準化された回答がない 2 つの主要なユースケースがあります。

  • パッケージの開発依存関係はどのように定義すべきですか?
  • 配布物をビルドしないプロジェクト (非パッケージ プロジェクト) の依存関係はどのように定義すべきですか?

これら 2 つのニーズをサポートするために、この提案に似た 2 つの一般的なソリューションがあります。

  • requirements.txt ファイル
  • パッケージ extras

requirements.txt ファイルと extras には、この標準が克服しようとする制限があります。

上記の 2 つのユースケースは、この PEP がサポートしようとする 2 つの異なるタイプのプロジェクトを説明しています。

  • ライブラリなどの Python パッケージ
  • データ サイエンス プロジェクトなどの非パッケージ プロジェクト

いくつかの動機付けとなるユースケースは、Use Cases Appendix で詳細に定義されています。

Limitations of requirements.txt files

多くのプロジェクトは 1 つ以上の requirements.txt ファイルを定義し、プロジェクトのルート (例: requirements.txt および test-requirements.txt) に配置するか、ディレクトリ (例: requirements/base.txt および requirements/test.txt) に配置する場合があります。ただし、次のような requirements.txt ファイルの使用には大きな問題があります。

  • これらのファイルを名前で検出または使用できるようにする標準化された命名規則はありません。
  • requirements.txt ファイルは 標準化されていません が、代わりに pip オプションを提供します。

その結果、requirements.txt ファイルに基づいてツールの動作を定義することは困難です。これらを名前で検出または識別するのは簡単ではなく、その内容にはパッケージ指定子と追加の pip オプションが混在している場合があります。

requirements.txt の内容に標準がないため、他のツールが処理するために pip 以外のツールに移植することはできません。

さらに、requirements.txt ファイルには依存関係リストごとにファイルが必要です。いくつかのユースケースでは、これにより依存関係のグループ化の限界費用が高くなり、その利益に対して高くなります。より簡潔な宣言は、多くの小さな依存関係グループを持つプロジェクトにとって有益です。

これに対して、依存関係グループは、完全に標準化された内容で pyproject.toml の既知の場所に定義されます。これらは即時に有用であるだけでなく、将来の標準の出発点としても機能します。

Limitations of extras

extras は、[project.optional-dependencies] テーブルに宣言された追加のパッケージ メタデータです。これにより、パッケージ指定子のリストに名前を付けて、パッケージのメタデータの一部として公開し、ユーザーがその名前で要求できるようにします。たとえば、pip install 'foo[bar]'foobar エクストラ付きでインストールします。

extras はパッケージ メタデータであるため、静的に定義されていることが保証されておらず、解決にはビルド システムが必要な場合があります。さらに、[project.optional-dependencies] の定義は、多くのツールにプロジェクトがパッケージであることを示し、[project] テーブルの検証などのツールの動作を促進する場合があります。

パッケージであるプロジェクトの場合、extras は開発依存関係を定義するための一般的なソリューションですが、これらの状況下でも欠点があります。

  • extra はオプションの 追加 依存関係を定義するため、現在のパッケージとその依存関係をインストールせずに extra をインストールすることはできません。
  • ユーザーがインストール可能なため、extras はパッケージの公開インターフェイスの一部です。extras が公開されるため、パッケージ開発者は開発用のエクストラがユーザー向けのエクストラと混同されないようにすることを心配することがよくあります。

Rationale

この PEP は、依存関係データを [dependency-groups] テーブル内のリストに保存する方法を定義します。この名前は、機能の標準名 (「依存関係グループ」) に一致するように選択されました。

この形式は、既存の requirements.txt ファイルと非常に似た形式を持ち、できるだけシンプルで学びやすいものである必要があります。[dependency-groups] の各リストは、パッケージ指定子のリストとして定義されます。たとえば:

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

requirements.txt ファイルには、PEP 508 依存関係指定子では表現できないデータが必要なユースケースがいくつかあります。これらのフィールドは依存関係グループでは無効です。インデックス サーバー、ハッシュ、パス依存関係など、pip がサポートする多くのデータやフィールドを含めるには、新しい標準が必要です。この標準は、新しい標準や開発の余地を残していますが、すべての有効な requirements.txt コンテンツをサポートしようとはしていません。

これに対する唯一の例外は、requirements.txt ファイルが 1 つのファイルを別のファイルに含めるために使用する -r フラグです。依存関係グループは、意味が似ている「include」メカニズムをサポートしており、1 つの依存関係グループが別の依存関係グループを拡張できるようにします。

依存関係グループには、requirements.txt ファイルと同様の 2 つの追加機能があります。

  • それらは、ビルドされた配布物の個別のメタデータとして公開されません
  • 依存関係グループのインストールは、パッケージの依存関係やパッケージ自体のインストールを意味しません

Use Cases

次のユースケースは、この PEP の重要なターゲットと見なされています。これらは、Use Cases Appendix で詳細に定義されています。

  • 非 Python パッケージング ビルド プロセスを介してデプロイされた Web アプリケーション
  • 未公開の開発依存関係グループを持つライブラリ
  • コア パッケージを持たない依存関係グループを持つデータ サイエンス プロジェクト
  • ロックファイル生成の 入力データ (依存関係グループは、ロックされた依存関係データの場所として一般的に使用されるべきではありません)
  • tox、Nox、Hatch などの環境マネージャーへの入力データ
  • テストおよびリンターの要件の構成可能な IDE 検出

Regarding Poetry and PDM Dependency Groups

既存の Poetry および PDM ツールは、すでにそれぞれ「依存関係グループ」と呼ばれる機能を提供しています。ただし、依存関係のコレクションを指定するための標準がない場合、各ツールは関連する [tool] テーブルのセクションでこれらをツール固有の方法で定義します。

(PDM は一部の依存関係グループにエクストラも使用しており、概念をエクストラと大きく重複させています。)

この PEP は、Poetry および PDM のすべての機能をサポートしているわけではありません。これらのツールは、piprequirements.txt ファイルと同様に、一般的な依存関係指定子に対するいくつかの非標準拡張をサポートしています。

これらのツールが独自の依存関係グループ メカニズムの拡張として標準化された依存関係グループを使用できるようにする必要があります。ただし、既存の Poetry および PDM ソリューションを置き換える新しいデータ形式を定義することは目標ではありません。これを行うには、これらのツールでサポートされているパス依存関係などのいくつかの追加機能を標準化する必要があります。

Dependency Groups are not Hidden Extras

依存関係グループは、公開されないエクストラと非常に似ています。ただし、次の 2 つの主要な機能により、エクストラとはさらに区別されます。

  • 非パッケージ プロジェクトをサポートします
  • 依存関係グループのインストールは、パッケージの依存関係 (またはパッケージ自体) のインストールを意味しません

Future Compatibility & Invalid Data

依存関係グループは、将来の PEP で拡張可能であることを意図しています。ただし、依存関係グループは、単一の Python プロジェクトで複数のツールが使用できる必要があります。複数のツールが同じデータを使用する場合、1 つのツールが依存関係グループ データの将来の PEP を実装し、もう 1 つのツールが実装しない可能性があります。

この場合のユーザーをサポートするために、この PEP は、ツールが使用している依存関係グループのみを調べる検証動作を定義および推奨します。これにより、複数のツールが異なるバージョンの依存関係グループ データを使用して、pyproject.toml の単一テーブルを共有できるようになります。

Specification

この PEP は、pyproject.toml ファイルに dependency-groups という名前の新しいセクション (テーブル) を定義します。dependency-groups テーブルには、ユーザー定義のキーが任意の数含まれており、その値として依存関係のリスト (以下で定義) があります。これらのキーは valid non-normalized names でなければならず、比較する前に normalized する必要があります。

ツールは、デフォルトで元の非正規化名をユーザーに表示することを優先する必要があります (SHOULD)。正規化後に重複する名前が見つかった場合、ツールはエラーを出力する必要があります (SHOULD)。

dependency-groups の下の要件リストには、文字列、テーブル (Python では「dict」)、または文字列とテーブルの混合を含めることができます。

要件リストの文字列は、有効な Dependency Specifiers である必要があります。PEP 508 で定義されています。

要件リストのテーブルは、有効な依存関係オブジェクト指定子である必要があります。

Dependency Object Specifiers

依存関係オブジェクト指定子は、0 個以上の依存関係を定義するテーブルです。

この PEP は、1 種類の依存関係オブジェクト指定子、つまり「依存関係グループのインクルード」のみを標準化しています。他のタイプは将来の標準で追加される可能性があります。

Dependency Group Include

依存関係グループのインクルードは、現在の依存関係グループに別の依存関係グループの依存関係を含めます。

インクルードは、キーが "include-group" であり、その値が別の依存関係グループの名前である文字列であるテーブルとして定義されます。

たとえば、{include-group = "test"} は、test 依存関係グループの内容に展開されるインクルードです。

インクルードは、現在のグループのインクルードの場所に挿入された、名前付き依存関係グループの内容とまったく同じであると定義されています。たとえば、foo = ["a", "b"] が 1 つのグループであり、bar = ["c", {include-group = "foo"}, "d"] が別のグループである場合、依存関係グループのインクルードが展開されると bar["c", "a", "b", "d"] になるはずです。

依存関係グループのインクルードは、同じパッケージを複数回指定する場合があります。ツールは、インクルードによって生成されたリストの内容を重複排除したり、変更したりしない必要があります (SHOULD NOT)。たとえば、次のテーブルがあるとします。

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

all の解決された値は ["foo", "foo", "foo>1.0", "foo<1.0"] である必要があります。ツールは、そのようなリストを、異なるバージョン制約で同じ要件を複数回処理するように求められる場合とまったく同じように処理する必要があります。

依存関係グループのインクルードは、依存関係グループのインクルードを含むリストを含める場合があり、その場合、それらのインクルードも展開する必要があります。依存関係グループのインクルードはサイクルを含めることはできず、ツールはサイクルを検出した場合にエラーを報告する必要があります (SHOULD)。

Example Dependency Groups Table

次に、これを使用して 4 つの依存関係グループ testdocstyping、および typing-test を定義する部分的な pyproject.toml の例を示します。

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

これらの依存関係グループの宣言は、現在のパッケージ、その依存関係、またはオプションの依存関係を暗黙的にインストールしないことに注意してください。test のような依存関係グループを使用してパッケージをテストするには、ユーザーの構成またはツールチェーンが現在のパッケージ (.) もインストールする必要があります。たとえば、

$TOOL install-dependency-group test
pip install -e .

(依存関係グループをサポートするツールが $TOOL であると仮定して) テスト環境を構築するために使用できます。

これにより、プロジェクトをパッケージとしてインストールせずに docs 依存関係グループを使用できるようになります。

$TOOL install-dependency-group docs

Package Building

ビルド バックエンドは、ビルドされた配布物に依存関係グループ データをパッケージ メタデータとして含めてはなりません (MUST NOT)。これは、sdist の PKG-INFO およびホイールの METADATA に依存関係グループを参照するフィールドが含まれないことを意味します。

依存関係グループを動的メタデータの評価に使用することは有効ですが、sdist に含まれる pyproject.toml ファイルには自然に [dependency-groups] テーブルが含まれます。ただし、テーブルの内容は公開されたパッケージのインターフェイスの一部ではありません。

Installing Dependency Groups

依存関係グループをサポートするツールは、ユーザーが依存関係グループからインストールできるようにする新しいオプションとインターフェイスを提供することが期待されます。

この PEP では、パッケージの依存関係グループを表現する構文は定義されていません。その理由は 2 つあります。

  • 依存関係グループのデータは公開されないと定義されているため、PyPI からのサードパーティ パッケージの依存関係グループを参照することは有効ではありません。
  • 依存関係グループには、依存関係グループが存在することが保証されていません。依存関係グループの目的の一部は、非パッケージ プロジェクトをサポートすることです。

たとえば、依存関係グループをインストールするための pip インターフェイスは次のようになります。

pip install --dependency-groups=test,typing

これはあくまで一例です。この PEP では、ツールが依存関係グループのインストールをサポートする方法についての要件は宣言されていません。

Overlapping Install UX with Extras

ツールは、依存関係グループのインストールにエクストラのインストールと同じインターフェイスを提供することを選択できます (MAY)。

この仕様では、エクストラと名前が一致する依存関係グループを持つことを禁止していないことに注意してください。

ユーザーは、エクストラと一致する名前の依存関係グループを作成しないようにアドバイスされます。ツールは、そのような一致をエラーとして扱うことができます (MAY)。

Validation and Compatibility

依存関係グループをサポートするツールは、使用する前にデータを検証したい場合があります。ただし、そのような検証動作を実装するツールは、新しい構文の存在下で不必要にエラーや警告を出さないように、この仕様の将来の拡張を許可するように注意する必要があります。

ツールは、依存関係グループで認識されないデータを評価または処理する際にエラーを出す必要があります (SHOULD)。

ツールは、すべての 依存関係グループのリスト内容を積極的に検証してはなりません (SHOULD NOT)。

これは、次のデータが存在する場合、ほとんどのツールが foo グループを使用できるようにし、bar グループが使用されるときにのみエラーを出すことを意味します。

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

Linters and Validators may be stricter

積極的な検証は、主に依存関係グループをインストールまたは解決するツールには推奨されません。リンターおよび検証ツールには、この推奨事項を無視する正当な理由がある場合があります。

Reference Implementation

次のリファレンス実装は、依存関係グループの内容を標準出力に改行区切りで出力します。出力は有効な 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-group",):
                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])))

Backwards Compatibility

執筆時点では、pyproject.toml ファイル内の dependency-groups 名前空間は未使用です。トップレベルの名前空間は packaging.python.org で指定された標準でのみ使用するために予約されているため、直接的な下位互換性の問題はありません。

ただし、この機能の導入には、特に setup.py および requirements.txt のデータの調査をサポートしようとする多くのエコシステム ツールに影響があります。

Audit and Update Tools

幅広いツールが requirements.txt ファイルに記述された Python 依存関係データを理解しています (例: Dependabot、Tidelift など)。

このようなツールは依存関係データを調査し、場合によってはツール支援または完全に自動化された更新を提供します。これらのツールが新しい依存関係グループを最初にサポートすることは期待しておらず、広範なエコシステムのサポートには数か月、さらには数年かかる場合があります。

その結果、依存関係グループを使用し始めたときに、依存関係グループのユーザーはワークフローとツールのサポートが低下することになります。これは、依存関係データの保存場所と方法に関する新しい標準に当てはまります。

Security Implications

この PEP は、プロジェクトの依存関係情報を指定するための新しい構文とデータ形式を導入します。ただし、依存関係の処理または解決のための新しく指定されたメカニズムは導入されていません。

したがって、依存関係をインストールするために既に使用されているツールに固有のセキュリティ上の懸念以外のセキュリティ上の懸念はありません。つまり、悪意のある依存関係は、requirements.txt ファイルに指定されるのと同様に、ここに指定される可能性があります。

How to Teach This

この機能は、その標準名である「依存関係グループ」と呼ばれるべきです。

基本的な使用方法は、多くのユースケースで requirements.txt データのバリアントとして教えられるべきです。標準の依存関係指定子 (PEP 508) を名前付きリストに追加できます。pip に requirements.txt ファイルからインストールするように依頼する代わりに、pip または関連するワークフロー ツールが名前付き依存関係グループからインストールします。

新しい Python ユーザーに対しては、requirements.txt ファイルを使用する方法を教えるのと同様に、依存関係グループを含むセクションを pyproject.toml に作成するように直接教えることができます。これにより、新しい Python ユーザーはパッケージ ビルドについて学ぶ必要なく、pyproject.toml ファイルについて学ぶことができます。[dependency-groups] のみを含み、他のテーブルを含まない pyproject.toml ファイルは有効です。

新しいユーザーと経験豊富なユーザーの両方に対して、依存関係グループのインクルードを説明する必要があります。requirements.txt を使用した経験があるユーザーには、これを -r の類似物として説明できます。新しいユーザーには、インクルードが 1 つの依存関係グループを別の依存関係グループに拡張できることを教える必要があります。類似の構成インターフェイスや Python の list.extend メソッドを使用して、このアイデアを類推して説明することができます。

setup.py パッケージングを使用した経験がある Python ユーザーは、pyproject.toml の前に静的リストを定義するなど、一般的なプラクティスに精通しているかもしれません。requirements.txt ファイルから読み込まれた要件と setup() 呼び出しの前の静的リストの定義は、依存関係グループと容易に類推できます。

Interfaces for Use of Dependency Groups

この仕様は、パッケージ ビルドを介して project テーブルに含めること以外に、依存関係グループと対話するための普遍的なインターフェイスを提供しません。これは、ツールの作成者とユーザーの両方に影響を与えます。

ツールの作成者は、依存関係グループがユーザーストーリーにどのように関連するかを判断し、それに適した独自のインターフェイスを構築する必要があります。環境マネージャー、リゾルバー、インストーラー、および関連する非ビルド ツールの場合、依存関係グループの使用モードを文書化する責任があります。ビルド バックエンドの場合、依存関係グループをサポートするには project テーブルからのインクルードをサポートする必要がありますが、他の厳密な要件は設定されていません。

ユーザーにとっての主な結果は、パッケージ ビルドの外部で依存関係グループを使用するたびに、関連するツールのドキュメントを参照する必要があることです。ツールは、推奨されない使用法やサポートされていない使用法について、ドキュメントや実行時の警告やエラーを通じてユーザーにアドバイスする必要があります。たとえば、ツールがすべての依存関係グループが互換性があり、矛盾するパッケージ指定子を含まないことを要求する場合、その制限を文書化し、その目的に適した依存関係グループを適切に活用する方法についてユーザーにアドバイスする必要があります。

Rejected Ideas

Why not define each Dependency Group as a table?

将来の拡張を可能にすることが目標である場合、各依存関係グループをサブテーブルとして定義し、それによって将来のキーを各グループに添付できるようにすることで、将来の柔軟性が最大化されます。

ただし、これにより構造がより深くネストされ、教えたり学んだりするのが難しくなります。この PEP の目標の 1 つは、多くの requirements.txt ユースケースの簡単な代替手段であることです。

Why not define a special string syntax to extend Dependency Specifiers?

この仕様の初期のドラフトでは、依存関係グループのインクルードとパス依存関係のための構文形式が定義されていました。

ただし、このアプローチには 3 つの主要な問題がありました。

  • これにより、PEP 508 よりも教える必要がある文字列構文が複雑になります。
  • 結果として得られる文字列は常に PEP 508 指定子と区別する必要があり、実装が複雑になります。

Why not allow for more non-PEP 508 dependency specifiers?

議論中に、PEP 508 では可能ではない、より表現力豊かな指定子が必要なユースケースがいくつか浮上しました。

「パス依存関係」はローカル パスを参照し、[project.dependencies] への参照が特に関心を集めました。

ただし、これらの機能の既存の標準はありません (事実上の標準である pip の実装の詳細を除く)。

その結果、これらの機能をこの PEP に含めようとすると、これらのさまざまな機能や pip の動作を標準化しようとするため、範囲が大幅に拡大します。

特に、pip install -e および PEP 660 で表現される編集可能なインストールの表現を標準化しようとすることに特別な注意が払われました。ただし、編集可能なインストールの作成はビルド バックエンドに対して標準化されていますが、編集可能なインストールの動作はインストーラーに対して標準化されていません。この PEP に編集可能なインストールを含めるには、サポートするツールが編集可能なインストールのインストールを許可する必要があります。

したがって、Poetry および PDM はこれらの機能の構文を提供していますが、依存関係グループに含めるには、現時点では十分に標準化されていないと見なされています。

Why is the table not named [run], [project.dependency-groups], …?

この概念には多くの名前が考えられます。これは、既に存在する [project.dependencies] および [project.optional-dependencies] テーブルと並んで存在する必要があり、[external] 依存関係テーブルも存在する可能性があります (執筆時点では、[external] テーブルを定義する PEP 725 が進行中です)。

[run] は以前の議論で提案された主要な名前でしたが、その提案された使用法は単一の実行時依存関係セットを中心にしていました。この PEP は明示的に複数の依存関係グループを示しているため、[run] は適切な名前ではありません。これは、特定の実行時コンテキストの依存関係データだけでなく、複数のコンテキストの依存関係データです。

[project.dependency-groups][project.dependencies] および [project.optional-dependencies] と良い並行関係を提供しますが、非パッケージ プロジェクトには大きな欠点があります。[project] には nameversion などのキーを定義する必要があります。この名前を使用するには、[project] テーブルをこれらのキーが存在しないことを許可するように再定義するか、非パッケージ プロジェクトにこれらのキーを定義して使用することを要求する必要があります。拡張すると、非パッケージ プロジェクトがパッケージとして扱われることを事実上要求することになります。

Why is pip’s planned implementation of --only-deps not sufficient?

pip には現在、依存関係のみをインストールするための –only-deps flag を追加する予定の機能があります。このフラグは、ユーザーが現在のパッケージをインストールせずにパッケージの依存関係とエクストラをインストールできるようにすることを目的としています。

これは、非パッケージ プロジェクトのニーズには対応しておらず、エクストラをパッケージの依存関係なしでインストールすることもできません。

Why isn’t <environment manager> a solution?

tox、Nox、Hatch などの既存の環境マネージャーはすでに、構成データの一部としてインライン依存関係をリストする機能を備えています。これにより、多くの開発依存関係のニーズが満たされ、関連するタスクと依存関係グループが明確に関連付けられます。これらのメカニズムは 優れています が、十分ではありません

まず、これらは非パッケージ プロジェクトのニーズには対応していません。

第二に、他のツールがこれらのデータにアクセスするための標準はありません。これには、IDE や Dependabot などのハイレベル ツールに影響があります。これらのツールは、これらの依存関係グループと深く統合することをサポートできません (たとえば、執筆時点では Dependabot は tox.ini ファイルに固定された依存関係をフラグ付けしません)。

Deferred Ideas

Why not support Dependency Group Includes in [project.dependencies] or [project.optional-dependencies]?

この仕様の初期のドラフトでは、[project] テーブルで依存関係グループのインクルードを使用できるようにしていました。ただし、コミュニティ フィードバック中に提起された問題がいくつかあり、その結果、削除されました。

依存関係グループのインクルードを含めることで対応できるユースケースはごくわずかであり、仕様の範囲が大幅に拡大します。特に、依存関係メタデータを静的に定義することを複雑にし、依存関係メタデータを単一の場所に保存するという PEP 621 の目標と矛盾します。

最後に、この PEP から [project] サポートを除外することは最終的なものではありません。[project] テーブルからのインクルードの使用、または [dependency-groups] から [project] へのインクルード構文は、将来の PEP によって導入され、その独自のメリットに基づいて検討される可能性があります。

Use Cases for Dependency Group Includes From [project]

この PEP では延期されていますが、[project] テーブルからのインクルードを許可することで、いくつかのユースケースに対応できます。

特に、パッケージの依存関係のみをインストールし、パッケージ自体をインストールしない場合に便利なケースがあります。

たとえば:

  • 依存関係をビルドする際に、パッケージ自体をビルドする際とは異なる環境変数やオプションを指定する
  • 依存関係をパッケージから分離してレイヤード コンテナ イメージを作成する
  • パッケージ自体をビルドしてインストールすることなく、依存関係を分析環境 (例: 型チェック) に提供する

次のサンプル pyproject.toml を考えてみましょう。

[project]
dependencies = [{include = "runtime"}]
[optional-dependencies]
foo = [{include = "foo"}]
[dependency-groups]
runtime = ["a", "b"]
foo = ["c", "d"]
typing = ["mypy", {include = "runtime"}, {include = "foo"}]

この場合、typing グループは、パッケージ自体を含まずに、パッケージのすべてのランタイム依存関係を持つことができます。これにより、typing 依存関係グループの使用は、パッケージのインストールをスキップできます。これはより効率的であるだけでなく、テスト システムの要件を減らすことができます。

Why not support Dependency Group Includes in [build-system.requires]?

[project] テーブルへの依存関係グループのインクルードを許可しない場合、[build-system.requires][project.dependencies] と比較して検討できます。

ビルド要件をグループに指定するユースケースは、パッケージ要件よりも少ないです。さらに、この変更の影響は PEP 517 フロントエンドに及び、依存関係グループをサポートしてビルド環境を準備する必要があります。

[project.dependencies] および [project.optional-dependencies] の動作を変更することと比較して、[build-system.requires] の動作を変更することは影響が大きく、潜在的な使用例が少ないです。したがって、この PEP が [project] テーブルの変更を行わないことを決定したため、[build-system] の変更も延期されます。

Why not support a Dependency Group which includes the current project?

依存関係グループに関するいくつかの使用シナリオは、パッケージを [project] テーブルで定義された依存関係グループと一緒にインストールすることを中心に展開しています。たとえば、パッケージをテストするには、テスト依存関係とパッケージ自体をインストールする必要があります。さらに、依存関係グループの互換性は、ロックファイル生成にとって貴重な入力です。

このような場合、依存関係グループがプロジェクト自体に依存していることを宣言することが望ましいです。議論からの例として、{include-project = true} および {include-group = ":project:"} などの構文が含まれます。

ただし、PEP 508 を拡張してパス依存関係をサポートする仕様が確立された場合、依存関係グループにはパッケージ自体を指定する 2 つの方法が存在することになります。たとえば、. が正式にサポートされ、{include-project = true} がこの PEP に含まれる場合、依存関係グループは次のいずれかのグループを指定できます。

[dependency-groups]
case1 = [{include-project = true}]
case2 = ["."]
case3 = [{include-project = true}, "."]
case4 = [{include-project = false}, "."]

このため、pyproject.toml でパッケージを指定するための複数の異なるオプションが存在する混乱を避けるために、この PEP にはこの関係を宣言する構文は含まれていません。

Appendix A: Prior Art in Non-Python Languages

このセクションは主に情報提供を目的としており、他の言語エコシステムが同様の問題をどのように解決しているかを文書化するためのものです。

JavaScript and package.json

JavaScript コミュニティでは、パッケージは pyproject.toml に似た範囲の標準的な構成およびデータファイルを package.json に含めます。

package.json の 2 つのキーが依存関係データを制御します。"dependencies""devDependencies" です。"dependencies" の役割は、pyproject.toml[project.dependencies] と実質的に同じであり、パッケージの直接の依存関係を宣言します。

"dependencies" data

依存関係データは、パッケージ名からバージョン指定子へのマッピングとして package.json に宣言されます。

バージョン指定子は、Python の PEP 440 バージョン指定子に似た、バージョン、範囲、およびその他の値の小さな文法をサポートします。

たとえば、次のような依存関係を宣言する部分的な package.json ファイルがあります。

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

@ 記号の使用は、パッケージ所有者を宣言する scope です。したがって、"@angular/compiler"angular 所有権の下にグループ化された compiler という名前のパッケージを宣言します。

Dependencies Referencing URLs and Local Paths

依存関係指定子は、URL および Git リポジトリの構文をサポートしており、Python パッケージングの規定に似ています。

URL はバージョン番号の代わりに使用できます。使用される場合、パッケージのソースコードの tarball を暗黙的に参照します。

Git リポジトリも同様に使用でき、コミット指定子のサポートも含まれます。

PEP 440 とは異なり、NPM は依存関係のためにローカル パスをパッケージ ソースコード ディレクトリに使用することを許可します。これらのデータが標準の npm install --save コマンドを介して package.json に追加されると、パスは package.json を含むディレクトリからの相対パスに正規化され、file: でプレフィックスされます。たとえば、次の部分的な package.json には、現在のディレクトリの兄弟を参照する参照が含まれています。

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

公式 NPM ドキュメント では、ローカル パス依存関係は公開パッケージ リポジトリに公開すべきではないと述べていますが、そのような依存関係データが公開パッケージで有効か無効かについては言及していません。

"devDependencies" data

package.json には、"dependencies" と同じ形式で "devDependencies" という名前の 2 番目のセクションを含めることが許可されています。"devDependencies" に宣言された依存関係は、パッケージがパッケージ リポジトリからインストールされるとき (例: 依存関係が解決される一部として) にはデフォルトではインストールされませんが、package.json を含むソース ツリーで npm install が実行されるとインストールされます。

"dependencies" が URL およびローカル パスをサポートするのと同様に、"devDependencies" もサポートします。

"peerDependencies" and "optionalDependencies"

package.json には、関連する 2 つの追加セクションがあります。

"peerDependencies" は、"dependencies" と同じ形式で依存関係のリストを宣言しますが、これらは互換性の宣言であることを意味します。たとえば、次のデータはパッケージ foo バージョン 2 との互換性を宣言します。

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

"optionalDependencies" は、可能であればインストールするが、利用できない場合は失敗と見なさない依存関係のリストを宣言します。これも "dependencies" と同じマッピング形式を使用します。

"peerDependenciesMeta"

"peerDependenciesMeta" は、"peerDependencies" の処理方法をさらに制御できるセクションです。

このセクションでパッケージを optional に設定することで、欠落している依存関係に関する警告を無効にできます。次のサンプルのように:

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

--omit and --include

npm install コマンドは、--omit および --include の 2 つのオプションをサポートしており、これにより「prod」、「dev」、「optional」、または「peer」依存関係がインストールされるかどうかを制御できます。

「prod」名は "dependencies" にリストされている依存関係を指します。

デフォルトでは、npm install がソース ツリーで実行されると、これらの 4 つのグループすべてがインストールされますが、これらのオプションを使用してインストールの動作をより正確に制御できます。さらに、これらの値は .npmrc ファイルに宣言でき、ユーザーごとおよびプロジェクトごとの構成によりインストールの動作を制御できます。

Ruby & Ruby Gems

Ruby プロジェクトは、Ruby エコシステムでパッケージ (「ジェム」) を生成することを意図しているかどうかに関係なく存在します。実際、言語のほとんどのユーザーは自分のパッケージを生成することに興味がなく、パッケージを生成することを望んでいないと期待されています。多くのチュートリアルでは、パッケージの生成について触れておらず、ツールチェーンはサポートされているユースケースに対してユーザーコードをパッケージ化する必要はありません。

Ruby は依存関係の指定を 2 つの別々のファイルに分割します。

  • Gemfile: 依存関係グループの形式でのみ依存関係データをサポートする専用ファイル
  • <package>.gemspec: パッケージ (ジェム) メタデータを宣言する専用ファイル

bundler ツールは、bundle コマンドを提供し、Gemfile データを使用するための主要なインターフェイスです。

gem ツールは、gem build コマンドを介して .gemspec データからジェムをビルドする責任があります。

Gemfiles & bundle

Gemfile は、任意の数の group 宣言に囲まれた gem ディレクティブを含む Ruby ファイルです。gem ディレクティブは group 宣言の外部でも使用でき、その場合、依存関係の暗黙の名前のないグループを形成します。

たとえば、次の Gemfilerails をプロジェクトの依存関係としてリストしています。他のすべての依存関係はグループの下にリストされています。

source 'https://rubygems.org'

gem 'rails'

group :test do
  gem 'rspec'
end

group :lint do
  gem 'rubocop'
end

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

ユーザーがこれらのデータを使用して bundle install を実行すると、すべてのグループがインストールされます。ユーザーは、.bundle/config にバンドラー構成を作成または変更することで、グループを選択解除できます。たとえば、bundle config set --local without 'lint:docs'

トップレベルで 'rails' ジェムの使用を除外したり、その暗黙のグループを名前で参照したりすることはできません。

gemspec and packaged dependency data

gemspec file は、Gem::Specification インスタンス宣言を含む Ruby ファイルです。

パッケージ (ジェム) 依存関係データに関連する Gem::Specification オブジェクトのフィールドは 2 つだけです。add_development_dependencyadd_runtime_dependency です。Gem::Specification オブジェクトは、動的に依存関係を追加するためのメソッドも提供しており、add_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

add_development_dependency の使用はありません。その他の主流の主要パッケージ (例: rubocop) は、ジェム内で開発依存関係を使用していません。

他のプロジェクトはこの機能を使用しています。たとえば、kramdown は開発依存関係を使用しており、その Rakefile に次の仕様が含まれています。

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'

開発依存関係の目的は、bundler によって使用される暗黙のグループを宣言することだけです。

完全な詳細については、Gemfiles に関する bundlerdocumentation on Gemfiles ディレクティブを参照してください。ただし、.gemspec 開発依存関係と Gemfile/bundle の使用との統合は、例を通じて最もよく理解されます。

gemspec development dependency example

次のような Gemfile および .gemspec の形式の単純なプロジェクトを考えてみましょう。cool-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 'https://rubygems.org'

gemspec

Gemfilegemspec ディレクティブは、ローカル パッケージ cool-gem に依存関係を宣言し、ローカルで利用可能な cool-gem.gemspec ファイルで定義されます。また、すべての開発依存関係を development という名前の依存関係グループに暗黙的に追加します。

したがって、この場合、gemspec ディレクティブは次の Gemfile コンテンツと同等です。

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

group :development do
  gem 'kramdown'
end

Appendix B: Prior Art in Python

依存関係グループの標準がない場合、PDM および Poetry という 2 つの既知のワークフロー ツールが独自のソリューションを定義しています。

このセクションでは、主に Python における依存関係グループの定義と使用に関する先行事例として、これら 2 つのツールに焦点を当てます。

Projects are Packages

PDM および Poetry は、サポートするプロジェクトをパッケージとして扱います。これにより、いくつかのニーズに対して標準の pyproject.toml メタデータを使用および操作でき、ビルド バックエンドを使用してビルドおよびインストールを行うことで「現在のプロジェクト」のインストールをサポートできます。

実質的に、Poetry および PDM は非パッケージ プロジェクトをサポートしていません。

Non-Standard Dependency Specifiers

PDM および Poetry は、追加の機能を持つ PEP 508 依存関係指定子を拡張しており、共有標準の一部ではありません。2 つのツールはこれらの問題に対してわずかに異なるアプローチを使用しています。

PDM は、ローカル パスおよび編集可能なインストールを指定するために、pip install の引数のセットのような構文をサポートしています。たとえば、次の依存関係グループには、ローカル パッケージが編集可能モードで含まれています。

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

これは、foo ディレクトリからのローカル編集可能インストールを含む mygroup という依存関係グループを宣言します。

Poetry は、パッケージ名を指定子にマッピングするテーブルとして依存関係グループを説明します。たとえば、上記の mygroup の例と同じ構成は、Poetry では次のように表示されます。

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

PDM は文字列構文に制限されており、Poetry は依存関係を説明するテーブルを導入しています。

Installing and Referring to Dependency Groups

PDM および Poetry の両方には、依存関係グループをインストールするためのツール固有のサポートがあります。両方のプロジェクトは独自のロックファイル形式をサポートしているため、依存関係グループ名を使用してそのグループのロックされたバリアントを参照する機能もあります。

ただし、どちらのツールの依存関係グループも toxnox、または pip などの他のツールからネイティブに参照することはできません。依存関係グループをインストールしようとすると、PDM または Poetry に明示的に呼び出して依存関係データを解析し、関連するインストール手順を実行する必要があります。

Appendix C: Use Cases

Web Applications

Web アプリケーション (例: Django または Flask アプリ) は、配布物をビルドする必要はありませんが、ソースをバンドルしてデプロイメント ツールチェーンに送信します。

たとえば、ソースコード リポジトリは、Python パッケージング メタデータとコンテナ化やその他のビルド パイプライン メタデータ (Dockerfile など) を定義する場合があります。Python アプリケーションは、リポジトリ全体をビルド コンテキストにコピーし、依存関係をインストールし、結果をマシン イメージやコンテナとしてバンドルすることでビルドされます。

このようなアプリケーションには、ビルドのための依存関係グループがあり、リンティング、テストなどのための依存関係グループもあります。実際には、これらのアプリケーションは、パッケージング ツールや extras などのメカニズムを使用して依存関係グループを管理するために、パッケージとして自分自身を定義することがよくあります。ただし、概念的には、これらはパッケージではなく、sdist やホイール形式での配布を意図していません。

依存関係グループを使用すると、これらのアプリケーションはパッケージング メタデータに依存せず、パッケージング用語でニーズを表現することなく、さまざまな依存関係を定義できます。

Libraries

ライブラリは、sdist およびホイールをビルドし、PyPI に公開する Python パッケージです。

ライブラリにとって、依存関係グループは開発依存関係を定義するための extras の代替手段を提供し、上記の重要な利点があります。

ライブラリは、テストおよび型チェックを可能にする test および typing のグループを定義し、そのためにライブラリ自体の依存関係 ([project.dependencies] に指定) に依存します。

他の開発ニーズには、パッケージのインストールがまったく必要ない場合があります。たとえば、lint 依存関係グループは、blackruffflake8 などのツールのみをインストールするため、ライブラリなしで有効であり、インストールが速くなります。

lint および test 環境は、IDE やエディタのサポートをフックするための貴重な場所でもあります。以下のケースでそのような使用法の詳細な説明を参照してください。

次に、ライブラリに適した依存関係グループ テーブルの例を示します。

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

これらのいずれもライブラリ自体を暗黙的にインストールしないことに注意してください。したがって、環境管理ツールチェーンが適切な依存関係グループと一緒にライブラリをインストールする必要があります。たとえば、test の場合です。

Data Science Projects

データ サイエンス プロジェクトは、共通のツールチェーンを使用してデータを処理および分析するためのスクリプトやユーティリティの論理的なコレクションの形式を取ります。コンポーネントは Jupyter Notebook 形式 (ipynb) で定義される場合がありますが、同じ共通のコア ツールセットに依存しています。

このようなプロジェクトでは、ビルドしてインストールするパッケージはありません。したがって、pyproject.toml は現在、依存関係管理や宣言のためのソリューションを提供していません。

このようなプロジェクトにとって、少なくとも 1 つの主要な依存関係グループを定義できることは価値があります。たとえば:

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

ただし、さまざまなスクリプトには追加のサポート ツールが必要な場合もあります。プロジェクトは進化するにつれて、さまざまなコンポーネントに対して競合するツールや互換性のないツールやツールのバージョンを持つこともあります。

次のようなより詳細な構成を考えてみましょう。

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

これにより、scikit および scikit-old が、異なるバージョンの scikit-learn を引き込むために、共通の依存関係セットの 2 つの類似バリアントとして定義されます。

この PEP はこれらのデータのみを定義します。データ サイエンス プロジェクト (またはその他のタイプのプロジェクト) が依存関係を既知の環境にインストールし、これらの環境をさまざまなスクリプトに関連付けるためのメカニズムを公式化することはありません。このようなデータの組み合わせは、ツール作成者が解決し、最終的には標準化する問題として残されています。

Lockfile Generation

現在、Python エコシステムではロックファイルを生成するツールがいくつかあります。PDM および Poetry はそれぞれ独自のロックファイル形式を使用しており、pip-tools はバージョンピンおよびハッシュを含む requirements.txt ファイルを生成します。

依存関係グループはロックファイルを保存する適切な場所ではありません。必要な機能が多く欠けているためです。特に、ほとんどのロックファイル ユーザーが重要と考えるハッシュを保存できません。

ただし、依存関係グループはロックファイルを生成するツールへの有効な入力です。さらに、PDM および Poetry の両方は、依存関係グループ名を使用してそのグループのロックされたバリアントを参照する機能を提供します。

したがって、$TOOL と呼ばれるロックファイルを生成するツールを考えてみましょう。次のように使用される場合があります。

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

そのようなツールが行う必要があるのは、ロックファイル データに test という名前を記録してその使用をサポートすることだけです。

依存関係グループの相互互換性は保証されていません。たとえば、上記のデータ サイエンスの例では、scikit-learn の競合するバージョンが示されています。したがって、複数のロックされた依存関係グループを一緒にインストールするには、ツールが追加の制約を適用するか、追加のロックファイル データを生成する必要があります。これらの問題は、この PEP の範囲外と見なされます。

組み合わせをロックする方法の例としては、次のようなものがあります。

  • ツールは、組み合わせが有効と見なされるためにロックファイル データが明示的に生成されることを要求する場合があります。
  • Poetry は、すべての依存関係グループが相互に互換性があることを要求し、1 つのロックされたバージョンのみを生成します (つまり、単一のソリューションを見つけるのではなく、ソリューションのセットまたはマトリックスを見つける)。

Environment Manager Inputs

tox、Nox、Hatch などの一般的な使用法は、テスト環境に依存関係のセットをインストールすることです。

たとえば、tox.ini では、型チェック依存関係がインラインで定義される場合があります。

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

この組み合わせにより、限られたコンテキスト内で望ましい開発者エクスペリエンスが提供されます。関連する環境マネージャーの下で、テスト環境に必要な依存関係が宣言され、それらの依存関係グループが関連するコマンドと明確に関連付けられます。これらはパッケージ メタデータとして公開されず、extras のように公開されず、ツールがそれらを使用して関連する環境を構築するために検出可能です。

依存関係グループは、このような使用法に適用され、これらの依存関係データをツール固有の場所からより広く利用可能な場所に「持ち上げる」ことができます。上記の例では、依存関係のリストを宣言するのは tox のみです。依存関係グループをサポートする実装の下では、同じデータが依存関係グループに存在する場合があります。

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

このデータは複数のツールで使用できます。たとえば、toxdependency_groups = typing としてサポートを実装し、上記の deps の使用を置き換えることができます。

依存関係グループが環境マネージャーのユーザーにとって実行可能な代替手段となるためには、環境マネージャーがインライン依存関係の宣言と同様に依存関係グループを処理することをサポートする必要があります。

IDE and Editor Use of Requirements Data

IDE およびエディタの統合は、依存関係グループがさまざまな目的に使用されるための標準名や構成可能な名前の定義から恩恵を受ける可能性があります。

プロジェクトの公開されていない依存関係を検出できることが価値のあるシナリオが少なくとも 2 つ知られています。

  • テスト: VS Code などの IDE は、特定のテストを実行するための GUI インターフェイスをサポートしています。
  • リンティング: エディタや IDE は、エラーを強調表示したり自動修正したりするリンティングや自動フォーマットの統合をサポートすることがよくあります。

これらのケースは、testlintfix などの標準的なグループ名を定義するか、依存関係グループの選択を許可する構成メカニズムを定義することで処理できます。

たとえば、次の pyproject.toml は、上記の 3 つのグループを宣言しています。

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

この PEP は、そのような名前を標準化したり、それらをそのような使用法のために予約したりする試みを行いません。IDE は標準化するか、ユーザーがさまざまな目的のために使用するグループ名を構成できるようにする場合があります。

この宣言により、プロジェクトの著者のプロジェクトに適したツールに関する知識が、そのプロジェクトのすべてのエディタに共有されます。


Source: https://github.com/python/peps/blob/main/peps/pep-0735.rst

Last modified: 2024-10-16 03:45:21 GMT