odxtools 9.7.0__py3-none-any.whl → 10.1.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 (193) hide show
  1. odxtools/additionalaudience.py +7 -7
  2. odxtools/admindata.py +14 -13
  3. odxtools/audience.py +17 -17
  4. odxtools/basecomparam.py +9 -8
  5. odxtools/basevariantpattern.py +9 -10
  6. odxtools/basicstructure.py +15 -15
  7. odxtools/cli/_print_utils.py +34 -22
  8. odxtools/cli/browse.py +8 -8
  9. odxtools/cli/compare.py +24 -24
  10. odxtools/cli/decode.py +3 -4
  11. odxtools/cli/find.py +4 -5
  12. odxtools/cli/list.py +6 -6
  13. odxtools/cli/main.py +2 -2
  14. odxtools/cli/snoop.py +3 -3
  15. odxtools/codec.py +3 -3
  16. odxtools/commrelation.py +18 -17
  17. odxtools/companydata.py +13 -13
  18. odxtools/companydocinfo.py +15 -17
  19. odxtools/companyrevisioninfo.py +9 -9
  20. odxtools/companyspecificinfo.py +11 -13
  21. odxtools/comparam.py +8 -7
  22. odxtools/comparaminstance.py +14 -14
  23. odxtools/comparamspec.py +10 -11
  24. odxtools/comparamsubset.py +17 -25
  25. odxtools/complexcomparam.py +14 -14
  26. odxtools/complexdop.py +1 -1
  27. odxtools/compositecodec.py +8 -8
  28. odxtools/compumethods/compucodecompumethod.py +7 -7
  29. odxtools/compumethods/compuconst.py +5 -6
  30. odxtools/compumethods/compudefaultvalue.py +2 -3
  31. odxtools/compumethods/compuinternaltophys.py +13 -12
  32. odxtools/compumethods/compumethod.py +10 -9
  33. odxtools/compumethods/compuphystointernal.py +13 -12
  34. odxtools/compumethods/compurationalcoeffs.py +7 -7
  35. odxtools/compumethods/compuscale.py +15 -16
  36. odxtools/compumethods/createanycompumethod.py +12 -13
  37. odxtools/compumethods/identicalcompumethod.py +4 -5
  38. odxtools/compumethods/limit.py +14 -14
  39. odxtools/compumethods/linearcompumethod.py +5 -5
  40. odxtools/compumethods/linearsegment.py +10 -11
  41. odxtools/compumethods/ratfunccompumethod.py +6 -6
  42. odxtools/compumethods/ratfuncsegment.py +7 -8
  43. odxtools/compumethods/scalelinearcompumethod.py +9 -9
  44. odxtools/compumethods/scaleratfunccompumethod.py +7 -7
  45. odxtools/compumethods/tabintpcompumethod.py +10 -13
  46. odxtools/compumethods/texttablecompumethod.py +6 -6
  47. odxtools/createanycomparam.py +5 -7
  48. odxtools/createanydiagcodedtype.py +7 -8
  49. odxtools/database.py +34 -31
  50. odxtools/dataobjectproperty.py +19 -20
  51. odxtools/decodestate.py +5 -5
  52. odxtools/description.py +9 -9
  53. odxtools/determinenumberofitems.py +8 -7
  54. odxtools/diagcodedtype.py +10 -10
  55. odxtools/diagcomm.py +29 -30
  56. odxtools/diagdatadictionaryspec.py +36 -36
  57. odxtools/diaglayercontainer.py +35 -34
  58. odxtools/diaglayers/basevariant.py +14 -12
  59. odxtools/diaglayers/basevariantraw.py +22 -23
  60. odxtools/diaglayers/diaglayer.py +24 -22
  61. odxtools/diaglayers/diaglayerraw.py +43 -52
  62. odxtools/diaglayers/diaglayertype.py +1 -2
  63. odxtools/diaglayers/ecushareddata.py +9 -9
  64. odxtools/diaglayers/ecushareddataraw.py +15 -16
  65. odxtools/diaglayers/ecuvariant.py +15 -13
  66. odxtools/diaglayers/ecuvariantraw.py +21 -22
  67. odxtools/diaglayers/functionalgroup.py +12 -11
  68. odxtools/diaglayers/functionalgroupraw.py +17 -18
  69. odxtools/diaglayers/hierarchyelement.py +48 -54
  70. odxtools/diaglayers/hierarchyelementraw.py +10 -11
  71. odxtools/diaglayers/protocol.py +7 -7
  72. odxtools/diaglayers/protocolraw.py +13 -14
  73. odxtools/diagnostictroublecode.py +15 -17
  74. odxtools/diagservice.py +28 -27
  75. odxtools/diagvariable.py +24 -25
  76. odxtools/docrevision.py +18 -17
  77. odxtools/dopbase.py +13 -14
  78. odxtools/dtcconnector.py +8 -7
  79. odxtools/dtcdop.py +24 -20
  80. odxtools/dynamicendmarkerfield.py +10 -9
  81. odxtools/dynamiclengthfield.py +10 -9
  82. odxtools/dyndefinedspec.py +10 -10
  83. odxtools/dynenddopref.py +9 -9
  84. odxtools/dyniddefmodeinfo.py +21 -21
  85. odxtools/ecuvariantpattern.py +8 -10
  86. odxtools/element.py +12 -13
  87. odxtools/encodestate.py +11 -11
  88. odxtools/encoding.py +2 -3
  89. odxtools/endofpdufield.py +9 -10
  90. odxtools/envdataconnector.py +8 -8
  91. odxtools/environmentdata.py +7 -9
  92. odxtools/environmentdatadescription.py +18 -17
  93. odxtools/exceptions.py +5 -5
  94. odxtools/externalaccessmethod.py +4 -6
  95. odxtools/externaldoc.py +6 -6
  96. odxtools/field.py +15 -15
  97. odxtools/functionalclass.py +9 -9
  98. odxtools/inputparam.py +11 -10
  99. odxtools/internalconstr.py +10 -11
  100. odxtools/isotp_state_machine.py +12 -11
  101. odxtools/leadinglengthinfotype.py +4 -6
  102. odxtools/library.py +9 -8
  103. odxtools/linkeddtcdop.py +9 -8
  104. odxtools/loadfile.py +5 -6
  105. odxtools/matchingbasevariantparameter.py +5 -6
  106. odxtools/matchingparameter.py +10 -10
  107. odxtools/message.py +1 -1
  108. odxtools/minmaxlengthtype.py +6 -7
  109. odxtools/modification.py +7 -6
  110. odxtools/multiplexer.py +54 -18
  111. odxtools/multiplexercase.py +13 -13
  112. odxtools/multiplexerdefaultcase.py +11 -10
  113. odxtools/multiplexerswitchkey.py +8 -8
  114. odxtools/nameditemlist.py +13 -13
  115. odxtools/negoutputparam.py +8 -8
  116. odxtools/obd.py +1 -2
  117. odxtools/odxcategory.py +14 -26
  118. odxtools/odxdoccontext.py +16 -0
  119. odxtools/odxlink.py +23 -25
  120. odxtools/odxtypes.py +18 -15
  121. odxtools/outputparam.py +9 -8
  122. odxtools/parameterinfo.py +1 -1
  123. odxtools/parameters/codedconstparameter.py +10 -10
  124. odxtools/parameters/createanyparameter.py +15 -16
  125. odxtools/parameters/dynamicparameter.py +5 -7
  126. odxtools/parameters/lengthkeyparameter.py +10 -10
  127. odxtools/parameters/matchingrequestparameter.py +6 -7
  128. odxtools/parameters/nrcconstparameter.py +13 -13
  129. odxtools/parameters/parameter.py +17 -18
  130. odxtools/parameters/parameterwithdop.py +13 -13
  131. odxtools/parameters/physicalconstantparameter.py +8 -7
  132. odxtools/parameters/reservedparameter.py +6 -8
  133. odxtools/parameters/systemparameter.py +5 -7
  134. odxtools/parameters/tableentryparameter.py +8 -8
  135. odxtools/parameters/tablekeyparameter.py +17 -17
  136. odxtools/parameters/tablestructparameter.py +11 -11
  137. odxtools/parameters/valueparameter.py +11 -11
  138. odxtools/paramlengthinfotype.py +10 -9
  139. odxtools/parentref.py +15 -13
  140. odxtools/physicaldimension.py +15 -15
  141. odxtools/physicaltype.py +5 -6
  142. odxtools/posresponsesuppressible.py +11 -12
  143. odxtools/preconditionstateref.py +11 -11
  144. odxtools/progcode.py +11 -10
  145. odxtools/protstack.py +10 -9
  146. odxtools/relateddiagcommref.py +5 -6
  147. odxtools/relateddoc.py +11 -10
  148. odxtools/request.py +18 -19
  149. odxtools/response.py +19 -20
  150. odxtools/scaleconstr.py +8 -9
  151. odxtools/servicebinner.py +5 -5
  152. odxtools/singleecujob.py +16 -15
  153. odxtools/snrefcontext.py +3 -3
  154. odxtools/specialdata.py +8 -7
  155. odxtools/specialdatagroup.py +17 -17
  156. odxtools/specialdatagroupcaption.py +7 -6
  157. odxtools/standardlengthtype.py +14 -22
  158. odxtools/state.py +7 -6
  159. odxtools/statechart.py +12 -11
  160. odxtools/statemachine.py +4 -3
  161. odxtools/statetransition.py +9 -9
  162. odxtools/statetransitionref.py +19 -19
  163. odxtools/staticfield.py +9 -7
  164. odxtools/structure.py +5 -6
  165. odxtools/subcomponent.py +20 -18
  166. odxtools/subcomponentparamconnector.py +10 -9
  167. odxtools/subcomponentpattern.py +9 -9
  168. odxtools/swvariable.py +6 -7
  169. odxtools/table.py +25 -26
  170. odxtools/tablediagcommconnector.py +9 -8
  171. odxtools/tablerow.py +64 -43
  172. odxtools/tablerowconnector.py +8 -8
  173. odxtools/teammember.py +16 -15
  174. odxtools/templates/macros/printParentRef.xml.jinja2 +3 -1
  175. odxtools/text.py +4 -5
  176. odxtools/uds.py +2 -3
  177. odxtools/unit.py +14 -13
  178. odxtools/unitgroup.py +11 -10
  179. odxtools/unitspec.py +18 -19
  180. odxtools/utils.py +3 -3
  181. odxtools/variablegroup.py +5 -6
  182. odxtools/variantmatcher.py +10 -10
  183. odxtools/variantpattern.py +5 -6
  184. odxtools/version.py +2 -2
  185. odxtools/writepdxfile.py +5 -24
  186. odxtools/xdoc.py +13 -12
  187. {odxtools-9.7.0.dist-info → odxtools-10.1.0.dist-info}/METADATA +4 -5
  188. odxtools-10.1.0.dist-info/RECORD +265 -0
  189. {odxtools-9.7.0.dist-info → odxtools-10.1.0.dist-info}/WHEEL +1 -1
  190. odxtools-9.7.0.dist-info/RECORD +0 -264
  191. {odxtools-9.7.0.dist-info → odxtools-10.1.0.dist-info}/entry_points.txt +0 -0
  192. {odxtools-9.7.0.dist-info → odxtools-10.1.0.dist-info}/licenses/LICENSE +0 -0
  193. {odxtools-9.7.0.dist-info → odxtools-10.1.0.dist-info}/top_level.txt +0 -0
