Skip to content

Modernize Python repo: pyproject.toml + uv + semantic-release - #388

Merged
salman2013 merged 1 commit into
openedx:masterfrom
salman2013:salman/modernize-repo
Sep 3, 2026
Merged

salman2013 merged 1 commit into
openedx:masterfrom
salman2013:salman/modernize-repo

Conversation

@salman2013

@salman2013 salman2013 commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Modernizes the Python tooling for DoneXBlock in three phases, aligning with the Open edX org standard established in openedx/sample-plugin.

Ticket: openedx/public-engineering#511
Parent ticket: openedx/public-engineering#506

Changes generated by Claude Sonnet 4.6 using the modernize-python-tooling skill, reviewed by human.

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Jul 13, 2026
@openedx-webhooks

openedx-webhooks commented Jul 13, 2026 •

Copy link
Copy Markdown

Thanks for the pull request, @salman2013!

This repository is currently maintained by @openedx/axim-engineering.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@codecov

codecov Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.35%. Comparing base (5f248cd) to head (15282c2).

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           
Flag Coverage Δ
unittests 55.35% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Jul 13, 2026
@salman2013
salman2013 marked this pull request as ready for review July 14, 2026 14:48
@salman2013
salman2013 marked this pull request as draft July 14, 2026 14:48
@salman2013
salman2013 marked this pull request as ready for review July 14, 2026 14:52
@irfanuddinahmad

Copy link
Copy Markdown

👋 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. pypa/gh-action-pypi-publish@release/v1 is a floating branch ref, not a pinned SHA. Same issue flagged on the other PRs in this batch — this is what caused openedx/xblocks-core's release to fail with docker: manifest unknown (https://github.com/openedx/xblocks-core/actions/runs/29327012856/job/87066591905). Worth pinning to a commit SHA. (Filed the same fix upstream: openedx/sample-plugin#54.)

