Python: UV and Pants Monorepo Setup
Table of Contents
Section titled “Table of Contents”- Python Structure
- 0) Repo layout you’ll end up with
- 1) Initialize with uv
- 2) Configure deps so Pants can read them (uv_requirements)
- 3) Add Pants
- 4) Add BUILD files
- 5) Add the Python code
- 6) Generate the Pants lockfile + run
Python Structure
Section titled “Python Structure”uv + Pants Python Monorepo Guide (lib_math, math scripts, tests)
Section titled “uv + Pants Python Monorepo Guide (lib_math, math scripts, tests)”0) Repo layout you’ll end up with
Section titled “0) Repo layout you’ll end up with”.├── pants.toml├── pyproject.toml├── BUILD├── 3rdparty/│ └── BUILD├── lib_math/│ ├── BUILD│ └── utils/│ ├── __init__.py│ └── core.py├── math/│ ├── BUILD│ └── scripts/│ ├── __init__.py│ └── main.py└── tests/ └── math/ ├── BUILD └── test_main.pyNote: naming a top-level package
mathcan shadow the Python stdlibmath. If that’s a problem for you, rename the folder/package (e.g.math_pkg). I’ll follow your requested layout as-is.
1) Initialize with uv
Section titled “1) Initialize with uv”mkdir myrepo && cd myrepouv init --bareuv add --dev pytestThis creates pyproject.toml and uv.lock.
2) Configure deps so Pants can read them (uv_requirements)
Section titled “2) Configure deps so Pants can read them (uv_requirements)”Pants has a uv_requirements target that generates python_requirement targets from entries under [tool.uv] in pyproject.toml.
Edit pyproject.toml to include at least your dev deps there:
[project]name = "myrepo"version = "0.1.0"requires-python = ">=3.11"dependencies = []
[tool.uv]dev-dependencies = [ "pytest>=8",]3) Add Pants
Section titled “3) Add Pants”Create pants.toml:
[GLOBAL]pants_version = "2.30.0"backend_packages = [ "pants.backend.python",]
[python]interpreter_constraints = ["CPython>=3.11,<3.13"]enable_resolves = truedefault_resolve = "python-default"resolves = { python-default = "3rdparty/python-default.lock" }
[pytest]install_from_resolve = "python-default"4) Add BUILD files
Section titled “4) Add BUILD files”Root BUILD
Section titled “Root BUILD”# (optional) keep empty or add repo-wide targets later3rdparty/BUILD
Section titled “3rdparty/BUILD”This reads [tool.uv] from your root pyproject.toml and generates requirement targets.
uv_requirements( name="reqs", source="../pyproject.toml", resolve="python-default",)lib_math/BUILD
Section titled “lib_math/BUILD”python_sources(name="lib_math")math/BUILD
Section titled “math/BUILD”python_sources(name="math")
pex_binary( name="run", entry_point="math.scripts.main:main", resolve="python-default",)tests/math/BUILD
Section titled “tests/math/BUILD”python_tests( name="tests", resolve="python-default",)5) Add the Python code
Section titled “5) Add the Python code”lib_math/utils/__init__.py
Section titled “lib_math/utils/__init__.py”from .core import add
__all__ = ["add"]lib_math/utils/core.py
Section titled “lib_math/utils/core.py”def add(a: float, b: float) -> float: return a + bmath/scripts/__init__.py
Section titled “math/scripts/__init__.py”math/scripts/main.py
Section titled “math/scripts/main.py”from lib_math.utils import add
def main() -> None: print(add(2, 3))
if __name__ == "__main__": main()tests/math/test_main.py
Section titled “tests/math/test_main.py”from lib_math.utils import add
def test_add() -> None: assert add(2, 3) == 56) Generate the Pants lockfile + run
Section titled “6) Generate the Pants lockfile + run”Generate lockfiles for your resolve:
pants generate-lockfiles --resolve=python-defaultRun your script (PEX):
pants run math:runRun tests:
pants test tests/math::