Skip to content

feat: pip-compile -> uv migration - #38915

Open
irfanuddinahmad wants to merge 58 commits into
masterfrom
irfanuddinahmad/uv-migration-consolidated
Open

feat: pip-compile -> uv migration#38915
irfanuddinahmad wants to merge 58 commits into
masterfrom
irfanuddinahmad/uv-migration-consolidated

Conversation

@irfanuddinahmad

@irfanuddinahmad irfanuddinahmad commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates edx-platform from pip-compile/pip-tools to pyproject.toml + uv, tracked in openedx/public-engineering#543.

Old file -> new equivalent

Old file New equivalent
requirements/edx/kernel.in [project.dependencies]
requirements/edx/bundled.in [dependency-groups].bundled
requirements/edx/testing.in [dependency-groups].testing
requirements/edx/doc.in [dependency-groups].docs (now also pulls in bundled directly -- doc.in did -r base.txt, i.e. kernel + bundled, and docs/conf.py needs bundled XBlocks like openedx-forum/ora2 importable to build at all)
requirements/edx/assets.in [dependency-groups].assets
requirements/edx/development.in [dependency-groups].development
requirements/edx/coverage.in [dependency-groups].coverage
requirements/edx/semgrep.in its own standalone sub-project, scripts/semgrep/{pyproject.toml,uv.lock} -- see below, not a root dependency-group
requirements/edx/github.in absorbed into [project.dependencies]
requirements/edx/openstack.txt [project.optional-dependencies].openstack (a PEP 621 extra, not a [dependency-groups] entry -- installable via pip install openedx-platform[openstack])
requirements/constraints.txt + requirements/common_constraints.txt [tool.edx_lint].uv_constraints (hand-maintained) + [tool.uv].constraint-dependencies (machine-generated from it)
requirements/pip-tools.{in,txt} not needed -- pip-tools itself is gone
requirements/edx/private.in/private.txt no direct successor file, but the same need (keeping extra personal packages installed across syncs) is covered by uv sync --inexact -- see requirements/README.rst for the exact workflow
(none -- new in this PR) [dependency-groups].django42/django52: a real, independently-resolved Django-version test matrix, not a runtime patch on an already-resolved venv
requirements/edx/testing.in's tox entry moved to [dependency-groups].ci, alongside new tox-uv (needed for tox.ini's uv-venv-lock-runner) -- pulled into a bare uv sync via default's {include-group = "ci"} so tox stays available locally

requirements/edx/{base,assets,development}.txt and the 5 sub-projects' compat .txt files stay as machine-generated uv export outputs at their historical paths (for external tooling, e.g. Tutor's Dockerfile, that still installs from them directly) -- see the file-by-file breakdown below for exactly what each one exports.

File-by-file summary

Root packaging & build config

  • pyproject.toml: core of the migration. Added the full [project.dependencies] list (previously just ["setuptools"]), a new [dependency-groups] tree (coverage, bundled, testing, docs, assets, development, django42, django52, ci, default), [tool.uv].default-groups/constraint-dependencies (machine-managed by edx_lint write_uv_constraints), [tool.edx_lint].uv_constraints (the hand-maintained version pins, each with a dated comment and issue link, carried over from the old constraints.txt), and a new [tool.coverage.*] tree.
    • django42/django52: a real, independently-resolved Django-version test matrix, not a runtime patch on an already-resolved venv -- [tool.uv].conflicts declares the two mutually exclusive so uv.lock carries a genuinely separate resolution for each (confirmed: django42 locks Django 4.2.30, django52/testing locks 5.2.x, with real transitive-dependency divergence between the two, not just a Django-version override).
  • scripts/update_uv_constraint_version.py (new): a helper the upgrade-one-python-dependency.yml workflow uses to bump a version pin in [tool.edx_lint].uv_constraints via a proper tomlkit TOML round-trip, instead of sed-patching a text file.
  • scripts/compile_requirements_exports.py (new): the .txt-compatibility-export logic for the root project and every sub-project, extracted out of the Makefile (previously ~70 lines of inline heredoc-style shell) into a plain Python script.
  • .coveragerc (deleted): content moved verbatim into [tool.coverage.*] in pyproject.toml.
  • Makefile: pre-requirements/pip-sync targets replaced with uv sync --group ... targets; compile-requirements rewritten to run edx_lint write_uv_constraints -> uv lock for the root project (and the same lock step for each of the 5 uv-managed sub-projects), then scripts/compile_requirements_exports.py to re-export compatibility .txt files at the old paths for external tools (e.g. Tutor's Dockerfile) that still pip install -r requirements/edx/base.txt directly.
  • tox.ini: switched to runner = uv-venv-lock-runner + dependency_groups = testing; dropped quality from envlist (quality now runs via a dedicated CI workflow/Makefile target, not tox) and the now-redundant usedevelop/commands_pre = make test-requirements.
  • README.rst: install instructions updated from pip install -r requirements/edx/*.txt to uv sync --group ....

CI workflows

All follow the same mechanical pattern: drop the manual pip cache steps in favor of astral-sh/setup-uv's built-in cache (enable-cache: true), and replace every bare pip/pylint/mypy/./manage.py/etc. invocation with uv run <tool> -- not a $GITHUB_PATH splice. An earlier draft of this PR did splice .venv/bin onto $GITHUB_PATH job-wide instead of fixing individual invocations; that approach is gone everywhere now except one line in static-assets-check.yml that's unrelated to Python (node_modules/.bin, needed by npm-installed JS tooling).

  • check-consistent-dependencies.yml: trigger-detection regex extended to also watch pyproject.toml/uv.lock/sub-project files, not just requirements/.
  • check_python_dependencies.yml: re-enabled (was temporarily disabled earlier in this PR's history pending edx-repo-tools[find_dependencies] gaining the ability to scan uv.lock instead of .in/.txt files -- that upstream fix, openedx/repo-tools#735, has since merged). Now runs via uvx --from 'edx-repo-tools[find_dependencies]' find_python_dependencies --req-file uv.lock ..., no persistent install, no actions/setup-python.
  • ci-static-analysis.yml, js-tests.yml, lint-imports.yml, migrations-check.yml, pylint-checks.yml, quality-checks.yml, semgrep.yml, unit-tests.yml: mechanical pip->uv swap as described above.
  • compile-python-requirements.yml, upgrade-one-python-dependency.yml: updated to operate on pyproject.toml/uv.lock instead of .in/.txt/constraints.txt. The dependency-downgrade script now edits [tool.edx_lint].uv_constraints via a proper tomlkit TOML round-trip instead of sed-patching a text file. Both manually triggered and verified against this branch: compile-python-requirements ran make compile-requirements, detected no diff, and correctly exited without opening a PR; upgrade-one-python-dependency (package=django-filter) opened a PR with exactly the expected uv.lock + compat-export changes.
  • static-assets-check.yml: same pip->uv swap, scoped the Python-deps-install step to uv sync --no-default-groups --group bundled --group assets --frozen (rather than pulling in the full default group of dev/test/docs/ci tooling this job doesn't need), and wraps the two npm run build-dev/npm run build steps in uv run (instead of a $GITHUB_PATH splice) so the scripts/compile_sass.py script they shell out to -- resolved through its own #!/usr/bin/env python shebang -- finds the venv's Python on PATH.
  • units-test-scripts-structures-pruning.yml, units-test-scripts-user-retirement.yml: intentionally untouched -- they still pip install -r scripts/.../requirements/testing.txt, and those compatibility files continue to be regenerated (via uv export) at the same paths, so these two workflows keep working unmodified.

Documentation

  • requirements/README.rst: rewritten to describe the new pyproject.toml/dependency-groups model instead of the old .in/.txt workflow, including how to replace the dropped private.in mechanism with uv sync --inexact, and how the 5 standalone sub-project directories (requirements/edx-sandbox, scripts/xblock, scripts/user_retirement, scripts/structures_pruning, scripts/semgrep) each carry their own independent pyproject.toml + uv.lock.
  • requirements/edx-sandbox/README.rst: same treatment for the sandbox sub-project.
  • scripts/semgrep/README.rst (new): explains why semgrep is isolated into its own sub-project rather than a root [dependency-groups] entry -- semgrep's own dependency chain (via wcmatch) is incompatible with other root-project dependencies (e.g. openedx-authz's pycasbin pin) when resolved together in one shared graph. master's old pip-compile-based setup avoided this by compiling semgrep.txt as a fully separate run; the root uv.lock needs the same isolation, which only a genuinely separate sub-project (like requirements/edx-sandbox/scripts/xblock already were) provides.
  • docs/concepts/testing/testing.rst, docs/references/static-assets.rst: install instructions updated to the uv sync equivalents, scoped to the specific dependency-groups each workflow step actually needs.
  • .readthedocs.yaml: replaced RTD's native python.install pip-requirements mechanism with a single uv sync --active call in post_install, reading directly from uv.lock rather than through an intermediate exported requirements.txt -- this also installs the local package itself as an editable install automatically, replacing what used to be a separate step. --active needs $VIRTUAL_ENV set to know which environment "active" refers to; RTD's build jobs only prepend to $PATH, so post_install derives it explicitly (dirname "$(dirname "$(command -v python)")", matching the standard PEP 405 venv layout) rather than assuming RTD sets it. --group docs, not --only-group docs, so [project.dependencies] (Django/XBlock/etc.) resolves against uv.lock's pins instead of an unconstrained setuptools>=82 that breaks fs/pyfilesystem2's pkg_resources import; docs also now directly includes the bundled group (matching doc.in's old -r base.txt), since docs/conf.py imports cms.envs.common, which needs bundled XBlocks importable to succeed. Verified against Read the Docs' actual build (not just locally) before landing.

Dependency files (requirements/, uv.lock, scripts/*)

  • requirements/edx/*.in (all deleted): superseded by [project.dependencies]/[dependency-groups] in pyproject.toml.
  • requirements/edx/base.txt, assets.txt, development.txt (kept, regenerated): machine-generated uv export compatibility exports at their historical paths for external tooling that still installs from them directly. base.txt exports [project.dependencies] plus the bundled group -- [project.dependencies] (the old base.in's "kernel" packages) installs with every uv sync regardless of which --group you pass, so --group bundled alone reproduces the old base.txt's kernel + bundled combination.
  • requirements/edx/coverage.{in,txt}, doc.{in,txt}, testing.{in,txt}, semgrep.{in,txt}, bundled.in, github.in, kernel.in (all deleted): fully absorbed into pyproject.toml dependency-groups (or, for semgrep, its own standalone sub-project); no external tooling installed from these paths directly, so no compatibility export was needed.
  • requirements/edx/openstack.txt (deleted): absorbed into [project.optional-dependencies].openstack -- a PEP 621 extra, not a [dependency-groups] entry.
  • requirements/edx/private.readme (deleted): documented the private.in/private.txt mechanism, which itself was never checked into git (git-ignored). No direct successor file -- see the mapping table above for the uv sync --inexact replacement workflow.
  • requirements/common_constraints.txt, requirements/constraints.txt (deleted): absorbed into [tool.uv].constraint-dependencies (machine-managed) and [tool.edx_lint].uv_constraints (hand-maintained) in pyproject.toml, with all original rationale comments and issue links preserved.
  • requirements/pip-tools.{in,txt} (deleted): pip-tools itself is no longer needed.
  • uv.lock, requirements/edx-sandbox/{pyproject.toml,uv.lock}, scripts/{xblock,user_retirement,structures_pruning,semgrep}/{pyproject.toml,uv.lock} (new): machine-generated lockfiles for the root project and its 5 independent standalone sub-projects (codejail sandbox, XBlock scripts, user-retirement scripts, structures-pruning scripts, semgrep). Each sub-project sets [tool.uv] package = false (they're script bags, not installable packages) and carries only the subset of root constraints relevant to its own deps.
  • scripts/*/requirements/{base,testing}.txt (kept, regenerated): same compatibility-export treatment as requirements/edx/*.txt.

Misc scripts

  • scripts/compile_sass.py: docstring rewritten with the minimal setup command needed to run just this script (uv sync --no-default-groups --only-group assets --no-install-project), verified to actually compile Sass correctly with that reduced environment.
  • scripts/watch_sass.sh: one pip install -r ... message updated to uv sync --group default.
  • scripts/xsslint_config.py: added .venv to the linter's skip-dirs list.

What's intentionally NOT done here (tracked externally)

  • Tutor's Dockerfile installs from requirements/edx/{base,assets,development}.txt with plain pip. Those stay as uv export compatibility artifacts rather than being deleted, so no action is required on Tutor's side right now -- but Tutor maintainers should be aware these paths are now machine-generated, not hand-compiled. Verified tutor images build openedx-dev succeeds against this branch on tutor 22.0.0/Verawood (Python 3.12); the previous named release's Python 3.11 base can't satisfy this migration's requires-python = ">=3.12" at all, independent of anything in this PR.

Verification

  • uv lock resolves cleanly (419 packages, root project); all 5 sub-projects (requirements/edx-sandbox, scripts/xblock, scripts/user_retirement, scripts/structures_pruning, scripts/semgrep) sync cleanly with uv sync --frozen.
  • Re-ran make compile-requirements end-to-end for the root project and all 5 uv sub-projects -- regenerated compatibility export files are byte-identical to what's committed, confirming consistency.
  • Manually triggered compile-python-requirements and upgrade-one-python-dependency (both workflow_dispatch-only, so they don't run automatically on this PR) against this branch -- see the CI workflows section above.
  • All CI checks pass on this PR, including docs/readthedocs.org against the real Read the Docs build and tutor images build openedx-dev against a local Verawood (Python 3.12) environment.

🤖 Generated with Claude Code

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Jul 21, 2026
@openedx-webhooks

openedx-webhooks commented Jul 21, 2026

Copy link
Copy Markdown

Thanks for the pull request, @irfanuddinahmad!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform-oncall.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

Comment thread tox.ini
Comment thread .github/workflows/unit-tests.yml Outdated
Comment thread .github/workflows/compile-python-requirements.yml
Comment thread .github/workflows/js-tests.yml Outdated
Comment thread .github/workflows/js-tests.yml Outdated
Comment thread .github/workflows/lint-imports.yml Outdated
@irfanuddinahmad

Copy link
Copy Markdown
Contributor Author

Re: the astral-sh/setup-uv SHA-pinning comments on unit-tests.yml, js-tests.yml, and lint-imports.yml

Good catch, and worth noting: the org already ran a dedicated SHA-pinning sweep for this exact reason (openedx/.github#165, prompted by the tj-actions/changed-files supply-chain incident), but openedx-platform wasn't part of that ~121-repo effort.

That said, I don't think pinning just setup-uv here would meaningfully help: 82 other uses: refs in this repo's workflows (checkout, setup-python, setup-node, etc.) are still floating tags, so the actual attack surface stays open regardless, and pinning only one action creates an inconsistent special case for little real benefit. I'd rather this be a proper repo-wide pinact-based migration (same approach used for the other 121 repos) as its own dedicated follow-up, not a partial fix bundled into this PR.

Irfan Ahmad and others added 10 commits July 27, 2026 15:37
…ration 1/5)

Populates [project.dependencies] (from kernel.in + bundled.in), adds
[project.optional-dependencies] for the legacy openstack storage backend,
adds PEP 735 [dependency-groups] (coverage/testing/doc/assets/development/
semgrep/ci/dev, mirroring the current .in file composition), and
[tool.edx_lint].uv_constraints + generated [tool.uv].constraint-dependencies
for the ~20 repo-specific version pins, with a committed uv.lock.

This is purely additive: the Makefile, tox.ini, and CI workflows are
untouched and continue to use pip-compile/requirements/*.txt as the
source of truth. Part of the pip-compile -> uv migration tracked in
openedx/public-engineering#543 (1 of 5 PRs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ions

Found via real CI runs: a fresh uv resolution picked social-auth-core
5.0.2 (previously locked at 4.9.1 via pip-compile), which changes the
OAuth pipeline's post-login redirect behavior and breaks
common/djangoapps/third_party_auth's integration test suite (AzureAD,
Google, LinkedIn, Twitter full-pipeline specs all failed the same
assertion in tests/specs/base.py's assert_logged_in_cookie_redirect).

This migration is meant to be a tooling swap, not a dependency
upgrade, so pin back to the 4.x line rather than bundle an
investigation into social-auth-core 5.x's behavior change into this
PR. Mirrors the existing social-auth-app-django<=5.4.1 constraint,
pinned for a related, already-deferred migration in this same
dependency family. Follow-up tracked at
#38841.

Verified: all 46 previously-failing third_party_auth tests pass with
social-auth-core==4.9.1 restored via this constraint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rewrites the Makefile's requirements targets, tox.ini, and ~13 CI
workflows to use uv instead of pip-compile/pip-sync for the main app.
Deletes requirements/edx/*.in and *.txt (superseded by pyproject.toml +
uv.lock, added in PR 1 / #38835).

requirements/constraints.txt, common_constraints.txt, and pip-tools.{in,txt}
are intentionally kept for now: requirements/edx-sandbox and scripts/* still
pip-compile against them and aren't migrated until PR 3/4.

requirements/edx/{base,assets,development}.txt are regenerated as `uv
export` compatibility artifacts (via the Makefile's compile-requirements
target) since external tooling -- notably tutor's Dockerfile -- installs
from those exact paths with plain pip, not uv.

check_python_dependencies.yml is disabled (workflow_dispatch only, job
gated with if: false) since find_python_dependencies can't scan
pyproject.toml yet; tracked at openedx/repo-tools#725. User-confirmed
before committing since this removes a CI safety net.

Part of openedx/public-engineering#543 (2 of 5).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found via real CI runs after opening the PR (unit tests, Quality
Others, and the ReadTheDocs build all failed):

1. Makefile's test-requirements used `uv sync --only-group testing`,
   which EXCLUDES [project.dependencies] entirely (confirmed: `--only-group`
   replaces the dependency set rather than adding to it, unlike `--group`).
   This meant Django, XBlock, and the rest of the actual application were
   never installed for test runs -- ModuleNotFoundError: No module named
   'xblock' on every unit test shard. Fixed to
   `--no-default-groups --group testing`, matching what tox-uv itself
   generates for the equivalent tox environment.

2. .readthedocs.yaml had the same bug in its doc-requirements.txt export
   (`--only-group doc`). Since [project.dependencies] were excluded from
   that export, the subsequent `pip install -e .` resolved the whole
   dependency tree completely unconstrained by uv.lock's
   [tool.uv].constraint-dependencies -- picking setuptools==82.0.1 (violates
   setuptools<82) and Django==6.0.6 (violates Django<6.0). The setuptools
   violation broke fs/pyfilesystem2's pkg_resources import, crashing the
   Sphinx build via Django app loading. Fixed to `--group doc` plus
   `--no-deps` on the `pip install -e .` step, so dependencies only ever
   come from the properly-constrained export.

3. scripts/xsslint_config.py's SKIP_DIRS didn't exclude .venv. Under the
   old pip-compile system, dependencies installed into the system Python
   outside the repo checkout, so this never mattered. Now that `uv sync`
   creates .venv/ inside the checkout, xsslint's directory walk (which
   defaults to scanning the whole cwd) swept up thousands of vendored
   third-party files, inflating violations from 64 (the accepted baseline)
   to 316.

Verified all three: real pytest run (59 passed) plus full Django
`manage.py check` for both LMS and CMS against the corrected
test-requirements; a local simulation of the RTD build job sequence
confirms Django==5.2.15/setuptools==81.0.0 (both constraint-compliant)
and a successful Sphinx build.

Audited every other `--only-group` usage introduced in this migration
(assets.txt compat export, semgrep.yml) -- both are intentionally
project-dependency-free, matching the original files' documented
behavior, and their CI checks already passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
requirements/edx/{base,development}.txt were regenerated from the
uv.lock that predated PR 1's social-auth-core<5.0.0 constraint, so
they still referenced social-auth-core==5.0.2 -- caught by
check-consistent-dependencies.yml's re-run of `make compile-requirements`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gives requirements/edx-sandbox/ its own standalone pyproject.toml +
uv.lock, independent of the main app's dependency graph (codejail
intentionally runs untrusted code in a separate, isolated venv).

[tool.edx_lint].uv_constraints holds only the subset of the root
constraints relevant to this environment's deps (numpy, lxml,
setuptools) -- uv/edx-lint have no cross-project constraint chaining
equivalent to pip-compile's "-c ../constraints.txt", so root and
sandbox constraints are now independently maintained (documented in
requirements/edx-sandbox/README.rst).

base.txt is regenerated as a `uv export` compatibility artifact (the
README documents it as a supported, if unstable, direct pip-install
target). releases/*.txt are untouched -- they're frozen historical
snapshots, not part of any active compile loop; README now documents
cutting future ones via `uv export` instead of pip-compile.

Part of openedx/public-engineering#543 (3 of 5).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…on 4/5)

Gives scripts/xblock, scripts/user_retirement, and scripts/structures_pruning
each their own standalone pyproject.toml + uv.lock, mirroring the
codejail sandbox pattern from PR 3. structures_pruning's local
[tool.edx_lint].uv_constraints keeps the pymongo<4.4.1 pin it inherited
via the old "-c ../../../requirements/constraints.txt" chain.

These scripts are documented (in their own READMEs) to support git
sparse-checkout usage -- cloning only e.g. scripts/user_retirement/
without the rest of edx-platform. A self-contained pyproject.toml is
actually an improvement here over the old relative "-c
../../../requirements/constraints.txt" reference, which wouldn't even
resolve in a sparse checkout that excludes the root requirements/ dir.

Compatibility .txt exports are kept at their previously-documented
paths (e.g. scripts/user_retirement/requirements/{base,testing}.txt)
since each script's own README explicitly instructs `pip install -r`
against those exact paths.

Also fixes check-consistent-dependencies.yml's path filter and the two
PR-creating workflows' add-paths, neither of which would have picked
up changes to the new scripts/*/pyproject.toml or uv.lock files.

Part of openedx/public-engineering#543 (4 of 5).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deletes requirements/constraints.txt, common_constraints.txt, and
pip-tools.{in,txt} -- these were kept alive through PR 2-4 because
requirements/edx-sandbox and scripts/* still pip-compiled against
them, but PR 4 was the last consumer, so they're now fully unused.

Removes the correspondingly-vestigial Makefile machinery: the
pre-requirements target, the COMMON_CONSTRAINTS_TXT curl-fetch-and-sed
target, and the CUSTOM_COMPILE_COMMAND/COMPILE_OPTS variables that only
existed to feed pip-compile invocations which no longer exist anywhere
in this repo.

Finalizes requirements/README.rst for the fully-migrated state and
fixes a couple of remaining stale references (constraints.txt ->
[tool.edx_lint].uv_constraints).

This is the last of 5 PRs migrating openedx-platform from pip-compile
to uv + PEP 621/735 pyproject.toml, tracked in
openedx/public-engineering#543. Two follow-up
items remain outside this repo's control:
- openedx/repo-tools#725: find_python_dependencies needs pyproject.toml
  support before check_python_dependencies.yml can be re-enabled.
- Tutor's Dockerfile installs from requirements/edx/{base,assets,development}.txt
  with plain pip; those are kept as `uv export` compatibility artifacts
  (see PR 2 / #38836) rather than deleted, so no action is required there,
  but tutor maintainers should be aware these are now generated files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's "Compile requirements" check re-runs `make compile-requirements`
and fails if it produces any diff, to catch exactly this kind of
inconsistency. The compat-export files in this PR were originally
generated with uv 0.11.26; a newer uv (0.11.30, matching what
astral-sh/setup-uv installs in CI) resolves grpcio/grpcio-status with
an explicit `; platform_python_implementation != 'PyPy'` marker that
0.11.26 omitted. Regenerated with uv 0.11.30 to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The uv migration for scripts/user_retirement dropped the lxml pin that
requirements/constraints.txt previously carried, letting the lockfile
resolve to lxml 6.1.1. The pin exists to avoid a libxml2 version
mismatch at runtime (#36695), and this script
transitively depends on lxml via simple-salesforce -> zeep, so the
same pin applies here as it does at the repo root.
@irfanuddinahmad irfanuddinahmad changed the title feat: consolidated pip-compile -> uv migration (all 5 stacked PRs, rebased onto master) feat: pip-compile -> uv migration Aug 11, 2026
Resolves conflicts in the generated requirements/edx/*.txt compatibility
exports, per master's 16 new commits since this branch's merge-base:

- requirements/edx/{doc,testing}.txt: modify/delete conflicts (this
  migration already deleted both; master's still-pip-compile-based CI kept
  regenerating them). Kept deleted, per this migration's actual intent
  (matches the documented resolution pattern for this exact case).
- requirements/edx/{base,development}.txt: content conflicts (both sides
  regenerated the same file via different toolchains). Regenerated fresh
  via `make compile-requirements` against the merged pyproject.toml/uv.lock
  rather than hand-merging generated output.

Also bumped openedx-core 1.1.0 -> 1.2.0 in uv.lock to match master's own
bump (feat: remove system-defined taxonomies, CONTENT_TAGGING_AUTO #38895) --
that PR's taxonomy-removal changes in openedx/core/djangoapps/content_tagging/
need the newer openedx-core, and merging its code without also bumping the
dependency would have reproduced the same class of bug as the
openedx-filters/CourseModePriceRequested ImportError from earlier in this
PR's history. Verified the constraint (openedx-core<2) already permits
1.2.0, so this is a straightforward `uv lock --upgrade-package` matching
what master already needed for this code to run.

No actual application-code conflicts -- only the generated compatibility
files conflicted; everything else auto-merged cleanly.
@irfanuddinahmad

Copy link
Copy Markdown
Contributor Author

@irfanuddinahmad have you personally reviewed all the code that has been generated and fixed? Please let me know once you have done so and believe it's good for others to review.

@feanil I have reviewed the PR. Please start your review once @salman2013 and @farhan have approved. Thanks.

Irfan Ahmad added 6 commits August 11, 2026 21:45
semgrep's dependency chain (via wcmatch) is incompatible with other
root-project dependencies (e.g. openedx-authz's pycasbin pin) when
resolved together in one shared graph -- master avoided this by
compiling semgrep.txt as a fully separate pip-compile run, but our
migration merged it into the root project's shared dependency-groups
without the same isolation, only django42/django52 got that treatment.

Mirrors the existing requirements/edx-sandbox and scripts/xblock
pattern: a standalone pyproject.toml + uv.lock, independent of the
root project's graph. Unblocks matching openedx-authz/edx-submissions
to their current master versions in the next commit.
The original uv lock resolved fresh, silently bundling months of
accumulated-but-unreviewed dependency upgrades into what was supposed
to be a pure tooling migration -- e.g. openedx-authz 1.20.0 -> master's
1.21.1, edx-submissions 4.0.0 -> 4.0.2, plus ~80 third-party packages.

Re-locked by temporarily constraining each package to master's current
requirements/edx/*.txt pin, then removing the constraint once resolved
(uv lock keeps an already-valid resolution stable, so the pin doesn't
need to persist in pyproject.toml). Left three packages unconstrained
where forcing master's pin would break the django42 test-matrix fork
(django, django-filter, django-simple-history all have a django>=5.2
floor incompatible with django42's django>=4.2,<4.3), since master
never had to resolve against an older Django at all.
The inline shell (heredoc-style echo blocks, nested for-loops, escaped
line continuations) was cluttering the Makefile the same way the old
team-constraints merge logic did before scripts/merge_team_constraints.py
extracted it. Verified byte-identical output against the previous
inline version for every generated file before switching over.

Note: invoke as a plain `python3` script, not via `uv run --no-project`
-- wrapping it in an outer uv-managed ephemeral environment caused the
script's own nested `uv export` calls to silently drop environment
markers (e.g. grpcio's `platform_python_implementation != 'PyPy'`) that
a directly-invoked `uv export` preserves correctly.
Per salman2013's review: npm run build-dev/build shells out to
scripts/compile_sass.py bare via package.json, resolved through its own
#!/usr/bin/env python shebang -- but that doesn't actually require
manually splicing the venv's bin/ onto $GITHUB_PATH for the whole job.
uv run prepends the venv's bin/ to PATH for the wrapped process and all
its descendants, so wrapping the outer npm invocation is sufficient.
Verified the PATH-inheritance mechanism directly (nested subprocess
shebang resolution through uv run) before applying this.
…migration-consolidated

# Conflicts:
#	requirements/constraints.txt
#	requirements/edx/base.txt
#	requirements/edx/development.txt
#	requirements/edx/doc.txt
#	requirements/edx/testing.txt
@irfanuddinahmad

irfanuddinahmad commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Verified the manual workflow testing:

  • compile-python-requirements: triggered on this branchmake compile-requirements ran successfully, correctly detected no diff, and the preflight step exited early without creating a PR, exactly as expected.
  • upgrade-one-python-dependency: triggered with package=django-filter (unpinned) — succeeded end to end and opened feat: Upgrade Python dependency django-filter #38975 with exactly the expected changes (uv.lock + the two uv export compat .txt files, django-filter 25.2→26.1). Closed that PR since it was verification-only.
  • Scripts manual testing: verified uv sync --frozen works in all three sub-project directories (user_retirement, structures_pruning, xblock), and independently tested each README's documented pip install -r ... step against a fresh venv — all three still install successfully against the new uv export-generated files.

@irfanuddinahmad

irfanuddinahmad commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

CI Testing

Compared the two "Upgrade Requirements" runs (#38976 on master, #38974 on this branch) as a cross-check of the uv migration's dependency resolution:

  • 48 of 49 overlapping packages match exactly between the pip-compile-based run and the uv-based run — same target version, same direction.
  • 1 mismatch: django-simple-history (master 3.13.0→3.12.0, this branch 3.12.0→3.13.0). No explicit constraint on either side forces either direction; looks like a minor pip-vs-uv resolver tie-break. Both versions satisfy the package's own django>=5.2 requirement, so non-blocking.
  • Packages only in master's PR (pip/pip-tools, uvicorn/starlette/sse-starlette/pydantic-settings, enterprise-integrated-channels) are all accounted for: the first two don't exist in the uv world at all; the ASGI-stack ones are semgrep's own transitive deps, already at the same latest versions in the now-isolated scripts/semgrep/uv.lock; enterprise-integrated-channels resolves cleanly to the same version locally when forced, just outside this particular run's scope.
  • Packages only in this branch's PR are mostly uv-specific tooling (uv, tox-uv) plus third-party packages already caught up on master from earlier cycles but still catching up here — exactly the kind of routine catch-up this approach is designed to surface as its own separate, reviewable PR.

@salman2013

Copy link
Copy Markdown
Contributor

@irfanuddinahmad
I tested running the upgrade Python requirements workflow with the master branch and this branch there are a few packages like djangorestframework and django-filter, that are not upgrading with master but are upgrading with our uv migration branch. We need to confirm the reason for it.

Upgrade Workflow with master branch
Screenshot 2026-08-12 at 1 00 27 PM

Upgrade Workflow with this branch
Screenshot 2026-08-12 at 1 00 37 PM

Irfan Ahmad added 4 commits August 12, 2026 16:49
Both steps install an already uv.lock-resolved requirements file (and
the local package itself) into RTD's own externally-managed
virtualenv -- uv sync has no mode for installing into an arbitrary
existing venv from a plain requirements.txt, so uv pip install is the
uv-native tool for this, same as the one other confirmed legitimate
uv pip usage in this migration (installing into an external
consumer's environment). Doesn't change what gets installed or where
-- doc-requirements.txt is already fully pinned by uv.lock via the
uv export step above it, this only swaps which tool re-installs it.
…e to docs group

uv sync --active reads directly from uv.lock/pyproject.toml and syncs
into RTD's own already-active virtualenv -- no intermediate uv export
step needed, and it installs the local project as an editable install
automatically, replacing both of the previous two uv pip install steps
with one command.

While verifying this against a simulated RTD-style external venv, a
real gap surfaced: the docs group never got the equivalent of
requirements/edx/doc.in's old `-r base.txt` -- docs/conf.py imports
cms.envs.common, whose add_optional_apps() needs bundled XBlocks (e.g.
openassessment) importable, not just Sphinx tooling. The prior
uv export --group docs command (no --no-default-groups) accidentally
worked because it also pulled in the implicit default group, which
happens to chain through to bundled -- fragile, and broken by adding
--no-default-groups here. Fixed at the source instead: docs now
directly includes the bundled group, matching doc.in's real
composition regardless of default-groups.
uv sync --active failed on RTD's actual build runner (66s duration --
too fast to have gotten far), for a reason I couldn't confirm without
access to RTD's detailed build log (only public build-status API,
which doesn't include command output). Most likely --active isn't
detecting an active venv there the way it did in a local simulation
where I explicitly exported $VIRTUAL_ENV myself -- RTD's actual
activation mechanism for post_install jobs may only prepend to PATH.

Reverted to the confirmed-working uv export + uv pip install
mechanism from before, but keeping the actually-necessary fix from
that attempt: the previous uv export --group docs call (this repo's
prior working config) had no --no-default-groups, so it only worked
by accident via the implicit default-group chain reaching bundled.
Now that docs directly includes bundled (previous commit), this export
can correctly use --no-default-groups too.
…thedocs

Root-caused (via uv sync --active -v) why this failed the first time
it was tried: uv sync --active silently falls back to a project-local
.venv when $VIRTUAL_ENV isn't set, rather than erroring -- RTD's
post_install jobs apparently only prepend to $PATH, never needed to
set $VIRTUAL_ENV since bare pip/python don't require it. Deriving it
ourselves from `command -v python` (standard PEP 405 venv layout:
interpreter always lives at <venv>/bin/python) removes the dependency
on RTD having set it. Verified locally by reproducing the exact
failure mode (PATH-only activation, no $VIRTUAL_ENV) and confirming
this fixes it.
@irfanuddinahmad

Copy link
Copy Markdown
Contributor Author

Tested this branch locally on Tutor 22.0.0 (Verawood, Python 3.12): built both openedx and openedx-dev images from 3b81acad10 cleanly, then ran full init (migrations, Meilisearch reindex) with no errors. Confirms the uv/Python 3.12 setup works end-to-end on the new release, not just in CI.

@farhan farhan 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.

I have completed my second pass.

Overall I think we should not introduce un-neccessary long code documentation, their removal is highly recommended.

Comment thread README.rst
Comment thread pyproject.toml
Comment thread pyproject.toml
"libsass",
"nodeenv",
]
development = [

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.

@irfanuddinahmad @salman2013

doc.in has been moved to docs altering the name not in unparity with master
Headup on it if it's under consideration

Comment thread pyproject.toml
Comment thread scripts/merge_team_constraints.py Outdated
Comment thread .github/workflows/static-assets-check.yml
Comment thread .github/workflows/static-assets-check.yml Outdated
Comment thread .github/workflows/static-assets-check.yml Outdated
Comment thread .github/workflows/upgrade-one-python-dependency.yml Outdated
Comment thread docs/references/static-assets.rst Outdated
Irfan Ahmad added 4 commits August 13, 2026 16:02
- Remove uv-run wrapping of npm invocations; move uv into package.json's
  compile-sass scripts instead, since that's what actually needs the venv's
  Python for scripts/compile_sass.py's shebang.
- Trim/remove several explanatory comments and docstrings added during the
  migration (check_python_dependencies.yml, quality-checks.yml,
  static-assets-check.yml, .readthedocs.yaml, scripts/compile_sass.py,
  requirements/README.rst, docs/references/static-assets.rst) that reviewers
  flagged as unnecessary.
- Fix scripts/watch_sass.sh to point at the `development` dependency-group
  specifically, not the broader `default` umbrella group.
- Add a clarifying note to README.rst about how [project.dependencies] maps
  to the old base.in's "kernel" packages.
…migration-consolidated

# Conflicts:
#	requirements/constraints.txt
…ption

Per feedback -- this belonged in the PR description, not committed docs.
Comment thread tox.ini Outdated
Comment thread README.rst
Irfan Ahmad added 3 commits August 13, 2026 19:17
…constraints

Consolidate the team-governed edx-enterprise pin directly into
[tool.edx_lint].uv_constraints instead of maintaining a separate
CODEOWNERS-gated file + merge step. Removes requirements/team_constraints.txt,
scripts/merge_team_constraints.py, the Makefile/CODEOWNERS/docs references to
them, and simplifies scripts/update_uv_constraint_version.py to only edit
uv_constraints.
…vious commit)

Follow-up to 7ceae62 -- a failed git add silently dropped these from that
commit. Removes the remaining Makefile/CODEOWNERS/workflow/docs references to
requirements/team_constraints.txt and scripts/merge_team_constraints.py, and
moves edx-enterprise's pin into [tool.edx_lint].uv_constraints.

@feanil feanil 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.

There's a lot of extra work happening because we're trying to keep the old requirements files around but I think we can drop a lot of that if we can do a fast-track DEPR before we merge this work. As long as operators know what to do instead of what they were doing, they can adapt with sufficient warning.

if [[ "${{ matrix.django-version }}" == "pinned" ]]; then
make dev-requirements
else
uv sync --group default --group "django${{ matrix.django-version }}" --frozen

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.

shouldn't the django version group include the default group in it?

if [[ "${{ matrix.django-version }}" == "pinned" ]]; then
make test-requirements
else
uv sync --no-default-groups --group testing --group "django${{ matrix.django-version }}" --frozen

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.

Why the --no-default-groups here? Don't we want that?

Comment thread .readthedocs.yaml
os: "ubuntu-lts-latest"
tools:
python: "3.12"
jobs:

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.

https://about.readthedocs.com/blog/2026/04/uv-native-support/

Let's use the native support to do this.

Comment thread Makefile
Comment on lines +67 to +68
local-requirements: ## no-op; kept for backwards compatibility -- uv sync handles this now
@true

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.

We should echo out a message to tell folks to stop using this so it's showing up in their logs. And decide when this can be removed.

Comment thread Makefile
# edx-platform installs some Python projects from within the edx-platform repo itself.
pip install -e .
dev-requirements: ## install development environment requirements
uv sync --group default --frozen

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.

shouldn't this be the development group instead?

Comment thread Makefile
pip-sync requirements/edx/development.txt $(wildcard requirements/edx/private.txt)
make local-requirements
base-requirements: ## install only production/runtime dependencies
uv sync --no-default-groups --group bundled --frozen

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.

This seems like it should be the default?

Comment thread Makefile
Comment thread README.rst
- ``pip install -r requirements/edx/base.txt`` (production)
- ``pip install -r requirements/edx/development.txt`` (development)
- ``uv sync --no-default-groups --group bundled`` (production)
- ``uv sync --group default`` (development)

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.

Isn't default the default? Why add that here instead of just documenting this as uv sync?

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.

I think we don't need these compatibility files, we just need to communicate the fact that they're going away and that people should use the make targets via a fast-track DEPR. That should reduce the complexity of this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

6 participants