Support the uv package manager
The Python package manager uv is looking extremely promising (and quite likely to supersede Poetry and Pipenv), so we should add support for it (after we add Poetry support, and once uv's project management/sync features have stabilised):
https://docs.astral.sh/uv/
https://astral.sh/blog/uv-unified-python-packaging
Potential blockers:
- https://github.com/astral-sh/uv/issues/5229
GUS-W-17141085.
https://github.com/astral-sh/uv/pull/6834 has just merged (not yet released, but will be soon), which adds support for UV_PROJECT_ENVIRONMENT to uv sync, allowing control of the venv location - which was one of the blockers I'd noticed for uv support so far.
There's also the matter of uv still having very frequent releases and occasional breaking changes/regressions (not surprising given that the uv sync feature is still only 2 weeks old) - we'll need to decide at which point it's stable enough that we won't be breaking users of the CNB every time we update it.
Adding support for uv will also increase the priority for doing https://github.com/heroku/buildpacks-python/issues/271, since uv defaults to storing venvs inside the project directory (unlike Poetry or Pipenv), which will mean that users are much more likely to accidentally either (a) git commit their local venv to the repo (which means it will be included for git push heroku main etc), (b) include the venv in a locally run pack build due to not knowing about project.toml's exclude.
Cross-linking:
- The Heroku roadmap entry: https://github.com/heroku/roadmap/issues/323
- The equivalent feature request issue for the classic Python buildpack: https://github.com/heroku/heroku-buildpack-python/issues/1616
Btw the more thumbs up I can get on all the uv-related GitHub issues the easier it will be to make a case for it during internal team planning - so please everyone who is interested in uv support add a 👍 to all the things! 😂
Support for uv has now been added to the Python CNB in #362, and released in the latest versions of the heroku/builder:22 and heroku/builder:24 CNB builder images, so can now be used with Fir-generation Heroku apps, and locally with Pack CLI.
An example using Pack CLI locally with the Python Getting Started guide repo (using a demo branch of the guide that has had it's requirements.txt replaced with a pyproject.toml and uv.lock)...
$ git clone --branch edmorley/uv-demo https://github.com/heroku/python-getting-started && cd python-getting-started
...
Resolving deltas: 100% (378/378), done.
$ pack build --builder heroku/builder:24 uv-getting-started-test
24: Pulling from heroku/builder
Digest: sha256:01baf4fceab23b28d38fa9cd80257d7406528e235407e0c219ed6131738d4135
Status: Image is up to date for heroku/builder:24
24: Pulling from heroku/heroku
Digest: sha256:18cc42eb7e264191f270dca48cf5de4aba65e5317dbdd807df3456b4ab50fcd5
Status: Image is up to date for heroku/heroku:24
===> ANALYZING
Image with name "uv-getting-started-test" not found
===> DETECTING
2 of 3 buildpacks participating
heroku/python 2.0.0
heroku/procfile 4.2.1
===> RESTORING
===> BUILDING
[Determining Python version]
Using Python version 3.13 specified in .python-version
[Installing Python]
Installing Python 3.13.3
[Installing uv]
Installing uv 0.7.3
[Installing dependencies using uv]
Creating virtual environment
Running 'uv sync --locked --no-default-groups'
Resolved 11 packages in 0.49ms
Downloading django (7.9MiB)
Downloading brotli (2.8MiB)
Downloading brotli
Downloading django
Prepared 9 packages in 657ms
Installed 9 packages in 37ms
Bytecode compiled 979 files in 110ms
+ asgiref==3.8.1
+ brotli==1.1.0
+ dj-database-url==2.3.0
+ django==5.2.1
+ gunicorn==23.0.0
+ packaging==25.0
+ sqlparse==0.5.3
+ typing-extensions==4.13.2
+ whitenoise==6.9.0
[Generating Django static files]
Running 'manage.py collectstatic'
1 static file copied to '/workspace/staticfiles', 1 post-processed.
## Procfile Buildpack
- Processes from `Procfile`
- web: `gunicorn --config gunicorn.conf.py gettingstarted.wsgi`
- Done (finished in < 0.1s)
===> EXPORTING
Adding layer 'heroku/python:python'
Adding layer 'heroku/python:venv'
Adding layer 'buildpacksio/lifecycle:launch.sbom'
Added 1/1 app layer(s)
Adding layer 'buildpacksio/lifecycle:launcher'
Adding layer 'buildpacksio/lifecycle:config'
Adding layer 'buildpacksio/lifecycle:process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving uv-getting-started-test...
*** Images (b0c281199042):
uv-getting-started-test
Adding cache layer 'heroku/python:python'
Adding cache layer 'heroku/python:uv'
Adding cache layer 'heroku/python:venv'
Successfully built image uv-getting-started-test
$ docker run --rm -it -e PORT=5006 -p 5006:5006 uv-getting-started-test
[2025-05-13 21:26:45 +0000] [1] [INFO] Starting gunicorn 23.0.0
[2025-05-13 21:26:45 +0000] [1] [INFO] Listening at: http://[::]:5006 (1)
[2025-05-13 21:26:45 +0000] [1] [INFO] Using worker: gthread
[2025-05-13 21:26:45 +0000] [11] [INFO] Booting worker with pid: 11
gunicorn method=GET path="/" status=200 duration=8ms request_id=- fwd="-" user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"
...
Then visit the app at http://localhost:5006/
https://www.heroku.com/blog/local-speed-smooth-deploys-heroku-adds-support-uv/