1 | """StarStruct element class.""" |
||
2 | |||
3 | 1 | from typing import Optional, Tuple |
|
0 ignored issues
–
show
|
|||
4 | |||
5 | 1 | from starstruct.modes import Mode |
|
6 | |||
7 | |||
8 | 1 | def register(cls): |
|
9 | """ A handy decorator to register a class as an element """ |
||
10 | 1 | Element.register(cls) |
|
11 | 1 | return cls |
|
12 | |||
13 | |||
14 | 1 | class Element(object): |
|
15 | """ |
||
16 | A class factory that determines the type of the field passed in, and |
||
17 | instantiates the correct class type. |
||
18 | """ |
||
19 | 1 | elementtypes = [] |
|
20 | |||
21 | 1 | @classmethod |
|
22 | def register(cls, element): |
||
23 | """Function used to register new element subclasses.""" |
||
24 | 1 | cls.elementtypes.append(element) |
|
25 | |||
26 | 1 | @classmethod |
|
27 | 1 | def factory(cls, field: tuple, mode: Optional[Mode]=Mode.Native, alignment: Optional[int]=1): |
|
28 | """ |
||
29 | Initialize a StarStruct element object based on the type of element |
||
30 | parameters provided. |
||
31 | |||
32 | |||
33 | Where the values in the tuple determine the type of element. |
||
34 | |||
35 | These are the possible element types: |
||
36 | 1. Normal (base): a standard python struct format character, and a |
||
37 | field name are provided. The optional element should not provided. |
||
38 | |||
39 | 2. Enum: a standard python struct format character and field name are |
||
40 | provided, but the 3rd optional element is provided which is a |
||
41 | subclass of enum.Enum. |
||
42 | |||
43 | 3. Length: a standard python struct format character that represents |
||
44 | an unsigned numeric value, and the field name are provided, but the |
||
45 | 3rd optional element is provided and is a string. In this case the |
||
46 | string is assumed to be another field which is the name of a |
||
47 | Variable element. |
||
48 | |||
49 | 4. Variable: a variable length element that accommodates 0 or more of |
||
50 | another StarStruct.message. The format field should be a valid |
||
51 | StarStruct.message, the optional 3rd element must be provided and |
||
52 | should be the name of a valid Length element or an int. The |
||
53 | validity of the referenced element must be checked after the |
||
54 | creation of the entire message with the Message.validate() function. |
||
55 | |||
56 | 5. Discriminated: a message element that can have multiple formats |
||
57 | such as a C union. The format field should be a dictionary where |
||
58 | the keys represent values of a referenced enumeration field, and |
||
59 | the value for each entry is a valid StarStruct.message, or None. |
||
60 | The optional 3rd element must be provided and should be the name of |
||
61 | a valid Enum element. The validity of the referenced element must |
||
62 | be checked after the creation of the entire message with the |
||
63 | Message.validate() function. |
||
64 | |||
65 | |||
66 | :param field: The field must be a tuple of the following form:: |
||
67 | |||
68 | (name, format, <optional>) |
||
69 | |||
70 | :param mode: The mode in which to pack the information. |
||
71 | :param alignment: The number of bytes to align objects with. |
||
72 | :returns: An element whose fields match those passed in |
||
73 | """ |
||
74 | |||
75 | 1 | if not isinstance(mode, Mode): |
|
76 | raise TypeError('invalid mode: {}'.format(mode)) |
||
77 | |||
78 | # The field parameter is a single field tuple: |
||
79 | # ('name', 'format', <optional>) |
||
80 | 1 | if not isinstance(field, tuple): |
|
81 | raise TypeError('invalid element: {}'.format(field)) |
||
82 | |||
83 | # The name of the element must be a non-null string or bytes |
||
84 | # provided in as the first part of the field tuple |
||
85 | 1 | if not field[0] or not isinstance(field[0], (str, bytes)): |
|
86 | raise TypeError('invalid name: {}'.format(field[0])) |
||
87 | |||
88 | 1 | valid_elems = [] |
|
89 | 1 | for elem in cls.elementtypes: |
|
90 | 1 | try: |
|
91 | 1 | if elem.valid(field): |
|
92 | 1 | valid_elems.append(elem) |
|
93 | 1 | except (TypeError, KeyError): |
|
94 | 1 | continue |
|
95 | |||
96 | 1 | if len(valid_elems) > 1: |
|
97 | raise ValueError('More than one elemn was valid.\n\tField: {0}\n\tElems: {1}'.format( |
||
98 | field, valid_elems)) |
||
99 | 1 | elif len(valid_elems) == 1: |
|
100 | 1 | return valid_elems[0](field, mode, alignment) |
|
101 | |||
102 | # If the function made it this far, the field specification is not valid |
||
103 | raise TypeError('invalid field: {}'.format(field)) |
||
104 | |||
105 | 1 | @staticmethod |
|
106 | 1 | def valid(field: tuple) -> bool: |
|
107 | """ |
||
108 | Require element objects to implement this abstract function. |
||
109 | |||
110 | Validation function to determine if a field tuple represents a valid |
||
111 | element type. |
||
112 | |||
113 | The basics have already been validated by the Element factory class, |
||
114 | validate that the struct format is a valid numeric value. |
||
115 | |||
116 | :param field: The format specifier for an element |
||
117 | :returns: Whether this field tuple is valid for this class. |
||
118 | """ |
||
119 | raise NotImplementedError |
||
120 | |||
121 | 1 | def validate(self, msg: dict) -> bool: |
|
122 | """ |
||
123 | Require element objects to implement this function. |
||
124 | |||
125 | :param msg: The current values passed in to the element |
||
126 | :returns: Whether this message represents a valid element. |
||
127 | """ |
||
128 | raise NotImplementedError |
||
129 | |||
130 | 1 | def update(self, mode: Mode, alignment: int) -> None: |
|
131 | """ |
||
132 | Require element objects to implement this function. |
||
133 | |||
134 | :param mode: The new mode for the Element |
||
135 | :param alignment: The new alignment for the element |
||
136 | """ |
||
137 | raise NotImplementedError |
||
138 | |||
139 | 1 | def pack(self, msg: dict) -> bytes: |
|
140 | """ |
||
141 | Require element objects to implement this function. |
||
142 | |||
143 | :param msg: The values to pack into bytes |
||
144 | :returns: The msg packed into bytes as specified by the format |
||
145 | """ |
||
146 | raise NotImplementedError |
||
147 | |||
148 | 1 | def unpack(self, msg: dict, buf: bytes) -> Tuple[dict, bytes]: |
|
149 | """ |
||
150 | Require element objects to implement this function. |
||
151 | |||
152 | :param msg: The values unpacked thus far from the bytes |
||
153 | :param buf: The remaining bytes to unpack |
||
154 | :returns: The updated message and the remaining bytes |
||
155 | """ |
||
156 | raise NotImplementedError |
||
157 | |||
158 | 1 | def make(self, msg: dict): |
|
159 | """ |
||
160 | Require element objects to implement this function. |
||
161 | |||
162 | :param msg: The values to place into the named tuple object |
||
163 | |||
164 | :todo: How do I specify the correct type for this? |
||
165 | """ |
||
166 | raise NotImplementedError |
||
167 |
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.
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.