Skip to content
This repository has been archived by the owner on Apr 26, 2021. It is now read-only.

Commit

Permalink
Merge branch 'documentation'
Browse files Browse the repository at this point in the history
  • Loading branch information
sergirubio committed Oct 24, 2018
2 parents 43ee88d + af35719 commit b0d2bae
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 98 deletions.
16 changes: 16 additions & 0 deletions fandango/CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------

Expand Down
2 changes: 1 addition & 1 deletion fandango/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14.1.0
14.3.0
76 changes: 65 additions & 11 deletions fandango/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
###########################################################################
"""
import sys,os,time,re
import threading,weakref
import threading,weakref,types
from copy import *

from excepts import getLastException,exc2str
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -865,21 +871,30 @@ 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'):
"""
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]
Expand All @@ -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
Expand All @@ -917,13 +938,16 @@ 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'%
(event_type,pending))
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
Expand All @@ -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:
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()))

Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion fandango/dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions fandango/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()))
Expand Down
30 changes: 30 additions & 0 deletions fandango/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading

0 comments on commit b0d2bae

Please sign in to comment.