updates from mlz repo

- asynconn: raise * from *
- asynconn: correct handling for timeout in AsynSerial
- add new py35compat
- target unit $

Change-Id: I052185ad3ebb3e3d1e3374f7ece9c7df06223951
This commit is contained in:
zolliker 2021-11-10 14:37:57 +01:00
parent 41ce909172
commit a7b741eaa4
3 changed files with 90 additions and 35 deletions

View File

@ -48,6 +48,7 @@ class ConnectionClosed(ConnectionError):
class AsynConn:
timeout = 1 # inter byte timeout
scheme = None
SCHEME_MAP = {}
connection = None # is not None, if connected
defaultport = None
@ -62,11 +63,11 @@ class AsynConn:
except (ValueError, TypeError, AssertionError):
if 'COM' in uri:
raise ValueError("the correct uri for a COM port is: "
"'serial://COM<i>[?<option>=<value>[+<option>=value ...]]'")
"'serial://COM<i>[?<option>=<value>[+<option>=value ...]]'") from None
if '/dev' in uri:
raise ValueError("the correct uri for a serial port is: "
"'serial:///dev/<tty>[?<option>=<value>[+<option>=value ...]]'")
raise ValueError('invalid uri: %s' % uri)
"'serial:///dev/<tty>[?<option>=<value>[+<option>=value ...]]'") from None
raise ValueError('invalid uri: %s' % uri) from None
iocls = cls.SCHEME_MAP['tcp']
uri = 'tcp://%s:%d' % host_port
return object.__new__(iocls)
@ -80,7 +81,9 @@ class AsynConn:
@classmethod
def __init_subclass__(cls):
cls.SCHEME_MAP[cls.scheme] = cls
"""register subclass to scheme, if available"""
if cls.scheme:
cls.SCHEME_MAP[cls.scheme] = cls
def disconnect(self):
raise NotImplementedError
@ -166,7 +169,7 @@ class AsynTcp(AsynConn):
self.connection = tcpSocket(uri, self.defaultport, self.timeout)
except (ConnectionRefusedError, socket.gaierror) as e:
# indicate that retrying might make sense
raise CommunicationFailedError(str(e))
raise CommunicationFailedError(str(e)) from None
def disconnect(self):
if self.connection:
@ -237,8 +240,8 @@ class AsynSerial(AsynConn):
options = dict((kv.split('=') for kv in uri[1].split('+')))
except IndexError: # no uri[1], no options
options = {}
except ValueError:
raise ConfigError('illegal serial options')
except ValueError as e:
raise ConfigError('illegal serial options') from e
parity = options.pop('parity', None) # only parity is to be treated as text
for k, v in options.items():
try:
@ -251,14 +254,12 @@ class AsynSerial(AsynConn):
if not fullname.startswith(name):
raise ConfigError('illegal parity: %s' % parity)
options['parity'] = name[0]
if 'timeout' in options:
options['timeout'] = float(self.timeout)
else:
if 'timeout' not in options:
options['timeout'] = self.timeout
try:
self.connection = Serial(dev, **options)
except ValueError as e:
raise ConfigError(e)
raise ConfigError(e) from None
# TODO: turn exceptions into ConnectionFailedError, where a retry makes sense
def disconnect(self):

58
secop/lib/py35compat.py Normal file
View File

@ -0,0 +1,58 @@
# -*- 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>
#
# *****************************************************************************
"""workaround for python versions older than 3.6
``Object`` must be inherited for classes needing support for
__init_subclass__ and __set_name__
"""
if hasattr(object, '__init_subclass__'):
class Object:
pass
else:
class PEP487Metaclass(type):
# support for __set_name__ and __init_subclass__ for older python versions
# slightly modified from PEP487 doc
def __new__(cls, *args, **kwargs):
if len(args) != 3:
return super().__new__(cls, *args)
name, bases, ns = args
init = ns.get('__init_subclass__')
if callable(init):
ns['__init_subclass__'] = classmethod(init)
newtype = super().__new__(cls, name, bases, ns)
for k, v in newtype.__dict__.items():
func = getattr(v, '__set_name__', None)
if func is not None:
func(newtype, k)
if bases:
super(newtype, newtype).__init_subclass__(**kwargs) # pylint: disable=bad-super-call
return newtype
def __init__(cls, name, bases, ns, **kwargs):
super().__init__(name, bases, ns)
class Object(metaclass=PEP487Metaclass):
@classmethod
def __init_subclass__(cls, *args, **kwargs):
pass

View File

@ -29,7 +29,7 @@ from collections import OrderedDict
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
IntRange, StatusType, StringType, TextType, TupleOf
from secop.errors import BadValueError, ConfigError, InternalError, \
from secop.errors import BadValueError, ConfigError, \
ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import formatException, mkthread
from secop.lib.enum import Enum
@ -57,6 +57,7 @@ class HasAccessibles(HasProperties):
merged_properties = {} # dict of dict of merged properties
new_names = [] # list of names of new accessibles
override_values = {} # bare values overriding a parameter and methods overriding a command
for base in reversed(cls.__mro__):
for key, value in base.__dict__.items():
if isinstance(value, Accessible):
@ -66,8 +67,6 @@ class HasAccessibles(HasProperties):
accessibles[key] = value
override_values.pop(key, None)
elif key in accessibles:
# either a bare value overriding a parameter
# or a method overriding a command
override_values[key] = value
for aname, aobj in accessibles.items():
if aname in override_values:
@ -244,12 +243,12 @@ class Module(HasAccessibles):
# reference to the dispatcher (used for sending async updates)
DISPATCHER = None
pollerClass = Poller #: default poller used
def __init__(self, name, logger, cfgdict, srv):
# remember the dispatcher object (for the async callbacks)
self.DISPATCHER = srv.dispatcher
self.omit_unchanged_within = getattr(self.DISPATCHER, 'omit_unchanged_within', 0.1)
self.log = logger
self.name = name
self.valueCallbacks = {}
@ -444,23 +443,20 @@ class Module(HasAccessibles):
pobj = self.parameters[pname]
timestamp = timestamp or time.time()
changed = pobj.value != value
if value is not None:
pobj.value = value # store the value even in case of error
try:
# store the value even in case of error
pobj.value = pobj.datatype(value)
except Exception as e:
if not err: # do not overwrite given error
err = e
if err:
if not isinstance(err, SECoPError):
err = InternalError(err)
err = secop_error(err)
if str(err) == str(pobj.readerror):
return # do call updates for repeated errors
else:
try:
pobj.value = pobj.datatype(value)
except Exception as e:
err = secop_error(e)
if not changed and timestamp < ((pobj.timestamp or 0)
+ self.DISPATCHER.OMIT_UNCHANGED_WITHIN):
# no change within short time -> omit
return
pobj.timestamp = timestamp
elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within:
# no change within short time -> omit
return
pobj.timestamp = timestamp or time.time()
pobj.readerror = err
if pobj.export:
self.DISPATCHER.announce_update(self.name, pname, pobj)
@ -495,15 +491,15 @@ class Module(HasAccessibles):
for pname in self.parameters:
errfunc = getattr(modobj, 'error_update_' + pname, None)
if errfunc:
def errcb(err, p=pname, m=modobj, efunc=errfunc):
def errcb(err, p=pname, efunc=errfunc):
try:
efunc(err)
except Exception as e:
m.announceUpdate(p, err=e)
modobj.announceUpdate(p, err=e)
self.errorCallbacks[pname].append(errcb)
else:
def errcb(err, p=pname, m=modobj):
m.announceUpdate(p, err=err)
def errcb(err, p=pname):
modobj.announceUpdate(p, err=err)
if pname in autoupdate:
self.errorCallbacks[pname].append(errcb)
@ -516,8 +512,8 @@ class Module(HasAccessibles):
efunc(e)
self.valueCallbacks[pname].append(cb)
elif pname in autoupdate:
def cb(value, p=pname, m=modobj):
m.announceUpdate(p, value)
def cb(value, p=pname):
modobj.announceUpdate(p, value)
self.valueCallbacks[pname].append(cb)
def isBusy(self, status=None):