odxtools/loadfile.py CHANGED
@@ -1,19 +1,18 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import os
3
3
  from pathlib import Path
4
- from typing import Union
5
4
 
6
5
  from .database import Database
7
6
 
8
7
 
9
- def load_pdx_file(pdx_file: Union[str, Path]) -> Database:
8
+ def load_pdx_file(pdx_file: str | Path) -> Database:
10
9
  db = Database()
11
10
  db.add_pdx_file(str(pdx_file))
12
11
  db.refresh()
13
12
  return db
14
13
 
15
14
 
16
- def load_odx_d_file(odx_d_file_name: Union[str, Path]) -> Database:
15
+ def load_odx_d_file(odx_d_file_name: str | Path) -> Database:
17
16
  db = Database()
18
17
  db.add_odx_file(str(odx_d_file_name))
19
18
  db.refresh()
@@ -21,7 +20,7 @@ def load_odx_d_file(odx_d_file_name: Union[str, Path]) -> Database:
21
20
  return db
22
21
 
23
22
 
24
- def load_file(file_name: Union[str, Path]) -> Database:
23
+ def load_file(file_name: str | Path) -> Database:
25
24
  if str(file_name).lower().endswith(".pdx"):
26
25
  return load_pdx_file(str(file_name))
27
26
  elif str(file_name).lower().endswith(".odx-d"):
@@ -30,7 +29,7 @@ def load_file(file_name: Union[str, Path]) -> Database:
30
29
  raise RuntimeError(f"Could not guess the file format of file '{file_name}'!")
31
30
 
32
31
 
33
- def load_files(*file_names: Union[str, Path]) -> Database:
32
+ def load_files(*file_names: str | Path) -> Database:
34
33
  db = Database()
35
34
  for file_name in file_names:
36
35
  p = Path(file_name)
@@ -45,7 +44,7 @@ def load_files(*file_names: Union[str, Path]) -> Database:
45
44
  return db
46
45
 
47
46
 
48
- def load_directory(dir_name: Union[str, Path]) -> Database:
47
+ def load_directory(dir_name: str | Path) -> Database:
49
48
  db = Database()
50
49
  for file_name in os.listdir(str(dir_name)):
51
50
  p = Path(dir_name) / file_name
@@ -1,15 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
4
3
  from xml.etree import ElementTree
5
4
 
6
5
  from .matchingparameter import MatchingParameter
7
- from .odxlink import OdxDocFragment
6
+ from .odxdoccontext import OdxDocContext
8
7
  from .odxtypes import odxstr_to_bool
9
8
  from .utils import dataclass_fields_asdict
10
9
 
11
10
 
12
- @dataclass
11
+ @dataclass(kw_only=True)
13
12
  class MatchingBaseVariantParameter(MatchingParameter):
