A modern annotation-based library for registering FastAPI services with Consul service discovery
Project description
Service Discovery
A Python library for microservice registration and discovery using Consul. This is the Python counterpart to service-discovery-java, providing decorator-based service registration and client-side load balancing for FastAPI applications.
Installation
pip install prtc-service-discovery
Note: While the package is named prtc-service-discovery on PyPI, you import it as service_discovery in your code.
Prerequisites
- Python 3.10+
- FastAPI application
- Consul server (for service registration/discovery)
Quick Start
Service Registration
from fastapi import FastAPI, APIRouter
from service_discovery import register_service, create_consul_lifespan
app = FastAPI(lifespan=create_consul_lifespan)
api_router = APIRouter(prefix="/api/v1")
@register_service("user-service")
class UserService:
router = api_router
@router.get("/users/{id}")
async def get_user(id: str):
return {"id": id, "name": "John Doe"}
app.include_router(api_router)
@app.get("/health")
async def health():
return {"status": "healthy"}
Service Discovery
from service_discovery import create_service_discovery
# Discover services
discovery = create_service_discovery()
# Get all SERVICE-tagged services
services = await discovery.get_services()
# Returns: {"user-service": ["http://10.0.0.1:8080/api/v1", "http://10.0.0.2:8080/api/v1"]}
# Get a random URI (client-side load balancing)
uri = await discovery.get_service_uri("user-service")
# Returns: "http://10.0.0.1:8080/api/v1"
# Get all URIs for a service
uris = await discovery.get_all_service_uris("user-service")
# Returns: ["http://10.0.0.1:8080/api/v1", "http://10.0.0.2:8080/api/v1"]
# Cleanup
await discovery.close()
Configuration
Configuration is handled through environment variables. All settings are optional with sensible defaults:
# Consul connection
CONSUL_HOST=localhost # Consul server host (default: localhost)
CONSUL_PORT=8500 # Consul server port (default: 8500)
# Service networking
ACCESS_HOST=my-service.local # Hostname/IP other services use to reach this service
ACCESS_PORT=8080 # Port other services use to reach this service
# Feature flags
ENABLE_REGISTRATION=true # Enable/disable Consul registration (default: true)
# Health check networking (optional, defaults to ACCESS_HOST:ACCESS_PORT)
HEALTH_HOST=0.0.0.0 # Interface for health checks
HEALTH_PORT=8080 # Port for health checks
Service Types
The library provides three decorators for different service types:
| Decorator | Consul Tag | Discoverable | Use Case |
|---|---|---|---|
@register_service |
SERVICE |
✅ Yes | REST APIs, gRPC services, and other client-facing services |
@register_worker |
WORKER |
❌ No | |
@register_indexer |
INDEXER |
❌ No |
Only services registered with @register_service are discoverable via the ServiceDiscovery client. Workers and indexers are registered in Consul for monitoring but are not included in service discovery results.
Testing
Unit Testing
from service_discovery import get_service_registry
def test_service_registration():
get_service_registry().clear()
@register_service("test-service", base_route="/api/v1")
class TestService:
pass
services = get_service_registry().get_all_services()
assert len(services) == 1
assert services[0].name == "test-service"
Integration Testing
from service_discovery.testing import ConsulRegistrationTestBase, ExpectedService
class TestMyApp(ConsulRegistrationTestBase):
def get_expected_services(self) -> list[ExpectedService]:
return [
ExpectedService.api_service("user-service", 8080),
ExpectedService.worker("pdf-processor", 8080),
]
def create_app(self) -> FastAPI:
from my_app import app
return app
The base test class automatically verifies:
- Service registration with correct metadata
- Health check configuration
- Service deregistration on shutdown
Advanced Usage
Custom Configuration
from service_discovery import DiscoveryConfig, create_consul_lifespan
config = DiscoveryConfig(
consul__host="consul.prod",
consul__port=8500,
access__host="api.example.com",
access__port=443,
health__host="internal.example.com", # Separate health check network
health__port=8080,
enable_registration=True
)
app = FastAPI(lifespan=lambda app: create_consul_lifespan(app, config))
Multiple Service Registrations
@register_service("auth-service", base_route="/api/auth/v1")
class AuthService:
router = APIRouter()
@register_worker("email-worker", base_route="/api/workers/email/v1")
class EmailWorker:
router = APIRouter()
# Include all routers
for service in [AuthService(), EmailWorker()]:
app.include_router(service.router)
Service Discovery Patterns
# Singleton pattern
discovery = create_service_discovery()
async def make_user_service_call(user_id: str):
# Get a random instance (client-side load balancing)
uri = await discovery.get_service_uri("user-service")
if not uri:
raise ServiceUnavailableError("user-service not found")
async with httpx.AsyncClient() as client:
response = await client.get(f"{uri}/users/{user_id}")
return response.json()
# Context manager pattern
async def get_all_services():
discovery = create_service_discovery()
try:
return await discovery.get_services()
finally:
await discovery.close()
Architecture Notes
Service Discovery
- Only discovers services tagged with
SERVICE(notWORKERorINDEXER) - Uses Consul's catalog API for real-time service discovery
- Implements client-side random load balancing
- Caches service data with configurable refresh interval (default: 30s)
- Automatic retry and error handling for network failures
Service Registration
- Automatic registration on app startup via FastAPI lifespan events
- Built-in health check endpoint configuration
- Generates unique service IDs per instance (consistent within a session)
- Automatically extracts base routes from FastAPI routers
- Graceful deregistration on shutdown
Key Differences from Java Library
- No Stork integration (uses simple random selection for load balancing)
- Discovery returns HTTP URIs directly instead of stork:// URIs
- No static service configuration fallback
- Async-first design using python-consul2
- Decorator-based registration instead of annotations
Development
Setup
This project uses Hatchling for packaging (not Poetry, as libraries should not pin dependencies). For development, create a virtual environment:
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # On macOS/Linux
# .venv\Scripts\activate # On Windows
# Install package in editable mode with dev dependencies
pip install -e ".[dev,test]"
Running Tests & Quality Checks
# Run tests
make test # All tests
make test-unit # Unit tests only
make test-example # Example app tests
# Code quality
make format # Format with black
make lint # Lint with ruff
make type-check # Type check with mypy
make all # All checks + tests
Troubleshooting
Services not registering
- Ensure
ENABLE_REGISTRATION=true(or not set, astrueis the default) - Check that
ACCESS_HOSTandACCESS_PORTare set correctly - Verify Consul is reachable at
CONSUL_HOST:CONSUL_PORT - Check application logs for registration errors
Services not discoverable
- Verify the service is registered with
@register_service(not@register_workeror@register_indexer) - Check that the service is healthy in Consul UI
- Ensure the service has the
SERVICEtag in Consul
Health checks failing
- Verify the
/healthendpoint is accessible atHEALTH_HOST:HEALTH_PORT - If using separate health check networking, ensure
HEALTH_HOSTis reachable from Consul - Check that the health endpoint returns a 2xx status code
License
Proprietary - Perceptic Technologies Ltd.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file prtc_service_discovery-0.3.0.tar.gz.
File metadata
- Download URL: prtc_service_discovery-0.3.0.tar.gz
- Upload date:
- Size: 40.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c49e516235de9d87e33b1ffb099f250be6ba39899a5432f2316db43e575e9460
|
|
| MD5 |
bc37e8ed4ba768a07b9ba4550067ea13
|
|
| BLAKE2b-256 |
f021c204a97b29c2ab0c42a58bcbf839aa91bce105c99bd8275d4d74a2893fa4
|
File details
Details for the file prtc_service_discovery-0.3.0-py3-none-any.whl.
File metadata
- Download URL: prtc_service_discovery-0.3.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
566e5d98140a67816adec4e020c2f97716f0993164c8aba187344b520153ec31
|
|
| MD5 |
13090ce91eb915f46e1fdc385bddd9bd
|
|
| BLAKE2b-256 |
9bc1ce1055b3e3cff7740d511e072abfd647ca9098c6f57a60adce2c58e80820
|