Skip to content

Commit

Permalink
Update/new data models (#67)
Browse files Browse the repository at this point in the history
* lestarch: refactoring data model architecture to avoid deepcopies

* mstarch: bug fixes for refactored type setup

* lestarch: formatted new data model architecture

* lestarch: sp

* lestarch: fixing UTs, models consistency

* lestarch: better models UTs and fixes there in

* lestarch: sp

* lestarch: formatting

* lestarch: fixing static analysis errors

* lestarch: more static analysis fixes

* lestarch: no more .val usage in BoolType implementation

* lestarch: fixing review requested items

* lestarch: sp

* lestarch: inconsistency after deserialization

* lestarch: formatting
  • Loading branch information
LeStarch committed May 13, 2022
1 parent d53e5aa commit 2b81916
Show file tree
Hide file tree
Showing 12 changed files with 610 additions and 350 deletions.
4 changes: 4 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ autoapi
autocoded
autocode
Autocoders
autocoders
autocoding
autodoc
autoescape
Expand Down Expand Up @@ -118,6 +119,7 @@ isdir
isfile
isinstance
isnumeric
issubclass
iterdir
itertools
itle
Expand All @@ -136,6 +138,7 @@ lestarch
LGTM
lgtm
Linux
lld
locs
lstrip
lxml
Expand Down Expand Up @@ -200,6 +203,7 @@ SCLK
scm
sdd
Serializables
serializables
setuptools
shutil
someotherpath
Expand Down
110 changes: 46 additions & 64 deletions src/fprime/common/models/serialize/array_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
Created on May 29, 2020
@author: jishii
"""
import copy

from .type_base import ValueType
from .type_base import DictionaryType
from .type_exceptions import (
ArrayLengthException,
NotInitializedException,
Expand All @@ -16,39 +14,38 @@
from fprime.util.string_util import format_string_template


class ArrayType(ValueType):
class ArrayType(DictionaryType):
"""Generic fixed-size array type representation.
Represents a custom named type of a fixed number of like members, each of which are other types in the system.
"""

def __init__(self, typename, config_info, val=None):
"""Constructs a new array type.
@classmethod
def construct_type(cls, name, member_type, length, format):
"""Constructs a sub-array type
Constructs a new sub-type of array to represent an array of the given name, member type, length, and format
string.
Args:
typename: name of this array type
config_info: (type, size, format string) information for array
val: (optional) list of values to assign to array
name: name of the array subtype
member_type: type of the members of the array subtype
length: length of the array subtype
format: format string for members of the array subtype
"""
super().__init__()
if not isinstance(typename, str):
raise TypeMismatchException(str, type(typename))
self.__val = None
self.__typename = typename
self.__arr_type, self.__arr_size, self.__arr_format = config_info
# Set value only if it is a valid, non-empty list
if not val:
return
self.val = val

def validate(self, val):
return DictionaryType.construct_type(
cls, name, MEMBER_TYPE=member_type, LENGTH=length, FORMAT=format
)

@classmethod
def validate(cls, val):
"""Validates the values of the array"""
size = self.__arr_size
if len(val) != size:
raise ArrayLengthException(self.__arr_type, size, len(val))
for i in range(self.__arr_size):
if not isinstance(val[i], type(self.__arr_type)):
raise TypeMismatchException(type(self.__arr_type), type(val[i]))
if not isinstance(val, (tuple, list)):
raise TypeMismatchException(list, type(val))
elif len(val) != cls.LENGTH:
raise ArrayLengthException(cls.MEMBER_TYPE, cls.LENGTH, len(val))
for i in range(cls.LENGTH):
cls.MEMBER_TYPE.validate(val[i])

@property
def val(self) -> list:
Expand All @@ -58,7 +55,9 @@ def val(self) -> list:
:return dictionary of member names to python values of member keys
"""
return [item.val for item in self.__val]
if self._val is None:
return None
return [item.val for item in self._val]

@property
def formatted_val(self) -> list:
Expand All @@ -69,11 +68,11 @@ def formatted_val(self) -> list:
:return a formatted array
"""
result = []
for item in self.__val:
for item in self._val:
if isinstance(item, (serializable_type.SerializableType, ArrayType)):
result.append(item.formatted_val)
else:
result.append(format_string_template(self.__arr_format, item.val))
result.append(format_string_template(self.FORMAT, item.val))
return result

@val.setter
Expand All @@ -85,58 +84,41 @@ def val(self, val: list):
:param val: dictionary containing python types to key names. This
"""
items = []
for item in val:
cloned = copy.deepcopy(self.arr_type)
cloned.val = item
items.append(cloned)
self.__val = items
self.validate(val)
items = [self.MEMBER_TYPE(item) for item in val]
self._val = items

def to_jsonable(self):
"""
JSONable type
"""
members = {
"name": self.__typename,
"type": self.__typename,
"size": self.__arr_size,
"format": self.__arr_format,
"name": self.__class__.__name__,
"type": self.__class__.__name__,
"size": self.LENGTH,
"format": self.FORMAT,
"values": None
if self.__val is None
else [member.to_jsonable() for member in self.__val],
if self._val is None
else [member.to_jsonable() for member in self._val],
}
return members

def serialize(self):
"""Serialize the array by serializing the elements one by one"""
if self.val is None:
raise NotInitializedException(type(self))
return b"".join([item.serialize() for item in self.val])
return b"".join([item.serialize() for item in self._val])

def deserialize(self, data, offset):
"""Deserialize the members of the array"""
values = []
for i in range(self.__arr_size):
item = copy.deepcopy(self.arr_type)
item.deserialize(data, offset + i * item.getSize())
values.append(item.val)
self.val = values

@property
def arr_type(self):
"""Property representing the size of the array"""
return self.__arr_type

@property
def arr_size(self):
"""Property representing the number of elements of the array"""
return self.__arr_size

@property
def arr_format(self):
"""Property representing the format string of an item in the array"""
return self.__arr_format
for i in range(self.LENGTH):
item = self.MEMBER_TYPE()
item.deserialize(data, offset)
offset += item.getSize()
values.append(item)
self._val = values

def getSize(self):
"""Return the size of the array"""
return self.arr_type.getSize() * self.arr_size
return sum([item.getSize() for item in self._val])
9 changes: 5 additions & 4 deletions src/fprime/common/models/serialize/bool_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@ class BoolType(ValueType):
TRUE = 0xFF
FALSE = 0x00

def validate(self, val):
@classmethod
def validate(cls, val):
"""Validate the given class"""
if not isinstance(val, bool):
raise TypeMismatchException(bool, type(val))

def serialize(self):
"""Serialize a boolean value"""
if self.val is None:
if self._val is None:
raise NotInitializedException(type(self))
return struct.pack("B", 0xFF if self.val else 0x00)
return struct.pack("B", self.TRUE if self._val else self.FALSE)

def deserialize(self, data, offset):
"""Deserialize boolean value"""
try:
int_val = struct.unpack_from("B", data, offset)[0]
if int_val not in [self.TRUE, self.FALSE]:
raise TypeRangeException(int_val)
self.val = int_val == self.TRUE
self._val = int_val == self.TRUE
except struct.error:
raise DeserializeException("Not enough bytes to deserialize bool.")

Expand Down
78 changes: 34 additions & 44 deletions src/fprime/common/models/serialize/enum_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
import struct

from .type_base import ValueType
from .type_base import DictionaryType
from .type_exceptions import (
DeserializeException,
EnumMismatchException,
Expand All @@ -14,7 +14,7 @@
)


class EnumType(ValueType):
class EnumType(DictionaryType):
"""
Representation of the ENUM type.
Expand All @@ -23,75 +23,65 @@ class EnumType(ValueType):
containing code based on C enum rules
"""

def __init__(self, typename="", enum_dict=None, val=None):
"""
Constructor
@classmethod
def construct_type(cls, name, enum_dict):
"""Construct the custom enum type
:param typename: name of the enumeration type
:param enum_dict: dictionary of value to integer representation
:param val: value of the enumeration
"""
super().__init__()
if not isinstance(typename, str):
raise TypeMismatchException(str, type(val))
self.__typename = typename
# Setup the enum dictionary
if enum_dict is None:
enum_dict = {"UNDEFINED": 0}
# Check if the enum dict is an instance of dictionary
self.__enum_dict = enum_dict
# Set val to undefined if not set
if val is None:
val = "UNDEFINED"
self.val = val
Constructs the custom enumeration type, with the supplied enumeration dictionary.
def validate(self, val):
"""Validate the value passed into the enumeration"""
if not isinstance(self.enum_dict(), dict):
raise TypeMismatchException(dict, type(self.enum_dict()))
for member in self.keys():
Args:
name: name of the enumeration type
enum_dict: enumeration: value dictionary defining the enumeration
"""
if not isinstance(enum_dict, dict):
raise TypeMismatchException(dict, type(enum_dict))
for member in enum_dict.keys():
if not isinstance(member, str):
raise TypeMismatchException(str, type(member))
elif not isinstance(self.enum_dict()[member], int):
raise TypeMismatchException(int, self.enum_dict()[member])
if val != "UNDEFINED" and val not in self.keys():
raise EnumMismatchException(self.__typename, val)
elif not isinstance(enum_dict[member], int):
raise TypeMismatchException(int, enum_dict[member])
return DictionaryType.construct_type(cls, name, ENUM_DICT=enum_dict)

def keys(self):
@classmethod
def validate(cls, val):
"""Validate the value passed into the enumeration"""
if not isinstance(val, str):
raise TypeMismatchException(str, type(val))
if val not in cls.keys():
raise EnumMismatchException(cls.__class__.__name__, val)

@classmethod
def keys(cls):
"""
Return all the enum key values.
"""
return list(self.enum_dict().keys())

def typename(self):
return self.__typename

def enum_dict(self):
return self.__enum_dict
return list(cls.ENUM_DICT.keys())

def serialize(self):
"""
Serialize the enumeration type using an int type
"""
# for enums, take the string value and convert it to
# the numeric equivalent
if self.val is None:
if self._val is None or (
self._val == "UNDEFINED" and "UNDEFINED" not in self.ENUM_DICT
):
raise NotInitializedException(type(self))
return struct.pack(">i", self.enum_dict()[self.val])
return struct.pack(">i", self.ENUM_DICT[self._val])

def deserialize(self, data, offset):
"""
Deserialize the enumeration using an int type
"""
try:
int_val = struct.unpack_from(">i", data, offset)[0]
except:
except struct.error:
raise DeserializeException(
f"Could not deserialize enum value. Needed: {self.getSize()} bytes Found: {len(data[offset:])}"
)
for key, val in self.enum_dict().items():
for key, val in self.ENUM_DICT.items():
if int_val == val:
self.val = key
self._val = key
break
# Value not found, invalid enumeration value
else:
Expand Down
Loading

0 comments on commit 2b81916

Please sign in to comment.