# SPDX-FileCopyrightText: All Contributors to the PyTango project
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Conversion between python values, tango vectors and their string forms."""
from tango import AttrDataFormat, CmdArgType, StdDoubleVector, StdStringVector
from tango.constants import AlrmValueNotSpec, StatusNotSet
from ._types import (
_array_float_types,
_array_int_types,
_scalar_float_types,
_scalar_int_types,
_scalar_str_types,
_scalar_types,
is_bool_type,
is_float_type,
is_int_type,
is_non_str_seq,
is_numerical_type,
is_pure_str,
is_scalar_type,
scalar_to_array_type,
)
__NO_STR_VALUE = AlrmValueNotSpec, StatusNotSet
def strtobool(val):
"""Convert a string representation of truth to True or False.
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
elif val in ("n", "no", "f", "false", "off", "0"):
return False
else:
raise ValueError(f"invalid truth value {val}")
def __seq_2_vector(seq, vec, vector_type, coerce):
"""Fill a tango vector from a python sequence, coercing each element."""
if vec is None:
if isinstance(seq, vector_type):
return seq
vec = vector_type()
if not isinstance(vec, vector_type):
raise TypeError(f"vec must be a tango.{vector_type.__name__}")
for e in seq:
vec.append(coerce(e))
return vec
def __vector_2_seq(vec, seq, vector_type, coerce):
"""Fill a python sequence from a tango vector, coercing each element."""
if seq is None:
seq = []
if not isinstance(vec, vector_type):
raise TypeError(f"vec must be a tango.{vector_type.__name__}")
for e in vec:
seq.append(coerce(e))
return seq
def seq_2_StdStringVector(seq, vec=None):
"""Converts a python sequence<str> object to a :class:`tango.StdStringVector`
:param seq: the sequence of strings
:type seq: sequence<:py:obj:`str`>
:param vec: (optional, default is None) an :class:`tango.StdStringVector`
to be filled. If None is given, a new :class:`tango.StdStringVector`
is created
:return: a :class:`tango.StdStringVector` filled with the same contents as seq
:rtype: :class:`tango.StdStringVector`
"""
return __seq_2_vector(seq, vec, StdStringVector, str)
def StdStringVector_2_seq(vec, seq=None):
"""Converts a :class:`tango.StdStringVector` to a python sequence<str>
:param seq: the :class:`tango.StdStringVector`
:type seq: :class:`tango.StdStringVector`
:param vec: (optional, default is None) a python sequence to be filled.
If None is given, a new list is created
:return: a python sequence filled with the same contents as seq
:rtype: sequence<str>
"""
return __vector_2_seq(vec, seq, StdStringVector, str)
def seq_2_StdDoubleVector(seq, vec=None):
"""Converts a python sequence<float> object to a :class:`tango.StdDoubleVector`
:param seq: the sequence of floats
:type seq: sequence<:py:obj:`float`>
:param vec: (optional, default is None) an :class:`tango.StdDoubleVector`
to be filled. If None is given, a new :class:`tango.StdDoubleVector`
is created
:return: a :class:`tango.StdDoubleVector` filled with the same contents as seq
:rtype: :class:`tango.StdDoubleVector`
"""
return __seq_2_vector(seq, vec, StdDoubleVector, float)
def StdDoubleVector_2_seq(vec, seq=None):
"""Converts a :class:`tango.StdDoubleVector` to a python sequence<float>
:param seq: the :class:`tango.StdDoubleVector`
:type seq: :class:`tango.StdDoubleVector`
:param vec: (optional, default is None) a python sequence to be filled.
If None is given, a new list is created
:return: a python sequence filled with the same contents as seq
:rtype: sequence<float>
"""
return __vector_2_seq(vec, seq, StdDoubleVector, float)
[docs]
def seqStr_2_obj(seq, tg_type, tg_format=None):
"""Translates a sequence<str> to a sequence of objects of give type and format
:param seq: the sequence
:type seq: sequence<str>
:param tg_type: tango type
:type tg_type: :class:`tango.CmdArgType`
:param tg_format: (optional, default is None, meaning SCALAR) tango format
:type tg_format: :class:`tango.AttrDataFormat`
:return: a new sequence
"""
# AttrDataFormat.SCALAR is 0, so None is the only "not given" value.
if tg_format is not None:
return _seqStr_2_obj_from_type_format(seq, tg_type, tg_format)
return _seqStr_2_obj_from_type(seq, tg_type)
def _seqStr_2_obj_from_type(seq, tg_type):
if is_pure_str(seq):
seq = (seq,)
# Scalar cases
if tg_type in _scalar_int_types:
return int(seq[0])
if tg_type in _scalar_float_types:
return float(seq[0])
if tg_type in _scalar_str_types:
return seq[0]
if tg_type == CmdArgType.DevBoolean:
return strtobool(seq[0])
# sequence cases
if tg_type in (CmdArgType.DevVarCharArray, CmdArgType.DevVarStringArray):
return seq
if tg_type in _array_int_types:
return [int(x) for x in seq]
if tg_type in _array_float_types:
return [float(x) for x in seq]
if tg_type == CmdArgType.DevVarBooleanArray:
return [strtobool(x) for x in seq]
return []
def _seqStr_2_obj_from_type_format(seq, tg_type, tg_format):
if tg_format == AttrDataFormat.SCALAR:
return _seqStr_2_obj_from_type(seq, tg_type)
elif tg_format == AttrDataFormat.SPECTRUM:
return _seqStr_2_obj_from_type(seq, scalar_to_array_type(tg_type))
elif tg_format == AttrDataFormat.IMAGE:
array_type = scalar_to_array_type(tg_type)
return [_seqStr_2_obj_from_type(row, array_type) for row in seq]
# UNKNOWN_FORMAT
return _seqStr_2_obj_from_type(seq, tg_type)
def str_2_obj(obj_str, tg_type=None):
"""Converts a string into an object according to the given tango type
:param obj_str: the string to be converted
:type obj_str: :py:obj:`str`
:param tg_type: tango type
:type tg_type: :class:`tango.CmdArgType`
:return: an object calculated from the given string
:rtype: :py:obj:`object`
"""
if tg_type is None:
return obj_str
f = str
if is_scalar_type(tg_type):
if is_numerical_type(tg_type) and obj_str in __NO_STR_VALUE:
return None
if is_int_type(tg_type):
f = int
elif is_float_type(tg_type):
f = float
elif is_bool_type(tg_type):
f = strtobool
return f(obj_str)
[docs]
def obj_2_str(obj, tg_type=None):
"""Converts a python object into a string according to the given tango type
:param obj: the object to be converted
:type obj: :py:obj:`object`
:param tg_type: tango type
:type tg_type: :class:`tango.CmdArgType`
:return: a string representation of the given object
:rtype: :py:obj:`str`
"""
if tg_type is None:
return obj
if tg_type in _scalar_types:
# scalar cases
if is_pure_str(obj):
return obj
elif is_non_str_seq(obj):
if not len(obj):
return ""
obj = obj[0]
return str(obj)
# sequence cases
if obj is None:
return ""
return "\n".join([str(i) for i in obj])
def ensure_binary(s, encoding="utf-8", errors="strict"):
"""Coerce **s** to the bytes type.
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
Code taken from https://github.com/benjaminp/six/blob/1.12.0/six.py#L853
"""
if isinstance(s, str):
return s.encode(encoding, errors)
elif isinstance(s, bytes):
return s
else:
raise TypeError(f"not expecting type '{type(s)}'")