14
13
  """A description of a parameter used for base variant matching.
15
14
 
@@ -18,7 +17,7 @@ class MatchingBaseVariantParameter(MatchingParameter):
18
17
  additional subtag `USE-PHYSICAL-ADDRESSING`.
19
18
  """
20
19
 
21
- use_physical_addressing_raw: Optional[bool]
20
+ use_physical_addressing_raw: bool | None = None
22
21
 
23
22
  @property
24
23
  def use_physical_addressing(self) -> bool:
@@ -26,9 +25,9 @@ class MatchingBaseVariantParameter(MatchingParameter):
26
25
 
27
26
  @staticmethod
28
27
  def from_et(et_element: ElementTree.Element,
29
- doc_frags: List[OdxDocFragment]) -> "MatchingBaseVariantParameter":
28
+ context: OdxDocContext) -> "MatchingBaseVariantParameter":
30
29
 
31
- kwargs = dataclass_fields_asdict(MatchingParameter.from_et(et_element, doc_frags))
30
+ kwargs = dataclass_fields_asdict(MatchingParameter.from_et(et_element, context))
32
31
 
33
32
  use_physical_addressing_raw = odxstr_to_bool(et_element.findtext("USE-PHYSICAL-ADDRESSING"))
34
33
 
@@ -1,18 +1,19 @@
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, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .diaglayers.diaglayer import DiagLayer
7
7
  from .diagnostictroublecode import DiagnosticTroubleCode
8
8
  from .diagservice import DiagService
9
9
  from .exceptions import odxraise, odxrequire
10
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref
10
+ from .odxdoccontext import OdxDocContext
11
+ from .odxlink import OdxLinkDatabase, OdxLinkId, resolve_snref
11
12
  from .odxtypes import BytesTypes, ParameterValue, ParameterValueDict
12
13
  from .snrefcontext import SnRefContext
13
14
 
14
15
 
15
- @dataclass
16
+ @dataclass(kw_only=True)
16
17
  class MatchingParameter:
17
18
  """According to ISO 22901, a MatchingParameter contains a string
18
19
  value identifying the active ECU or base variant. Moreover, it
@@ -34,12 +35,11 @@ class MatchingParameter:
34
35
  # or negative response. What it probably actually wants to say is
35
36
  # that any response that can possibly be received shall exhibit
36
37
  # the referenced parameter.
37
- out_param_if_snref: Optional[str]
38
- out_param_if_snpathref: Optional[str]
38
+ out_param_if_snref: str | None = None
39
+ out_param_if_snpathref: str | None = None
39
40
 
40
41
  @staticmethod
41
- def from_et(et_element: ElementTree.Element,
42
- doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
42
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MatchingParameter":
43
43
 
44
44
  expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
45
45
  diag_comm_snref = odxrequire(
@@ -61,7 +61,7 @@ class MatchingParameter:
61
61
  out_param_if_snpathref=out_param_if_snpathref,
62
62
  )
63
63
 
64
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
64
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
65
65
  return {}
66
66
 
67
67
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -93,7 +93,7 @@ class MatchingParameter:
93
93
 
94
94
  return self.__matches(param_dict, snpath_chunks)
95
95
 
96
- def __matches(self, param_dict: ParameterValue, snpath_chunks: List[str]) -> bool:
96
+ def __matches(self, param_dict: ParameterValue, snpath_chunks: list[str]) -> bool:
97
97
  if len(snpath_chunks) == 0:
98
98
  parameter_value = param_dict
99
99
  if isinstance(parameter_value, dict):
@@ -105,7 +105,7 @@ class MatchingParameter:
105
105
  # floating point
106
106
  return abs(float(self.expected_value) - parameter_value) < 1e-8
107
107
  elif isinstance(parameter_value, BytesTypes):
108
- return parameter_value.hex().upper() == self.expected_value.upper()
108
+ return bytes(parameter_value).hex().upper() == self.expected_value.upper()
109
109
  elif isinstance(parameter_value, DiagnosticTroubleCode):
110
110
  # TODO: what happens if non-numerical DTCs like
111
111
  # "U123456" are specified? Is specifying DTCs even
odxtools/message.py CHANGED
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
  from .response import Response
11
11
 
12
12
 
13
- @dataclass
13
+ @dataclass(kw_only=True)
14
14
  class Message:
15
15
  """A diagnostic message with its interpretation.
16
16
 
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional, cast
3
+ from typing import cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -10,15 +10,15 @@ from .diagcodedtype import DctType, DiagCodedType
10
10
  from .encodestate import EncodeState
11
11
  from .encoding import get_string_encoding
12
12
  from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
