import logging import datetime import traceback import pandas as pd import numpy as np import tango import ArchiveExtractor as ae import ArchiveExtractor.Amenities as aea import ArchiveExtractor.Core as aec ########################################################################## ### Install logger for the module ### ########################################################################## logger = logging.getLogger("ArchiveExtractor") if not logger.hasHandlers(): # No handlers, create one sh = logging.StreamHandler() sh.setLevel(logger.level) sh.setFormatter(logging.Formatter("{name}-{levelname:8}: {message}", style='{')) logger.addHandler(sh) ########################################################################## ### Module initialisation functions ### ########################################################################## def init( HdbExtractorPath="archiving/hdbextractor/2", TdbExtractorPath="archiving/tdbextractor/2", loglevel="info", ): """ Initialize the module. Instanciate tango.DeviceProxy for extractors (TDB and HDB) Parameters: ----------- HdbExtractorPath, TdbExtractorPath: string Tango path to the extractors. loglevel: string loglevel to pass to logging.Logger """ ae._Extractors = (None, None) ae._AttrTables = (None, None) _Extr = [] _Attr = [] try: logger.setLevel(getattr(logging, loglevel.upper())) except AttributeError: logger.error("Wrong log level specified: {}".format(loglevel.upper())) logger.error("Init module failed. You can try to run ArchiveExtractor.init() again with another configuration") return logger.debug("Instanciating extractors device proxy...") for p in [HdbExtractorPath, TdbExtractorPath]: try: _Extr.append(tango.DeviceProxy(p)) except tango.DevFailed: logger.error("Could not find extractor {}".format(p)) logger.error("Init module failed. You can try to run ArchiveExtractor.init() again with another configuration") return logger.debug("{} and {} instanciated.".format(*_Extr)) logger.debug("Configuring extractors device proxy...") for e in _Extr: # set timeout to 3 sec e.set_timeout_millis(3000) logger.debug("Filling attributes lookup tables (use GetAttNameAll)...") for e in _Extr: try: _Attr.append(e.getattnameall()) except (tango.DevFailed, AttributeError): logger.error("Could not fetch attributes for extractor {}".format(e)) logger.error("Init module failed. You can try to run ArchiveExtractor.init() again with another configuration") return logger.debug("HDB: {} TDB: {} attributes counted".format(len(_Attr[0]), len(_Attr[1]))) ae._Extractors = tuple(_Extr) ae._AttrTables = tuple(_Attr) ########################################################################## ### Module access functions ### ########################################################################## def extract( attr, date1, date2=None, method="nearest", db='H', ): """ Access function to perform extraction between date1 and date2. Can extract one or several attributes. date1 and date2 can be both exact date, or one of two can be a time interval that will be taken relative to the other. Parameters: ----------- attr: string, list, dict Attribute(s) to extract. If string, extract the given attribute, returning a pandas.Series. If list, extract attributes and return a list of pandas.Series. If a dict, extract attributes and return a dict of pandas.Series with same keys. date1, date2: string, datetime.datetime, datetime.timedelta, None Exact date, or duration relative to date2. If string, it will be parsed. A start date can be given with string format '%Y-%m-%d-%H:%M:%S' or less precise (ie '2021-02', '2022-11-03' '2022-05-10-21:00'.i..). A duration can be given with string format 'Xu' where X is a number and u is a unit in ('m':minutes, 'h':hours, 'd':days, 'M':months) A datetime.datetime object or datetime.timedelta object will be used as is. date2 can be None. In that case it is replaced by the current time. method: str Method of extraction 'nearest': Retrieve nearest value of date1, date2 is ignored. 'between': Retrive data between date1 and date2. db: str Which database to look in, 'H' or 'T'. """ ## _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ # Perform a few sanity checks if not aea._check_initialized(): # Stop here, the function has produced a message if necessary return if not db in ("H", "T"): raise ValueError("Attribute 'db' should be 'H' or 'T'") allowedmethods=("nearest", "between", "minmaxmean") if not method in allowedmethods: raise ValueError("Attribute 'method' should be in {}".format(str(allowedmethods))) ## _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ # Work with dates if not type(date1) in (datetime.datetime, datetime.timedelta): date1 = aea._dateparse(date1) if date2 is None: date2 = datetime.datetime.now() else: if not type(date2) in (datetime.datetime, datetime.timedelta): date2 = aea._dateparse(date2) if not datetime.datetime in (type(date1), type(date2)): logger.error("One of date1 date2 should be an exact date.\nGot {} {}".format(date1, date2)) raise ValueError("date1 and date2 not valid") # Use timedelta relative to the other date. date1 is always before date2 if type(date1) is datetime.timedelta: date1 = date2-date1 if type(date2) is datetime.timedelta: date2 = date1+date2 if date1 > date2: logger.error("date1 must precede date2.\nGot {} {}".format(date1, date2)) raise ValueError("date1 and date2 not valid") ## _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ # Perform extraction and return if type(attr) is dict: d=dict() for k,v in attr.items(): try: d.update({k:aec._extract_attribute(v, method, date1, date2, db)}) except Exception as e: logger.debug("Exception in _extract_attribute(): "+str(e)) logger.debug(traceback.print_tb(e.__traceback__)) logger.error("Could not extract {}.".format(v)) return d if type(attr) in (list,tuple): d=[] for v in attr: try: d.append(aec._extract_attribute(v, method, date1, date2, db)) except Exception as e: logger.debug("Exception in _extract_attribute(): "+str(e)) logger.debug(traceback.print_tb(e.__traceback__)) logger.error("Could not extract {}.".format(v)) return d try: d=aec._extract_attribute(attr, method, date1, date2, db) except Exception as e: logger.debug("Exception in _extract_attribute(): "+str(e)) logger.debug(traceback.print_tb(e.__traceback__)) logger.error("Could not extract {}.".format(attr)) return None return d ##----------------------------------------------------------------------## def findattr(pattern, db="H"): """ Search for an attribute path using the pattern given. Case insensitive. Parameters: ----------- pattern: str Pattern to search, wildchar * accepted. example "dg*dcct*current" db: str Which database to look in, 'H' or 'T'. Returns: -------- results: (str,) List of string match """ if not aea._check_initialized(): return if not db in ("H", "T"): raise AttributeError("Attribute db should be 'H' or 'T'") keywords=pattern.lower().split('*') # Select DB attr_table = ae._AttrTables[{'H':0, 'T':1}[db]] matches = [attr for attr in attr_table if all(k in attr.lower() for k in keywords)] return matches ##----------------------------------------------------------------------## def infoattr(attribute, db='H'): """ Get informations for an attribute and pack it into a python dict. Parameters ---------- attribute : String Name of the attribute. Full Tango name i.e. "test/dg/panda/current". db: str Which database to look in, 'H' or 'T'. Returns ------- info : dict Dictionnary of propertyname:propertyvalue """ if not aea._check_initialized(): return if not db in ("H", "T"): raise AttributeError("Attribute db should be 'H' or 'T'") info = dict() for func in ("GetAttDefinitionData", "GetAttPropertiesData"): R=getattr(ae._Extractors[{'H':0, 'T':1}[db]], func)(attribute) if not R is None: for i in R: _s=i.split("::") info[_s[0]]=_s[1] else: logger.warning("Function %s on extractor returned None"%func) return info