How to Build a Scalable Python Monorepo with UV: From Setup to Dependency Management
This tutorial shows how to use the UV tool to create a Python monorepo with separate API, core, and common library projects, manage dependencies with individual pyproject.toml files, and share reusable modules across FastAPI and other services.
Previously I introduced UV , a Python package manager that does more than just install packages. Curious about its capabilities, I used UV to design a monorepo.
Repository Structure
When creating a monorepo I usually include three key projects:
API project : handles various APIs such as FastAPI REST API, gRPC, GraphQL, etc.
Core project : provides the core functionality of the monorepo.
Common library project : a shared library containing utilities, constants, and other common code.
This layout ensures scalability, better code organization, and improved dependency management.
Three Nested Projects
py-api
py-basecamp
py-zcommonlib
py-api and py-basecamp are independent and cannot import each other. Shared functionality should be placed in py-zcommonlib to keep the projects isolated.
Creating the Three Projects with UV
Run the following commands:
uv init py-zcommonlib --lib
uv init py-api
uv init py-basecampThe --lib flag creates a src directory for Python modules; without it, UV defaults to --app, producing a flat structure.
Note: the flat structure is not wrong, just a different approach.
Dependency Management
Each project has its own pyproject.toml , allowing independent and clear dependency control.
Adding a Common Module in py-zcommonlib
Create datetime_lib.py with the following code:
from datetime import datetime, timezone
def get_utc_timestamp():
"""Return the current UTC timestamp in ISO 8601 format.
Example: '2025-02-15T12:34:56Z'"""
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
if __name__ == "__main__":
print(get_utc_timestamp())Run it with UV:
uv run src/py_zcommonlib/datetime_lib.pyUsing the Module in py-basecamp
Update main.py to import the function:
from py_zcommonlib.datetime_lib import get_utc_timestamp
def main():
print(get_utc_timestamp())
if __name__ == "__main__":
main()The script fails because py-zcommonlib is not yet listed as a dependency of py-basecamp .
Updating py-basecamp pyproject.toml
Add py-zcommonlib as an editable dependency using a relative path:
[project]
name = "py-basecamp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.9"
dependencies = ["py-zcommonlib"]
[tool.uv.sources]
py_zcommonlib = { path="../py-zcommonlib", editable=true }Run uv sync in the py-basecamp directory to install the dependency, then uv run main.py to test.
Understanding [tool.uv.sources]
The tool.uv.sources table extends the standard dependency list, allowing alternative sources such as editable installs and relative paths, which are not supported by project.dependencies.
Using the Module in py-api
In the FastAPI project, import and expose the timestamp endpoint:
from fastapi import FastAPI
from py_zcommonlib.datetime_lib import get_utc_timestamp
app = FastAPI()
@app.get("/")
async def utc_timestamp():
return get_utc_timestamp()Corresponding pyproject.toml for py-api :
[project]
name = "py-api"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"fastapi>=0.115.11",
"uvicorn>=0.34.0",
"py-zcommonlib",
]
[tool.uv.sources]
py_zcommonlib = { path="../py-zcommonlib", editable=true }Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
