跳到内容

迁移到 uv

我们为什么迁移到 uv

我们最近从 poetry 迁移到 uv,因为我们想从它的许多特性中受益,例如:

  • 更轻松的依赖管理,内置自动缓存
  • 与 poetry 相比,CI/CD 显著更快,尤其是在使用 Astral 团队提供的 caching 功能时
  • Cargo 风格的 lockfile,使得更容易采用新的 PEP 功能

我们花了大约 1-2 天时间处理迁移,对结果很满意。平均而言,对于 CI/CD,我们看到我们的任务速度大幅提升。

以下是一些从我们的 CI/CD 运行中提取的任务耗时。

总的来说,我会说一旦我们为单独的 uv github actions 实现了缓存,我们的任务速度提升了约 3 倍,所需时间减少了约 67%。

任务 耗时 (Poetry) 耗时 (UV)
Ruff 格式化 1分16秒 28秒 (-63%)
Pyright 3分3秒 39秒 (-79%)
测试 Python 3.9 1分21秒 32秒 (-61%)
测试 Python 3.10 1分32秒 33秒 (-64%)
测试 Python 3.11 3分19秒 2分48秒 (-16%)
  • 请注意,对于 3.11,我从时间中减去了 1分12秒,因为我们为 gemini 增加了大约 60 个测试,为了进行公平比较,我减去了运行 gemini 测试所需的时间。

我们的大部分较重的任务,如 Test Python 任务,并行运行多个 LLM 调用,因此 UV 的缓存加速在那里的效益有所降低。

我们如何迁移

我们做的第一件事是使用自动化工具将我们的 poetry lockfile 转换为 uv 兼容的 lockfile。为此,我参考了 Sebastian Ramirez 关于如何进行转换的这个帖子

步骤 1:使用 uv 运行 pdm,这将迁移您的 pyproject.toml,并确保删除所有 tool.poetry 部分。您可以在此处查看最初的 pyproject.toml

uvx pdm import pyproject.toml

请注意,由于您正在使用 uv,请确保也删除 pdm 部分以及您的可选组。

# dependency versions for extras
fastapi = { version = ">=0.109.2,<0.116.0", optional = true }
redis = { version = "^5.0.1", optional = true }
diskcache = { version = "^5.6.3", optional = true }
...


[tool.poetry.extras]
anthropic = ["anthropic", "xmltodict"]
groq = ["groq"]
cohere = ["cohere"]
...


[tool.pdm.build]
includes = ["instructor"]
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

步骤 2:完成上述操作后,由于您不再使用 poetry,您需要更新构建系统。如果您只是删除它,默认情况下会使用 setuptools,如果您使用 license = {text = "MIT"} 声明了您的许可证,这将引发错误。因此,您需要在 pyproject.toml 中添加以下内容。

这在此 UV issue 此处有文档说明,该 issue 记录了 setuptools 无法处理 Metadata 2.4 键的 bug,因此您需要使用 hatchling 作为您的构建后端。

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

步骤 3:完成上述操作后,运行 uv sync 以生成您的 uv.lock 文件,确保没有依赖问题。

需要了解的新命令

现在我们已经从 poetry 迁移到了 uv,您需要使用一些新命令。

  1. uv sync --all-extras --group <您想安装的依赖组>:这将使用 uv 安装项目的所有依赖项,请确保安装您想安装的特定依赖项。例如,如果您正在编写文档,您可以运行 uv sync --all-extras --group docs

  2. uv run <命令>:这将在您创建的虚拟环境中运行特定命令。在运行我们的 CI 管道时,我们使用此命令来确保我们的命令使用了正确的环境。

迁移您的工作流

我们有一些使用 poetry 的工作流,因此我们需要更新它们以使用 uv。如下所示,您需要对相关工作流进行一些主要更改:

name: Test
on:
  pull_request:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11"]

    steps:
      - uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }} # (1)!

      - name: Cache Poetry virtualenv
        uses: actions/cache@v2
        with:
          path: ~/.cache/pypoetry/virtualenvs
          key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
          restore-keys: |
            ${{ runner.os }}-poetry-

      - name: Install Poetry
        uses: snok/install-poetry@v1.3.1 # (2)!

      - name: Install dependencies
        run: poetry install --with dev,anthropic # (3)!

      - name: Run tests
        if: matrix.python-version != '3.11'
        run: poetry run pytest tests/ -k 'not llm and not openai and not gemini and not anthropic and not cohere and not vertexai' && poetry run pytest tests/llm/test_cohere
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}

      - name: Run Gemini Tests
        run: poetry run pytest tests/llm/test_gemini # (4)!
        env:
          GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}

      - name: Generate coverage report
        if: matrix.python-version == '3.11'
        run: |
          poetry run coverage run -m pytest tests/ -k "not docs and not anthropic and not gemini and not cohere and not vertexai and not fireworks"
          poetry run coverage report
          poetry run coverage html
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  1. 我们改用 uv 来安装 python

  2. 我们改用 astral 的 astral-sh/setup-uv@v4 action 来安装 uv

  3. 使用 uv sync 明显比 poetry install 快得多,并且有缓存我想它会更快。

  4. 我们不再使用 poetry run,而是使用 uv run,它会启动带有依赖项的 python 虚拟环境,然后运行您传入的命令。

然后我们将工作流修改为以下 yml 配置:

name: Test
on:
  pull_request:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11"]

    steps:
      - uses: actions/checkout@v2
      - name: Install uv
        uses: astral-sh/setup-uv@v4
        with:
          enable-cache: true # (1)!

      - name: Set up Python
        run: uv python install ${{ matrix.python-version }}

      - name: Install the project
        run: uv sync --all-extras
      - name: Run tests
        if: matrix.python-version != '3.11'
        run: uv run pytest tests/ -k 'not llm and not openai and not gemini and not anthropic and not cohere and not vertexai' # (2)!
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}

      - name: Run Gemini Tests
        if: matrix.python-version == '3.11'
        run: uv run pytest tests/llm/test_gemini
        env:
          GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}

      - name: Generate coverage report
        if: matrix.python-version == '3.11'
        run: |
          uv run coverage run -m pytest tests/ -k "not docs and not anthropic and not gemini and not cohere and not vertexai and not fireworks"
          uv run coverage report
          uv run coverage html
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  1. 不要忘记启用缓存,以便您的任务更快

  2. 在这里使用 uv run 很重要,因为如果您只运行 pytest,它不会在您的虚拟环境中运行测试,导致测试失败。

基本就是这样!大部分迁移工作实际上是试图找出导致测试失败的原因,然后慢慢修复它们。我们能够轻松升级许多现有依赖项,并确保一切都按预期工作。

我们也刚刚用 uv 进行了第一次发布,非常成功!

结论

我们对结果很满意,并且很高兴迁移到 uv。这是一个平稳的过渡,我们已经在 CI/CD 任务中看到了显著的加速。我们期待未来继续使用 uv。