Completed
Pull Request — master (#35)
by
unknown
07:26
created

ElementCallable   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 111
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 111
rs 10
wmc 21

8 Methods

Rating   Name   Duplication   Size   Complexity  
A valid() 0 12 1
A _struct() 0 3 1
A unpack() 0 9 2
D make() 0 31 8
A validate() 0 14 3
A update() 0 7 3
A pack() 0 3 1
A __init__() 0 16 2
1
"""
2
Element callable.
3
4
Call a function to validate data.
5
6
.. code-block:: python
7
8
    from binascii import crc32
9
10
    ExampleMessage = Message('VarTest', [('x', 'B'), ('y', 'B')])
11
12
    CRCedMessage = Message('CRCedMessage', [
13
       ('data', ExampleMessage),                    # A data field that has the example message in it
14
       ('crc', 'I', crc32, ['data']),
15
   ])
16
17
Following creating this message, you have two options:
18
19
1. Specify a value. The function will be used to validate the value.
20
2. Use the function to generate a value.
21
"""
22
23
import struct
24
25
from typing import Optional
0 ignored issues
show
Configuration introduced by
The import typing could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
26
27
from starstruct.element import register, Element
28
from starstruct.modes import Mode
29
30
31
@register
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
32
class ElementCallable(Element):
33
    """
34
    Initialize a StarStruct element object.
35
36
    :param field: The fields passed into the constructor of the element
37
    :param mode: The mode in which to pack the bytes
38
    :param alignment: Number of bytes to align to
39
    """
40
    def __init__(self, field: list, mode: Optional[Mode]=Mode.Native, alignment: Optional[int]=1):
41
        # All of the type checks have already been performed by the class
42
        # factory
43
        self.name = field[0]
44
45
        # Callable elements use the normal struct packing method
46
        self.format = field[1]
47
        self._func_ref = field[2]
48
        self._func_args = field[3]
49
50
        if len(field) == 5:
51
            self._error_on_bad_result = field[4]
52
        else:
53
            self._error_on_bad_result = True
54
55
        self.update(mode, alignment)
56
57
    @property
58
    def _struct(self):
59
        return struct.Struct(self._mode.value + self.format)
60
61
    @staticmethod
62
    def valid(field: tuple) -> bool:
63
        """
64
        See :py:func:`starstruct.element.Element.valid`
65
66
        :param field: The items to determine the structure of the element
67
        """
68
        return len(field) >= 4 \
69
            and isinstance(field[0], str) \
70
            and isinstance(field[1], str) \
71
            and callable(field[2]) \
72
            and isinstance(field[3], list)
73
74
    def validate(self, msg):
75
        """
76
        Ensure that the supplied message contains the required information for
77
        this element object to operate.
78
79
        All elements that are Variable must reference valid Length elements.
80
        """
81
        # TODO: Validate the object
82
        self._elements = msg
0 ignored issues
show
Coding Style introduced by
The attribute _elements was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
83
84
        if not all(k in msg for k in self._func_args):
85
            raise ValueError('Need all keys to be in the message')
86
87
        pass
0 ignored issues
show
Unused Code introduced by
Unnecessary pass statement
Loading history...
88
89
    def update(self, mode=None, alignment=None):
90
        """change the mode of the struct format"""
91
        if mode:
92
            self._mode = mode
93
94
        if alignment:
95
            self._alignment = alignment
96
97
    def pack(self, msg):
98
        """Pack the provided values into the supplied buffer."""
99
        return self._struct.pack(self.make(msg))
100
101
    def unpack(self, msg, buf):
102
        """Unpack data from the supplied buffer using the initialized format."""
103
        ret = self._struct.unpack_from(buf)
104
        if isinstance(ret, (list, tuple)):
105
            # TODO: I don't know if there is a case where we want to keep
106
            # it as a list... but for now I'm just going to do this
107
            ret = ret[0]
108
109
        return (ret, buf[self._struct.size:])
110
111
    def make(self, msg):
112
        """Return the expected "made" value"""
113
        # If we aren't going to error on a bad result
114
        # and our name is in the message, just send the value
115
        # No need to do extra work.
116
        if not self._error_on_bad_result and self.name in msg:
117
            return msg[self.name]
118
119
        items = []
120
121
        for reference in self._func_args:
122
            if isinstance(reference, str):
123
                index = reference
124
                attr = 'make'
125
            elif isinstance(reference, bytes):
126
                index = reference.decode('utf-8')
127
                attr = 'pack'
128
            else:
129
                raise ValueError('Needed str or bytes for the reference')
130
131
            items.append(
132
                getattr(self._elements[index], attr)(msg)
133
            )
134
135
        ret = self._func_ref(*items)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
136
137
        if self.name in msg:
138
            if ret != msg[self.name]:
139
                raise ValueError('Excepted value: {0}, but got: {1}'.format(ret, msg[self.name]))
140
141
        return ret
142