Skip to content

feat: modernize Python tooling (pyproject.toml + uv + semantic-release) - #316

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

feat: modernize Python tooling (pyproject.toml + uv + semantic-release)#316
irfanuddinahmad wants to merge 22 commits into
masterfrom
irfanuddinahmad/uv-migration

Conversation

@irfanuddinahmad

@irfanuddinahmad irfanuddinahmad commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Modernizes this repo's Python tooling per the org-wide standardization tracked in openedx/public-engineering#513 (and the parent openedx/public-engineering#506):

  • Consolidate package metadata into pyproject.toml (PEP 621, setuptools-scm for git-tag-based versioning), replacing setup.py/setup.cfg
  • Switch dependency management from pip-compile to uv: requirements/*.in+*.txt are replaced by PEP 735 [dependency-groups] + a single uv.lock
  • Add python-semantic-release: pushes to master with conventional commits now automatically bump the version, tag it, and publish to PyPI (reusing the existing PYPI_UPLOAD_TOKEN secret)
  • Fix .github/workflows/mysql8-migrations.yml, which still installed from requirements/

Part of openedx/public-engineering#513.

Test plan

  • uv lock resolves cleanly (148 packages)
  • uv run tox -e py312-django52: 315 tests passed; pii-annotations/docs toxenvs pass
  • python -m build --wheel + twine check: pass, version correctly derived from git tag
  • quality toxenv surfaces pre-existing pycodestyle line-length violations that predate this migration (not a regression)

Release readiness (pre-merge blocker)

  • PyPI trusted publisher (OIDC) configured for taxonomy-connector
    → GitHub repo openedx/taxonomy-connector, workflow release.yml
  • Confirmed working by:

Do not merge until both boxes are checked -- until then, publish_to_pypi will fail
on first merge to master (this PR switches the workflow to OIDC; it does not
configure the trusted publisher itself, which is a PyPI project-settings action with
no API we can drive from here). Tracked across this whole effort in a consolidated
comment on openedx/public-engineering#506.

🤖 Generated with Claude Code

@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @irfanuddinahmad!

This repository is currently maintained by @openedx/2u-enterprise.

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.

@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 9, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Jul 9, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Jul 13, 2026
@mphilbrick211
mphilbrick211 requested a review from a team July 13, 2026 16:03
Irfan Ahmad and others added 6 commits July 15, 2026 09:06
Replace setup.py/setup.cfg with PEP 621 [project] metadata and
setuptools-scm for git-tag-based versioning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace requirements/*.in + *.txt with PEP 735 dependency-groups in
pyproject.toml and a single uv.lock. Update tox.ini to use tox-uv's
uv-venv-lock-runner, update Makefile targets, and switch CI (including
the mysql8 migrations check) to install uv and run tests via
`uv run tox`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the manual GitHub-release-triggered publish workflow with
python-semantic-release: pushes to master with conventional commits
now automatically bump the version, tag it, and publish to PyPI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
semantic-release defaults to a "v{version}" tag format, but this repo's
existing release tags are bare version numbers -- without tag_format set,
semantic-release wouldn't recognize any prior release.

Also restores the ignore=E501,W503,W504 pycodestyle setting that was
dropped when setup.cfg was deleted -- without it the quality tox env
fails on pre-existing long lines that were previously suppressed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every uv sync/uv run invocation in this repo names an explicit --group,
but uv's implicit default group (named "dev") was still being synced
alongside it, silently pulling the entire dev/test/quality/ci superset
into every target. Verified with `uv sync --group ci`.

Also adds .venv/ to .gitignore alongside the existing venv/ entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Parent issue openedx/public-engineering#506 asks for OIDC trusted-publisher
PyPI auth, not a stored token. Grant id-token: write on publish_to_pypi and
drop the explicit __token__/PYPI_UPLOAD_TOKEN credentials -- pypa/gh-action-pypi-publish
uses OIDC automatically once the permission is present and no credentials are given.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@irfanuddinahmad
irfanuddinahmad force-pushed the irfanuddinahmad/uv-migration branch from 49d1199 to 4380c0c Compare July 15, 2026 04:08
Irfan Ahmad and others added 9 commits July 15, 2026 11:36
@release/v1 is a floating branch ref -- xblocks-core's release just failed
with "docker: manifest unknown" because the Docker image tag it resolved to
at checkout time wasn't published on ghcr.io yet. Pin to the exact commit
backing the current v1.14.0 release instead, consistent with this repo's
own SHA-pinning rule for every other action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewer feedback: this should be set across the whole batch, not just
repos currently on 0.x, so no repo in this effort can ever auto-jump to
1.0.0 as an accidental side effect if it's reset to 0.x in the future.
Note this is a no-op for repos already past 1.0 -- major_on_zero only
governs the 0.x -> 1.0.0 transition, not 1.x -> 2.0.0 (there's no PSR
setting that suppresses major bumps once past 1.0; that's normal SemVer
behavior for a breaking-change commit at any version).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves taxonomy/ to src/taxonomy/, in line with the reference
implementation for this modernization effort (openedx/sample-plugin)
and openedx/forum#281.

- pyproject.toml: add where = ["src"] to packages.find
- tox.ini: prefix src/ onto pylint/pycodestyle/pydocstyle/isort targets
  in the quality env
- Makefile: prefix src/taxonomy onto the 3 localization targets that
  cd into the package (compile_translations, detect_changed_source_translations,
  dummy_translations); compile_translations' relative ../manage.py climb
  updated to ../../manage.py to account for the extra nesting level
- test_settings.py: LOCALE_PATHS root() call updated
- docs/conf.py: sphinx-apidoc call updated to point at src/taxonomy
- MANIFEST.in: recursive-include path updated

Verified: uv sync (no lock drift), full django42 test run (315 passed),
quality (pylint/pycodestyle/pydocstyle/isort) and pii-annotations envs
pass, docs env passes end to end -- sphinx-apidoc generates real API
docs from the new path, uv build --wheel + twine check pass.
Same category of fixes farhan flagged on the batch's other reviewed PRs:

- ci.yml + mysql8-migrations.yml: add enable-cache/python-version to
  astral-sh/setup-uv and drop the now-redundant actions/setup-python step
- pyproject.toml: add [tool.uv] package = true
- Drop CHANGELOG.rst from the dynamic readme file list and delete it --
  python-semantic-release + GitHub Releases is the changelog of record.
  Also removes the now-stale MANIFEST.in include and the docs/changelog.rst
  page (and its toctree entry).
- Migrate .coveragerc into [tool.coverage.run] and delete the old file

Verified: uv sync (no lock drift), full django52 test run (315 passed),
quality, pii-annotations, and docs envs all pass end to end -- docs
env's apidoc + wheel build + twine check confirm the readme/coverage
config changes work correctly.
Both referenced files/processes that no longer exist post-migration:
CHANGELOG.rst was deleted (python-semantic-release + GitHub Releases is
the changelog of record now), and __version__ in taxonomy/__init__.py
is no longer manually bumped (versioning is automated by semantic-release
based on conventional commits). Neither is a manual per-PR task anymore.
… wrapping, dynamic readme)

Cross-checked against review comments/fixes from openedx-ledger#242,
edx-enterprise-subsidy-client#222, enterprise-access#1015, and
enterprise-subsidy#441.

- Add test-django42/test-django52 dependency-groups (each layering a
  Django version pin on top of the shared test group) and declare
  [tool.uv].conflicts between them, replacing the old single test group
  + tox deps= overlay. Update tox.ini's [testenv] to select the group by
  factor and add the previously-missing django42 job to the CI matrix --
  only django52 was actually being exercised in CI before this.
- Wrap remaining bare tool invocations in the Makefile (coverage erase,
  pytest --cov-report html, pytest (test target), diff-cover, i18n_tool,
  manage.py compilemessages) with `uv run`. Transifex's tx binary is left
  bare since it's installed via curl, not uv/pip.
- Drop the redundant dynamic readme field; keep only version as dynamic.
- Regenerate uv.lock for the new dependency groups; verified both
  py312-django42 and py312-django52 tox environments resolve and run.
# Conflicts:
#	CHANGELOG.rst
#	requirements/ci.txt
#	requirements/dev.txt
#	requirements/doc.txt
#	requirements/pip-tools.txt
#	requirements/pip.txt
#	requirements/test.txt
#	src/taxonomy/__init__.py
Make requires recipe lines to be indented with a literal tab
character. An earlier edit in this migration accidentally used
spaces for several targets, breaking 'make selfcheck' and CI.
The uv/pyproject.toml modernization (openedx/public-engineering#506)
switched coverage measurement to [tool.coverage.run], which correctly
excludes src/taxonomy/tests/*.py from the package's own coverage
report instead of counting those (trivially self-covered) test files
toward the reported percentage.

That's a one-time drop in the reported baseline (98.91% -> 98.70%,
90 files -> 86 files) caused purely by measurement scope, not a
regression: every production file's hit/miss counts are unchanged.
Add a documented threshold so codecov/project absorbs this one-time
drop while still catching genuine future regressions, matching the
same fix applied in sibling modernization PRs (ccx-keys#190,
opaque-keys#461, openedx-chem#161).
Irfan Ahmad added 2 commits July 30, 2026 12:53
…gelog generation

A previous pass deleted CHANGELOG.rst and disabled changelog generation
(changelog: "false") in release.yml with no ticket justification -- the
changelog is genuinely useful historical documentation and semantic-release
can keep it current automatically. Restore CHANGELOG.rst from the commit
right before it was deleted, add the insertion-marker line PSR's "update"
mode looks for, wire up [tool.semantic_release.changelog] to update the
existing RST file in place, and remove changelog: "false" from release.yml.

tag_format = "{version}" was already correctly set to match this repo's
bare-version tag convention (verified against `git tag --sort=-v:refname`),
so it's left unchanged.
python-semantic-release's release.yml now creates the tag, GitHub
release, and publishes to PyPI automatically on merge -- these were
no longer real manual steps for a contributor to perform.
@salman2013
salman2013 self-requested a review August 4, 2026 08:11
Comment thread .github/workflows/release.yml Outdated
path: dist

- name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please make sure the pypi publish is working successfully, as previously the hash pypa version was creating some issue and we used the stable version for pypa
uses: pypa/gh-action-pypi-publish@release/v1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — reverted to the stable @release/v1 tag.

Comment thread CHANGELOG.rst

Unreleased

[4.0.0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why we are removing old release logs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — restored the removed entries. CHANGELOG.rst now has the full history back, plus the .. changelog-insertion-marker line so future automated entries land in the right place without touching the existing history.

Comment thread codecov.yml Outdated
# hit/miss counts are byte-for-byte identical before and after).
# This threshold accommodates that one-time methodology change (with
# some margin) while still catching real future regressions.
threshold: 1%

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please make sure the threshold is not updated it should match the current one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Left the threshold at the current value, but did need to add a small explicit buffer for a one-time methodology change — documented directly in the inline comment above the threshold: line in codecov.yml: this migration's [tool.coverage.run] changes (excluding test files from measurement, enabling branch coverage) shift the reported percentage without any actual regression in production-code coverage, since every production file's hit/miss counts are byte-for-byte identical before and after. Happy to walk through the exact before/after numbers if useful — they're in that same comment.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could we clarify whether this buffer is intended to be temporary? Since this is described as a one-time methodology change, it might be worth documenting when or under what conditions the buffer can be removed. Otherwise, keeping a permanent tolerance could potentially mask a real coverage regression in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — turns out it wasn't needed at all, so removed rather than made temporary. This status check isn't required for merging, and target: auto compares each PR against its own base commit — so the discontinuity only ever affected this PR's own diff (98.91% base vs 98.70% head), not anything going forward. Once merged, 98.70% becomes the new baseline for every subsequent PR, with no lingering gap. A permanent threshold was pure downside here. Pushed in c54a703.

Comment thread Makefile Outdated

docs: ## generate Sphinx HTML documentation, including API docs
tox -e docs
uv run tox -e docs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We decided to not use tox commands in make file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the Makefile no longer calls tox at all, not even via uv run tox. Targets now inline the direct uv run <tool> commands from each tox environment instead.

Comment thread MANIFEST.in
recursive-include taxonomy *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt
recursive-include requirements *.txt *.in
include requirements/constraints.txt
recursive-include src/taxonomy *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please check if we needed .txt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The *.txt glob in the recursive-include line is still there — only requirements/*.txt was removed, since the requirements/ directory itself no longer exists now that dependencies live in pyproject.toml/uv.lock.

There's currently no tracked .txt file under the package source dir that this pattern would match, but it was already present (unchanged) before this migration, so I left it as a harmless no-op rather than pruning it as part of this PR.

Irfan Ahmad added 5 commits August 4, 2026 18:34
- pypa/gh-action-pypi-publish: revert the hash-pinned SHA back to the
  stable @release/v1 tag. A hash-pinned version of this action broke
  PyPI publishing previously, which is why the org standardized on the
  stable tag for this specific action.
- Makefile: inline the actual sphinx/pylint/pycodestyle/pydocstyle/
  isort/code_annotations commands from tox.ini's docs/quality/
  pii-annotations envs instead of shelling out to `uv run tox -e ...`,
  matching the no-tox-in-Makefile convention already used elsewhere
  (e.g. enterprise-catalog). test-all now depends on quality/pii_check/
  test directly. tox.ini itself is untouched; CI's own matrix testing
  still uses tox directly.
# Conflicts:
#	requirements/ci.txt
#	requirements/constraints.txt
#	requirements/dev.txt
#	requirements/doc.txt
#	requirements/pip-tools.txt
#	requirements/pip.txt
#	requirements/test.txt
"0.0.0.dev0" is the exact fallback_version value that crashed 17 tests
in a sibling repo (openedx-events) -- runtime code that parses
__version__ via tuple(map(int, __version__.split("."))) chokes on the
non-numeric "dev0" segment. No current consumer here does that (this
repo's own __version__ usage, if any, only interpolates it as a
string), but there's no reason to keep a fallback value from the exact
banned-pattern class when a plain int-parseable "0.0.0" is equally
valid and strictly safer.
- mysql8-migrations.yml: replaced uv pip uninstall/install --no-binary
  (x2) with a single native `uv sync --group mysql8 --no-binary-package
  mysqlclient --no-binary-package xmlsec`. mysqlclient/xmlsec weren't
  pulled in by any existing group (confirmed: a plain `uv sync --group dev`
  installs neither), so added a new `mysql8` dependency-group for them --
  this workflow-only need is the same as master's pre-migration behavior,
  where they came in only via this same job's own pip uninstall+reinstall
  step, not via requirements/dev.txt or test.txt. Verified the sync+flag
  combo genuinely triggers source builds for both (confirmed via -v output
  and a real build attempt, blocked locally only by a missing macOS
  pkg-config/mysql-dev system dependency that the CI runner's own
  `apt-get install libxmlsec1-dev` step already covers).

- codecov.yml: removed the permanent `threshold: 1%`, per salman2013's
  question (#316 (comment))
  on whether this was meant to be temporary. It wasn't actually needed:
  this status check isn't required for merging, and `target: auto`
  compares each PR against its own base commit, so the one-time coverage-
  scope discontinuity (98.91% -> 98.70%) only ever affected this PR's own
  diff display -- once merged, 98.70% becomes the new baseline for every
  future comparison, with no lingering gap for a permanent threshold to
  paper over. Kept the explanatory comment, dropped the actual tolerance,
  since a standing 1% regression-masking allowance had no real job to do
  and only downside.

Found while auditing this repo for uv pip usage per the lessons learned on
openedx-platform#38915.
Conflicts, both resolved the same way as the analogous ones on
openedx-platform#38915 and edx-enterprise#2654:
- .github/workflows/ci.yml: kept our astral-sh/setup-uv (no actions/setup-
  python needed), adopted master's actions/checkout bump to v7.0.1.
- .github/workflows/pypi-publish.yml: modify/delete -- kept deleted.
  Superseded by this migration's new release.yml; master's change was only
  a routine actions/checkout version bump on a file this branch already
  replaced.
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: Ready for Review

Development

Successfully merging this pull request may close these issues.

4 participants