diff --git a/fandango/CHANGES b/fandango/CHANGES index b289e4e92..46add030a 100644 --- a/fandango/CHANGES +++ b/fandango/CHANGES @@ -11,6 +11,22 @@ BUGS PENDING for 13.* (to not be released in master until bugs will be solved) threads.wait: exceptions on ipython exit threads/WorkerDS: uncatched deadlocks +14.3.0 + + Enable erasing of events in set_attribute_vents + use defaults regexp in tango eval, allowing dots and wildcards + update dynds scripts (port bug) + +14.2.1 + + solve DynamicDS bug (ORBEndPoint dashes) + add check_device_events and Eval.CHECK methods + + +14.2.0 + +Bugfixes on callbacks/EventSource and servers/Astor methods + 14.1.0 - August 2018 -------------------- diff --git a/fandango/VERSION b/fandango/VERSION index 7b3b6e02b..32f02f10e 100644 --- a/fandango/VERSION +++ b/fandango/VERSION @@ -1 +1 @@ -14.1.0 +14.3.0 diff --git a/fandango/callbacks.py b/fandango/callbacks.py index d36975ede..92643c399 100644 --- a/fandango/callbacks.py +++ b/fandango/callbacks.py @@ -42,7 +42,7 @@ ########################################################################### """ import sys,os,time,re -import threading,weakref +import threading,weakref,types from copy import * from excepts import getLastException,exc2str @@ -343,8 +343,9 @@ def register(self,source): self.info('EventThread.register(%s)'%source) if not self.get_started(): self.start() - if source not in self.sources: - #self.sources[source.full_name] = source + if source in self.sources: + self.debug('\t%s already registered'%source) + else: source.full_name = str(source.full_name).lower() self.sources.add(source) # sources to not be refreshed in the first keepalive cycle @@ -517,13 +518,16 @@ def fireEvent(self,source,*args): try: if hasattr(source,'fireEvent'): ## Event types are filtered at EventSource.fireEvent - source.fireEvent(*args) + source.fireEvent(event_type=args[0],event_value=args[1]) elif hasattr(source,'listeners'): listeners = source.listeners.keys() for l in listeners: - try: getattr(l,'eventReceived',l)(*([source]+list(args))) - except: traceback.print_exc() - finally: self.event.wait(self.MinWait/len(source.listeners)) + try: + getattr(l,'eventReceived',l)(*([source]+list(args))) + except: + traceback.print_exc() + finally: + self.event.wait(self.MinWait/len(source.listeners)) elif isCallable(source): source(*data[1:0]) except: @@ -640,7 +644,8 @@ def __init__(self, name, keeptime=1000., fake=False, parent=None, **kw): assert self.fake or self.proxy,\ '%s: A valid and existing device is needed'%(name) - self.listeners = defaultdict(set) #[] + self.listeners = defaultdict(set) #[] + self._hard_refs = [] #Needed to keep instance method refs @TODO self.event_ids = dict() # An {EventType:ID} dict self.state = self.STATES.UNSUBSCRIBED self.tango_asynch = kw.get('tango_asynch',False) @@ -699,7 +704,7 @@ def getSimpleName(self): return self.simple_name def getNormalName(self): return self.normal_name def cleanUp(self): - self.debug("cleanUp") + self.warning("cleanUp") if not hasattr(self,'listeners'): return while self.listeners: @@ -829,6 +834,7 @@ def getPollingPeriod(self): def _listenerDied(self, weak_listener): try: + self.warning('_listenerDied(%s)!' % str(weak_listener)) self.listeners.pop(weak_listener) except Exception, e: pass @@ -865,13 +871,22 @@ def addListener(self, listener,use_events=True,use_polling=False): if self.forced and not self.polled: self.activatePolling() + if type(listener) == types.MethodType: + self.info('keeping hard ref') + # Bound methods will die on arrival if not kept (sigh) + self._hard_refs.append(listener) + weak = weakref.ref(listener,self._listenerDied) + if weak not in self.listeners: #This line is needed, as listeners may be polling only self.listeners[weak] = set() for e in use_events: self.listeners[weak].add(e) + self.debug('addListener(%s): %d listeners registered' + % (listener,len(self.listeners))) + print(id(self),self.full_name) return True def removeListener(self, listener, exclude='dummy'): @@ -879,7 +894,7 @@ def removeListener(self, listener, exclude='dummy'): Remove a listener object or callback. :listener: can be object, weakref, sequence or '*' """ - + self.info('removeListener(%s)' % listener) if listener == '*': self.warning('Removing all listeners') listener = [k for k in self.listeners.keys() if not k().name==exclude] @@ -893,12 +908,18 @@ def removeListener(self, listener, exclude='dummy'): return elif not isinstance(listener,weakref.ReferenceType): - listener = weakref.ref(listener,self._listenerDied) + try: + if listener in self._hard_refs: + self._hard_refs.remove(listener) + except: + traceback.print_exc() + listener = weakref.ref(listener) try: self.listeners.pop(listener) except Exception, e: return False + if not self.listeners: self.unsubscribeEvents() return True @@ -917,6 +938,7 @@ def fireEvent(self, event_type, event_value, listeners=None): event type filtering is done here poll() events will be allowed to pass through """ + self.debug('fireEvent()') pending = self.get_thread().get_pending() if pending: self.warning('fireEvent(%s), %d events still in queue'% @@ -924,6 +946,8 @@ def fireEvent(self, event_type, event_value, listeners=None): self.stats['fired']+=1 listeners = listeners or self.listeners.keys() + self.debug('fireEvent(): %d/%d listeners' + % (len(listeners),len(self.listeners))) for l in listeners: try: #event filter will allow poll() events to pass through @@ -939,8 +963,14 @@ def fireEvent(self, event_type, event_value, listeners=None): if hasattr(l,'eventReceived'): self.debug('fireEvent(%s) => %s?' % (event_type,l)) l.eventReceived(self, event_type, event_value) + elif hasattr(l,'event_received'): + self.debug('fireEvent(%s) => %s?' % (event_type,l)) + l.event_received(self, event_type, event_value) elif isCallable(l): + self.debug('fireEvent(%s) => %s()' % (event_type,l)) l(self, event_type, event_value) + else: + self.debug('fireEvent(%s): %s!?!?!?!?' % (event_type,l)) except: traceback.print_exc() try: @@ -1283,6 +1313,7 @@ def push_event(self,event): et = t0 if event.err: + # MANAGING ERROR EVENTS self.last_error = event has_evs,is_polled = self.isUsingEvents(),self.isPollingActive() value = event.errors[0] @@ -1323,12 +1354,14 @@ def push_event(self,event): self.last_event_time = et or time.time() elif isinstance(event,PyTango.AttrConfEventData): + # MANAGING CONF EVENTS self.debug('push_event(%s)'%str(type_)) value = event.attr_conf self.decodeAttrInfoEx(value) #(Taurus sends here a read cache=False instead of AttrConf) else: + # MANAGING VALUE EVENTS (self.debug if self.last_event.get(type_,None) else self.info)( 'push_event(%s,err=%s,has_events=%s)'%(type_,event.err,self.isUsingEvents())) @@ -1370,6 +1403,27 @@ class TangoListener(EventListener): class TangoAttribute(EventSource): __doc__ = EventSource.__doc__ pass + +# Taurus <-> Tango event types conversion + +def taurusevent2tango(src,type_,value): + # Converts (src,type,value) into a fake PyTango.EventData object + from fandango import Struct + event = Struct() + event.source = src + event.event = type_ + event.type = type_ + #EventData uses only full URL names + event.attr_name = getattr(src,'full_name',getattr(src,'attr_name',str(src))) + event.device = src.proxy + event.attr_value = value + event.err = not hasattr(value,'value') or 'err' in str(type_) or isinstance(value,Exception) + event.errors = [] if not event.err else [value] + event.date = getattr(value,'time',None) + event.time = fandango.ctime2time(event.date) if event.date else time.time() + #event.get_date = lambda t=event.date:return t + event.reception_date = event.date or event.time + return event #For backwards compatibility import fandango.tango diff --git a/fandango/dicts.py b/fandango/dicts.py index 9f52dab3b..0408d201b 100644 --- a/fandango/dicts.py +++ b/fandango/dicts.py @@ -272,7 +272,12 @@ def get_timewait(self): return self.timewait def set_timewait(self,value): self.timewait=value @self_locked - def append(self,key,value=None,period=0): #period=0 means that attribute will be updated always + def append(self,key,value=None,period=0): + """ + args: key, value=None, period=0 + periods shall be specified in seconds + period=0 means that attribute will be updated every timewait*length + """ if not dict.has_key(self,key): self.parent.__setitem__(self,key,value) if key not in self._threadkeys: self._threadkeys.append(key) diff --git a/fandango/dynamic.py b/fandango/dynamic.py index 436726103..f8d61f73e 100644 --- a/fandango/dynamic.py +++ b/fandango/dynamic.py @@ -1030,7 +1030,10 @@ def read_dyn_attr(self,attr,fire_event=True): ").read_dyn_attr("+aname+")="+text_result+ ", ellapsed %1.2e"%(self._eval_times[aname])+" seconds.\n") - if 'debug' in str(self.getLogLevel()) and (time.time()>(self._cycle_start+self.PollingCycle*1e-3) if hasattr(self,'PollingCycle') else aname==sorted(self.dyn_values.keys())[-1]): + if 'debug' in str(self.getLogLevel()) and \ + (time.time()>(self._cycle_start+self.PollingCycle*1e-3) \ + if hasattr(self,'PollingCycle') \ + else aname==sorted(self.dyn_values.keys())[-1]): self.attribute_polling_report() except Exception, e: @@ -1354,12 +1357,12 @@ def processEvents(self): """ try: c = 0 - t0 = fun.now() + self._events_lock.acquire() + t0 = fun.now() if (not self.MaxEventStream or not self.check_attribute_events('state')): self.debug('Events not queued ...') return 0 - self._events_lock.acquire() self.info('*'*80) self.info('In processEvents(%d/%d)' % (self.MaxEventStream,self._events_queue.qsize())) diff --git a/fandango/functional.py b/fandango/functional.py index 071aef38e..029652353 100644 --- a/fandango/functional.py +++ b/fandango/functional.py @@ -946,6 +946,36 @@ def utcdiff(t=None): return now() - date2time(datetime.datetime.utcnow()) def time2str(epoch=None,cad='%Y-%m-%d %H:%M:%S',us=False,bt=True,utc=False): + """ + Use us=True to introduce ms precission + ] + cad accepts the following formats: + + %a Locale’s abbreviated weekday name. + %A Locale’s full weekday name. + %b Locale’s abbreviated month name. + %B Locale’s full month name. + %c Locale’s appropriate date and time representation. + %d Day of the month as a decimal number [01,31]. + %H Hour (24-hour clock) as a decimal number [00,23]. + %I Hour (12-hour clock) as a decimal number [01,12]. + %j Day of the year as a decimal number [001,366]. + %m Month as a decimal number [01,12]. + %M Minute as a decimal number [00,59]. + %p Locale’s equivalent of either AM or PM. (1) + %S Second as a decimal number [00,61]. (2) + %U Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + All days in a new year preceding the first Sunday are considered to be in week 0. (3) + %w Weekday as a decimal number [0(Sunday),6]. + %W Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. + All days in a new year preceding the first Monday are considered to be in week 0. (3) + %x Locale’s appropriate date representation. + %X Locale’s appropriate time representation. + %y Year without century as a decimal number [00,99]. + %Y Year with century as a decimal number. + %Z Time zone name (no characters if no time zone exists). + %% A literal '%' character. + """ if epoch is None: epoch = now() elif bt and epoch<0: epoch = now()+epoch t = time.strftime(cad,time2tuple(epoch,utc=utc)) diff --git a/fandango/scripts/CopyCatDS b/fandango/scripts/CopyCatDS index 6a93ce247..41749ab1c 100755 --- a/fandango/scripts/CopyCatDS +++ b/fandango/scripts/CopyCatDS @@ -5,6 +5,7 @@ DS=$(basename $0) +## The instance argument can be used to override the server name if [ $(echo $1 | grep "/" ) ] ; then IFS='/' read -r DS INSTANCE <<< "$1" else @@ -12,40 +13,71 @@ else INSTANCE=$1 fi -DSORB=$(python -c "import PyTango;print(PyTango.Database().get_property('ORBendPoint',['${DS}/${INSTANCE}']).values()[0][0])" 2>/dev/null) +## @TODO:all these database calls should be done in a single rc.py file + +DSPROPS=$(tango_property -e \ + DSORB=ORBEndPoint.$DS/$INSTANCE \ + LcPATH=Starter/$HOST.StartDSPath \ + DfPATH=PYTHON_CLASSPATH.DeviceClasses \ + PyPATH=PYTHON_CLASSPATH.$DS) + +echo "Tango properties for $DS/$INSTANCE: $DSPROPS" +for p in $DSPROPS; do +# echo $p; + export $p; +done + +# DSORB=$(python -c "import PyTango;print(PyTango.Database().get_property('ORBendPoint',['${DS}/${INSTANCE}']).values()[0][0])" 2>/dev/null) + +# MUST if [ "$DSORB" ] ; then - DSORB="--ORBendPoint $DSORB" + DSORB="-ORBendPoint $DSORB" fi +export PYTHONPATH=$PYTHONPATH:$LcPATH:$DfPATH:$PyPATH:$(pwd)/python/ +# echo $PYTHONPATH + DSPATH=$(python -c "import imp;print(imp.find_module('${DS}')[1])" 2>/dev/null) if [ ! "$DSPATH" ] ; then - DSPATH=$(python -c "import imp;print(imp.find_module('fandango')[1])") - if [ -e "$DSPATH/device/$DS.py" ] ; then - DSPATH=$DSPATH/device - elif [ -e "$DSPATH/interface/$DS.py" ] ; then - DSPATH=$DSPATH/interface + FnPATH=$(python -c "import imp;print(imp.find_module('fandango')[1])") + if [ -e "$FnPATH/device/$DS.py" ] ; then + DSPATH=$FnPATH/device + elif [ -e "$FnPATH/interface/$DS.py" ] ; then + DSPATH=$FnPATH/interface fi fi -DSPATH=$(readlink -f $DSPATH) - -echo "Launching $DSPATH/$DS $INSTANCE at $DSORB" +if [ "$DSPATH" ] ; then + DSPATH=$(readlink -f $DSPATH) +fi -# TODO: if it is mandatory to be in the module path -cd ${DSPATH} +if [ ! -e "${DSPATH}/$DS.py" ] ; then + echo "############## ERROR ###############" + echo "Module $DS not found in PYTHONPATH!?" -if [ $(which screen 2>/dev/null) ] ; then - if [ ! "$(echo $* | grep attach)" ] ; then - echo "run detached" - CMD="screen -dm -S $DS-$INSTANCE " - else - CMD="screen -S $DS-$INSTANCE " - fi else - CMD="" + echo "-------------------------------------------" + echo "Launching ${DSPATH}/$DS $INSTANCE at $DSORB" + + # TODO: if it is mandatory to be in the module path + cd ${DSPATH} + + HASSCREEN=$(which screen 2>/dev/null) + if [ ! "$( echo $1 | grep '\?' )" ] && [ "$HASSCREEN" ] ; then + if [ ! "$(echo $* | grep 'attach')" ] ; then + echo "run detached" + CMD="screen -dm -S $DS-$INSTANCE " + else + CMD="screen -S $DS-$INSTANCE " + fi + else + CMD="" + fi + + CMD="${CMD} python ${DSPATH}/$DS.py $INSTANCE $2 $DSORB" + echo $CMD + $CMD + fi -CMD="${CMD} python ${DSPATH}/$DS.py $INSTANCE $2 $DSORB" -echo $CMD -$CMD diff --git a/fandango/scripts/DynamicDS b/fandango/scripts/DynamicDS index 7decdc1d0..41749ab1c 100755 --- a/fandango/scripts/DynamicDS +++ b/fandango/scripts/DynamicDS @@ -31,7 +31,7 @@ done # MUST if [ "$DSORB" ] ; then - DSORB="--ORBendPoint $DSORB" + DSORB="-ORBendPoint $DSORB" fi export PYTHONPATH=$PYTHONPATH:$LcPATH:$DfPATH:$PyPATH:$(pwd)/python/ diff --git a/fandango/scripts/FolderDS b/fandango/scripts/FolderDS index 7d9501ca4..41749ab1c 100755 --- a/fandango/scripts/FolderDS +++ b/fandango/scripts/FolderDS @@ -5,6 +5,7 @@ DS=$(basename $0) +## The instance argument can be used to override the server name if [ $(echo $1 | grep "/" ) ] ; then IFS='/' read -r DS INSTANCE <<< "$1" else @@ -12,40 +13,71 @@ else INSTANCE=$1 fi -DSORB=$(python -c "import PyTango;print(PyTango.Database().get_property('ORBendPoint',['${DS}/${INSTANCE}']).values()[0][0])" 2>/dev/null) +## @TODO:all these database calls should be done in a single rc.py file + +DSPROPS=$(tango_property -e \ + DSORB=ORBEndPoint.$DS/$INSTANCE \ + LcPATH=Starter/$HOST.StartDSPath \ + DfPATH=PYTHON_CLASSPATH.DeviceClasses \ + PyPATH=PYTHON_CLASSPATH.$DS) + +echo "Tango properties for $DS/$INSTANCE: $DSPROPS" +for p in $DSPROPS; do +# echo $p; + export $p; +done + +# DSORB=$(python -c "import PyTango;print(PyTango.Database().get_property('ORBendPoint',['${DS}/${INSTANCE}']).values()[0][0])" 2>/dev/null) + +# MUST if [ "$DSORB" ] ; then DSORB="-ORBendPoint $DSORB" fi +export PYTHONPATH=$PYTHONPATH:$LcPATH:$DfPATH:$PyPATH:$(pwd)/python/ +# echo $PYTHONPATH + DSPATH=$(python -c "import imp;print(imp.find_module('${DS}')[1])" 2>/dev/null) if [ ! "$DSPATH" ] ; then - DSPATH=$(python -c "import imp;print(imp.find_module('fandango')[1])") - if [ -e "$DSPATH/device/$DS.py" ] ; then - DSPATH=$DSPATH/device - elif [ -e "$DSPATH/interface/$DS.py" ] ; then - DSPATH=$DSPATH/interface + FnPATH=$(python -c "import imp;print(imp.find_module('fandango')[1])") + if [ -e "$FnPATH/device/$DS.py" ] ; then + DSPATH=$FnPATH/device + elif [ -e "$FnPATH/interface/$DS.py" ] ; then + DSPATH=$FnPATH/interface fi fi -DSPATH=$(readlink -f $DSPATH) - -echo "Launching $DSPATH/$DS $INSTANCE at $DSORB" +if [ "$DSPATH" ] ; then + DSPATH=$(readlink -f $DSPATH) +fi -# TODO: if it is mandatory to be in the module path -cd ${DSPATH} +if [ ! -e "${DSPATH}/$DS.py" ] ; then + echo "############## ERROR ###############" + echo "Module $DS not found in PYTHONPATH!?" -if [ $(which screen 2>/dev/null) ] ; then - if [ ! "$(echo $* | grep attach)" ] ; then - echo "run detached" - CMD="screen -dm -S $DS-$INSTANCE " - else - CMD="screen -S $DS-$INSTANCE " - fi else - CMD="" + echo "-------------------------------------------" + echo "Launching ${DSPATH}/$DS $INSTANCE at $DSORB" + + # TODO: if it is mandatory to be in the module path + cd ${DSPATH} + + HASSCREEN=$(which screen 2>/dev/null) + if [ ! "$( echo $1 | grep '\?' )" ] && [ "$HASSCREEN" ] ; then + if [ ! "$(echo $* | grep 'attach')" ] ; then + echo "run detached" + CMD="screen -dm -S $DS-$INSTANCE " + else + CMD="screen -S $DS-$INSTANCE " + fi + else + CMD="" + fi + + CMD="${CMD} python ${DSPATH}/$DS.py $INSTANCE $2 $DSORB" + echo $CMD + $CMD + fi -CMD="${CMD} python ${DSPATH}/$DS.py $INSTANCE $2 $DSORB" -echo $CMD -$CMD diff --git a/fandango/scripts/WorkerDS b/fandango/scripts/WorkerDS index 0b6f7b581..41749ab1c 100755 --- a/fandango/scripts/WorkerDS +++ b/fandango/scripts/WorkerDS @@ -28,9 +28,11 @@ for p in $DSPROPS; do done # DSORB=$(python -c "import PyTango;print(PyTango.Database().get_property('ORBendPoint',['${DS}/${INSTANCE}']).values()[0][0])" 2>/dev/null) -# if [ "$DSORB" ] ; then -# DSORB="-ORBendPoint $DSORB" -# fi + +# MUST +if [ "$DSORB" ] ; then + DSORB="-ORBendPoint $DSORB" +fi export PYTHONPATH=$PYTHONPATH:$LcPATH:$DfPATH:$PyPATH:$(pwd)/python/ # echo $PYTHONPATH diff --git a/fandango/scripts/tango_monitor b/fandango/scripts/tango_monitor index f5412b4f3..e84b8f85c 100755 --- a/fandango/scripts/tango_monitor +++ b/fandango/scripts/tango_monitor @@ -12,6 +12,8 @@ import fandango as fn class MyCallback(): + counter = 0 + def __init__(self): self.t0 = time.time() self.counters = fn.dicts.defaultdict(int) @@ -20,6 +22,8 @@ class MyCallback(): self.ratios = fn.dicts.defaultdict(float) def push_event(self,event): + + MyCallback.counter+=1 aname = fn.tango.get_normal_name(event.attr_name) self.counters[aname] = self.counters[aname] + 1 self.ratios[aname] = self.counters[aname] / (time.time()-self.t0) @@ -29,9 +33,9 @@ class MyCallback(): self.dups[aname] += 1 self.values[aname] = value - print('%s:%s = %s; ct=%d, e/s=%2.2f, dups=%d' % + print('%s:%s = %s; ct=%d/%d, e/s=%2.2f, dups=%d' % (fn.time2str(),aname,value,self.counters[aname], - self.ratios[aname],self.dups[aname])) + MyCallback.counter,self.ratios[aname],self.dups[aname])) def monitor(d, args=[], events = []): diff --git a/fandango/servers.py b/fandango/servers.py index e0e042725..4ddeafabd 100644 --- a/fandango/servers.py +++ b/fandango/servers.py @@ -836,7 +836,7 @@ def get_server_level(self,server_name): def set_server_level(self,server_name,host,level): """ It executes a DbPutServerInfo command in dbserver device. """ - mode = 1 if host or level else 0 + mode = 1 if level else 0 #host or level else 0 host = host.split('.')[0].strip() or 'localhost' if mode else '' level = int(level) if level else 0 dbserver = self.get_db_device() diff --git a/fandango/tango/defaults.py b/fandango/tango/defaults.py index c65a586ff..72a09a180 100644 --- a/fandango/tango/defaults.py +++ b/fandango/tango/defaults.py @@ -110,16 +110,24 @@ def loadTaurus(): #Regular Expressions metachars = re.compile('([.][*])|([.][^*])|([$^+\-?{}\[\]|()])') -#alnum = '[a-zA-Z_\*][a-zA-Z0-9-_\*]*' #[a-zA-Z0-9-_]+ #Added wildcards + +# alnum must match alphanumeric strings, including "-_.*" alnum = '(?:[a-zA-Z0-9-_\*\.]|(?:\.\*))(?:[a-zA-Z0-9-_\*\.]|(?:\.\*))*' no_alnum = '[^a-zA-Z0-9-_]' no_quotes = '(?:^|$|[^\'"a-zA-Z0-9_\./])' + +#redev matches device names, includes fqdn host rehost = '(?:(?P'+alnum+'(?:\.'+alnum+')?'+'(?:\.'+alnum+')?'\ +'(?:\.'+alnum+')?'+'[\:][0-9]+)(?:/))' #(?:'+alnum+':[0-9]+/)? -redev = '(?P'+'(?:'+'/'.join([alnum]*3)+'))' #It matches a device name -reattr = '(?:/(?P'+alnum\ - +')(?:(?:\\.)(?Pquality|time|value|exception|history))?)' - #reattr matches attribute and extension + +# redev = '(?P(?:'+alnum+':[0-9]+/{1,2})?(?:'+'/'.join([alnum]*3)+'))' +redev = '(?P'+'(?:'+'/'.join([alnum]*3)+'))' + +#reattr matches attribute and extension +rewhat = '(?:(?:\\.)(?Pquality|time|value|exception|history))' +reattr = '(?:/(?P'+alnum+')'+rewhat+'?)' + +#retango matches the whole expression retango = '(?:tango://)?'+(rehost+'?')+redev+(reattr+'?')+'(?:\$?)' AC_PARAMS = [ diff --git a/fandango/tango/methods.py b/fandango/tango/methods.py index 6e6924f86..e7459cf61 100644 --- a/fandango/tango/methods.py +++ b/fandango/tango/methods.py @@ -591,7 +591,13 @@ def set_attribute_config(device,attribute,config,events=True,verbose=False): def get_attribute_events(target,polled=True,throw=False): """ Get current attribute events configuration - TODO: it uses Tango Device Proxy, should be Tango Database instead + + Pushed events will be not show, attributes not polled may not works + + Use check_attribute_events to verify if events are really working + + TODO: it uses Tango Device Proxy, should be Tango Database instead to + allow offline checking """ try: d,a = target.rsplit('/',1) @@ -667,34 +673,58 @@ def set_attribute_events(target, polling = None, rel_event = None, abs_event = None, per_event = None, arch_rel_event = None, arch_abs_event = None, arch_per_event = None,verbose = False): + """ + Allows to set independently each event property of the attribute + + Event properties should have same type that the attribute to be set + + Polling must be integer, in millisecons + + Setting any event to 0 or False will erase the current configuration + + """ cfg = CaselessDefaultDict(dict) if polling is not None: #first try if the attribute can be subscribed w/out polling: cfg['polling'] = polling - if any(map(notNone,(rel_event, abs_event, ))): + if any(e is not None for e in (rel_event, abs_event, )): d = cfg['events']['ch_event'] = {} - if notNone(rel_event): + if rel_event is not None: d['rel_change'] = str(rel_event or 'Not specified') - if notNone(abs_event): + if abs_event is not None: d['abs_change'] = str(abs_event or 'Not specified') - if any(map(notNone,(arch_rel_event, arch_abs_event, arch_per_event))): + if any(e is not None for e in + (arch_rel_event, arch_abs_event, arch_per_event)): d = cfg['events']['arch_event'] = {} - if notNone(arch_rel_event): + if arch_rel_event is not None: d['archive_rel_change'] = str(arch_rel_event or 'Not specified') - if notNone(arch_abs_event): + if arch_abs_event is not None: d['archive_abs_change'] = str(arch_abs_event or 'Not specified') - if notNone(arch_per_event): + if arch_per_event is not None: d['archive_period'] = str(arch_per_event or 'Not specified') - if notNone(per_event): + if per_event is not None: cfg['events']['per_event'] = {'period': str(per_event)} dev,attr = target.rsplit('/',1) return set_attribute_config(dev,attr,cfg,True,verbose=verbose) + +def check_device_events(device): + """ + apply check_attribute_events to all attributes of the device + """ + if not check_device(device): + return None + dp = get_device(device,keep=True) + attrs = dict.fromkeys(dp.get_attribute_list()) + + for a in attrs: + attrs[a] = check_attribute_events(device+'/'+a) + return attrs def get_attribute_label(target,use_db=True): dev,attr = target.rsplit('/',1) @@ -1140,7 +1170,7 @@ def check_device(dev,attribute=None,command=None,full=False,admin=False, except Exception,e: return e if throw else None -@Cached(depth=1000,expire=10) +@Cached(depth=1000,expire=10,catched=True) def check_device_cached(*args,**kwargs): """ Cached implementation of check_device method @@ -1150,6 +1180,10 @@ def check_device_cached(*args,**kwargs): def check_attribute(attr,readable=False,timeout=0,brief=False,trace=False): """ checks if attribute is available. + + Returns None if attribute does not exist, Exception if unreadable, + an AttrValue object if brief is False, just the value or None if True + :param readable: Whether if it's mandatory that the attribute returns a value or if it must simply exist. :param timeout: Checks if the attribute value have been effectively @@ -1173,10 +1207,10 @@ def check_attribute(attr,readable=False,timeout=0,brief=False,trace=False): return None else: if not brief: - return attvalue + return attvalue else: - return (getattr(attvalue,'value', - getattr(attvalue,'rvalue',None))) + return (getattr(attvalue,'value', + getattr(attvalue,'rvalue',None))) except Exception,e: if trace: traceback.print_exc() return None if readable or brief else e @@ -1184,6 +1218,14 @@ def check_attribute(attr,readable=False,timeout=0,brief=False,trace=False): if trace: traceback.print_exc() return None +@Cached(depth=10000,expire=300,catched=True) +def check_attribute_cached(*args,**kwargs): + """ + Cached implementation of check_attribute method + @Cached(depth=10000,expire=300,catched=True) + """ + return check_attribute(*args,**kwargs) + def read_attribute(attr,timeout=0,full=False): """ Alias to check_attribute(attr,brief=True)""" return check_attribute(attr,timeout=timeout,brief=not full) diff --git a/fandango/tango/tangoeval.py b/fandango/tango/tangoeval.py index 8b5997406..27d843169 100644 --- a/fandango/tango/tangoeval.py +++ b/fandango/tango/tangoeval.py @@ -174,18 +174,22 @@ class TangoEval(object): eval() will be triggered by events only if event_hook is True or a callable """ - #FIND( optional quotes and whatever is not ')' ) + + ## FIND( optional quotes and whatever is not ')' ) FIND_EXP = 'FIND\(((?:[ \'\"])?[^)]*(?:[ \'\"])?)\)' - #operators = '[><=][=>]?|and|or|in|not in|not' - #l_split = re.split(operators,formula)#.replace(' ','')) - alnum = '[a-zA-Z0-9-_]+' - no_alnum = '[^a-zA-Z0-9-_]' - no_quotes = '(?:^|$|[^\'"a-zA-Z0-9_\./])' + operators = '[><=][=>]?|and|or|in|not in|not' + + # Using regexps as loaded from fandango.tango.defaults + alnum = alnum #'[a-zA-Z0-9-_]+' + no_alnum = no_alnum + no_quotes = no_quotes + #THIS REGULAR EXPRESSIONS DOES NOT MATCH THE HOST IN THE FORMULA!!!; #IT IS TAKEN AS PART OF THE DEVICE NAME!! #It matches a device name - redev = '(?P(?:'+alnum+':[0-9]+/{1,2})?(?:'+'/'.join([alnum]*3)+'))' + redev = redev + #Matches attribute and extension rewhat = '(?:(?:\\.)(?Pquality|time|value|exception|delta|all|'\ 'hist|ALARM|WARNING|VALID|INVALID|OK))?' @@ -243,20 +247,30 @@ def init_locals(self): [(str(v),v) for v in DevState.values.values()]+ [(str(q),q) for q in AttrQuality.values.values()] ) + self._defaults['T'] = str2time self._defaults['str2time'] = str2time self._defaults['time'] = time self._defaults['NOW'] = time.time + #self._locals['now'] = time.time() #Updated at execution time + + # Internal objects self._defaults['DEVICES'] = self.proxies self._defaults['DEV'] = lambda x:self.proxies[x] - self._defaults['NAMES'] = lambda x: get_matching_devices(x) \ - if x.count('/')<3 else get_matching_attributes(x) self._defaults['CACHE'] = self.cache self._defaults['PREV'] = self.previous + + # Tango DB methods + self._defaults['NAMES'] = lambda x: get_matching_attributes(x) \ + if parse_tango_model(x).get('attribute') \ + else get_matching_devices(x) + self._defaults['CHECK'] = lambda x: read_attribute(x) \ + if parse_tango_model(x).get('attribute') \ + else check_device(x) + self._defaults['READ'] = self.read_attribute #For ComposerDS syntax compatibility - self._defaults['READ'] = self._defaults['ATTR'] = \ - self._defaults['XATTR'] = self.read_attribute - #self._locals['now'] = time.time() #Updated at execution time + self._defaults['ATTR'] = self._defaults['XATTR'] = self.read_attribute + self._defaults.update((k,v) for k,v in {'get_domain':get_domain, 'get_family':get_family, 'get_member':get_member,