Skip to content

chore: migrate backend to uv package manager#84

Merged
keting merged 18 commits intoketing:mainfrom
black-pwq:chore/uv
May 6, 2026
Merged

chore: migrate backend to uv package manager#84
keting merged 18 commits intoketing:mainfrom
black-pwq:chore/uv

Conversation

@black-pwq
Copy link
Copy Markdown
Contributor

Summary

This PR migrates the backend from plain pip + requirements.txt to uv, a fast Python package manager. All 211 backend tests pass.

Changes

src/backend/pyproject.toml (new)

Adds a standard pyproject.toml declaring the project metadata and pinned dependencies. Replaces requirements.txt as the source of truth for the backend's dependency set. A [dependency-groups] dev section lists test-only packages (pytest, httpx).

src/backend/uv.lock (new)

Lock file generated by uv, ensuring fully reproducible installs across all environments.

src/backend/Dockerfile

  • Install uv via pip in the base image layer.
  • Replace COPY requirements.txt + pip install -r requirements.txt with COPY pyproject.toml + uv sync --no-dev.
  • Change the CMD from bare uvicorn to uv run uvicorn so the server runs inside the uv-managed virtual environment.

src/docker-compose.yml

Update the backend service command override from uvicorn ... to uv run uvicorn ... to match the Dockerfile change.

src/backend/main.py

Add load_dotenv() at startup so environment variables defined in a .env file are loaded before config.settings is evaluated. This fixes a subtle ordering issue when running locally outside of Docker.

src/backend/services/git_service.py + tests/test_git_service.py

Introduce a module-level alias _sleep = time.sleep and patch services.git_service._sleep in tests instead of services.git_service.time.sleep. The old patch target replaced time.sleep globally, causing anyio's internal thread-pool scheduler (which also calls time.sleep) to inflate the mock call count from 1 to 32, breaking the assertion. Patching the private alias isolates the mock to git_service only.

README.md / README.zh-CN.md

Update the Local Development backend setup instructions to reflect the uv workflow:

  • Remove manual venv creation (python3.12 -m venv .venv && source .venv/bin/activate) and pip install -r requirements-dev.txt.
  • Replace uvicorn main:app --reload … with uv run uvicorn main:app --reload ….
  • Add a note explaining that uv auto-creates the venv from pyproject.toml on first run and that uv sync installs dev dependencies explicitly.

black-pwq added 5 commits May 5, 2026 17:11
…om anyio

The test test_ensure_repo_sync_retries_retryable_fetch_failure was
asserting that time.sleep is called exactly once during a retryable
fetch failure, but it was failing with:

  AssertionError: Expected 'sleep' to have been called once.
  Called 32 times.

Root cause
----------
The test patched 'services.git_service.time.sleep', which replaces the
'sleep' attribute on the shared time module object itself.  Because
patch operates on the module-level name binding, this ends up replacing
time.sleep globally for the duration of the test.  The anyio library
(used by pytest-anyio, which is loaded as a plugin) runs an internal
thread pool that polls with exponential back-off using time.sleep
directly.  Those 31 extra calls (0.001, 0.002, 0.004, ... 0.05 × 25)
belong to anyio's scheduler, not to git_service, yet they were captured
by the same mock, causing the assertion to fail.

Fix
---
Introduce a module-level alias in git_service:

    _sleep = time.sleep

and use _sleep(delay) inside _retry_git_operation instead of calling
time.sleep(delay) directly.

The test now patches 'services.git_service._sleep' instead of
'services.git_service.time.sleep'.  This replaces only the name binding
inside the git_service module namespace; time.sleep elsewhere in the
process (including inside anyio) is unaffected.  The mock therefore
captures exactly the one call made by the retry logic, and
assert_called_once() passes reliably.
Replace the manual venv + pip workflow with uv in both the English
and Chinese README:

- Remove: python3.12 -m venv .venv && source .venv/bin/activate
- Remove: pip install -r requirements-dev.txt
- Replace: uvicorn main:app ... → uv run uvicorn main:app ...
- Add a note explaining that uv auto-creates the venv from pyproject.toml
  and that dev deps can be installed with
@black-pwq black-pwq requested a review from keting as a code owner May 6, 2026 04:36
@black-pwq
Copy link
Copy Markdown
Contributor Author

073e552 之后新增了以下改动:

src/backend/pyproject.toml / uv.lock

将依赖 dotenv 更正为正确的包名 python-dotenv,并更新 lock 文件。

src/backend/Dockerfile

改用 COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv 安装 uv,替代原来的 pip install uv,避免在生产镜像中保留 pip 层。

.github/workflows/ci.yml

将后端 CI 依赖安装从 pip install -r requirements-dev.txt 迁移至 astral-sh/setup-uv action + uv sync --frozen;测试命令改为 uv run python -m pytest

.github/dependabot.yml

将后端包生态从 pip 改为 uv,避免 Dependabot 因找不到 requirements.txt 而报错;同步更新注释中的文件名引用。

src/backend/requirements.txt / requirements-dev.txt (已删除)

删除迁移后不再使用的两个文件,消除与 pyproject.toml 的歧义。

docs/project-structure.md

将后端目录树中的 requirements.txt 条目替换为 pyproject.tomluv.lock 的描述。

README.md / README.zh-CN.md

  • 本地开发:在后端步骤前新增 uv 安装说明(附官方文档链接和 curl 安装命令)。
  • Testing 节:将 python -m pytest tests/ -v 改为 uv run pytest tests/ -v,避免在未激活 venv 时报错。

black-pwq added 4 commits May 6, 2026 09:19
Without these flags, uv run syncs the full environment (including dev
dependencies) at container startup, causing network failures in air-gapped
or --network none deployments.
@keting keting merged commit 94010f8 into keting:main May 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants