result from merge with gerrit
secop subdir only Change-Id: I65ab7049719b374ae3ec0259483e7e7d16aafcd1
This commit is contained in:
@ -30,57 +30,95 @@ import traceback
|
||||
from configparser import ConfigParser
|
||||
from os import environ, path
|
||||
|
||||
CONFIG = {}
|
||||
unset_value = object()
|
||||
|
||||
class GeneralConfig:
|
||||
|
||||
def getGeneralConfig(confdir=None):
|
||||
global CONFIG # pylint: disable=global-statement
|
||||
def __init__(self):
|
||||
self._config = None
|
||||
self.defaults = {} #: default values. may be set before or after :meth:`init`
|
||||
|
||||
if CONFIG:
|
||||
if confdir:
|
||||
raise ValueError('getGeneralConfig with argument must be called first')
|
||||
else:
|
||||
def init(self, configfile=None):
|
||||
cfg = {}
|
||||
mandatory = 'piddir', 'logdir', 'confdir'
|
||||
repodir = path.abspath(path.join(path.dirname(__file__), '..', '..'))
|
||||
# create default paths
|
||||
if path.splitext(sys.executable)[1] == ".exe" and not path.basename(sys.executable).startswith('python'):
|
||||
# special MS windows environment
|
||||
CONFIG = {
|
||||
'piddir': './',
|
||||
'logdir': './log',
|
||||
'confdir': './',
|
||||
}
|
||||
elif not path.exists(path.join(repodir, '.git')):
|
||||
CONFIG = {
|
||||
'piddir': '/var/run/secop',
|
||||
'logdir': '/var/log',
|
||||
'confdir': '/etc/secop',
|
||||
}
|
||||
cfg.update(piddir='./', logdir='./log', confdir='./')
|
||||
elif path.exists(path.join(repodir, '.git')):
|
||||
# running from git repo
|
||||
cfg['confdir'] = path.join(repodir, 'cfg')
|
||||
# take logdir and piddir from <repodir>/cfg/generalConfig.cfg
|
||||
else:
|
||||
CONFIG = {
|
||||
'piddir': path.join(repodir, 'pid'),
|
||||
'logdir': path.join(repodir, 'log'),
|
||||
'confdir': path.join(repodir, 'cfg'),
|
||||
}
|
||||
gen_config_path = confdir or environ.get('FRAPPY_CONFIG_FILE',
|
||||
path.join(CONFIG['confdir'], 'generalConfig.cfg'))
|
||||
if gen_config_path and path.exists(gen_config_path):
|
||||
# running on installed system (typically with systemd)
|
||||
cfg.update(piddir='/var/run/frappy', logdir='/var/log', confdir='/etc/frappy')
|
||||
if configfile is None:
|
||||
configfile = environ.get('FRAPPY_CONFIG_FILE',
|
||||
path.join(cfg['confdir'], 'generalConfig.cfg'))
|
||||
if configfile and path.exists(configfile):
|
||||
parser = ConfigParser()
|
||||
parser.optionxform = str
|
||||
parser.read([gen_config_path])
|
||||
CONFIG = {}
|
||||
parser.read([configfile])
|
||||
# mandatory in a general config file:
|
||||
cfg['logdir'] = cfg['piddir'] = None
|
||||
cfg['confdir'] = path.dirname(configfile)
|
||||
# only the FRAPPY section is relevant, other sections might be used by others
|
||||
for key, value in parser['FRAPPY'].items():
|
||||
if value.startswith('./'):
|
||||
CONFIG[key] = path.abspath(path.join(repodir, value))
|
||||
cfg[key] = path.abspath(path.join(repodir, value))
|
||||
else:
|
||||
# expand ~ to username, also in path lists separated with ':'
|
||||
CONFIG[key] = ':'.join(path.expanduser(v) for v in value.split(':'))
|
||||
cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':'))
|
||||
else:
|
||||
for dirname in CONFIG:
|
||||
CONFIG[dirname] = environ.get('SECOP_%s' % dirname.upper(), CONFIG[dirname])
|
||||
for key in mandatory:
|
||||
cfg[key] = environ.get('FRAPPY_%s' % key.upper(), cfg[key])
|
||||
missing_keys = [key for key in mandatory if cfg[key] is None]
|
||||
if missing_keys:
|
||||
if path.exists(configfile):
|
||||
raise KeyError('missing value for %s in %s' % (' and '.join(missing_keys), configfile))
|
||||
raise FileNotFoundError(configfile)
|
||||
# this is not customizable
|
||||
CONFIG['basedir'] = repodir
|
||||
return CONFIG
|
||||
cfg['basedir'] = repodir
|
||||
self._config = cfg
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self._config[key]
|
||||
except KeyError:
|
||||
return self.defaults[key]
|
||||
except TypeError:
|
||||
if key in self.defaults:
|
||||
# accept retrieving defaults before init
|
||||
# e.g. 'lazy_number_validation' in secop.datatypes
|
||||
return self.defaults[key]
|
||||
raise TypeError('generalConfig.init() has to be called first') from None
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self.__getitem__(key)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def getint(self, key, default=None):
|
||||
try:
|
||||
return int(self.__getitem__(key))
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""goodie: use generalConfig.<key> instead of generalConfig.get('<key>')"""
|
||||
return self.get(key)
|
||||
|
||||
@property
|
||||
def initialized(self):
|
||||
return bool(self._config)
|
||||
|
||||
def testinit(self, **kwds):
|
||||
"""for test purposes"""
|
||||
self._config = kwds
|
||||
|
||||
|
||||
generalConfig = GeneralConfig()
|
||||
|
||||
|
||||
class lazy_property:
|
||||
@ -289,4 +327,4 @@ class UniqueObject:
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return 'UniqueObject(%r)' % self.name
|
||||
return self.name
|
||||
|
@ -74,29 +74,29 @@ SIMPLETYPES = {
|
||||
}
|
||||
|
||||
|
||||
def short_doc(datatype):
|
||||
def short_doc(datatype, internal=False):
|
||||
# pylint: disable=possibly-unused-variable
|
||||
|
||||
def doc_EnumType(dt):
|
||||
return 'one of %s' % str(tuple(dt._enum.keys()))
|
||||
|
||||
def doc_ArrayOf(dt):
|
||||
return 'array of %s' % short_doc(dt.members)
|
||||
return 'array of %s' % short_doc(dt.members, True)
|
||||
|
||||
def doc_TupleOf(dt):
|
||||
return 'tuple of (%s)' % ', '.join(short_doc(m) for m in dt.members)
|
||||
return 'tuple of (%s)' % ', '.join(short_doc(m, True) for m in dt.members)
|
||||
|
||||
def doc_CommandType(dt):
|
||||
argument = short_doc(dt.argument) if dt.argument else ''
|
||||
result = ' -> %s' % short_doc(dt.result) if dt.result else ''
|
||||
argument = short_doc(dt.argument, True) if dt.argument else ''
|
||||
result = ' -> %s' % short_doc(dt.result, True) if dt.result else ''
|
||||
return '(%s)%s' % (argument, result) # return argument list only
|
||||
|
||||
def doc_NoneOr(dt):
|
||||
other = short_doc(dt.other)
|
||||
other = short_doc(dt.other, True)
|
||||
return '%s or None' % other if other else None
|
||||
|
||||
def doc_OrType(dt):
|
||||
types = [short_doc(t) for t in dt.types]
|
||||
types = [short_doc(t, True) for t in dt.types]
|
||||
if None in types: # type is anyway broad: no doc
|
||||
return None
|
||||
return ' or '.join(types)
|
||||
@ -104,14 +104,17 @@ def short_doc(datatype):
|
||||
def doc_Stub(dt):
|
||||
return dt.name.replace('Type', '').replace('Range', '').lower()
|
||||
|
||||
clsname = datatype.__class__.__name__
|
||||
def doc_BLOBType(dt):
|
||||
return 'byte array'
|
||||
|
||||
clsname = type(datatype).__name__
|
||||
result = SIMPLETYPES.get(clsname)
|
||||
if result:
|
||||
return result
|
||||
fun = locals().get('doc_' + clsname)
|
||||
if fun:
|
||||
return fun(datatype)
|
||||
return None # broad type like ValueType: no doc
|
||||
return clsname if internal else None # broad types like ValueType: no doc
|
||||
|
||||
|
||||
def append_to_doc(cls, lines, itemcls, name, attrname, fmtfunc):
|
||||
|
@ -21,41 +21,51 @@
|
||||
# *****************************************************************************
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
ETERNITY = 1e99
|
||||
|
||||
|
||||
class _SingleEvent:
|
||||
"""Single Event
|
||||
|
||||
remark: :meth:`wait` is not implemented on purpose
|
||||
"""
|
||||
def __init__(self, multievent, timeout, name=None):
|
||||
self.multievent = multievent
|
||||
self.multievent.clear_(self)
|
||||
self.name = name
|
||||
if timeout is None:
|
||||
self.deadline = ETERNITY
|
||||
else:
|
||||
self.deadline = time.monotonic() + timeout
|
||||
|
||||
def clear(self):
|
||||
self.multievent.clear_(self)
|
||||
|
||||
def set(self):
|
||||
self.multievent.set_(self)
|
||||
|
||||
def is_set(self):
|
||||
return self in self.multievent.events
|
||||
|
||||
|
||||
class MultiEvent(threading.Event):
|
||||
"""Class implementing multi event objects.
|
||||
"""Class implementing multi event objects."""
|
||||
|
||||
meth:`new` creates Event like objects
|
||||
meth:'wait` waits for all of them being set
|
||||
"""
|
||||
|
||||
class SingleEvent:
|
||||
"""Single Event
|
||||
|
||||
remark: :meth:`wait` is not implemented on purpose
|
||||
"""
|
||||
def __init__(self, multievent):
|
||||
self.multievent = multievent
|
||||
self.multievent._clear(self)
|
||||
|
||||
def clear(self):
|
||||
self.multievent._clear(self)
|
||||
|
||||
def set(self):
|
||||
self.multievent._set(self)
|
||||
|
||||
def is_set(self):
|
||||
return self in self.multievent.events
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, default_timeout=None):
|
||||
self.events = set()
|
||||
self._lock = threading.Lock()
|
||||
self.default_timeout = default_timeout or None # treat 0 as None
|
||||
self.name = None # default event name
|
||||
self._actions = [] # actions to be executed on trigger
|
||||
super().__init__()
|
||||
|
||||
def new(self):
|
||||
"""create a new SingleEvent"""
|
||||
return self.SingleEvent(self)
|
||||
def new(self, timeout=None, name=None):
|
||||
"""create a single event like object"""
|
||||
return _SingleEvent(self, timeout or self.default_timeout,
|
||||
name or self.name or '<unnamed>')
|
||||
|
||||
def set(self):
|
||||
raise ValueError('a multievent must not be set directly')
|
||||
@ -63,21 +73,69 @@ class MultiEvent(threading.Event):
|
||||
def clear(self):
|
||||
raise ValueError('a multievent must not be cleared directly')
|
||||
|
||||
def _set(self, event):
|
||||
def is_set(self):
|
||||
return not self.events
|
||||
|
||||
def set_(self, event):
|
||||
"""internal: remove event from the event list"""
|
||||
with self._lock:
|
||||
self.events.discard(event)
|
||||
if self.events:
|
||||
return
|
||||
try:
|
||||
for action in self._actions:
|
||||
action()
|
||||
except Exception:
|
||||
pass # we silently ignore errors here
|
||||
self._actions = []
|
||||
super().set()
|
||||
|
||||
def _clear(self, event):
|
||||
def clear_(self, event):
|
||||
"""internal: add event to the event list"""
|
||||
with self._lock:
|
||||
self.events.add(event)
|
||||
super().clear()
|
||||
|
||||
def deadline(self):
|
||||
deadline = 0
|
||||
for event in self.events:
|
||||
deadline = max(event.deadline, deadline)
|
||||
return None if deadline == ETERNITY else deadline
|
||||
|
||||
def wait(self, timeout=None):
|
||||
"""wait for all events being set or timed out"""
|
||||
if not self.events: # do not wait if events are empty
|
||||
return
|
||||
super().wait(timeout)
|
||||
return True
|
||||
deadline = self.deadline()
|
||||
if deadline is not None:
|
||||
deadline -= time.monotonic()
|
||||
timeout = deadline if timeout is None else min(deadline, timeout)
|
||||
if timeout <= 0:
|
||||
return False
|
||||
return super().wait(timeout)
|
||||
|
||||
def waiting_for(self):
|
||||
return set(event.name for event in self.events)
|
||||
|
||||
def get_trigger(self, timeout=None, name=None):
|
||||
"""create a new single event and return its set method
|
||||
|
||||
as a convenience method
|
||||
"""
|
||||
return self.new(timeout, name).set
|
||||
|
||||
def queue(self, action):
|
||||
"""add an action to the queue of actions to be executed at end
|
||||
|
||||
:param action: a function, to be executed after the last event is triggered,
|
||||
and before the multievent is set
|
||||
|
||||
- if no events are waiting, the actions are executed immediately
|
||||
- if an action raises an exception, it is silently ignore and further
|
||||
actions in the queue are skipped
|
||||
- if this is not desired, the action should handle errors by itself
|
||||
"""
|
||||
with self._lock:
|
||||
self._actions.append(action)
|
||||
if self.is_set():
|
||||
self.set_(None)
|
||||
|
@ -137,8 +137,8 @@ class SequencerMixin:
|
||||
if self._seq_fault_on_stop:
|
||||
return self.Status.ERROR, self._seq_stopped
|
||||
return self.Status.WARN, self._seq_stopped
|
||||
if hasattr(self, 'read_hw_status'):
|
||||
return self.read_hw_status()
|
||||
if hasattr(self, 'readHwStatus'):
|
||||
return self.readHwStatus()
|
||||
return self.Status.IDLE, ''
|
||||
|
||||
def stop(self):
|
||||
@ -153,7 +153,7 @@ class SequencerMixin:
|
||||
self._seq_error = str(e)
|
||||
finally:
|
||||
self._seq_thread = None
|
||||
self.pollParams(0)
|
||||
self.doPoll()
|
||||
|
||||
def _seq_thread_inner(self, seq, store_init):
|
||||
store = Namespace()
|
||||
|
Reference in New Issue
Block a user