Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2adfef3
refactor: move package to src/ layout
irfanuddinahmad Jul 28, 2026
c655cfd
feat: consolidate package metadata into pyproject.toml
irfanuddinahmad Jul 28, 2026
3acbe29
feat: switch dependency management from pip-compile to uv
irfanuddinahmad Jul 28, 2026
e1ad523
feat: add semantic-release and OIDC PyPI publish workflow
irfanuddinahmad Jul 28, 2026
c9cb951
fix: enable python-semantic-release changelog generation
irfanuddinahmad Jul 30, 2026
1d05962
chore: remove obsolete version-bump/changelog checklist items from PR…
irfanuddinahmad Jul 30, 2026
1a63d89
docs: remove stale manual tag/PyPI-verification steps from PR template
irfanuddinahmad Jul 30, 2026
dd27a6a
fix: disable python-semantic-release changelog generation
Aug 19, 2026
33c6463
fix: drop tox from Makefile's docs and quality targets
Aug 24, 2026
5c1dc90
fix: create GitHub release with assets attached, not after publish
irfanuddinahmad Aug 27, 2026
4db58af
fix: pin release.yml actions to sample-plugin gold-standard SHAs
irfanuddinahmad Sep 2, 2026
1389079
fix: drop unneeded fetch-depth: 0 from ci.yml checkout
irfanuddinahmad Sep 2, 2026
e28bb1a
fix: align importlib.metadata.version aliasing with sample-plugin
irfanuddinahmad Sep 2, 2026
03c2db2
fix: remove unnecessary try/except around __version__ (not present pr…
irfanuddinahmad Sep 9, 2026
fc649fb
fix: update author to Open edX Project convention
irfanuddinahmad Sep 9, 2026
736b1ae
fix: remove fail-fast: false (not present pre-migration)
irfanuddinahmad Sep 9, 2026
f7baaee
fix: correct tests_utils directory name in quality target's isort call
irfanuddinahmad Sep 9, 2026
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
10 changes: 0 additions & 10 deletions .coveragerc

This file was deleted.

5 changes: 0 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@ instructions.
**Merge checklist:**
- [ ] All reviewers approved
- [ ] CI build is green
- [ ] Version bumped
- [ ] Changelog record added
- [ ] Documentation updated (not only docstrings)
- [ ] Commits are squashed
- [ ] PR author is listed in AUTHORS

**Post merge:**
- [ ] Create a tag
- [ ] Check new version is pushed to PyPi after tag-triggered build is
finished.
- [ ] Delete working branch (if not needed anymore)

**Author concerns:** List any concerns about this PR - inelegant
Expand Down
28 changes: 13 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
name: Python CI

on:
push:
branches: [master]
workflow_call:
pull_request:
branches:
- '**'

jobs:
run_tests:
name: tests
name: ${{ matrix.toxenv }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [ubuntu-latest]
python-version:
- '3.12'
toxenv: [quality, docs, django42, django52]
steps:
- uses: actions/checkout@v7
- name: setup python
uses: actions/setup-python@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
python-version: ${{ matrix.python-version }}

- name: Install pip
run: pip install -r requirements/pip.txt

- name: Install Dependencies
run: pip install -r requirements/ci.txt
- name: Install CI dependencies
run: uv sync --group ci

- name: Run Tests
env:
TOXENV: ${{ matrix.toxenv }}
run: tox
run: uv run tox -e ${{ matrix.toxenv }}

- name: Run coverage
if: matrix.python-version == '3.12' && matrix.toxenv == 'django52'
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: unittests
fail_ci_if_error: true
Expand Down
31 changes: 0 additions & 31 deletions .github/workflows/publish_pypi.yml

This file was deleted.

90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Release

on:
push:
branches: [master]

jobs:
run_tests:
uses: ./.github/workflows/ci.yml
secrets: inherit
permissions:
contents: read

release:
runs-on: ubuntu-latest
needs: run_tests
if: github.ref_name == 'master'
concurrency:
group: ${{ github.workflow }}-release-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: write
outputs:
released: ${{ steps.release.outputs.released || 'false' }}
version: ${{ steps.release.outputs.version }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.ref_name }}

- run: git reset --hard ${{ github.sha }}

- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@9a026e9303981c866c3425723009becb2437c757 # v10.6.2
with:
github_token: ${{ secrets.OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN }}
git_committer_name: "github-actions"
git_committer_email: "github-actions@github.com"
changelog: "false"
# Commit, tag, push and build, but don't create the GitHub release.
# We create it ourselves in the next step so that the distributions
# are attached before the release is published. See that step for why.
vcs_release: "false"

# This repo has immutable releases enabled, which freezes a release's
# assets the moment it is published, so assets cannot be attached
# afterwards. `gh release create` handles this by creating the release as
# a draft, uploading the assets, and only then publishing it:
# https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases
- name: Publish | 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/*

- name: Upload dist artifacts
if: steps.release.outputs.released == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
if-no-files-found: error

publish_to_pypi:
runs-on: ubuntu-latest
needs: release
if: github.ref_name == 'master' && needs.release.outputs.released == 'true'
permissions:
contents: read
id-token: write

steps:
- name: Download dist artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
__pycache__
.pytest_cache
venv/*
.venv/*

# C extensions
*.so
Expand Down Expand Up @@ -66,9 +67,6 @@ docs/modules.rst
docs/xss_utils.rst
docs/xss_utils.*.rst

# Private requirements
requirements/private.in
requirements/private.txt

# Development task artifacts
default.db
4 changes: 1 addition & 3 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ include CHANGELOG.rst
include CONTRIBUTING.rst
include LICENSE.txt
include README.rst
include requirements/base.in
recursive-include xss_utils *.html *.png *.gif *js *.css *jpg *jpeg *svg *py
include requirements/constraints.txt
recursive-include src/xss_utils *.html *.png *.gif *js *.css *jpg *jpeg *svg *py
46 changes: 21 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,31 @@ coverage: clean ## generate and view HTML coverage report
$(BROWSER) htmlcov/index.html

docs: ## generate Sphinx HTML documentation, including API docs
tox -e docs
uv sync --group doc
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run doc8 --ignore-path docs/_build README.rst docs
rm -f docs/xss_utils.rst
rm -f docs/modules.rst
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run make -C docs clean
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run make -C docs html
uv run python -m build
$(BROWSER) docs/_build/html/index.html

upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade
upgrade: ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip install -qr requirements/pip-tools.txt
# Make sure to compile files after any other files they include!
pip-compile --upgrade --allow-unsafe --rebuild -o requirements/pip.txt requirements/pip.in
pip-compile --upgrade --verbose --rebuild -o requirements/pip-tools.txt requirements/pip-tools.in
pip install -qr requirements/pip.txt
pip install -qr requirements/pip-tools.txt
pip-compile --upgrade --verbose --rebuild -o requirements/base.txt requirements/base.in
pip-compile --upgrade --verbose --rebuild -o requirements/test.txt requirements/test.in
pip-compile --upgrade --verbose --rebuild -o requirements/doc.txt requirements/doc.in
pip-compile --upgrade --verbose --rebuild -o requirements/quality.txt requirements/quality.in
pip-compile --upgrade --verbose --rebuild -o requirements/ci.txt requirements/ci.in
pip-compile --upgrade --verbose --rebuild -o requirements/dev.txt requirements/dev.in
# Let tox control the Django version for tests
sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp
mv requirements/test.tmp requirements/test.txt
upgrade: ## update the uv.lock file with the latest packages satisfying pyproject.toml
uv run --with edx-lint edx_lint write_uv_constraints pyproject.toml
uv lock --upgrade

quality: ## check coding style with pycodestyle and pylint

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

make quality is not working as expected.

tox -e quality
uv sync --group quality
touch tests/__init__.py
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run pylint src/xss_utils tests tests_utils manage.py
rm tests/__init__.py
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run pycodestyle src/xss_utils tests manage.py
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run pydocstyle src/xss_utils tests manage.py
DJANGO_SETTINGS_MODULE=test_settings PYTHONPATH=$(CURDIR) uv run isort --check-only --diff tests tests_utils src/xss_utils manage.py test_settings.py
$(MAKE) selfcheck

requirements: ## install development environment requirements

pip install -qr requirements/pip.txt
pip install -qr requirements/pip-tools.txt
pip-sync requirements/dev.txt requirements/private.*
uv sync --group dev

test: clean ## run tests in the current virtualenv
pytest
Expand All @@ -72,8 +68,8 @@ diff_cover: test ## find diff lines that need test coverage
diff-cover coverage.xml

test-all: ## run tests on every supported Python/Django combination
tox -e quality
tox
uv run tox -e quality
uv run tox

validate: quality test ## run tests and quality checks

Expand Down
25 changes: 4 additions & 21 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,16 @@
All configuration values have a default; values that are commented out
serve to show the default.
"""
import io
import os
import re
import sys
from datetime import datetime
from importlib.metadata import version as get_version
from subprocess import check_call

from django import setup as django_setup
from django.conf import settings


def get_version(*file_paths):
"""
Extract the version string from the file at the given relative path fragments.
"""
filename = os.path.join(os.path.dirname(__file__), *file_paths)
version_file = open(filename).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.')


REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(REPO_ROOT)

VERSION = get_version('../xss_utils', '__init__.py')
VERSION = get_version('xss-utils')

# Configure Django for autodoc usage
settings.configure()
Expand Down Expand Up @@ -520,8 +503,8 @@ def on_init(app): # pylint: disable=unused-argument
# If we are, assemble the path manually
bin_path = os.path.abspath(os.path.join(sys.prefix, 'bin'))
apidoc_path = os.path.join(bin_path, apidoc_path)
check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'xss_utils'),
os.path.join(root_path, 'xss_utils/migrations')])
check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'src/xss_utils'),
os.path.join(root_path, 'src/xss_utils/migrations')])


def setup(app):
Expand Down
Loading