odxtools 9.2.0__py3-none-any.whl → 9.4.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.
Files changed (63) hide show
  1. odxtools/companyrevisioninfo.py +1 -1
  2. odxtools/comparaminstance.py +1 -6
  3. odxtools/comparamspec.py +3 -2
  4. odxtools/comparamsubset.py +3 -5
  5. odxtools/dataobjectproperty.py +12 -6
  6. odxtools/decodestate.py +120 -26
  7. odxtools/description.py +19 -2
  8. odxtools/diagcodedtype.py +9 -2
  9. odxtools/diagcomm.py +4 -4
  10. odxtools/diaglayercontainer.py +2 -2
  11. odxtools/diaglayers/diaglayerraw.py +2 -2
  12. odxtools/diaglayers/hierarchyelement.py +2 -0
  13. odxtools/diagnostictroublecode.py +4 -8
  14. odxtools/diagservice.py +70 -4
  15. odxtools/diagvariable.py +45 -7
  16. odxtools/encodestate.py +147 -51
  17. odxtools/encoding.py +56 -0
  18. odxtools/environmentdatadescription.py +77 -47
  19. odxtools/functionalclass.py +7 -2
  20. odxtools/inputparam.py +9 -3
  21. odxtools/leadinglengthinfotype.py +4 -0
  22. odxtools/minmaxlengthtype.py +57 -36
  23. odxtools/modification.py +3 -2
  24. odxtools/odxcategory.py +2 -2
  25. odxtools/odxlink.py +31 -7
  26. odxtools/odxtypes.py +1 -1
  27. odxtools/outputparam.py +8 -3
  28. odxtools/parameters/matchingrequestparameter.py +1 -0
  29. odxtools/parameters/physicalconstantparameter.py +1 -0
  30. odxtools/parameters/reservedparameter.py +1 -0
  31. odxtools/parameters/tableentryparameter.py +15 -4
  32. odxtools/parameters/tablekeyparameter.py +20 -17
  33. odxtools/paramlengthinfotype.py +5 -3
  34. odxtools/physicaltype.py +2 -1
  35. odxtools/scaleconstr.py +4 -4
  36. odxtools/standardlengthtype.py +98 -22
  37. odxtools/statetransition.py +24 -3
  38. odxtools/structure.py +10 -2
  39. odxtools/swvariable.py +3 -1
  40. odxtools/table.py +55 -3
  41. odxtools/tablerow.py +91 -8
  42. odxtools/templates/macros/printDOP.xml.jinja2 +2 -2
  43. odxtools/templates/macros/printDescription.xml.jinja2 +5 -1
  44. odxtools/templates/macros/printDiagLayer.xml.jinja2 +1 -1
  45. odxtools/templates/macros/printDiagVariable.xml.jinja2 +12 -3
  46. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +4 -0
  47. odxtools/templates/macros/printParentRef.xml.jinja2 +27 -0
  48. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  49. odxtools/templates/macros/printService.xml.jinja2 +30 -0
  50. odxtools/templates/macros/printStructure.xml.jinja2 +2 -1
  51. odxtools/templates/macros/printTable.xml.jinja2 +43 -0
  52. odxtools/templates/macros/printUnitSpec.xml.jinja2 +4 -0
  53. odxtools/uds.py +2 -2
  54. odxtools/unit.py +8 -12
  55. odxtools/unitspec.py +5 -2
  56. odxtools/utils.py +44 -1
  57. odxtools/version.py +2 -2
  58. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/METADATA +2 -2
  59. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/RECORD +63 -62
  60. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/WHEEL +1 -1
  61. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/LICENSE +0 -0
  62. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/entry_points.txt +0 -0
  63. {odxtools-9.2.0.dist-info → odxtools-9.4.0.dist-info}/top_level.txt +0 -0
@@ -72,6 +72,7 @@ class LeadingLengthInfoType(DiagCodedType):
72
72
  used_mask=None,
73
73
  bit_length=self.bit_length,
74
74
  base_data_type=DataType.A_UINT32,
75
+ base_type_encoding=None,
75
76
  is_highlow_byte_order=self.is_highlow_byte_order,
76
77
  )
77
78
 
@@ -80,6 +81,7 @@ class LeadingLengthInfoType(DiagCodedType):
80
81
  used_mask=None,
81
82
  bit_length=8 * byte_length,
82
83
  base_data_type=self.base_data_type,
84
+ base_type_encoding=None,
83
85
  is_highlow_byte_order=self.is_highlow_byte_order,
84
86
  )
85
87
 
@@ -90,6 +92,7 @@ class LeadingLengthInfoType(DiagCodedType):
90
92
  byte_length = decode_state.extract_atomic_value(
91
93
  bit_length=self.bit_length,
92
94
  base_data_type=DataType.A_UINT32, # length is an integer
95
+ base_type_encoding=None,
93
96
  is_highlow_byte_order=self.is_highlow_byte_order,
94
97
  )
95
98
 
@@ -102,6 +105,7 @@ class LeadingLengthInfoType(DiagCodedType):
102
105
  value = decode_state.extract_atomic_value(
103
106
  bit_length=8 * byte_length,
104
107
  base_data_type=self.base_data_type,
108
+ base_type_encoding=None,
105
109
  is_highlow_byte_order=self.is_highlow_byte_order,
106
110
  )
107
111
 
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from enum import Enum
4
+ from typing import List, Optional, cast
4
5
  from xml.etree import ElementTree
5
6
 
6
7
  from typing_extensions import override
@@ -8,17 +9,24 @@ from typing_extensions import override
8
9
  from .decodestate import DecodeState
9
10
  from .diagcodedtype import DctType, DiagCodedType
10
11
  from .encodestate import EncodeState
12
+ from .encoding import get_string_encoding
11
13
  from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
12
14
  from .odxlink import OdxDocFragment
13
15
  from .odxtypes import AtomicOdxType, DataType
14
16
  from .utils import dataclass_fields_asdict
15
17
 
16
18
 
19
+ class Termination(Enum):
20
+ END_OF_PDU = "END-OF-PDU"
21
+ ZERO = "ZERO"
22
+ HEX_FF = "HEX-FF"
23
+
24
+
17
25
  @dataclass
18
26
  class MinMaxLengthType(DiagCodedType):
19
27
  min_length: int
20
28
  max_length: Optional[int]
21
- termination: str
29
+ termination: Termination
22
30
 
23
31
  @property
24
32
  def dct_type(self) -> DctType:
@@ -34,7 +42,13 @@ class MinMaxLengthType(DiagCodedType):
34
42
  max_length = None
35
43
  if et_element.find("MAX-LENGTH") is not None:
36
44
  max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
37
- termination = odxrequire(et_element.get("TERMINATION"))
45
+
46
+ termination_str = odxrequire(et_element.get("TERMINATION"))
47
+ try:
48
+ termination = Termination(termination_str)
49
+ except ValueError:
50
+ termination = cast(Termination, None)
51
+ odxraise(f"Encountered unknown termination type '{termination_str}'")
38
52
 
39
53
  return MinMaxLengthType(
40
54
  min_length=min_length, max_length=max_length, termination=termination, **kwargs)
@@ -48,23 +62,18 @@ class MinMaxLengthType(DiagCodedType):
48
62
  DataType.A_UNICODE2STRING,
49
63
  DataType.A_UTF8STRING,
50
64
  ], f"A min-max length type cannot have the base data type {self.base_data_type}.")
51
- odxassert(self.termination in [
52
- "ZERO",
53
- "HEX-FF",
54
- "END-OF-PDU",
55
- ], f"A min-max length type cannot have the termination {self.termination}")
56
65
 
57
66
  def __termination_sequence(self) -> bytes:
58
67
  """Returns the termination byte sequence if it isn't defined."""
59
68
  # The termination sequence is actually not specified by ASAM
60
69
  # for A_BYTEFIELD but I assume it is only one byte.
61
70
  termination_sequence = b''
62
- if self.termination == "ZERO":
71
+ if self.termination == Termination.ZERO:
63
72
  if self.base_data_type not in [DataType.A_UNICODE2STRING]:
64
73
  termination_sequence = bytes([0x0])
65
74
  else:
66
75
  termination_sequence = bytes([0x0, 0x0])
67
- elif self.termination == "HEX-FF":
76
+ elif self.termination == Termination.HEX_FF:
68
77
  if self.base_data_type not in [DataType.A_UNICODE2STRING]:
69
78
  termination_sequence = bytes([0xFF])
70
79
  else:
@@ -78,29 +87,52 @@ class MinMaxLengthType(DiagCodedType):
78
87
  odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
79
88
  EncodeError)
80
89
 
81
- if self.max_length is not None:
82
- data_length = min(len(internal_value), self.max_length)
90
+ raw_value = b''
91
+ if isinstance(internal_value, str):
92
+ str_encoding = get_string_encoding(self.base_data_type, self.base_type_encoding,
93
+ self.is_highlow_byte_order)
94
+
95
+ if str_encoding is None:
96
+ odxraise(f"Internal string value specified for object which is "
97
+ f"'{self.base_data_type.value}' not a string")
98
+ raw_value = b''
99
+ else:
100
+ raw_value = internal_value.encode(str_encoding)
83
101
  else:
84
- data_length = len(internal_value)
102
+ raw_value = bytes(internal_value)
103
+
104
+ data_length = len(raw_value)
105
+
106
+ if data_length < self.min_length:
107
+ odxraise(
108
+ f"Encoded value for MinMaxLengthType "
109
+ f"must be at least {self.min_length} bytes long. "
110
+ f"(Is: {data_length} bytes.)", EncodeError)
111
+ data_length = self.min_length
112
+ elif self.max_length is not None and data_length > self.max_length:
113
+ odxraise(
114
+ f"Encoded value for MinMaxLengthType "
115
+ f"must not be longer than {self.max_length} bytes. "
116
+ f"(Is: {data_length} bytes.)", EncodeError)
117
+ data_length = self.max_length
85
118
 
86
- orig_cursor = encode_state.cursor_byte_position
87
119
  encode_state.emplace_atomic_value(
88
- internal_value=internal_value,
120
+ internal_value=raw_value,
89
121
  used_mask=None,
90
122
  bit_length=8 * data_length,
91
- base_data_type=self.base_data_type,
92
- is_highlow_byte_order=self.is_highlow_byte_order,
123
+ base_data_type=DataType.A_BYTEFIELD,
124
+ base_type_encoding=None,
125
+ is_highlow_byte_order=True,
93
126
  )
94
- value_len = encode_state.cursor_byte_position - orig_cursor
95
127
 
96
128
  # TODO: ensure that the termination delimiter is not
97
129
  # encountered within the encoded value.
98
130
 
99
131
  odxassert(
100
- self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
132
+ self.termination != Termination.END_OF_PDU or encode_state.is_end_of_pdu,
101
133
  "Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
102
134
  "which is not located at the end of the PDU")
103
- if encode_state.is_end_of_pdu or value_len == self.max_length:
135
+ if encode_state.is_end_of_pdu or data_length == self.max_length:
104
136
  # All termination types may be ended by the end of the PDU
105
137
  # or once reaching the maximum length. In this case, we
106
138
  # must not add the termination sequence
@@ -110,24 +142,11 @@ class MinMaxLengthType(DiagCodedType):
110
142
 
111
143
  # ensure that we don't try to encode an odd-length
112
144
  # value when using a two-byte terminator
113
- odxassert(value_len % len(termination_sequence) == 0)
145
+ odxassert(data_length % len(termination_sequence) == 0)
114
146
 
115
- value_len += len(termination_sequence)
147
+ data_length += len(termination_sequence)
116
148
  encode_state.emplace_bytes(termination_sequence)
117
149
 
118
- if value_len < self.min_length:
119
- odxraise(
120
- f"Encoded value for MinMaxLengthType "
121
- f"must be at least {self.min_length} bytes long. "
122
- f"(Is: {value_len} bytes.)", EncodeError)
123
- return
124
- elif self.max_length is not None and value_len > self.max_length:
125
- odxraise(
126
- f"Encoded value for MinMaxLengthType "
127
- f"must not be longer than {self.max_length} bytes. "
128
- f"(Is: {value_len} bytes.)", EncodeError)
129
- return
130
-
131
150
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
132
151
  odxassert(decode_state.cursor_bit_position == 0,
133
152
  "No bit position can be specified for MIN-MAX-LENGTH-TYPE values.")
@@ -142,7 +161,7 @@ class MinMaxLengthType(DiagCodedType):
142
161
  if self.max_length is not None:
143
162
  max_terminator_pos = min(max_terminator_pos, orig_cursor_pos + self.max_length)
144
163
 
145
- if self.termination != "END-OF-PDU":
164
+ if self.termination != Termination.END_OF_PDU:
146
165
  # The parameter either ends after the maximum length, at
147
166
  # the end of the PDU or if a termination sequence is
148
167
  # found.
@@ -180,6 +199,7 @@ class MinMaxLengthType(DiagCodedType):
180
199
  value = decode_state.extract_atomic_value(
181
200
  bit_length=8 * byte_length,
182
201
  base_data_type=self.base_data_type,
202
+ base_type_encoding=self.base_type_encoding,
183
203
  is_highlow_byte_order=self.is_highlow_byte_order,
184
204
  )
185
205
 
@@ -198,6 +218,7 @@ class MinMaxLengthType(DiagCodedType):
198
218
  value = decode_state.extract_atomic_value(
199
219
  bit_length=8 * byte_length,
200
220
  base_data_type=self.base_data_type,
221
+ base_type_encoding=self.base_type_encoding,
201
222
  is_highlow_byte_order=self.is_highlow_byte_order,
202
223
  )
203
224
 
odxtools/modification.py CHANGED
@@ -3,18 +3,19 @@ from dataclasses import dataclass
3
3
  from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from .exceptions import odxrequire
6
7
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
7
8
  from .snrefcontext import SnRefContext
8
9
 
9
10
 
10
11
  @dataclass
11
12
  class Modification:
12
- change: Optional[str]
13
+ change: str
13
14
  reason: Optional[str]
14
15
 
15
16
  @staticmethod
16
17
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Modification":
17
- change = et_element.findtext("CHANGE")
18
+ change = odxrequire(et_element.findtext("CHANGE"))
18
19
  reason = et_element.findtext("REASON")
19
20
 
20
21
  return Modification(change=change, reason=reason)
odxtools/odxcategory.py CHANGED
@@ -8,7 +8,7 @@ from .companydata import CompanyData
8
8
  from .element import IdentifiableElement
9
9
  from .exceptions import odxrequire
10
10
  from .nameditemlist import NamedItemList
11
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
11
+ from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
12
  from .snrefcontext import SnRefContext
13
13
  from .specialdatagroup import SpecialDataGroup
14
14
  from .utils import dataclass_fields_asdict
@@ -32,7 +32,7 @@ class OdxCategory(IdentifiableElement):
32
32
 
33
33
  @staticmethod
34
34
  def category_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
35
- doc_type: str) -> "OdxCategory":
35
+ doc_type: DocType) -> "OdxCategory":
36
36
 
37
37
  short_name = odxrequire(et_element.findtext("SHORT-NAME"))
38
38
  # create the current ODX "document fragment" (description of the
odxtools/odxlink.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
+ from enum import Enum
4
5
  from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, overload
5
6
  from xml.etree import ElementTree
6
7
 
@@ -8,10 +9,22 @@ from .exceptions import OdxWarning, odxassert, odxraise, odxrequire
8
9
  from .nameditemlist import OdxNamed, TNamed
9
10
 
10
11
 
12
+ class DocType(Enum):
13
+ FLASH = "FLASH"
14
+ CONTAINER = "CONTAINER"
15
+ LAYER = "LAYER"
16
+ MULTIPLE_ECU_JOB_SPEC = "MULTIPLE-ECU-JOB-SPEC"
17
+ COMPARAM_SPEC = "COMPARAM-SPEC"
18
+ VEHICLE_INFO_SPEC = "VEHICLE-INFO-SPEC"
19
+ COMPARAM_SUBSET = "COMPARAM-SUBSET"
20
+ ECU_CONFIG = "ECU-CONFIG"
21
+ FUNCTION_DICTIONARY_SPEC = "FUNCTION-DICTIONARY-SPEC"
22
+
23
+
11
24
  @dataclass(frozen=True)
12
25
  class OdxDocFragment:
13
26
  doc_name: str
14
- doc_type: str
27
+ doc_type: DocType
15
28
 
16
29
 
17
30
  @dataclass(frozen=True)
