odxtools 9.5.0__py3-none-any.whl → 9.6.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 (102) hide show
  1. odxtools/additionalaudience.py +2 -2
  2. odxtools/admindata.py +3 -0
  3. odxtools/audience.py +9 -13
  4. odxtools/basecomparam.py +1 -2
  5. odxtools/basevariantpattern.py +5 -5
  6. odxtools/basicstructure.py +34 -35
  7. odxtools/commrelation.py +2 -1
  8. odxtools/companydata.py +1 -2
  9. odxtools/companyspecificinfo.py +3 -0
  10. odxtools/comparam.py +16 -8
  11. odxtools/comparaminstance.py +12 -12
  12. odxtools/comparamspec.py +4 -3
  13. odxtools/comparamsubset.py +26 -24
  14. odxtools/compumethods/compuconst.py +4 -4
  15. odxtools/compumethods/limit.py +9 -9
  16. odxtools/compumethods/linearsegment.py +8 -8
  17. odxtools/dataobjectproperty.py +16 -18
  18. odxtools/description.py +4 -2
  19. odxtools/determinenumberofitems.py +4 -4
  20. odxtools/diagcodedtype.py +20 -20
  21. odxtools/diagcomm.py +61 -41
  22. odxtools/diagdatadictionaryspec.py +51 -55
  23. odxtools/diaglayercontainer.py +25 -25
  24. odxtools/diaglayers/diaglayerraw.py +26 -27
  25. odxtools/diagnostictroublecode.py +13 -10
  26. odxtools/diagservice.py +48 -50
  27. odxtools/diagvariable.py +10 -8
  28. odxtools/docrevision.py +5 -5
  29. odxtools/dtcdop.py +17 -17
  30. odxtools/dynamicendmarkerfield.py +8 -8
  31. odxtools/dynamiclengthfield.py +2 -0
  32. odxtools/dyndefinedspec.py +21 -8
  33. odxtools/encodestate.py +1 -2
  34. odxtools/endofpdufield.py +7 -9
  35. odxtools/environmentdatadescription.py +9 -20
  36. odxtools/field.py +21 -21
  37. odxtools/inputparam.py +15 -14
  38. odxtools/leadinglengthinfotype.py +4 -4
  39. odxtools/matchingparameter.py +2 -3
  40. odxtools/minmaxlengthtype.py +7 -7
  41. odxtools/multiplexer.py +38 -39
  42. odxtools/multiplexercase.py +3 -6
  43. odxtools/multiplexerdefaultcase.py +3 -6
  44. odxtools/multiplexerswitchkey.py +4 -4
  45. odxtools/negoutputparam.py +6 -9
  46. odxtools/odxlink.py +21 -5
  47. odxtools/odxtypes.py +4 -4
  48. odxtools/outputparam.py +9 -8
  49. odxtools/parameterinfo.py +1 -1
  50. odxtools/parameters/codedconstparameter.py +28 -27
  51. odxtools/parameters/dynamicparameter.py +9 -9
  52. odxtools/parameters/lengthkeyparameter.py +18 -18
  53. odxtools/parameters/matchingrequestparameter.py +15 -15
  54. odxtools/parameters/nrcconstparameter.py +32 -24
  55. odxtools/parameters/parameter.py +35 -37
  56. odxtools/parameters/parameterwithdop.py +6 -6
  57. odxtools/parameters/physicalconstantparameter.py +19 -20
  58. odxtools/parameters/reservedparameter.py +10 -11
  59. odxtools/parameters/systemparameter.py +10 -11
  60. odxtools/parameters/tableentryparameter.py +19 -20
  61. odxtools/parameters/tablekeyparameter.py +0 -2
  62. odxtools/parameters/tablestructparameter.py +27 -21
  63. odxtools/parameters/valueparameter.py +20 -20
  64. odxtools/parentref.py +6 -7
  65. odxtools/physicaldimension.py +11 -11
  66. odxtools/physicaltype.py +9 -14
  67. odxtools/preconditionstateref.py +85 -0
  68. odxtools/progcode.py +1 -2
  69. odxtools/protstack.py +4 -4
  70. odxtools/relateddoc.py +3 -4
  71. odxtools/scaleconstr.py +0 -1
  72. odxtools/singleecujob.py +8 -4
  73. odxtools/specialdata.py +10 -9
  74. odxtools/specialdatagroup.py +1 -0
  75. odxtools/standardlengthtype.py +10 -10
  76. odxtools/statechart.py +10 -6
  77. odxtools/statemachine.py +186 -0
  78. odxtools/statetransitionref.py +231 -0
  79. odxtools/structure.py +4 -4
  80. odxtools/subcomponent.py +72 -8
  81. odxtools/table.py +23 -13
  82. odxtools/tablerow.py +86 -69
  83. odxtools/teammember.py +4 -4
  84. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  85. odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
  86. odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
  87. odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
  88. odxtools/templates/macros/printParam.xml.jinja2 +5 -5
  89. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
  90. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
  91. odxtools/templates/macros/printTable.xml.jinja2 +13 -9
  92. odxtools/text.py +35 -0
  93. odxtools/unit.py +1 -3
  94. odxtools/unitgroup.py +6 -8
  95. odxtools/utils.py +0 -4
  96. odxtools/version.py +2 -2
  97. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
  98. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/RECORD +102 -96
  99. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
  100. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
  101. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
  102. {odxtools-9.5.0.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Union
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .diagcomm import DiagClassType, DiagComm
7
- from .exceptions import odxraise, odxrequire
7
+ from .exceptions import odxassert, odxraise, odxrequire
8
8
  from .nameditemlist import NamedItemList
9
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
10
  from .snrefcontext import SnRefContext
@@ -50,17 +50,20 @@ class DynIdDefModeInfo:
50
50
 
51
51
  clear_dyn_def_message_ref = OdxLinkRef.from_et(
52
52
  et_element.find("CLEAR-DYN-DEF-MESSAGE-REF"), doc_frags)
53
+ clear_dyn_def_message_snref = None
53
54
  if (snref_elem := et_element.find("CLEAR-DYN-DEF-MESSAGE-SNREF")) is not None:
54
- clear_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
55
+ clear_dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
55
56
 
56
57
  read_dyn_def_message_ref = OdxLinkRef.from_et(
57
58
  et_element.find("READ-DYN-DEF-MESSAGE-REF"), doc_frags)
59
+ read_dyn_def_message_snref = None
58
60
  if (snref_elem := et_element.find("READ-DYN-DEF-MESSAGE-SNREF")) is not None:
59
- read_dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
61
+ read_dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
60
62
 
61
63
  dyn_def_message_ref = OdxLinkRef.from_et(et_element.find("DYN-DEF-MESSAGE-REF"), doc_frags)
64
+ dyn_def_message_snref = None
62
65
  if (snref_elem := et_element.find("DYN-DEF-MESSAGE-SNREF")) is not None:
63
- dyn_def_message_snref = snref_elem.attrib["SHORT-NAME"]
66
+ dyn_def_message_snref = odxrequire(snref_elem.attrib.get("SHORT-NAME"))
64
67
 
65
68
  supported_dyn_ids = [
66
69
  bytes.fromhex(odxrequire(x.text))
@@ -89,14 +92,23 @@ class DynIdDefModeInfo:
89
92
  selection_table_refs=selection_table_refs,
90
93
  )
91
94
 
95
+ def __post_init__(self) -> None:
96
+ odxassert(
97
+ self.clear_dyn_def_message_ref is not None or
98
+ self.clear_dyn_def_message_snref is not None,
99
+ "A CLEAR-DYN-DEF-MESSAGE must be specified")
100
+ odxassert(
101
+ self.read_dyn_def_message_ref is not None or
102
+ self.read_dyn_def_message_snref is not None, "A READ-DYN-DEF-MESSAGE must be specified")
103
+ odxassert(self.dyn_def_message_ref is not None or self.dyn_def_message_snref is not None,
104
+ "A DYN-DEF-MESSAGE must be specified")
105
+
92
106
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
93
107
  result: Dict[OdxLinkId, Any] = {}
94
108
 
95
109
  return result
96
110
 
97
111
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
98
- self._selection_tables = NamedItemList[Table]()
99
-
100
112
  if self.clear_dyn_def_message_ref is not None:
101
113
  self._clear_dyn_def_message = odxlinks.resolve(self.clear_dyn_def_message_ref, DiagComm)
102
114
 
@@ -106,7 +118,8 @@ class DynIdDefModeInfo:
106
118
  if self.dyn_def_message_ref is not None:
107
119
  self._dyn_def_message = odxlinks.resolve(self.dyn_def_message_ref, DiagComm)
108
120
 
109
- # resolve the selection tables referenced using ODXLINK
121
+ # resolve the selection tables that are referenced via ODXLINK
122
+ self._selection_tables = NamedItemList[Table]()
110
123
  for x in self.selection_table_refs:
111
124
  if isinstance(x, OdxLinkRef):
112
125
  self._selection_tables.append(odxlinks.resolve(x, Table))
@@ -141,9 +154,9 @@ class DynIdDefModeInfo:
141
154
  f"DYN-DEF-MESSAGE)")
