This repository serves as an example of how to get python wheels and pex files easily built in a uv workspace with simple one-liners. The gist of a pex is that it lets you package up all your python code and dependencies into an executable file (optionally with a bundled in python). If you haven't used wheels, they're great as distributables for other code to rely on via package managers like pip, uv, etc; you publish wheels to pypi.
I built this because I couldn't find any definitive or comprehensive examples on how to wrangle uv and pex to do this. I hit some snags along the way but I'm pretty satisfied with what's here. This also serves as a decent example on how to structure and build a uv workspace that respects your dependencies (see just check-all-imports
).
Feedback and PRs are welcome.
- uv (https://github.com/astral-sh/uv)
- just (https://github.com/casey/just)
- yq (https://github.com/mikefarah/yq)
- actionlint (https://github.com/rhysd/actionlint)
just build-pex <package> <module> [{native}|docker](platform) [{no}|eager|lazy](bundle-python)
To build a pex native to your system, use just build-pex bar bar.__init__
.
To build a pex for linux matching your architecture, use just build-pex bar bar.__init__ docker
.
To build a pex for a platform of your choice (e.g. linux/x86_64
), use DOCKER_PLATFORM=linux/x86_64 just build-pex bar bar.__init__ docker
.
The pex files for these will show up in the project's dist/os_arch
directory where os and arch are your operating system and architecture.
Check out https://docs.pex-tool.org/scie.html for more details but if you can either rely on the system python that matches this project's .python-version
(bundle-python=no), bundle in python directly (bundle-python=eager), or lazily download it on demand (bundle-python=lazy).
For example, just build-pex bar bar.__init__ native eager
. When bundling like this, the output file will have the .pex extension missing, which shows that it is fully self contained. You'll also get the .pex version alongside it, but it's not needed.
just build-uvicorn-pex <package> <module> [{native}|docker](platform) [{no}|eager|lazy](bundle-python)
Check out https://docs.pex-tool.org/recipes.html#uvicorn-and-other-customizable-application-servers for more details but you can bundle a pex to call uvicorn like you'd expect.
For example, just build-uvicorn-pex uvicorn-server uvicorn_server.main:app
will give you a pex that executes uvicorn on top of your uvicorn_server.main
's app function.
just build-wheel <package> [{native}|docker](platform)
To build a wheel native to your system, use just build-wheel bar
.
To build a wheel for linux matching your architecture, use just build-wheel bar docker
.
To build a wheel for a platform of your choice (e.g. linux/x86_64
), use DOCKER_PLATFORM=linux/x86_64 just build-wheel bar docker
.
The outputs for these will show up in the project's wheelhouse
directory.
Check out a real example in the external-package-uv and external-script directories. Quickly though:
main.py
from bar.speak import hello
def main() -> None:
print("Hello from external-package!")
hello()
If you were trying to use the wheel from an external project with just pip
, you could consume it like so:
requirements.txt
bar==0.1.0
python3 -m venv .venv
source .venv/bin/activate
pip3 install --find-links=file://`pwd`/dist -r requirements.txt
python3 main.py
pyproject.yaml
[project]
name = "testing"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["bar==0.1.0"]
[tool.uv.sources]
bar = { index = "local" }
[[tool.uv.index]]
name = "local"
url = "/path/to/this/wheelhouse"
format = "flat"
If the version of the package isn't changing, you may need to do something like uv run --exact --isolated --no-cache --refresh main.py
.