Skip to content

feat: modernize Python tooling (pyproject.toml + uv) - #1015

Open
irfanuddinahmad wants to merge 13 commits into
mainfrom
irfanuddinahmad/uv-migration
Open

feat: modernize Python tooling (pyproject.toml + uv)#1015
irfanuddinahmad wants to merge 13 commits into
mainfrom
irfanuddinahmad/uv-migration

Conversation

@irfanuddinahmad

Copy link
Copy Markdown
Contributor

Summary

Modernizes this repo's Python tooling per the org-wide standardization tracked in openedx/public-engineering#513 (and the parent openedx/public-engineering#506):

  • Consolidate tool config into pyproject.toml, replacing setup.cfg. This is a deployed Django service, not a published package, so no [project]/packaging metadata or semantic-release is added.
  • Switch dependency management from pip-compile to uv: requirements/*.in+*.txt are replaced by PEP 735 [dependency-groups] + a single uv.lock. Since there's no [project] table, runtime deps live in an explicit base group that test/quality/doc/production/dev all compose via {include-group = "base"}.
  • Update tox.ini/Makefile to use tox-uv's uv-venv-lock-runner, and switch CI (including upgrade-python-requirements.yml, which calls make upgrade directly rather than the shared reusable workflow) to install uv and run via uv sync/uv run tox.

Part of openedx/public-engineering#513.

Test plan

  • uv lock resolves cleanly (204 packages)
  • Could not fully verify uv sync/uv run tox on my machine (no libmysqlclient/pkg-config locally to build mysqlclient from source) -- CI's ubuntu-latest runners have these preinstalled, so this needs verifying there.

🤖 Generated with Claude Code

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jul 9, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @irfanuddinahmad!

This repository is currently maintained by @openedx/2u-enterprise.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@openedx-webhooks openedx-webhooks added the core contributor PR author is a Core Contributor (who may or may not have write access to this repo). label Jul 9, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Jul 9, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Jul 13, 2026
@mphilbrick211
mphilbrick211 requested a review from a team July 13, 2026 15:54
Irfan Ahmad and others added 7 commits July 15, 2026 09:13
Move isort/pytest settings from setup.cfg/pytest.ini into pyproject.toml.
enterprise-access is a deployed Django service, not a published package, so
no [build-system]/packaging metadata is added here. Also drop the now-dead
[pytest] section from tox.ini (pytest.ini took precedence over it already).
flake8 config from setup.cfg is dropped since flake8 isn't invoked anywhere
in this repo's Makefile or CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace requirements/*.in + *.txt with PEP 735 dependency-groups in
pyproject.toml and a single uv.lock. Since this repo has no [project]
packaging metadata, runtime deps live in an explicit `base` group that
test/quality/doc/production/dev all compose via {include-group = "base"}
(quality and doc compose {include-group = "test"} directly, mirroring
this repo's own requirements/quality.in and doc.in which built on top of
test.txt rather than base.txt). Wire the existing tox.ini up to
uv-venv-lock-runner with per-env dependency_groups, update the Makefile's
requirements targets to their uv equivalents, and switch CI to install uv
and run tests via `uv run tox`. Also add an uv install step to the
scheduled requirements-upgrade workflow so `make upgrade` keeps working.

Note: could not fully verify `uv sync`/`uv run tox` locally on this
machine (no libmysqlclient/pkg-config for building mysqlclient from
source) -- `uv lock` resolves cleanly; full sync needs verifying via CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The comment said "Disable pylint import error because we don't install
django-debug-toolbar for CI build" but the actual # pylint: disable
pragma was never added. This was previously masked because the old CI
ran the quality checks inside the broader dev-dependency environment
(which does include django-debug-toolbar); the new tox quality env is
correctly scoped to just the quality group, which doesn't include it,
exposing the missing pragma.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
(and mysqlclient) into every target. Verified: `uv sync --group ci`
previously failed trying to build mysqlclient "because
enterprise-access:dev depends on mysqlclient"; with default-groups = []
it now installs only the ci group's own tools.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This migration's ci.yml is the first time pylint actually runs in CI for
this repo -- the old ci.yml called `make style isort_check pii_check
check_keywords` directly, bypassing tox.ini's pre-existing (but dormant)
`quality` env entirely. Turning it on surfaced real, previously-invisible
findings across the app:

- Unused imports (customer_billing.py, test_api.py, test_checkout_intent_views.py,
  test_enrollment_deadline_strategies.py).
- Deliberate lazy imports used to avoid circular imports (utils.py,
  enrollment_deadline_strategies.py) that were missing their
  pylint-disable annotations.
- A redundant `pass` after a logged except block.
- A missing docstring (stripe_event_handlers.py).
- `staff_user = self.create_user(...)` in two tests: create_user() has no
  return value (it sets self.user as a side effect), so this always
  assigned None; set_jwt_cookie's user=None already falls back to
  self.user, so behavior is unchanged, but the dead assignment is removed.
- A mutable default argument (`roles_and_contexts=[]`) in test_utils.
- cyclic-import (R0401) between utils.py and enrollment_deadline_strategies.py:
  the two modules are legitimately mutually dependent and already broken
  via deferred imports, so disabled via pylintrc_tweaks like the other
  intentional suppressions already there.

Verified: pylint/pycodestyle/isort all pass; ran the 4 touched test
modules directly (136 passed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… gate

Same root cause as the preceding pylint fix: pydocstyle runs immediately
after pylint in tox.ini's quality env, which this migration is the first
to actually wire into CI. ~40 pre-existing findings (missing docstrings,
non-imperative wording, D205/D400/D415 summary-line formatting) across
utils.py, tasks.py, cache_utils.py, celery.py, and __init__.py were never
previously checked. Purely docstring wording/formatting -- no behavior
changes. Verified: pydocstyle/pylint/pycodestyle/isort all pass; full
test suite (1243 tests) passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The uv migration's unpinned "stripe" dependency resolved to the latest
15.3.0 at lock time, silently upgrading from the 14.1.0 that master's
pip-compiled requirements/test.txt pins. stripe 15.x changed StripeObject's
attribute/item access behavior in a way that breaks this app's `.get()`
calls on nested event payloads -- confirmed via the actual CI logs for
this PR's "tests" jobs (KeyError/AttributeError in stripe/_stripe_object.py),
and reproduced/fixed locally: 51 failing tests across customer_billing
and provisioning all pass again once stripe is constrained back to <15.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@irfanuddinahmad
irfanuddinahmad force-pushed the irfanuddinahmad/uv-migration branch from f3fd404 to de63148 Compare July 15, 2026 04:14
@farhan
farhan self-requested a review July 16, 2026 11:39
@farhan

farhan commented Jul 16, 2026

Copy link
Copy Markdown

Important

Written with the assistance of Claude Code, human-reviewed before posting.


Must Fix

  1. Makefile bare tool invocations
    make lint, make test, make style, make isort_check invoke pylint, pytest, pycodestyle, isort directly without uv run. These fail on a clean checkout where only uv is installed. Prefix each with uv run or delegate to uv run tox -e quality/unittests.

  2. Stale .coveragerc
    .coveragerc is still on disk and its contents have not been migrated to pyproject.toml — the file has no [tool.coverage] section at all. Migrate and delete the old file.

  3. Redundant actions/setup-python
    ci.yml uses both actions/setup-python and astral-sh/setup-uv in all jobs. Target state is setup-uv only (it handles Python installation via its python-version input). Remove actions/setup-python from all jobs.

  4. __version__ still in source
    enterprise_access/__init__.py still declares __version__ = '1.0.1', and it is actively imported in enterprise_access/cache_utils.py (from enterprise_access import __version__ as code_version). Remove __version__ from __init__.py and update cache_utils.py to use importlib.metadata.version("enterprise-access") or a hard-coded string.

  5. Static version mismatch
    pyproject.toml has version = "0.1.0" but master's __version__ is "1.0.1". Change to version = "1.0.1".


We should decide on:

  1. Branch protection check names
    Old CI produced a check named tests (3.12, pinned). New CI produces tests (py312-django42), tests (py312-django52), tests (translations). If the old name is a required status check in branch protection, all future PRs will be blocked. This cannot be verified without admin access — must be confirmed before merging. tests (3.12, pinned) is failing on this PR because of this reason I believe.

  2. src/ layout not adopted
    enterprise_access/ remains at repo root. We should decide whether to accept the non-adoption for this Django service.

  3. Minimal [project] metadata
    [project] contains only name, version, requires-python. Missing: description, readme, license, authors, classifiers, urls, dependencies. We needs to decided either its alright for a django service.


Informational

@claude says following, please verify:

This PR includes some non-migration source changes alongside the tooling migration: enterprise_access/utils.py has notable modifications, and several other source files (celery.py, tasks.py, urls.py, cache_utils.py, customer_billing/stripe_event_handlers.py) were touched beyond what the migration requires.

@irfanuddinahmad

Copy link
Copy Markdown
Contributor Author

Re: the informational note about non-migration source changes — verified this in detail:

This is isolated to enterprise-access; I checked all 10 repos in the modernization effort and none of the others have source-level lint fixes, only tooling/config changes.

Root cause: enterprise-access's old CI called make style isort_check pii_check check_keywords directly, which never actually ran pylint or pydocstyle — they were configured in tox.ini's quality env but that env was never wired into CI. This migration switches CI to uv run tox, which runs that env for the first time and surfaced pre-existing debt across the app: missing/non-imperative docstrings, two unused imports, one redundant pass after a logger call, one mutable default arg, and a few missing pylint-disable annotations. Without fixing it, CI would be permanently red on this PR since the debt predates it but the gate is newly active here.

Verified each change individually (diffed against master) — all behavior-preserving. Happy to point to the exact commits (1ab7bc1, 1ed7a71) if useful, each scoped separately from the tooling-migration commits with a full rationale in the message.

@farhan

farhan commented Jul 17, 2026

Copy link
Copy Markdown

tests are not running

@farhan farhan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the detailed explanation on the source-level lint fixes — the justification makes sense given the previously unexercised CI gate.

The Must Fix items from the earlier review still need to be addressed. I am pinning them inline now.

One change request is related to a file not touched by this PR but I believe should cover within this PR:

Change request:

.coveragerc (not in diff): The file still exists on disk with its original [run] section. I think its contents (branch, data_file, source, omit) should be migrated to [tool.coverage.run] in pyproject.toml and the file deleted. Currently pyproject.toml has no [tool.coverage] section at all. For reference, this has already been done in xblocks-extra — it includes [tool.coverage.run], [tool.coverage.report], and [tool.coverage.html] sections.

Comment thread Makefile

dev_requirements: ## sync to requirements for local development
pip-sync -q requirements/dev.txt
uv sync --group dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The dev_requirements, validation_requirements, etc. correctly use uv sync, but test (line 60), isort_check (line 68), style (line 74), and lint (line 77) still call pytest, isort, pycodestyle, pylint directly without uv run. On a clean checkout where only uv is installed, these targets will fail. Prefix each bare invocation with uv run.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed already fixed -- test, isort_check, style, lint all use uv run now.

Comment thread .github/workflows/ci.yml Outdated
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- name: setup python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

actions/setup-python is redundant here — astral-sh/setup-uv (line 28) already handles Python installation when its python-version input is set. Remove actions/setup-python from both the tests and quality jobs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — will fix. setup-python is redundant here since astral-sh/setup-uv handles Python via its python-version input.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed — see farhan@2c32f2b (note: commits are on a fork branch since I lack push access to the openedx repo; irfanuddinahmad can cherry-pick or the branch can be merged from the fork).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed already fixed (via a separate commit, 15fc242, pushed the same day) -- no actions/setup-python step remains in the tests job; astral-sh/setup-uv carries python-version and enable-cache: true.

Comment thread .github/workflows/ci.yml Outdated
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: setup python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above: actions/setup-python is redundant in the quality job too. astral-sh/setup-uv at line 58 handles Python via its python-version input. Remove this step.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — will fix, same as the tests job.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed in the same commit as the tests job: farhan@2c32f2b

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed already fixed (via a separate commit, 15fc242, pushed the same day) -- no actions/setup-python step remains in the quality job either; astral-sh/setup-uv carries python-version and enable-cache: true.

Comment thread .github/workflows/ci.yml
matrix:
python-version: ["3.12"]
django-version: ["pinned", "5.2"]
toxenv:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Generated with Claude Code

These matrix values change the check names from the old tests (3.12, pinned) format to tests (py312-django42), tests (py312-django52), tests (translations). If tests (3.12, pinned) is currently a required status check in branch protection, all future PRs will be blocked after this merges. Please confirm with a repo admin that branch protection rules are updated (or were never set to the old name) before merging. The currently failing tests (3.12, pinned) check on this PR likely confirms it was a required check.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

perhaps tests (3.12, pinned) are not running in the PR because of it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Checked directly via the GitHub API (repos/.../branches/main/protection and .../rulesets) -- this repo has no branch protection rules or rulesets configured, so there's no required status check that could be broken by the renamed checks. Confirmed, not just assumed.

Comment thread pyproject.toml Outdated
@@ -0,0 +1,150 @@
[project]
name = "enterprise-access"
version = "0.1.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Version mismatch: master's __version__ is 1.0.1 but pyproject.toml declares 0.1.0. Please change to version = "1.0.1" to stay consistent with the current release.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — will fix. version = "0.1.0" was a placeholder; will update to "1.0.1" to match the current release.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed with farhan@03bc3b6 (same commit also migrates .coveragerc contents to [tool.coverage.run] in pyproject.toml and deletes the file).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed fixed -- pyproject.toml now declares version = "1.0.1", matching master.

Comment thread enterprise_access/__init__.py Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

__version__ = '1.0.1' still exists. With python-semantic-release owning the version going forward, this should be removed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — will fix. __version__ will be removed from __init__.py.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed with farhan@10d8fe3

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed fixed -- enterprise_access/__init__.py no longer has __version__.

Comment thread enterprise_access/cache_utils.py Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Line 9 (from enterprise_access import __version__ as code_version) is outside this diff hunk and wasn't updated. Once __version__ is removed from __init__.py, this import will break at runtime. Replace with importlib.metadata.version("enterprise-access") — either inline or at module level:

from importlib.metadata import version as _get_version
code_version = _get_version("enterprise-access")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed — will fix. Since [tool.uv] package = false, this is not an installed package and importlib.metadata.version() would raise PackageNotFoundError at runtime. Will replace with a hardcoded code_version = '1.0.1' alongside a comment explaining why, consistent with how the version is now tracked in pyproject.toml.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed with farhan@10d8fe3 — replaced the import with a hardcoded code_version = '1.0.1' with a comment explaining why importlib.metadata is not viable here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed fixed -- replaced with a hardcoded code_version = '1.0.1' and an explanatory comment (since [tool.uv] package = false, importlib.metadata.version() isn't viable here as this isn't an installed package).

Addresses farhan's review on #1015:

- Makefile: prefix uv run on all bare tool invocations (test,
  isort_check, style, lint, plus coverage and isort which have the same
  gap) -- these fail on a clean checkout where only uv is installed
- ci.yml: remove actions/setup-python from both the tests and quality
  jobs (redundant once astral-sh/setup-uv gets python-version) and add
  enable-cache/python-version to setup-uv
- enterprise_access/__init__.py: remove __version__ -- this repo is a
  deployed service (pyproject.toml's [tool.uv] package = false), not an
  installed package, and has no python-semantic-release/release.yml, so
  a hardcoded version string here was just stale (0.1.0 vs master's 1.0.1)
- enterprise_access/cache_utils.py: replace the __version__ import
  (which would now break) with a hardcoded code_version constant --
  importlib.metadata.version() doesn't work here since package = false
  means this project is never actually installed with dist-info metadata
- pyproject.toml: sync version to 1.0.1 to match master's actual
  __version__; migrate .coveragerc into [tool.coverage.run] and delete
  the old file

Branch protection concern (renamed CI check names) is moot: verified via
gh api that openedx/enterprise-access's main branch has no branch
protection rules or rulesets configured at all, so there's no required
status check to break.

Verified: pyproject.toml parses, both edited .py files compile, no other
callers of the removed __version__ import (grepped the whole repo). Could
not run the full tox matrix locally -- same mysqlclient build environment
limitation already documented in the original PR.
@irfanuddinahmad

Copy link
Copy Markdown
Contributor Author

Addressed the Must-Fix items (Makefile `uv run` prefixes, `.coveragerc` migration, version sync to 1.0.1, `version`/`cache_utils.py` removal) and the redundant `actions/setup-python` steps.

On the branch protection concern: checked via `gh api repos/openedx/enterprise-access/branches/main/protection` and `.../rulesets` — both return empty/404. There's no branch protection or ruleset configured on `main` at all, so the renamed CI check names can't break anything here.

One thing worth a second look: `cache_utils.py`'s `code_version` can't use `importlib.metadata.version()` since this repo's `[tool.uv] package = false` means it's never actually installed with dist-info metadata — went with a hardcoded string synced to the new `1.0.1`, with a comment explaining why (and that it needs manual bumping going forward, since there's no semantic-release here).

@farhan

farhan commented Jul 27, 2026

Copy link
Copy Markdown

Addressed all review comments. Since I do not have push access to the openedx repo, the fix commits are on a fork branch at https://github.com/farhan/enterprise-access/tree/irfanuddinahmad/uv-migration@irfanuddinahmad please cherry-pick or apply the 4 commits from there.

Summary of fixes:

  • Removed actions/setup-python from both tests and quality CI jobs (redundant with astral-sh/setup-uv): 2c32f2b
  • Added uv run prefix to all bare pytest/isort/pycodestyle/pylint calls in Makefile: 3b4cd69
  • Fixed version from 0.1.0 to 1.0.1 in pyproject.toml; migrated .coveragerc to [tool.coverage.run] in pyproject.toml and deleted the file: 03bc3b6
  • Removed __version__ from enterprise_access/__init__.py; replaced the from enterprise_access import __version__ as code_version import in cache_utils.py with a hardcoded code_version = '1.0.1' (importlib.metadata is not viable since package = false): 10d8fe3

The branch protection concern (check name change from tests (3.12, pinned) to tests (py312-django*)) is not a code change — a repo admin needs to update branch protection rules before merging.

username='a-staff-user', password='password', is_staff=True,
)
self.set_jwt_cookie([{
'system_wide_role': SYSTEM_ENTERPRISE_LEARNER_ROLE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why are we doing this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a behavior-preserving cleanup -- create_user() doesn't return a value (it sets self.user internally), so staff_user was always None; set_jwt_cookie(user=None) already falls back to self.user, which create_user() just set to the staff user. Dropping the dead staff_user = assignment and the redundant user=staff_user kwarg doesn't change behavior -- verified by tracing through test_utils/__init__.py.

username='a-staff-user', password='password', is_staff=True,
)
self.set_jwt_cookie([{
'system_wide_role': SYSTEM_ENTERPRISE_LEARNER_ROLE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why are we doing this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a behavior-preserving cleanup -- create_user() doesn't return a value (it sets self.user internally), so staff_user was always None; set_jwt_cookie(user=None) already falls back to self.user, which create_user() just set to the staff user. Dropping the dead staff_user = assignment and the redundant user=staff_user kwarg doesn't change behavior -- verified by tracing through test_utils/__init__.py.

Comment thread pylintrc
superfluous-parens,
too-many-positional-arguments,
cyclic-import,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cyclic-import is globally disabled here. The PR correctly adds # pylint: disable=import-outside-toplevel at the specific import sites, but suppressing cyclic-import at the config level hides future circular import regressions across the whole codebase. Can you explain why a global disable is needed rather than per-file or per-module suppressions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- narrowed to per-file suppression at the actual import sites instead of a global disable, so this doesn't hide future cyclic-import regressions elsewhere in the codebase. Pylint's cyclic-import check excludes a module-to-module edge from its cycle graph when cyclic-import is disabled at the specific import statement's line, so # pylint: disable=cyclic-import at the three deferred-import sites in utils.py/enrollment_deadline_strategies.py is sufficient. Verified pylint enterprise_access test_utils manage.py (what CI's quality job runs) is clean with the global disable removed -- no other cyclic imports exist elsewhere in the codebase.

Comment thread pylintrc_tweaks Outdated
useless-option-value,
superfluous-parens,
too-many-positional-arguments,
cyclic-import,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The line after this one (+ cyclic-import,) globally disables cyclic-import — same concern as the comment in pylintrc. Suppressing this at the config level hides future circular import regressions across the whole codebase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- narrowed to per-file suppression at the actual import sites instead of a global disable, so this doesn't hide future cyclic-import regressions elsewhere in the codebase. Same fix as the pylintrc comment above.

Comment thread pyproject.toml
"pycodestyle",
"pydocstyle",
"twine",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

twine is a PyPI publishing tool. enterprise-access is a deployed Django service (package = false) that never publishes to PyPI. This looks like it was inherited from a shared template and is safe to remove.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- removed, confirmed no consumer in this repo (deployed service, package = false).


- name: Install uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

astral-sh/setup-uv installs Python via its python-version input, making actions/setup-python (a few steps above) redundant in this workflow. Worth removing setup-python here unless a later step explicitly needs the Python it provisions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- removed the redundant actions/setup-python step; astral-sh/setup-uv now provisions Python via its python-version input.

Comment thread .github/workflows/ci.yml

- name: Codecov
if: startsWith(matrix.toxenv, 'py312-django')
run: uv run codecov

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This condition does not pin to a specific python-version. If a second Python version is ever added to the matrix, Codecov uploads from both jobs for every Django variant. Suggest:

if: startsWith(matrix.toxenv, 'py312-django') && matrix.python-version == '3.12'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- added python-version as an explicit matrix dimension and the condition now also checks matrix.python-version == '3.12', to avoid double-uploads if a second Python version is added to the matrix later.

Irfan Ahmad and others added 5 commits July 28, 2026 16:04
enterprise-access is a deployed Django service (`[tool.uv] package =
false`), never published to PyPI, so twine (a PyPI-publishing tool) has
no consumer here -- it was inherited from a shared template. Removing
it also drops its PyPI-publish-only transitive deps (rich, keyring,
secretstorage, jeepney, jaraco-*, markdown-it-py, mdurl,
more-itertools, id, pywin32-ctypes, requests-toolbelt, rfc3986) from
uv.lock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…workflow

astral-sh/setup-uv already provisions Python via its python-version
input, making the separate actions/setup-python step redundant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The condition only checked matrix.toxenv, with no python-version pin.
If a second Python version is ever added to the tests matrix, Codecov
would upload for every py312-django* variant across both Python
versions. Add python-version as an explicit matrix dimension and gate
the Codecov step on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cyclic-import (R0401) was disabled globally in pylintrc/pylintrc_tweaks,
which would silently hide any future circular-import regression
anywhere in the codebase -- not just the one legitimate case.

The only real cycle is between enterprise_access/utils.py and
enterprise_access/apps/content_assignments/enrollment_deadline_strategies.py,
both already broken via deferred (function-local) imports. Pylint's
cyclic-import check excludes a module-to-module edge from its cycle
graph if cyclic-import is disabled at the specific import statement's
line (see pylint's ImportsChecker._add_imported_module), so adding
`# pylint: disable=cyclic-import` at the three deferred import sites
that form this cycle is sufficient to suppress it there without
touching the global config. Regenerated pylintrc from pylintrc_tweaks
via `edx_lint write pylintrc`.

Verified: with the global disable removed and these three per-line
disables in place, `pylint enterprise_access test_utils manage.py`
(what CI's quality job actually runs) is clean -- no other cyclic
imports exist in the codebase that the global disable was masking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tox.ini's django42/django52 factors both synced the same `test`
dependency group (locked against the loose base "Django>=2.2" pin,
which resolves to 5.2.x) and then force-overrode just the Django
package version via `deps=`. Every other transitively-resolved
package -- django-crispy-forms, django-filter, social-auth-app-django,
social-auth-core, etc. -- stayed pinned to whatever uv resolved for
Django 5.2, even when running "against Django 4.2". This silently
masked real Django-4.2-incompatible dependency versions instead of
actually testing against them (same bug as enterprise-subsidy#441).

Fix, mirroring that PR's approach:
- Split `test` into a Django-agnostic `test-base` plus two real
  groups that each add their own Django pin: `test` (>=5.2,<6.0,
  the current default) and a new `django42` (>=4.2,<5.0).
- Declare `django42` as conflicting with `test`/`quality`/`doc`/`dev`
  in [tool.uv].conflicts so uv.lock can carry both resolutions at
  once without them clobbering each other.
- Point tox.ini's django42 factor at the new `django42` group instead
  of `test`, and drop the `deps=` overrides entirely.

Verified: `uv lock` now resolves genuinely different versions for
django-crispy-forms (2.5 vs 2.6), django-filter (25.1 vs 25.2), and
social-auth-app-django/social-auth-core between the two groups.
Manually synced each group and confirmed `import django;
django.VERSION` reports (4, 2, 30, ...) under `django42` and
(5, 2, 16, ...) under `test`, and ran the full test suite under both
(1243 passed each). Confirmed uv rejects syncing `test` and
`django42` together, per the conflicts declaration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Ready for Review

Development

Successfully merging this pull request may close these issues.

4 participants