Skip to content

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

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

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

Conversation

@irfanuddinahmad

@irfanuddinahmad irfanuddinahmad commented Jul 27, 2026

Copy link
Copy Markdown

Summary

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

Part of openedx/public-engineering#506.

  • Replace setup.py with pyproject.toml (PEP 621 static metadata); .coveragerc config folded into [tool.coverage.*]
  • Switch from pip-compile to uv with PEP 735 dependency groups (test-base, test, quality, ci, dev); commit uv.lock
  • Update tox.ini to use tox-uv with uv-venv-lock-runner
  • Update CI (ci.yml) to use astral-sh/setup-uv, SHA-pin all actions, add fetch-depth: 0 so setuptools-scm sees tags
  • Add python-semantic-release + release.yml, replacing the old tag-triggered pypi-publish.yml; publish to PyPI via OIDC trusted publishing (no stored token)
  • commitlint.yml already existed and needed no changes
  • Move ccx_keys/ to src/ccx_keys/ — per the confirmed decision on public-engineering#506 (2026-07-15) that src/ layout is in scope this cycle, precedented by xblock-core/xblock-extras

Removed

Deleted files: setup.py, .coveragerc, requirements/ (all .in/.txt files), .github/workflows/pypi-publish.yml

Removed Makefile targets: $(COMMON_CONSTRAINTS_TXT) wget target (no longer needed — edx_lint write_uv_constraints handles constraints now)

Not included

No ruff/lint-tooling changes — pylint/pycodestyle/edx-lint are left exactly as configured before; this migration is tooling-only, not a linting overhaul.

Versioning

Dynamic — setuptools-scm derives the package version from git tags (version_scheme = "only-version", local_scheme = "no-local-version", fallback_version = "0.0.0"). Confirmed the latest git tag (2.0.2) matches the latest version actually published on PyPI, so the first automated release from python-semantic-release should compute a version strictly newer than 2.0.2.

Testing Notes

Verified locally:

  • uv lock resolves cleanly
  • uv sync --group dev succeeds
  • uv run tox — both unittest (79 tests) and quality (pycodestyle + pylint) pass
  • python -m build (the exact build_command semantic-release will invoke) produces a correct wheel: entry points, SPDX license expression, and README-as-long-description all verified by inspecting the built wheel's METADATA; test files are correctly excluded from the wheel

Could not verify locally (needs CI / a maintainer):

  • The actual GitHub Actions run (uv caching behavior, secrets.CODECOV_TOKEN availability)
  • Whether the OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN org secret (used by release.yml to let python-semantic-release push the release commit/tag) is configured for this repo — used by other already-migrated library repos (e.g. openedx/XBlock) in this same effort, but not something I can confirm from outside the org
  • PyPI trusted publisher (OIDC) is not yet configured for edx-ccx-keys, as far as I can tell — this needs a maintainer with PyPI project-owner access to add openedx/ccx-keys's release.yml / publish_to_pypi environment as a trusted publisher on pypi.org. This is not a blocker for merging this PR, but the first automated release will fail at the publish_to_pypi job until it's set up.

codecov.yml added

This migration's [tool.coverage.run] (in pyproject.toml) omits
ccx_keys/tests/* from coverage measurement, per this effort's standard omit
pattern — a test file trivially covers itself just by running, so counting it
was inflating the reported package coverage (previously: source = ./ccx_keys
with no omit, so ccx_keys/tests/test_ccx_keys.py itself counted toward the
percentage). That's a one-time ~3-4% drop in the reported baseline (97.78% →
94.44%), not a regression in actual production-code coverage
(ccx_keys/key.py and ccx_keys/__init__.py are both 100%,
ccx_keys/locator.py is unchanged at 95%). Added codecov.yml with a 5%
project threshold to absorb that one-time change while still catching real
future regressions — sibling repos already migrated in this same effort
(XBlock, openedx-events, edx-enterprise) all carry an equivalent file;
ccx-keys had none before this PR.

Known out-of-scope gap: upgrade-python-requirements.yml

.github/workflows/upgrade-python-requirements.yml calls the org's shared reusable
workflow (openedx/.github/.github/workflows/upgrade-python-requirements.yml), which
hardcodes ADD_PATHS="requirements" (plus a scripts/**/requirements* glob) for the
PR-creation step, with no add_paths-style input exposed on its workflow_call
(confirmed by reading its source directly). Now that requirements/ is deleted, this
scheduled job will keep running but will never see uv.lock/pyproject.toml changes
as a match for its hardcoded add-paths glob — it won't fail, it'll just silently stop
producing real dependency-upgrade PRs. This can't be fixed from this repo alone (it
requires parameterizing or auto-detecting in the centrally-owned reusable workflow) —
flagging it here rather than leaving it as a silent gap, same treatment as the PyPI
OIDC item above.

Code reviewer notes

  • six remains a runtime dependency (six>=1.10.0) even though nothing in ccx_keys/ currently imports it — carried forward unchanged from the old install_requires since this migration doesn't touch actual dependency behavior.
  • ccx_keys/__init__.py's __version__ now reads from importlib.metadata instead of being hardcoded, to avoid it going stale after the first tag-less commit (confirmed a real bug class elsewhere in this effort — a hardcoded __version__ left in place after adding setuptools-scm silently drifts from the actual installed version).
  • src/ layout: verified uv sync still gives an importable editable install (import ccx_keys resolves to src/ccx_keys/__init__.py) and python -m build still produces a wheel with ccx_keys/* at its root (the src/ prefix is a source-tree-only convention, not part of the installed package). This repo has no mypy config to verify against, so I could only confirm the editable-install resolution the decision was motivated by, not an actual mypy-clean run.

This PR was created 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 27, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @irfanuddinahmad!

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

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.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.44%. Comparing base (e160f05) to head (89ca71e).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #190      +/-   ##
==========================================
- Coverage   97.78%   94.44%   -3.35%     
==========================================
  Files           4        3       -1     
  Lines         226       72     -154     
  Branches        8        6       -2     
==========================================
- Hits          221       68     -153     
+ Misses          5        4       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Jul 27, 2026
@mphilbrick211 mphilbrick211 added the needs reviewer assigned PR needs to be (re-)assigned a new reviewer label Aug 19, 2026
irfanuddinahmad and others added 15 commits August 27, 2026 13:02
Replace setup.py with PEP 621 static metadata in pyproject.toml.
setuptools-scm now derives the version from git tags instead of the
hardcoded __version__ in ccx_keys/__init__.py; that attribute is kept
for backward compatibility but now reads the installed package version
via importlib.metadata. Coverage configuration is consolidated from
.coveragerc into [tool.coverage.*].

Part of openedx/public-engineering#506.
Replace requirements/*.in/*.txt with PEP 735 dependency groups in
pyproject.toml, resolved into a committed uv.lock. Update tox.ini to
use tox-uv with the uv-venv-lock-runner, and update the Makefile and
CI workflow to install dependencies via uv sync instead of pip-compile
/ pip-sync. CI now uses astral-sh/setup-uv (SHA-pinned) with caching,
runs each tox env via `uv run tox -e <env>`, and checks out with
fetch-depth: 0 so setuptools-scm can see tags during test runs.

Part of openedx/public-engineering#506.
Add python-semantic-release configuration to pyproject.toml
(major_on_zero = false, allow_zero_version = true) and a release.yml
workflow that runs the CI suite, cuts a release with
python-semantic-release on pushes to master, and publishes to PyPI
via OIDC trusted publishing (no stored API token). Replace the old
tag-triggered, token-based pypi-publish.yml, which would otherwise
race with the new release.yml on the first semantic-release tag.
ci.yml gains a workflow_call trigger and drops its push trigger, since
release.yml now owns the master-branch path. commitlint.yml already
existed and needed no changes.

The latest git tag (2.0.2) matches the actual latest version published
on PyPI, so the pre-flight check for enabling semantic-release passes.

Part of openedx/public-engineering#506.
The importlib.metadata fallback added in the pyproject.toml migration
(except PackageNotFoundError: pass) can't be exercised in CI since the
package is always installed there, so it showed up as uncovered patch
diff and tripped codecov/patch. Mark it pragma: no cover, consistent
with the exclude_lines already configured in [tool.coverage.report].
The pyproject.toml coverage config added in this migration omits
ccx_keys/tests/* from measurement, per this effort's standard
[tool.coverage.run] omit pattern (a test file trivially covers itself
just by running, so counting it was inflating the reported package
coverage). That's a one-time, ~3-4% drop in the reported baseline, not
an actual reduction in production-code coverage (ccx_keys/key.py and
ccx_keys/__init__.py are both 100%, ccx_keys/locator.py is unchanged
at 95%). Add a threshold so codecov/project reflects that instead of
flagging every PR against a baseline that no longer measures the same
thing. Sibling repos already migrated in this same effort (XBlock,
openedx-events, edx-enterprise) all carry an equivalent codecov.yml.
Move ccx_keys/ to src/ccx_keys/ per the confirmed decision on
openedx/public-engineering#506 (2026-07-15): src/ layout is in scope
for this modernization cycle, with precedent set in xblock-core and
xblock-extras. A flat layout's editable install can resolve
inconsistently for tools like mypy; src/ layout avoids that ambiguity.

Update [tool.setuptools.packages.find] (where = ["src"]),
[tool.coverage.run].source, tox.ini's test/pylint/pycodestyle target
paths, and MANIFEST.in's recursive-include path accordingly. Also
drop MANIFEST.in's two remaining requirements/base.in and
requirements/constraints.txt lines, both dead references to files
requirements/ deleted in the dependency-management commit.

Verified locally: `uv sync` installs the package in editable mode and
`python -c "import ccx_keys"` resolves to src/ccx_keys/__init__.py;
`uv run tox` (both unittest and quality envs) passes unchanged.
Verified every uses:@sha in release.yml against the GitHub API
(repos/<owner>/<repo>/commits/<sha>). While these particular SHAs
happened to be valid commits (unlike the same pins in sibling PRs
opaque-keys/openedx-core, where they were swapped/fabricated), pinning
these 4 actions isn't required by the migration ticket and isn't done
by openedx/XBlock's actual, already-releasing release.yml. Reverted
python-semantic-release, publish-action, upload-artifact, and
download-artifact to plain version tags matching XBlock exactly, to
avoid this whole class of error going forward. pypa/gh-action-pypi-publish
was already correctly pinned to the real commit SHA (not the tag
object SHA some sibling PRs used) -- left unchanged.
…ration

This repo never had a CHANGELOG.rst. Per the modernize-python-tooling
effort's decision, repos should not disable changelog generation when
using python-semantic-release -- there's no reason to throw away
release-to-release history.

- Add CHANGELOG.rst containing only the insertion marker; PSR's
  "update" mode will build all version sections above it going forward.
- Remove changelog: "false" from release.yml's PSR step.
- Configure [tool.semantic_release.changelog] to update CHANGELOG.rst
  in place using the insertion marker, RST output.
- Set tag_format = "{version}" to match this repo's actual bare
  X.Y.Z tag convention (confirmed via git tag --sort=-v:refname);
  without this PSR would not recognize any prior release tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same reasoning as taxonomy-connector#316/edx-enterprise-data#693/
opaque-keys#461 in this same lessons-learned pass: 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 only ever
affects this PR's own diff -- once merged, the new percentage becomes the
baseline for every future comparison. A permanent threshold had no real
job to do.

No uv pip usage and no Django-version matrix in this repo (pure-Python
key-parsing library, no framework dependency at all) -- nothing else to
check against the recurring bugs found in the enterprise-track batch.
Set changelog: "false" on the PSR release step and remove the
[tool.semantic_release.changelog] config / insertion marker.

Checked against openedx/XBlock's actual production release.yml (the one
repo in this effort that has cut real automated releases) -- every run
passes changelog: false and invokes `semantic-release -v version
--no-changelog`, and the repo has zero github-actions[bot] commits ever.
The auto-changelog config this migration previously added was only ever
verified via a local dry-run prototype, never against a real release.

feanil flagged the same issue on openedx/DoneXBlock#388: "I thought we
were not going to add changelogs since they can't be updated by
python-semantic-release the way we have it setup."
Reviewers on this effort (farhan, feanil) have repeatedly asked to delete
multi-line AI-written justification comments from committed files. Moved
the detail to this commit message instead:

This tooling migration correctly omits ccx_keys/tests/test_ccx_keys.py
from [tool.coverage.run] instead of counting the test file's own
statements toward coverage -- a one-time ~3-4% discontinuity in this PR's
base-vs-head comparison, not a real regression. target: auto means the
new percentage becomes the baseline once this merges; no threshold was
added.
CI switched to codecov/codecov-action; the Coveralls badge/link pointed
at a service this repo no longer reports to. Also fixed the build-status
badge referencing ci.yaml (the actual workflow is ci.yml) and pointing at
the wrong repo's Actions page.
Inline tox.ini's [testenv:quality] commands via uv run/uv sync instead
of shelling out to `uv run tox -e quality`, matching the no-tox-in-
Makefile convention already used elsewhere. tox.ini itself is
untouched; CI's own matrix testing still uses tox directly.
The 5% threshold described in this PR's own description (to absorb the
one-time coverage-measurement shift from omitting tests/* -- see that
section) was never actually added to the file. Without it, target: auto
requires zero drop from the auto-detected base, which is exactly what
codecov/project has been failing on.
@irfanuddinahmad
irfanuddinahmad force-pushed the irfanuddinahmad/modernize-python-tooling branch from d9dcfdf to 8cad4c7 Compare August 27, 2026 08:18
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
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