Fallback
Overview
The Fallback pattern provides an alternative execution path for an operation when it fails. Instead of allowing a failure to cascade through the system, a fallback can return a default value, data from an alternative source, or trigger a different logic path. This allows the application to degrade gracefully rather than failing completely.
Athomic provides two distinct implementations of this pattern:
- A generic
@fallback_handlerdecorator for protecting any function. - A specialized
FallbackKVProviderfor creating highly resilient, multi-layer caching strategies.
1. Generic Fallback with @fallback_handler
This decorator allows you to wrap any asynchronous function with one or more fallback functions. If the primary function raises an exception, the fallback functions are executed in order until one succeeds.
How It Works
The FallbackLogicHandler executes your primary function. If an exception occurs, it catches it and begins iterating through the list of fallback functions you provided, calling them with the same arguments as the original function. The result of the first successful fallback is returned. If all fallbacks also fail, a single FallbackError containing all the underlying exceptions is raised.
Usage Example
from nala.athomic.resilience.fallback import fallback_handler
# A fallback function that returns a default static value
async def get_default_user_config(*args, **kwargs):
return {"theme": "dark", "notifications": "enabled"}
# Another fallback that could try a different source
async def get_user_config_from_legacy_system(user_id: str):
# ... logic to call a legacy API ...
pass
@fallback_handler(fallbacks=[get_user_config_from_legacy_system, get_default_user_config])
async def get_user_config_from_primary(user_id: str) -> dict:
"""
Attempts to fetch user config from the primary Redis cache.
If Redis is down, it tries the legacy system.
If that also fails, it returns a hardcoded default.
"""
# This call might fail if Redis is unavailable
return await redis_cache.get(f"user-config:{user_id}")
2. The FallbackKVProvider (for Resilient Caching)
This is a specialized, high-performance implementation of the fallback pattern designed specifically for caching. It's a KVStoreProtocol provider that wraps a chain of other KV store providers (e.g., a primary Redis cache and a secondary in-memory cache).
Key Features
Read Strategies
You can configure when the fallback is triggered:
- on_error: The fallback is used only if the primary cache is down (e.g., Redis connection fails). A normal cache miss on the primary will not trigger the fallback.
- on_miss_or_error: (Default) The fallback is used if the primary cache is down OR if the key is not found (a cache miss).
Write Strategies
You can configure how writes are propagated through the cache layers:
- write_around: (Default) Writes are only sent to the primary cache. The fallback caches are not updated.
- write_through: Writes are sent to the primary cache and then propagated to all fallback caches concurrently.
Self-Healing
This is a powerful feature of the FallbackKVProvider. If a value is successfully retrieved from a fallback cache (e.g., the in-memory cache), a background task is automatically spawned to write that value back into the primary cache (and any other preceding caches that failed). This "heals" the primary cache, so the next request for the same key will be a hit on the fastest layer.
Configuration
The FallbackKVProvider is typically configured as part of your main cache setup in settings.toml.
[default.performance.cache]
enabled = true
# The primary provider for the cache is a connection named "main_redis"
kv_store_connection_name = "main_redis"
# --- Fallback Configuration ---
[default.performance.cache.fallback]
enabled = true
# Define the read strategy
read_strategy = "on_miss_or_error"
# An ordered list of KVStore connection names to use as fallbacks.
# Here, it will try "local_memory_cache" if "main_redis" misses or fails.
provider_connection_names = ["local_memory_cache"]
# You must also define the KV store connections themselves:
[default.database.kvstore]
[default.database.kvstore.main_redis]
# ... redis config ...
[default.database.kvstore.local_memory_cache]
provider.backend = "local"
API Reference
nala.athomic.resilience.fallback.decorator.fallback_handler(fallbacks)
Decorator that applies the Fallback resilience pattern to a function.
It wraps the decorated primary function with an execution chain: if the primary function fails for any reason, the configured list of fallback functions is executed sequentially until one succeeds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fallbacks
|
Union[Callable, List[Callable]]
|
A single callable or an ordered list of callables (sync or async) to be tried as alternatives if the decorated function raises an exception. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Callable |
Callable[[Callable[..., T]], Callable[..., T]]
|
The decorator function. |
nala.athomic.resilience.fallback.providers.fallback_kv_provider.FallbackKVProvider
Bases: BaseService, KVStoreProtocol
A Key-Value (KV) provider that implements a resilient fallback and self-healing strategy.
It orchestrates a chain of underlying KV providers: trying the primary first, and then sequentially trying fallbacks in case of failure or cache miss (depending on the configured read strategy).
Attributes:
| Name | Type | Description |
|---|---|---|
primary |
KVStoreProtocol
|
The main, preferred KV store provider. |
fallbacks |
List[KVStoreProtocol]
|
Ordered list of secondary providers. |
write_strategy |
WriteStrategyProtocol
|
Strategy defining how write operations are handled across providers. |
read_strategy |
ReadStrategyProtocol
|
Strategy defining when the fallback chain is triggered. |
enable_self_healing |
bool
|
If True, successful reads from a fallback provider will asynchronously write the value back to the failed/missing providers. |
__init__(primary_provider, fallback_providers, read_strategy_type=FallbackReadStrategyType.ON_MISS_OR_ERROR, write_strategy_type=FallbackWriteStrategyType.WRITE_AROUND, enable_self_healing=True)
Initializes the FallbackKVProvider.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primary_provider
|
KVStoreProtocol
|
The primary KV store instance. |
required |
fallback_providers
|
List[KVStoreProtocol]
|
A list of secondary providers, ordered by preference. |
required |
read_strategy_type
|
FallbackReadStrategyType
|
Defines the trigger for the fallback read chain. |
ON_MISS_OR_ERROR
|
write_strategy_type
|
FallbackWriteStrategyType
|
Defines how write operations propagate. |
WRITE_AROUND
|
enable_self_healing
|
bool
|
Enables background healing of failed providers on successful reads. |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
clear()
async
Clears all keys in the provider(s) based on the configured write strategy.
delete(key)
async
Deletes a key based on the configured write strategy.
exists(key)
async
Checks for the existence of a key by delegating the check/fallback logic to the configured read strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key to check. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if the key exists in any available provider, False otherwise. |
get(key)
async
Retrieves a value by delegating the complex read/fallback logic to the configured read strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key to retrieve. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Any]
|
Optional[Any]: The value from the first successful provider, or None. |
get_final_client()
async
Returns the raw client of the primary provider for direct access.
get_sync_client()
Returns the synchronous client of the primary provider for direct access.
heal(key, value, providers_to_heal)
async
Asynchronously attempts to heal failed or missed providers by writing the successfully retrieved value back to them.
This method is non-blocking and is executed as a fire-and-forget background task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key that was successfully retrieved. |
required |
value
|
Any
|
The value associated with the key. |
required |
providers_to_heal
|
List[KVStoreProtocol]
|
The chain of providers (including the primary) that failed or missed the key. |
required |
is_available()
Returns True if AT LEAST ONE provider is connected and ready.
set(key, value, ttl=None, nx=False)
async
Sets a key-value pair based on the configured write strategy.
nala.athomic.resilience.fallback.exceptions.FallbackError
Bases: Exception
Raised when all fallback attempts fail.