odxtools 8.3.4__py3-none-any.whl → 9.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,34 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import warnings
3
2
  from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, cast
3
+ from typing import Any, Dict, List, Optional
5
4
  from xml.etree import ElementTree
6
5
 
7
6
  from typing_extensions import override
8
7
 
8
+ from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
9
+ composite_codec_get_free_parameters, composite_codec_get_required_parameters,
10
+ composite_codec_get_static_bit_length)
9
11
  from .complexdop import ComplexDop
10
- from .dataobjectproperty import DataObjectProperty
11
12
  from .decodestate import DecodeState
12
13
  from .encodestate import EncodeState
13
- from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
14
+ from .exceptions import DecodeError, odxraise
14
15
  from .nameditemlist import NamedItemList
15
16
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
- from .odxtypes import ParameterDict, ParameterValue, ParameterValueDict
17
- from .parameters.codedconstparameter import CodedConstParameter
17
+ from .odxtypes import ParameterValue
18
18
  from .parameters.createanyparameter import create_any_parameter_from_et
19
- from .parameters.lengthkeyparameter import LengthKeyParameter
20
- from .parameters.matchingrequestparameter import MatchingRequestParameter
21
19
  from .parameters.parameter import Parameter
22
- from .parameters.parameterwithdop import ParameterWithDOP
23
- from .parameters.physicalconstantparameter import PhysicalConstantParameter
24
- from .parameters.tablekeyparameter import TableKeyParameter
25
20
  from .snrefcontext import SnRefContext
26
21
  from .utils import dataclass_fields_asdict
27
22
 
28
23
 
29
24
  @dataclass
30
25
  class BasicStructure(ComplexDop):
26
+ """Base class for structure-like objects
27
+
28
+ "Structure-like" objects are structures as well as environment
29
+ data objects. All structure-like objects adhere to the
30
+ `CompositeCodec` type protocol.
31
+ """
31
32
  parameters: NamedItemList[Parameter]
32
33
  byte_size: Optional[int]
33
34
 
@@ -48,62 +49,20 @@ class BasicStructure(ComplexDop):
48
49
  return BasicStructure(parameters=parameters, byte_size=byte_size, **kwargs)
49
50
 
50
51
  def get_static_bit_length(self) -> Optional[int]:
51
- # Explicit size was specified
52
- if self.byte_size:
52
+ # Explicit size was specified, so we do not need to look at
53
+ # the list of parameters
54
+ if self.byte_size is not None:
53
55
  return 8 * self.byte_size
54
56
 
55
- cursor = 0
56
- byte_length = 0
57
- for param in self.parameters:
58
- param_bit_length = param.get_static_bit_length()
59
- if param_bit_length is None:
60
- # We were not able to calculate a static bit length
61
- return None
62
- elif param.byte_position is not None:
63
- cursor = param.byte_position
64
-
65
- cursor += ((param.bit_position or 0) + param_bit_length + 7) // 8
66
- byte_length = max(byte_length, cursor)
67
-
68
- # Round up to account for padding bits (all structures are
69
- # byte aligned)
70
- return byte_length * 8
71
-
72
- def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
73
- encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
74
-
75
- for param in self.parameters:
76
- if (isinstance(param, MatchingRequestParameter) and param.request_byte_position < len(request_prefix)) or \
77
- isinstance(param, (CodedConstParameter, PhysicalConstantParameter)):
78
- param.encode_into_pdu(physical_value=None, encode_state=encode_state)
79
- else:
80
- break
81
-
82
- return encode_state.coded_message
57
+ return composite_codec_get_static_bit_length(self)
83
58
 
84
59
  @property
85
60
  def required_parameters(self) -> List[Parameter]:
86
- """Return the list of parameters which are required for
87
- encoding the structure."""
88
- return [p for p in self.parameters if p.is_required]
61
+ return composite_codec_get_required_parameters(self)
89
62
 
90
63
  @property
91
64
  def free_parameters(self) -> List[Parameter]:
92
- """Return the list of parameters which can be freely specified by
93
- the user when encoding the structure.
94
-
95
- This means all required parameters plus the parameters that
96
- can be omitted minus those which are implicitly specified by
97
- the corresponding request (in the case of responses).
98
-
99
- """
100
- result: List[Parameter] = []
101
- for param in self.parameters:
102
- if not param.is_settable:
103
- continue
104
- result.append(param)
105
-
106
- return result
65
+ return composite_codec_get_free_parameters(self)
107
66
 
108
67
  def print_free_parameters_info(self) -> None:
