跳至内容

理念和愿景

Rye 的构建是为了解决 的问题。以下是我构建它时的想法

  • 虚拟环境:虽然我个人不太喜欢虚拟环境,但它们非常普遍并且有合理的工具支持,所以我选择它而不是 __pypackages__

  • 没有默认依赖:虚拟环境在出现时完全没有依赖。甚至没有在其中安装 pipsetuptools。Rye 从虚拟环境外部管理虚拟环境。

  • 没有核心非标准内容:Rye(除了 pyproject.toml 中的 tool 部分之外)使用标准化的键。这意味着它使用的是您期望的常规要求。它也不使用自定义锁文件格式,而是使用 uv

  • 没有 Pip:Rye 使用 uv 通过 pyproject.toml 来管理依赖。

  • 没有系统 Python:我无法忍受任何更多 Linux 发行版奇怪的 Python 安装或 macOS 上的任何混乱。我以前构建过我自己的 Python,这些 Python 在任何地方都相同,现在我使用 indygreg 的 Python 构建。Rye 会自动从那里下载和管理 Python 构建。无需编译,无需差异。

  • 项目本地 Shims:Rye 维护一个 python shim,该 shim 自动发现当前的 pyproject.toml 并自动在其下方运行。只需将 shims 添加到您的 shell,您就可以运行 python,它会自动始终在正确的项目中运行。

可能是什么?

Python 包世界中存在一些缺点,主要是因为缺乏标准化。这是该项目多年来遇到的问题

  • 没有 Python 二进制发行版:python.org 上的 CPython 构建完全不足。在某些平台上,您只能获得 .msi 安装程序,在某些平台上,您实际上只能获得 tarball。近年来流行的各种 Python 发行版正在大幅度地分化,导致下游出现各种混乱。这就是为什么这个项目使用 indygreg 的独立构建。我希望随着时间的推移,有人会开始分发维护良好且可靠的 Python 构建,以取代我们今天正在处理的混乱局面。

  • 没有开发依赖:Rye 目前需要 pyproject.toml 中的自定义部分来表示开发依赖。生态系统中没有为此制定标准。它确实应该被添加。

  • 没有本地依赖覆盖:没有关于如何表示本地依赖的标准。Rust 为此目的有一个类似于 { path = "../foo" } 的东西,它允许同时存在远程和本地引用,并在发布时对其进行重写。

  • 没有公开的 Pip:pip 故意没有公开。如果您使用 pip 在虚拟环境中安装了一些东西,那么下次您同步时它就会消失。

  • 没有工作区规范:对于单仓库和类似的东西,Python 生态系统需要一个工作区定义。今天还不存在,这迫使每个工具都为这个问题提出自己的解决方案。

  • 没有基本脚本部分:pyproject.toml 中应该有一个标准来表示脚本,就像 ryerye.tools.scripts 中所做的那样。

愿景

这描述了我设想 Python 包和项目管理在理想情况下应该是什么样子

Rust 体验

来自 Rust 环境,有两个工具协同工作:rustupcargo。第一个用于确保您的机器上拥有正确的 Rust 工具链。Rust 非常喜欢官方网站上的语言二进制发行版,而不是外部发行版。

cargo 是 Rust 开发的主要入口点。它充当触发测试运行、启动构建过程、调用文档构建工具、linter 以及工作区管理、依赖管理和包发布等操作的工具。

至关重要的是,Rust 开发体验的一个非常重要的方面是对语义化版本控制的坚定承诺以及对其内置的支持。这深入到核心。例如,解析器将在整个图中对匹配的依赖进行重复数据消除。这意味着,如果四个库依赖于 [email protected],它们都将解析为该依赖项。但是,如果对 [email protected] 出现了其他需求,那么依赖项图可能会导致两者都被加载!

生态系统很大程度上依赖于此。例如,当一个非常核心的库发布了一个新的主要版本时,在某些情况下,会格外注意通过从较新版本重新导出核心类型来统一现在不兼容的版本,从而简化过渡。因此,例如,[email protected] 可以依赖于 [email protected](在内部),以便能够更轻松地过渡。

此外,Rust 很大程度上依赖于锁文件。每当您编译时,依赖项都会被锁定到位,并且以后的构建将重复使用相同的依赖项版本,除非您进行更新。

最重要的是,Rust 生态系统已经拥抱了 rustupcargo,以至于绝大多数人都在日常使用这些工具。即使是选择其他工具(如 buck)的开发人员,也仍然经常使用 cargo

转向 Python

Rye 想要探索这样的体验是否可以在 Python 中实现。我相信可以!生态系统中有很多可以利用的东西,但还有更多需要构建的东西。

重要说明:当您在文档中阅读“rye”时,它指的是像 rye 这样的潜在工具。也可能今天存在的众多工具之一,会变成这里描述的工具。

