1from importlib import import_module 2from funcy import memoize, merge 3 4from django.conf import settings as base_settings 5from django.core.exceptions import ImproperlyConfigured 6from django.core.signals import setting_changed 7 8 9ALL_OPS = {'get', 'fetch', 'count', 'aggregate', 'exists'} 10 11 12class Defaults: 13 CACHEOPS_ENABLED = True 14 CACHEOPS_REDIS = {} 15 CACHEOPS_DEFAULTS = {} 16 CACHEOPS = {} 17 CACHEOPS_PREFIX = lambda query: '' 18 CACHEOPS_LRU = False 19 CACHEOPS_CLIENT_CLASS = None 20 CACHEOPS_DEGRADE_ON_FAILURE = False 21 CACHEOPS_SENTINEL = {} 22 # NOTE: we don't use this fields in invalidator conditions since their values could be very long 23 # and one should not filter by their equality anyway. 24 CACHEOPS_SKIP_FIELDS = "FileField", "TextField", "BinaryField", "JSONField" 25 CACHEOPS_LONG_DISJUNCTION = 8 26 CACHEOPS_SERIALIZER = 'pickle' 27 28 FILE_CACHE_DIR = '/tmp/cacheops_file_cache' 29 FILE_CACHE_TIMEOUT = 60*60*24*30 30 31 32class Settings(object): 33 def __getattr__(self, name): 34 res = getattr(base_settings, name, getattr(Defaults, name)) 35 if name in ['CACHEOPS_PREFIX', 'CACHEOPS_SERIALIZER']: 36 res = import_string(res) if isinstance(res, str) else res 37 38 # Convert old list of classes to list of strings 39 if name == 'CACHEOPS_SKIP_FIELDS': 40 res = [f if isinstance(f, str) else f.get_internal_type(res) for f in res] 41 42 # Save to dict to speed up next access, __getattr__ won't be called 43 self.__dict__[name] = res 44 return res 45 46settings = Settings() 47setting_changed.connect(lambda setting, **kw: settings.__dict__.pop(setting, None), weak=False) 48 49 50def import_string(path): 51 if "." in path: 52 module, attr = path.rsplit(".", 1) 53 return getattr(import_module(module), attr) 54 else: 55 return import_module(path) 56 57 58@memoize 59def prepare_profiles(): 60 """ 61 Prepares a dict 'app.model' -> profile, for use in model_profile() 62 """ 63 profile_defaults = { 64 'ops': (), 65 'local_get': False, 66 'db_agnostic': True, 67 'lock': False, 68 } 69 profile_defaults.update(settings.CACHEOPS_DEFAULTS) 70 71 model_profiles = {} 72 for app_model, profile in settings.CACHEOPS.items(): 73 if profile is None: 74 model_profiles[app_model.lower()] = None 75 continue 76 77 model_profiles[app_model.lower()] = mp = merge(profile_defaults, profile) 78 if mp['ops'] == 'all': 79 mp['ops'] = ALL_OPS 80 # People will do that anyway :) 81 if isinstance(mp['ops'], str): 82 mp['ops'] = {mp['ops']} 83 mp['ops'] = set(mp['ops']) 84 85 if 'timeout' not in mp: 86 raise ImproperlyConfigured( 87 'You must specify "timeout" option in "%s" CACHEOPS profile' % app_model) 88 if not isinstance(mp['timeout'], int): 89 raise ImproperlyConfigured( 90 '"timeout" option in "%s" CACHEOPS profile should be an integer' % app_model) 91 92 return model_profiles 93 94 95def model_profile(model): 96 """ 97 Returns cacheops profile for a model 98 """ 99 # Django migrations these fake models, we don't want to cache them 100 if model.__module__ == '__fake__': 101 return None 102 103 model_profiles = prepare_profiles() 104 105 app = model._meta.app_label.lower() 106 model_name = model._meta.model_name 107 for guess in ('%s.%s' % (app, model_name), '%s.*' % app, '*.*'): 108 if guess in model_profiles: 109 return model_profiles[guess] 110 else: 111 return None 112