Skip to content

chore(api): manage Python dependencies with uv - #9950

Merged
vihar merged 2 commits into
previewfrom
chore/api-migrate-to-uv
Oct 7, 2026
Merged

vihar merged 2 commits into
previewfrom
chore/api-migrate-to-uv

Conversation

@sriramveeraghanta

@sriramveeraghanta sriramveeraghanta commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Description

Moves the API server (apps/api) from pip requirements*.txt files to uv, with a committed lockfile so every environment installs the same, hash-verified dependency set.

  • Dependencies are declared in apps/api/pyproject.toml and pinned in apps/api/uv.lock. The old files map to: base.txt → [project].dependencies; production.txt → production group (gunicorn); local.txt → dev group; test.txt → test group. uv sync installs dev + test by default. .python-version pins 3.12. [tool.uv] required-version = "==0.12.21" pins uv itself. Ruff config moved to apps/api/ruff.toml, so pyproject.toml (and the Docker dependency layer) only changes when dependencies do.
  • Docker images (Dockerfile.api, Dockerfile.dev) install with uv sync --locked, using the uv 0.12.21 image pinned by digest, into the image's own Python (/usr/local) rather than a venv. That keeps python, gunicorn, celery and the entrypoints at the same paths as before, keeps pip usable in the container, and avoids the dev compose bind-mount (./apps/api:/code) hiding an in-tree .venv. The production image bind-mounts uv for the install step only, so the binary isn't shipped.
  • Test stack: docker-compose-test.yml re-syncs against the mounted uv.lock (a no-op when the image is current) instead of pip install -r requirements/test.txt. On an image built before this PR it exits with a hint to rebuild, and the documented run commands pass --build.
  • CI (migration-check.yml, pull-request-build-lint-api.yml) uses astral-sh/setup-uv@v7 with lock-keyed caching. It reads the uv version from required-version and runs on managed CPython 3.12.12, the same patch release as the production image. --locked fails the job if pyproject.toml changes without regenerating uv.lock. Lint syncs only the dev group, uses the locked ruff (0.9.7), and no longer passes --fix, so fixable violations fail the job. The three unused imports --fix had been silently rewriting are removed.
  • Docs: AGENTS.md gets a short uv section; RUNNING_TESTS.md and CONTRIBUTING.md are updated. A new apps/api/.dockerignore keeps local venvs, .env and host bytecode out of the dev image.

