Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: Python CI

on:
push:
branches: [main]
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:
Expand Down
93 changes: 93 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Python Semantic Release

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/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions"
git_committer_email: "[email protected]"
working-directory: './backend'
Comment on lines +38 to +46
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The working-directory key is incorrectly placed. In GitHub Actions, working-directory should be nested under the with block for actions, or under the run key for shell commands, but cannot be placed at the step level for action steps. This will cause a workflow syntax error.

For the semantic release action, if you need to set the working directory, you should use the root_options parameter with the --noop flag or configure it via the pyproject.toml file in the backend directory.

Copilot uses AI. Check for mistakes.

- name: Publish | Upload to GitHub Release Assets
uses: python-semantic-release/[email protected]
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.release.outputs.tag }}
working-directory: './backend'
Comment on lines +48 to +54
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The working-directory key is incorrectly placed at the step level. For the publish-action, this should be nested under the with block if the action supports it, or you may need to handle path resolution differently. This will cause a workflow syntax error.

Copilot uses AI. Check for mistakes.

- name: Upload | Distribution Artifacts
uses: actions/upload-artifact@v4
with:
name: distribution-artifacts
path: dist
if-no-files-found: error
working-directory: './backend'
Comment on lines +60 to +62
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The working-directory key is incorrectly placed at the step level. For actions/upload-artifact@v4, this should be removed and the path parameter should be updated to backend/dist to reference the correct directory. This will cause a workflow syntax error.

Suggested change
path: dist
if-no-files-found: error
working-directory: './backend'
path: backend/dist
if-no-files-found: error

Copilot uses AI. Check for mistakes.

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 }}
Comment on lines +92 to +93
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The pypa/gh-action-pypi-publish action is being used with deprecated parameters. The 'user' and 'password' parameters are deprecated in favor of trusted publishing. Since the job already has 'id-token: write' permission configured (line 75), which is required for trusted publishing, the 'user' and 'password' parameters should be removed to use the more secure OIDC-based trusted publisher flow instead of API tokens.

Suggested change
user: __token__
password: ${{ secrets.PYPI_UPLOAD_TOKEN }}

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +93
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

Using username/password authentication with PyPI is deprecated. The workflow is using user: __token__ and password parameters, but with the id-token: write permission already granted, you should use trusted publishing instead. Remove the user and password parameters to use OIDC trusted publishing, which is more secure and doesn't require storing secrets.

Suggested change
user: __token__
password: ${{ secrets.PYPI_UPLOAD_TOKEN }}

Copilot uses AI. Check for mistakes.
19 changes: 2 additions & 17 deletions backend/docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Comment on lines +21 to +27
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The use of importlib.metadata.version() will fail if the package is not installed. When building documentation, the package may not be installed yet, which will cause the documentation build to fail. Consider adding a fallback mechanism or wrapping this in a try-except block to handle cases where the package metadata is not available.

Copilot uses AI. Check for mistakes.
# Configure Django for autodoc usage
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings'
django_setup()
Expand Down
7 changes: 5 additions & 2 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The hardcoded version "0.1.0" conflicts with the semantic release approach. With python-semantic-release, the version should be dynamically managed by the tool. However, since you removed "version" from the dynamic list and added a static version, this creates a situation where semantic-release updates will need to modify the static version field in pyproject.toml. Ensure that your semantic_release configuration (once complete) correctly specifies this location as the version source.

Copilot uses AI. Check for mistakes.
authors = [
{name = "Open edX Project", email = "[email protected]"},
]
Expand All @@ -24,7 +25,7 @@ keywords= [
"edx",
]

dynamic = ["version", "readme", "dependencies"]
dynamic = ["readme", "dependencies"]

[project.entry-points."lms.djangoapp"]
sample_plugin = "sample_plugin.apps:SamplePluginConfig"
Expand All @@ -37,10 +38,12 @@ 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"}

[tool.setuptools.packages.find]
include = ["sample_plugin*"]
exclude = ["sample_plugin.tests*"]

[tool.semantic_release.changelog.default_templates]
changelog_file = "../CHANGELOG.md"
6 changes: 5 additions & 1 deletion backend/sample_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 `openedx-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')
Comment on lines +5 to +9
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The use of importlib.metadata.version() will fail if the package is not installed. This can cause issues during development when working with the package in editable mode or when building documentation. Consider adding a fallback mechanism to handle the case when the package metadata is not available, or wrapping this in a try-except block.

Suggested change
from importlib.metadata import version as get_version
# The name of the package is `openedx-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 importlib.metadata import PackageNotFoundError, version as get_version
# The name of the package is `openedx-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__`.
try:
__version__ = get_version('openedx-sample-plugin')
except PackageNotFoundError:
__version__ = "0.0.0"

Copilot uses AI. Check for mistakes.