109
68
  """Return a human readable description of the structure's
@@ -113,171 +72,44 @@ class BasicStructure(ComplexDop):
113
72
 
114
73
  print(parameter_info(self.free_parameters), end="")
115
74
 
116
- def _validate_coded_message_size(self, coded_byte_len: int) -> None:
117
-
118
- if self.byte_size is not None:
119
- # We definitely broke something if we didn't respect the explicit byte_size
120
- if self.byte_size != coded_byte_len:
121
- warnings.warn(
122
- "Verification of coded message failed: Incorrect size.",
123
- OdxWarning,
124
- stacklevel=1)
125
-
126
- return
127
-
128
- bit_length = self.get_static_bit_length()
129
-
130
- if bit_length is None:
131
- # Nothing to check
132
- return
133
-
134
- if coded_byte_len * 8 != bit_length:
135
- # We may have broke something
136
- # but it could be that bit_length was mis calculated and not the actual bytes are wrong
137
- # Could happen with overlapping parameters and parameters with gaps
138
- warnings.warn(
139
- "Verification of coded message possibly failed: Size may be incorrect.",
140
- OdxWarning,
141
- stacklevel=1)
142
-
143
75
  @override
144
76
  def encode_into_pdu(self, physical_value: Optional[ParameterValue],
145
77
  encode_state: EncodeState) -> None:
146
- if not isinstance(physical_value, dict):
147
- odxraise(
148
- f"Expected a dictionary for the values of structure {self.short_name}, "
149
- f"got {type(physical_value).__name__}", EncodeError)
150
- elif encode_state.cursor_bit_position != 0:
151
- odxraise(
152
- f"Structures must be byte aligned, but "
153
- f"{self.short_name} requested to be at bit position "
154
- f"{encode_state.cursor_bit_position}", EncodeError)
155
- encode_state.bit_position = 0
156
-
157
- orig_origin = encode_state.origin_byte_position
158
- encode_state.origin_byte_position = encode_state.cursor_byte_position
159
-
160
- orig_is_end_of_pdu = encode_state.is_end_of_pdu
161
- encode_state.is_end_of_pdu = False
162
-
163
- # ensure that no values for unknown parameters are specified.
164
- if not encode_state.allow_unknown_parameters:
165
- param_names = {param.short_name for param in self.parameters}
166
- for param_value_name in physical_value:
167
- if param_value_name not in param_names:
168
- odxraise(f"Value for unknown parameter '{param_value_name}' specified "
169
- f"for structure {self.short_name}")
78
+ orig_pos = encode_state.cursor_byte_position
79
+
80
+ composite_codec_encode_into_pdu(self, physical_value, encode_state)
170
81
 
171
- for param in self.parameters:
172
- if id(param) == id(self.parameters[-1]):
173
- # The last parameter of the structure is at the end of
174
- # the PDU if the structure itself is at the end of the
175
- # PDU. TODO: This assumes that the last parameter
176
- # specified in the ODX is located last in the PDU...
177
- encode_state.is_end_of_pdu = orig_is_end_of_pdu
178
-
179
- if isinstance(param, (LengthKeyParameter, TableKeyParameter)):
180
- # At this point, we encode a placeholder value for length-
181
- # and table keys, since these can be specified
182
- # implicitly (i.e., by means of parameters that use
183
- # these keys). To avoid getting an "overlapping
184
- # parameter" warning, we must encode a value of zero
185
- # into the PDU here and add the real value of the
186
- # parameter in a post-processing step.
187
- param.encode_placeholder_into_pdu(
188
- physical_value=physical_value.get(param.short_name), encode_state=encode_state)
189
-
190
- continue
191
-
192
- if param.is_required and param.short_name not in physical_value:
193
- odxraise(f"No value for required parameter {param.short_name} specified",
194
- EncodeError)
195
-
196
- param_phys_value = physical_value.get(param.short_name)
197
- param.encode_into_pdu(physical_value=param_phys_value, encode_state=encode_state)
198
-
199
- encode_state.journal.append((param, param_phys_value))
200
-
201
- encode_state.is_end_of_pdu = False
202
82
  if self.byte_size is not None:
203
- actual_len = encode_state.cursor_byte_position - encode_state.origin_byte_position
83
+ actual_len = encode_state.cursor_byte_position - orig_pos
84
+
204
85
  if actual_len < self.byte_size:
205
- # Padding bytes needed. We add an empty object at the
206
- # position directly after the structure and let
86
+ # Padding bytes are needed. We add an empty object at
87
+ # the position directly after the structure and let
207
88
  # EncodeState add the padding as needed.
208
89
  encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_size
209
90
  # Padding bytes needed. these count as "used".
210
91
  encode_state.coded_message += b"\x00" * (self.byte_size - actual_len)
211
92
  encode_state.used_mask += b"\xff" * (self.byte_size - actual_len)
212
93
 
213
- # encode the length- and table keys. This cannot be done above
214
- # because we allow these to be defined implicitly (i.e. they
215
- # are defined by their respective users)
216
- for param in self.parameters:
217
- if not isinstance(param, (LengthKeyParameter, TableKeyParameter)):
218
- # the current parameter is neither a length- nor a table key
219
- continue
220
-
221
- # Encode the value of the key parameter into the message
222
- param.encode_value_into_pdu(encode_state=encode_state)
223
-
224
- # Assert that length is as expected
225
- self._validate_coded_message_size(encode_state.cursor_byte_position -
226
- encode_state.origin_byte_position)
227
-
228
- encode_state.origin_byte_position = orig_origin
229
-
230
94
  @override
231
95
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
232
- # move the origin since positions specified by sub-parameters of
233
- # structures are relative to the beginning of the structure object.
234
- orig_origin = decode_state.origin_byte_position
235
- decode_state.origin_byte_position = decode_state.cursor_byte_position
96
+ orig_pos = decode_state.cursor_byte_position
236
97
 
237
- result = {}
238
- for param in self.parameters:
239
- value = param.decode_from_pdu(decode_state)
98
+ result = composite_codec_decode_from_pdu(self, decode_state)
240
99
 
241
- decode_state.journal.append((param, value))
242
- result[param.short_name] = value
100
+ if self.byte_size is not None:
101
+ n = decode_state.cursor_byte_position - orig_pos
102
+ if n > self.byte_size:
103
+ odxraise(
104
+ f"Attempted to decode too large instance of structure "
105
+ f"{self.short_name} ({n} instead at most "
106
+ f"{self.byte_size} bytes)", DecodeError)
107
+ return result
243
108
 
244
- # decoding of the structure finished. go back the original origin.
245
- decode_state.origin_byte_position = orig_origin
109
+ decode_state.cursor_byte_position = orig_pos + self.byte_size
246
110
 
247
111
  return result
248
112
 
249
- def decode(self, message: bytes) -> ParameterValueDict:
250
- decode_state = DecodeState(coded_message=message)
251
- param_values = self.decode_from_pdu(decode_state)
252
-
253
- if not isinstance(param_values, dict):
254
- odxraise("Decoding structures must result in a dictionary")
255
-
256
- return cast(ParameterValueDict, param_values)
257
-
258
- def parameter_dict(self) -> ParameterDict:
259
- """
260
- Returns a dictionary with all parameter short names as keys.
261
-
262
- The values are parameters for simple types or a nested dict for structures.
263
- """
264
- from .structure import Structure
265
- odxassert(
266
- all(not isinstance(p, ParameterWithDOP) or isinstance(p.dop, DataObjectProperty) or
267
- isinstance(p.dop, Structure) for p in self.parameters))
268
- param_dict: ParameterDict = {
269
- p.short_name: p
270
- for p in self.parameters
271
- if not isinstance(p, ParameterWithDOP) or not isinstance(p.dop, Structure)
272
- }
273
- param_dict.update({
274
- struct_param.short_name: struct_param.dop.parameter_dict()
275
- for struct_param in self.parameters
276
- if isinstance(struct_param, ParameterWithDOP) and
277
- isinstance(struct_param.dop, BasicStructure)
278
- })
279
- return param_dict
280
-
281
113
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
282
114
  result = super()._build_odxlinks()
283
115
 
@@ -287,18 +119,15 @@ class BasicStructure(ComplexDop):
287
119
  return result
288
120
 
289
121
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
290
- """Recursively resolve any references (odxlinks or sn-refs)"""
291
122
  super()._resolve_odxlinks(odxlinks)
292
123
 
293
124
  for param in self.parameters:
294
125
  param._resolve_odxlinks(odxlinks)
295
126
 
296
127
  def _resolve_snrefs(self, context: SnRefContext) -> None:
297
- """Recursively resolve any references (odxlinks or sn-refs)"""
298
128
  context.parameters = self.parameters
299
129
 
300
130
  super()._resolve_snrefs(context)
301
-
302
131
  for param in self.parameters:
303
132
  param._resolve_snrefs(context)
304
133