2. fallback_version = "0.0.0.dev0" in [tool.setuptools_scm] — checked whether this could hit the same crash we found on openedx-events (that repo's fallback_version had the identical value, and a runtime .split(".")/int() parse of __version__ crashed 17 tests when it was hit). Confirmed done/__init__.py's __version__ isn't consumed anywhere else in this codebase, so it's safe here — no action needed, just flagging why the value looked familiar.

Non-blocking: major_on_zero/allow_zero_version aren't set in [tool.semantic_release] here — since this repo is already at 3.0.0 it'd be a no-op either way, but worth adding for consistency with the rest of the batch (protects against ever auto-jumping to 1.0.0 if the version is reset).

@salman2013
salman2013 force-pushed the salman/modernize-repo branch from f53a032 to 48fda12 Compare July 17, 2026 17:31
@salman2013
salman2013 force-pushed the salman/modernize-repo branch 2 times, most recently from 4053301 to 11cd266 Compare August 10, 2026 08:42

@irfanuddinahmad irfanuddinahmad left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread pyproject.toml Outdated
[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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
major_on_zero = false
allow_zero_version = true
major_on_zero = false
tag_format = "{version}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread pyproject.toml Outdated
[tool.setuptools_scm]
version_scheme = "only-version"
local_scheme = "no-local-version"
fallback_version = "0.0.0.dev0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"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.

Suggested change
fallback_version = "0.0.0.dev0"
fallback_version = "0.0.0"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread .github/workflows/ci.yml Outdated
toxenv: [django42, django52, quality]

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread src/done/__init__.py
from importlib.metadata import version

from .done import DoneXBlock

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
from importlib.metadata import PackageNotFoundError, version
from .done import DoneXBlock
try:
__version__ = version("done-xblock")
except PackageNotFoundError:
__version__ = "unknown"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I removed this version as UV manages by own.

Comment thread Makefile

install: install-test

quality: ## Run the quality checks

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We are not adding ruff in this scope because it needs to add more files formatting, so i removed that.

Comment thread pyproject.toml
changelog_file = "CHANGELOG.rst"
output_format = "rst"

[tool.uv]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
[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"}],
]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread pyproject.toml Outdated
name = "done-xblock"
description = "done XBlock"
readme = "README.rst"
license = "AGPL-3.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

@irfanuddinahmad irfanuddinahmad left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A couple of minor, non-blocking notes.

Comment thread Makefile Outdated
quality: ## Run the quality checks
pylint --rcfile=pylintrc done
python setup.py -q sdist
pylint --rcfile=pylintrc src/done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread .readthedocs.yaml Outdated
python:
install:
- requirements: requirements/docs.txt
- method: pip

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

@salman2013
salman2013 force-pushed the salman/modernize-repo branch 2 times, most recently from a507202 to c5a89c5 Compare August 18, 2026 14:34
Comment thread done/__init__.py

from .done import DoneXBlock

__version__ = '3.0.0'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be replaced with a get_version call and the variable should still be set for convenience/compatibility.

Comment thread CHANGELOG.rst Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread codecov.yml Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, codecov has default configuration which is fine in most cases. We only need to override it sometimes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have removed it.

@salman2013
salman2013 requested a review from feanil August 18, 2026 19:16
irfanuddinahmad pushed a commit to irfanuddinahmad/opaque-keys that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/ccx-keys that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/openedx-filters that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/openedx-core that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/openedx-calc that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/openedx-chem that referenced this pull request Aug 19, 2026
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."
irfanuddinahmad pushed a commit to openedx/taxonomy-connector that referenced this pull request Aug 19, 2026
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."
@mphilbrick211 mphilbrick211 moved this from Waiting on Author to In Eng Review in Contributions Aug 19, 2026
@salman2013
salman2013 force-pushed the salman/modernize-repo branch 2 times, most recently from 7be658b to 720583e Compare August 20, 2026 15:11
@salman2013

Copy link
Copy Markdown
Contributor Author

@feanil I believe its ready for another pass.

@farhan farhan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

few changes, mostly looks good

Comment thread pyproject.toml Outdated

[tool.semantic_release]
build_command = "pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=$NEW_VERSION python -m build"
allow_zero_version = true

@farhan farhan Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/done/__init__.py Outdated

try:
__version__ = version("done-xblock")
except PackageNotFoundError:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread .github/workflows/release.yml Outdated

- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 # v10.5.3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should update it to latest version

Comment thread tox.ini Outdated
@@ -1,21 +1,21 @@
[tox]
envlist = py{312}-django{42,52}, quality

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should add docs environment

I tested it locally, make docs is failing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is pending, make sure we run docs in the ci as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already done in this commit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2026-09-02 at 10 08 56 PM

@farhan farhan Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread MANIFEST.in Outdated
@@ -1,4 +1,2 @@
include requirements/base.in
include NOTICE
include LICENSE

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can remove this LICENSE declaration now

Comment thread Makefile
@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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have decided to not to drop the make file targets, we can provide their alternatives.

irfanuddinahmad pushed a commit to irfanuddinahmad/ccx-keys that referenced this pull request Aug 27, 2026
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."
Comment thread .github/workflows/ci.yml Outdated
strategy:
matrix:
python-version: ['3.12']
toxenv: [django42, django52, quality]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should docs as well in it.

@irfanuddinahmad

Copy link
Copy Markdown

Hey @salman2013 — nudge on farhan's review from 8/25 (still CHANGES_REQUESTED, no changes pushed since). The # pragma: no cover on except PackageNotFoundError in particular is why codecov/project is currently red. The other four (zero-version guard, release.yml version bump, docs tox env, MANIFEST.in LICENSE line) are still open too.

@farhan

farhan commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

📦 Packaging check — asset diff vs master

I built the wheel + sdist from this branch and from master and compared the contents. Core static assets (static/, templates/, public/ — png/css/js/html) are identical between the two ✅, but the installed wheel has a couple of differences worth a look.

✅ Added in the PR wheel (not in master's wheel)

  • done/tests/__init__.py, done/tests/test_nothing.py — the test package is now shipped into site-packages. [tool.setuptools.packages.find] under src/ picks up done.tests, and exclude-package-data only strips data files, not .py modules. Master's wheel didn't install these.
  • done/conf/locale/config.yaml — benign (legitimate package data).
  • done_xblock-3.0.0.dist-info/licenses/LICENSE — LICENSE just relocated to the PEP 639 licenses/ subdir (not new content).

❌ Dropped in the PR wheel (was in master's wheel)

  • done_xblock-3.0.0.dist-info/NOTICE — NOTICE is no longer shipped anywhere in the installed wheel. license-files = ["LICENSE"] omits it. (It still lands in the sdist via include NOTICE, but not in the wheel that users actually install.)
  • done_xblock-3.0.0.dist-info/LICENSE — only moved to licenses/LICENSE (above), so not truly lost.
Lower-impact: the sdist is now bloated

The sdist now ships nearly every repo file — .github/, docs/, Dockerfile, tox.ini, uv.lock, scripts/, catalog-info.yaml, pylintrc, root screenshots, etc. This is because setuptools-scm includes all git-tracked files by default and the new MANIFEST.in (just include NOTICE) has no prune/exclude rules. It doesn't affect installs, but it's untidy and would let htmlcov/ / coverage.xml / .coverage leak into a release build made from a post-test tree.

Suggested fix (one small packaging commit)

  1. Keep NOTICE in the wheel: license-files = ["LICENSE", "NOTICE"]
  2. Exclude tests from the wheel: [tool.setuptools.packages.find] → exclude = ["*.tests*", "tests*"]
  3. Add prune rules to MANIFEST.in (.github, docs, htmlcov, coverage.xml, .coverage) to trim the sdist and prevent artifact leakage.
Repro
python -m build   # this branch
git worktree add /tmp/m master && (cd /tmp/m && python -m build --no-isolation -o /tmp/mb)
diff <(unzip -l /tmp/mb/*.whl | awk 'NR>3{print $4}' | sort) \
     <(unzip -l dist/*.whl    | awk 'NR>3{print $4}' | sort)

@farhan farhan left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 important points to address

rest all seems good

Comment thread .github/workflows/ci.yml Outdated
@salman2013
salman2013 requested a review from farhan September 1, 2026 18:03
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions[bot]"
git_committer_email: "github-actions[bot]@users.noreply.github.com"
changelog: "false"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Set vcs_release: "false" to match the sample-plugin standard

Suggested change
changelog: "false"
vcs_release: "false"

git_committer_email: "github-actions[bot]@users.noreply.github.com"
changelog: "false"

- name: Upload dist artifacts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/*

Comment thread tox.ini Outdated
@@ -1,21 +1,21 @@
[tox]
envlist = py{312}-django{42,52}, quality

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is pending, make sure we run docs in the ci as well.

@farhan farhan left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 nits and a change request

Comment thread tox.ini Outdated
@@ -1,21 +1,21 @@
[tox]
envlist = py{312}-django{42,52}, quality

@farhan farhan Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread .github/workflows/release.yml Outdated

- name: Upload dist artifacts
if: steps.release.outputs.released == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bump artifact actions to the latest: upload-artifact → v7.0.1 (043fb46d…), download-artifact → v8.0.1 (3e5f45b2…).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread .github/workflows/release.yml
@salman2013
salman2013 requested a review from farhan September 3, 2026 10:44
@salman2013
salman2013 force-pushed the salman/modernize-repo branch from 4f36000 to e33608c Compare September 3, 2026 11:52
- 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>
@salman2013
salman2013 force-pushed the salman/modernize-repo branch from e33608c to 15282c2 Compare September 3, 2026 11:54
@salman2013
salman2013 merged commit 53a3d1d into openedx:master Sep 3, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from In Eng Review to Done in Contributions Sep 3, 2026
irfanuddinahmad pushed a commit to irfanuddinahmad/event-bus-redis that referenced this pull request Sep 7, 2026
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."
irfanuddinahmad pushed a commit to irfanuddinahmad/openedx-filters that referenced this pull request Sep 7, 2026
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."
irfanuddinahmad pushed a commit to openedx/enterprise-integrated-channels that referenced this pull request Sep 7, 2026
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."
UsamaSadiq added a commit to openedx/edx-rest-api-client that referenced this pull request Sep 21, 2026
…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>
feanil pushed a commit to openedx/taxonomy-connector that referenced this pull request Sep 29, 2026
…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>
feanil pushed a commit to openedx/openedx-authz that referenced this pull request Sep 29, 2026
…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>
feanil pushed a commit to openedx/openedx-ledger that referenced this pull request Sep 30, 2026
…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>
feanil pushed a commit to openedx/openedx-filters that referenced this pull request Oct 1, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

6 participants