feat: modernize to uv + pyproject.toml + semantic-release - #324
feat: modernize to uv + pyproject.toml + semantic-release#324irfanuddinahmad wants to merge 4 commits into
Conversation
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.
|
Thanks for the pull request, @irfanuddinahmad! This repository is currently unmaintained. 🔘 Find a technical reviewerTo get help with finding a technical reviewer, reach out to the community contributions project manager for this PR:
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 approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo 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:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere 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:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Summary
Modernize
xss-utilsto uv + pyproject.toml (PEP 621/735) + python-semantic-release.Part of openedx/public-engineering#506 (tracked in openedx/public-engineering#516).
xss_utils/tosrc/xss_utils/(org-widesrc/layout decision on #506, precedent set byxblocks-extra)setup.py/setup.cfgwithpyproject.toml(PEP 621 static metadata)uvwith PEP 735 dependency groups; commituv.locktox.inito usetox-uvwithuv-venv-lock-runnerastral-sh/setup-uv; SHA-pin actions touched in this PR (actions/checkout,astral-sh/setup-uv,codecov/codecov-action)python-semantic-release+release.yml, publishing to PyPI via OIDCRemoved
Deleted files:
setup.py,setup.cfg,.coveragerc,requirements/*,.github/workflows/publish_pypi.yml(the old tag-push/token-based PyPI publish workflow, replaced byrelease.yml's OIDC publish job)Removed Makefile targets: none removed;
upgrade,requirements,quality,docs,test-allwere rewritten to calluv/uv run toxinstead ofpip-compile/baretox, but every existing sub-command is preserved (diffed target-by-target against the pre-migration Makefile).Not included
setup.cfgintopyproject.toml's[tool.isort]/[tool.pydocstyle]/[tool.doc8];pycodestylehas nopyproject.tomlsupport, so its[pycodestyle]section stays intox.ini).Known gaps (flagging, not fixing here)
.github/workflows/upgrade-python-requirements.ymlwill silently stop producing real PRs. This repo callsopenedx/.github's sharedupgrade-python-requirements.ymlreusable workflow, which hardcodesADD_PATHS="requirements"for its PR-creation step with noadd_pathsinput exposed. Now thatrequirements/is deleted,make upgrade's real output (uv.lock/pyproject.tomlchanges) 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 inopenedx/.github(parameterizeadd_paths, or auto-detectuv.lockvsrequirements/).release.yml'spublish_to_pypijob assumes a PyPI trusted publisher is already set up forxss-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 tagv1.0.0(viagit 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 lockresolves cleanly (104 packages), anduv lock --checkconfirms the committed lock matchespyproject.tomluv sync --group devsucceedsuv run toxpasses all four envs:py312-django42,py312-django52,quality,docs(including thedocsenv'spython -m build+twine check dist/*step)src/layout works, and the full test suite passes with 100% coverage both before and after the coverage-config migration (nocodecov.ymlthreshold widening was needed — the old.coveragercdidn't measure test files any differently from production code here, so there's no reported % drop from theomitpattern 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.ymlrun (OIDC publish, PSR version computation) and theupgrade-python-requirements.ymlgap described above.Code reviewer notes
[tool.uv].conflictslists every dependency group that transitively includes the default Django pin (test,quality,doc,ci,dev) oppositedjango42, so both Django versions can coexist in oneuv.lock. Update this list if theinclude-groupchain 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 matchingopenedx/XBlock's actual productionrelease.yml, rather than SHA pins —release.ymlonly runs on push tomaster, never during PR CI, so a bad SHA pin here would be invisible until the first real release attempt.pypa/gh-action-pypi-publishis the one exception, SHA-pinned toba38be9e461d3875417946c167d0b5f3d385a247(v1.14.1's actual commit, verified viagh api repos/pypa/gh-action-pypi-publish/commits/<sha>) because its@release/v1floating branch ref has caused a real production publish failure elsewhere in the org.docs/conf.pyandsrc/xss_utils/__init__.pyno longer read__version__via regex; both useimportlib.metadata.version("xss-utils")instead, with a# pragma: no cover'dPackageNotFoundErrorfallback.This PR was created with Claude Code.