Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/cachier/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class Params:
cleanup_interval: timedelta = timedelta(days=1)
entry_size_limit: Optional[int] = None
allow_non_static_methods: bool = False
key_prefix: str = "cachier"
func_prefix: str = "."


_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
3 changes: 2 additions & 1 deletion src/cachier/cores/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def _get_func_str(func: Callable) -> str:
__name__, but at runtime the decorated functions always do.

"""
return f".{func.__module__}.{func.__name__}"
func_prefix = _update_with_defaults(None, "func_prefix")
return f"{func_prefix}{func.__module__}.{func.__name__}"


class _BaseCore(metaclass=abc.ABCMeta):
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
25 changes: 25 additions & 0 deletions tests/redis_tests/test_redis_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,31 @@ def _test_redis_caching(arg_1, arg_2):
assert val6 == val5


@pytest.mark.redis
def test_redis_key_prefix_passed_to_client():
pytest.importorskip("redis")

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)

client = PrefixCapturingRedis()

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

result = 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)


@pytest.mark.redis
def test_redis_stale_after():
"""Testing Redis core stale_after functionality."""
Expand Down
18 changes: 17 additions & 1 deletion tests/test_base_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import pytest

from cachier.cores.base import RecalculationNeeded, _BaseCore
from cachier.config import get_global_params, set_global_params
from cachier.cores.base import RecalculationNeeded, _BaseCore, _get_func_str


class ConcreteCachingCore(_BaseCore):
Expand Down Expand Up @@ -82,6 +83,21 @@ def test_should_store_exception():
assert core._should_store("test_value") is True


def test_get_func_str_honors_global_func_prefix():
"""Test _get_func_str prepends the configured func_prefix."""
original_prefix = get_global_params().func_prefix
try:
set_global_params(func_prefix="custom-prefix-")

def dummy_function():
return None

expected = f"custom-prefix-{dummy_function.__module__}.{dummy_function.__name__}"
assert _get_func_str(dummy_function) == expected
finally:
set_global_params(func_prefix=original_prefix)


@pytest.mark.asyncio
async def test_base_core_async_default_wrappers():
"""Test async default wrappers delegate correctly to sync methods."""
Expand Down
2 changes: 2 additions & 0 deletions tests/test_core_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ def test_get_default_params():
"cleanup_interval",
"cleanup_stale",
"entry_size_limit",
"func_prefix",
"hash_func",
"key_prefix",
"mongetter",
"next_time",
"pickle_reload",
Expand Down