ElementDiscriminated   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Test Coverage

Coverage 71.15%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
c 6
b 1
f 0
dl 0
loc 126
ccs 37
cts 52
cp 0.7115
rs 10
wmc 22

7 Methods

Rating   Name   Duplication   Size   Complexity  
C validate() 0 26 7
B pack() 0 22 4
A update() 0 8 3
A unpack() 0 16 2
A make() 0 12 3
A valid() 0 14 2
A __init__() 0 16 1
1
"""StarStruct element class."""
2
3 1
import starstruct
4 1
from starstruct.element import register, Element
5 1
from starstruct.modes import Mode
6
7
8 1
@register
9 1
class ElementDiscriminated(Element):
10
    """
11
    The discriminated StarStruct element class.
12
    """
13
14 1
    def __init__(self, field, mode=Mode.Native, alignment=1):
15
        """Initialize a StarStruct element object."""
16
17
        # All of the type checks have already been performed by the class
18
        # factory
19 1
        self.name = field[0]
20 1
        self.ref = field[2]
21
22
        # Discriminated elements don't use the normal struct format, the format
23
        # is the supplied dictionary where the key is a value of the referenced
24
        # enum element, and the value for each entry is a StarStruct.Message
25
        # object.
26 1
        self.format = field[1]
27
28
        # but change the mode to match the current mode.
29 1
        self.update(mode, alignment)
30
31 1
    @staticmethod
32
    def valid(field):
33
        """
34
        Validation function to determine if a field tuple represents a valid
35
        enum element type.
36
37
        The basics have already been validated by the Element factory class,
38
        validate that the struct format is a valid numeric value.
39
        """
40 1
        return len(field) == 3 \
41
            and isinstance(field[1], dict) \
42
            and isinstance(field[2], str) \
43
            and all(isinstance(val, (starstruct.message.Message, type(None)))
44
                    for val in field[1].values())
45
46 1
    def validate(self, msg):
47
        """
48
        Ensure that the supplied message contains the required information for
49
        this element object to operate.
50
51
        All Discriminated elements must reference valid Enum elements, and the
52
        keys of the discriminated format must be valid instances of the
53
        referenced Enum class.
54
        """
55 1
        from starstruct.elementenum import ElementEnum
56 1
        if not isinstance(msg[self.ref], ElementEnum):
57
            err = 'discriminated field {} reference {} invalid type'
58
            raise TypeError(err.format(self.name, self.ref))
59 1
        elif not all(isinstance(key, msg[self.ref].ref)
60
                     for key in self.format.keys()):
61
            err = 'discriminated field {} reference {} mismatch'
62
            raise TypeError(err.format(self.name, self.ref))
63
        else:
64 1
            for key in self.format.keys():
65 1
                try:
66 1
                    ref_cls = msg[self.ref].ref
67 1
                    assert ref_cls(key)
68
                except:
69
                    err = 'discriminated field {} key {} not a valid {}'
70
                    msg = err.format(self.name, key, self.ref)
71
                    raise TypeError(msg)
72
73 1
    def update(self, mode=None, alignment=None):
74
        """change the mode of each message format"""
75 1
        self._mode = mode
76 1
        self._alignment = alignment
77
78 1
        for key in self.format.keys():
79 1
            if self.format[key] is not None:
80 1
                self.format[key].update(mode, alignment)
81
82 1
    def pack(self, msg):
83
        """Pack the provided values into the supplied buffer."""
84
        # When packing use the value of the referenced element to determine
85
        # which field format to use to pack this element.  Be sure to check if
86
        # the referenced format is None or a Message object.
87 1
        if msg[self.ref] not in self.format:
88
            msg = 'invalid value {} for element {}:{}'.format(
89
                msg[self.ref], self.name, self.format.keys())
90
            raise ValueError(msg)
91
92 1
        if self.format[msg[self.ref]] is not None:
93 1
            if msg[self.name] is not None:
94 1
                data = self.format[msg[self.ref]].pack(dict(msg[self.name]))
95
            else:
96
                data = self.format[msg[self.ref]].pack({})
97
        else:
98
            data = b''
99
100
        # There is no need to make sure that the packed data is properly
101
        # aligned, because that should already be done by the individual
102
        # messages that have been packed.
103 1
        return data
104
105 1
    def unpack(self, msg, buf):
106
        """Unpack data from the supplied buffer using the initialized format."""
107
        # When unpacking a discriminated element, reference the already unpacked
108
        # enum field to determine how many elements need unpacked.  If the
109
        # specific value is None rather than a Message object, return no new
110
        # parsed data.
111
        #
112
        # There is no need to make sure that the unpacked data consumes a
113
        # properly aligned number of bytes because that should already be done
114
        # by the message that is unpacked.
115
        #
116
        # Use the getattr() function since the referenced value is an enum
117 1
        if self.format[getattr(msg, self.ref)] is not None:
118 1
            return self.format[getattr(msg, self.ref)].unpack_partial(buf)
119
        else:
120
            return (None, buf)
121
122 1
    def make(self, msg):
123
        """Return the expected "made" value"""
124 1
        if hasattr(msg, self.ref):
125
            key = getattr(msg, self.ref)
126
        else:
127
            # Assume it's a dictionary, not a tuple
128 1
            key = msg[self.ref]
129
130 1
        if self.format[key] is not None:
131 1
            return self.format[key].make(msg[self.name])
132
        else:
133
            return None
134