Repository navigation
chore(deps): migrate dependency management to pyproject.toml + uv (v2) - #247
Merged
Merged
Conversation
Replaces requirements/main.in, dev.in, production.in and their pip-compile-generated *.txt with a single pyproject.toml ([project.dependencies] + a "dev" dependency-group) and uv.lock as the committed, resolved lock file. The project is marked non-packaged (tool.uv.package = false) since this is a deployable Django app, not a distributed library, so pythonie/setup.py is removed as unused. Dependency versions are carried over unchanged from the current requirements/*.txt (including the yesterday's security patches); django is capped at <6.1 to avoid an incidental minor-version bump from the unconstrained resolution, and ruff is pinned to 0.15.16 since 0.16 changes the default lint rule set. requirements.txt at the repo root is now generated via `uv export` (see the new `task dependencies:export`) and kept only for Heroku's buildpack, which expects that exact filename. The empty requirements/production.in (a bare `-c main.txt` constraint with no packages of its own) is dropped entirely rather than carried over as an empty dependency-group.
…gement Replaces the pip-compile invocations in `task dependencies:*` / `toast deps:*` with their uv equivalents (uv lock, uv lock --upgrade[-package], uv tree, uv run pip-audit) and adds `dependencies:export` / `deps:export` to (re)generate the Heroku-facing requirements.txt from uv.lock.
Replaces the requirements/*.txt-based pip/uv install with `uv sync --frozen --all-groups` against the committed pyproject.toml and uv.lock, and puts the resulting .venv on PATH so existing `docker compose run ... python ...` invocations keep working unchanged.
Swaps the requirements/*.txt pip install for astral-sh/setup-uv (with lock-file-keyed caching) and `uv sync --all-groups`, and prefixes every step with `uv run`. Keeps the existing checkout version and all quality gates (ruff format, ruff check, pip-audit, Django tests) exactly as they are on master.
Switches the pinned tool versions (Python, Task, uv) from asdf's .tool-versions format to mise's native mise.toml, which is the project's preferred tool version manager going forward. mise still reads .tool-versions for compatibility, but this makes mise.toml the canonical source.
Updates README, CONTRIBUTING, DEVELOPMENT and CLAUDE.md to reflect `uv sync` + `uv run` instead of a manually created venv and `pip install -r requirements/dev.txt`, mise.toml instead of .tool-versions, and pyproject.toml/uv.lock instead of the requirements/*.in and *.txt files. Also refreshes the stale "Wagtail 7.2" mentions to 7.3, matching the version actually deployed.
9 of 11 tasks
v10 alone doesn't resolve; the action only publishes full semver tags for its current major.
…natively
heroku/heroku-buildpack-python detects the package manager by looking
for exactly one of requirements.txt, poetry.lock or uv.lock at the
repo root, and now hard-fails the build ("Multiple Python package
manager files were found") if it sees more than one — this stopped
being just a deprecation warning. Committing both uv.lock and a
uv-export-generated requirements.txt (as the previous commit did)
would have broken every Heroku deploy.
The buildpack has native uv support: it runs
`uv sync --locked --no-default-groups` against the slug's system
Python, so no requirements.txt is needed at all. Verified locally with
`UV_PROJECT_ENVIRONMENT=<tmp> uv sync --locked --no-default-groups`
(dev group correctly excluded) and
`manage.py check --settings=pythonie.settings.production` against
that environment.
Drops the now-pointless `dependencies:export` / `deps:export` tasks
and the requirements.txt mentions in CLAUDE.md/DEVELOPMENT.md, and
documents the "never commit a requirements.txt here" constraint
directly so it isn't reintroduced later. `.python-version` (already
present, Python 3.13) continues to pin the buildpack's Python version.
Caught by an independent review pass (h/t python-validator).
Contributor
Author
|
An independent review found a real blocker: Fix pushed (7d67c67): removed Verified:
|
matrixise
added a commit
that referenced
this pull request
Sep 25, 2026
The previous config was written for the pip-compile workflow and pointed
at requirements/{main,dev,production}.txt, which no longer exist since
the migration to pyproject.toml + uv.lock (#247). It was a no-op.
- Drop the pip-compile / pip_requirements / pip_setup blocks; the pep621
manager (enabled by config:recommended) picks up pyproject.toml and
drives `uv lock` for uv.lock.
- Main vs dev split now uses depTypes instead of file paths:
[project.dependencies] -> "project.dependencies" (never automerged),
[dependency-groups] -> "dependency-groups" (patch automerge). pep621
keeps the PEP 735 group name only in managerData, so it cannot be
matched; "dev" is currently the only group. Dev patches get their own
group so automerge is not blocked by production deps on the same branch.
- Security: the vulnerability alert settings were inside a packageRule
restricted to patch updates (and to non-0.x versions), which excluded
fixes needing a minor bump. Move them to the top-level
vulnerabilityAlerts object with no update-type restriction. prPriority
is dropped: it is not allowed there, and vulnerability PRs already
bypass schedule and PR limits.
- Restrict the "All non-major dependencies" group to pep621 so Dockerfile
and GitHub Actions bumps are not mixed into the Monday Python group.
- Replace the removed matchPackagePatterns / excludePackagePatterns /
matchFiles options with matchPackageNames regexes and depTypes.
- Disable requires-python updates: Python is pinned to 3.13.x.
- Run lockFileMaintenance weekly instead of monthly: most dependencies in
pyproject.toml have no version specifier, so Renovate skips them
(skipReason "unspecified-version") and the uv.lock refresh is the only
way they get updated.
Validated with `renovate-config-validator --strict --no-global` and a
local `renovate --platform=local --dry-run=extract` run.
This was referenced Sep 25, 2026
matrixise
added a commit
that referenced
this pull request
Sep 25, 2026
The previous config was written for the pip-compile workflow and pointed
at requirements/{main,dev,production}.txt, which no longer exist since
the migration to pyproject.toml + uv.lock (#247). It was a no-op.
- Drop the pip-compile / pip_requirements / pip_setup blocks; the pep621
manager (enabled by config:recommended) picks up pyproject.toml and
drives `uv lock` for uv.lock.
- Main vs dev split now uses depTypes instead of file paths:
[project.dependencies] -> "project.dependencies" (never automerged),
[dependency-groups] -> "dependency-groups" (patch automerge). pep621
keeps the PEP 735 group name only in managerData, so it cannot be
matched; "dev" is currently the only group. Dev patches get their own
group so automerge is not blocked by production deps on the same branch.
- Security: the vulnerability alert settings were inside a packageRule
restricted to patch updates (and to non-0.x versions), which excluded
fixes needing a minor bump. Move them to the top-level
vulnerabilityAlerts object with no update-type restriction. prPriority
is dropped: it is not allowed there, and vulnerability PRs already
bypass schedule and PR limits.
- Restrict the "All non-major dependencies" group to pep621 so Dockerfile
and GitHub Actions bumps are not mixed into the Monday Python group.
- Replace the removed matchPackagePatterns / excludePackagePatterns /
matchFiles options with matchPackageNames regexes and depTypes.
- Disable requires-python updates: Python is pinned to 3.13.x.
- Run lockFileMaintenance weekly instead of monthly: most dependencies in
pyproject.toml have no version specifier, so Renovate skips them
(skipReason "unspecified-version") and the uv.lock refresh is the only
way they get updated.
Validated with `renovate-config-validator --strict --no-global` and a
local `renovate --platform=local --dry-run=extract` run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworked take on #172, from scratch, rebased on current master (that PR is now
CONFLICTING/stale and had a real CI regression, see below). Replaces therequirements/*.in+ pip-compile-generatedrequirements/*.txtworkflow with a singlepyproject.toml+ committeduv.lock, matching the project's move to a uv-only workflow.pyproject.toml:[project.dependencies](main) + adevdependency-group,tool.uv.package = false(this is a deployable Django app, not a distributed library). Drops the now-unusedpythonie/setup.pyuv.lock: committed, resolved lock file (115 packages)djangocapped at<6.1andruffpinned to0.15.16to avoid incidental version bumps changing behavior as a side effect of this migrationrequirements.txt: Heroku's Python buildpack now installs fromuv.locknatively (see "Heroku deployment" below)Dockerfile:uv sync --frozen --all-groups,.venvonPATHso existingdocker compose run ... python ...invocations are unchangedTaskfile.yaml/toast.yml:dependencies:*tasks now calluv lock/uv lock --upgrade[-package]/uv tree/uv run pip-auditinstead ofuv pip compile.github/workflows/test.yml:astral-sh/setup-uv+uv sync --all-groups, every step prefixed withuv run. Keeps every existing quality gate (ruff format, ruff check, pip-audit, Django tests). This is the part Migrate dependency management to pyproject.toml with uv #172 regressed, silently dropping format/lint/security checks and downgradingactions/checkout/setup-pythonto@v2.tool-versions(asdf) →mise.toml(native format for the team's tool version manager going forward)README.md,CONTRIBUTING.md,DEVELOPMENT.md,CLAUDE.md) updated to theuv sync/uv runworkflowHeroku deployment
The
heroku/pythonbuildpack picks its package manager from the files at the repo root, and aborts the build (Error: Multiple Python package manager files were found) if it finds more than one ofrequirements.txt,poetry.lock,uv.lock. An earlier revision of this PR kept auv export-generatedrequirements.txtnext touv.lock, which would have broken the deploy. It has been removed, along with thedependencies:exporttask.With only
uv.lockpresent, the buildpack (uv support since v286, 2025-05-13):.python-version(3.13, already on master;runtime.txtis deprecated and not needed)uv sync --locked --no-default-groupsinto the slug's system Python, so thedevgroup is not installed anduv.lockmust be in sync withpyproject.tomlcollectstaticas beforeAt runtime
pythonandgunicornlive in/app/.heroku/python/binand uv is not needed, so theProcfile(release: python pythonie/manage.py migrate ...,web: gunicorn ...) is unchanged.The
pythonieapp usesheroku/pythonfrom the buildpack registry (not pinned to a tag), so it gets the current buildpack.Why not just update #172?
#172 is 9 months stale, conflicts with master (mostly from yesterday's dependency-security PRs touching
requirements/*.txt), pins Django 5.2.x/Wagtail 7.2.x (both outdated now), and its CI workflow dropsruff format --check,ruff checkandpip_auditentirely while downgradingactions/checkout/setup-pythonto@v2. Rather than untangling that history, this PR rebuilds the same end state cleanly on top of current master. Happy to close #172 once this is reviewed, or keep both open for comparison, your call.Test plan
uv sync --all-groupsinstalls cleanly (115 packages resolved)uv lock --check: lock file in sync withpyproject.tomluv run python pythonie/manage.py test pythonie --settings=pythonie.settings.tests(SQLite, 7/7 passed)uv run ruff format --check pythonie/uv run ruff check pythonie: both clean, matching masteruv run pip-audit: no known vulnerabilitiesdocker build .succeeds;python,ruff, django/wagtail imports resolve via the.venvonPATHinside the imageheroku-buildpack-python(v352)bin/compilerun against this branch in theheroku/heroku:24-buildimage, with production config vars: Python 3.13.15 from.python-version,uv sync --locked --no-default-groups(68 packages),collectstaticOK (257 files)/appand a clean env:manage.py check --settings=pythonie.settings.productionreports no issues, themigratecommand loads,gunicorn pythonie.wsgiboots and serves requests (HTTP 301 SSL redirect, as expected)heroku buildpacks -a pythonie:heroku/pythonfrom the registry, not pinnedheroku releases, keeptask heroku:rollbackat hand (the first uv build is a full rebuild since the pip cache is discarded)