result from merge with gerrit

secop subdir only

Change-Id: I65ab7049719b374ae3ec0259483e7e7d16aafcd1
This commit is contained in:
2022-03-07 17:49:08 +01:00
parent dee3514065
commit bd246c5ca7
20 changed files with 760 additions and 583 deletions

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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()