Skip to content

feat: modernize to uv + pyproject.toml + semantic-release - #324

Open
irfanuddinahmad wants to merge 4 commits into
openedx:masterfrom
irfanuddinahmad:irfanuddinahmad/modernize-python-tooling
Open

feat: modernize to uv + pyproject.toml + semantic-release#324
irfanuddinahmad wants to merge 4 commits into
openedx:masterfrom
irfanuddinahmad:irfanuddinahmad/modernize-python-tooling

Conversation

@irfanuddinahmad

@irfanuddinahmad irfanuddinahmad commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Modernize xss-utils to uv + pyproject.toml (PEP 621/735) + python-semantic-release.

Part of openedx/public-engineering#506 (tracked in openedx/public-engineering#516).

  • Move xss_utils/ to src/xss_utils/ (org-wide src/ layout decision on #506, precedent set by xblocks-extra)
  • Replace setup.py/setup.cfg with pyproject.toml (PEP 621 static metadata)
  • Switch from pip-compile to uv with PEP 735 dependency groups; commit uv.lock
  • Update tox.ini to use tox-uv with uv-venv-lock-runner
  • Update CI to use astral-sh/setup-uv; SHA-pin actions touched in this PR (actions/checkout, astral-sh/setup-uv, codecov/codecov-action)
  • Add python-semantic-release + release.yml, publishing to PyPI via OIDC

Removed

Deleted files: setup.py, setup.cfg, .coveragerc, requirements/*, .github/workflows/publish_pypi.yml (the old tag-push/token-based PyPI publish workflow, replaced by release.yml's OIDC publish job)

Removed Makefile targets: none removed; upgrade, requirements, quality, docs, test-all were rewritten to call uv/uv run tox instead of pip-compile/bare tox, but every existing sub-command is preserved (diffed target-by-target against the pre-migration Makefile).

Not included

  • Ruff: out of scope per the #506 decision thread (2026-07-15) — "Ruff is out of scope for this cycle." Existing pylint/isort/pycodestyle/pydocstyle tooling is left exactly as configured (settings ported verbatim from setup.cfg into pyproject.toml's [tool.isort]/[tool.pydocstyle]/[tool.doc8]; pycodestyle has no pyproject.toml support, so its [pycodestyle] section stays in tox.ini).
  • edx-platform constraint sync script: not added — this repo never had one.

Known gaps (flagging, not fixing here)

  1. .github/workflows/upgrade-python-requirements.yml will silently stop producing real PRs. This repo calls openedx/.github's shared upgrade-python-requirements.yml reusable workflow, which hardcodes ADD_PATHS="requirements" for its PR-creation step with no add_paths input exposed. Now that requirements/ is deleted, make upgrade's real output (uv.lock/pyproject.toml changes) won't match that hardcoded glob — the scheduled job will keep running but silently produce empty-diff/no-op runs instead of real dependency-upgrade PRs. This can't be fixed from this repo; it needs a fix in openedx/.github (parameterize add_paths, or auto-detect uv.lock vs requirements/).
  2. PyPI trusted publisher (OIDC) must be confirmed configured before merge. release.yml's publish_to_pypi job assumes a PyPI trusted publisher is already set up for xss-utils (per the #506 decision thread, this is being configured centrally). If it isn't configured by the time this merges, the first automated release's publish step will fail — this needs a maintainer with PyPI project admin access to confirm or configure it.

Versioning

Dynamic via setuptools-scm, derived from git tags (fallback_version = "0.0.0", never hit in practice since the repo already has real tags). Pre-flight check: the latest tag v1.0.0 (via git tag --sort=-v:refname) matches the actual latest version published on PyPI (1.0.0), so semantic-release's first computed version will be correct.

Testing Notes

Verified locally before pushing:

  • uv lock resolves cleanly (104 packages), and uv lock --check confirms the committed lock matches pyproject.toml
  • uv sync --group dev succeeds
  • uv run tox passes all four envs: py312-django42, py312-django52, quality, docs (including the docs env's python -m build + twine check dist/* step)
  • Editable install under the new src/ layout works, and the full test suite passes with 100% coverage both before and after the coverage-config migration (no codecov.yml threshold widening was needed — the old .coveragerc didn't measure test files any differently from production code here, so there's no reported % drop from the omit pattern change)
  • pyproject.toml's [tool.semantic_release] config parses correctly (semantic-release version --print)

Could not be verified locally (needs CI / a real merge): the actual release.yml run (OIDC publish, PSR version computation) and the upgrade-python-requirements.yml gap described above.

Code reviewer notes

  • [tool.uv].conflicts lists every dependency group that transitively includes the default Django pin (test, quality, doc, ci, dev) opposite django42, so both Django versions can coexist in one uv.lock. Update this list if the include-group chain changes.
  • release.yml's action versions (python-semantic-release@v10.6.1, publish-action@v10.6.1, upload-artifact@v7, download-artifact@v8) intentionally use plain version tags matching openedx/XBlock's actual production release.yml, rather than SHA pins — release.yml only runs on push to master, never during PR CI, so a bad SHA pin here would be invisible until the first real release attempt. pypa/gh-action-pypi-publish is the one exception, SHA-pinned to ba38be9e461d3875417946c167d0b5f3d385a247 (v1.14.1's actual commit, verified via gh api repos/pypa/gh-action-pypi-publish/commits/<sha>) because its @release/v1 floating branch ref has caused a real production publish failure elsewhere in the org.
  • docs/conf.py and src/xss_utils/__init__.py no longer read __version__ via regex; both use importlib.metadata.version("xss-utils") instead, with a # pragma: no cover'd PackageNotFoundError fallback.

This PR was created with Claude Code.

Move xss_utils/ to src/xss_utils/ per the org-wide decision on
openedx/public-engineering#506 (src/ layout in scope for this
modernization cycle, precedent set by xblocks-extra). Update every
path reference to the package: MANIFEST.in, .coveragerc source,
tox.ini quality/lint commands, setup.py's package discovery,
test_settings.py's LOCALE_PATHS, and docs/conf.py's version lookup.

Verified via an editable install + pytest run under the new layout;
all tests still collect and pass.
Replace setup.py/setup.cfg with PEP 621 static metadata in
pyproject.toml, per openedx/public-engineering#506.

- Version is now dynamic via setuptools-scm (derived from git tags),
  with a plain int-parseable fallback_version of "0.0.0"
- License uses the PEP 639 SPDX string format + license-files
- Coverage config ([tool.coverage.*]) moves from .coveragerc; isort,
  pydocstyle, and doc8 config move from setup.cfg to their pyproject
  equivalents ([tool.isort], [tool.pydocstyle], [tool.doc8]) with
  identical settings preserved
- xss_utils/__init__.py no longer hardcodes __version__; it now reads
  the installed distribution's version via importlib.metadata (with a
  pragma-excluded fallback for the uninstalled case), and docs/conf.py
  does the same instead of regex-parsing __init__.py, including the
  sphinx-apidoc invocation's path (stale from the src/ move)
- setup.py and setup.cfg deleted

Verified via an editable install + pytest run: tests pass, coverage
report is unaffected (100% before and after), and isort/pydocstyle/
doc8 all read their config from pyproject.toml correctly. pycodestyle
does not support pyproject.toml, so its config is kept in tox.ini in
the next commit.
Per openedx/public-engineering#506.

- Add PEP 735 [dependency-groups] to pyproject.toml: test-base, test
  (default Django 5.2), django42 (legacy Django 4.2, coexisting with
  the default via [tool.uv].conflicts since it hard-pins a different
  Django range), quality, doc, ci, and dev
- Add [tool.edx_lint].uv_constraints and populate
  [tool.uv].constraint-dependencies via `edx_lint write_uv_constraints`
- Generate and commit uv.lock
- Delete requirements/ entirely; grepped the repo for stale references
  -- MANIFEST.in was already clean from the src/ layout commit. A
  scheduled .github/workflows/upgrade-python-requirements.yml still
  calls openedx/.github's shared upgrade-requirements workflow, which
  hardcodes requirements/ in its add-paths and can't be parameterized
  from this repo -- flagged in the PR description as a known,
  out-of-scope gap
- tox.ini: tox-uv>=1 + uv-venv-lock-runner, dependency_groups instead
  of deps=-r requirements/*.txt; keep pycodestyle's [pycodestyle]
  section here since pycodestyle (unlike isort/pydocstyle/doc8) has no
  pyproject.toml support; drop setup.py from pylint/pycodestyle/
  pydocstyle/isort invocations since setup.py no longer exists; replace
  the docs env's `python setup.py bdist_wheel` with `python -m build`
- Makefile: requirements/quality/docs/test-all now go through
  `uv sync --group dev` / `uv run tox`; upgrade target now runs
  `edx_lint write_uv_constraints` + `uv lock --upgrade` instead of the
  pip-compile chain
- ci.yml: workflow_call trigger (for the release workflow added next),
  astral-sh/setup-uv (SHA-pinned) replaces actions/setup-python,
  fetch-depth: 0 so setuptools-scm can see tags, per-matrix job names,
  job-level permissions: contents: read, and SHA-pin codecov-action
  (previously on a floating major-version tag)
- .gitignore: drop stale requirements/private.* entries, add .venv/*

Verified: `uv lock` resolves cleanly, `uv sync --group dev` succeeds,
and `uv run tox` passes all four envs (py312-django42, py312-django52,
quality, docs) including the docs env's `python -m build` +
`twine check dist/*` step.
Per openedx/public-engineering#506. Pre-flight check: the latest git
tag (v1.0.0, via --sort=-v:refname) matches the actual latest version
published on PyPI (1.0.0), so it's safe to let semantic-release
compute the next version from here.

- Add [tool.semantic_release] to pyproject.toml: build_command uses
  `python -m build` (the PSR action environment has no uv), plus
  major_on_zero = false / allow_zero_version = true
- Add .github/workflows/release.yml: run_tests (reuses ci.yml via
  workflow_call), release (python-semantic-release, GitHub release
  assets via publish-action), and publish_to_pypi (OIDC, id-token:
  write, no stored credentials) -- action versions match
  openedx/XBlock's actual production release.yml exactly
  (python-semantic-release@v10.6.1, publish-action@v10.6.1,
  upload-artifact@v7, download-artifact@v8), except
  pypa/gh-action-pypi-publish, which is SHA-pinned to
  ba38be9e461d3875417946c167d0b5f3d385a247 (v1.14.1's actual commit,
  not the tag object's SHA) because its floating @release/v1 ref has
  caused a real production publish failure elsewhere in the org
- Delete .github/workflows/publish_pypi.yml (the old token-based
  publish workflow triggered on tag push); grepped all workflow files
  for other pypi-publish/gh-action-pypi callers, found none remaining
- commitlint.yml already existed and calls openedx/.github's shared
  workflow correctly; no changes needed

Verified pyproject.toml's semantic_release config parses correctly
(`semantic-release version --print` loads the config without error).

PRE-MERGE BLOCKER: this repo's PyPI trusted publisher (OIDC) must be
confirmed configured before merging, or the first automated release's
publish_to_pypi job will fail.
@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 28, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @irfanuddinahmad!

This repository is currently unmaintained.

🔘 Find a technical reviewer To get help with finding a technical reviewer, reach out to the community contributions project manager for this PR:
  1. On the right-hand side of the PR, find the Contributions project, click the caret in the top right corner to expand it, and check the "Primary PM" field for the name of your project manager.
  2. Find their GitHub handle here.

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.

@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Jul 28, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Jul 28, 2026
@mphilbrick211 mphilbrick211 added the needs reviewer assigned PR needs to be (re-)assigned a new reviewer label Jul 28, 2026
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). needs reviewer assigned PR needs to be (re-)assigned a new reviewer open-source-contribution PR author is not from Axim or 2U

Projects

Status: Ready for Review

Development

Successfully merging this pull request may close these issues.

3 participants