142
155
 
143
156
  # resolve the remaining selection tables that are referenced via SNREF
157
+ ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
144
158
  for i, x in enumerate(self.selection_table_refs):
145
159
  if isinstance(x, str):
146
- ddd_spec = odxrequire(diag_layer.diag_data_dictionary_spec)
147
160
  self._selection_tables.insert(i, resolve_snref(x, ddd_spec.tables, Table))
148
161
 
149
162
 
odxtools/encodestate.py CHANGED
@@ -5,8 +5,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
5
5
 
6
6
  from .encoding import Encoding, get_string_encoding
7
7
  from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
8
- from .odxtypes import AtomicOdxType, DataType, ParameterValue
9
- from .utils import BytesTypes
8
+ from .odxtypes import AtomicOdxType, BytesTypes, DataType, ParameterValue
10
9
 
11
10
  try:
12
11
  import bitstruct.c as bitstruct
odxtools/endofpdufield.py CHANGED
@@ -17,30 +17,28 @@ from .utils import dataclass_fields_asdict
17
17
  @dataclass
18
18
  class EndOfPduField(Field):
19
19
  """End of PDU fields are structures that are repeated until the end of the PDU"""
20
- min_number_of_items: Optional[int]
21
20
  max_number_of_items: Optional[int]
21
+ min_number_of_items: Optional[int]
22
22
 
23
23
  @staticmethod
24
24
  def from_et(et_element: ElementTree.Element,
25
25
  doc_frags: List[OdxDocFragment]) -> "EndOfPduField":
26
26
  kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
27
27
 
28
- if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
29
- min_number_of_items = int(min_n_str)
30
- else:
31
- min_number_of_items = None
32
28
  if (max_n_str := et_element.findtext("MAX-NUMBER-OF-ITEMS")) is not None:
33
29
  max_number_of_items = int(max_n_str)
34
30
  else:
35
31
  max_number_of_items = None
32
+ if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
33
+ min_number_of_items = int(min_n_str)
34
+ else:
35
+ min_number_of_items = None
36
36
 
37
- eopf = EndOfPduField(
38
- min_number_of_items=min_number_of_items,
37
+ return EndOfPduField(
39
38
  max_number_of_items=max_number_of_items,
39
+ min_number_of_items=min_number_of_items,
40
40
  **kwargs)
41
41
 
42
- return eopf
43
-
44
42
  @override
45
43
  def encode_into_pdu(self, physical_value: Optional[ParameterValue],
46
44
  encode_state: EncodeState) -> None:
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional, cast
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -44,18 +44,6 @@ class EnvironmentDataDescription(ComplexDop):
44
44
  env_datas: NamedItemList[EnvironmentData]
45
45
  env_data_refs: List[OdxLinkRef]
46
46
 
47
- @property
48
- def param(self) -> Parameter:
49
- # the parameter referenced via SNREF cannot be resolved here
50
- # because the relevant list of parameters depends on the
51
- # concrete codec object processed, whilst an environment data
52
- # description object can be featured in an arbitrary number of
53
- # responses. Instead, lookup of the appropriate parameter is
54
- # done within the encode and decode methods.
55
- odxraise("The parameter of ENV-DATA-DESC objects cannot be resolved "
56
- "because it depends on the context")
57
- return cast(None, Parameter)
58
-
59
47
  @staticmethod
60
48
  def from_et(et_element: ElementTree.Element,
61
49
  doc_frags: List[OdxDocFragment]) -> "EnvironmentDataDescription":
@@ -74,20 +62,20 @@ class EnvironmentDataDescription(ComplexDop):
74
62
  # situation is reversed. This means that we will create one
75
63
  # empty and one non-empty list here. (Which is which depends
76
64
  # on the version of the standard used by the file.)
77
- env_data_refs = [
78
- odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
79
- for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
80
- ]
81
65
  env_datas = NamedItemList([
82
66
  EnvironmentData.from_et(env_data_elem, doc_frags)
83
67
  for env_data_elem in et_element.iterfind("ENV-DATAS/ENV-DATA")
84
68
  ])
69
+ env_data_refs = [
70
+ odxrequire(OdxLinkRef.from_et(env_data_ref, doc_frags))
71
+ for env_data_ref in et_element.iterfind("ENV-DATA-REFS/ENV-DATA-REF")
72
+ ]
85
73
 
86
74
  return EnvironmentDataDescription(
87
75
  param_snref=param_snref,
88
76
  param_snpathref=param_snpathref,
89
- env_data_refs=env_data_refs,
90
77
  env_datas=env_datas,
78
+ env_data_refs=env_data_refs,
91
79
  **kwargs)
92
80
 
93
81
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -103,7 +91,8 @@ class EnvironmentDataDescription(ComplexDop):
103
91
  # ODX 2.0 specifies environment data objects here, ODX 2.2
104
92
  # uses references
105
93
  if self.env_data_refs:
106
- self.env_datas = NamedItemList([odxlinks.resolve(x) for x in self.env_data_refs])
94
+ self.env_datas = NamedItemList(
95
+ [odxlinks.resolve(x, EnvironmentData) for x in self.env_data_refs])
107
96
  else:
108
97
  for ed in self.env_datas:
109
98
  ed._resolve_odxlinks(odxlinks)
@@ -221,7 +210,7 @@ class EnvironmentDataDescription(ComplexDop):
221
210
  if not isinstance(dop, (DataObjectProperty, DtcDop)):
222
211
  odxraise(f"The DOP of the parameter referenced by environment data descriptions "
223
212
  f"must use either be DataObjectProperty or a DtcDop (encountered "
224
- f"{type(param).__name__} for parameter '{self.param.short_name}' "
213
+ f"{type(param).__name__} for parameter '{param.short_name}' "
225
214
  f"of ENV-DATA-DESC '{self.short_name}')")
226
215
  return 0
227
216
 
odxtools/field.py CHANGED
@@ -21,19 +21,14 @@ class Field(ComplexDop):
21
21
  env_data_desc_snref: Optional[str]
22
22
  is_visible_raw: Optional[bool]
23
23
 
24
- def __post_init__(self) -> None:
25
- self._structure: Optional[BasicStructure] = None
26
- self._env_data_desc: Optional[EnvironmentDataDescription] = None
27
- num_struct_refs = 0 if self.structure_ref is None else 1
28
- num_struct_refs += 0 if self.structure_snref is None else 1
29
-
30
- num_edd_refs = 0 if self.env_data_desc_ref is None else 1
31
- num_edd_refs += 0 if self.env_data_desc_snref is None else 1
24
+ @property
25
+ def structure(self) -> BasicStructure:
26
+ """may be a Structure or a env-data-desc"""
27
+ return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
32
28
 
33
- odxassert(
34
- num_struct_refs + num_edd_refs == 1,
35
- "FIELDs need to specify exactly one reference to a "
36
- "structure or an environment data description")
29
+ @property
30
+ def is_visible(self) -> bool:
31
+ return self.is_visible_raw in (None, True)
37
32
 
38
33
  @staticmethod
39
34
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Field":
@@ -58,17 +53,19 @@ class Field(ComplexDop):
58
53
  is_visible_raw=is_visible_raw,
59
54
  **kwargs)
