class ICATCache(object): """ A wrapper for ICATCommunication that caches information, and in the case of ICAT failure will try to use this cache. It stores Cache models in the database, and will get all fields from ICATCommunication if a model is requested but has expired or doesn't exist. Most of the methods it wraps from ICATCommunication are templated rather than declared explicitly; see below. """ def __init__(self, **kwargs): self.kwargs = kwargs self.icat = None self.cache_lifetime = CACHE_LIFETIME def __enter__(self): return self def __exit__(self, type, value, traceback): if self.icat is not None: self.icat.__exit__(type, value, traceback) def open_icat(self): """ Try to open an ICAT session, if we don't have one already. """ try: if self.icat is None: self.icat = ICATCommunication(**self.kwargs) except Exception as e: logger.error("Failed to connect to ICAT: %s - %s" % (type(e).__name, e)) raise ICATConnectionException() def is_valid(self, cache_obj): """ Check whether a cache object is fresh and is not None. """ return cache_obj and cache_obj.created + datetime.timedelta(seconds=self.cache_lifetime) > timezone.now() def to_list(self, l): return ",".join(map(str, l)) def cull_invalid(self, list): """ Removes all objects in the list that have expired. """ rlist = [] for obj in list: obj.delete() if not self.is_valid(obj) else rlist.append(obj) return rlist def update_cache(self, obj_type, obj_id): """ Adds an object of type obj_type and id obj_id to the cache - querying ICAT - and returns the object. E.g., obj_type = InstrumentCache, obj_id = "WISH". """ self.open_icat() # Open an ICAT session if we don't have one open. if obj_type != ExperimentCache: # Check func_list for the attributes that each model should have, and the corresponding ICATCommunication function to query for it; call it for each, building a dict, and then splice it into the constructor kwargs. new_obj = obj_type(**{attr:(getattr(self.icat, func)(obj_id) if typ is None else self.to_list(getattr(self.icat, func)(obj_id))) for (func, model, attr, typ) in func_list if model == obj_type}) else: # In this case, ICATCommunication returns all the ExperimentCache fields in one query, so we splice that into the constructor. new_obj = obj_type(**{attr:str(val) for attr,val in self.icat.get_experiment_details(obj_id).iteritems() if attr is not "reference_number"}) new_obj.id_name = obj_id new_obj.save() return new_obj def check_cache(self, obj_type, obj_id): """ Checks the cache for an object of type obj_type and id obj_id - querying for a new one if there isn't a fresh copy - and returns it. If ICAT is unavailable, use a local copy if it exists. If we can't use anything, return None. """ in_cache = obj_type.objects.filter(id_name = obj_id).order_by("-created") ret_obj = None if in_cache: ret_obj = in_cache[0] if not self.is_valid(ret_obj): try: ret_obj = self.update_cache(obj_type, obj_id) self.cull_invalid(in_cache) except ICATConnectionException: pass return ret_obj def get_valid_experiments_for_instruments(self, user_number, instruments): experiment_dict = {} user_experiments = set(self.get_associated_experiments(user_number)) for instrument_name in instruments: instrument_experiments = self.get_valid_experiments_for_instrument(instrument_name) experiment_dict[instrument_name] = list(user_experiments.intersection(instrument_experiments)) return experiment_dict def get_experiment_details(self, experiment_number): experiment = self.check_cache(ExperimentCache, experiment_number) return experiment.__dict__
class ICATCache(object): """ A wrapper for ICATCommunication that caches information, and in the case of ICAT failure will try to use this cache. It stores Cache models in the database, and will get all fields from ICATCommunication if a model is requested but has expired or doesn't exist. Most of the methods it wraps from ICATCommunication are templated rather than declared explicitly; see below. """ def __init__(self, **kwargs): self.kwargs = kwargs self.icat = None self.cache_lifetime = CACHE_LIFETIME def __enter__(self): return self def __exit__(self, type, value, traceback): if self.icat is not None: self.icat.__exit__(type, value, traceback) def open_icat(self): """ Try to open an ICAT session, if we don't have one already. """ try: if self.icat is None: self.icat = ICATCommunication(**self.kwargs) except Exception as e: logger.error("Failed to connect to ICAT: %s - %s" % (type(e).__name, e)) raise ICATConnectionException() def is_valid(self, cache_obj): """ Check whether a cache object is fresh and is not None. """ return cache_obj and cache_obj.created + datetime.timedelta( seconds=self.cache_lifetime) > timezone.now() def to_list(self, l): return ",".join(map(str, l)) def cull_invalid(self, list): """ Removes all objects in the list that have expired. """ rlist = [] for obj in list: obj.delete() if not self.is_valid(obj) else rlist.append(obj) return rlist def update_cache(self, obj_type, obj_id): """ Adds an object of type obj_type and id obj_id to the cache - querying ICAT - and returns the object. E.g., obj_type = InstrumentCache, obj_id = "WISH". """ self.open_icat() # Open an ICAT session if we don't have one open. if obj_type != ExperimentCache: # Check func_list for the attributes that each model should have, and the corresponding ICATCommunication function to query for it; call it for each, building a dict, and then splice it into the constructor kwargs. new_obj = obj_type( **{ attr: (getattr(self.icat, func)(obj_id) if typ is None else self.to_list(getattr(self.icat, func)(obj_id))) for (func, model, attr, typ) in func_list if model == obj_type }) else: # In this case, ICATCommunication returns all the ExperimentCache fields in one query, so we splice that into the constructor. new_obj = obj_type( **{ attr: str(val) for attr, val in self.icat.get_experiment_details( obj_id).iteritems() if attr is not "reference_number" }) new_obj.id_name = obj_id new_obj.save() return new_obj def check_cache(self, obj_type, obj_id): """ Checks the cache for an object of type obj_type and id obj_id - querying for a new one if there isn't a fresh copy - and returns it. If ICAT is unavailable, use a local copy if it exists. If we can't use anything, return None. """ in_cache = obj_type.objects.filter(id_name=obj_id).order_by("-created") ret_obj = None if in_cache: ret_obj = in_cache[0] if not self.is_valid(ret_obj): try: ret_obj = self.update_cache(obj_type, obj_id) self.cull_invalid(in_cache) except ICATConnectionException: pass return ret_obj def get_valid_experiments_for_instruments(self, user_number, instruments): experiment_dict = {} user_experiments = set(self.get_associated_experiments(user_number)) for instrument_name in instruments: instrument_experiments = self.get_valid_experiments_for_instrument( instrument_name) experiment_dict[instrument_name] = list( user_experiments.intersection(instrument_experiments)) return experiment_dict def get_experiment_details(self, experiment_number): experiment = self.check_cache(ExperimentCache, experiment_number) return experiment.__dict__