13
- from .odxlink import OdxDocFragment
13
+ from .odxdoccontext import OdxDocContext
14
14
  from .odxtypes import AtomicOdxType, BytesTypes, DataType
15
15
  from .termination import Termination
16
16
  from .utils import dataclass_fields_asdict
17
17
 
18
18
 
19
- @dataclass
19
+ @dataclass(kw_only=True)
20
20
  class MinMaxLengthType(DiagCodedType):
21
- max_length: Optional[int]
21
+ max_length: int | None = None
22
22
  min_length: int
23
23
  termination: Termination
24
24
 
@@ -28,9 +28,8 @@ class MinMaxLengthType(DiagCodedType):
28
28
 
29
29
  @staticmethod
30
30
  @override
31
- def from_et(et_element: ElementTree.Element,
32
- doc_frags: List[OdxDocFragment]) -> "MinMaxLengthType":
33
- kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, doc_frags))
31
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MinMaxLengthType":
32
+ kwargs = dataclass_fields_asdict(DiagCodedType.from_et(et_element, context))
34
33
 
35
34
  max_length = None
36
35
  if et_element.find("MAX-LENGTH") is not None:
odxtools/modification.py CHANGED
@@ -1,26 +1,27 @@
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
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .exceptions import odxrequire
7
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
7
+ from .odxdoccontext import OdxDocContext
8
+ from .odxlink import OdxLinkDatabase, OdxLinkId
8
9
  from .snrefcontext import SnRefContext
9
10
 
10
11
 
11
- @dataclass
12
+ @dataclass(kw_only=True)
12
13
  class Modification:
13
14
  change: str
14
- reason: Optional[str]
15
+ reason: str | None = None
15
16
 
16
17
  @staticmethod
17
- def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Modification":
18
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Modification":
18
19
  change = odxrequire(et_element.findtext("CHANGE"))
19
20
  reason = et_element.findtext("REASON")
20
21
 
21
22
  return Modification(change=change, reason=reason)
22
23
 
23
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
24
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
24
25
  return {}
25
26
 
26
27
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
odxtools/multiplexer.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional, Tuple, Union, cast
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -13,13 +13,14 @@ from .multiplexercase import MultiplexerCase
13
13
  from .multiplexerdefaultcase import MultiplexerDefaultCase
14
14
  from .multiplexerswitchkey import MultiplexerSwitchKey
15
15
  from .nameditemlist import NamedItemList
16
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
+ from .odxdoccontext import OdxDocContext
17
+ from .odxlink import OdxLinkDatabase, OdxLinkId
17
18
  from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
18
19
  from .snrefcontext import SnRefContext
19
20
  from .utils import dataclass_fields_asdict
20
21
 
21
22
 
22
- @dataclass
23
+ @dataclass(kw_only=True)
23
24
  class Multiplexer(ComplexDop):
