Repository navigation
feat: self-register TPA budget group sync into SOCIAL_AUTH_PIPELINE - #197
Conversation
There was a problem hiding this comment.
Pull request overview
This PR registers the TPA budget-group sync step during Django app startup, replacing the removed upstream pipeline extension hook.
Changes:
- Adds idempotent pipeline-step registration after Enterprise logistration.
- Invokes registration from
AppConfig.ready(). - Adds tests, bumps the package version, and updates the changelog.
Review finding: pipeline.py catches unexpected registration errors and only logs a warning, potentially silently disabling synchronization. Registration failures should be surfaced or narrowly handled.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Summary |
|---|---|
tests/test_channel_integrations/test_integrated_channel/test_pipeline.py |
Tests registration behavior, ordering, idempotency, no-op cases, and exception handling. |
channel_integrations/integrated_channel/pipeline.py |
Adds pipeline registration logic. |
channel_integrations/integrated_channel/apps.py |
Registers the step during app startup. |
channel_integrations/__init__.py |
Bumps the package version. |
CHANGELOG.rst |
Documents the feature and release change. |
Suppressed comments (1)
channel_integrations/integrated_channel/apps.py:27
- The added tests call
register_pipeline_step()directly but never exercise the newIntegratedChannelConfig.ready()wiring.test_settings.pydoes not defineSOCIAL_AUTH_PIPELINE, so Django startup takes the no-op path in this suite; a regression removing this call would still leave all ofTestRegisterPipelineStepgreen while the sync is absent from real logins. Add an AppConfig-level or startup-level test that verifiesready()registers the step after the enterprise pipeline is present.
register_pipeline_step()
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| `sync_tpa_budget_group` needs to run as a SOCIAL_AUTH_PIPELINE step, right after | ||
| `enterprise.tpa_pipeline.handle_enterprise_logistration`. Runs on every SSO login on the platform, | ||
| not just Skillsoft's, so this module carries the outermost safety layer: a waffle switch (default off) | ||
| and a broad try/except that never raises. See the "Development spec" in the ENT-12084 plan for the | ||
| rollout sequencing this switch is used for. |
There was a problem hiding this comment.
Honestly, I feel like this entire section is noise. It's implementation details, hard-coded module references, and rollout details that don't really help future engineers. It's also almost completely redundant with the step's docstring. Delete the whole paragraph.
| `sync_tpa_budget_group` needs to run as a SOCIAL_AUTH_PIPELINE step, right after | |
| `enterprise.tpa_pipeline.handle_enterprise_logistration`. Runs on every SSO login on the platform, | |
| not just Skillsoft's, so this module carries the outermost safety layer: a waffle switch (default off) | |
| and a broad try/except that never raises. See the "Development spec" in the ENT-12084 plan for the | |
| rollout sequencing this switch is used for. |
| def register_pipeline_step(): | ||
| """ | ||
| Insert `sync_tpa_budget_group` into settings.SOCIAL_AUTH_PIPELINE, right after | ||
| `handle_enterprise_logistration`, if it isn't there already. | ||
|
|
||
| This only ever mutates a plain in-memory settings list - no database access, no waffle checks | ||
| - so it's safe to run this early, including during management commands that run before | ||
| migrations (e.g. `migrate` itself). | ||
| """ |
There was a problem hiding this comment.
Nit: The naming/docs of this function is not scalable and is potentially ambiguous. Just generalize it:
| def register_pipeline_step(): | |
| """ | |
| Insert `sync_tpa_budget_group` into settings.SOCIAL_AUTH_PIPELINE, right after | |
| `handle_enterprise_logistration`, if it isn't there already. | |
| This only ever mutates a plain in-memory settings list - no database access, no waffle checks | |
| - so it's safe to run this early, including during management commands that run before | |
| migrations (e.g. `migrate` itself). | |
| """ | |
| def register_pipeline_steps(): | |
| """ | |
| Register all `channel_integrations` pipeline steps. | |
| """ |
| ENABLE_TPA_ORG_GROUP_LOGIN_SYNC_SWITCH = 'enable_tpa_org_group_login_sync' | ||
|
|
||
| PIPELINE_STEP_PATH = 'channel_integrations.integrated_channel.pipeline.sync_tpa_budget_group' | ||
| ENTERPRISE_LOGISTRATION_STEP_PATH = 'enterprise.tpa_pipeline.handle_enterprise_logistration' |
There was a problem hiding this comment.
❌ This cross-dependency to another plugin should be done carefully. Soon I will remove edx-enterprise from edx-platform base requirements, and this injection will begin to fail on non-2U platform environments.
You probably should key off a non-enterprise step instead, if possible.
There was a problem hiding this comment.
sync_tpa_budget_group depends on the EnterpriseCustomerUser link that handle_enterprise_logistration creates. Moved the import to inside sync_tpa_budget_group so that importing this module doesn't require enterprise to be installed.
| * feat: self-register ``pipeline.sync_tpa_budget_group`` into ``SOCIAL_AUTH_PIPELINE`` from | ||
| ``IntegratedChannelConfig.ready()``, right after ``enterprise.tpa_pipeline.handle_enterprise_logistration``. | ||
| edx-platform no longer exposes a generic extension point for enterprise pipeline steps (that | ||
| moved into edx-enterprise's own entry_points-based plugin_settings, which only inserts its own | ||
| step) and this repo isn't registered as an entry_points plugin itself, so there's no external | ||
| hook left to append to - this makes installing the package enough to wire up the login-time | ||
| sync, no edx-platform change required. |
There was a problem hiding this comment.
❌ Excessive noise in this message, and it doesn't even explain what the new pipeline step actually does. Something like this would be better:
| * feat: self-register ``pipeline.sync_tpa_budget_group`` into ``SOCIAL_AUTH_PIPELINE`` from | |
| ``IntegratedChannelConfig.ready()``, right after ``enterprise.tpa_pipeline.handle_enterprise_logistration``. | |
| edx-platform no longer exposes a generic extension point for enterprise pipeline steps (that | |
| moved into edx-enterprise's own entry_points-based plugin_settings, which only inserts its own | |
| step) and this repo isn't registered as an entry_points plugin itself, so there's no external | |
| hook left to append to - this makes installing the package enough to wire up the login-time | |
| sync, no edx-platform change required. | |
| * feat: Register the new ``sync_tpa_budget_group`` pipeline step which syncs the logging-in | |
| learner's EnterpriseGroup membership to the Learner Credit budget group mapped to the | |
| EnterpriseCustomer for this active pipeline. |
| Register signal handlers, configure structured logging, and register the TPA org budget | ||
| group sync step in SOCIAL_AUTH_PIPELINE. |
There was a problem hiding this comment.
| Register signal handlers, configure structured logging, and register the TPA org budget | |
| group sync step in SOCIAL_AUTH_PIPELINE. | |
| Register signal handlers, configure structured logging, and register social-auth pipeline steps. |
| @@ -1,11 +1,11 @@ | |||
| """ | |||
| Third-party-auth pipeline entry point for TPA org allowlist -> Learner Credit budget group sync. | |||
There was a problem hiding this comment.
| Third-party-auth pipeline entry point for TPA org allowlist -> Learner Credit budget group sync. | |
| Third-party-auth pipeline steps. |
pwnage101
left a comment
There was a problem hiding this comment.
Done reviewing, just some minor docs issues and one legitimate dependency on the enterprise plugin which we soon won't tolerate.
2c5ae4e to
55fb531
Compare
55fb531 to
9c9142e
Compare
The CHANGELOG.rst restoration commit recreated the file from a fixed snapshot frozen at 0.1.62. Rebasing onto current main (now at 0.1.69) exposed that the restoration doesn't track main's ongoing history -- it's a one-time static blob, not a merge. Replaced with main's actual current content, restoring the 7 missing entries (0.1.63-0.1.69) between Unreleased and 0.1.62, including PR #197's entry that collided with this rebase. Co-Authored-By: Claude Sonnet 5 <[email protected]>
…e) (#180) * 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 <[email protected]> * 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 to install uv and run tests via `uv run tox`. Point .readthedocs.yaml at uv too, since it referenced the now-deleted requirements/doc.txt. Co-Authored-By: Claude Sonnet 5 <[email protected]> * 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 <[email protected]> * fix: correct release tag format and 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. 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 <[email protected]> * 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`. Co-Authored-By: Claude Sonnet 5 <[email protected]> * 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 <[email protected]> * fix: prevent premature 1.0 bump and pin pypa/gh-action-pypi-publish - major_on_zero defaults to True in python-semantic-release, which would auto-bump this still-0.x repo straight to 1.0.0 on the first breaking-change commit. 1.0.0 is supposed to be a deliberate "first stable release" signal, not an accidental side effect -- keep 0.x bumps within 0.x until that's an intentional decision (allow_zero_version stays true so 0.x remains valid). - @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. Co-Authored-By: Claude Sonnet 5 <[email protected]> * refactor: adopt src-layout, matching openedx/sample-plugin Moves channel_integrations/ to src/channel_integrations/, 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 targets in the quality env - Makefile: prefix src/ onto all 5 localization targets (extract, compile, detect_changed, pull, dummy); atlas pull's local destination path updated, remote Transifex identifier left unchanged - test_settings.py: LOCALE_PATHS root() call updated - docs/conf.py: sphinx-apidoc call updated to point at src/channel_integrations - MANIFEST.in: recursive-include path updated Verified: uv sync (no lock drift), full django42 test run (893 passed), quality and pii_check envs pass, uv build --wheel + twine check pass. The docs tox env fails both before and after this change (a pre-existing sphinx/autodoc ValueError in object_description, unrelated to src-layout and unrelated to this repo's file layout) -- confirmed by reproducing the identical failure on the pre-migration commit via git stash. * 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: 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 django42 test run (893 passed), quality env passes. docs env still fails on the same pre-existing sphinx/autodoc ValueError already documented in this repo's src-layout commit (confirmed via git stash at that time it predates and is unrelated to any of our changes) -- docs isn't part of the CI matrix. * fix: pattern-gap audit round 2 (uv run wrapping, dynamic readme, tox envlist, readthedocs os) Cross-checked against review comments/fixes from openedx-ledger#242, edx-enterprise-subsidy-client#222, enterprise-access#1015, and enterprise-subsidy#441. - Wrap remaining bare tool invocations in the Makefile (coverage erase, pytest --cov-report html, PYTHONPATH pytest, 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. - Match the py{311}-django{42,52} brace convention in the tox envlist. - Bump Read the Docs build image to ubuntu-26.04. * 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 and dropped the ifeq(OPENEDX_ATLAS_PULL) conditional line, breaking 'make selfcheck' and CI. * fix: restore CHANGELOG.rst and re-enable changelog generation A previous migration pass deleted CHANGELOG.rst and disabled python-semantic-release's changelog generation (changelog: "false" in release.yml). Neither was required by the tooling-modernization ticket, and deleting the file discarded real historical release notes. - Restore CHANGELOG.rst from the commit before it was deleted - Add the .. changelog-insertion-marker as the first line so PSR's "update" mode inserts new release notes above existing history - Remove changelog: "false" from release.yml's PSR step - Add [tool.semantic_release.changelog] config to pyproject.toml, pointing PSR at the restored CHANGELOG.rst in RST format - tag_format was already correctly set to "{version}" matching this repo's bare version tags (e.g. 0.1.65), no change needed there Co-Authored-By: Claude Sonnet 5 <[email protected]> * docs: remove stale version-bump/changelog checklist items from PR template python-semantic-release now handles versioning and changelog generation automatically, so asking PR authors to manually bump the version or add a changelog record is obsolete and was left over from before that automation was wired up. Co-Authored-By: Claude Sonnet 5 <[email protected]> * 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 doc8/sphinx/pylint/pycodestyle/ code_annotations commands from tox.ini's docs/quality/pii_check 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/ docs 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: 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: conform release.yml action pins to sample-plugin gold standard release.yml's actions/checkout, python-semantic-release, upload-artifact, and download-artifact pins were all stale relative to openedx/sample-plugin's release.yml, this org's designated reference for this file. Also re-pins pypa/gh-action-pypi-publish to a SHA (dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2), reversing an earlier revert to @release/v1 that was made on this PR at salman2013's request after a prior SHA-pin caused issues. That guidance has since been superseded: salman2013 has since directed (on a sibling repo's PR) to match sample-plugin exactly, which now itself SHA-pins gh-action-pypi-publish at v1.14.2. Every pin below was independently verified against the GitHub API before use (commit resolves, and matches its claimed tag, dereferencing through the tag object for annotated tags): - actions/checkout -> 3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - python-semantic-release/python-semantic-release -> 9a026e9303981c866c3425723009becb2437c757 # v10.6.2 - actions/upload-artifact -> 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - actions/download-artifact -> 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - pypa/gh-action-pypi-publish -> dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 Co-Authored-By: Claude Sonnet 5 <[email protected]> * fix: reconcile CHANGELOG.rst with main after rebase The CHANGELOG.rst restoration commit recreated the file from a fixed snapshot frozen at 0.1.62. Rebasing onto current main (now at 0.1.69) exposed that the restoration doesn't track main's ongoing history -- it's a one-time static blob, not a merge. Replaced with main's actual current content, restoring the 7 missing entries (0.1.63-0.1.69) between Unreleased and 0.1.62, including PR #197's entry that collided with this rebase. Co-Authored-By: Claude Sonnet 5 <[email protected]> * fix: use v-prefixed tag_format for release tag consistency * 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 (docs, quality, pii_check), restoring the pre-migration one-liner tox -e <env> delegation. Co-Authored-By: Claude Sonnet 5 <[email protected]> * 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 <[email protected]> * fix: bump python-semantic-release pin to v10.7.0, add doc group to dev, add signxml dep - Bump python-semantic-release action pin from v10.6.2 to v10.7.0 (b700dbeb1f431a0fcc30a79f3f0869407fe07278), matching openedx/sample-plugin's current live pin. - Fold {include-group = "doc"} into the dev dependency-group so `make requirements` (uv sync --group dev) provisions doc8/Sphinx, matching the pattern already correct in openedx/edx-enterprise's pyproject.toml. - Add signxml (pulled in transitively via the SAML assertion generator added on main in #201/#206) to [project.dependencies], since this migration replaced requirements/base.in with pyproject.toml as the source of truth for runtime dependencies and base.in's new direct dependency needs an equivalent entry there. Re-lock uv.lock accordingly. Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Irfan Ahmad <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]>
Description
The edx-platform hook this repo relied on for inserting extra SOCIAL_AUTH_PIPELINE steps (
insert_enterprise_pipeline_elements) has been removed upstream. Enterprise steps are now inserted by edx-enterprise's own entry_points-basedplugin_settings(), which only inserts its own step and has no extension point for a sibling plugin. This repo isn't registered as an entry_points plugin either, so there's no external hook left to use.This PR closes the gap from inside this repo:
IntegratedChannelConfig.ready()now calls a newregister_pipeline_step(), which insertspipeline.sync_tpa_budget_groupintosettings.SOCIAL_AUTH_PIPELINEdirectly, right afterhandle_enterprise_logistration. This is safe because Django fully executes all settings modules - including edx-enterprise'splugin_settings()- before callingready(), so that step is guaranteed to already be present. Only mutates an in-memory list, no DB access, so it's safe even before migrations run.Safety properties (unchanged)
Idempotent, and never raises: no-ops if
SOCIAL_AUTH_PIPELINEis undefined, our step is already registered, orhandle_enterprise_logistrationis missing. The actual sync logic is unchanged - still behind theenable_tpa_org_group_login_syncwaffle switch (default off), still exception-isolated. Only how the step gets registered changed, not what it does.Testing instructions
New tests in
test_pipeline.py::TestRegisterPipelineStepcover insertion position, idempotency, both no-op cases, and exception safety. Fulltest_integrated_channelsuite (352 tests) passes; no migrations required.Merge checklist:
Check off if complete or not applicable: