Modernize Python repo: pyproject.toml + uv + semantic-release - #388
Conversation
|
Thanks for the pull request, @salman2013! 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. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #388 +/- ##
=======================================
Coverage 55.35% 55.35%
=======================================
Files 3 2 -1
Lines 56 56
Branches 0 2 +2
=======================================
Hits 31 31
Misses 25 25
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
👋 Reviewed this against the same checklist we've been applying across the modernization effort. Nothing blocking — CI is fully green — just two small things worth a look: 1. 2. Non-blocking: |
f53a032 to
48fda12
Compare
4053301 to
11cd266
Compare
irfanuddinahmad
left a comment
There was a problem hiding this comment.
First review on this PR (no prior review activity). Verified everything against the actual current file content and live CI (all green) rather than just the diff. A few of these are the same recurring pattern already confirmed causing real breakage on sibling PRs in this same migration effort (fallback_version/fetch-depth, default-groups).
Things that are already done correctly and worth not re-litigating: all 6 action SHA pins are genuine (verified via the commits API, including one that needed resolving an annotated-tag object to its real commit), pypa/gh-action-pypi-publish correctly left at the stable tag, OIDC-only publish scoping, no License :: classifier conflicting with the SPDX field, src/ layout correctly adopted, CHANGELOG.rst's insertion marker present with no history dropped, and the Django version-matrix uses the correct [tool.uv].conflicts pattern rather than the shared-group anti-pattern found on 3 sibling repos.
| [tool.semantic_release] | ||
| build_command = "pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=$NEW_VERSION python -m build" | ||
| allow_zero_version = true | ||
| major_on_zero = false |
There was a problem hiding this comment.
tag_format isn't set here, so it defaults to python-semantic-release's "v{version}". This repo's actual releases are bare X.Y.Z (3.0.0, 2.5.0, ... -- confirmed via the tags API, and 3.0.0 matches PyPI's actual latest published version). Left at the default, PSR won't recognize any prior release as such.
There's also a genuinely confusing existing tag worth flagging separately: v3.0.0 (with the prefix) exists too, but it points to a different, later commit (21c502fb, a routine "chore: Upgrade Python requirements" commit from 11 days after the real 3.0.0 release at c116322a) -- not something this PR should try to silently fix, but worth a maintainer's attention since it could confuse PSR's history scan regardless of tag_format.
| major_on_zero = false | |
| allow_zero_version = true | |
| major_on_zero = false | |
| tag_format = "{version}" |
| [tool.setuptools_scm] | ||
| version_scheme = "only-version" | ||
| local_scheme = "no-local-version" | ||
| fallback_version = "0.0.0.dev0" |
There was a problem hiding this comment.
"0.0.0.dev0" is the exact fallback value that crashed 17 tests in a sibling repo (openedx-events) via tuple(map(int, __version__.split("."))) choking on the non-numeric dev0 segment. I checked every __version__ consumer in this repo (src/done/__init__.py, src/done/done.py, docs/source/conf.py) -- none int-parse it, so it's not an active crash risk here today, but there's no reason to keep a value from the banned-pattern class when a plain int-parseable one works just as well.
| fallback_version = "0.0.0.dev0" | |
| fallback_version = "0.0.0" |
| toxenv: [django42, django52, quality] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 |
There was a problem hiding this comment.
Missing fetch-depth: 0. Without it, this is a shallow, tag-less clone, so setuptools-scm can't see any tags during test runs and silently falls back to fallback_version (see the pyproject.toml comment) on every ordinary CI run. release.yml's own checkout correctly has this set; this job's doesn't.
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 |
| from importlib.metadata import version | ||
|
|
||
| from .done import DoneXBlock | ||
|
|
There was a problem hiding this comment.
This should be wrapped in try/except PackageNotFoundError -- as written, any environment where done-xblock's dist-info isn't discoverable at import time (some editable-install/plugin-loading edge cases) raises unhandled and takes down the whole XBlock module, which is a harder failure than the hardcoded string this replaced.
| from importlib.metadata import PackageNotFoundError, version | |
| from .done import DoneXBlock | |
| try: | |
| __version__ = version("done-xblock") | |
| except PackageNotFoundError: | |
| __version__ = "unknown" |
There was a problem hiding this comment.
I removed this version as UV manages by own.
|
|
||
| install: install-test | ||
|
|
||
| quality: ## Run the quality checks |
There was a problem hiding this comment.
ruff is declared as a dependency in the quality group and fully configured ([tool.ruff]/[tool.ruff.lint]/[tool.ruff.format] in pyproject.toml), but this target never actually invokes it -- only pylint runs. Was ruff meant to run here too (matching the #511 XBlocks track's adoption of it elsewhere, e.g. xblocks-core), or was pylint intended to stay as the sole linter with ruff left over from an earlier draft? Either way it's currently dead config -- worth wiring in or dropping.
There was a problem hiding this comment.
We are not adding ruff in this scope because it needs to add more files formatting, so i removed that.
| changelog_file = "CHANGELOG.rst" | ||
| output_format = "rst" | ||
|
|
||
| [tool.uv] |
There was a problem hiding this comment.
A dev-named dependency-group exists elsewhere in this file, and every invocation in this repo (uv sync --group ci, uv sync --group dev, tox's dependency_groups =) already names an explicit group -- exactly the condition where default-groups = [] is needed. Without it, uv sync --group ci also implicitly syncs the full dev superset alongside whatever group was actually requested.
| [tool.uv] | |
| [tool.uv] | |
| package = true | |
| # Every `uv sync`/`uv run` invocation in this repo names an explicit --group. | |
| # Without this, uv's implicit default group (named "dev") would be synced *in | |
| # addition* to whatever --group is passed, defeating the point of having | |
| # separate groups. | |
| default-groups = [] | |
| conflicts = [ | |
| [{group = "test"}, {group = "django42"}], | |
| ] |
| name = "done-xblock" | ||
| description = "done XBlock" | ||
| readme = "README.rst" | ||
| license = "AGPL-3.0" |
There was a problem hiding this comment.
Minor: "AGPL-3.0" is a deprecated SPDX identifier (superseded by AGPL-3.0-only/AGPL-3.0-or-later). Not a build failure (no conflicting License :: classifier is present), just imprecise -- worth checking the LICENSE file's actual text to pick the right variant.
631547b to
fe96ae6
Compare
irfanuddinahmad
left a comment
There was a problem hiding this comment.
A couple of minor, non-blocking notes.
| quality: ## Run the quality checks | ||
| pylint --rcfile=pylintrc done | ||
| python setup.py -q sdist | ||
| pylint --rcfile=pylintrc src/done |
There was a problem hiding this comment.
These (and test/covreport below) call pylint/python/twine bare, no uv run — only bites if someone runs them directly instead of through tox. Same assumption existed pre-migration too, so not a new issue, just worth a follow-up sometime.
| python: | ||
| install: | ||
| - requirements: requirements/docs.txt | ||
| - method: pip |
There was a problem hiding this comment.
Heads up, RTD now has native uv support (method: uv, command: sync) if you ever want doc deps resolved from uv.lock too — not necessary for just Sphinx + the theme though, this is fine as is.
a507202 to
c5a89c5
Compare
|
|
||
| from .done import DoneXBlock | ||
|
|
||
| __version__ = '3.0.0' |
There was a problem hiding this comment.
This should be replaced with a get_version call and the variable should still be set for convenience/compatibility.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
You're right — python-semantic-release can't auto-update it in our current setup . Removed the file and the
[tool.semantic_release.changelog] config block from pyproject.toml as well, which auto-generated this file.
There was a problem hiding this comment.
Is this file new or is this config being moved from somewhere else, I don't see the file this is coming from if it's moving.
There was a problem hiding this comment.
Yes this is a new addition, just added in parity of other repo like xblock-core to show the code coverage in the checks list. should i remove this? as it is not a requirement of modernization.
There was a problem hiding this comment.
Yes, codecov has default configuration which is fine in most cases. We only need to override it sometimes.
There was a problem hiding this comment.
I have removed it.
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."
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."
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."
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."
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."
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."
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."
7be658b to
720583e
Compare
|
@feanil I believe its ready for another pass. |
farhan
left a comment
There was a problem hiding this comment.
few changes, mostly looks good
|
|
||
| [tool.semantic_release] | ||
| build_command = "pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=$NEW_VERSION python -m build" | ||
| allow_zero_version = true |
There was a problem hiding this comment.
allow_zero_version and major_on_zero form a zero-version guard that only applies to 0.x repos — this repo is at 3.0.0.
|
|
||
| try: | ||
| __version__ = version("done-xblock") | ||
| except PackageNotFoundError: |
There was a problem hiding this comment.
Add # pragma: no cover — the except branch is unreachable in tests (the package is always installed) and codecov will flag it as uncovered:
except PackageNotFoundError: # pragma: no cover|
|
||
| - name: Python Semantic Release | ||
| id: release | ||
| uses: python-semantic-release/python-semantic-release@350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 # v10.5.3 |
There was a problem hiding this comment.
we should update it to latest version
| @@ -1,21 +1,21 @@ | |||
| [tox] | |||
| envlist = py{312}-django{42,52}, quality | |||
There was a problem hiding this comment.
We should add docs environment
I tested it locally, make docs is failing
There was a problem hiding this comment.
this is pending, make sure we run docs in the ci as well.
There was a problem hiding this comment.
Ah, docs env is defined at the bottom of the file
Please add it in this envlist so a bare tox run covers it too and its easy to read the file
| @@ -1,4 +1,2 @@ | |||
| include requirements/base.in | |||
| include NOTICE | |||
| include LICENSE | |||
There was a problem hiding this comment.
We can remove this LICENSE declaration now
| @echo "Please use \`make <target>' where <target> is one of" | ||
| @awk -F ':.*?## ' '/^[a-zA-Z]/ && NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort | ||
|
|
||
| install-test: |
There was a problem hiding this comment.
We have decided to not to drop the make file targets, we can provide their alternatives.
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."
| strategy: | ||
| matrix: | ||
| python-version: ['3.12'] | ||
| toxenv: [django42, django52, quality] |
There was a problem hiding this comment.
We should docs as well in it.
|
Hey @salman2013 — nudge on farhan's review from 8/25 (still |
📦 Packaging check — asset diff vs
|
| github_token: ${{ secrets.GITHUB_TOKEN }} | ||
| git_committer_name: "github-actions[bot]" | ||
| git_committer_email: "github-actions[bot]@users.noreply.github.com" | ||
| changelog: "false" |
There was a problem hiding this comment.
Set vcs_release: "false" to match the sample-plugin standard
| changelog: "false" | |
| vcs_release: "false" |
| git_committer_email: "github-actions[bot]@users.noreply.github.com" | ||
| changelog: "false" | ||
|
|
||
| - name: Upload dist artifacts |
There was a problem hiding this comment.
Paired with vcs_release: "false" above: once PSR no longer publishes the release, nothing creates it. Add an immutable-safe step here, before the artifact upload, that creates the release as a draft, attaches the dists, then publishes — the only ordering immutable releases allow. This mirrors sample-plugin:
- name: Create GitHub Release with Assets
if: steps.release.outputs.released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NOTES: ${{ steps.release.outputs.release_notes }}
TAG: ${{ steps.release.outputs.tag }}
run: |
printf '%s' "$RELEASE_NOTES" > "$RUNNER_TEMP/release_notes.md"
gh release create "$TAG" \
--verify-tag --title "$TAG" \
--notes-file "$RUNNER_TEMP/release_notes.md" \
dist/*| @@ -1,21 +1,21 @@ | |||
| [tox] | |||
| envlist = py{312}-django{42,52}, quality | |||
There was a problem hiding this comment.
this is pending, make sure we run docs in the ci as well.
| @@ -1,21 +1,21 @@ | |||
| [tox] | |||
| envlist = py{312}-django{42,52}, quality | |||
There was a problem hiding this comment.
Ah, docs env is defined at the bottom of the file
Please add it in this envlist so a bare tox run covers it too and its easy to read the file
|
|
||
| - name: Upload dist artifacts | ||
| if: steps.release.outputs.released == 'true' | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 |
There was a problem hiding this comment.
Bump artifact actions to the latest: upload-artifact → v7.0.1 (043fb46d…), download-artifact → v8.0.1 (3e5f45b2…).
4f36000 to
e33608c
Compare
- Migrate from setup.cfg/setup.py + pip-tools to pyproject.toml (PEP 621/735) + uv
- Add python-semantic-release for automated versioning and changelog generation
- Add GitHub release workflow: draft release with dist artifacts, then publish to PyPI
- Restructure to src layout (done/ → src/done/)
- Update CI matrix to py312 + django{42,52}, add docs tox env
- Drop requirements/*.txt (replaced by uv lockfile)
- Fix docs/source/conf.py for src layout
- Remove tag_format from semantic_release config
- Exclude done.tests from installed wheel, add NOTICE to license-files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e33608c to
15282c2
Compare
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."
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."
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."
…ase (#462) * refactor: move package to src/ layout Move edx_rest_api_client/ to src/edx_rest_api_client/ per the org-wide decision on openedx/public-engineering#506 (2026-07-15): src/ layout is in scope for this modernization cycle so that editable installs resolve correctly under tools like mypy (see https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/). Update setup.py, .coveragerc, and tox.ini to reference the new path so the repo remains buildable/testable with the existing pip-based tooling until pyproject.toml formalizes the src/ layout in the next commit. * feat: consolidate package metadata into pyproject.toml Replace setup.py with PEP 621 static metadata in pyproject.toml: - name, description, classifiers, dependencies as a static list - SPDX license expression + license-files (PEP 639) - setuptools-scm for git-tag-derived versioning (dynamic version) - setuptools packages.find configured for the src/ layout - coverage configuration moved from .coveragerc into [tool.coverage.*] Remove the hardcoded __version__.py module; __version__ is now read via importlib.metadata at runtime (falls back silently if the package isn't installed), so it can no longer go stale relative to the git tag that setuptools-scm derives the build version from. * feat: switch dependency management from pip-compile to uv - Add PEP 735 [dependency-groups] (test-base, test, django42, quality, ci, dev) to pyproject.toml, with a Django 42/52 version matrix via [tool.uv].conflicts - Add [tool.edx_lint].uv_constraints and generate [tool.uv].constraint-dependencies via `edx_lint write_uv_constraints` - Generate and commit uv.lock - Delete requirements/ (pip-compile inputs/outputs); drop the now-stale references in MANIFEST.in - tox.ini: use tox-uv's uv-venv-lock-runner and dependency_groups instead of deps/-r requirements files - Makefile: requirements/test/upgrade targets now use uv sync/uv run tox/uv lock instead of pip-compile and pip-sync - CI: use astral-sh/setup-uv (SHA-pinned) instead of a separate actions/setup-python + pip install step; run tests via `uv run tox`; name matrix jobs by toxenv; add contents: read permissions and fetch-depth: 0 (so setuptools-scm can see tags); add a workflow_call trigger so release.yml can reuse this workflow * feat: add semantic-release and commitlint workflows - Add [tool.semantic_release] to pyproject.toml (build via `python -m build` with SETUPTOOLS_SCM_PRETEND_VERSION; major_on_zero = false, allow_zero_version = true) - Add release.yml: runs CI via workflow_call, then python-semantic-release cuts the release and publishes to PyPI via OIDC trusted publishing (id-token: write, no stored credentials). Actions are pinned to plain version tags (matching openedx/XBlock's production release.yml) except pypa/gh-action-pypi-publish, which is pinned to its verified commit SHA because @release/v1 is a floating branch, not a tag (root cause of a real production failure in xblocks-core's release run) - Remove publish_pypi.yml: it published on `push: tags` using a stored PYPI_UPLOAD_TOKEN and an unpinned @release/v1 ref; once semantic-release starts pushing version tags this would race with release.yml's OIDC-based publish for the same version - commitlint.yml already existed and needed no changes Pre-flight check: latest git tag v7.0.0 matches the actual latest version on PyPI, so the first semantic-release run will compute a correct next version rather than a stale/lower one. * feat: enable python-semantic-release changelog generation changelog: "false" was blindly copied from the sample-plugin reference template in release.yml with no ticket ever requiring it -- CHANGELOG.rst was never deleted and is still hand-maintained. Wire up PSR to update it automatically going forward instead of disabling it outright. - Remove changelog: "false" from release.yml's python-semantic-release step - Add [tool.semantic_release.changelog] (mode = "update", insertion_flag) and [tool.semantic_release.changelog.default_templates] (CHANGELOG.rst, rst) to pyproject.toml - Add the ".. changelog-insertion-marker" line to the top of CHANGELOG.rst so PSR's update mode inserts new version sections above it, leaving the existing hand-written history untouched - Set tag_format = "v{version}" explicitly to match this repo's actual tag convention (v7.0.0, v6.2.0, ...) rather than relying on PSR's untested default * fix: disable python-semantic-release changelog generation 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." * fix: drop tox from Makefile's quality target 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. * fix: create GitHub release with assets attached, not after publish This repo has immutable releases enabled, which freezes a release's assets the moment it's published. Previously this workflow didn't even attempt to attach dist files to the GitHub release (relying only on the actions/upload-artifact -> download-artifact path for PyPI publish), so every release shipped with an empty GitHub Release page. Matches the fix already proven 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. Not adding the separate gitpython/uv workaround from event-tracking#435/#436 here -- these PRs won't merge until upstream python-semantic-release fixes the GitPython 3.1.60 break (python-semantic-release/python-semantic-release#1476). * fix: bring release.yml into conformance with sample-plugin gold standard Several action pins had drifted from openedx/sample-plugin's verified release.yml (the org's Phase 3 conformance reference for openedx/public-engineering#506): - actions/checkout was pinned to v6.0.2 (de0fac2...); bump to the verified v7.0.1 SHA (3d3c42e...). - The release job's checkout also carried fetch-depth: 0, which python-semantic-release never needs (it auto-deepens a shallow clone itself before evaluating version history) and which sample-plugin's reference does not set. - python-semantic-release and the upload/download-artifact steps were pinned to bare floating tags (@v10.6.1, @v7, @v8) instead of pinned commit SHAs -- exactly the kind of unpinned action this org's modernization effort is meant to close off. Pinned to verified SHAs for the current versions (v10.6.2, v7.0.1, v8.0.1 respectively). - pypa/gh-action-pypi-publish was correctly SHA-pinned but one version behind (v1.14.1); bumped to the verified v1.14.2 SHA. - The upload-artifact step was missing if-no-files-found: error, so a build that silently produced no dist/ files would upload nothing and the workflow would report success anyway. - publish_to_pypi's `if:` was missing the github.ref_name == 'master' guard sample-plugin's reference uses alongside needs.release.outputs.released == 'true'. Every SHA above was independently re-verified against its action's own commit and tag history (resolving annotated tags one level to their commit) rather than trusted at face value, per this org's history of fabricated/swapped SHAs in this exact file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop unneeded fetch-depth: 0 from ci.yml checkout setuptools-scm (used for this package's dynamic version) has fallback_version = "0.0.0" configured in pyproject.toml, so a shallow clone without tag history never fails the build -- it just resolves to the fallback version. Nothing downstream parses or splits __version__ (checked src/edx_rest_api_client/client.py and its tests): it's only ever interpolated as a plain string into the User-Agent header. sample-plugin's own backend-ci.yml does not set fetch-depth: 0 either. Same reasoning already confirmed safe on opaque-keys#461. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address review feedback (author metadata, stray classifier) - Author metadata matches the org-wide convention used elsewhere in this migration (Open edX Project / oscm@openedx.org). - Drop the Python 3.11 classifier -- CI has only ever tested 3.12, both before and after this migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove unnecessary try/except around __version__ (not present pre-migration) * fix: remove fail-fast: false (not present pre-migration) --------- Co-authored-by: irfanuddinahmad <irfanuddinahmad@users.noreply.github.com> Co-authored-by: Irfan Ahmad <irfan.ahmad@A006-01919.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Usama Sadiq <usama7274@gmail.com>
…e) (#316) * feat: consolidate package metadata into pyproject.toml 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> * feat: switch dependency management from pip-compile to uv 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> * feat: add semantic-release for automated PyPI publishing 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> * fix: correct release tag format and restore pycodestyle ignore list 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> * fix: prevent uv sync from implicitly pulling in the dev group 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> * fix: publish to PyPI via OIDC instead of token 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> * fix: pin pypa/gh-action-pypi-publish to a commit SHA @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> * fix: set major_on_zero=false and allow_zero_version=true for consistency 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> * refactor: adopt src-layout, matching openedx/sample-plugin 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. * fix: pattern-gap audit (uv setup, package=true, coverage, changelog) 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. * docs: drop stale Version/Changelog checklist items from PR template 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. * fix: pattern-gap audit round 2 (django42/52 dependency groups, uv run 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. * fix: correct Makefile recipe indentation (tabs, not spaces) 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. * fix: add codecov project threshold for coverage-scope change 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). * fix: restore CHANGELOG.rst and re-enable python-semantic-release changelog 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. * docs: remove stale manual tag/PyPI-verification steps from PR template 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. * fix: use stable pypa publish tag and drop tox from Makefile - 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. * fix: use int-parseable fallback_version instead of 0.0.0.dev0 "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. * fix: pure-uv mysql8-migrations job, remove permanent codecov threshold - 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. * fix: disable python-semantic-release changelog generation 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." * fix: create GitHub release with assets attached, not after publish 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. * fix: trim verbose codecov.yml comment to match sibling repos Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: pin release.yml actions to sample-plugin's gold-standard SHAs openedx/sample-plugin's release.yml is this org's designated reference for action pins in this migration effort. This repo's release.yml had drifted from it on every pin: - actions/checkout: v7.0.0 (9c091bb2) -> v7.0.1 (3d3c42e5), matching sample-plugin - python-semantic-release: v10.5.3 (350c48fc) -> v10.6.2 (9a026e93) - actions/upload-artifact: v4.6.2 (ea165f8d) -> v7.0.1 (043fb46d) - actions/download-artifact: v4.3.0 (d3f86a10) -> v8.0.1 (3e5f45b2) - pypa/gh-action-pypi-publish: unpinned @release/v1 -> SHA-pinned v1.14.2 (dc37677b) The pypa/gh-action-pypi-publish change reverses earlier guidance on this PR: salman2013 previously asked (review comment #3710602930) for this action to be reverted off a SHA pin back to @release/v1 due to a prior publish issue with the hashed version. That guidance has since been superseded -- the same reviewer, on a sibling repo's PR in this same migration effort, directed matching sample-plugin exactly, which now means SHA-pinning this action like every other one in the file. Every SHA above was independently verified against the GitHub API (commit lookup + tag ref, resolving through the annotated tag object where applicable) before use, per this org's history of fabricated/ swapped SHAs in this exact file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: restore [4.0.0] entry silently dropped from CHANGELOG.rst by merges salman2013 asked (review comment #3710606973) why old release log entries were being removed; this was previously addressed by restoring CHANGELOG.rst's full history in commit e58b416. But three later merge-from-master commits on this branch (6012451, then 1804d5d) brought in master's tip without carrying over master's own [4.0.0] / "chore: upgrade requirements" changelog entry -- despite there being no textual conflict on this file (the entry existed cleanly on master's side with no competing change on the branch side), the merge results ended up without it, so it was silently lost again. Confirmed via `git diff origin/master -- CHANGELOG.rst` before this fix: the only difference between this branch and current master was the missing [4.0.0] entry. Restored it so the file now matches master exactly, keeping this PR's changelog history complete and consistent with what will already be on master when this merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use v-prefixed tag_format for release tag consistency * fix: update author to Open edX Project convention * fix: restore __version__ dropped during pyproject.toml consolidation 4e45689 removed the hardcoded __version__ = "3.0.0" (correctly, since version is now dynamic via setuptools-scm) but never replaced it with the importlib.metadata equivalent, unlike sibling repos in this migration batch. taxonomy.__version__ has been silently missing since. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove dangling codecov.yml comment, not present pre-migration Points at "see PR description" for an explanation -- fragile since the PR description is external and mutable. Coverage config itself stays out of scope for this migration; just dropping the comment, no change to target/threshold behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use secrets.GITHUB_TOKEN instead of custom org secret for release job Matches openedx/sample-plugin's current release.yml; the custom OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN secret isn't needed. * fix: restore tox delegation for docs/quality/pii_check targets Makefile targets were fully inlining commands that tox.ini already defines as standalone envs. pii_check delegates to tox -e pii-annotations, matching the actual tox env name (not pii_check). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop uv run from Makefile per feanil's review feedback Makefile now assumes an already-synced/activated local env; uv run stays in CI workflow steps only. See openedx/ccx-keys#190 (review comment r4097501942). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: restore full tox matrix on test-all, correct tag_format comment test-all had drifted to depend only on `test` (a single pytest run), making it identical to `validate` and no longer actually covering py312-django42/py312-django52 like its own help text claims. Restore the bare `tox` call so it runs the full envlist. Also corrected the tag_format comment, which claimed no v-prefixed tag existed -- v4.0.0 already does, same commit as the old bare 4.0.0 tag. Per feanil's review on #316: discussion_r4097760680, r4097760715. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove django42 from CI matrix, restore pre-migration scope CI stopped exercising django42 in 2026-02 (3b842b7), keeping it only in tox.ini's envlist for local/manual use; the migration's ci.yml regenerated the matrix from tox.ini and silently reintroduced it as a CI job. Per this effort's policy, a migration PR must not expand CI scope beyond what pre-existed. Coverage step already correctly targets django52, so no other change is needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: include doc group in dev group; bump python-semantic-release to v10.7.0 - dev omitted {include-group = "doc"}, so `uv sync --group dev` never provisioned doc8/Sphinx and `make docs` failed on a fresh clone. Matches the pattern already used in openedx/edx-enterprise's pyproject.toml. - Bump python-semantic-release action pin from v10.6.2 to v10.7.0, matching openedx/sample-plugin's current live pin. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Irfan Ahmad <irfan.ahmad@A006-01919.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…se (#371) * refactor: move package to src/ layout Adopt the src/ layout for openedx_authz per the 2026-07-15 decision on openedx/public-engineering#506: move openedx_authz/ -> src/openedx_authz/ so editable installs resolve correctly for tools like mypy (flat-layout editable installs are not reliably resolvable). Update every hardcoded flat-path reference to the new location: MANIFEST.in, .coveragerc source, docs/conf.py (version-file path and the sphinx-apidoc source paths), tox.ini (pytest --cov/--ignore and the quality testenv's pylint/ruff/pydocstyle targets), and the Makefile's translation targets (extract/compile/detect/pull/dummy_translations, ruff format/check). pyproject.toml's [tool.setuptools.packages.find] where=["src"] wiring lands in the next commit alongside the rest of the metadata migration. * feat: consolidate package metadata into pyproject.toml Replace setup.py/setup.cfg with PEP 621 static metadata in pyproject.toml. setuptools-scm now derives the version from git tags (fallback_version = "0.0.0"); the hardcoded __version__ in openedx_authz/__init__.py is removed since nothing else in the repo reads it beyond docs/conf.py, which now gets the version via importlib.metadata instead of regex-parsing __init__.py. Coverage configuration is consolidated from .coveragerc into [tool.coverage.*]. The dead [isort]/[wheel] settings from setup.cfg are ported into [tool.isort] verbatim (import sorting is actually enforced via ruff's "I" rule per the CHANGELOG's prior pycodestyle/isort -> ruff migration, but the old isort section is preserved rather than silently dropped). Also fixes a pre-existing MANIFEST.in/README.rst mismatch: the repo's license file is named `LICENSE` (no extension), but both referenced the nonexistent `LICENSE.txt`. Part of openedx/public-engineering#506, tracked in #516. * feat: switch dependency management from pip-compile to uv Replace requirements/*.in/*.txt with PEP 735 dependency groups in pyproject.toml (test-base, test, quality, doc, ci, dev), resolved into a committed uv.lock. Populate [tool.uv].constraint-dependencies via `edx_lint write_uv_constraints` and add the (currently empty) [tool.edx_lint].uv_constraints override point. Update tox.ini to use tox-uv (uv-venv-lock-runner) and dependency_groups instead of -r requirements/*.txt. Update the Makefile's upgrade/quality/ format/requirements/test/diff_cover/test-all targets to go through `uv run`/`uv sync`/`uv lock` instead of pip-compile/pip-sync. Update .readthedocs.yaml to install via `uv sync --group doc`. Drop the now- stale requirements/ references from MANIFEST.in and .gitignore. Update the CI workflow to install uv via astral-sh/setup-uv (SHA-pinned, with caching), sync via `uv sync --group ci`, and run each tox env via `uv run tox -e <env>`; add `fetch-depth: 0` so setuptools-scm can see tags during test runs, and `permissions: contents: read`. While verifying the full tox matrix locally post-migration, found and fixed a real bug surfaced by the src/ layout move: three tests in test_enforcer.py opened `openedx_authz/engine/config/{model.conf, authz.policy}` as paths relative to the process cwd rather than via the package's own ROOT_DIRECTORY (as settings/common.py and settings/test.py already correctly do), so they broke once the package moved under src/. Part of openedx/public-engineering#506, tracked in #516. * feat: add semantic-release and OIDC PyPI publish workflow Add python-semantic-release configuration to pyproject.toml (major_on_zero = false, allow_zero_version = true; build_command uses python -m build since python-semantic-release's action environment doesn't have uv available) and a release.yml workflow that runs the CI suite, cuts a release with python-semantic-release on pushes to main, and publishes to PyPI via OIDC trusted publishing (no stored API token). ci.yml now triggers on workflow_call instead of push, since release.yml owns that path (avoids running the suite twice on main). Replace the old tag-triggered, token-based pypi-publish.yml (built `python setup.py sdist bdist_wheel` and used an unpinned `pypa/gh-action-pypi-publish@release/v1` ref with a stored PYPI_UPLOAD_TOKEN) -- it would otherwise race release.yml's OIDC publish on the first semantic-release tag. pypa/gh-action-pypi-publish is pinned to a commit SHA (release/v1 is a floating branch, not a version tag: confirmed root cause of a real production failure on a sibling repo in this effort); the python-semantic-release and actions/{upload,download}-artifact actions use plain version tags matching openedx/XBlock's actual, currently-releasing release.yml, per the org-wide lesson that hand-verified SHA pins for those four actions were wrong in 3 of 5 audited sibling PRs. Pre-flight check: latest git tag v1.21.1 (via --sort=-v:refname) matches the version currently live on PyPI (1.21.1). Part of openedx/public-engineering#506, tracked in #516. * feat: enable semantic-release changelog generation Wire up python-semantic-release to update the existing CHANGELOG.rst in place instead of skipping changelog generation: - Add the `.. changelog-insertion-marker` line to CHANGELOG.rst so PSR knows where to insert new version sections above the existing hand-written history. - Remove `changelog: "false"` from release.yml's PSR step. - Add `[tool.semantic_release.changelog]` config (update mode, RST output) to pyproject.toml. - Add `tag_format = "v{version}"` to match this repo's actual tag convention (e.g. v1.21.1), which was previously unset and would have caused PSR to silently ignore all existing release history. * fix: remove obsolete version-bump/changelog checklist items from PR template Both are now handled automatically by python-semantic-release on merge to main, so asking contributors to manually bump the version or add a changelog entry is stale advice left over from before this migration. * fix: restore __version__ via importlib.metadata in __init__.py An earlier pass of the pyproject.toml/setuptools-scm migration removed the hardcoded __version__ string (correctly, since it would go stale under scm-derived versioning) but never added the importlib.metadata replacement used by sibling repos in this effort. Grepped the repo for other __version__ consumers first: none found (docs/conf.py already reads the installed distribution version directly via importlib.metadata, independent of this attribute), so this is a straightforward, safe addition. * fix: disable python-semantic-release changelog generation 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." * fix: create GitHub release with assets attached, not after publish 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. * fix: pin release.yml actions to sample-plugin gold-standard SHAs The release workflow had several conformance gaps against openedx/sample-plugin's release.yml, this org's Phase 3 reference: - python-semantic-release, actions/upload-artifact, and actions/download-artifact were referenced by floating version tags (v10.6.1, v7, v8) instead of pinned commit SHAs, defeating the supply-chain pinning the rest of the migration established. PSR was also a version behind (v10.6.1 vs the reference's v10.6.2). - pypa/gh-action-pypi-publish was pinned but to an outdated v1.14.1 SHA instead of the reference's v1.14.2. - The dist upload step lacked if-no-files-found: error, so a build that silently produced no artifacts would upload nothing instead of failing loudly. - publish_to_pypi's if-condition only checked needs.release.outputs.released, omitting the github.ref_name == 'main' guard the reference uses to keep the PyPI-publish jobs scoped to the default branch. - The release job's checkout carried fetch-depth: 0, which is unnecessary here: python-semantic-release auto-deepens a shallow clone itself before evaluating version history, and the reference workflow (which uses the identical checkout-then-git-reset-hard pattern) doesn't set it either. All new SHAs were independently verified against each action's own commit/tag history (resolving annotated tags to their commit where needed) before being applied. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop unneeded fetch-depth: 0 from ci.yml checkout The only place __version__ is set is src/openedx_authz/__init__.py's plain `__version__ = version("openedx-authz")` via importlib.metadata - nothing in the repo parses or splits it, so nothing here needs deep git history. Sample-plugin's own backend-ci.yml (the Phase 3 reference) doesn't set fetch-depth: 0 either. Same reasoning already applied on opaque-keys#461 in this same modernization effort. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add missing contents:read permission to publish_to_pypi job Sample-plugin's gold-standard release.yml grants publish_to_pypi both contents: read and id-token: write. This job's permissions block only had id-token: write, leaving contents scoped to 'none' (an explicit permissions: block resets unlisted scopes to none rather than falling back to the repo default) instead of matching the reference workflow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: align importlib.metadata.version aliasing with sample-plugin __init__.py and docs/conf.py aliased the same import to two different names (plain `version` vs `get_distribution_version`/`get_installed_version`, both leftover from the pkg_resources.get_distribution() era). Renamed both to `get_version`, matching sample-plugin's convention exactly (it uses `version as get_version` in both files). No behavioral change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove unnecessary try/except around __version__ (not present pre-migration) * fix: remove fail-fast: false (not present pre-migration) * fix: use static 'tests' job name in ci.yml to match pre-migration checks Pre-migration main already uses name: tests (auto-suffixed by GitHub with the matrix values, e.g. "tests (ubuntu-latest, 3.12, quality)"). This branch had overridden it to name: ${{ matrix.toxenv }}, which produces bare check names (quality, docs, pii_check, django52) instead, leaving any required status checks configured against the original naming permanently unsatisfied. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove unnecessary try/except around VERSION in docs/conf.py Not present pre-migration (the old conf.py read __version__ from openedx_authz/__init__.py via regex, no try/except). Matches src/openedx_authz/__init__.py, which already calls get_version("openedx-authz") directly without swallowing PackageNotFoundError. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use secrets.GITHUB_TOKEN instead of custom org secret for release job Matches openedx/sample-plugin's current release.yml; the custom OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN secret isn't needed. * fix: exclude tests packages from wheel Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: prefix i18n and atlas Makefile targets with uv run Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop uv run from Makefile per feanil's review feedback Makefile now assumes an already-synced/activated local env; uv run stays in CI workflow steps only. See openedx/ccx-keys#190 (review comment r4097501942). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add sphinx-jsonschema to doc dependency group The upstream merge (docs: add authorization schema reference, #431) added sphinx-jsonschema to the old requirements/doc.in, which was deleted as part of resolving this branch's merge conflict without porting the new dependency into pyproject.toml's doc group -- causing the docs build to fail with "Could not import extension sphinx-jsonschema". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Irfan Ahmad <irfan.ahmad@A006-01919.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…e) (#242) * feat: consolidate package metadata into pyproject.toml 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> * feat: switch dependency management from pip-compile to uv 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-runner/uv-venv-lock-runner, update Makefile targets, and fix .readthedocs.yaml to install docs deps via uv. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat: add semantic-release for automated PyPI publishing Replace the manual GitHub-release-triggered publish workflow with python-semantic-release: pushes to main with conventional commits now automatically bump the version, tag it, and publish to PyPI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: correct release tag format, exclude tests from wheel, drop stale MANIFEST.in entries 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. packages.find.exclude only stops setuptools from registering the tests subpackage; with include-package-data=true it still swept tests/*.py into the wheel as package_data. Verified: built wheels before/after this fix -- tests/__init__.py and tests/test_views.py were present in the wheel prior to this commit and are absent after, while test_utils/ (the intentionally shipped factories module) is unaffected. Also removes MANIFEST.in references to requirements/base.in and requirements/constraints.txt, which no longer exist after the uv migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: prevent uv sync from implicitly pulling in the dev group 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. Also adds venv/ and .venv/ to .gitignore (previously absent). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: publish to PyPI via OIDC instead of token 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> * fix: pin pypa/gh-action-pypi-publish to a commit SHA @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> * fix: set major_on_zero=false and allow_zero_version=true for consistency 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> * refactor: adopt src-layout, matching openedx/sample-plugin Moves openedx_ledger/ to src/openedx_ledger/, 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 the quality env's pylint/pycodestyle/isort targets - Makefile: prefix src/openedx_ledger onto the standalone isort/style/lint targets and the 5 localization targets that cd into the package; extract_translations/compile_translations' relative ../manage.py climbs 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/openedx_ledger - MANIFEST.in: recursive-include path updated - Dockerfile needs no change: it COPYs the whole repo rather than naming the package directory directly Verified: uv build --wheel + twine check pass (direct proof the where=["src"] packaging change works). Could not run the full pytest/quality/docs tox matrix locally -- this machine has no libmysqlclient/pkg-config to build the mysqlclient C extension (a [project.dependencies] entry, so it's required for every uv sync regardless of group), same environment limitation hit previously on enterprise-access. Relying on CI for full-matrix confirmation. * fix: use uv-venv-lock-runner for the main and pii_check tox envs CI failed after the src-layout move: pytest raised ModuleNotFoundError for openedx_ledger.tests when collecting src/openedx_ledger/tests/test_views.py. Root cause: these two envs were the only ones in this repo still using tox-uv's non-lock uv-venv-runner, which builds and installs a real (non-editable) sdist for testing. That build genuinely excludes openedx_ledger.tests per packages.find(exclude=["*tests"]) -- correct for the published PyPI wheel, wrong for a test run. In flat layout this was masked because the sdist-installed copy in site-packages was shadowed on sys.path by the physically-identical openedx_ledger/ directory at repo root; moving the package under src/ removes that accidental shadow (the exact masking effect the src-layout/flat-layout packaging guide warns about), so the real gap surfaced. docs/quality already used uv-venv-lock-runner (uv sync, editable install) and were unaffected. Verified the fix mechanism in an isolated repro (same packages.find exclude pattern, in-package tests subpackage, tox + tox-uv): switching uv-venv-runner -> uv-venv-lock-runner changes the install from a built sdist to `uv sync`'s editable install, and the ModuleNotFoundError goes away. Could not run the real repo's tox matrix locally (mysqlclient build environment limitation) -- pushing to confirm via CI. * fix: address review feedback (uv setup, coverage, readme, changelog) Addresses farhan's review on #242: - ci.yml: add enable-cache/python-version to astral-sh/setup-uv and drop the now-redundant actions/setup-python step; SHA-pin codecov-action (was floating on @v7, inconsistent with every other repo in this batch) - pyproject.toml: make readme static (dynamic = ["version"] only -- dynamic readme is non-standard for this migration pattern), add [tool.uv] package = true, migrate .coveragerc into [tool.coverage.*] (also fixing its stale source=edx_ledger -> source_pkgs=["openedx_ledger"]) and delete the old file - Drop CHANGELOG.rst from the dynamic readme file list (before removing the dynamic readme entirely) and delete it -- python-semantic-release + GitHub Releases is the changelog of record now. Also removes the now-stale MANIFEST.in include and the docs/changelog.rst page (and toctree entry) that only existed to embed it into Sphinx docs. - Makefile: requirements target now also installs tox as a uv tool (uv tool install tox --with tox-uv), so tox is available on a fresh checkout that only has uv installed Verified: pyproject.toml parses, uv build --wheel + twine check pass (direct proof the readme/coverage config changes work). Could not run the full tox matrix locally -- same mysqlclient build environment limitation as before (no libmysqlclient/pkg-config on this machine). * fix: remove unpinned tox install from make requirements `uv tool install tox --with tox-uv` was added to the `requirements` target in response to review feedback, but it installs an unpinned, un-lockfiled tox outside uv.lock. Every other tox invocation in this repo (test-all/quality/pii_check Makefile targets, ci.yml) uses `uv run tox` via the ci/dev dependency-groups, which already declare tox/tox-uv. Remove the added line to keep this consistent. * fix: give django42/django52 tox envs genuinely independent locked resolutions [project].dependencies had an unconstrained "Django" entry, so uv.lock resolved a single Django version (5.2.x) for the whole project. The django42 tox env then force-overrode just the Django package afterward via tox's deps=, while every other locked/transitive dependency stayed resolved against the Django-5.2 graph -- not a real, independent resolution for the 4.2 case. Add a django42 dependency-group pinning Django>=4.2,<4.3, pin the default test group to Django>=5.2,<6.0, and declare the two as conflicting via [tool.uv].conflicts so uv locks a genuine fork for each. Update tox.ini's testenv/pii_check envs to select the matching group per Django factor instead of overriding just the Django package. `uv lock` confirms this was a real bug: django-filter also forks to 25.1 (django42) vs 25.2 (django52) -- the single prior resolution had been silently testing 4.2 against a django-filter version potentially never resolved against Django 4.2's constraints. Verified locally (mysqlclient excluded due to a sandbox limitation building it; unrelated to this change): `uv sync --group django42` and `--group test` each install the correct, independently-resolved Django version, `manage.py check` and the full pytest suite (34 passed) pass under both, and quality/pii_check pass under django42. * fix: regenerate uv.lock to fix ordering left by the main merge The merge of origin/main into this branch auto-merged uv.lock as plain text (it wasn't flagged as conflicted), which left the auto-derived [tool.uv]-conflicts expansion for the django42 group with its "doc" and "quality" entries swapped relative to what a fresh `uv lock` produces. uv 0.11.33 (what CI's setup-uv currently installs) treats this as a stale lockfile and fails `--locked`/`--check`; the slightly older uv 0.11.30 tolerated it, which is why this wasn't caught before pushing. Verified: `uv lock --check` and `uv sync --locked --group <g>` pass clean uv 0.11.33 for django42/test/quality/dev/ci; pytest (34 passed), manage.py check, pylint/pycodestyle/isort, and pii_check all pass under both django42 and django52. * fix: upgrade locked uv package to match CI's system uv version The previous push's lockfile fix regenerated uv.lock with system uv 0.11.33, but the "uv" PyPI package itself (a transitive dependency of tox-uv-bare, used internally by tox-uv's uv-venv-lock-runner inside each tox env) was still locked at the older 0.11.28. These two uv releases disagree on the canonical serialized order of the auto-derived [tool.uv].conflicts entries, so whichever version last wrote the lock, the other considered it stale under --locked -- CI's "Install Dependencies" step (system uv, "latest" via setup-uv) and its "Run Tests" step (tox-uv's pinned "uv" package) were fighting each other. `uv lock --upgrade-package uv` bumps the locked uv package to 0.11.33, matching today's system uv, so both steps agree. Verified: `uv sync --locked --group <g>` passes clean under uv 0.11.33 (matching CI) for django42/test/quality/doc/dev/ci; pytest (34 passed) and quality checks pass under both django42 and django52. * fix: pii_check env has no django42/django52 factor in its name Unlike [testenv] (whose generated env names py312-django42/py312-django52 genuinely contain those factors), [testenv:pii_check] is a fixed-name env ("pii_check"), so a factor-conditional dependency_groups (as used for [testenv]) never matches and left pii_check with zero groups installed -- confirmed by CI: "Exception running subprocess [Errno 2] No such file or directory: 'code_annotations'". pii_check was never actually parametrized by Django version (its old deps= factor overrides were dead code for the same reason), so restore its original always-on `test` group, matching quality/docs' pattern of an unconditional dependency_groups for fixed-name envs. Verified via `tox config -e <env>` that django42/django52/pii_check/ quality/docs all resolve to the intended group, and that code_annotations (pii_check) now installs and runs successfully. * fix: restore CHANGELOG.rst and re-enable semantic-release changelog generation A previous migration pass deleted CHANGELOG.rst and set changelog: "false" in release.yml's python-semantic-release step. There is no ticket requirement to disable changelog generation, and deleting the file discarded real historical release notes. Restore CHANGELOG.rst from the commit before its deletion, add the insertion marker PSR's "update" mode looks for, remove the changelog: "false" override, and configure [tool.semantic_release.changelog] to update the existing RST file in place. * chore: remove stale version-bump/changelog checklist items from PR template python-semantic-release now handles both version bumping and changelog generation automatically on release, so these manual checklist items are obsolete. * docs: remove stale manual tag/PyPI-verification steps from PR template python-semantic-release's release.yml now creates the tag and publishes to PyPI automatically on merge -- these were no longer real manual steps. * fix: use int-parseable fallback_version instead of 0.0.0.dev0 "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. * fix: disable python-semantic-release changelog generation 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." * docs: update README dev workflow to use uv instead of raw pip install -e pip install -e + pip freeze + source venv/bin/activate predates the uv migration -- make requirements (uv sync) now installs this repo into its own .venv already, and there's no venv/ directory to activate. * fix: use stable pypa publish tag, drop tox from Makefile targets - pypa/gh-action-pypi-publish: revert the hash-pinned SHA back to the stable @release/v1 tag, matching the rest of this effort's PRs. A hash-pinned version broke PyPI publishing previously for this org. - Makefile docs/quality/pii_check targets: inline the actual tox.ini commands via uv run/uv sync instead of shelling out to `uv run tox -e <env>`, matching the no-tox-in-Makefile convention already used elsewhere (edx-enterprise, enterprise-access). tox.ini itself is untouched; test-all still uses bare `uv run tox` for the actual Python/Django matrix run, which is what tox is for. * fix: upgrade edx-lint to 6.2.0 to match merged pylintrc The just-merged pylintrc (from main) was regenerated by edx-lint 6.2.0 and adds the new pii-invalid-no-pii-annotation check's [PII] pii-terms option. Our lock was still on edx-lint 6.1.0, whose pylint plugin doesn't recognize that option, breaking `make quality` with E0015: Unrecognized option found: pii-terms. * fix: delete CHANGELOG.rst Automation is disabled (changelog: false) and the file was already told to farhan as removed; a later commit re-enabled/disabled automation without re-deleting it. Deleting for real this time, matching the batch convention. * fix: create GitHub release with assets attached, not after publish 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. * fix: pin release.yml actions to gold-standard SHAs from openedx/sample-plugin actions/checkout, python-semantic-release, actions/upload-artifact and actions/download-artifact were pinned to outdated commit SHAs, and pypa/gh-action-pypi-publish was pinned to the floating `release/v1` tag rather than a commit SHA at all (a supply-chain risk, since that tag can be moved). Bring all five in line with the versions already verified against openedx/sample-plugin's release.yml (v7.0.1, v10.6.2, v7.0.1, v8.0.1, and v1.14.2 respectively), each cross-checked via `gh api .../commits/<sha>` and the corresponding tag ref. This repo's custom draft-release step (to work around immutable-releases freezing assets) and its branch-conditional guard were already correct and are left as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: restore CHANGELOG.rst Reverses 1a91f62 per explicit user decision -- keeping the historical changelog intact as a plain static file (no insertion marker, no semantic-release wiring; automation stays disabled via changelog: "false" in release.yml, unchanged). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use v-prefixed tag_format for release tag consistency * fix: update author to Open edX Project convention * fix: restore __version__ dropped during pyproject.toml consolidation 9a6b160 removed the hardcoded __version__ = "1.8.0" (correctly, since version is now dynamic via setuptools-scm) but never replaced it with the importlib.metadata equivalent, unlike every sibling repo in this migration batch. openedx_ledger.__version__ has been silently missing since. Restored using the same pattern used elsewhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address farhan's review comments on PR #242 - Consolidate tox.ini's docs env to call `make docs` instead of duplicating the doc8/sphinx-build commands in both places. - Verifying that consolidation surfaced make docs itself was broken: a stray trailing-whitespace doc8 failure and two docstrings with malformed RST (missing blank lines, informal "params:" list not valid RST) that only ever got exercised once `docs` became a real tox env. Fixed all three; make docs and tox -e docs both pass now. - Drop the browser-open step from the Makefile's docs target so it doesn't try to launch a browser when invoked from CI via tox. - Add deprecation notice to CHANGELOG.rst now that release.yml publishes real GitHub Releases. - Switch release.yml's python-semantic-release step to secrets.GITHUB_TOKEN, matching openedx/sample-plugin's current release.yml, removing the dependency on an org-level secret that isn't in the pre-merge checklist. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop uv run from Makefile per feanil's review feedback Makefile now assumes an already-synced/activated local env; uv run stays in CI workflow steps only. See openedx/ccx-keys#190 (review comment r4097501942). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address feanil's review on Makefile/README/Dockerfile/pyproject.toml - Makefile: remove `uv sync --group doc`/`uv sync --group quality` from the docs/quality targets. These re-provisioned .venv down to a smaller group mid-run, pruning tools (pylint, tox) that later targets in the same session (make test-all) needed. The Makefile should use whatever env the caller already has active, not manage it. - README.rst: add the missing `source .venv/bin/activate` step after `make requirements` in the documented dev workflow -- `uv sync` creates .venv but doesn't activate it, so `make validate` right after failed at `make clean`'s `coverage erase` with "not found". - Dockerfile: switch from `pip install -r requirements/dev.txt` (a directory this migration deletes) to `uv sync --locked --no-install-project --group dev` against the committed pyproject.toml/uv.lock, with UV_PROJECT_ENVIRONMENT pointing at the existing custom venv path. --no-install-project matches the original behavior, which never pip-installed the local package either (tests run from the source tree). Verified the actual uv sync mechanism directly (UV_PROJECT_ENVIRONMENT + --no-install-project correctly populates an arbitrary pre-existing venv with only third-party deps). Full container build couldn't be verified end-to-end: this repo's Dockerfile has a separate, pre-existing bug unrelated to this PR -- python3.12/python3.12-venv aren't available in stock Ubuntu focal apt repos on any architecture without a deadsnakes-PPA-style addition, confirmed blocking the apt-get layer before this change's layer even runs. Left unfixed as out of scope for this migration. - pyproject.toml: drop the `[tool.coverage.html]`/`[tool.coverage.xml]` path overrides (build/coverage/html, build/coverage/coverage.xml) so coverage output goes back to the default htmlcov/coverage.xml paths the Makefile's coverage/diff_cover targets actually read from; verified with a local coverage run. Also corrected the tag_format comment, which claimed no v-prefixed tag existed -- v2.0.0 already does, same commit as the old bare 2.0.0 tag. Per feanil's review on this PR: discussion_r4105646132, r4105646142, r4105646152, r4105646158, r4105646167, r4105646180, r4105646189. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: delegate tox quality env to make quality, re-lock tox.ini's [testenv:quality] duplicated the exact same 5 commands as the Makefile's quality target -- a second copy to keep in sync by hand. [testenv:docs] already delegates as `commands = make docs`; match that pattern for quality too. Also regenerated uv.lock: `uv sync --locked` was failing with "needs to be updated" again, caused by an upstream index change (build==1.5.1 was yanked from PyPI after the lockfile was last generated), not anything in this PR's own diff. Per feanil's review on #242: discussion_r4122549288, r4122577078. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: tighten tag_format comment wording Per feanil's exact suggested phrasing on #242 (discussion_r4122562412). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use glob-CONTAINS exclude pattern for tests packages packages.find.exclude was "*tests" (suffix-only), which misses nested namespace test packages like foo.bar.tests.fixtures since setuptools matches exclude patterns against the full dotted package name. Changed to "*tests*" per org-wide packaging convention for this migration batch. Verified: wheel builds clean, still 38 files (same as before), all 34 tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: include doc group in dev, bump python-semantic-release pin to v10.7.0 - Fold {include-group = "doc"} into the dev dependency-group so `make requirements` (uv sync --group dev) provisions doc8/Sphinx; without it, `make docs` fails on a fresh clone with "doc8 not found". Matches the pattern already used in edx-enterprise's pyproject.toml. - Bump python-semantic-release/python-semantic-release pin from 9a026e9303981c866c3425723009becb2437c757 (v10.6.2) to b700dbeb1f431a0fcc30a79f3f0869407fe07278 (v10.7.0), matching sample-plugin's current live pin. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: explicit uv.lock conflicts ordering, delegate pii_check to make, fix Dockerfile for real - pyproject.toml: enumerate every group that includes `test` (test, quality, doc, dev) against django42 explicitly in [tool.uv].conflicts, instead of letting uv derive the pairing implicitly. The derived order varied between uv versions (passing on 0.10.0/0.11.0, failing on 0.11.32/0.12.0/0.12.19), making `uv lock --check`/`uv sync --locked` version-dependent. Verified passing against uv 0.10.0, 0.11.28, and 0.12.19 with the regenerated lock. - tox.ini: [testenv:pii_check] now delegates to `make pii_check` instead of duplicating its command inline, matching [testenv:quality]/[testenv:docs]. - Dockerfile: switch FROM ubuntu:focal to ubuntu:noble (24.04), since focal never had python3.12/python3.12-venv packages at all. Drop the system-wide `pip install --upgrade pip setuptools` (debian's python3-pip on noble can't be upgraded in place and PEP 668 blocks it anyway; nothing depends on the system Python since everything runs in the venv). Fix the --no-install-project mismatch: the local package now lives under src/ and was never actually getting installed, so the full source tree is now COPY'd in and a second `uv sync --locked --group dev` (without --no-install-project) installs the local project from its real src/ layout. This is a two-stage sync: the first, --no-install-project sync stays cached as long as pyproject.toml/uv.lock don't change; the second (fast) one reruns on every source change, since it has to. Added .dockerignore so local build/test artifacts (.tox/, __pycache__/, etc.) that may exist in the working tree don't get copied into the image root-owned, which would otherwise break `make clean` for the unprivileged app user. Verified: `docker build` succeeds from a clean `--no-cache` build, the resulting image imports openedx_ledger correctly, and `make test`/ `make pii_check` pass via `docker-compose`'s bind-mount usage pattern (34 tests passed, 94% coverage). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add --locked to ci.yml's uv sync/uv run tox steps This was requested on discussion_r4137487159 and mistakenly reported as already done -- it wasn't actually pushed. Adding it now for real: CI will now fail loudly if uv.lock doesn't match pyproject.toml, instead of silently relocking in the runner. Per feanil's review on #242: discussion_r4137487159. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Irfan Ahmad <irfan.ahmad@A006-01919.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat: consolidate package metadata into pyproject.toml Replace setup.py/setup.cfg with PEP 621 static metadata in pyproject.toml, following the org-wide modernization effort tracked in openedx/public-engineering#506. - Static [project] metadata (name, description, classifiers, static dependencies) with dynamic version resolved via setuptools-scm from git tags instead of the hardcoded __init__.py string - SPDX license expression (license = "AGPL-3.0") + license-files, replacing the old license="AGPL 3.0" string - __version__ in openedx_filters/__init__.py now reads from importlib.metadata instead of being hand-maintained (and permanently stale relative to git tags) - Move .coveragerc and setup.cfg's [isort] section into pyproject.toml ([tool.coverage.*], [tool.isort]) - Exclude test subpackages from the built wheel via [tool.setuptools.packages.find] / [tool.setuptools.exclude-package-data] - Drop changelog.d/scriv.ini's now-stale version literal (was pointed at the removed __init__.py __version__ string) * feat: switch dependency management from pip-compile to uv Replace pip-compile/requirements/*.txt with uv + PEP 735 dependency groups, per openedx/public-engineering#506. - Add [dependency-groups] (test-base, test, django42, quality, doc, ci, dev) mirroring the old base/test/quality/doc/ci/dev requirements layering; commit the resulting uv.lock - [tool.edx_lint].uv_constraints / [tool.uv].constraint-dependencies wired up via `edx_lint write_uv_constraints` - tox.ini: tox-uv>=1 with the uv-venv-lock-runner; dependency_groups replace deps/commands_pre (the old py311-only "make upgrade" + requirements/test.txt install pre-step is gone, no longer needed since uv.lock drives all envs identically) - Makefile: `requirements`/`upgrade`/quality targets now use `uv sync`/`uv lock`/`uv run tox` instead of pip-tools and `python setup.py bdist_wheel` - .readthedocs.yaml: install docs deps via RTD's native uv support (method: uv, group: doc) instead of requirements/doc.txt - ci.yml: astral-sh/setup-uv instead of actions/setup-python + pip install; fetch-depth: 0 so setuptools-scm can see tags; per-toxenv job names; contents: read permissions - Delete the requirements/ directory Explicitly pin the `uv` package version (a transitive dependency of tox-uv/tox-uv-bare) via [tool.edx_lint].uv_constraints, and match it in ci.yml's setup-uv step, and explicitly list all django42-conflicting dependency groups in [tool.uv].conflicts instead of relying on uv to derive them. Both were needed for a reproducible uv.lock: different uv releases serialize auto-derived [tool.uv].conflicts entries in different (and, within a single uv release, not always stable) order, which otherwise makes tox-uv-bare's `uv sync --locked` step fail nondeterministically depending on which uv version last touched the lockfile. * feat: add semantic-release and commitlint workflows Replace the manual workflow_dispatch release process (bump-my-version + scriv github-release) with python-semantic-release, per openedx/public-engineering#506. - [tool.semantic_release]: build via `python -m build` with SETUPTOOLS_SCM_PRETEND_VERSION so setuptools-scm reports the version semantic-release just computed; major_on_zero = false, allow_zero_version = true - .github/workflows/release.yml: on push to main, run CI, then python-semantic-release (tag + GitHub release, no changelog file management since CHANGELOG.rst stays scriv/manually curated), then publish to PyPI via OIDC (id-token: write, no stored credentials) - ci.yml: add workflow_call trigger so release.yml can reuse it as its test gate; drop the push-to-main trigger now that release.yml owns that path, avoiding a duplicate test run on every push to main - Delete pypi-publish.yml (the old release: published-triggered, token-based publish workflow) so it can't race the new OIDC-based publish_to_pypi job once semantic-release starts pushing tags - commitlint.yml already existed (reusable openedx/.github workflow) and needed no changes Pre-flight check: the latest git tag (v3.8.0, via `git tag --sort=-v:refname`) matches the version currently live on PyPI (3.8.0), so the first semantic-release run has an accurate base to compute the next version from. Pinned pypa/gh-action-pypi-publish to a commit SHA (not the floating @release/v1 ref some other org repos still use) and python-semantic-release/python-semantic-release + python-semantic-release/publish-action + actions/upload-artifact + actions/download-artifact to commit SHAs too, matching this repo's existing house style of SHA-pinning every action. Note: PyPI's trusted publisher (OIDC) configuration for this project still needs to be confirmed/added by a maintainer with PyPI project admin access before the first automated release can publish -- this PR cannot configure that from here. See PR description. * fix: absorb one-time coverage % drop from correcting the test omit pattern The pre-migration .coveragerc's `omit = tests` never actually matched anything -- coverage.py omit patterns need a wildcard (e.g. `*/tests/*`), so test modules were inadvertently included in the coverage measurement and inflated the reported project total (test files execute almost all of their own lines, so including them nudges the % up). Verified locally: re-running the exact same test suite with the old config's omit pattern measures 99.7809%, vs 99.5338% with the new (correct) [tool.coverage.run] omit patterns from `pyproject.toml` -- a ~0.25 point drop entirely explained by that fix, not a real regression in production code coverage. Add a documented 1% threshold to codecov.yml's project status so this one-time methodology correction doesn't fail codecov/project, while still catching genuine coverage regressions going forward. * refactor: move package to src/ layout Per the org-wide decision on openedx/public-engineering#506 (src/ layout in scope for this modernization cycle, precedent set in xblocks-core/xblocks-extra), move openedx_filters/ to src/openedx_filters/. Fixes editable-install resolution under tools like mypy, which don't reliably resolve flat-layout editable installs (see https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/). - pyproject.toml: [tool.setuptools] package-dir = {"" = "src"}; [tool.setuptools.packages.find] where = ["src"] - MANIFEST.in: recursive-include path updated to src/openedx_filters - mypy.ini: files = src/openedx_filters - Makefile: pylint/pycodestyle/ruff/isort quality-check targets updated to point at src/openedx_filters (these take filesystem paths, not importable module names, so they needed the explicit path fix; mypy.ini's `files` setting is likewise path-based) - docs/conf.py: sys.path.insert now targets ../src; linkcode_resolve's REPO_URL relpath calculation now walks up two directories (out of src/) instead of one, so generated GitHub source links point at src/openedx_filters/... instead of the now-stale openedx_filters/... - Fixed two docs files with hardcoded (now-broken) GitHub blob links to the pre-move flat path (docs/how-tos/create-a-pipeline-step.rst, docs/reference/glossary.rst) [tool.coverage.run].source and tox.ini's `pytest --cov openedx_filters` are left as the module name (not a path) -- both coverage.py and pytest-cov resolve module names via the installed package regardless of physical layout, verified locally to still report accurate per-file coverage against the new src/ paths. Verified locally: editable install (`uv sync`) resolves `openedx_filters.__file__` to src/openedx_filters/__init__.py; `uv run mypy` succeeds against the new layout; full tox matrix (py311-django42, py311-django52, django42, django52, quality, docs) passes; `uv build` produces a wheel with an unchanged install layout (openedx_filters/... at the wheel root, no src/ prefix leaks into the installed package) -- so this does not change the package's import path or public API for consumers like openedx-platform, only this repo's internal file layout. * fix: correct invalid/mismatched action SHA pins in release.yml Verified every uses:@sha in release.yml against the GitHub API (repos/<owner>/<repo>/commits/<sha>). python-semantic-release/publish-action was pinned to a SHA that doesn't exist in that repo at all; python-semantic-release/python-semantic-release and pypa/gh-action-pypi-publish were pinned to their tag *objects'* SHAs rather than the underlying commit SHAs (a real but different git object, not resolvable the same way a `uses:` checkout needs). Both invisible to PR CI since release.yml only runs on push to main. Reverted python-semantic-release, publish-action, upload-artifact, and download-artifact to plain version tags matching openedx/XBlock's actual, already-releasing release.yml. Corrected pypa/gh-action-pypi-publish to the real commit SHA. * feat: enable python-semantic-release changelog generation changelog: "false" was blindly copied from the sample-plugin reference template with no ticket ever requiring it to be disabled. Wire up PSR to update the existing CHANGELOG.rst in place instead: - Add the insertion marker as the first line of CHANGELOG.rst so PSR's update mode has an anchor to insert new version sections above. - Remove changelog: "false" from release.yml's PSR step. - Configure [tool.semantic_release.changelog] (mode="update", RST output) to target CHANGELOG.rst. - Set tag_format = "v{version}" explicitly to match this repo's actual tag convention (confirmed via git tag, e.g. v3.8.0). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: remove stale scriv changelog checklist item from PR template The PR template still told contributors to add a changelog entry "using scriv" — a manual process this migration effort already replaced with python-semantic-release, which now generates CHANGELOG.rst automatically from conventional commit messages. Drop the obsolete checklist item so the template doesn't ask for a step that no longer applies. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: remove stale manual tag/PyPI-verification steps from PR template 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. * fix: add myst-parser to doc dependency-group The origin/main merge brought in docs/conf.py's myst_parser Sphinx extension (for the new Markdown-format ADR under docs/decisions/), but the doc group in pyproject.toml was never updated to match, breaking the docs build. * fix: disable python-semantic-release changelog generation 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." * style: trim codecov.yml explanatory comment 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: The old .coveragerc's omit = tests didn't actually match any file paths (coverage.py omit patterns need a wildcard, e.g. */tests/*), so test modules were inadvertently included in the coverage measurement pre-migration and inflated the reported project % (test files execute almost all of their own lines). The new [tool.coverage.run] omit patterns in pyproject.toml correctly exclude them, which drops the reported total by ~0.25 percentage points as a one-time discontinuity in this PR's own base-vs-head comparison, not an actual regression. target: auto means the new percentage becomes the baseline once this merges; no threshold was added. * fix: drop tox from Makefile's docs target Inline tox.ini's [testenv:docs] commands via uv run/uv sync instead of shelling out to `uv run tox -e docs`, matching the no-tox-in-Makefile convention already used elsewhere. tox.ini itself is untouched. * fix: create GitHub release with assets attached, not after publish 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. * fix: re-pin release.yml actions to sample-plugin's verified SHAs A prior commit (209d708) deliberately un-pinned python-semantic-release, actions/upload-artifact, and actions/download-artifact to plain version tags, reasoning that 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 == 'main'` 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> * fix: drop unneeded fetch-depth: 0 from ci.yml checkout src/openedx_filters/__init__.py sets __version__ from importlib.metadata (falling back to "0.0.0"); 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> * fix: remove unnecessary try/except around __version__ (not present pre-migration) * fix: remove fail-fast: false (not present pre-migration) * fix: resolve conflicts with main, add SupportContactContextRequested filter Merges in filter changes that landed on main after this branch's src-layout migration (openedx_filters/ -> src/openedx_filters/): the new SupportContactContextRequested filter, its tests, and the CHANGELOG.rst entry. __init__.py's version stays dynamic (importlib.metadata.version) rather than reverting to the static 3.10.0 bump, consistent with this migration's versioning approach. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove dangling codecov.yml comment, not present pre-migration The comment referenced a threshold that a prior commit accidentally deleted, and pointed at "the PR description" for an explanation that was never actually added there. Coverage config itself is out of scope for this migration (per team decision), so restore codecov.yml to be byte-identical to pre-migration main rather than leaving a stale, misleading comment behind. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: ignore .venv, missed when this repo moved to uv uv creates .venv by default; without a gitignore entry there's a real risk of it getting accidentally committed. Same fix already applied to the other repos in this migration batch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: revert CI job name to static "tests" ${{ 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> * fix: use secrets.GITHUB_TOKEN instead of custom org secret for release job Matches openedx/sample-plugin's current release.yml; the custom OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN secret isn't needed. * fix: prefix Makefile tool invocations with uv run Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: correct SPDX license identifier to AGPL-3.0-or-later Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop uv run from Makefile per feanil's review feedback Makefile now assumes an already-synced/activated local env; uv run stays in CI workflow steps only. See openedx/ccx-keys#190 (review comment r4097501942). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop Django upper-bound cap from published dependency spec Requires-Dist declared django<6.0, which blocks every downstream consumer of openedx-filters from Django 6.0 until a new release is cut. Django<6.0 already exists in [tool.uv].constraint-dependencies, which only bounds this repo's own lockfile and is the right place for a pin like this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: use AGPL-3.0-or-later SPDX license identifier The pre-migration setup.py classifier was "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", so the SPDX identifier should carry the "or later" qualifier too, consistent with the same fix applied to other repos in this batch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: drop scriv in favor of PSR-generated release notes Per feanil's recommendation, standardize on Python Semantic Release's own release-notes generation instead of scriv. Removes the scriv dev dependency, the make changelog-entry/changelog targets that invoked it, and its config/templates under changelog.d/ (only scriv's own config and fragment templates were there -- no real pending changelog fragments to fold into CHANGELOG.rst first). CHANGELOG.rst keeps its "Unreleased" section so future entries have somewhere to land; the scriv-specific usage instructions and insertion marker are removed since they no longer apply. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: remove unneeded exact uv version pin from CI setup-uv step tox-uv installs its own uv into each per-env venv independent of the uv used to run `uv sync`/`uv run` in the workflow itself, so pinning the astral-sh/setup-uv version to match uv.lock's own uv pin isn't necessary. Verified locally with uv 0.11.28 on PATH (vs 0.11.32 locked): `uv sync`, `uv lock --check`, and `uv run tox -e quality` all pass unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add --locked to CI's uv sync/run invocations Without --locked, a uv.lock that's out of sync with pyproject.toml gets silently relocked in the runner instead of failing CI. Both `uv sync --locked --group ci` and `uv run --locked tox -e ...` pass unchanged on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add contents:read permission to publish_to_pypi job This job only downloads a build artifact and publishes it to PyPI, so make that explicitly read-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: delegate docs to tox, drop unneeded uv==0.11.32 pin - Makefile docs: reverted to `tox -e docs` (matching main) instead of inlining tox.ini's 5 commands plus a stray `uv sync --group doc` line -- second copy to keep in sync, same issue already fixed on quality/pii_check. - Dropped the `uv==0.11.32` pin from constraint-dependencies and [tool.edx_lint].uv_constraints. Verified with `edx_lint write_uv_constraints` itself (reproduces the same result) and with `uv lock --locked`/`tox -e quality` passing under both uv 0.11.28 and 0.11.32 -- the pin's stated reason (uv versions disagreeing on how derived [tool.uv].conflicts entries serialize) is moot now that this repo's conflicts list is already explicit, not derived. Per feanil's review on #381: discussion_r4147344066, r4147661867. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Irfan Ahmad <irfan.ahmad@A006-01919.local>

Modernizes the Python tooling for
DoneXBlockin three phases, aligning with the Open edX org standard established inopenedx/sample-plugin.Ticket: openedx/public-engineering#511
Parent ticket: openedx/public-engineering#506