experimental persistent mixin

- still contains changes in mouduls/params ...
This commit is contained in:
2021-06-04 12:19:04 +02:00
parent 1409959f53
commit fe5a2caac7
6 changed files with 483 additions and 308 deletions

View File

@ -24,13 +24,15 @@
import sys
import os
import json
import time
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
IntRange, StatusType, StringType, TextType, TupleOf, get_datatype
from secop.errors import BadValueError, ConfigError, InternalError, \
ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import formatException, mkthread
from secop.lib import formatException, getGeneralConfig, mkthread
from secop.lib.enum import Enum
from secop.params import Accessible, Command, Parameter
from secop.poller import BasicPoller, Poller
@ -240,6 +242,8 @@ class Module(HasAccessibles):
self.name = name
self.valueCallbacks = {}
self.errorCallbacks = {}
self.persistentFile = None
self.persistentData = {}
errors = []
# handle module properties
@ -385,7 +389,7 @@ class Module(HasAccessibles):
if k in self.parameters or k in self.propertyDict:
setattr(self, k, v)
cfgdict.pop(k)
except (ValueError, TypeError):
except (ValueError, TypeError) as e:
# self.log.exception(formatExtendedStack())
errors.append('module %s, parameter %s: %s' % (self.name, k, e))
@ -395,6 +399,8 @@ class Module(HasAccessibles):
if '$' in dt.unit:
dt.setProperty('unit', dt.unit.replace('$', self.parameters['value'].datatype.unit))
self.writeDict.update(self.loadParameters())
# 6) check complete configuration of * properties
if not errors:
try:
@ -533,6 +539,7 @@ class Module(HasAccessibles):
self.log.error(str(e))
except Exception:
self.log.error(formatException())
self.saveParameters()
if started_callback:
started_callback()
@ -545,6 +552,60 @@ class Module(HasAccessibles):
"""
mkthread(self.writeInitParams, started_callback)
def loadParameters(self):
"""load persistent parameters
:return: persistent parameters which have to be written
is called upon startup and may be called from a module
when a hardware powerdown is detected
"""
if any(pobj.persistent for pobj in self.parameters.values()):
persistentdir = os.path.join(getGeneralConfig()['logdir'], 'persistent')
self.persistentFile = os.path.join(persistentdir, '%s.%s.json' % (self.DISPATCHER.equipment_id, self.name))
else:
self.persistentFile = None
return {}
try:
with open(self.persistentFile, 'r') as f:
self.persistentData = json.load(f)
except FileNotFoundError:
self.persistentData = {}
writeDict = {}
for pname, pobj in self.parameters.items():
if pobj.persistent and pname in self.persistentData:
value = pobj.datatype.import_value(self.persistentData[pname])
try:
pobj.value = value
if not pobj.readonly:
writeDict[pname] = value
except Exception as e:
self.log.warning('can not restore %r to %r (%r)' % (pname, value, e))
return writeDict
def saveParameters(self):
"""save persistent parameters
to be called regularely explicitly by the module
"""
data = {k: v.export_value() for k, v in self.parameters.items() if v.persistent}
if data != self.persistentData:
self.persistentData = data
persistentdir = os.path.basename(self.persistentFile)
tmpfile = self.persistentFile + '.tmp'
if not os.path.isdir(persistentdir):
os.makedirs(persistentdir, exist_ok=True)
try:
with open(tmpfile, 'w') as f:
json.dump(self.persistentData, f, indent=2)
f.write('\n')
os.rename(tmpfile, self.persistentFile)
finally:
try:
os.remove(tmpfile)
except FileNotFoundError:
pass
class Readable(Module):
"""basic readable module"""

View File

@ -39,10 +39,6 @@ class Accessible(HasProperties):
kwds = None # is a dict if it might be used as Override
def __init__(self, **kwds):
super().__init__()
self.init(kwds)
def init(self, kwds):
# do not use self.propertyValues.update here, as no invalid values should be
# assigned to properties, even not before checkProperties
@ -153,6 +149,9 @@ class Parameter(Accessible):
handler = Property(
'[internal] overload the standard read and write functions', ValueType(),
export=False, default=None, settable=False)
persistent = Property(
'[internal] persistent setting', BoolType(),
export=False, default=False)
initwrite = Property(
'''[internal] write this parameter on initialization
@ -165,7 +164,7 @@ class Parameter(Accessible):
readerror = None
def __init__(self, description=None, datatype=None, inherit=True, *, unit=None, constant=None, **kwds):
super().__init__(**kwds)
super().__init__()
if datatype is not None:
if not isinstance(datatype, DataType):
if isinstance(datatype, type) and issubclass(datatype, DataType):
@ -178,6 +177,8 @@ class Parameter(Accessible):
if 'default' in kwds:
self.default = datatype(kwds['default'])
self.init(kwds) # datatype must be defined before we can treat dataset properties like fmtstr or unit
if description is not None:
self.description = inspect.cleandoc(description)
@ -313,7 +314,8 @@ class Command(Accessible):
func = None
def __init__(self, argument=False, *, result=None, inherit=True, **kwds):
super().__init__(**kwds)
super().__init__()
self.init(kwds)
if result or kwds or isinstance(argument, DataType) or not callable(argument):
# normal case
if argument is False and result:

94
secop/persistent.py Normal file
View File

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""Define base classes for real Modules implemented in the server"""
import os
import json
from secop.errors import BadValueError, ConfigError, InternalError, \
ProgrammingError, SECoPError, SilentError, secop_error
class PersistentMixin:
def __init__(*args, **kwds):
super().__init__(*args, **kwds)
# self.persistentFile = None
# self.persistentData = {}
self.writeDict.update(self.loadParameters())
def writeInitParams(self, started_callback=None):
super().writeInitParams()
self.saveParameters()
if started_callback:
started_callback()
def loadParameters(self):
"""load persistent parameters
:return: persistent parameters which have to be written
is called upon startup and may be called from a module
when a hardware powerdown is detected
"""
persistentdir = os.path.join(getGeneralConfig()['logdir'], 'persistent')
self.persistentFile = os.path.join(persistentdir, '%s.%s.json' % (self.DISPATCHER.equipment_id, self.name))
try:
with open(self.persistentFile, 'r') as f:
self.persistentData = json.load(f)
except FileNotFoundError:
self.persistentData = {}
writeDict = {}
for pname, pobj in self.parameters.items():
if pobj.persistent and pname in self.persistentData:
value = pobj.datatype.import_value(self.persistentData[pname])
try:
pobj.value = value
if not pobj.readonly:
writeDict[pname] = value
except Exception as e:
self.log.warning('can not restore %r to %r (%r)' % (pname, value, e))
return writeDict
def saveParameters(self):
"""save persistent parameters
to be called regularely explicitly by the module
"""
data = {k: v.export_value() for k, v in self.parameters.items() if v.persistent}
if data != self.persistentData:
self.persistentData = data
persistentdir = os.path.basename(self.persistentFile)
tmpfile = self.persistentFile + '.tmp'
if not os.path.isdir(persistentdir):
os.makedirs(persistentdir, exist_ok=True)
try:
with open(tmpfile, 'w') as f:
json.dump(self.persistentData, f, indent=2)
f.write('\n')
os.rename(tmpfile, self.persistentFile)
finally:
try:
os.remove(tmpfile)
except FileNotFoundError:
pass

View File

@ -260,7 +260,8 @@ class Server:
self.modules[modname] = modobj
except ConfigError as e:
errors.append('error creating module %s:' % modname)
for errtxt in e.args[0] if isinstance(e.args, list) else [e.args[0]]:
print('E', e.args)
for errtxt in e.args[0] if isinstance(e.args[0], list) else [e.args[0]]:
errors.append(' ' + errtxt)
except Exception:
failure_traceback = traceback.format_exc()