@@ -83,6 +96,10 @@ class OdxLinkRef:
83
96
  #: The document fragments to which the `ref_id` refers to (in reverse order)
84
97
  ref_docs: List[OdxDocFragment]
85
98
 
99
+ # TODO: this is difficult because OdxLinkRef is derived from and
100
+ # we do not want having to specify it mandatorily
101
+ #revision: Optional[str] = None
102
+
86
103
  @overload
87
104
  @staticmethod
88
105
  def from_et(et: None, source_doc_frags: List[OdxDocFragment]) -> None:
@@ -109,18 +126,25 @@ class OdxLinkRef:
109
126
  odxraise(f"Tag {et.tag} is not a ODXLINK reference")
110
127
  return None
111
128
 
112
- doc_ref = et.attrib.get("DOCREF")
113
- doc_type = et.attrib.get("DOCTYPE")
129
+ docref = et.attrib.get("DOCREF")
130
+ doctype_attr = et.attrib.get("DOCTYPE")
114
131
 
115
- odxassert((doc_ref is not None and doc_type is not None) or
116
- (doc_ref is None and doc_type is None),
132
+ odxassert((docref is not None and doctype_attr is not None) or
133
+ (docref is None and doctype_attr is None),
117
134
  "DOCREF and DOCTYPE must both either be specified or omitted")
118
135
 
136
+ doctype = None
137
+ if doctype_attr is not None:
138
+ try:
139
+ doctype = DocType(doctype_attr)
140
+ except ValueError:
141
+ odxraise(f"Encountered unknown document type '{doctype_attr}'")
142
+
119
143
  # if the target document fragment is specified by the
120
144
  # reference, use it, else use the document fragment containing
121
145
  # the reference.
122
- if doc_ref is not None:
123
- doc_frags = [OdxDocFragment(doc_ref, odxrequire(doc_type))]
146
+ if docref is not None:
147
+ doc_frags = [OdxDocFragment(docref, odxrequire(doctype))]
124
148
  else:
125
149
  doc_frags = source_doc_frags
126
150
 
odxtools/odxtypes.py CHANGED
@@ -157,7 +157,7 @@ def compare_odx_values(a: AtomicOdxType, b: AtomicOdxType) -> int:
157
157
 
158
158
  # format specifiers for the data type using the bitstruct module
159
159
  _BITSTRUCT_FORMAT_LETTER_MAP__ = {
160
- "A_INT32": "s",
160
+ "A_INT32": "u", # the sign must be handled separately
161
161
  "A_UINT32": "u",
162
162
  "A_FLOAT32": "f",
163
163
  "A_FLOAT64": "f",
odxtools/outputparam.py CHANGED
@@ -3,6 +3,8 @@ from dataclasses import dataclass
3
3
  from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from deprecation import deprecated
7
+
6
8
  from .dopbase import DopBase
7
9
  from .element import IdentifiableElement
8
10
  from .exceptions import odxrequire
@@ -29,12 +31,15 @@ class OutputParam(IdentifiableElement):
29
31
  return {}
30
32
 
31
33
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
32
- self._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
34
+ self._dop = odxlinks.resolve(self.dop_base_ref, DopBase)
33
35
 
34
36
  def _resolve_snrefs(self, context: SnRefContext) -> None:
35
37
  pass
36
38
 
37
39
  @property
40
+ def dop(self) -> DopBase:
41
+ return self._dop
42
+
43
+ @deprecated(details="use .dop") # type: ignore[misc]
38
44
  def dop_base(self) -> DopBase:
39
- """The data object property describing this parameter."""
40
- return self._dop_base
45
+ return self._dop
@@ -79,4 +79,5 @@ class MatchingRequestParameter(Parameter):
79
79
  return decode_state.extract_atomic_value(
80
80
  bit_length=self.byte_length * 8,
81
81
  base_data_type=DataType.A_UINT32,
82
+ base_type_encoding=None,
82
83
  is_highlow_byte_order=False)
@@ -54,6 +54,7 @@ class PhysicalConstantParameter(ParameterWithDOP):
54
54
  dop = odxrequire(self.dop)
55
55
  if not isinstance(dop, DataObjectProperty):
56
56
  odxraise("The type of PHYS-CONST parameters must be a simple DOP")
57
+ return
57
58
  base_data_type = dop.physical_type.base_data_type
58
59
  self._physical_constant_value = base_data_type.from_string(self.physical_constant_value_raw)
59
60
 
@@ -61,4 +61,5 @@ class ReservedParameter(Parameter):
61
61
  return decode_state.extract_atomic_value(
62
62
  bit_length=self.bit_length,
63
63
  base_data_type=DataType.A_UINT32,
64
+ base_type_encoding=None,
64
65
  is_highlow_byte_order=False)
@@ -1,13 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, List, Optional
3
+ from enum import Enum
4
+ from typing import TYPE_CHECKING, List, Optional, cast
4
5
  from xml.etree import ElementTree
5
6
 
6
7
  from typing_extensions import override
7
8
 
8
9
  from ..decodestate import DecodeState
9
10
  from ..encodestate import EncodeState
10
- from ..exceptions import odxrequire
11
+ from ..exceptions import odxraise, odxrequire
11
12
  from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
12
13
  from ..odxtypes import ParameterValue
13
14
  from ..utils import dataclass_fields_asdict
@@ -17,9 +18,14 @@ if TYPE_CHECKING:
17
18
  from ..tablerow import TableRow
18
19
 
19
20
 
21
+ class RowFragment(Enum):
22
+ KEY = "KEY"
23
+ STRUCT = "STRUCT"
24
+
25
+
20
26
  @dataclass
21
27
  class TableEntryParameter(Parameter):
22
- target: str
28
+ target: RowFragment
23
29
  table_row_ref: OdxLinkRef
24
30
 
25
31
  @staticmethod
@@ -29,7 +35,12 @@ class TableEntryParameter(Parameter):
29
35
 
30
36
  kwargs = dataclass_fields_asdict(Parameter.from_et(et_element, doc_frags))
31
37
 
32
- target = odxrequire(et_element.findtext("TARGET"))
38
+ target_str = odxrequire(et_element.findtext("TARGET"))
39
+ try:
40
+ target = RowFragment(target_str)
41
+ except ValueError:
42
+ odxraise(f"Encountered unknown target '{target_str}'")
43
+ target = cast(RowFragment, None)
33
44
  table_row_ref = odxrequire(OdxLinkRef.from_et(et_element.find("TABLE-ROW-REF"), doc_frags))
34
45
 
35
46
  return TableEntryParameter(target=target, table_row_ref=table_row_ref, **kwargs)
@@ -23,10 +23,16 @@ if TYPE_CHECKING:
23
23
  class TableKeyParameter(Parameter):
24
24
 
25
25
  odx_id: OdxLinkId
26
+
27
+ # the spec mandates that exactly one of the two attributes must
28
+ # be non-None
26
29
  table_ref: Optional[OdxLinkRef]
27
30
  table_snref: Optional[str]
28
- table_row_snref: Optional[str]
31
+
32
+ # the spec mandates that exactly one of the two attributes must
33
+ # be non-None
29
34
  table_row_ref: Optional[OdxLinkRef]
35
+ table_row_snref: Optional[str]
30
36
 
31
37
  @staticmethod
32
38
  @override
@@ -58,9 +64,6 @@ class TableKeyParameter(Parameter):
58
64
  def __post_init__(self) -> None:
59
65
  self._table: Table
60
66
  self._table_row: Optional[TableRow] = None
61
- if self.table_ref is None and self.table_snref is None and \
62
- self.table_row_ref is None and self.table_row_snref is None:
63
- odxraise("Either a table or a table row must be defined.")
64
67
 
65
68
  @property
66
69
  @override
@@ -86,17 +89,20 @@ class TableKeyParameter(Parameter):
86
89
  else:
87
90
  self._table = odxlinks.resolve(self.table_ref)
88
91
 
89
- if self.table_row_ref is not None:
92
+ elif self.table_row_ref is not None:
90
93
  if TYPE_CHECKING:
91
94
  self._table_row = odxlinks.resolve(self.table_row_ref, TableRow)
92
95
  else:
93
96
  self._table_row = odxlinks.resolve(self.table_row_ref)
94
97
 
95
- if self.table_ref is None and self.table_snref is None:
96
- if TYPE_CHECKING:
97
- self._table = odxlinks.resolve(self._table_row.table_ref, Table)
98
- else:
99
- self._table = odxlinks.resolve(self._table_row.table_ref)
98
+ # be aware that we cannot simply use
99
+ # `self._table_row.table` here because the table object
100
+ # might not have resolved its references yet because the
101
+ # order of reference resolution is undefined
102
+ if TYPE_CHECKING:
103
+ self._table = odxlinks.resolve(self._table_row.table_ref, Table)
104
+ else:
105
+ self._table = odxlinks.resolve(self._table_row.table_ref)
100
106
 
101
107
  @override
102
108
  def _resolve_snrefs(self, context: SnRefContext) -> None:
@@ -108,16 +114,13 @@ class TableKeyParameter(Parameter):
108
114
  self._table = resolve_snref(self.table_snref, tables, Table)
109
115
  else:
110
116
  self._table = resolve_snref(self.table_snref, tables)
117
+
111
118
  if self.table_row_snref is not None:
112
- # make sure that we know the table to which the table row
113
- # SNREF is relative to.
114
- table = odxrequire(
115
- self._table, "If a table row is referenced via short name, a table must "
116
- "be referenced as well")
117
119
  if TYPE_CHECKING:
118
- self._table_row = resolve_snref(self.table_row_snref, table.table_rows, TableRow)
120
+ self._table_row = resolve_snref(self.table_row_snref, self._table.table_rows,
121
+ TableRow)
119
122
  else:
120
- self._table_row = resolve_snref(self.table_row_snref, table.table_rows)
123
+ self._table_row = resolve_snref(self.table_row_snref, self._table.table_rows)
121
124
 
122
125
  @property
123
126
  def table(self) -> "Table":
@@ -96,6 +96,7 @@ class ParamLengthInfoType(DiagCodedType):
96
96
  used_mask=None,
97
97
  bit_length=bit_length,
98
98
  base_data_type=self.base_data_type,
99
+ base_type_encoding=self.base_type_encoding,
99
100
  is_highlow_byte_order=self.is_highlow_byte_order,
100
101
  )
101
102
 
@@ -114,7 +115,8 @@ class ParamLengthInfoType(DiagCodedType):
114
115
 
115
116
  # Extract the internal value and return.
116
117
  return decode_state.extract_atomic_value(
117
- bit_length,
118
- self.base_data_type,
119
- self.is_highlow_byte_order,
118
+ bit_length=bit_length,
119
+ base_data_type=self.base_data_type,
120
+ base_type_encoding=self.base_type_encoding,
121
+ is_highlow_byte_order=self.is_highlow_byte_order,
120
122
  )
odxtools/physicaltype.py CHANGED
@@ -74,4 +74,5 @@ class PhysicalType:
74
74
  precision_str = et_element.findtext("PRECISION")
75
75
  precision = int(precision_str) if precision_str is not None else None
76
76
 
77
- return PhysicalType(base_data_type, display_radix=display_radix, precision=precision)
77
+ return PhysicalType(
78
+ base_data_type=base_data_type, display_radix=display_radix, precision=precision)
odxtools/scaleconstr.py CHANGED
@@ -25,8 +25,8 @@ class ScaleConstr:
25
25
 
26
26
  short_label: Optional[str]
27
27
  description: Optional[Description]
28
- lower_limit: Optional[Limit]
29
- upper_limit: Optional[Limit]
28
+ lower_limit: Limit
29
+ upper_limit: Limit
30
30
  validity: ValidType
31
31
  value_type: DataType
32
32
 
@@ -37,9 +37,9 @@ class ScaleConstr:
37
37
  description = Description.from_et(et_element.find("DESC"), doc_frags)
38
38
 
39
39
  lower_limit = Limit.limit_from_et(
40
- et_element.find("LOWER-LIMIT"), doc_frags, value_type=value_type)
40
+ odxrequire(et_element.find("LOWER-LIMIT")), doc_frags, value_type=value_type)
41
41
  upper_limit = Limit.limit_from_et(
42
- et_element.find("UPPER-LIMIT"), doc_frags, value_type=value_type)
42
+ odxrequire(et_element.find("UPPER-LIMIT")), doc_frags, value_type=value_type)
43
43
 
44
44
  validity_str = odxrequire(et_element.get("VALIDITY"))
45
45
  try: