"""
|
|
This caching is very important for speed and memory optimizations. There's
|
|
nothing really spectacular, just some decorators. The following cache types are
|
|
available:
|
|
|
|
- ``time_cache`` can be used to cache something for just a limited time span,
|
|
which can be useful if there's user interaction and the user cannot react
|
|
faster than a certain time.
|
|
|
|
This module is one of the reasons why |jedi| is not thread-safe. As you can see
|
|
there are global variables, which are holding the cache information. Some of
|
|
these variables are being cleaned after every API usage.
|
|
"""
|
|
import time
|
|
from functools import wraps
|
|
|
|
from jedi import settings
|
|
from parso.cache import parser_cache
|
|
|
|
_time_caches = {}
|
|
|
|
|
|
def underscore_memoization(func):
|
|
"""
|
|
Decorator for methods::
|
|
|
|
class A(object):
|
|
def x(self):
|
|
if self._x:
|
|
self._x = 10
|
|
return self._x
|
|
|
|
Becomes::
|
|
|
|
class A(object):
|
|
@underscore_memoization
|
|
def x(self):
|
|
return 10
|
|
|
|
A now has an attribute ``_x`` written by this decorator.
|
|
"""
|
|
name = '_' + func.__name__
|
|
|
|
def wrapper(self):
|
|
try:
|
|
return getattr(self, name)
|
|
except AttributeError:
|
|
result = func(self)
|
|
setattr(self, name, result)
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
|
|
def clear_time_caches(delete_all=False):
|
|
""" Jedi caches many things, that should be completed after each completion
|
|
finishes.
|
|
|
|
:param delete_all: Deletes also the cache that is normally not deleted,
|
|
like parser cache, which is important for faster parsing.
|
|
"""
|
|
global _time_caches
|
|
|
|
if delete_all:
|
|
for cache in _time_caches.values():
|
|
cache.clear()
|
|
parser_cache.clear()
|
|
else:
|
|
# normally just kill the expired entries, not all
|
|
for tc in _time_caches.values():
|
|
# check time_cache for expired entries
|
|
for key, (t, value) in list(tc.items()):
|
|
if t < time.time():
|
|
# delete expired entries
|
|
del tc[key]
|
|
|
|
|
|
def call_signature_time_cache(time_add_setting):
|
|
"""
|
|
This decorator works as follows: Call it with a setting and after that
|
|
use the function with a callable that returns the key.
|
|
But: This function is only called if the key is not available. After a
|
|
certain amount of time (`time_add_setting`) the cache is invalid.
|
|
|
|
If the given key is None, the function will not be cached.
|
|
"""
|
|
def _temp(key_func):
|
|
dct = {}
|
|
_time_caches[time_add_setting] = dct
|
|
|
|
def wrapper(*args, **kwargs):
|
|
generator = key_func(*args, **kwargs)
|
|
key = next(generator)
|
|
try:
|
|
expiry, value = dct[key]
|
|
if expiry > time.time():
|
|
return value
|
|
except KeyError:
|
|
pass
|
|
|
|
value = next(generator)
|
|
time_add = getattr(settings, time_add_setting)
|
|
if key is not None:
|
|
dct[key] = time.time() + time_add, value
|
|
return value
|
|
return wrapper
|
|
return _temp
|
|
|
|
|
|
def time_cache(seconds):
|
|
def decorator(func):
|
|
cache = {}
|
|
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
key = (args, frozenset(kwargs.items()))
|
|
try:
|
|
created, result = cache[key]
|
|
if time.time() < created + seconds:
|
|
return result
|
|
except KeyError:
|
|
pass
|
|
result = func(*args, **kwargs)
|
|
cache[key] = time.time(), result
|
|
return result
|
|
|
|
wrapper.clear_cache = lambda: cache.clear()
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
def memoize_method(method):
|
|
"""A normal memoize function."""
|
|
@wraps(method)
|
|
def wrapper(self, *args, **kwargs):
|
|
cache_dict = self.__dict__.setdefault('_memoize_method_dct', {})
|
|
dct = cache_dict.setdefault(method, {})
|
|
key = (args, frozenset(kwargs.items()))
|
|
try:
|
|
return dct[key]
|
|
except KeyError:
|
|
result = method(self, *args, **kwargs)
|
|
dct[key] = result
|
|
return result
|
|
return wrapper
|