How to Implement Graceful Degradation with Feature Disabling in Python
A pattern that disables enhanced features and falls back to basic functionality when a dependency fails, with mock-based testing.
Python code
50 linesimport random
from unittest.mock import patch
class EnhancedFeature:
"""A feature that can gracefully degrade when a dependency is unavailable."""
def __init__(self):
self.feature_enabled = True
def get_enhanced_data(self):
"""Simulate an enhanced feature that depends on external data."""
try:
# Simulate potentially failing external API call
mock_response = random.choice([True, False])
if not mock_response:
raise ConnectionError("External service unavailable")
return {"level": "enhanced", "value": 42}
except ConnectionError:
self.feature_enabled = False
return self.get_basic_data()
def get_basic_data(self):
"""Fallback with reduced functionality."""
return {"level": "basic", "value": 10}
def is_capable(self):
"""Check if advanced features are available."""
return self.feature_enabled
def demonstrate_degradation():
feature = EnhancedFeature()
# Simulate failure by patching random to always return False
with patch("__main__.random.choice", return_value=False):
result = feature.get_enhanced_data()
print(f"With failure: {result}, capable={feature.is_capable()}")
# Reset for normal operation
feature.feature_enabled = True
# Normal working case
with patch("__main__.random.choice", return_value=True):
result = feature.get_enhanced_data()
print(f"Without failure: {result}, capable={feature.is_capable()}")
if __name__ == "__main__":
demonstrate_degradation()
Output
With failure: {'level': 'basic', 'value': 10}, capable=False
Without failure: {'level': 'enhanced', 'value': 42}, capable=True
How it works
The EnhancedFeature class wraps a potentially failing external call in a try/except. When ConnectionError is raised, it sets feature_enabled to False and returns a degraded response from get_basic_data(). The is_capable() method exposes the degradation state for other parts of the system. Patches with unittest.mock simulate both failure and success paths deterministically, verifying that the feature degrades gracefully rather than crashing. This pattern is essential for building resilient systems that continue serving users even when supporting services go down.
Common mistakes
- Letting exceptions propagate instead of catching them and falling back
- Forgetting to reset the degraded state when the dependency recovers
- Making the fallback logic as complex as the primary path, defeating the purpose
- Not testing both the success and failure paths with mocks
Variations
- Use a circuit breaker pattern that opens after N consecutive failures and closes after a cooldown
- Implement a feature flag service to toggle enhanced capabilities remotely without redeploying
Real-world use cases
- An e-commerce app that falls back to plain listings when personalized recommendations fail.
- A dashboard that degrades from live WebSocket updates to periodic polling on connection loss.
- A payment gateway that disables premium checkout options when the fraud-detection service times out.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.