Skip to content
1 change: 1 addition & 0 deletions src/cachier/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Params:
cleanup_interval: timedelta = timedelta(days=1)
entry_size_limit: Optional[int] = None
allow_non_static_methods: bool = False
key_prefix: str = "cachier"


_global_params = Params()
Expand Down
5 changes: 5 additions & 0 deletions src/cachier/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def cachier(
mongetter: Optional[Mongetter] = None,
sql_engine: Optional[Union[str, Any, Callable[[], Any]]] = None,
redis_client: Optional["RedisClient"] = None,
key_prefix: Optional[str] = None,
s3_bucket: Optional[str] = None,
s3_prefix: str = "cachier",
s3_client: Optional["S3Client"] = None,
Expand Down Expand Up @@ -280,6 +281,8 @@ def cachier(
redis_client : redis.Redis or callable, optional
Redis client instance or callable returning a Redis client.
Used for the Redis backend.
key_prefix : str, optional
Key prefix applied to all redis keys. Defaults to ``"cachier"``.
s3_bucket : str, optional
The S3 bucket name for cache storage. Required when using the S3 backend.
s3_prefix : str, optional
Expand Down Expand Up @@ -357,6 +360,7 @@ def cachier(
backend = _update_with_defaults(backend, "backend")
mongetter = _update_with_defaults(mongetter, "mongetter")
size_limit_bytes = parse_bytes(_update_with_defaults(entry_size_limit, "entry_size_limit"))
key_prefix = _update_with_defaults(key_prefix, "key_prefix")

# Create metrics object if enabled
cache_metrics = None
Expand Down Expand Up @@ -407,6 +411,7 @@ def cachier(
wait_for_calc_timeout=wait_for_calc_timeout,
entry_size_limit=size_limit_bytes,
metrics=cache_metrics,
key_prefix=key_prefix,
)
elif backend == "s3":
core = _S3Core(
Expand Down
30 changes: 30 additions & 0 deletions tests/redis_tests/test_async_redis_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,36 @@ def _func(x: int) -> int:
return core


@pytest.mark.redis
@pytest.mark.asyncio
async def test_async_redis_key_prefix_passed_to_client():
pytest.importorskip("redis")

class PrefixCapturingRedis(_AsyncInMemoryRedis):
def __init__(self):
super().__init__()
self.keys_used: list[str] = []

async def hset(self, key: str, field=None, value=None, mapping=None, **kwargs):
self.keys_used.append(key)
await super().hset(key, field=field, value=value, mapping=mapping, **kwargs)

client = PrefixCapturingRedis()

async def get_redis_client():
return client

@cachier(backend="redis", redis_client=get_redis_client, key_prefix="custom-prefix")
async def async_cached_value(x: int) -> int:
return x + 1

result = await async_cached_value(1)
assert result == 2
assert client.keys_used, "Redis client was not called"
assert all(key.startswith("custom-prefix:") for key in client.keys_used)
assert all(stored_key.startswith("custom-prefix:") for stored_key in client._data)


@pytest.mark.redis
@pytest.mark.asyncio
async def test_async_redis_core_helpers_and_client_resolution():
Expand Down
41 changes: 40 additions & 1 deletion tests/redis_tests/test_redis_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import queue
import threading
import warnings
from dataclasses import replace
from datetime import datetime, timedelta
from random import random
from time import sleep
Expand All @@ -12,7 +13,8 @@
import pandas as pd
import pytest

from cachier import cachier
import cachier.config as cachier_config
from cachier import cachier, set_global_params
from cachier.core import _is_async_redis_client
from cachier.cores.redis import MissingRedisClient, _RedisCore
from tests.redis_tests.clients import _SyncInMemoryRedis
Expand Down Expand Up @@ -130,6 +132,43 @@ def _test_redis_caching(arg_1, arg_2):
assert val6 == val5


@pytest.mark.redis
def test_redis_key_prefix_uses_global_default_and_decorator_override():
_copied_defaults = replace(cachier_config.get_global_params())

set_global_params(key_prefix="global-prefix")

class PrefixCapturingRedis(_SyncInMemoryRedis):
def __init__(self):
super().__init__()
self.keys_used: list[str] = []

def hset(self, key: str, field=None, value=None, mapping=None, **kwargs):
self.keys_used.append(key)
return super().hset(key, field=field, value=value, mapping=mapping, **kwargs)

global_client = PrefixCapturingRedis()
explicit_client = PrefixCapturingRedis()

@cachier(backend="redis", redis_client=global_client)
def cached_with_global_prefix(x: int) -> int:
return x + 1

@cachier(backend="redis", redis_client=explicit_client, key_prefix="explicit-prefix")
def cached_with_explicit_prefix(x: int) -> int:
return x + 1

assert cached_with_global_prefix(1) == 2
assert cached_with_explicit_prefix(1) == 2

cachier_config.set_global_params(**vars(_copied_defaults))

assert global_client.keys_used, "Redis client was not called"
assert explicit_client.keys_used, "Redis client was not called"
assert all(key.startswith("global-prefix:") for key in global_client.keys_used)
assert all(key.startswith("explicit-prefix:") for key in explicit_client.keys_used)


@pytest.mark.redis
def test_redis_stale_after():
"""Testing Redis core stale_after functionality."""
Expand Down
1 change: 1 addition & 0 deletions tests/test_core_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def test_get_default_params():
"cleanup_stale",
"entry_size_limit",
"hash_func",
"key_prefix",
"mongetter",
"next_time",
"pickle_reload",
Expand Down