Spaces:
Sleeping
Sleeping
File size: 16,110 Bytes
f3192a6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
"""Django-compatible disk and file backed cache."""
from functools import wraps
from django.core.cache.backends.base import BaseCache
try:
from django.core.cache.backends.base import DEFAULT_TIMEOUT
except ImportError: # pragma: no cover
# For older versions of Django simply use 300 seconds.
DEFAULT_TIMEOUT = 300
from .core import ENOVAL, args_to_key, full_name
from .fanout import FanoutCache
class DjangoCache(BaseCache):
"""Django-compatible disk and file backed cache."""
def __init__(self, directory, params):
"""Initialize DjangoCache instance.
:param str directory: cache directory
:param dict params: cache parameters
"""
super().__init__(params)
shards = params.get('SHARDS', 8)
timeout = params.get('DATABASE_TIMEOUT', 0.010)
options = params.get('OPTIONS', {})
self._cache = FanoutCache(directory, shards, timeout, **options)
@property
def directory(self):
"""Cache directory."""
return self._cache.directory
def cache(self, name):
"""Return Cache with given `name` in subdirectory.
:param str name: subdirectory name for Cache
:return: Cache with given name
"""
return self._cache.cache(name)
def deque(self, name, maxlen=None):
"""Return Deque with given `name` in subdirectory.
:param str name: subdirectory name for Deque
:param maxlen: max length (default None, no max)
:return: Deque with given name
"""
return self._cache.deque(name, maxlen=maxlen)
def index(self, name):
"""Return Index with given `name` in subdirectory.
:param str name: subdirectory name for Index
:return: Index with given name
"""
return self._cache.index(name)
def add(
self,
key,
value,
timeout=DEFAULT_TIMEOUT,
version=None,
read=False,
tag=None,
retry=True,
):
"""Set a value in the cache if the key does not already exist. If
timeout is given, that timeout will be used for the key; otherwise the
default cache timeout will be used.
Return True if the value was stored, False otherwise.
:param key: key for item
:param value: value for item
:param float timeout: seconds until the item expires
(default 300 seconds)
:param int version: key version number (default None, cache parameter)
:param bool read: read value as bytes from file (default False)
:param str tag: text to associate with key (default None)
:param bool retry: retry if database timeout occurs (default True)
:return: True if item was added
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
timeout = self.get_backend_timeout(timeout=timeout)
return self._cache.add(key, value, timeout, read, tag, retry)
def get(
self,
key,
default=None,
version=None,
read=False,
expire_time=False,
tag=False,
retry=False,
):
"""Fetch a given key from the cache. If the key does not exist, return
default, which itself defaults to None.
:param key: key for item
:param default: return value if key is missing (default None)
:param int version: key version number (default None, cache parameter)
:param bool read: if True, return file handle to value
(default False)
:param float expire_time: if True, return expire_time in tuple
(default False)
:param tag: if True, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: value for item if key is found else default
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
return self._cache.get(key, default, read, expire_time, tag, retry)
def read(self, key, version=None):
"""Return file handle corresponding to `key` from Cache.
:param key: Python key to retrieve
:param int version: key version number (default None, cache parameter)
:return: file open for reading in binary mode
:raises KeyError: if key is not found
"""
key = self.make_key(key, version=version)
return self._cache.read(key)
def set(
self,
key,
value,
timeout=DEFAULT_TIMEOUT,
version=None,
read=False,
tag=None,
retry=True,
):
"""Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
:param key: key for item
:param value: value for item
:param float timeout: seconds until the item expires
(default 300 seconds)
:param int version: key version number (default None, cache parameter)
:param bool read: read value as bytes from file (default False)
:param str tag: text to associate with key (default None)
:param bool retry: retry if database timeout occurs (default True)
:return: True if item was set
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
timeout = self.get_backend_timeout(timeout=timeout)
return self._cache.set(key, value, timeout, read, tag, retry)
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, retry=True):
"""Touch a key in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
:param key: key for item
:param float timeout: seconds until the item expires
(default 300 seconds)
:param int version: key version number (default None, cache parameter)
:param bool retry: retry if database timeout occurs (default True)
:return: True if key was touched
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
timeout = self.get_backend_timeout(timeout=timeout)
return self._cache.touch(key, timeout, retry)
def pop(
self,
key,
default=None,
version=None,
expire_time=False,
tag=False,
retry=True,
):
"""Remove corresponding item for `key` from cache and return value.
If `key` is missing, return `default`.
Operation is atomic. Concurrent operations will be serialized.
:param key: key for item
:param default: return value if key is missing (default None)
:param int version: key version number (default None, cache parameter)
:param float expire_time: if True, return expire_time in tuple
(default False)
:param tag: if True, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default True)
:return: value for item if key is found else default
"""
key = self.make_key(key, version=version)
return self._cache.pop(key, default, expire_time, tag, retry)
def delete(self, key, version=None, retry=True):
"""Delete a key from the cache, failing silently.
:param key: key for item
:param int version: key version number (default None, cache parameter)
:param bool retry: retry if database timeout occurs (default True)
:return: True if item was deleted
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
return self._cache.delete(key, retry)
def incr(self, key, delta=1, version=None, default=None, retry=True):
"""Increment value by delta for item with key.
If key is missing and default is None then raise KeyError. Else if key
is missing and default is not None then use default for value.
Operation is atomic. All concurrent increment operations will be
counted individually.
Assumes value may be stored in a SQLite column. Most builds that target
machines with 64-bit pointer widths will support 64-bit signed
integers.
:param key: key for item
:param int delta: amount to increment (default 1)
:param int version: key version number (default None, cache parameter)
:param int default: value if key is missing (default None)
:param bool retry: retry if database timeout occurs (default True)
:return: new value for item on success else None
:raises ValueError: if key is not found and default is None
"""
# pylint: disable=arguments-differ
key = self.make_key(key, version=version)
try:
return self._cache.incr(key, delta, default, retry)
except KeyError:
raise ValueError("Key '%s' not found" % key) from None
def decr(self, key, delta=1, version=None, default=None, retry=True):
"""Decrement value by delta for item with key.
If key is missing and default is None then raise KeyError. Else if key
is missing and default is not None then use default for value.
Operation is atomic. All concurrent decrement operations will be
counted individually.
Unlike Memcached, negative values are supported. Value may be
decremented below zero.
Assumes value may be stored in a SQLite column. Most builds that target
machines with 64-bit pointer widths will support 64-bit signed
integers.
:param key: key for item
:param int delta: amount to decrement (default 1)
:param int version: key version number (default None, cache parameter)
:param int default: value if key is missing (default None)
:param bool retry: retry if database timeout occurs (default True)
:return: new value for item on success else None
:raises ValueError: if key is not found and default is None
"""
# pylint: disable=arguments-differ
return self.incr(key, -delta, version, default, retry)
def has_key(self, key, version=None):
"""Returns True if the key is in the cache and has not expired.
:param key: key for item
:param int version: key version number (default None, cache parameter)
:return: True if key is found
"""
key = self.make_key(key, version=version)
return key in self._cache
def expire(self):
"""Remove expired items from cache.
:return: count of items removed
"""
return self._cache.expire()
def stats(self, enable=True, reset=False):
"""Return cache statistics hits and misses.
:param bool enable: enable collecting statistics (default True)
:param bool reset: reset hits and misses to 0 (default False)
:return: (hits, misses)
"""
return self._cache.stats(enable=enable, reset=reset)
def create_tag_index(self):
"""Create tag index on cache database.
Better to initialize cache with `tag_index=True` than use this.
:raises Timeout: if database timeout occurs
"""
self._cache.create_tag_index()
def drop_tag_index(self):
"""Drop tag index on cache database.
:raises Timeout: if database timeout occurs
"""
self._cache.drop_tag_index()
def evict(self, tag):
"""Remove items with matching `tag` from cache.
:param str tag: tag identifying items
:return: count of items removed
"""
return self._cache.evict(tag)
def cull(self):
"""Cull items from cache until volume is less than size limit.
:return: count of items removed
"""
return self._cache.cull()
def clear(self):
"""Remove *all* values from the cache at once."""
return self._cache.clear()
def close(self, **kwargs):
"""Close the cache connection."""
# pylint: disable=unused-argument
self._cache.close()
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
"""Return seconds to expiration.
:param float timeout: seconds until the item expires
(default 300 seconds)
"""
if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
elif timeout == 0:
# ticket 21147 - avoid time.time() related precision issues
timeout = -1
return None if timeout is None else timeout
def memoize(
self,
name=None,
timeout=DEFAULT_TIMEOUT,
version=None,
typed=False,
tag=None,
ignore=(),
):
"""Memoizing cache decorator.
Decorator to wrap callable with memoizing function using cache.
Repeated calls with the same arguments will lookup result in cache and
avoid function evaluation.
If name is set to None (default), the callable name will be determined
automatically.
When timeout is set to zero, function results will not be set in the
cache. Cache lookups still occur, however. Read
:doc:`case-study-landing-page-caching` for example usage.
If typed is set to True, function arguments of different types will be
cached separately. For example, f(3) and f(3.0) will be treated as
distinct calls with distinct results.
The original underlying function is accessible through the __wrapped__
attribute. This is useful for introspection, for bypassing the cache,
or for rewrapping the function with a different cache.
An additional `__cache_key__` attribute can be used to generate the
cache key used for the given arguments.
Remember to call memoize when decorating a callable. If you forget,
then a TypeError will occur.
:param str name: name given for callable (default None, automatic)
:param float timeout: seconds until the item expires
(default 300 seconds)
:param int version: key version number (default None, cache parameter)
:param bool typed: cache different types separately (default False)
:param str tag: text to associate with arguments (default None)
:param set ignore: positional or keyword args to ignore (default ())
:return: callable decorator
"""
# Caution: Nearly identical code exists in Cache.memoize
if callable(name):
raise TypeError('name cannot be callable')
def decorator(func):
"""Decorator created by memoize() for callable `func`."""
base = (full_name(func),) if name is None else (name,)
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper for callable to cache arguments and return values."""
key = wrapper.__cache_key__(*args, **kwargs)
result = self.get(key, ENOVAL, version, retry=True)
if result is ENOVAL:
result = func(*args, **kwargs)
valid_timeout = (
timeout is None
or timeout == DEFAULT_TIMEOUT
or timeout > 0
)
if valid_timeout:
self.set(
key,
result,
timeout,
version,
tag=tag,
retry=True,
)
return result
def __cache_key__(*args, **kwargs):
"""Make key for cache given function arguments."""
return args_to_key(base, args, kwargs, typed, ignore)
wrapper.__cache_key__ = __cache_key__
return wrapper
return decorator
|