Skip to content
Draft
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
19 changes: 5 additions & 14 deletions base_cacheable_class/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
from .base import BaseCacheableClass
from .cache.in_memory import InMemoryCache, InMemoryCacheDecorator
from .interfaces import CacheDecoratorInterface, CacheInterface
from .cache.async_ import AsyncCacheDecoratorFactory
from .cache.sync import SyncCacheDecoratorFactory
from .interfaces import CacheDecoratorInterface
from .models import CacheItem

__all__ = [
"BaseCacheableClass",
"CacheInterface",
"CacheDecoratorInterface",
"CacheItem",
"InMemoryCache",
"InMemoryCacheDecorator",
"AsyncCacheDecoratorFactory",
"SyncCacheDecoratorFactory",
]

# Conditional export for Redis classes
try:
from .cache.redis import RedisCache, RedisCacheDecorator

__all__.extend(["RedisCache", "RedisCacheDecorator"])
except ImportError:
# Redis is optional
pass
49 changes: 32 additions & 17 deletions base_cacheable_class/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar
Expand All @@ -7,24 +8,42 @@
F = TypeVar("F", bound=Callable[..., Any])


def _wrapper_sync_or_async(func: F, execute_func: Any):
if asyncio.iscoroutinefunction(func):

@wraps(func)
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
return await execute_func(self, *args, **kwargs)

return wrapper

@wraps(func)
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
return execute_func(self, *args, **kwargs)

return wrapper


class BaseCacheableClass:
def __init__(self, cache_decorator: CacheDecoratorInterface) -> None:
self._cache_decorator = cache_decorator

def get_cache_client(self):
return self._cache_decorator.cache

def wrapped(self, func: F) -> F:
return self._cache_decorator()(func) # type: ignore

@classmethod
def cache(cls, ttl: int | None = None) -> Callable[[F], F]:
# Note: if `ttl` is None, then the cache is stored forever in-memory.
def decorator(func: F) -> F:
@wraps(func)
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
def decorator(func: F) -> Any:
def _execute(self, *args, **kwargs) -> Any:
if not hasattr(self, "_cache_decorator"):
raise AttributeError("_cache_decorator not found. Did you call super().__init__?")
return await self._cache_decorator(ttl=ttl)(func)(self, *args, **kwargs)
return self._cache_decorator(ttl=ttl)(func)(self, *args, **kwargs)

return wrapper # type: ignore
return _wrapper_sync_or_async(func, _execute)

return decorator

