This repository contains several Python services and shared libraries managed via Rye and built with uv.
.
├── deploy # Dockerfiles and docker-compose
├── pyproject.toml # Root pyproject (virtual) - ONLY dev dependencies
├── requirements-dev.lock # Lock file of development dependencies (tests, linters, etc.)
├── requirements.lock # Common lock file for dependencies if a single lock is used
└── src
├── libs # Shared code used by services
│ ├── pyproject.toml # Separate pyproject for building/publishing libs
│ └── src/libs/... # Library source code
├── service_A # Service A: random number generator
│ ├── pyproject.toml # Separate pyproject for building/publishing service A
│ └── src/service_a/... # Service A source code (named after service)
└── service_B # Service B: echo + random, depends on A and libs
├── pyproject.toml # Separate pyproject for building/publishing service B
└── src/service_b/... # Service B source code (named after service)
The root pyproject.toml
serves as a virtual project for local development coordination:
- ONLY contains development dependencies (linters, formatters, test tools, etc.)
- NO production dependencies - these belong in individual service/library pyproject.toml files
- This prevents production dependencies from being missed during Docker builds
- Provides a unified development environment for the entire monorepo
- Acts as constraints for dependency resolution across all workspace members
All packages in src/
that should be used as final packages or dependencies must be defined in the workspace:
[tool.rye.workspace]
members = [
"src/service_*",
"src/libs",
]
This configuration:
- Automatically discovers all services matching the
src/service_*
pattern - Includes shared libraries from
src/libs
- Enables cross-package dependencies within the monorepo
- Coordinates dependency resolution across all workspace members
Each service's source code is organized with the package named after the service:
src/service_A/src/service_a/ # Package named 'service_a'
src/service_B/src/service_b/ # Package named 'service_b'
This prevents PYTHONPATH conflicts when running services locally, as each service has its own uniquely named package.
# Sync all dependencies using Rye
rye sync
# Run individual services locally using Rye scripts
rye run run-service-A
rye run run-service-B
# Build and run using Docker Compose
cd deploy
docker-compose up --build
The root pyproject.toml
includes convenient scripts for running and debugging services locally:
[tool.rye.scripts]
run-service-A = { cmd = "python src/service_A/src/service_a/main.py", target = "src/service_A", env = {PYTHONPATH = "src/service_A/src"} }
run-service-B = { cmd = "python src/service_B/src/service_b/main.py", target = "src/service_B", env = {PYTHONPATH = "src/service_B/src"} }
target
: Sets the working directory to the service's rootenv.PYTHONPATH
: Ensures proper module resolution for the service- Individual execution: Each service runs with its own isolated Python path
# Run Service A locally
rye run run-service-A
# Run Service B locally
rye run run-service-B
# Run with additional environment variables
CUSTOM_VAR=value rye run run-service-A
- Install dependencies:
rye sync
- Develop locally: Use
rye run run-<service-name>
scripts - Test changes: Run tests for individual services or the entire monorepo
- Build containers: Use Docker Compose for integration testing
- Deploy: Deploy individual services or the entire stack
- Root level: Development tools only (pytest, black, mypy, etc.)
- Service level: Service-specific production and development dependencies
- Library level: Library dependencies that can be shared across services
- Lock files: Centralized dependency resolution while maintaining service isolation