Skip to content

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:

  1. A generic @fallback_handler decorator for protecting any function.
  2. A specialized FallbackKVProvider for 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 primary_provider is None.

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.