feat: modernize Python tooling (pyproject.toml + uv + semantic-release) - #425
feat: modernize Python tooling (pyproject.toml + uv + semantic-release)#425irfanuddinahmad wants to merge 6 commits into
Conversation
Move eventtracking/ to src/eventtracking/ per the org-wide decision that src/ layout is in scope for this modernization cycle (openedx/public- engineering#506, decision comment 2026-07-15). This fixes editable-install resolution for tools like mypy, which don't resolve flat-layout packages correctly under `pip install -e .`. Update setup.py (package_dir/find_packages), .coveragerc, tox.ini, Makefile, and doc/conf.py to reference the new src/ path. Verified: an editable install still exposes `eventtracking` importable from repo root, and the full test suite (96 passed) plus pycodestyle/pylint both pass unchanged against the new location.
Replace setup.py/setup.cfg with PEP 621 static metadata in pyproject.toml:
- Static `dependencies` list (from requirements/base.in), with a lower
bound on Django matching the only currently-tested version (5.2)
- setuptools-scm derives the version from git tags (version_scheme =
"only-version", local_scheme = "no-local-version", fallback_version =
"0.0.0"); remove the now-permanently-stale hardcoded __version__ from
eventtracking/__init__.py
- SPDX license expression ("AGPL-3.0") + license-files, matching sibling
repos already modernized under this same effort
- [tool.setuptools.exclude-package-data] excludes tests from the wheel
- coverage config ported from .coveragerc into [tool.coverage.*], with the
standard omit patterns (tests/migrations/pycache/settings) added on top
of the repo's existing performance/integration/admin/apps omissions --
verified this does not drop the 95% coverage threshold (still ~97%)
- pycodestyle's setup.cfg config moved into tox.ini's own [pycodestyle]
section (pycodestyle does not read pyproject.toml, so porting it there
would be a silent no-op) rather than setup.cfg, which is deleted
- Draft [dependency-groups] and edx_lint scaffolding added here; wired up
fully in the next (uv) commit
Verified locally: editable install resolves version/metadata correctly,
wheel build excludes test files, full test suite (96 passed) and
pycodestyle/pylint both still pass unchanged.
- Add PEP 735 [dependency-groups] (test-base/test/quality/doc/ci/dev)
mapping the old requirements/*.in layering, plus [tool.edx_lint]
uv_constraints for the repo-specific celery<6.0 pin (the old
backports.zoneinfo;python_version<"3.9" constraint is dropped -- dead
weight now that requires-python is >=3.12)
- Run `edx_lint write_uv_constraints` to populate
[tool.uv].constraint-dependencies from the org's common constraints
- Generate and commit uv.lock
- Delete requirements/; the celery54.txt test-matrix file is dropped too,
since it duplicated pins already present in base.txt/uv.lock and the
tox envlist only ever exercised one celery version
- Update MANIFEST.in: drop its two now-dangling `include requirements/...`
lines (grepped the whole repo for other stale requirements/ references;
the only remaining one is in pypi-publish.yml, which is replaced in the
next commit)
- tox.ini: tox-uv>=1, uv-venv-lock-runner, dependency_groups = ci;
pycodestyle's setup.cfg settings moved into tox.ini's own [pycodestyle]
section (dropped in the metadata commit); envlist simplified from
py{312}-celery{54}-django{52} to py312-django52 since celery54 was a
single-value factor, not an actual matrix
- Makefile: uv sync --group dev/ci, uv run for all tool invocations;
upgrade target now runs edx_lint write_uv_constraints + uv lock --upgrade
- CI workflow: astral-sh/setup-uv pinned to a commit SHA (replaces the
separate setup-python step), uv sync --group ci, uv run tox -e
${{ matrix.toxenv }}, matrix job named after toxenv, job-level
permissions: contents: read, fetch-depth: 0 on checkout (so
setuptools-scm can see tags during test runs), and a workflow_call
trigger so release.yml can reuse it in the next commit
Known gap flagged for the PR description: this repo has a
.github/workflows/upgrade-python-requirements.yml calling the org's
shared reusable workflow, which hardcodes requirements/ in its add-paths
-- that scheduled job will keep running but silently stop producing real
upgrade PRs now that requirements/ is gone. Cannot be fixed from this repo.
Verified locally: uv lock resolves, uv sync --group dev/ci succeed,
uv run tox -e py312-django52 passes the unit-test command (96 passed,
~97% coverage) and both quality commands (pycodestyle, pylint) cleanly;
the integration-test command needs a live MongoDB (provided in CI by
supercharge/mongodb-github-action) so it could not be exercised locally.
- [tool.semantic_release]: build_command uses `python -m build` (the action's environment has no uv available), major_on_zero = false and allow_zero_version = true (no-op today since the repo is past 1.0, but set for consistency and so it never auto-jumps to 1.0.0 if ever reset to 0.x) - Add .github/workflows/release.yml: run_tests reuses ci.yml via workflow_call; release job runs python-semantic-release and uploads a dist artifact; publish_to_pypi job publishes via PyPI OIDC trusted publishing (id-token: write, no stored token). Action versions (python-semantic-release@v10.6.1, publish-action@v10.6.1, upload-artifact@v7, download-artifact@v8) match openedx/XBlock's actual, already-releasing release.yml exactly rather than being SHA-pinned -- release.yml only runs on push to master, never PR CI, so a hand-verified SHA pin here is unusually easy to get wrong invisibly (confirmed in several sibling migration PRs under this same effort). The one exception is pypa/gh-action-pypi-publish, pinned to commit SHA ba38be9e461d3875417946c167d0b5f3d385a247 (v1.14.1) because its `@release/v1` ref is a floating branch, not a tag -- confirmed root cause of a real production publish failure on a sibling repo. All three touched SHAs (checkout, setup-uv, gh-action-pypi-publish) were verified directly against `gh api repos/<owner>/<repo>/commits/<sha>`. - Delete .github/workflows/pypi-publish.yml, the old token-based publish workflow (triggered on `release: published`); release.yml's publish_to_pypi job replaces it. Grepped all workflow files for any other pypi-publish/pypa-gh-action reference; none found. - commitlint.yml already existed and needed no changes. Verified locally: `python -m build` with SETUPTOOLS_SCM_PRETEND_VERSION produces a correctly versioned sdist/wheel, and `semantic-release version --print` (run against a release-branch-shaped ref) computes a real next version from this branch's own conventional commits. Pre-flight check (per migration skill): latest git tag v4.0.2 matches PyPI's actual latest published version (4.0.2) -- confirmed via `git tag --sort=-v:refname` and the PyPI JSON API, so no missing-tag gap exists here. Flagged as a pre-merge blocker in the PR description (not fixable from this repo/PR): PyPI OIDC trusted publisher needs to be configured for `event-tracking` before the first automated release, or publish_to_pypi will simply fail.
|
Thanks for the pull request, @irfanuddinahmad! This repository is currently maintained by 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. |
changelog: "false" was blindly copied from the reference template with
no ticket requirement to disable it. CHANGELOG.rst still exists and is
useful historical documentation, so wire up PSR to keep it updated in
place instead of throwing that away.
- Remove changelog: "false" from release.yml's PSR step
- Add [tool.semantic_release.changelog] config (mode="update", RST
output) so new releases insert above the existing history
- Add the .. changelog-insertion-marker as the first line of
CHANGELOG.rst
- Set tag_format = "v{version}" explicitly to match this repo's actual
v-prefixed tag convention (verified against git tag history)
Resolves modify/delete conflicts on requirements/*.txt: master gained a routine dependency-upgrade commit after this branch deleted the requirements/ directory in Phase 2 of the tooling migration. Keeping the deletion, since replacing requirements/*.txt with uv.lock + pyproject.toml dependency groups is this migration's actual intent.
Summary
Modernize
event-trackingto uv + pyproject.toml (PEP 621/735) + python-semantic-release.Part of openedx/public-engineering#506 (tracked in openedx/public-engineering#514).
eventtracking/tosrc/eventtracking/(org-wide decision, in scope this cycle)setup.py/setup.cfgwithpyproject.toml(PEP 621 static metadata)uvwith PEP 735 dependency groups; commituv.locktox.inito usetox-uvwithuv-venv-lock-runnerastral-sh/setup-uvpython-semantic-release+release.yml, targeting PyPI OIDC trusted publishingRemoved
Deleted files:
setup.py,setup.cfg,requirements/*,.github/workflows/pypi-publish.yml(replaced byrelease.yml'spublish_to_pypijob)Makefile targets: none dropped —
compile-requirements-equivalent (upgrade) is rewritten to useedx_lint write_uv_constraints+uv lock --upgradeinstead ofpip-compile; all other targets (test.unit,test.integration,test.performance,style,lint,install,develop,doc,report,requirements) kept the same name and behavior, just re-pointed atuv.Not included
ruffmigration — per the 2026-07-15 decision on #506, ruff is explicitly out of scope for this cycle.quality/docstox envs — this repo's originaltox.iniran pycodestyle/pylint in the same env as pytest (not a separate env), so that structure is preserved rather than introduced new.celery54tox factor andrequirements/celery54.txt: that file was a byte-for-byte duplicate of pins already present inbase.txt(extracted viagrepin the oldmake upgradetarget), and the tox envlist only ever exercised one celery version — not an actual matrix.uv.lockpins the resolved celery version directly, so the separate file added no information.Versioning
Dynamic, via
setuptools-scm(version_scheme = "only-version",local_scheme = "no-local-version",fallback_version = "0.0.0"). Pre-flight checked: the latest git tag (v4.0.2, viagit tag --sort=-v:refname) matches PyPI's actual latest published version (4.0.2), so there's no missing-tag gap for semantic-release to trip over.Testing Notes
Verified locally at each phase:
uv lockresolves cleanly (96 packages),uv sync --group dev/--group cisucceeduv run tox -e py312-django52— the unit-test command passes (96 passed, ~97% branch coverage,--cov-fail-under=95satisfied) and both quality commands (pycodestyle,pylint) pass cleanlyuv pip install -e .) correctly resolveseventtrackingunder the newsrc/layout; wheel build excludes test filespython -m buildwithSETUPTOOLS_SCM_PRETEND_VERSIONproduces a correctly versioned sdist/wheel (matchingrelease.yml'sbuild_command)semantic-release version --printcomputes a real next version from this branch's conventional commit historyCould not verify locally: the
test.integrationcommand (pytest -k integration) needs a live MongoDB, provided in CI viasupercharge/mongodb-github-action— this only runs in CI, same as before this PR.Pre-merge blockers (not fixable from this PR)
event-trackingbefore the first automated release — otherwisepublish_to_pypiwill simply fail on the first release attempt..github/workflows/upgrade-python-requirements.ymlcalls the org's shared reusable workflow (openedx/.github/.github/workflows/upgrade-python-requirements.yml), which hardcodesrequirements/in itsadd-pathsfor the fork PR-creation step. Now thatrequirements/is deleted, that scheduled job will keep running but silently stop producing real dependency-upgrade PRs (empty diff each time) — it won't fail loudly, so nothing will alert anyone. This can't be fixed from this repo; it needs a fix inopenedx/.github's reusable workflow (parameterizeadd_paths, or auto-detectuv.lockvs.requirements/).Code reviewer notes
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 tags matchingopenedx/XBlock's actual, currently-releasingrelease.yml, rather than being SHA-pinned —release.ymlonly runs on push tomaster, never PR CI, so a hand-verified bad SHA pin here is unusually easy to introduce invisibly. Onlypypa/gh-action-pypi-publishis SHA-pinned (ba38be9e461d3875417946c167d0b5f3d385a247/ v1.14.1), because its@release/v1ref is a floating branch, not a version tag.omitpatterns (*/tests/*,*/migrations/*, etc.) on top of this repo's existing*/*performance*/*/*integration*/*/admin.py/*/apps.pyomissions. Verified this doesn't cause a coverage drop — actual coverage stays ~97%, comfortably above the existing 95% threshold.This PR was created with Claude Code.