24
25
  """This class represents a Multiplexer (MUX)
25
26
 
@@ -30,9 +31,9 @@ class Multiplexer(ComplexDop):
30
31
 
31
32
  byte_position: int
32
33
  switch_key: MultiplexerSwitchKey
33
- default_case: Optional[MultiplexerDefaultCase]
34
- cases: NamedItemList[MultiplexerCase]
35
- is_visible_raw: Optional[bool]
34
+ default_case: MultiplexerDefaultCase | None = None
35
+ cases: NamedItemList[MultiplexerCase] = field(default_factory=NamedItemList)
36
+ is_visible_raw: bool | None = None
36
37
 
37
38
  @property
38
39
  def is_visible(self) -> bool:
@@ -40,20 +41,20 @@ class Multiplexer(ComplexDop):
40
41
 
41
42
  @staticmethod
42
43
  @override
43
- def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Multiplexer":
44
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Multiplexer":
44
45
  """Reads a Multiplexer from Diag Layer."""
45
- kwargs = dataclass_fields_asdict(ComplexDop.from_et(et_element, doc_frags))
46
+ kwargs = dataclass_fields_asdict(ComplexDop.from_et(et_element, context))
46
47
 
47
48
  byte_position = int(et_element.findtext("BYTE-POSITION", "0"))
48
49
  switch_key = MultiplexerSwitchKey.from_et(
49
- odxrequire(et_element.find("SWITCH-KEY")), doc_frags)
50
+ odxrequire(et_element.find("SWITCH-KEY")), context)
50
51
 
51
52
  default_case = None
52
53
  if (dc_elem := et_element.find("DEFAULT-CASE")) is not None:
53
- default_case = MultiplexerDefaultCase.from_et(dc_elem, doc_frags)
54
+ default_case = MultiplexerDefaultCase.from_et(dc_elem, context)
54
55
 
55
56
  cases = NamedItemList(
56
- [MultiplexerCase.from_et(el, doc_frags) for el in et_element.iterfind("CASES/CASE")])
57
+ [MultiplexerCase.from_et(el, context) for el in et_element.iterfind("CASES/CASE")])
57
58
 
58
59
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
59
60
 
@@ -66,7 +67,7 @@ class Multiplexer(ComplexDop):
66
67
  **kwargs)
67
68
 
68
69
  @override
69
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
70
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
70
71
  odxlinks = super()._build_odxlinks()
71
72
 
72
73
  odxlinks.update(self.switch_key._build_odxlinks())
@@ -98,7 +99,7 @@ class Multiplexer(ComplexDop):
98
99
  for mux_case in self.cases:
99
100
  mux_case._resolve_snrefs(context)
100
101
 
101
- def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
102
+ def _get_case_limits(self, case: MultiplexerCase) -> tuple[AtomicOdxType, AtomicOdxType]:
102
103
  key_type = self.switch_key.dop.physical_type.base_data_type
103
104
  lower_limit = key_type.make_from(case.lower_limit.value)
104
105
  upper_limit = key_type.make_from(case.upper_limit.value)
@@ -127,8 +128,8 @@ class Multiplexer(ComplexDop):
127
128
  f"Values of multiplexer parameters must be defined as a "
128
129
  f"(case_name, content_value) tuple instead of as '{physical_value!r}'")
129
130
 
130
- mux_case: Union[MultiplexerCase, MultiplexerDefaultCase]
131
- applicable_cases: List[Union[MultiplexerCase, MultiplexerDefaultCase]]
131
+ mux_case: MultiplexerCase | MultiplexerDefaultCase
132
+ applicable_cases: list[MultiplexerCase | MultiplexerDefaultCase]
132
133
 
133
134
  if isinstance(case_spec, str):
134
135
  applicable_cases = [x for x in self.cases if x.short_name == case_spec]
@@ -147,7 +148,7 @@ class Multiplexer(ComplexDop):
147
148
  elif isinstance(case_spec, int):
148
149
  applicable_cases = []
149
150
  for x in self.cases:
150
- lower, upper = cast(Tuple[int, int], self._get_case_limits(x))
151
+ lower, upper = cast(tuple[int, int], self._get_case_limits(x))
151
152
  if lower <= case_spec and case_spec <= upper:
152
153
  applicable_cases.append(x)
153
154
 
@@ -208,7 +209,7 @@ class Multiplexer(ComplexDop):
208
209
  # relatively to the byte position of the MUX."
209
210
  decode_state.cursor_byte_position = decode_state.origin_byte_position + self.byte_position
210
211
 
211
- applicable_case: Optional[Union[MultiplexerCase, MultiplexerDefaultCase]] = None
212
+ applicable_case: MultiplexerCase | MultiplexerDefaultCase | None = None
212
213
  for mux_case in self.cases:
213
214
  lower, upper = self._get_case_limits(mux_case)
214
215
  if lower <= key_value and key_value <= upper: # type: ignore[operator]
@@ -235,3 +236,38 @@ class Multiplexer(ComplexDop):
235
236
  decode_state.origin_byte_position = orig_origin
236
237
 
237
238
  return result
239
+
240
+ @override
241
+ def get_static_bit_length(self) -> int | None:
242
+ """
243
+ Returns the static bit length of the multiplexer structure, if determinable.
244
+
245
+ If all cases (including the default, if present) have the same static bit length,
246
+ the codec length is considered static and is returned.
247
+ Otherwise, returns None to indicate that the size is dynamic.
248
+ """
249
+ reference_case = self.default_case if self.default_case else self.cases[0]
250
+
251
+ case_bit_length: int | None
252
+ if reference_case.structure is None:
253
+ case_bit_length = 0
254
+ else:
255
+ case_bit_length = reference_case.structure.get_static_bit_length()
256
+ if case_bit_length is None:
257
+ return None
258
+ case_size: int | None
259
+ for mux_case in self.cases:
260
+ if mux_case.structure is None:
261
+ case_size = 0
262
+ else:
263
+ case_size = mux_case.structure.get_static_bit_length()
264
+ if case_size != case_bit_length:
265
+ return None # Found a case with a different or unknown size
266
+
267
+ switch_key_size = self.switch_key.dop.get_static_bit_length()
268
+ if switch_key_size is None:
269
+ return None
270
+
271
+ return max(
272
+ switch_key_size + self.switch_key.byte_position * 8 +
273
+ (self.switch_key.bit_position or 0), case_bit_length + self.byte_position * 8)
@@ -1,49 +1,49 @@
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
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .compumethods.limit import Limit
7
7
  from .element import NamedElement
8
8
  from .exceptions import odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
11
  from .odxtypes import AtomicOdxType, DataType
11
12
  from .snrefcontext import SnRefContext
12
13
  from .structure import Structure
13
14
  from .utils import dataclass_fields_asdict
14
15
 
15
16
 
16
- @dataclass
17
+ @dataclass(kw_only=True)
17
18
  class MultiplexerCase(NamedElement):
18
19
  """This class represents a case which represents a range of keys of a multiplexer."""
19
20
 
20
- structure_ref: Optional[OdxLinkRef]
21
- structure_snref: Optional[str]
21
+ structure_ref: OdxLinkRef | None = None
22
+ structure_snref: str | None = None
22
23
  lower_limit: Limit
23
24
  upper_limit: Limit
24
25
 
25
26
  @property
26
- def structure(self) -> Optional[Structure]:
27
+ def structure(self) -> Structure | None:
27
28
  return self._structure
28
29
 
29
30
  @staticmethod
30
- def from_et(et_element: ElementTree.Element,
31
- doc_frags: List[OdxDocFragment]) -> "MultiplexerCase":
31
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MultiplexerCase":
32
32
  """Reads a case for a Multiplexer."""
33
- kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
34
- structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
33
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
34
+ structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), context)
35
35
  structure_snref = None
36
36
  if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
37
37
  structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
38
38
 
39
39
  lower_limit = Limit.limit_from_et(
40
40
  odxrequire(et_element.find("LOWER-LIMIT")),
41
- doc_frags,
41
+ context,
42
42
  value_type=None,
43
43
  )
44
44
  upper_limit = Limit.limit_from_et(
45
45
  odxrequire(et_element.find("UPPER-LIMIT")),
46
- doc_frags,
46
+ context,
47
47
  value_type=None,
48
48
  )
49
49
 
@@ -54,7 +54,7 @@ class MultiplexerCase(NamedElement):
54
54
  upper_limit=upper_limit,
55
55
  **kwargs)
56
56
 
57
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
57
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
58
58
  return {}
59
59
 
60
60
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -1,33 +1,34 @@
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
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .element import NamedElement
7
7
  from .exceptions import odxrequire
8
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
9
10
  from .snrefcontext import SnRefContext
10
11
  from .structure import Structure
11
12
  from .utils import dataclass_fields_asdict
12
13
 
13
14
 
14
- @dataclass
15
+ @dataclass(kw_only=True)
15
16
  class MultiplexerDefaultCase(NamedElement):
16
17
  """This class represents a Default Case, which is selected when there are no cases defined in the Multiplexer."""
17
- structure_ref: Optional[OdxLinkRef]
18
- structure_snref: Optional[str]
18
+ structure_ref: OdxLinkRef | None = None
19
+ structure_snref: str | None = None
19
20
 
20
21
  @property
21
- def structure(self) -> Optional[Structure]:
22
+ def structure(self) -> Structure | None:
22
23
  return self._structure
23
24
 
24
25
  @staticmethod
25
26
  def from_et(et_element: ElementTree.Element,
26
- doc_frags: List[OdxDocFragment]) -> "MultiplexerDefaultCase":
27
+ context: OdxDocContext) -> "MultiplexerDefaultCase":
27
28
  """Reads a default case for a multiplexer."""
28
- kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
29
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
29
30
 
30
- structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
31
+ structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), context)
31
32
  structure_snref = None
32
33
  if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
33
34
  structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
@@ -35,7 +36,7 @@ class MultiplexerDefaultCase(NamedElement):
35
36
  return MultiplexerDefaultCase(
36
37
  structure_ref=structure_ref, structure_snref=structure_snref, **kwargs)
37
38
 
38
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
39
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
39
40
  return {}
40
41
 
41
42
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -1,21 +1,22 @@
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
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .dataobjectproperty import DataObjectProperty
7
7
  from .exceptions import odxrequire
8
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
10
  from .snrefcontext import SnRefContext
10
11
 
11
12
 
12
- @dataclass
13
+ @dataclass(kw_only=True)
13
14
  class MultiplexerSwitchKey:
14
15
  """
