mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-22 22:03:54 +03:00
merged decorators
This commit is contained in:
commit
b036c4627e
@ -34,6 +34,8 @@ import functools
|
||||
import logging
|
||||
import inspect
|
||||
import typing
|
||||
import time
|
||||
import timeit
|
||||
import threading
|
||||
|
||||
from uds.core.util.cache import Cache
|
||||
@ -43,6 +45,54 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
RT = typing.TypeVar('RT')
|
||||
|
||||
# Caching statistics
|
||||
class StatsType:
|
||||
__slots__ = ('hits', 'misses', 'total', 'start_time', 'saving_time')
|
||||
|
||||
hits: int
|
||||
misses: int
|
||||
total: int
|
||||
start_time: float
|
||||
saving_time: float
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.hits = 0
|
||||
self.misses = 0
|
||||
self.total = 0
|
||||
self.start_time = time.time()
|
||||
self.saving_time = 0
|
||||
|
||||
def add_hit(self, saving_time: float = 0.0) -> None:
|
||||
self.hits += 1
|
||||
self.total += 1
|
||||
self.saving_time += saving_time
|
||||
|
||||
def add_miss(self, saving_time: float = 0.0) -> None:
|
||||
self.misses += 1
|
||||
self.total += 1
|
||||
self.saving_time += saving_time
|
||||
|
||||
@property
|
||||
def uptime(self) -> float:
|
||||
return time.time() - self.start_time
|
||||
|
||||
@property
|
||||
def hit_rate(self) -> float:
|
||||
return self.hits / self.total if self.total > 0 else 0.0
|
||||
|
||||
@property
|
||||
def miss_rate(self) -> float:
|
||||
return self.misses / self.total if self.total > 0 else 0.0
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'CacheStats: {self.hits}/{self.misses} on {self.total}, '
|
||||
f'uptime={self.uptime}, hit_rate={self.hit_rate:.2f}, '
|
||||
f'saving_time={self.saving_time:.2f}'
|
||||
)
|
||||
|
||||
stats = StatsType()
|
||||
|
||||
def _defaultDenyView(request) -> typing.Any:
|
||||
from uds.web.util import errors
|
||||
return errors.errorView(
|
||||
@ -154,12 +204,16 @@ def allowCache(
|
||||
"""Decorator that give us a "quick& clean" caching feature.
|
||||
The "cached" element must provide a "cache" variable, which is a cache object
|
||||
|
||||
:param cachePrefix: the cache key "prefix" (prepended on generated key from args)
|
||||
:param cacheTimeout: The cache timeout in seconds
|
||||
:param cachingArgs: The caching args. Can be a single integer or a list.
|
||||
First arg (self) is 0, so normally cachingArgs are 1, or [1,2,..]
|
||||
:param cachingKWArgs: The caching kwargs. Can be a single string or a list.
|
||||
:param cachingKeyFnc: A function that receives the args and kwargs and returns the key
|
||||
Parameters:
|
||||
cachePrefix: Prefix to use for cache key
|
||||
cacheTimeout: Timeout for cache
|
||||
cachingArgs: List of arguments to use for cache key (i.e. [0, 1] will use first and second argument for cache key, 0 will use "self" if a method, and 1 will use first argument)
|
||||
cachingKWArgs: List of keyword arguments to use for cache key (i.e. ['a', 'b'] will use "a" and "b" keyword arguments for cache key)
|
||||
cachingKeyFnc: Function to use for cache key. If provided, this function will be called with the same arguments as the wrapped function, and must return a string to use as cache key
|
||||
|
||||
Note:
|
||||
If cachingArgs and cachingKWArgs are not provided, the whole arguments will be used for cache key
|
||||
|
||||
"""
|
||||
cacheTimeout = Cache.DEFAULT_VALIDITY if cacheTimeout == -1 else cacheTimeout
|
||||
cachingArgList: typing.List[int] = (
|
||||
@ -172,13 +226,17 @@ def allowCache(
|
||||
def allowCacheDecorator(fnc: typing.Callable[..., RT]) -> typing.Callable[..., RT]:
|
||||
# If no caching args and no caching kwargs, we will cache the whole call
|
||||
# If no parameters provider, try to infer them from function signature
|
||||
setattr(fnc, '__cache_hit__', 0) # Cache hit
|
||||
setattr(fnc, '__cache_miss__', 0) # Cache miss
|
||||
setattr(fnc, '__exec_time__', 0.0) # Execution time
|
||||
try:
|
||||
if not cachingArgList and not cachingKwargList:
|
||||
if cachingArgList is None and cachingKwargList is None:
|
||||
for pos, (paramName, param) in enumerate(
|
||||
inspect.signature(fnc).parameters.items()
|
||||
):
|
||||
if paramName == 'self':
|
||||
continue
|
||||
# Parameters can be included twice in the cache, but it's not a problem
|
||||
if param.kind in (
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.POSITIONAL_ONLY,
|
||||
@ -211,14 +269,20 @@ def allowCache(
|
||||
if not kwargs.get('force', False) and timeout > 0:
|
||||
data = cache.get(cacheKey)
|
||||
if data:
|
||||
logger.debug('Cache hit for %s', cacheKey)
|
||||
setattr(fnc, '__cache_hit__', getattr(fnc, '__cache_hit__') + 1)
|
||||
stats.add_hit(getattr(fnc, '__exec_time__'))
|
||||
return data
|
||||
|
||||
setattr(fnc, '__cache_miss__', getattr(fnc, '__cache_miss__') + 1)
|
||||
stats.add_miss(getattr(fnc, '__exec_time__'))
|
||||
|
||||
if 'force' in kwargs:
|
||||
# Remove force key
|
||||
del kwargs['force']
|
||||
|
||||
time = timeit.default_timer()
|
||||
data = fnc(*args, **kwargs)
|
||||
setattr(fnc, '__exec_time__', getattr(fnc, '__exec_time__') + timeit.default_timer() - time)
|
||||
try:
|
||||
# Maybe returned data is not serializable. In that case, cache will fail but no harm is done with this
|
||||
cache.put(cacheKey, data, timeout)
|
||||
@ -236,6 +300,7 @@ def allowCache(
|
||||
|
||||
return allowCacheDecorator
|
||||
|
||||
|
||||
# Decorator to execute method in a thread
|
||||
def threaded(func: typing.Callable[..., None]) -> typing.Callable[..., None]:
|
||||
"""Decorator to execute method in a thread"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user