60
55
 
61
- @property
62
- def is_visible(self) -> bool:
63
- return self.is_visible_raw in (None, True)
56
+ def __post_init__(self) -> None:
57
+ self._structure: Optional[BasicStructure] = None
58
+ self._env_data_desc: Optional[EnvironmentDataDescription] = None
59
+ num_struct_refs = 0 if self.structure_ref is None else 1
60
+ num_struct_refs += 0 if self.structure_snref is None else 1
64
61
 
65
- @property
66
- def structure(self) -> BasicStructure:
67
- """may be a Structure or a env-data-desc"""
68
- return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
62
+ num_edd_refs = 0 if self.env_data_desc_ref is None else 1
63
+ num_edd_refs += 0 if self.env_data_desc_snref is None else 1
69
64
 
70
- def get_static_bit_length(self) -> Optional[int]:
71
- return None
65
+ odxassert(
66
+ num_struct_refs + num_edd_refs == 1,
67
+ "FIELDs need to specify exactly one reference to a "
68
+ "structure or an environment data description")
72
69
 
73
70
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
74
71
  """Recursively resolve any odxlinks references"""
@@ -89,3 +86,6 @@ class Field(ComplexDop):
89
86
  if self.env_data_desc_snref is not None:
90
87
  self._env_data_desc = resolve_snref(self.env_data_desc_snref, ddds.env_data_descs,
91
88
  EnvironmentDataDescription)
