From e0fa3319ec39b8000488ff639b6dffb8aea088c1 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Fri, 26 Dec 2025 16:11:09 -0500 Subject: [PATCH 1/5] build: Invert version fetching. A lot of systems expect the version number to be in the package metadata so make that the source of truth and update the code to pull the version variable from there instead of the other way around. --- backend/docs/conf.py | 19 ++----------------- backend/pyproject.toml | 4 ++-- backend/sample_plugin/__init__.py | 6 +++++- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/backend/docs/conf.py b/backend/docs/conf.py index 6d2d8fa..798bfce 100644 --- a/backend/docs/conf.py +++ b/backend/docs/conf.py @@ -18,28 +18,13 @@ from subprocess import check_call from django import setup as django_setup - - -def get_version(*file_paths): - """ - Extract the version string from the file. - - Input: - - file_paths: relative path fragments to file with - version string - """ - filename = os.path.join(os.path.dirname(__file__), *file_paths) - version_file = open(filename, encoding="utf8").read() - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError('Unable to find version string.') +from importlib.metadata import version as get_version REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(REPO_ROOT) -VERSION = get_version('../sample_plugin', '__init__.py') +VERSION = get_version('openedx-sample-plugin') # Configure Django for autodoc usage os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings' django_setup() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4c3b515..e0178d3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -7,6 +7,7 @@ name = "openedx-sample-plugin" description = "A sample backend plugin for the Open edX Platform" requires-python = ">=3.11" license="Apache-2.0" +version = "0.1.0" authors = [ {name = "Open edX Project", email = "oscm@openedx.org"}, ] @@ -24,7 +25,7 @@ keywords= [ "edx", ] -dynamic = ["version", "readme", "dependencies"] +dynamic = ["readme", "dependencies"] [project.entry-points."lms.djangoapp"] sample_plugin = "sample_plugin.apps:SamplePluginConfig" @@ -37,7 +38,6 @@ Homepage = "https://openedx.org/openedx/sample-plugin" Repository = "https://openedx.org/openedx/sample-plugin" [tool.setuptools.dynamic] -version = {attr = "sample_plugin.__version__"} readme = {file = ["README.rst", "CHANGELOG.rst"]} dependencies = {file = "requirements/base.in"} diff --git a/backend/sample_plugin/__init__.py b/backend/sample_plugin/__init__.py index 8ff2f9e..e84b710 100644 --- a/backend/sample_plugin/__init__.py +++ b/backend/sample_plugin/__init__.py @@ -2,4 +2,8 @@ A sample backend plugin for the Open edX Platform. """ -__version__ = "0.1.0" +from importlib.metadata import version as get_version + +# The name of the package is `opnedx-sample-plugin` but __package__ is `sample_plugin` so we hardcode the name of the +# package here so that the version fetching works correctly. A lot of examples will show using `__package__`. +__version__ = get_version('openedx-sample-plugin') From 190ec195429da4d1bab6af4671432c955d14f691 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Fri, 26 Dec 2025 16:27:26 -0500 Subject: [PATCH 2/5] build: Add minimal config needed for python-semantic-release --- backend/pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e0178d3..5434df0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -44,3 +44,7 @@ dependencies = {file = "requirements/base.in"} [tool.setuptools.packages.find] include = ["sample_plugin*"] exclude = ["sample_plugin.tests*"] + +[tool.semantic_release.changelog.default_templates] +changelog_file = "CHANGELOG.rst" +output_format = "rst" From addf512b534fc2baab0aeb9649e3b0a20a0e6d4b Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Mon, 29 Dec 2025 14:25:10 -0500 Subject: [PATCH 3/5] build: Add a github workflow for python-semantic-release This should release the plugin to PyPI on new merges to main. --- .github/workflows/ci.yml | 3 ++ .github/workflows/release.yml | 90 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9de30b..223daf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - "**" + # This is so we can call CI locally from other workflows that might want to + # run CI before doing whatever task they're doing. Like the release workflow. + workflow-call: defaults: run: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3ad1a82 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,90 @@ +name: Python CI + +on: + push: + branches: [main] + +jobs: + run_tests: + uses: ./.github/workflows/ci.yml + + release: + needs: run_tests + runs-on: ubuntu-latest + if: github.ref_name == 'main' + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref_name }} + cancel-in-progress: false + + permissions: + contents: write + + steps: + # Note: We checkout the repository at the branch that triggered the workflow. + # Python Semantic Release will automatically convert shallow clones to full clones + # if needed to ensure proper history evaluation. However, we forcefully reset the + # branch to the workflow sha because it is possible that the branch was updated + # while the workflow was running, which prevents accidentally releasing un-evaluated + # changes. + - name: Setup | Checkout Repository on Release Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + + - name: Setup | Force release branch to be at workflow sha + run: | + git reset --hard ${{ github.sha }} + + - name: Action | Semantic Version Release + id: release + # Adjust tag with desired version if applicable. + uses: python-semantic-release/python-semantic-release@v10.5.3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + git_committer_name: "github-actions" + git_committer_email: "actions@users.noreply.github.com" + + - name: Publish | Upload to GitHub Release Assets + uses: python-semantic-release/publish-action@v10.5.3 + if: steps.release.outputs.released == 'true' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release.outputs.tag }} + + - name: Upload | Distribution Artifacts + uses: actions/upload-artifact@v4 + with: + name: distribution-artifacts + path: dist + if-no-files-found: error + + outputs: + released: ${{ steps.release.outputs.released || 'false' }} + + deploy: + # 1. Separate out the deploy step from the publish step to run each step at + # the least amount of token privilege + # 2. Also, deployments can fail, and its better to have a separate job if you need to retry + # and it won't require reversing the release. + runs-on: ubuntu-latest + needs: release + if: github.ref_name == 'main' && needs.release.outputs.released == 'true' + + permissions: + contents: read + id-token: write + + steps: + - name: Setup | Download Build Artifacts + uses: actions/download-artifact@v4 + id: artifact-download + with: + name: distribution-artifacts + path: dist + + - name: Publish to PyPi + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + user: __token__ + password: ${{ secrets.PYPI_UPLOAD_TOKEN }} From 750a51b9347452cdbdc8a0fc9bc8548709d8778d Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Mon, 29 Dec 2025 15:58:05 -0500 Subject: [PATCH 4/5] fixup! build: Add a github workflow for python-semantic-release --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 223daf0..9b31999 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,6 @@ name: Python CI on: - push: - branches: [main] pull_request: branches: - "**" From dc0e8175f0cd729075ef4f47110122e131d84293 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Mon, 29 Dec 2025 15:59:11 -0500 Subject: [PATCH 5/5] fixup! build: Add a github workflow for python-semantic-release --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b31999..a315c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - "**" # This is so we can call CI locally from other workflows that might want to # run CI before doing whatever task they're doing. Like the release workflow. - workflow-call: + workflow_call: defaults: run: