Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d2dbcf6
refactor: move package to src/ layout
irfanuddinahmad Jul 28, 2026
b0979fe
feat: consolidate package metadata into pyproject.toml
irfanuddinahmad Jul 28, 2026
b9b74d1
feat: switch dependency management from pip-compile to uv
irfanuddinahmad Jul 28, 2026
82fdd52
feat: add semantic-release and OIDC PyPI publish workflow
irfanuddinahmad Jul 28, 2026
ce9d35a
feat: enable semantic-release changelog generation
Jul 30, 2026
aa9c98d
fix: remove obsolete version-bump/changelog checklist items from PR t…
Jul 30, 2026
75decba
fix: restore __version__ via importlib.metadata in __init__.py
Jul 30, 2026
0be8a90
Merge remote-tracking branch 'origin/main' into irfanuddinahmad/moder…
Aug 11, 2026
5e69b0c
fix: disable python-semantic-release changelog generation
Aug 19, 2026
8b54ed0
Merge branch 'main' into irfanuddinahmad/modernize-python-tooling
Aug 20, 2026
0b69beb
fix: create GitHub release with assets attached, not after publish
irfanuddinahmad Aug 27, 2026
6999fa9
fix: pin release.yml actions to sample-plugin gold-standard SHAs
irfanuddinahmad Sep 2, 2026
603dc75
fix: drop unneeded fetch-depth: 0 from ci.yml checkout
irfanuddinahmad Sep 2, 2026
4f2f8b9
fix: add missing contents:read permission to publish_to_pypi job
irfanuddinahmad Sep 2, 2026
e0c95d0
fix: align importlib.metadata.version aliasing with sample-plugin
irfanuddinahmad Sep 2, 2026
c301249
fix: remove unnecessary try/except around __version__ (not present pr…
irfanuddinahmad Sep 9, 2026
2a43458
fix: remove fail-fast: false (not present pre-migration)
irfanuddinahmad Sep 9, 2026
d49c07a
fix: use static 'tests' job name in ci.yml to match pre-migration checks
irfanuddinahmad Sep 10, 2026
d8ef543
fix: remove unnecessary try/except around VERSION in docs/conf.py
irfanuddinahmad Sep 10, 2026
fa08b19
Merge remote-tracking branch 'origin/main' into irfanuddinahmad/moder…
irfanuddinahmad Sep 23, 2026
bf2e1c5
fix: use secrets.GITHUB_TOKEN instead of custom org secret for releas…
irfanuddinahmad Sep 24, 2026
1dfee33
fix: exclude tests packages from wheel
irfanuddinahmad Sep 24, 2026
f5e9657
fix: prefix i18n and atlas Makefile targets with uv run
irfanuddinahmad Sep 24, 2026
1379f42
fix: drop uv run from Makefile per feanil's review feedback
irfanuddinahmad Sep 28, 2026
8ef37d9
Merge remote-tracking branch 'upstream/main' into irfanuddinahmad/mod…
irfanuddinahmad Sep 28, 2026
93e0a8b
fix: add sphinx-jsonschema to doc dependency group
irfanuddinahmad Sep 28, 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
11 changes: 0 additions & 11 deletions .coveragerc

This file was deleted.

2 changes: 0 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@

**Merge checklist:**
Check off if complete *or* not applicable:
- [ ] Version bumped
- [ ] Changelog record added
- [ ] Documentation updated (not only docstrings)
- [ ] Fixup commits are squashed away
- [ ] Unit tests added/updated
Expand Down
20 changes: 9 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Python CI

on:
push:
Comment thread
irfanuddinahmad marked this conversation as resolved.
Resolved
branches: [main]
workflow_call:
pull_request:
branches:
- "**"
Expand All @@ -11,28 +10,27 @@ jobs:
run_tests:
name: tests
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [ubuntu-latest]
python-version: ["3.12"]
toxenv: [quality, docs, pii_check, django52]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: setup python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0

- 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
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'
Expand Down
29 changes: 0 additions & 29 deletions .github/workflows/pypi-publish.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: [main]

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 == 'main'
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.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 == 'main' && 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: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ docs/modules.rst
docs/openedx_authz.rst
docs/openedx_authz.*.rst

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

# Persistent database files
*.sqlite3
*.db
Expand Down
11 changes: 4 additions & 7 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ build:

python:
install:
- requirements: requirements/doc.txt

# This will pip install this repo into the python environment
# if you are using this in a repo that is not pip installable
# then you should remove the following two lines.
- method: pip
path: .
- method: uv
command: sync
groups:
- doc
6 changes: 2 additions & 4 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
include CHANGELOG.rst
include LICENSE.txt
include LICENSE
include README.rst
include requirements/base.in
include requirements/constraints.txt
recursive-include openedx_authz *.html *.png *.gif *.js *.css *.jpg *.jpeg *.svg *.conf *.policy *.yaml *.yml *.json
recursive-include src *.html *.png *.gif *.js *.css *.jpg *.jpeg *.svg *.conf *.policy *.yaml *.yml *.json
53 changes: 14 additions & 39 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: clean clean_tox compile_translations coverage diff_cover docs dummy_translations \
extract_translations fake_translations help pii_check pull_translations \
quality requirements selfcheck test test-all upgrade compile-requirements validate install_transifex_client
quality requirements selfcheck test test-all upgrade validate install_transifex_client

.DEFAULT_GOAL := help

Expand Down Expand Up @@ -32,47 +32,22 @@ docs: ## generate Sphinx HTML documentation, including API docs
tox -e docs
$(BROWSER)docs/_build/html/index.html

# Define PIP_COMPILE_OPTS=-v to get more information during make upgrade.
PIP_COMPILE = pip-compile --rebuild $(PIP_COMPILE_OPTS)

compile-requirements: export CUSTOM_COMPILE_COMMAND=make upgrade
compile-requirements: ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip install -qr requirements/pip-tools.txt
pip install -qr requirements/pip.txt
# Make sure to compile files after any other files they include!
$(PIP_COMPILE) --allow-unsafe -o requirements/pip.txt requirements/pip.in
$(PIP_COMPILE) -o requirements/pip-tools.txt requirements/pip-tools.in
pip install -qr requirements/pip.txt
pip install -qr requirements/pip-tools.txt
$(PIP_COMPILE) -o requirements/base.txt requirements/base.in
$(PIP_COMPILE) -o requirements/test.txt requirements/test.in
$(PIP_COMPILE) -o requirements/doc.txt requirements/doc.in
$(PIP_COMPILE) -o requirements/quality.txt requirements/quality.in
$(PIP_COMPILE) -o requirements/ci.txt requirements/ci.in
$(PIP_COMPILE) -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 requirements/*.txt files with the latest packages satisfying requirements/*.in
make compile-requirements PIP_COMPILE_OPTS="--upgrade"
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
tox -e quality

format: ## format code with black and isort. Enable ruff to fix E (pycodestyle) and I (isort) issues
ruff format openedx_authz tests manage.py setup.py
ruff check --fix openedx_authz tests manage.py setup.py
ruff format src/openedx_authz tests manage.py
ruff check --fix src/openedx_authz tests manage.py

pii_check: ## check for PII annotations on all Django models
tox -e pii_check

piptools: ## install pinned version of pip-compile and pip-sync
pip install -r requirements/pip.txt
pip install -r requirements/pip-tools.txt

requirements: clean_tox piptools ## install development environment requirements
pip-sync -q requirements/dev.txt requirements/private.*
requirements: ## install development environment requirements
uv sync --group dev

test: clean ## run tests in the current virtualenv
pytest
Expand All @@ -93,29 +68,29 @@ selfcheck: ## check that the Makefile is well-formed

extract_translations: ## extract strings to be translated, outputting .mo files
rm -rf docs/_build
cd openedx_authz && i18n_tool extract --no-segment
cd src/openedx_authz && i18n_tool extract --no-segment

compile_translations: ## compile translation files, outputting .po files for each supported language
cd openedx_authz && i18n_tool generate
cd src/openedx_authz && i18n_tool generate

detect_changed_source_translations:
cd openedx_authz && i18n_tool changed
cd src/openedx_authz && i18n_tool changed

ifeq ($(OPENEDX_ATLAS_PULL),)
pull_translations: ## Pull translations from Transifex
tx pull -t -a -f --mode reviewed --minimum-perc=1
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
find openedx_authz/conf/locale -mindepth 1 -maxdepth 1 -type d -exec rm -r {} \;
atlas pull $(OPENEDX_ATLAS_ARGS) translations/openedx-authz/openedx_authz/conf/locale:openedx_authz/conf/locale
find src/openedx_authz/conf/locale -mindepth 1 -maxdepth 1 -type d -exec rm -r {} \;
atlas pull $(OPENEDX_ATLAS_ARGS) translations/openedx-authz/openedx_authz/conf/locale:src/openedx_authz/conf/locale
python manage.py compilemessages

@echo "Translations have been pulled via Atlas and compiled."
endif

dummy_translations: ## generate dummy translation (.po) files
cd openedx_authz && i18n_tool dummy
cd src/openedx_authz && i18n_tool dummy

build_dummy_translations: extract_translations dummy_translations compile_translations ## generate and compile dummy translation files

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Please do not report security issues in public. Please email security@openedx.or
:alt: Supported Python versions

.. |license-badge| image:: https://img.shields.io/github/license/openedx/openedx-authz.svg
:target: https://github.com/openedx/openedx-authz/blob/main/LICENSE.txt
:target: https://github.com/openedx/openedx-authz/blob/main/LICENSE
:alt: License

.. |status-badge| image:: https://img.shields.io/badge/Status-Experimental-yellow
30 changes: 8 additions & 22 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,17 @@
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


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

VERSION = get_version("openedx-authz")

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(REPO_ROOT)
sys.path.insert(0, REPO_ROOT)
sys.path.insert(0, os.path.join(REPO_ROOT, "src"))

VERSION = get_version("../openedx_authz", "__init__.py")
# Configure Django for autodoc usage
os.environ["DJANGO_SETTINGS_MODULE"] = "openedx_authz.settings.test"
django_setup()
Expand Down Expand Up @@ -573,10 +559,10 @@ def on_init(app): # pylint: disable=unused-argument
apidoc_path,
"-o",
docs_path,
os.path.join(root_path, "openedx_authz"),
os.path.join(root_path, "openedx_authz/migrations"),
os.path.join(root_path, "openedx_authz/schema"),
os.path.join(root_path, "openedx_authz/tests"),
os.path.join(root_path, "src/openedx_authz"),
os.path.join(root_path, "src/openedx_authz/migrations"),
os.path.join(root_path, "src/openedx_authz/schema"),
os.path.join(root_path, "src/openedx_authz/tests"),
],
env=env
)
Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/0017-static-authorization-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Decision
1. Schema format and boundary
=============================

The authz schema is a versioned YAML format for static permissions, permission categories, roles, and changes to existing roles. Every file declares ``schema_version`` and ``priority``, which apply to all definitions in that file. Priority is used when role extensions conflict. Open edX publishes a :download:`machine-readable JSON Schema <../../openedx_authz/schema/authz-schema-v1.json>` for this format so that editors, CI, and the compiler all apply the same field and validation rules.
The authz schema is a versioned YAML format for static permissions, permission categories, roles, and changes to existing roles. Every file declares ``schema_version`` and ``priority``, which apply to all definitions in that file. Priority is used when role extensions conflict. Open edX publishes a :download:`machine-readable JSON Schema <../../src/openedx_authz/schema/authz-schema-v1.json>` for this format so that editors, CI, and the compiler all apply the same field and validation rules.

The existing static role and permission definitions in Python modules and ``authz.policy`` will move into the schema. Once this migration is complete, the schema becomes the source for static definitions, so developers add a new role or permission there without duplicating it in Python constants or policy files.

Expand Down
Loading
Loading