Understanding and working with Discogsography's uv workspace-based monorepo
Discogsography uses a monorepo structure with uv workspaces, allowing multiple services to share code while maintaining independent dependencies. This guide explains how to effectively work within this structure.
graph TD
Root[discogsography/<br/>Root Workspace]
RootFiles[pyproject.toml<br/>uv.lock<br/>.python-version]
API[api/<br/>API Service]
APIFiles[api.py<br/>routers/]
Common[common/<br/>Shared Library]
CommonFiles[config.py<br/>health_server.py]
Dashboard[dashboard/<br/>Monitoring Service]
DashFiles[dashboard.py<br/>static/]
Extractor[extractor/<br/>Data Extraction]
ExtractorFiles[src/main.rs<br/>Cargo.toml]
Explore[explore/<br/>Static File Serving]
ExploreFiles[explore.py]
Graphinator[graphinator/<br/>Neo4j Service]
GraphFiles[graphinator.py]
SchemaInit[schema-init/<br/>DB Schema Init]
SchemaFiles[neo4j_schema.py<br/>postgres_schema.py]
Tableinator[tableinator/<br/>PostgreSQL Service]
TableFiles[tableinator.py]
BrainzGraph[brainzgraphinator/<br/>MusicBrainz Neo4j]
BrainzGraphFiles[brainzgraphinator.py]
BrainzTable[brainztableinator/<br/>MusicBrainz PostgreSQL]
BrainzTableFiles[brainztableinator.py]
Insights[insights/<br/>Analytics Service]
InsightsFiles[insights.py<br/>computations.py]
MCPServer[mcp-server/<br/>AI Assistant MCP]
MCPFiles[server.py]
Root --> RootFiles
Root --> API
Root --> Common
Root --> Dashboard
Root --> Extractor
Root --> Explore
Root --> Graphinator
Root --> SchemaInit
Root --> Tableinator
Root --> BrainzGraph
Root --> BrainzTable
Root --> Insights
Root --> MCPServer
API --> APIFiles
Common --> CommonFiles
Dashboard --> DashFiles
Extractor --> ExtractorFiles
Explore --> ExploreFiles
Graphinator --> GraphFiles
SchemaInit --> SchemaFiles
Tableinator --> TableFiles
BrainzGraph --> BrainzGraphFiles
BrainzTable --> BrainzTableFiles
Insights --> InsightsFiles
MCPServer --> MCPFiles
style Root fill:#f3e5f5,stroke:#9c27b0,stroke-width:3px
style API fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
style Common fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style Dashboard fill:#e3f2fd,stroke:#2196f3,stroke-width:2px
style Extractor fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style Explore fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style Graphinator fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style SchemaInit fill:#f1f8e9,stroke:#689f38,stroke-width:2px
style Tableinator fill:#e0f2f1,stroke:#009688,stroke-width:2px
style BrainzGraph fill:#e0f7fa,stroke:#00838f,stroke-width:2px
style BrainzTable fill:#fff8e1,stroke:#ff8f00,stroke-width:2px
style Insights fill:#fff9c4,stroke:#f57f17,stroke-width:2px
style MCPServer fill:#ede7f6,stroke:#5e35b1,stroke-width:2px
discogsography/ # Root workspace
βββ pyproject.toml # Root configuration & shared dev dependencies
βββ uv.lock # Single lock file for entire workspace
βββ .python-version # Python version for uv
β
βββ api/ # API service (workspace member)
β βββ pyproject.toml # Service-specific dependencies
β βββ api.py # Service entry point
β βββ routers/ # FastAPI routers
β
βββ common/ # Shared library (workspace member)
β βββ pyproject.toml # Declares [project] with name
β βββ __init__.py
β βββ config.py # Shared configuration
β βββ health_server.py # Shared health check server
β
βββ dashboard/ # Service (workspace member)
β βββ pyproject.toml # Service-specific dependencies
β βββ dashboard.py # Service entry point
β βββ static/ # Service assets
β
βββ extractor/ # Data extraction service (Rust β not a uv workspace member)
β βββ Cargo.toml # Rust dependencies
β βββ src/
β βββ main.rs # Rust entry point
β
βββ explore/ # Static file serving (workspace member)
β βββ pyproject.toml # Service dependencies
β βββ explore.py # Service entry point
β
βββ graphinator/ # Service (workspace member)
β βββ pyproject.toml # Neo4j dependencies
β βββ graphinator.py
β
βββ brainzgraphinator/ # MusicBrainz Neo4j enrichment (workspace member)
β βββ pyproject.toml # Service dependencies
β βββ brainzgraphinator.py
β
βββ brainztableinator/ # MusicBrainz PostgreSQL storage (workspace member)
β βββ pyproject.toml # Service dependencies
β βββ brainztableinator.py
β
βββ insights/ # Precomputed analytics (workspace member)
β βββ pyproject.toml # Service dependencies
β βββ insights.py # Service entry point
β βββ computations.py # Computation orchestration
β
βββ mcp-server/ # AI assistant MCP server (workspace member)
β βββ pyproject.toml # Service dependencies
β βββ server.py # FastMCP server
β
βββ schema-init/ # Database schema initializer (workspace member)
β βββ pyproject.toml # Schema dependencies
β βββ neo4j_schema.py # Neo4j constraints & indexes
β βββ postgres_schema.py # PostgreSQL tables
β
βββ tableinator/ # Service (workspace member)
βββ pyproject.toml # PostgreSQL dependencies
βββ tableinator.py
Each service is a workspace member with its own pyproject.toml:
# dashboard/pyproject.toml
[project]
name = "dashboard"
version = "1.0.0"
dependencies = [
"fastapi>=0.115.6",
"websockets>=14.1",
# Service-specific deps
]The root pyproject.toml defines:
- Development dependencies (testing, linting)
- Tool configurations (ruff, mypy, pytest)
- Workspace member declarations
# Root pyproject.toml
[tool.uv.workspace]
members = [
"api",
"brainzgraphinator",
"brainztableinator",
"common",
"dashboard",
"explore",
"graphinator",
"insights",
"mcp-server",
"schema-init",
"tableinator"
]
[project.optional-dependencies]
dev = [
"pytest>=8.3.4",
"ruff>=0.8.6",
"mypy>=1.14.1",
# Shared dev tools
]- One
uv.lockat the root locks ALL dependencies - Ensures version consistency across services
- Updated automatically by uv
# Install everything (recommended for development)
uv sync --all-extras
# Install specific service dependencies
uv sync --extra dashboard
# Install only dev dependencies
uv sync --extra dev# Add to specific service
cd dashboard
uv add fastapi # Adds to dashboard/pyproject.toml
# Add dev dependency (from root)
uv add --dev pytest-asyncio
# Add with version constraint
uv add "neo4j>=5.15.0"# From project root (recommended)
uv run python dashboard/dashboard.py
uv run python explore/explore.py
# Using just commands
just dashboard
just extractor# Services can import from common
from common.config import Config
from common.health_server import HealthServer
# Or from explore
from common.health_server import HealthServer
# But NOT from other services (bad practice)
# from dashboard.something import thing # β Don't do thisWhen modifying common/:
# 1. Make your changes in common/
edit common/config.py
# 2. No reinstall needed - changes are immediate
# (common is installed in editable mode)
# 3. Test affected services
just test# 1. Create service directory
mkdir myservice
cd myservice
# 2. Create pyproject.toml
cat > pyproject.toml << EOF
[project]
name = "myservice"
version = "1.0.0"
dependencies = [
# Service dependencies
]
EOF
# 3. Add to root pyproject.toml
# Edit [tool.uv.workspace] members list
# 4. Sync workspace
cd ..
uv sync# Test single service
uv run pytest tests/dashboard/
# Test with coverage
uv run pytest tests/dashboard/ --cov=dashboard
# Test everything
just test# β Bad: Running from service directory
cd dashboard
uv run python dashboard.py # May fail to find imports
# β
Good: Running from root
uv run python dashboard/dashboard.py# β Bad: Editing pyproject.toml manually
# Can break uv.lock consistency
# β
Good: Using uv commands
uv add package-name# β Bad: Services importing from each other
# dashboard/dashboard.py
from dashboard.something import thing
# β
Good: Only import from common
from common.config import Config# β Bad: Installing service in isolation
cd dashboard
pip install -e .
# β
Good: Using uv sync from root
uv sync --all-extrasEach service has its own Dockerfile but shares the workspace structure:
# Copy entire workspace
COPY pyproject.toml uv.lock ./
COPY common ./common
COPY dashboard ./dashboard
# Install specific service
RUN uv sync --frozen --no-dev --extra dashboard- Always Work from Root: Run commands from project root
- Use uv Commands: Don't edit dependency files manually
- Shared Code in common/: Put reusable code here
- Service Independence: Services shouldn't depend on each other
- Test After Changes: Especially when modifying common/
- Keep Services Focused: Each service should have a single responsibility
| Benefit | Description |
|---|---|
| Code Reuse | Share utilities via common/ |
| Version Consistency | Single lock file prevents conflicts |
| Faster Installation | uv caches dependencies efficiently |
| Atomic Updates | Update all services simultaneously |
| Simplified CI/CD | One install step for everything |
# Show dependency tree
uv tree
# List installed packages
uv pip list# Check if service is installed
uv run python -c "import dashboard; print(dashboard.__file__)"# Clean install
rm -rf .venv
uv sync --all-extras- uv Workspaces Documentation
- Task Automation - Task commands
- Docker Standards - Container setup