odxtools 7.2.0__py3-none-any.whl → 7.3.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.
odxtools/multiplexer.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional, Tuple
3
+ from typing import Any, Dict, List, Optional, Tuple, Union, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -8,7 +8,7 @@ from typing_extensions import override
8
8
  from .complexdop import ComplexDop
9
9
  from .decodestate import DecodeState
10
10
  from .encodestate import EncodeState
11
- from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
11
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
12
12
  from .multiplexercase import MultiplexerCase
13
13
  from .multiplexerdefaultcase import MultiplexerDefaultCase
14
14
  from .multiplexerswitchkey import MultiplexerSwitchKey
@@ -79,87 +79,123 @@ class Multiplexer(ComplexDop):
79
79
  def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
80
80
 
81
81
  if encode_state.cursor_bit_position != 0:
82
- raise EncodeError(f"Multiplexer must be aligned, i.e. bit_position=0, but "
82
+ raise EncodeError(f"Multiplexer parameters must be aligned, i.e. bit_position=0, but "
83
83
  f"{self.short_name} was passed the bit position "
84
84
  f"{encode_state.cursor_bit_position}")
85
85
 
86
- if not isinstance(physical_value, dict) or len(physical_value) != 1:
87
- raise EncodeError("""Multiplexer should be defined as a dict
88
- with only one key equal to the desired case""")
89
-
90
86
  orig_origin = encode_state.origin_byte_position
91
-
92
87
  encode_state.origin_byte_position = encode_state.cursor_byte_position
93
88
 
94
- case_name, case_value = next(iter(physical_value.items()))
95
-
96
- for mux_case in self.cases or []:
97
- if mux_case.short_name == case_name:
98
- key_value, _ = self._get_case_limits(mux_case)
99
-
100
- if self.switch_key.byte_position is not None:
101
- encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
102
- encode_state.cursor_bit_position = self.switch_key.bit_position or 0
103
-
104
- self.switch_key.dop.encode_into_pdu(
105
- physical_value=key_value, encode_state=encode_state)
106
-
107
- if self.byte_position is not None:
108
- encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
109
- encode_state.cursor_bit_position = 0
110
-
111
- if mux_case._structure is None:
112
- odxraise(f"Multiplexer case '{mux_case.short_name}' does not "
113
- f"reference a structure.")
114
- return
115
-
116
- mux_case.structure.encode_into_pdu(
117
- physical_value=key_value, encode_state=encode_state)
118
-
119
- encode_state.origin_byte_position = orig_origin
120
- return
121
-
122
- raise EncodeError(f"The case {case_name} is not found in Multiplexer {self.short_name}")
89
+ if isinstance(physical_value, (list, tuple)) and len(physical_value) == 2:
90
+ case_spec, case_value = physical_value
91
+ elif isinstance(physical_value, dict) and len(physical_value) == 1:
92
+ case_spec, case_value = next(iter(physical_value.items()))
93
+ else:
94
+ raise EncodeError(
95
+ f"Values of multiplexer parameters must be defined as a "
96
+ f"(case_name, content_value) tuple instead of as '{physical_value!r}'")
97
+
98
+ mux_case: Union[MultiplexerCase, MultiplexerDefaultCase]
99
+ if isinstance(case_spec, str):
100
+ applicable_cases = [x for x in self.cases if x.short_name == case_spec]
101
+ if len(applicable_cases) == 0:
102
+ raise EncodeError(
103
+ f"Multiplexer {self.short_name} does not know any case called {case_spec}")
104
+
105
+ odxassert(len(applicable_cases) == 1)
106
+ mux_case = applicable_cases[0]
107
+ key_value, _ = self._get_case_limits(mux_case)
108
+ elif isinstance(case_spec, int):
109
+ applicable_cases = []
110
+ for x in self.cases:
111
+ lower, upper = cast(Tuple[int, int], self._get_case_limits(x))
112
+ if lower <= case_spec and case_spec <= upper:
113
+ applicable_cases.append(x)
114
+
115
+ if len(applicable_cases) == 0:
116
+ if self.default_case is None:
117
+ raise EncodeError(
118
+ f"Multiplexer {self.short_name} does not know any case called {case_spec}")
119
+ mux_case = self.default_case
120
+ key_value = case_spec
121
+ else:
122
+ mux_case = applicable_cases[0]
123
+ key_value = case_spec
124
+ elif isinstance(case_spec, MultiplexerCase):
125
+ mux_case = case_spec
126
+ key_value, _ = self._get_case_limits(mux_case)
127
+ elif case_spec is None:
128
+ if self.default_case is None:
129
+ raise EncodeError(f"Multiplexer {self.short_name} does not define a default case")
130
+ key_value = 0
131
+ else:
132
+ raise EncodeError(f"Illegal case specification '{case_spec}' for "
133
+ f"multiplexer {self.short_name}")
134
+
135
+ # the byte position of the switch key is relative to
136
+ # the multiplexer's position
137
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.switch_key.byte_position
138
+ encode_state.cursor_bit_position = self.switch_key.bit_position or 0
139
+ self.switch_key.dop.encode_into_pdu(physical_value=key_value, encode_state=encode_state)
140
+ encode_state.cursor_bit_position = 0
141
+
142
+ if mux_case.structure is not None:
143
+ # the byte position of the content is specified by the
144
+ # BYTE-POSITION attribute of the multiplexer
145
+ encode_state.cursor_byte_position = encode_state.origin_byte_position + self.byte_position
146
+ mux_case.structure.encode_into_pdu(physical_value=case_value, encode_state=encode_state)
147
+
148
+ encode_state.origin_byte_position = orig_origin
123
149
 
124
150
  @override
125
151
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
126
-
127
- # multiplexers are structures and thus the origin position
128
- # must be moved to the start of the multiplexer
129
152
  orig_origin = decode_state.origin_byte_position
130
- if self.byte_position is not None:
131
- decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
132
153
  decode_state.origin_byte_position = decode_state.cursor_byte_position
133
154
 
155
+ # Decode the switch key. Its BYTE-POSITION is relative to the
156
+ # that of the multiplexer.
157
+ if self.switch_key.byte_position is not None:
158
+ decode_state.cursor_byte_position = decode_state.origin_byte_position + self.switch_key.byte_position
159
+ decode_state.cursor_bit_position = self.switch_key.bit_position or 0
134
160
  key_value = self.switch_key.dop.decode_from_pdu(decode_state)
161
+ decode_state.cursor_bit_position = 0
135
162
 
136
163
  if not isinstance(key_value, int):
137
164
  odxraise(f"Multiplexer keys must be integers (is '{type(key_value).__name__}'"
138
165
  f" for multiplexer '{self.short_name}')")
139
166
 
140
- case_value: Optional[ParameterValue] = None
141
- mux_case = None
142
- for mux_case in self.cases or []:
167
+ # "If a matching CASE is found, the referenced STRUCTURE is
168
+ # analyzed at the BYTE-POSITION (child element of MUX)
169
+ # relatively to the byte position of the MUX."
170
+ decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
171
+
172
+ applicable_case: Optional[Union[MultiplexerCase, MultiplexerDefaultCase]] = None
173
+ for mux_case in self.cases:
143
174
  lower, upper = self._get_case_limits(mux_case)
144
175
  if lower <= key_value and key_value <= upper: # type: ignore[operator]
145
- if mux_case._structure:
146
- case_value = mux_case._structure.decode_from_pdu(decode_state)
176
+ applicable_case = mux_case
147
177
  break
148
178
 
149
- if case_value is None and self.default_case is not None:
150
- if self.default_case._structure:
151
- case_value = self.default_case._structure.decode_from_pdu(decode_state)
179
+ if applicable_case is None:
180
+ applicable_case = self.default_case
181
+
182
+ if applicable_case is None:
183
+ odxraise(
184
+ f"Cannot find an applicable case for value {key_value} in "
185
+ f"multiplexer {self.short_name}", DecodeError)
186
+ decode_state.origin_byte_position = orig_origin
187
+ return (None, None)
152
188
 
153
- if mux_case is None or case_value is None:
154
- odxraise(f"Failed to find a matching case in {self.short_name} for value {key_value!r}",
155
- DecodeError)
189
+ if applicable_case.structure is not None:
190
+ case_value = applicable_case.structure.decode_from_pdu(decode_state)
191
+ else:
192
+ case_value = {}
156
193
 
157
- mux_value = (mux_case.short_name, case_value)
194
+ result = (applicable_case.short_name, case_value)
158
195
 
159
- # go back to the original origin
160
196
  decode_state.origin_byte_position = orig_origin
161
197
 
162
- return mux_value
198
+ return result
163
199
 
164
200
  @override
165
201
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -3,13 +3,13 @@ from dataclasses import dataclass
3
3
  from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
7
6
  from .compumethods.limit import Limit
8
7
  from .element import NamedElement
9
8
  from .exceptions import odxrequire
10
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
11
10
  from .odxtypes import AtomicOdxType, DataType
12
11
  from .snrefcontext import SnRefContext
12
+ from .structure import Structure
13
13
  from .utils import dataclass_fields_asdict
14
14
 
15
15
 
@@ -23,7 +23,7 @@ class MultiplexerCase(NamedElement):
23
23
  upper_limit: Limit
24
24
 
25
25
  def __post_init__(self) -> None:
26
- self._structure: BasicStructure
26
+ self._structure: Optional[Structure]
27
27
 
28
28
  @staticmethod
29
29
  def from_et(et_element: ElementTree.Element,
@@ -62,8 +62,9 @@ class MultiplexerCase(NamedElement):
62
62
 
63
63
  def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *,
64
64
  key_physical_type: DataType) -> None:
65
+ self._structure = None
65
66
  if self.structure_ref:
66
- self._structure = odxlinks.resolve(self.structure_ref)
67
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
67
68
 
68
69
  self.lower_limit.set_value_type(key_physical_type)
69
70
  self.upper_limit.set_value_type(key_physical_type)
@@ -71,12 +72,12 @@ class MultiplexerCase(NamedElement):
71
72
  def _resolve_snrefs(self, context: SnRefContext) -> None:
72
73
  if self.structure_snref:
73
74
  ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
74
- self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure)
75
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
75
76
 
76
77
  def applies(self, value: AtomicOdxType) -> bool:
77
78
  return self.lower_limit.complies_to_lower(value) \
78
79
  and self.upper_limit.complies_to_upper(value)
79
80
 
80
81
  @property
81
- def structure(self) -> BasicStructure:
82
+ def structure(self) -> Optional[Structure]:
82
83
  return self._structure
@@ -3,11 +3,11 @@ from dataclasses import dataclass
3
3
  from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
7
6
  from .element import NamedElement
8
7
  from .exceptions import odxrequire
9
8
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
9
  from .snrefcontext import SnRefContext
10
+ from .structure import Structure
11
11
  from .utils import dataclass_fields_asdict
12
12
 
13
13
 
@@ -18,12 +18,12 @@ class MultiplexerDefaultCase(NamedElement):
18
18
  structure_snref: Optional[str]
19
19
 
20
20
  def __post_init__(self) -> None:
21
- self._structure: BasicStructure
21
+ self._structure: Optional[Structure]
22
22
 
23
23
  @staticmethod
24
24
  def from_et(et_element: ElementTree.Element,
25
25
  doc_frags: List[OdxDocFragment]) -> "MultiplexerDefaultCase":
26
- """Reads a Default Case for a Multiplexer."""
26
+ """Reads a default case for a multiplexer."""
27
27
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
28
28
 
29
29
  structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
@@ -38,14 +38,15 @@ class MultiplexerDefaultCase(NamedElement):
38
38
  return {}
39
39
 
40
40
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
41
+ self._structure = None
41
42
  if self.structure_ref is not None:
42
- self._structure = odxlinks.resolve(self.structure_ref)
43
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
43
44
 
44
45
  def _resolve_snrefs(self, context: SnRefContext) -> None:
45
46
  if self.structure_snref:
46
47
  ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
47
- self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure)
48
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
48
49
 
49
50
  @property
50
- def structure(self) -> BasicStructure:
51
+ def structure(self) -> Optional[Structure]:
51
52
  return self._structure
odxtools/odxlink.py CHANGED
@@ -26,7 +26,6 @@ class OdxDocFragment:
26
26
  return self.doc_name == other.doc_name
27
27
 
28
28
  def __hash__(self) -> int:
29
- # only the document name is relevant for the hash value
30
29
  return hash(self.doc_name) + hash(self.doc_type)
31
30
 
32
31
 
@@ -59,10 +58,12 @@ class OdxLinkId:
59
58
  if not isinstance(other, OdxLinkId):
60
59
  return False
61
60
 
62
- # we do take the document fragment into consideration here, because
63
- # document fragments are handled using separate sub-databases,
64
- # i.e. the same OdxId object can be put into all of them.
65
- return self.local_id == other.local_id
61
+ # if the local ID is different, the whole id is different
62
+ if self.local_id != other.local_id:
63
+ return False
64
+
65
+ # the document fragments must be identical for the IDs to be identical
66
+ return self.doc_fragments == other.doc_fragments
66
67
 
67
68
  def __str__(self) -> str:
68
69
  return f"OdxLinkId('{self.local_id}')"
@@ -147,19 +148,6 @@ class OdxLinkRef:
147
148
  def __str__(self) -> str:
148
149
  return f"OdxLinkRef('{self.ref_id}')"
149
150
 
150
- def __contains__(self, odx_id: OdxLinkId) -> bool:
151
- """
152
- Returns true iff a given OdxLinkId object is referenced.
153
- """
154
-
155
- # we must reference at to at least of the ID's document
156
- # fragments
157
- if not any(ref_doc in odx_id.doc_fragments for ref_doc in self.ref_docs):
158
- return False
159
-
160
- # the local ID of the reference and the object ID must match
161
- return odx_id.local_id == self.ref_id
162
-
163
151
 
164
152
  T = TypeVar("T")
165
153
 
@@ -172,7 +160,7 @@ class OdxLinkDatabase:
172
160
  """
173
161
 
174
162
  def __init__(self) -> None:
175
- self._db: Dict[OdxDocFragment, Dict[OdxLinkId, Any]] = {}
163
+ self._db: Dict[OdxDocFragment, Dict[str, Any]] = {}
176
164
 
177
165
  @overload
178
166
  def resolve(self, ref: OdxLinkRef, expected_type: None = None) -> Any:
@@ -189,7 +177,6 @@ class OdxLinkDatabase:
189
177
  If the database does not contain any object which is referred to, a
190
178
  KeyError exception is raised.
191
179
  """
192
- odx_id = OdxLinkId(ref.ref_id, ref.ref_docs)
193
180
  for ref_frag in reversed(ref.ref_docs):
194
181
  doc_frag_db = self._db.get(ref_frag)
195
182
  if doc_frag_db is None:
@@ -204,8 +191,9 @@ class OdxLinkDatabase:
204
191
  )
205
192
  continue
206
193
 
207
- obj = doc_frag_db.get(odx_id)
208
- if obj is not None:
194
+ # locate an object exhibiting with the referenced local ID
195
+ # in the ID database for the document fragment
196
+ if (obj := doc_frag_db.get(ref.ref_id)) is not None:
209
197
  if expected_type is not None:
210
198
  odxassert(isinstance(obj, expected_type))
211
199
 
@@ -234,7 +222,6 @@ class OdxLinkDatabase:
234
222
  is returned.