15
16
  The object that determines the case to be used by a multiplexer
16
17
  """
17
18
  byte_position: int
18
- bit_position: Optional[int]
19
+ bit_position: int | None = None
19
20
  dop_ref: OdxLinkRef
20
21
 
21
22
  @property
@@ -23,12 +24,11 @@ class MultiplexerSwitchKey:
23
24
  return self._dop
24
25
 
25
26
  @staticmethod
26
- def from_et(et_element: ElementTree.Element,
27
- doc_frags: List[OdxDocFragment]) -> "MultiplexerSwitchKey":
27
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "MultiplexerSwitchKey":
28
28
  byte_position = int(odxrequire(et_element.findtext("BYTE-POSITION")))
29
29
  bit_position_str = et_element.findtext("BIT-POSITION")
30
30
  bit_position = int(bit_position_str) if bit_position_str is not None else None
31
- dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), doc_frags))
31
+ dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), context))
32
32
 
33
33
  return MultiplexerSwitchKey(
34
34
  byte_position=byte_position,
@@ -36,7 +36,7 @@ class MultiplexerSwitchKey:
36
36
  dop_ref=dop_ref,
37
37
  )
38
38
 
39
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
39
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
40
40
  return {}
41
41
 
42
42
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
odxtools/nameditemlist.py CHANGED
@@ -1,10 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
3
  import typing
4
+ from collections.abc import Collection, Iterable
4
5
  from copy import deepcopy
5
6
  from keyword import iskeyword
6
- from typing import (Any, Collection, Dict, Iterable, List, Optional, SupportsIndex, Tuple, TypeVar,
7
- Union, cast, overload, runtime_checkable)
7
+ from typing import Any, SupportsIndex, TypeVar, cast, overload, runtime_checkable
8
8
 
9
9
  from .exceptions import odxraise
10
10
 
@@ -21,7 +21,7 @@ T = TypeVar("T")
21
21
  TNamed = TypeVar("TNamed", bound=OdxNamed)
22
22
 
23
23
 
24
- class ItemAttributeList(List[T]):
24
+ class ItemAttributeList(list[T]):
25
25
  """A list that provides direct access to its items as named attributes.