89
+
90
+ def get_static_bit_length(self) -> Optional[int]:
91
+ return None
odxtools/inputparam.py CHANGED
@@ -15,25 +15,35 @@ from .utils import dataclass_fields_asdict
15
15
 
16
16
  @dataclass
17
17
  class InputParam(NamedElement):
18
+ physical_default_value: Optional[str]
18
19
  dop_base_ref: OdxLinkRef
19
20
  oid: Optional[str]
20
21
  semantic: Optional[str]
21
- physical_default_value: Optional[str]
22
+
23
+ @property
24
+ def dop(self) -> DopBase:
25
+ """The data object property describing this parameter."""
26
+ return self._dop
27
+
28
+ @deprecated(details="use .dop") # type: ignore[misc]
29
+ def dop_base(self) -> DopBase:
30
+ return self._dop
22
31
 
23
32
  @staticmethod
24
33
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "InputParam":
25
34
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
26
- dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
35
+
27
36
  physical_default_value = et_element.findtext("PHYSICAL-DEFAULT-VALUE")
37
+ dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
28
38
 
29
- semantic = et_element.get("SEMANTIC")
30
39
  oid = et_element.get("OID")
40
+ semantic = et_element.get("SEMANTIC")
31
41
 
32
42
  return InputParam(
33
- dop_base_ref=dop_base_ref,
34
43
  physical_default_value=physical_default_value,
35
- semantic=semantic,
44
+ dop_base_ref=dop_base_ref,
36
45
  oid=oid,
46
+ semantic=semantic,
37
47
  **kwargs)
38
48
 
39
49
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
@@ -44,12 +54,3 @@ class InputParam(NamedElement):
44
54
 
45
55
  def _resolve_snrefs(self, context: SnRefContext) -> None:
46
56
  pass
47
-
48
- @property
49
- def dop(self) -> DopBase:
50
- """The data object property describing this parameter."""
51
- return self._dop
52
-
53
- @deprecated(details="use .dop") # type: ignore[misc]
54
- def dop_base(self) -> DopBase:
55
- return self._dop
@@ -23,6 +23,10 @@ class LeadingLengthInfoType(DiagCodedType):
23
23
  #: object.
24
24
  bit_length: int
25
25
 
26
+ @property
27
+ def dct_type(self) -> DctType:
28
+ return "LEADING-LENGTH-INFO-TYPE"
29
+
26
30
  @staticmethod
27
31
  @override
28
32
  def from_et(et_element: ElementTree.Element,
@@ -46,10 +50,6 @@ class LeadingLengthInfoType(DiagCodedType):
46
50
  f"A leading length info type cannot have the base data type {self.base_data_type.name}."
47
51
  )
48
52
 
49
- @property
50
- def dct_type(self) -> DctType:
51
- return "LEADING-LENGTH-INFO-TYPE"
52
-
53
53
  @override
54
54
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
55
55
 
@@ -8,9 +8,8 @@ from .diagnostictroublecode import DiagnosticTroubleCode
8
8
  from .diagservice import DiagService
9
9
  from .exceptions import odxraise, odxrequire