235
223
  """
236
224
 
237
- odx_id = OdxLinkId(ref.ref_id, ref.ref_docs)
238
225
  for ref_frag in reversed(ref.ref_docs):
239
226
  doc_frag_db = self._db.get(ref_frag)
240
227
  if doc_frag_db is None:
@@ -249,8 +236,7 @@ class OdxLinkDatabase:
249
236
  )
250
237
  continue
251
238
 
252
- obj = doc_frag_db.get(odx_id)
253
- if obj is not None:
239
+ if (obj := doc_frag_db.get(ref.ref_id)) is not None:
254
240
  if expected_type is not None:
255
241
  odxassert(isinstance(obj, expected_type))
256
242
 
@@ -272,7 +258,7 @@ class OdxLinkDatabase:
272
258
  if doc_frag not in self._db:
273
259
  self._db[doc_frag] = {}
274
260
 
275
- self._db[doc_frag][odx_id] = obj
261
+ self._db[doc_frag][odx_id.local_id] = obj
276
262
 
277
263
 
278
264
  @overload
odxtools/odxtypes.py CHANGED
@@ -235,7 +235,7 @@ class DataType(Enum):
235
235
  expected_type = self.python_type
236
236
  if isinstance(value, expected_type):
237
237
  return True
238
- elif expected_type == float and isinstance(value, (int, float)):
238
+ elif expected_type is float and isinstance(value, (int, float)):
239
239
  return True
240
240
  elif self == DataType.A_BYTEFIELD and isinstance(value, (bytearray, bytes)):
241
241
  return True
odxtools/parameterinfo.py CHANGED
@@ -108,8 +108,8 @@ def parameter_info(param_list: Iterable[Parameter], quoted_names: bool = False)
108
108
  of.write(f"multiplexer; choices:\n")
109
109
  for mux_case in dop.cases:
110
110
  of.write(f" ({repr(mux_case.short_name)}, {{\n")
111
- of.write(
112
- textwrap.indent(parameter_info(mux_case.structure.parameters, True), " "))
111
+ if (struc := mux_case.structure) is not None:
112
+ of.write(textwrap.indent(parameter_info(struc.parameters, True), " "))
113
113
  of.write(f" }})\n")
114
114
  continue
115
115
 
@@ -1,7 +1,6 @@
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
@@ -10,7 +9,7 @@ from ..createanydiagcodedtype import create_any_diag_coded_type_from_et
10
9
  from ..decodestate import DecodeState
11
10
  from ..diagcodedtype import DiagCodedType
12
11
  from ..encodestate import EncodeState
13
- from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire
12
+ from ..exceptions import DecodeMismatch, EncodeError, odxraise, odxrequire
14
13
  from ..odxlink import OdxDocFragment, OdxLinkId
15
14
  from ..odxtypes import AtomicOdxType, DataType, ParameterValue
16
15
  from ..utils import dataclass_fields_asdict
@@ -19,7 +18,8 @@ from .parameter import Parameter, ParameterType
19
18
 
20
19
  @dataclass
21
20
  class NrcConstParameter(Parameter):
22
- """A param of type NRC-CONST defines a set of values to be matched for a negative response to apply.
21
+ """A parameter of type NRC-CONST defines a set of values to be
22
+ matched for a negative response object to apply
23
23
 
24
24
  The behaviour of NRC-CONST parameters is similar to CODED-CONST
25
25
  parameters in that they allow to specify which coding objects
@@ -88,49 +88,40 @@ class NrcConstParameter(Parameter):
88
88
  @override
89
89
  def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue],
90
90
  encode_state: EncodeState) -> None:
91
- coded_value: ParameterValue
91
+ # NRC-CONST parameters are not encoding any value on its
92
+ # own. instead, it is supposed to overlap with a value
93
+ # parameter.
92
94
  if physical_value is not None:
93
- if physical_value not in self.coded_values:
94
- odxraise(
95
- f"The value of parameter '{self.short_name}' must "
96
- f" be one of {self.coded_values} (is: {physical_value!r})", EncodeError)
97
- coded_value = self.coded_values[0]
98
- else:
99
- coded_value = physical_value
100
- else:
101
- # If the user did not select a value, the value of the
102
- # this parameter is set by another parameter which
103
- # overlaps with it. We thus just move the cursor.
104
- bit_pos = encode_state.cursor_bit_position
105
- bit_len = self.diag_coded_type.get_static_bit_length()
106
-
107
- if bit_len is None:
108
- odxraise("The diag coded type of NRC-CONST parameters must "
109
- "exhibit a static size")
110
- return
111
-
112
- encode_state.cursor_byte_position += (bit_pos + bit_len + 7) // 8
113
- encode_state.cursor_bit_position = 0
95
+ odxraise("The value of NRC-CONST parameters cannot be set directly!", EncodeError)
96
+
97
+ # TODO (?): extract the parameter and check if it is one of
98
+ # the values of self.coded_values. if not, throw an
99
+ # EncodeMismatch exception! This is probably a bad idea
100
+ # because the parameter which determines the value of the
101
+ # NRC-CONST might possibly be specified after the NRC-CONST.
102
+
103
+ # move the cursor forward by the size of the parameter
104
+ bit_pos = encode_state.cursor_bit_position
105
+ bit_len = self.diag_coded_type.get_static_bit_length()
106
+
107
+ if bit_len is None:
108
+ odxraise("The diag coded type of NRC-CONST parameters must "
109
+ "exhibit a static size")
114
110
  return
115
111
 
116
- self.diag_coded_type.encode_into_pdu(cast(AtomicOdxType, coded_value), encode_state)
112
+ encode_state.cursor_byte_position += (bit_pos + bit_len + 7) // 8
113
+ encode_state.cursor_bit_position = 0
114
+
115
+ encode_state.emplace_bytes(b'', self.short_name)
117
116
 
118
117
  @override
119
118
  def _decode_positioned_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
120
- # Extract coded values
119
+ # Extract coded value
121
120
  coded_value = self.diag_coded_type.decode_from_pdu(decode_state)
122
121
 
123
122
  # Check if the coded value in the message is correct.
124
123
  if coded_value not in self.coded_values:
125
- warnings.warn(
126
- f"Coded constant parameter does not match! "
127
- f"The parameter {self.short_name} expected a coded "
128
- f"value in {str(self.coded_values)} but got {str(coded_value)} "
129
- f"at byte position {decode_state.cursor_byte_position} "
130
- f"in coded message {decode_state.coded_message.hex()}.",
131
- DecodeError,
132
- stacklevel=1,
133
- )
124
+ raise DecodeMismatch(f"NRC-CONST parameter {self.short_name} does not apply")
134
125
 
135
126
  return coded_value
136
127
 
@@ -26,7 +26,7 @@ class SystemParameter(ParameterWithDOP):
26
26
 
27
27
  kwargs = dataclass_fields_asdict(ParameterWithDOP.from_et(et_element, doc_frags))
28
28
 
29
- sysparam = odxrequire(et_element.findtext("SYSPARAM"))
29
+ sysparam = odxrequire(et_element.get("SYSPARAM"))
30
30
 
31
31
  return SystemParameter(sysparam=sysparam, **kwargs)
32
32
 
odxtools/version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '7.2.0'
16
- __version_tuple__ = version_tuple = (7, 2, 0)
15
+ __version__ = version = '7.3.0'
16
+ __version_tuple__ = version_tuple = (7, 3, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: odxtools
3
- Version: 7.2.0
3
+ Version: 7.3.0
4
4
  Summary: Utilities to work with the ODX standard for automotive diagnostics
5
5
  Author-email: Katrin Bauer <katrin.bauer@mbition.io>, Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>
6
6
  Maintainer-email: Andreas Lauser <andreas.lauser@mbition.io>, Ayoub Kaanich <kayoub5@live.com>