Skip to content

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

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

irfanuddinahmad wants to merge 26 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)
  • 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. (OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN itself is already a standard org-level secret used by many other repos.)

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 target: auto
on the project check (no fixed threshold — an earlier draft added one, since
removed as unnecessary) — sibling repos already migrated in this same effort 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, which hardcodes ADD_PATHS="requirements". Now that requirements/ is
deleted, that scheduled job will silently stop producing real dependency-upgrade
PRs — same gap across every repo in this migration batch; needs a fix in
openedx/.github itself, not here.

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.36%. Comparing base (e160f05) to head (6950ea3).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #190      +/-   ##
==========================================
- Coverage   97.78%   94.36%   -3.43%     
==========================================
  Files           4        3       -1     
  Lines         226       71     -155     
  Branches        8        6       -2     
==========================================
- Hits          221       67     -154     
+ 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
irfanuddinahmad and others added 5 commits August 27, 2026 16:56
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.
A prior commit (b2d0b56) deliberately un-pinned python-semantic-release,
actions/upload-artifact, and actions/download-artifact to plain version
tags, reasoning that SHA-pinning wasn't required by the migration ticket
and that openedx/XBlock's release.yml doesn't do it either.

openedx/sample-plugin is this org's designated gold-standard reference
for release.yml, and it does pin all of these (each SHA independently
re-verified against the action's own tag history via the GitHub API).
Restored the pins to match, and picked up the version bumps that came
with it: python-semantic-release v10.6.1 -> v10.6.2, and
pypa/gh-action-pypi-publish v1.14.1 -> v1.14.2.

Also brought release.yml in line with sample-plugin in three more ways:
- added `if-no-files-found: error` to the dist upload step, so a release
  that produced no artifacts fails loudly instead of silently.
- added the `github.ref_name == 'master'` guard to publish_to_pypi's
  `if`, matching the release job's own guard, so the job can't fire on
  a non-default-branch workflow run.
- dropped `fetch-depth: 0` from the release job's checkout; python-semantic-release
  auto-deepens a shallow clone itself before evaluating version history,
  so it's never needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ccx_keys/__init__.py sets __version__ from importlib.metadata; nothing
else in the package reads or parses __version__, so a shallow clone is
sufficient for CI. sample-plugin's own backend-ci.yml doesn't set
fetch-depth: 0 either. Matches that reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
irfanuddinahmad and others added 4 commits September 9, 2026 14:21
The comment pointed at "see PR description" for an explanation, but
the description's account is stale (references a 5% threshold that a
later commit removed). Coverage config itself stays out of scope for
this migration -- just dropping the stale comment, no change to
target/threshold behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml Outdated
run_tests:
name: Tests
runs-on: ${{ matrix.os }}
name: ${{ matrix.toxenv }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

should we follow this here?
openedx/staff-graded-xblock#394 (comment)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, thanks for the staff-graded-xblock#394 link — confirmed the same risk here. Reverted to a static job name in 6950ea3.

Comment thread codecov.yml Outdated
@@ -0,0 +1,10 @@
coverage:

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 remove this file.

Comment thread pyproject.toml
irfanuddinahmad and others added 2 commits September 16, 2026 11:52
This file's original justification (a threshold buffer for the
one-time coverage-measurement drop from the migration's new
[tool.coverage.run] omit patterns) was already reverted in a prior
commit. What's left (target: auto, patch: 90%, comment: false) is
generic config the migration doesn't need -- codecov's own zero-config
defaults are sufficient, matching every other non-justified repo in
this batch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
${{ matrix.toxenv }} produces bare check names (django42, quality, ...)
instead of the pre-migration "Tests (ubuntu-latest, 3.12, <toxenv>)".
If branch protection requires the old check names, they'd show as
permanently pending after this merges -- same required-status-check
breakage already confirmed on enterprise-access#1015 and flagged on
staff-graded-xblock#394.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

4 participants