10
10
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
11
- from .odxtypes import ParameterValue, ParameterValueDict
11
+ from .odxtypes import BytesTypes, ParameterValue, ParameterValueDict
12
12
  from .snrefcontext import SnRefContext
13
- from .utils import BytesTypes
14
13
 
15
14
 
16
15
  @dataclass
@@ -53,7 +52,7 @@ class MatchingParameter:
53
52
  elif (out_param_snpathref_el := et_element.find("OUT-PARAM-IF-SNPATHREF")) is not None:
54
53
  out_param_if_snpathref = odxrequire(out_param_snpathref_el.get("SHORT-NAME-PATH"))
55
54
  else:
56
- odxraise("Output parameter must not left unspecified")
55
+ odxraise("Output parameter must not be left unspecified")
57
56
 
58
57
  return MatchingParameter(
59
58
  expected_value=expected_value,
@@ -10,10 +10,10 @@ from .decodestate import DecodeState
10
10
  from .diagcodedtype import DctType, DiagCodedType
11
11
  from .encodestate import EncodeState
12
12
  from .encoding import get_string_encoding
13
- from .exceptions import (DecodeError, EncodeError, odxassert, odxraise, odxrequire)
13
+ from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
14
14
  from .odxlink import OdxDocFragment
15
- from .odxtypes import AtomicOdxType, DataType
16
- from .utils import BytesTypes, dataclass_fields_asdict
15
+ from .odxtypes import AtomicOdxType, BytesTypes, DataType
16
+ from .utils import dataclass_fields_asdict
17
17
 
18
18
 
19
19
  class Termination(Enum):
@@ -24,8 +24,8 @@ class Termination(Enum):
24
24
 
25
25
  @dataclass
26
26
  class MinMaxLengthType(DiagCodedType):
27
- min_length: int
28
27
  max_length: Optional[int]
28
+ min_length: int
29
29
  termination: Termination
30
30
 
31
31
  @property
@@ -38,12 +38,12 @@ class MinMaxLengthType(DiagCodedType):
38
38
  doc_frags: List[OdxDocFragment]) -> "MinMaxLengthType":
39
39
  kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
40
40
 
41
- min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
42
41
  max_length = None
43
42
  if et_element.find("MAX-LENGTH") is not None:
44
43
  max_length = int(odxrequire(et_element.findtext("MAX-LENGTH")))
44
+ min_length = int(odxrequire(et_element.findtext("MIN-LENGTH")))
45
45
 
46
- termination_str = odxrequire(et_element.get("TERMINATION"))
46
+ termination_str = odxrequire(et_element.attrib.get("TERMINATION"))
47
47
  try:
48
48
  termination = Termination(termination_str)
49
49
  except ValueError:
@@ -51,7 +51,7 @@ class MinMaxLengthType(DiagCodedType):
51
51
  odxraise(f"Encountered unknown termination type '{termination_str}'")
52
52
 
53
53
  return MinMaxLengthType(
54
- min_length=min_length, max_length=max_length, termination=termination, **kwargs)
54
+ max_length=max_length, min_length=min_length, termination=termination, **kwargs)
55
55
 
56
56
  def __post_init__(self) -> None:
57
57
  odxassert(self.max_length is None or self.min_length <= self.max_length)
odxtools/multiplexer.py CHANGED
@@ -34,12 +34,15 @@ class Multiplexer(ComplexDop):
34
34
  cases: NamedItemList[MultiplexerCase]
35
35
  is_visible_raw: Optional[bool]
36
36
 
37
+ @property
38
+ def is_visible(self) -> bool:
39
+ return self.is_visible_raw is True
40
+
37
41
  @staticmethod
38
42
  @override
39
43
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
40
44
  """Reads a Multiplexer from Diag Layer."""
41
- base_obj = ComplexDop.from_et(et_element, doc_frags)
42
- kwargs = dataclass_fields_asdict(base_obj)
45
+ kwargs = dataclass_fields_asdict(ComplexDop.from_et(et_element, doc_frags))
43
46
 
44
47
  byte_position = int(et_element.findtext("BYTE-POSITION", "0"))