Expand All @@ -37,28 +56,24 @@ def invalidate(cls, target_func_name: str, param_mapping: dict[str, str] | None
예: {'user_id': 'customer_id'} -> 현재 함수의 customer_id를 target_func의 user_id로 매핑
"""

def decorator(func: F) -> F:
@wraps(func)
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
def decorator(func: F) -> Any:
def _execute(self, *args, **kwargs) -> Any:
if not hasattr(self, "_cache_decorator"):
raise AttributeError("_cache_decorator not found. Did you call super().__init__?")
return await self._cache_decorator.invalidate(target_func_name, param_mapping)(func)(
self, *args, **kwargs
)
return self._cache_decorator.invalidate(target_func_name, param_mapping)(func)(self, *args, **kwargs)

return wrapper # type: ignore
return _wrapper_sync_or_async(func, _execute)

return decorator

@classmethod
def invalidate_all(cls) -> Callable[[F], F]:
def decorator(func: F) -> F:
@wraps(func)
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
def decorator(func: F) -> Any:
def _execute(self, *args, **kwargs) -> Any:
if not hasattr(self, "_cache_decorator"):
raise AttributeError("_cache_decorator not found. Did you call super().__init__?")
return await self._cache_decorator.invalidate_all()(func)(self, *args, **kwargs)
return self._cache_decorator.invalidate_all()(func)(self, *args, **kwargs)

return wrapper # type: ignore
return _wrapper_sync_or_async(func, _execute)

return decorator
47 changes: 47 additions & 0 deletions base_cacheable_class/cache/async_/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from .decorator import CacheDecorator
from .in_memory import InMemoryCache
from .interface import CacheInterface

__all__ = ["InMemoryCache", "CacheInterface", "CacheDecorator", "AsyncCacheDecoratorFactory"]

# Conditional export for Redis classes
try:
from .redis.cache import RedisCache

__all__.extend(["RedisCache"])
except ImportError:
# Redis is optional
pass


from ..utils.key_builders import default_key, default_pattern


class AsyncCacheDecoratorFactory:
@classmethod
def in_memory(cls, default_ttl: int = 60) -> CacheDecorator:
cache = InMemoryCache()
return CacheDecorator(cache, key_builder=default_key, pattern_builder=default_pattern, default_ttl=default_ttl)

@classmethod
def redis(
cls,
host: str,
port: int,
password: str,
username: str,
db: int = 0,
socket_timeout: float = 0.5,
socket_connect_timeout: float = 0.5,
default_ttl: int = 60,
) -> CacheDecorator:
cache = RedisCache(
host=host,
port=port,
password=password,
username=username,
db=db,
socket_timeout=socket_timeout,
socket_connect_timeout=socket_connect_timeout,
)
return CacheDecorator(cache, key_builder=default_key, pattern_builder=default_pattern, default_ttl=default_ttl)
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@
from functools import wraps
from typing import Any

from ...interfaces import CacheDecoratorInterface, CacheInterface
from ...interfaces import CacheDecoratorInterface
from ..utils.key_builders import default_key, default_pattern
from .interface import CacheInterface

logger = logging.getLogger(__name__)


class InMemoryCacheDecorator(CacheDecoratorInterface):
def __init__(self, cache: CacheInterface, default_ttl: int = 60):
class CacheDecorator(CacheDecoratorInterface):
def __init__(
self, cache: CacheInterface, key_builder=default_key, pattern_builder=default_pattern, default_ttl: int = 60
):
self.cache = cache
self.default_ttl = default_ttl

def key_builder(self, f: Callable[..., Any], *args: Any, **kwargs: Any) -> str:
arg_str = str(args)
kwarg_str = str(kwargs) if kwargs else "{}"
func_name = getattr(f, "__name__", "unknown")
return f"{func_name}:{arg_str}:{kwarg_str}"
self._key_builder = key_builder
self._pattern_builder = pattern_builder

def __call__(self, ttl: int | None = None) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
_key = self.key_builder(func, *args, **kwargs)
_key = self._key_builder(func, *args, **kwargs)
current_ttl = ttl if ttl is not None else self.default_ttl

try:
Expand Down Expand Up @@ -52,17 +52,7 @@ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
pattern = rf"{target_func_name}:\(.*\):{{.*}}"
if param_mapping:
# 매핑된 파라미터 값 추출
mapped_kwargs = {
target_param: kwargs[source_param]
for target_param, source_param in param_mapping.items()
if source_param in kwargs
}
kwargs_patterns = [rf".*'{k}':\s*'{v!s}'" for k, v in mapped_kwargs.items()]
pattern = rf"{target_func_name}:\(.*\):{{" + ".*".join(kwargs_patterns) + ".*}"

pattern = self._pattern_builder(target_func_name, param_mapping, **kwargs)
cached_keys = await self.cache.get_keys(pattern)

for cache_key in cached_keys:
Expand All @@ -81,7 +71,7 @@ def invalidate_all(self) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
_key = self.key_builder(func, *args, **kwargs)
_key = self._key_builder(func, *args, **kwargs)
try:
await self.cache.clear()
except Exception as e:
Expand Down
3 changes: 3 additions & 0 deletions base_cacheable_class/cache/async_/in_memory/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .cache import InMemoryCache

__all__ = ["InMemoryCache"]
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import time
from typing import Any, cast

from ...interfaces import CacheInterface
from ...models import CacheItem
from ....models import CacheItem
from ..interface import CacheInterface


class InMemoryCache(CacheInterface):
Expand Down
3 changes: 3 additions & 0 deletions base_cacheable_class/cache/async_/redis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .cache import RedisCache

__all__ = ["RedisCache"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from redis.asyncio import Redis

from base_cacheable_class import CacheInterface
from ..interface import CacheInterface


class RedisCache(CacheInterface):
Expand Down
4 changes: 0 additions & 4 deletions base_cacheable_class/cache/in_memory/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions base_cacheable_class/cache/redis/__init__.py

This file was deleted.

98 changes: 0 additions & 98 deletions base_cacheable_class/cache/redis/decorator.py

This file was deleted.

Loading