理念とビジョン
Ryeは私の問題を解決するために作られました。開発にあたって私が考えていたことは以下のとおりです。
-
仮想環境: 個人的には仮想環境はあまり好きではありませんが、非常に普及しており、ツールによるサポートも充実しているため、`__pypackages__`ではなくこちらを選択しました。
-
デフォルトの依存関係なし: 仮想環境は作成された時点では依存関係が全くありません。`pip`や`setuptools`すらインストールされていません。Ryeは仮想環境の外部から仮想環境を管理します。
-
非標準的なコア要素なし: Ryeは(`pyproject.toml`の独自の`tool`セクションを除いて)標準化されたキーを使用します。つまり、期待どおりに通常の要件を使用します。また、カスタムロックファイル形式は使用せず、`uv`を使用します。
-
Pipなし: Ryeは、`pyproject.toml`のみを介して、`uv`を使用して依存関係を管理します。
-
システムPythonなし: Linuxディストリビューション特有のPythonインストールやmacOSの混乱した状態にはもう対処できません。以前は、どこでも同じになるように独自のPythonをビルドしていましたが、今はindygregのPythonビルドを使用しています。RyeはそこからPythonビルドを自動的にダウンロードして管理します。コンパイルも差異もありません。
-
プロジェクトローカルシム: Ryeは、現在の`pyproject.toml`を自動検出し、その下で自動的に動作する`python`シムを保持します。シムをシェルに追加するだけで、`python`を実行すると、常に正しいプロジェクトで自動的に動作します。
理想的な姿
Pythonのパッケージングの世界には、主に標準化の欠如の結果として、いくつかの欠点があります。このプロジェクトが長年にわたって直面してきた問題点は以下のとおりです。
-
Pythonバイナリディストリビューションがない: python.orgのCPythonビルドは完全に不十分です。プラットフォームによっては、.msiインストーラーのみが提供され、プラットフォームによっては、tarballのみが提供されます。長年にわたって普及してきたさまざまなPythonディストリビューションは大きく異なっており、ダウンストリームであらゆる種類のナンセンスを引き起こします。これが、このプロジェクトがindygregスタンドアロンビルドを使用する理由です。将来的には、誰かが適切に保守され、信頼性の高いPythonビルドを配布して、現在私たちが抱えている混乱を解消してくれることを願っています。
-
開発用依存関係がない: Ryeは現在、開発用依存関係を表すために`pyproject.toml`にカスタムセクションが必要です。エコシステムにはこれに関する標準がありません。本当に追加されるべきです。
-
ローカル依存関係のオーバーレイがない: ローカル依存関係を表す方法に標準がありません。Rustはこの目的のために、リモートとローカルの両方の参照が共存できるようにし、公開時にそれらを書き換える`{ path = "../foo" }`のようなものを持っています。
-
Pipが公開されていない: pipは意図的に公開されていません。pipを使用して仮想環境に何かをインストールすると、次回同期したときに消えます。
-
ワークスペースの仕様がない: モノレポなどの場合、Pythonエコシステムにはワークスペースの定義が必要です。現在、それは存在しないため、すべてのツールがこ 問題に対する独自ソリューションを考案する必要があります。
-
基本的なスクリプトセクションがない: `rye`が`rye.tools.scripts`で行うように、スクリプトを表すための標準が`pyproject.toml`にあるべきです。
ビジョン
これは、理想的な世界におけるPythonのパッケージングとプロジェクト管理の姿についての私の構想です。
Rustの開発体験
Rust環境から来た場合、連携して動作する2つのツールがあります。`rustup`と`cargo`です。最初のツールは、マシンに正しいRustツールチェーンがあることを確認するために使用されます。Rustは、外部ディストリビューションよりも公式Webサイトからの言語のバイナリディストリビューションをはるかに好みます。
`cargo`は、Rustにおける開発の主要なエントリポイントです。テスト実行のトリガー、ビルドプロセスの開始、ドキュメントビルドツールへのシェルアウト、リンターだけでなく、ワークスペース管理、依存関係管理、パッケージ公開などのツールとしても機能します。
Rustの開発体験の非常に重要な側面は、semverへの強いコミットメントと、組み込みのサポートです。これは非常に奥深いです。たとえば、リゾルバーはグラフ全体で一致する依存関係を重複排除します。これは、4つのライブラリが`[email protected]`に依存している場合、すべてその依存関係に解決されることを意味します。ただし、`[email protected]`に対する別のニーズが発生した場合、依存関係グラフによって両方がロードされる可能性があります!
エコシステムはこれに大きく依存しています。たとえば、非常にコアなライブラリの新しいメジャーリリースが行われた場合、場合によっては、コアタイプを新しいバージョンから古いバージョンに再エクスポートすることにより、互換性のないバージョンを統合するために特別な注意が払われます。したがって、たとえば、`[email protected]`が内部で`[email protected]`に依存して、移行を容易にすることができます。
さらに、Rustはロックファイルを heavily に活用しています. コンパイルするたびに、依存関係はロックされ、更新しない限り、将来のビルドでは同じ依存関係バージョンが再利用されます。
しかし、最も重要なことは、Rustエコシステムが`rustup`と`cargo`を採用しており、大多数の人々がこれらのツールを日常的に使用していることです。buckのような他のツールを選択する開発者でさえ、`cargo`を定期的に使用しています。
Pythonへの適用
Ryeは、そのような体験がPythonで可能かどうかを探求したいと考えています。私はそれができると信じています!この目的のために活用できるエコシステムはたくさんありますが、構築する必要があるものはさらにたくさんあります。
**重要事項:** このドキュメントのコンテキストで「rye」を読んだ場合、潜在的なツールであるryeがどのようなものであるかについて説明しています。今日存在する多くのツールの1つが、ここで説明されているまさにそのツールになる可能性もあります。
私の考えでは、「1つのツール」がPythonの世界に登場しない限り、さらに別のツールを導入することは、エコシステムにとってマイナスになる可能性があります。長年にわたって多くのツールが作成されてきましたが、残念ながら、Pythonコミュニティの大部分をどのツールの背後に結集させることができませんでした。しかし、私はそれが可能だと信じています。
Pythonのブートストラップ
正しいアプローチは、95%を超えるユーザーが`rye`を介してPythonディストリビューションを取得することであり、`rye`がシステムにインストールされているPythonディストリビューションを選択することではないと考えています。システムPythonインストールを使用する正当な理由がありますが、それはルールではなく例外であるべきです。最も重要なのは、`rye`が導入するPythonディストリビューションは、システム間で異ならない信頼性が高く、シンプルなルールを持つように作ることができるためです。
現在、ユーザーの混乱と不満の大きな原因は、Pythonに対するLinuxディストリビューション固有のパッチであり、ツールを壊し、動作を変更します。特に、Pythonパッケージングエコシステムにおいてです。
独立したツールを介してPythonをブートストラップすることには、他にも利点があります。たとえば、toxまたはCIを介したクロスPythonバージョンテストがはるかに容易になります。
やるべきこと
- インターネットから取得できる、 largely 標準化された構造を持つ、広く利用可能なPythonビルドを提供します。PEP 711はその方向への一歩です。
より強力なリゾルバー
今日、Pythonエコシステムには非常に多くの異なるリゾルバーがあります。Pipには2つ、poetryには1つ、pdmには1つ、その他にも独立したPythonとRustのリゾルバーが多数存在します. リゾルバーは重要ですが、残念ながら、既存のリゾルバーには多すぎる問題と多すぎる問題の両方が存在します. 私が信じているリゾルバーが達成できる必要があることは次のとおりです.
-
マーカーをまたいで解決できるようにする: 今日のPythonエコシステムのほとんどのリゾルバーは、現在のインタープリターとプラットフォーム(例:pip、pip-tools)に対してのみ解決できます。これは、異なるプラットフォームで gleichermaßen 有効な解決策を作成できないことを意味します。これは、Pythonの環境マーカーの定義方法が原因であるという問題が一部にあります。ほとんどのツールでは反映できないレベルの表現力が許容されますが、サブセットはサポートできます。
-
複数バージョンの解決のサポート: これは少し先の話ですが、さまざまな理由から、リゾルバーがすべての要件を単一バージョンに統合するのではなく、ライブラリのメジャーバージョン全体で複数の独立した解決をサポートできる必要があると信じています。将来のリゾルバーは、`package==2.0`と`package==1.1`の両方がツリーの異なる部分に対して解決されることを許可できる必要があります。
-
リゾルバーAPI: リゾルバーへのアクセスは重要です。エディタープラグイン、またはカスタムツールの場合、常にパッケージを解決できる必要があります。たとえば、「サポートされている最新バージョンの 'flask' を `pyproject.toml` に追加する」などの些細なことをしたい場合は、リゾルバーを操作できる必要があります。
-
フィルター: 優れたリゾルバーには、上にフィルターが必要であると強く信じています. たとえば、開発者がリゾルバーをターゲットPythonバージョンの範囲内に留め、新しすぎるPythonバージョンを含むツリーにアップグレードしないように制限できる必要があります。同様に、サプライチェーンの安全性のために、リゾルバーは、厳選された依存関係のセットに制限できる必要があります。
やるべきこと
- エコシステム内の複数のツールで使用できる再利用可能なリゾルバーを作成する。
- リゾルバーを提案されたメタデータキャッシュと連携させる。
- 複数のツールが使用できるように、リゾルバーをAPIとして公開する。
- 使用前に依存関係をフィルタリングするために使用できるポリシー層をリゾルバーに追加する。
メタデータキャッシュ
Pythonパッケージとパッケージインデックスはかなり単純な性質を持っているため、リゾルバーは常に確実に取得できるメタデータによって制限される。これは、システムが最悪の場合、依存関係を決定するためにPythonコードを実行する必要がある`sdist`アップロードにフォールバックする必要がある場合に特に問題となる。さらに、それらの依存関係は異なるプラットフォームでは一致しない可能性がある。
しかし、これは十分なキャッシングと適切なキャッシュ設計があれば解決可能な問題であり、このキャッシュは共有できる可能性がある。PyPIが、リゾルバーを支援するために、一般的なsdist専用パッケージの「偽の」メタデータレコードを提供することも非常に興味深いだろう。これは、開発者エクスペリエンスの質を向上させる上で大いに役立つ可能性がある。
やるべきこと
- リゾルバーが使用するためのローカルメタデータキャッシュが追加される。
- PyPIは依存関係メタデータを提供する機能を獲得する。
ロックファイル
異なる要件を考えると、ロックファイルの標準が出現するかどうかは不明だが、Pythonパッケージングソリューションはこれらをサポートする必要がある。現在、ロックファイルには多くの異なるアプローチがある(例えば、poetryやpdmはロックファイルを持っている)が、現在の処理方法が、ロックファイルに基づくツールが大多数に採用されることを可能にするのに十分実用的かどうかは、私には完全には明らかではない。
その理由の一部は、リゾルバーの状況が最適ではないこと(例:大規模プロジェクトでは、poetryでの依存関係チェックに10分以上かかる場合がある)に関連しているが、一方では、依存関係が現在どのように宣言されているかという現実にもよる。たとえば、特定のライブラリは、開発者にとって必要ない場合でも、サードパーティライブラリに「過剰に」依存する。しかし、これらのプルインされた依存関係は、依然としてリゾルバーに影響を与える。
最も重要なことは、優れたロックファイルは、現在の開発者のマシン以外のプラットフォームもカバーしていることだ。これは、プロジェクトがWindowsとLinuxをサポートしている場合、ロックファイルはどちらの依存関係ツリーも処理する必要があることを意味する。これは、cargoが現在達成していることだが、cargoはパッケージメタデータへの完全なアクセス権を持っているため、ここではるかに簡単な問題を解決している。Pythonのリゾルバーは現在、そのようなアクセス権を持っていない。Pythonで問題となるのは、依存関係ツリーの特定の部分がバージョンに依存している可能性があることだ。Rustでは、ライブラリAはライブラリBに依存するか、依存しないかのどちらかであり、Pythonバージョンに条件付きで依存することはない。
Pythonの依存関係の表現力の豊かさは、課題となっている。リゾルバーが優れたメタデータにアクセスできないことと、Pythonバージョンに条件付きで依存関係をオプションにすることができることの組み合わせは、それ自体が難しい。しかし、リゾルバーは、パッケージごとに1つの解決されたバージョンのみを生成できるソリューションを見つけなければならないという事実により、複雑さが増している。
やるべきこと
- 現在マーカーが提供しているもののサブセットを満たし、良好なバランスをとる、制限付きロック形式を試す。
- リゾルバーライブラリの一部としてロックファイルのサポートを提供する。
上限と複数バージョン対応
Pythonの依存関係の解決は、パッケージごとに単一のソリューションを見つけなければならないため、特に難しい。これがPythonエコシステムで機能する理由は、ほとんどのライブラリが上限を設定していないためだ。これは、将来のライブラリをサポートしないという代償を払ってでも、それらを熱心に受け入れることを意味する。これは、Pythonが動的言語であり、通常、ここで多くの柔軟性が可能であるため、 largely可能である。しかし、Pythonの世界で型情報の使用が増加し、適切なロックへの要望が強まるにつれて、上位バージョンの境界がより一般的になる可能性が非常に高い。
しかし、そうなると、Pythonエコシステムは、依存関係グラフ全体が移動するまで、将来のアップグレードをブロックするという問題にすぐに直面し、多くの摩擦が生じる。他のエコシステムでは、パッケージにsemverセマンティクスを厳密に適用し、semverに互換性のない複数のライブラリを同時にロードできるようにすることで、この問題を解決している。通常、ライブラリは依存関係の単一バージョンのみを許可できるが、その依存関係は依存関係ツリー全体で異なるバージョンで存在できる。
Pythonでは、`site-packages`、`PYTHONPATH`、`sys.modules`の仕組みのため、これが実現できないという懸念がある。しかし、私はこれらが解決可能な問題だと考えている。一つには、`.pth`ファイルを使用してインポートシステムの動作を完全に変更できるためであり、二つには、`importlib.metadata` APIが現在ではパッケージが独自のメタデータを解決できるほど強力であるためである。この2つを組み合わせることで、`sys.modules`のインポートとインポートステートメントを「リダイレクト」して、ライブラリが自身の依存関係をインポートする場合に、正しいバージョンになるようにすることができる。
やるべきこと
- パッケージがマルチバージョンをサポートしていることを宣言する新しいメタデータキーを`pyproject.toml`に追加する。
- マルチバージョン依存関係にsemverセマンティクスを適用する。
- Ryeの一部としてマルチバージョンインポートを提供するインポートフックを提供する。
- マルチバージョン依存関係の複数のソリューションを許可するようにリゾルバーを緩和する。
ワークスペースとローカル/複数パス参照
開発チームが大きくなるにつれて、最もイライラする経験の1つは、パッケージインデックスにマイナーバージョンを常に公開することなく、モノリシックなPythonモジュールをより小さなモジュールに分割できないことだ。Rustエコシステムがこの問題に対処する方法は2つある。1つは、Rustがワークスペースをネイティブにサポートしていることだ。ワークスペースは依存関係とリゾルバーの結果を共有する。Pythonでは、ワークスペースがすべてのプロジェクトでvirtualenvを共有することと同等である。Rustがこの問題を解決する2つ目の方法は、依存関係がパッケージ名、インデックス、およびローカル参照の宣言をサポートできるようにすることだ。
また、Rustは、インデックス外の pakketに参照を持つクレートをパッケージインデックスに公開することは許可していないが、公開前に別の書き換え手順が開始され、無効な依存関係参照が削除される。有効な参照が残っていない場合、パッケージは公開されない。
やるべきこと
- 要件宣言は、見つかったインデックスの名前とオプションのローカルパス参照を定義できるように拡張する必要がある。
すべてのプロジェクトを仮想環境に
virtualenvは私のお気に入りのツールではないが、標準に最も近いツールだ。私は、virtualenv `.venv`には常に1つのパスがあり、Ryeがそれを管理する場合、ユーザーは手動で操作すべきではないことを提案した。その時点で、それを管理するのはryeの責任であり、使い捨ての、常に再作成可能な依存関係のスクラッチパッドであるかのように管理する必要がある。
できれば、時間の経過とともに、virtualenvの構造が異なるPythonバージョン(例:WindowsとLinux)間で一致し、深くネストされた`lib/py-ver/site-packages`構造がフラット化される。
やるべきこと
- 管理対象virtualenvの配置場所の名前について合意する(例:ワークスペースルートの`.venv`)。
開発用とツール用の依存関係
現在、エコシステムのツール全体で未解決のもう1つのトピックは、本番環境で使用されていない依存関係をどのように処理するかだ。たとえば、特定の依存関係が実際には開発者のマシンでのみ重要であることはよくあることだ。現在、pdmといくつかの他のツールは、`pyproject.toml`ファイルにカスタムセクションがあり、開発の依存関係をマークしているが、ツール全体で合意はない。
やるべきこと
すべてのツールで合意された標準が必要である。この議論を参照
意見のあるデフォルト設定
PEP-8の要望に反して、Pythonには物事をレイアウトできる方法が多すぎる。共通の標準を奨励するためにもっと強くプッシュする必要がある。
やるべきこと
- Ryeには、唯一の真のフォーマッターが付属する。
- Ryeには、唯一の真のリンターが付属する。
- Ryeは、常に新しいプロジェクトに推奨されるフォルダー構造を作成する。
- `package-foo`が`package_foo`モジュールを提供していない場合、Ryeは大声で警告する。
既存のツール
エコシステムの既存のツールのいくつかは完成に近づいており、これらのいくつかが力を合わせて唯一の真のツールを作成できる可能性は十分にある。Ryeになろうとする3つのツールにならないように、十分な共通の関心があることを願っている。