Dependency decisions worth reviewing

  • httpx 0.24.1 → 0.28.1 (test group). A uv lock holds one version per package across all groups, so the old test-only pin would have pulled production down to httpcore 0.17 / h11 0.14.0, which is affected by CVE-2025-43859 (request smuggling). Production already resolved 0.28.1 via openai, and no code imports httpx directly (tests use DRF's APIClient).
  • The psycopg implementation now comes from a group. Base has only psycopg. psycopg-c (sdist-only, needs libpq headers and a compiler) is in production, so only the production image and migration-check compile it. psycopg-binary is in dev/test, so local setups and the dev/test image never compile, and production no longer ships the unused binary wheel. The dev/test image therefore uses the binary implementation, while production uses C.
  • The dev image now also includes the test group, because test is a default group.

Heads-up: apps/api/requirements.txt and requirements/ are deleted. Any external tooling that runs pip install -r apps/api/requirements.txt should switch to uv sync --locked --no-default-groups --group production, or install from uv export --no-default-groups --group production.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

Verified locally:

  • Lock parity: for each of production, dev and test, uv export from the lock lists the same package versions as uv pip compile of the old requirements files (linux, py3.12). The only differences are the intentional httpx bump and Windows-only colorama.
  • Production image (Dockerfile.api) builds on Alpine with 106 packages, including psycopg-c and not psycopg-binary. uv is not present in the image, psycopg.pq.__impl__ == "c", and python manage.py check passes with production settings.
  • Migration-check job steps pass locally under uv: manage.py check and makemigrations --check --dry-run.
  • Backend suite via docker compose -f docker-compose-test.yml up --build --abort-on-container-exit --exit-code-from api-tests (builds the new Dockerfile.dev): 706 passed. The only failures came from a local, uncommitted WIP test file that isn't part of this PR.
  • Local macOS: cd apps/api && uv sync succeeds without Postgres dev headers (psycopg.pq.__impl__ == "binary").
  • Lint job, simulated locally: UV_PYTHON=3.12.12 uv sync --locked --only-group dev installs 6 packages. ruff check . exits 1 on a fixable violation and 0 on the tree.
  • uv pin: a mismatched uv (0.12.20) refuses to run with a required-version error. On a pre-uv image, the test entrypoint prints the rebuild hint and exits 1.

For reviewers:

  • Confirm both API workflows go green on this PR. The lint job only runs once a reviewer is requested.
  • Run docker compose -f docker-compose-local.yml up --build and check that api, worker, beat-worker and migrator start and the app loads.
  • Build Dockerfile.api for linux/amd64 as well (the multi-arch release build) and run the image's entrypoint against a real DB.

References

  • No Plane work item.
  • After merge, check that Dependabot/dependency-graph alerts pick up apps/api/uv.lock in place of the removed requirements files.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Updated API development, testing, and production build workflows to use a consistent, locked Python 3.12 environment.
    • API linting now reports issues without automatically modifying files.
    • Updated API checks and test setup to use the locked dependency configuration.
  • Documentation

    • Updated setup and testing guidance to reflect the current Python environment and dependency workflow.

Replace apps/api/requirements*.txt with pyproject.toml dependencies and
dependency groups (production, dev, test), pinned in a committed uv.lock.

- Dockerfile.api / Dockerfile.dev install with `uv sync --locked` into the
  image's Python (/usr/local), so python/gunicorn/celery paths and pip are
  unchanged; uv is bind-mounted for the build step and not shipped in the
  production image.
- docker-compose-test.yml re-syncs against the mounted lock instead of
  pip-installing test requirements.
- CI uses astral-sh/setup-uv; --locked fails the job on a stale lock.
- Bump the test-only httpx pin 0.24.1 -> 0.28.1: a shared lock would
  otherwise downgrade production to h11 0.14.0 (CVE-2025-43859).
- Restrict psycopg-c to Linux so `uv sync` works on macOS without libpq
  headers (psycopg-binary covers it there).
Copilot AI balanced review requested due to automatic review settings October 5, 2026 14:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 69d07de3-3e23-4f90-87e8-196405357958
📥 Commits

Reviewing files that changed from the base of the PR and between 253fffe and 977e817.

⛔ Files ignored due to path filters (1)
  • apps/api/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .github/workflows/migration-check.yml
  • .github/workflows/pull-request-build-lint-api.yml
  • AGENTS.md
  • apps/api/.dockerignore
  • apps/api/Dockerfile.api
  • apps/api/Dockerfile.dev
  • apps/api/plane/app/views/issue/sub_issue.py
  • apps/api/plane/app/views/project/invite.py
  • apps/api/pyproject.toml
  • apps/api/ruff.toml
  • apps/api/tests/RUNNING_TESTS.md
  • docker-compose-test.yml
💤 Files with no reviewable changes (1)
  • apps/api/plane/app/views/project/invite.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/.dockerignore

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The API dependency configuration now uses uv and Python 3.12. Docker images, test execution, and CI workflows sync dependencies through uv. The previous requirements files are removed, and contributor and test documentation describes the updated setup.

Changes

API dependency management

Layer / File(s) Summary
Dependency declarations and Python setup
apps/api/pyproject.toml, apps/api/requirements/*, apps/api/requirements.txt, apps/api/.python-version, AGENTS.md, CONTRIBUTING.md
pyproject.toml defines the Python constraint and runtime, production, development, and test dependencies. The previous requirements files are removed. The Python version and dependency-management guidance now specify Python 3.12 and uv.
Container and test dependency installation
apps/api/Dockerfile.api, apps/api/Dockerfile.dev, apps/api/.dockerignore, docker-compose-test.yml, apps/api/tests/RUNNING_TESTS.md
The API and development images install dependencies with uv. The test entrypoint syncs locked dependencies with uv, and the test guide describes the updated dependency files and commands. The API Docker build context excludes local environments, .env, and Python cache files.
CI dependency setup and tool execution
.github/workflows/migration-check.yml, .github/workflows/pull-request-build-lint-api.yml, apps/api/ruff.toml, apps/api/plane/app/views/issue/sub_issue.py, apps/api/plane/app/views/project/invite.py
Both workflows set up uv and Python 3.12, sync dependencies, and run checks through uv run --no-sync. Ruff configuration is added, and unused imports are removed from two views.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other

Merge Risk: ⚪ Minimal · up to 977e8

The documented container dependency command preserves the image’s pip installation. No confirmed blocker remains; complete the planned CI and image checks before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 977e8

The migration pins dependency installation while preserving existing runtime entrypoints. No privilege expansion or unsafe startup transition was demonstrated. Remaining uncertainty concerns dependency state in reused test images and deployment-wide rollout and rollback behavior.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — A compromised dependency installation could affect each runtime role consuming the shared API image, rather than only the HTTP service. This shared supply-chain exposure predates the migration; the inspected changes do not establish additional tenant, datastore, or infrastructure privileges.

Trust Boundaries and Controls

  • inferred — Reviewed manifests and the lockfile control code installed into the image-owned Python environment. Installer digest pinning, artifact hashes, and locked synchronization constrain installation inputs, but do not prove package behavior trustworthy. Production mounts uv only during installation; development retains the binary for supported synchronization.

Resilience and Maintainability Implications

  • observed — Production installation chains synchronization to build-dependency removal and cache cleanup. Mounted test startup requires preparation on every invocation before command execution, including after a previous failed attempt; it does not contain a fallback to an unlocked installer.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: managing API Python dependencies with uv.
Description check ✅ Passed The description covers the change, type, test scenarios, and references. It also explains the dependency and Docker changes and includes reviewer guidance. Screenshots are not needed for these changes…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread .github/workflows/pull-request-build-lint-api.yml Outdated
Comment thread docker-compose-test.yml
Comment thread apps/api/.dockerignore
Comment thread apps/api/Dockerfile.dev
Comment thread .github/workflows/migration-check.yml Outdated
Comment thread apps/api/pyproject.toml Outdated
Comment thread .github/workflows/pull-request-build-lint-api.yml Outdated
Comment thread apps/api/Dockerfile.api
Comment thread apps/api/pyproject.toml Outdated
Comment thread apps/api/pyproject.toml
Comment thread apps/api/Dockerfile.api Outdated
- Lint: drop `ruff check --fix` so fixable violations fail the job, remove
  the three unused imports it was silently fixing, and sync only the dev
  group for the lint job.
- psycopg: base keeps only `psycopg`; `psycopg-c` moves to the production
  group (production image + migration-check) and `psycopg-binary` to
  dev/test, so local setups never compile and production ships no unused
  binary wheel.
- Pin uv once: `[tool.uv] required-version = "==0.12.21"`, read by setup-uv
  via version-file; the uv image is pinned by digest.
- CI runs on managed CPython 3.12.12 to match the production image.
- Move ruff config to ruff.toml so pyproject.toml (and the Docker dependency
  layer) only changes with dependencies.
- .dockerignore also excludes venv, .env and host bytecode.
- Test entrypoint fails with a rebuild hint on pre-uv images; documented
  `run` commands pass --build; document `uv sync --inexact` in containers.

@pablohashescobar pablohashescobar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. All first-round threads are addressed and CI is green.

Known limits, accepted for this PR:

  • The uv image is amd64/arm64 only, so from-source self-host builds on other architectures (install.sh fallback) no longer build.
  • RUN --mount in Dockerfile.api requires BuildKit.
  • Both API workflows share a setup-uv cache key, so migration-check never caches its psycopg-c build; a per-job cache-suffix fixes it.

@vihar
vihar merged commit 66c95bd into preview Oct 7, 2026
16 checks passed
@vihar
vihar deleted the chore/api-migrate-to-uv branch October 7, 2026 19:06
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.

4 participants