Add support for selecting UV dependency groups during build via environment variable
Currently, the Heroku Python buildpack uses uv sync --locked --no-default-groups when installing dependencies in a non-CI build environment.
While this works for most setups, it assumes that all runtime dependencies are declared in the [project.dependencies] section.
In some cases, teams organize dependencies differently. For example, by having a prod group for runtime dependencies and a dev or local group for development tools.
With the current buildpack logic, prod is never installed because it is excluded along with dev.
I propose to use an environment variable like UV_GROUPS.
# This config
UV_GROUPS="prod,extra"
# Would give this command
uv sync --locked --no-default-groups --group prod --group extra
IfUV_GROUPS is not set, behavior remains unchanged.
If INSTALL_TEST is set (Heroku CI), the existing logic for default groups remains untouched.
I will be happy to attempt a PR if needed
Hi! Thank you for filing an issue :-)
Could you add some more detail on why the production dependencies are kept in a separate group?
In general, we would encourage apps to be run in as close to a production configuration in all environments (locally, CI, review apps etc) - so with the same dependencies installed (excl dev/test only deps) and only things like debug mode enabled / automatic server reloading / ... etc different locally. Requiring that production dependencies be included in the main dependencies list encourages that best practice - and also reduces configuration/testing permutation burden and additional edge cases (like the user-provided groups not existing etc). It's also the same design we have for the other three package managers we support (pip, Poetry, Pipenv).
There's also some more context on the current dependency groups design in the original uv PR: https://github.com/heroku/heroku-buildpack-python/pull/1791
Sorry for the late reply, and thanks for the clarification. I totally understand the rationale and agree with the principle of minimizing environment differences.
That said, in some setups (like mine), certain packages are strictly production-only — for example, django-storages or monitoring-related dependencies that aren't needed locally or in CI. Keeping them in a dedicated prod group helps avoid unnecessary installations and configuration locally.
Thank you for the extra context!
For dependencies that are only used in production, I think there is still often value in having them as part of the main dependency group, so that your development environments are testing as close to the environment being used in production as possible. ie: IMO the purpose of a development environment isn't just to develop the app but also to make sure the app will actually work in production.
For example, what if the django-storages package had a transitive dependency that conflicts with one of the other dependencies in your main dependency group, and thus caused a different dependency version resolution outcome? (Such as django-storages not being compatible with Django X.Y and causing X.Y-1 to be used instead). Your production app could then be using a different version of Django compared to your development environment.
Or another example, what if a specific package adds import hooks or other magic machinery (like say setuptools adds its .pth hooks) that causes a behaviour difference between prod and development?
helps avoid unnecessary installations
I know django-storages was only an example (and there are packages out there that are much larger; albeit typically not the "production-only" class of packages we're talking about), but the package is only a 33KB wheel download:
https://pypi.org/project/django-storages/#files
...and locally with the uv cache and hardlinking, the local size/time impact of installing it will round down to zero. So optimising for not downloading a 33KB package at the risk of less dev-prod parity seems like it might be the wrong trade-off?
...and configuration locally
In terms of avoiding extra configuration - installing the package doesn't mean you need to configure it locally in your Django config, so that seems orthogonal?