我的想法是,除非“唯一工具”能够在 Python 世界中出现,否则引入另一个工具可能会对生态系统造成负面影响。多年来已经创建了许多工具,但不幸的是,它无法让大多数 Python 社区团结在任何工具后面。但我相信这是可能的。

引导 Python

我相信正确的方法是让超过 95% 的用户通过 rye 获取 Python 发行版,而不是让 rye 获取系统安装的 Python 发行版。使用系统 Python 安装有很好的理由,但它应该是例外而不是规则。最重要的是,因为 rye 安装的 Python 发行版可以设置为具有可靠且简单的规则,这些规则不会在不同的系统之间有所不同。

当前,混乱和用户沮丧的一个主要原因是 Linux 发行版在 Python 之上的特定补丁,这些补丁破坏了工具并改变了行为,尤其是在 Python 包生态系统中。

通过独立工具引导 Python 也具有其他优势。例如,它允许通过 tox 或 CI 更轻松地进行跨 Python 版本测试。

需要做些什么

  • 提供广泛可用的 Python 构建,这些构建具有高度标准化的结构,可以从互联网上检索。 PEP 711 是朝着这个方向迈出的一步。

更强大的解析器

如今,Python 生态系统中有大量不同的解析器。Pip 有两个,poetry 有一个,pdm 有一个,此外还有不同的独立 Python 和 Rust 解析器。解析器很重要,但不幸的是,解析器既太多又存在太多问题。以下是我认为解析器需要能够实现的目标

  • 允许跨标记解析:如今,Python 生态系统中的大多数解析器只能为当前解释器和平台解析(例如:pip、pip-tools)。这意味着它无法创建对不同平台同样有效的解析。部分原因是 Python 中的环境标记定义方式。它们允许一种表达能力,大多数工具无法反映,但可以支持一个子集。

  • 多版本解析支持:这有点预示着未来,但我认为,由于各种原因,解析器需要能够不将所有要求统一到单个版本,而是支持库主要版本之间的多个独立解析。未来的解析器应该能够允许 package==2.0package==1.1 都解析为树的不同部分。

  • 解析器 API:访问解析器非常重要。对于编辑器插件或自定义工具,能够解析包总是很有必要。例如,如果您想要一些简单的事情,比如“将'flask' 的最新支持版本添加到我的 pyproject.toml 中”,那么您需要能够使用解析器。

  • 过滤器:我坚信,一个好的解析器还需要一个过滤器。例如,开发人员必须能够限制解析器保持在目标 Python 版本的范围内,并且永远不要升级到包含过新 Python 版本的树中。同样,为了供应链安全,解析器应该能够限制自身使用一组经过验证的依赖项。

需要做些什么

  • 创建一个可重用的解析器,该解析器可用于生态系统中的多个工具。
  • 使解析器与提议的元数据缓存一起使用
  • 将解析器公开为 API,供多个工具使用。
  • 在解析器中添加一个策略层,可用于在使用之前过滤掉依赖项。

元数据缓存

由于 Python 包和包索引的性质相当简单,解析器将始终受到其可以可靠地提取的元数据的限制。如果系统需要回退到 sdist 上传,这将尤其糟糕,在最坏的情况下,这需要执行 Python 代码来确定依赖项,并且这些依赖项可能在不同的平台上甚至不匹配。

但是,这是一个可以通过足够的缓存解决的问题,并且通过对缓存进行正确的设计,该缓存可以共享。对于 PyPI 来说,为流行的仅 sdist 的包提供“伪”元数据记录,以帮助解析器,这可能很有趣。这可能在很大程度上提高开发人员体验的质量。

需要做些什么

  • 为解析器添加了本地元数据缓存。
  • PyPI 现在能够提供依赖项元数据。

锁文件

鉴于不同的需求,锁文件是否能形成标准尚不明确,但 Python 包管理解决方案需要支持它们。目前锁文件存在很多不同的方法(例如 poetry 和 pdm 就有锁文件),但目前它们处理方式是否足够实用以使基于锁文件的工具获得广泛采用还有待观察。

一部分原因是解析器现状不佳(例如:大型项目在 poetry 中进行依赖项检查可能需要 10 分钟或更长时间),另一方面也是由于依赖项当前声明方式的现实。例如,某些库会“过度”依赖第三方库,即使它们对开发人员来说并非必需。但这些引入的依赖项仍然会影响解析器。

最重要的是,一个好的锁文件还涵盖了当前开发者机器以外的平台。这意味着如果一个项目支持 Windows 和 Linux,锁文件应该能够处理这两个平台上的依赖项树。这就是 cargo 当前所实现的,但 cargo 需要解决的问题要简单得多,因为它可以完美访问包元数据,而 Python 中的解析器现在还没有这些权限。Python 中另一个问题是,依赖项树的某些部分可能是版本相关的。在 Rust 中,库 A 依赖库 B 或者不依赖,但它不会根据 Python 版本来决定是否依赖它。