45
48
  switch_key = MultiplexerSwitchKey.from_et(
@@ -55,16 +58,45 @@ class Multiplexer(ComplexDop):
55
58
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
56
59
 
57
60
  return Multiplexer(
58
- is_visible_raw=is_visible_raw,
59
61
  byte_position=byte_position,
60
62
  switch_key=switch_key,
61
63
  default_case=default_case,
62
64
  cases=cases,
65
+ is_visible_raw=is_visible_raw,
63
66
  **kwargs)
64
67
 
65
- @property
66
- def is_visible(self) -> bool:
67
- return self.is_visible_raw is True
68
+ @override
69
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
70
+ odxlinks = super()._build_odxlinks()
71
+
72
+ odxlinks.update(self.switch_key._build_odxlinks())
73
+ if self.default_case is not None:
74
+ odxlinks.update(self.default_case._build_odxlinks())
75
+
76
+ return odxlinks
77
+
78
+ @override
79
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
80
+ super()._resolve_odxlinks(odxlinks)
81
+
82
+ self.switch_key._resolve_odxlinks(odxlinks)
83
+ if self.default_case is not None:
84
+ self.default_case._resolve_odxlinks(odxlinks)
85
+
86
+ for mux_case in self.cases:
87
+ mux_case._mux_case_resolve_odxlinks(
88
+ odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
89
+
90
+ @override
91
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
92
+ super()._resolve_snrefs(context)
93
+
94
+ self.switch_key._resolve_snrefs(context)
95
+ if self.default_case is not None:
96
+ self.default_case._resolve_snrefs(context)
97
+
98
+ for mux_case in self.cases:
99
+ mux_case._resolve_snrefs(context)
68
100
 
69
101
  def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
70
102
  key_type = self.switch_key.dop.physical_type.base_data_type
@@ -203,36 +235,3 @@ class Multiplexer(ComplexDop):
203
235
  decode_state.origin_byte_position = orig_origin
204
236
 
205
237
  return result
206
-
207
- @override
208
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
209
- odxlinks = super()._build_odxlinks()
210
-
211
- odxlinks.update(self.switch_key._build_odxlinks())
212
- if self.default_case is not None:
213
- odxlinks.update(self.default_case._build_odxlinks())
214
-
215
- return odxlinks
216
-
217
- @override
218
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
219
- super()._resolve_odxlinks(odxlinks)
220
-
221
- self.switch_key._resolve_odxlinks(odxlinks)
222
- if self.default_case is not None:
223
- self.default_case._resolve_odxlinks(odxlinks)
224
-
225
- for mux_case in self.cases:
226
- mux_case._mux_case_resolve_odxlinks(
227
- odxlinks, key_physical_type=self.switch_key.dop.physical_type.base_data_type)
228
-
229
- @override
230
- def _resolve_snrefs(self, context: SnRefContext) -> None:
231
- super()._resolve_snrefs(context)
232
-
233
- self.switch_key._resolve_snrefs(context)
234
- if self.default_case is not None:
235
- self.default_case._resolve_snrefs(context)
236
-
237
- for mux_case in self.cases:
238
- mux_case._resolve_snrefs(context)
@@ -22,8 +22,9 @@ class MultiplexerCase(NamedElement):
22
22
  lower_limit: Limit
23
23
  upper_limit: Limit
24
24
 
25
- def __post_init__(self) -> None:
26
- self._structure: Optional[Structure]
25
+ @property
26
+ def structure(self) -> Optional[Structure]:
27
+ return self._structure
27
28
 
28
29
  @staticmethod
29
30
  def from_et(et_element: ElementTree.Element,
@@ -77,7 +78,3 @@ class MultiplexerCase(NamedElement):
77
78
  def applies(self, value: AtomicOdxType) -> bool:
78
79
  return self.lower_limit.complies_to_lower(value) \
79
80
  and self.upper_limit.complies_to_upper(value)
80
-
81
- @property
82
- def structure(self) -> Optional[Structure]:
83
- return self._structure
@@ -17,8 +17,9 @@ class MultiplexerDefaultCase(NamedElement):
17
17
  structure_ref: Optional[OdxLinkRef]
18
18
  structure_snref: Optional[str]
19
19
 
20
- def __post_init__(self) -> None:
21
- self._structure: Optional[Structure]
20
+ @property
21
+ def structure(self) -> Optional[Structure]:
22
+ return self._structure
22
23
 
23
24
  @staticmethod
24
25
  def from_et(et_element: ElementTree.Element,
@@ -46,7 +47,3 @@ class MultiplexerDefaultCase(NamedElement):
46
47
  if self.structure_snref:
47
48
  ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
48
49
  self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
49
-
50
- @property
51
- def structure(self) -> Optional[Structure]:
52
- return self._structure
@@ -18,6 +18,10 @@ class MultiplexerSwitchKey:
18
18
  bit_position: Optional[int]
19
19
  dop_ref: OdxLinkRef
20
20
 
21
+ @property
22
+ def dop(self) -> DataObjectProperty:
23
+ return self._dop
24
+
21
25
  @staticmethod
22
26
  def from_et(et_element: ElementTree.Element,
23
27
  doc_frags: List[OdxDocFragment]) -> "MultiplexerSwitchKey":
@@ -40,7 +44,3 @@ class MultiplexerSwitchKey:
40
44
 
41
45
  def _resolve_snrefs(self, context: SnRefContext) -> None:
42
46
  pass
43
-
44
- @property
45
- def dop(self) -> DataObjectProperty:
46
- return self._dop
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional
3
+ from typing import Any, Dict, List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .dopbase import DopBase
@@ -15,8 +15,10 @@ from .utils import dataclass_fields_asdict
15
15
  class NegOutputParam(NamedElement):
16
16
  dop_base_ref: OdxLinkRef
17
17
 
18
- def __post_init__(self) -> None:
19
- self._dop: Optional[DopBase] = None
18
+ @property
19
+ def dop(self) -> DopBase:
20
+ """The data object property describing this parameter."""
21
+ return self._dop
20
22
 
21
23
  @staticmethod
22
24
  def from_et(et_element: ElementTree.Element,
@@ -31,12 +33,7 @@ class NegOutputParam(NamedElement):
31
33
  return {}
32
34
 
33
35
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
34
- self._dop = odxlinks.resolve(self.dop_base_ref)
36
+ self._dop = odxlinks.resolve(self.dop_base_ref, DopBase)
35
37
 
36
38
  def _resolve_snrefs(self, context: SnRefContext) -> None:
37
39
  pass
38
-
39
- @property
40
- def dop(self) -> Optional[DopBase]:
41
- """The data object property describing this parameter."""
42
- return self._dop
odxtools/odxlink.py CHANGED
@@ -277,24 +277,40 @@ class OdxLinkDatabase:
277
277
  @overload
278
278
  def resolve_snref(target_short_name: str,
279
279
  items: Iterable[OdxNamed],
280
- expected_type: None = None) -> Any:
280
+ expected_type: None = None,
281
+ *,
282
+ lenient: None = None) -> Any:
281
283
  """Resolve a short name reference given a sequence of candidate objects"""
282
284
  ...
283
285
 
284
286
 
285
287
  @overload
286
- def resolve_snref(target_short_name: str, items: Iterable[OdxNamed],
287
- expected_type: Type[TNamed]) -> TNamed:
288
+ def resolve_snref(target_short_name: str,
289
+ items: Iterable[OdxNamed],
290
+ expected_type: Type[TNamed],
291
+ *,
292
+ lenient: None = None) -> TNamed:
293
+ ...
294
+
295
+
296
+ @overload
297
+ def resolve_snref(target_short_name: str,
298
+ items: Iterable[OdxNamed],
299
+ expected_type: Type[TNamed],
300
+ *,
301
+ lenient: bool = True) -> Optional[TNamed]:
288
302
  ...
289
303
 
290
304
 
291
305
  def resolve_snref(target_short_name: str,
292
306
  items: Iterable[OdxNamed],
293
- expected_type: Any = None) -> Any:
307
+ expected_type: Any = None,
308
+ lenient: Optional[bool] = None) -> Any:
294
309
  candidates = [x for x in items if x.short_name == target_short_name]
295
310
 
296
311
  if not candidates:
297
- odxraise(f"Cannot resolve short name reference to '{target_short_name}'")
312
+ if not lenient:
313
+ odxraise(f"Cannot resolve short name reference to '{target_short_name}'")
298
314
  return None
299
315
  elif len(candidates) > 1:
300
316
  odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'")