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
# base class for all DataTypes
class DataType(HasProperties):
"""base class for all data types"""
IS_COMMAND = False
unit = ''
class SimpleDataType(HasProperties):
"""base class for simple datatypes, used in properties only"""
default = None
client = False # used on the client side
def __call__(self, value):
"""convert given value to our datatype and validate
@@ -105,38 +101,10 @@ class DataType(HasProperties):
"""
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):
"""if needed, reformat value for transport"""
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):
"""init datatype properties"""
try:
@@ -161,6 +129,34 @@ class DataType(HasProperties):
# looks like the simplest way to make a deep copy
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):
"""check other for compatibility
@@ -169,6 +165,10 @@ class DataType(HasProperties):
"""
raise NotImplementedError
def export_datatype(self):
"""return a python object which after jsonifying identifies this datatype"""
raise NotImplementedError
def set_main_unit(self, unit):
"""replace $ in unit by argument"""
@@ -1131,10 +1131,23 @@ class CommandType(DataType):
# 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):
"""accepts a datatype"""
if isinstance(value, DataType):
if isinstance(value, SimpleDataType):
return value
#TODO: not needed anymore?
try:
@@ -1155,7 +1168,7 @@ class DataTypeType(DataType):
raise NotImplementedError
class ValueType(DataType):
class ValueType(SimpleDataType):
"""Can take any python value.
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"""
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.
"""
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):
class NoneOr(SimpleDataType):
"""validates a None or smth. else"""
default = None
@@ -1222,7 +1220,7 @@ class NoneOr(DataType):
return self.other.export_value(value)
class OrType(DataType):
class OrType(SimpleDataType):
def __init__(self, *types):
super().__init__()
self.types = types

View File

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

View File

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