26
26
 
27
27
  This is a hybrid between a list and a user-defined object: One can
@@ -35,8 +35,8 @@ class ItemAttributeList(List[T]):
35
35
  returned by the item-to-name function are valid identifiers in python.
36
36
  """
37
37
 
38
- def __init__(self, input_list: Optional[Iterable[T]] = None) -> None:
39
- self._item_dict: Dict[str, T] = {}
38
+ def __init__(self, input_list: Iterable[T] | None = None) -> None:
39
+ self._item_dict: dict[str, T] = {}
40
40
 
41
41
  if input_list is not None:
42
42
  for item in input_list:
@@ -121,10 +121,10 @@ class ItemAttributeList(List[T]):
121
121
  def values(self) -> Collection[T]:
122
122
  return self._item_dict.values()
123
123
 
124
- def items(self) -> Collection[Tuple[str, T]]:
124
+ def items(self) -> Collection[tuple[str, T]]:
125
125
  return self._item_dict.items()
126
126
 
127
- def __dir__(self) -> Dict[str, Any]:
127
+ def __dir__(self) -> dict[str, Any]:
128
128
  result = dict(self.__dict__)
129
129
  result.update(self._item_dict)
130
130
  return result
@@ -138,10 +138,10 @@ class ItemAttributeList(List[T]):
138
138
  ...
139
139
 
140
140
  @overload
141
- def __getitem__(self, key: slice) -> List[T]:
141
+ def __getitem__(self, key: slice) -> list[T]:
142
142
  ...
143
143
 
144
- def __getitem__(self, key: Union[SupportsIndex, str, slice]) -> Union[T, List[T]]:
144
+ def __getitem__(self, key: SupportsIndex | str | slice) -> T | list[T]:
145
145
  if isinstance(key, (SupportsIndex, slice)):
146
146
  return super().__getitem__(key)
147
147
  else:
@@ -153,13 +153,13 @@ class ItemAttributeList(List[T]):
153
153
 
154
154
  return self._item_dict[key]
155
155
 
156
- def get(self, key: Union[int, str], default: Optional[T] = None) -> Optional[T]:
156
+ def get(self, key: int | str, default: T | None = None) -> T | None:
157
157
  if isinstance(key, int):
158
158
  if 0 <= key and key < len(self):
159
159
  return super().__getitem__(key)
160
160
  return default
161
161
  else:
162
- return cast(Optional[T], self._item_dict.get(key, default))
162
+ return cast(T | None, self._item_dict.get(key, default))
163
163
 
164
164
  def __eq__(self, other: object) -> bool:
165
165
  """
@@ -179,7 +179,7 @@ class ItemAttributeList(List[T]):
179
179
  def __copy__(self) -> Any:
180
180
  return self.__class__(list(self))
181
181
 
182
- def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
182
+ def __deepcopy__(self, memo: dict[int, Any]) -> Any:
183
183
  cls = self.__class__
184
184
  result = cls.__new__(cls)
185
185
  memo[id(self)] = result
@@ -189,7 +189,7 @@ class ItemAttributeList(List[T]):
189
189
 
190
190
  return result
191
191
 
192
- def __reduce__(self) -> Tuple[Any, ...]:
192
+ def __reduce__(self) -> tuple[Any, ...]:
193
193
  """Support for Python's pickle protocol.
194
194
  This method ensures that the object can be reconstructed with its current state,
195
195
  using its class and the list of items it contains.