datatypes: split base classes for internal and SECoP datatypes

Simple datatypes used only in properties like ValueType of NoneOr
do not need a couple of methods. Splitting the base class avoids
warnings about unimlemented abstract methods.

Change-Id: Ie7d5754c44a5fb5c3ed8569df544495450347082
This commit is contained in:
2025-10-28 09:50:43 +01:00
parent 8575a2f785
commit 24153d2584
3 changed files with 56 additions and 58 deletions

View File

@@ -53,13 +53,9 @@ def shortrepr(value):
return r return r
# base class for all DataTypes class SimpleDataType(HasProperties):
class DataType(HasProperties): """base class for simple datatypes, used in properties only"""
"""base class for all data types"""
IS_COMMAND = False
unit = ''
default = None default = None
client = False # used on the client side
def __call__(self, value): def __call__(self, value):
"""convert given value to our datatype and validate """convert given value to our datatype and validate
@@ -105,38 +101,10 @@ class DataType(HasProperties):
""" """
return self.format_value(value, False) return self.format_value(value, False)
def export_datatype(self):
"""return a python object which after jsonifying identifies this datatype"""
raise ProgrammingError(
f"{type(self).__name__} is not able to be exported to SECoP. "
f"It is intended for internal use only."
)
def export_value(self, value): def export_value(self, value):
"""if needed, reformat value for transport""" """if needed, reformat value for transport"""
return value return value
def import_value(self, value):
"""opposite of export_value, reformat from transport to internal repr
note: for importing from gui/configfile/commandline use :meth:`from_string`
instead.
"""
return self(value)
def format_value(self, value, unit=True):
"""format a value of this type into a string
This is intended for 'nice' formatting for humans and is NOT
the opposite of :meth:`from_string`
possible values of unit:
- True: use the string of the datatype
- False: return a value interpretable by ast.literal_eval (internal use only)
- any other string: use as unit (internal use only)
"""
raise NotImplementedError
def set_properties(self, **kwds): def set_properties(self, **kwds):
"""init datatype properties""" """init datatype properties"""
try: try:
@@ -161,6 +129,34 @@ class DataType(HasProperties):
# looks like the simplest way to make a deep copy # looks like the simplest way to make a deep copy
return get_datatype(self.export_datatype()) return get_datatype(self.export_datatype())
class DataType(SimpleDataType):
"""base class for data types used in parameters and commands"""
IS_COMMAND = False
unit = ''
client = False # used on the client side
def import_value(self, value):
"""opposite of export_value, reformat from transport to internal repr
note: for importing from gui/configfile/commandline use :meth:`from_string`
instead.
"""
return self(value)
def format_value(self, value, unit=True):
"""format a value of this type into a string
This is intended for 'nice' formatting for humans and is NOT
the opposite of :meth:`from_string`
possible values of unit:
- True: use the string of the datatype
- False: return a value interpretable by ast.literal_eval (internal use only)
- any other string: use as unit (internal use only)
"""
raise NotImplementedError
def compatible(self, other): def compatible(self, other):
"""check other for compatibility """check other for compatibility
@@ -169,6 +165,10 @@ class DataType(HasProperties):
""" """
raise NotImplementedError raise NotImplementedError
def export_datatype(self):
"""return a python object which after jsonifying identifies this datatype"""
raise NotImplementedError
def set_main_unit(self, unit): def set_main_unit(self, unit):
"""replace $ in unit by argument""" """replace $ in unit by argument"""
@@ -1131,10 +1131,23 @@ class CommandType(DataType):
# internally used datatypes (i.e. only for programming the SEC-node) # internally used datatypes (i.e. only for programming the SEC-node)
class DataTypeType(DataType): class DefaultType(DataType):
"""datatype used as default for parameters
needs some minimal interface to avoid errors when
the datatype of a parameter is not yet defined
"""
def __call__(self, value):
return value
def setProperty(self, key, value):
"""silently ignored"""
class DataTypeType(SimpleDataType):
def __call__(self, value): def __call__(self, value):
"""accepts a datatype""" """accepts a datatype"""
if isinstance(value, DataType): if isinstance(value, SimpleDataType):
return value return value
#TODO: not needed anymore? #TODO: not needed anymore?
try: try:
@@ -1155,7 +1168,7 @@ class DataTypeType(DataType):
raise NotImplementedError raise NotImplementedError
class ValueType(DataType): class ValueType(SimpleDataType):
"""Can take any python value. """Can take any python value.
The optional (callable) validator can be used to restrict values to a The optional (callable) validator can be used to restrict values to a
@@ -1188,23 +1201,8 @@ class ValueType(DataType):
"""if needed, reformat value for transport""" """if needed, reformat value for transport"""
return value return value
def import_value(self, value):
"""opposite of export_value, reformat from transport to internal repr
note: for importing from gui/configfile/commandline use :meth:`from_string` class NoneOr(SimpleDataType):
instead.
"""
raise NotImplementedError
def setProperty(self, key, value):
"""silently ignored
as ValueType is used for the datatype default, this makes code
shorter for cases, where the datatype may not yet be defined
"""
class NoneOr(DataType):
"""validates a None or smth. else""" """validates a None or smth. else"""
default = None default = None
@@ -1222,7 +1220,7 @@ class NoneOr(DataType):
return self.other.export_value(value) return self.other.export_value(value)
class OrType(DataType): class OrType(SimpleDataType):
def __init__(self, *types): def __init__(self, *types):
super().__init__() super().__init__()
self.types = types self.types = types

View File

@@ -25,8 +25,8 @@
import inspect import inspect
from frappy.datatypes import ArrayOf, BoolType, CommandType, DataType, \ from frappy.datatypes import ArrayOf, BoolType, CommandType, DataType, \
DataTypeType, EnumType, FloatRange, NoneOr, OrType, StringType, StructOf, \ DataTypeType, DefaultType, EnumType, FloatRange, NoneOr, OrType, StringType, \
TextType, TupleOf, ValueType StructOf, TextType, TupleOf, ValueType
from frappy.errors import BadValueError, ProgrammingError, WrongTypeError from frappy.errors import BadValueError, ProgrammingError, WrongTypeError
from frappy.lib import generalConfig from frappy.lib import generalConfig
from frappy.properties import HasProperties, Property from frappy.properties import HasProperties, Property
@@ -144,7 +144,7 @@ class Parameter(Accessible):
extname='description', mandatory=True, export='always') extname='description', mandatory=True, export='always')
datatype = Property( datatype = Property(
'datatype of the Parameter (SECoP datainfo)', DataTypeType(), 'datatype of the Parameter (SECoP datainfo)', DataTypeType(),
extname='datainfo', mandatory=True, export='always', default=ValueType()) extname='datainfo', mandatory=True, export='always', default=DefaultType())
readonly = Property( readonly = Property(
'not changeable via SECoP (default True)', BoolType(), 'not changeable via SECoP (default True)', BoolType(),
extname='readonly', default=True, export='always') extname='readonly', default=True, export='always')

View File

@@ -75,7 +75,7 @@ def out_of_range(dt, *args):
def test_DataType(): def test_DataType():
dt = DataType() dt = DataType()
with pytest.raises(ProgrammingError): with pytest.raises(NotImplementedError):
dt.export_datatype() dt.export_datatype()
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
dt('') dt('')