Python 依赖项的整体表现力是一个挑战。解析器缺乏对元数据的良好访问权限,再加上能够根据 Python 版本将依赖项设置为可选,本身就很难处理。然而,复杂性还体现在解析器需要找到一个解决方案,该解决方案只能导致每个包只有一个解析后的版本。

需要做些什么

  • 尝试使用受限的锁格式,该格式满足目前标记提供功能的一部分,并在两者之间取得良好平衡。
  • 将锁文件支持作为解析器库的一部分提供。

上限和多版本化

解析 Python 依赖项特别具有挑战性,因为每个包都必须找到一个唯一的解决方案。这种做法在 Python 生态系统中能够运作的原因是,大多数库没有设置上限。这意味着它们将欣然接受未来的库,即使是以不支持它们为代价。这在很大程度上是因为 Python 是一种动态语言,通常具有很大的灵活性。然而,随着 Python 世界中类型信息的越来越多使用,以及对适当锁定的更强烈愿望,版本上限可能变得更加普遍。

一旦这种情况发生,Python 生态系统将很快遇到一个问题,即在整个依赖项图升级之前,将阻止未来的升级,这会造成很多摩擦。其他生态系统通过严格地将 semver 语义应用于包,以及允许同时加载多个 semver 不兼容的库来解决这个问题。虽然通常只允许一个库允许一个依赖项的单个版本,但该依赖项可以在整个依赖项树中存在不同的版本。

在 Python 中,人们担心这无法实现,因为 site-packages、`PYTHONPATH` 和 `sys.modules` 的工作原理。但我认为这些问题是可以解决的。一方面,因为 `.pth` 文件可以完全改变导入系统的工作方式,另一方面,因为 `importlib.metadata` API 现在足够强大,允许一个包解析它自己的元数据。这两种方法的结合可以用于在 `sys.modules` 和导入语句中“重定向”导入,以确保如果一个库导入其自身的一个依赖项,它最终会获得正确的版本。

需要做些什么

  • 在 `pyproject.toml` 中添加一个新的元数据键,用于声明一个包支持多版本化。
  • 对多版本依赖项强制执行 semver 语义。
  • 提供一个导入钩子,作为 Rye 的一部分提供多版本导入。
  • 放松解析器,允许对多版本依赖项使用多个解决方案。

工作区和本地/多路径引用

随着开发团队的不断壮大,最令人沮丧的体验之一是无法将一个单一的 Python 模块拆分成更小的模块,而无需不断发布次要版本到包索引中。Rust 生态系统解决此问题的方法有两个:一方面,Rust 本地支持工作区。工作区共享依赖项和解析器结果。在 Python 中,等效的方法是,工作区在所有项目之间共享一个虚拟环境。Rust 解决这个问题的第二种方法是允许依赖项既支持包名称、索引的声明,也支持本地引用的声明。

虽然 Rust 不允许将一个箱子发布到包索引中,其中包含对索引外部包的引用,但一个独立的重写步骤会在发布之前启动,以清除无效的依赖项引用。如果没有任何有效的引用,该包将不会发布。

需要做些什么

  • 需求声明需要扩展以支持定义可以找到它们的索引的名称,以及可选的本地路径引用。

每个项目都在虚拟环境中

虽然 virtualenv 不是我最喜欢的工具,但它是我们最接近标准的工具。我建议始终为虚拟环境提供一个路径 `。venv`,当 Rye 管理它时,用户不应该手动与它交互。这时,Rye 负责管理它,它会像管理一个一次性可重新创建的依赖项草稿一样管理它。

最好随着时间的推移,虚拟环境的结构在不同的 Python 版本之间保持一致(例如:Windows 与 Linux),并且深度嵌套的 `lib/py-ver/site-packages` 结构被扁平化。

需要做些什么

  • 就管理的虚拟环境的放置位置达成一致(例如:工作区根目录下的 `。venv`)。

开发和工具依赖

生态系统中工具之间目前尚未解决的另一个主题是如何处理在生产环境中不使用的依赖项。例如,某些依赖项实际上只在开发者机器上才有用。今天,pdm 和其他一些工具在 `pyproject.toml` 文件中创建了自定义部分来标记开发依赖项,但不同工具之间没有达成一致意见。

需要做些什么

需要为所有工具制定一个统一的标准。[查看此讨论](https://discuss.python.org/t/development-dependencies-in-pyproject-toml/26149/7)

有见地的默认值

与 PEP-8 的意愿相反,Python 有太多种布局方式。应该更加努力地推动鼓励使用通用标准。

需要做些什么

  • Rye 将与唯一正确的格式化程序一起发布。
  • Rye 将与唯一正确的 linter 一起发布。
  • Rye 将始终为新项目创建一个首选的文件夹结构。
  • 如果 `package-foo` 没有提供 `package_foo` 模块,Rye 将发出响亮的警告。

现有工具

生态系统中的一些现有工具非常接近,而且很有可能其中一些工具能够合力创建一个真正的工具。我希望有足够的共同兴趣,以避免最终出现三个工具都试图成为 Rye 的情况。