odxtools 10.0.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 (174) hide show
  1. odxtools/additionalaudience.py +5 -5
  2. odxtools/admindata.py +10 -9
  3. odxtools/audience.py +15 -15
  4. odxtools/basecomparam.py +7 -6
  5. odxtools/basevariantpattern.py +7 -7
  6. odxtools/basicstructure.py +9 -9
  7. odxtools/cli/_print_utils.py +1 -1
  8. odxtools/cli/browse.py +1 -1
  9. odxtools/cli/list.py +1 -1
  10. odxtools/commrelation.py +14 -13
  11. odxtools/companydata.py +11 -11
  12. odxtools/companydocinfo.py +11 -13
  13. odxtools/companyrevisioninfo.py +7 -7
  14. odxtools/companyspecificinfo.py +9 -11
  15. odxtools/comparam.py +6 -5
  16. odxtools/comparaminstance.py +10 -10
  17. odxtools/comparamspec.py +8 -9
  18. odxtools/comparamsubset.py +14 -22
  19. odxtools/complexcomparam.py +10 -10
  20. odxtools/complexdop.py +1 -1
  21. odxtools/compositecodec.py +3 -3
  22. odxtools/compumethods/compucodecompumethod.py +4 -4
  23. odxtools/compumethods/compuconst.py +3 -3
  24. odxtools/compumethods/compudefaultvalue.py +2 -2
  25. odxtools/compumethods/compuinternaltophys.py +11 -10
  26. odxtools/compumethods/compumethod.py +8 -7
  27. odxtools/compumethods/compuphystointernal.py +11 -10
  28. odxtools/compumethods/compurationalcoeffs.py +6 -6
  29. odxtools/compumethods/compuscale.py +14 -14
  30. odxtools/compumethods/createanycompumethod.py +12 -12
  31. odxtools/compumethods/identicalcompumethod.py +4 -4
  32. odxtools/compumethods/limit.py +8 -8
  33. odxtools/compumethods/linearcompumethod.py +4 -4
  34. odxtools/compumethods/linearsegment.py +8 -8
  35. odxtools/compumethods/ratfunccompumethod.py +4 -4
  36. odxtools/compumethods/ratfuncsegment.py +8 -8
  37. odxtools/compumethods/scalelinearcompumethod.py +5 -5
  38. odxtools/compumethods/scaleratfunccompumethod.py +4 -4
  39. odxtools/compumethods/tabintpcompumethod.py +12 -12
  40. odxtools/compumethods/texttablecompumethod.py +4 -4
  41. odxtools/createanycomparam.py +4 -4
  42. odxtools/createanydiagcodedtype.py +7 -7
  43. odxtools/database.py +28 -26
  44. odxtools/dataobjectproperty.py +15 -16
  45. odxtools/description.py +7 -7
  46. odxtools/determinenumberofitems.py +6 -5
  47. odxtools/diagcodedtype.py +6 -6
  48. odxtools/diagcomm.py +26 -27
  49. odxtools/diagdatadictionaryspec.py +34 -34
  50. odxtools/diaglayercontainer.py +32 -31
  51. odxtools/diaglayers/basevariant.py +5 -4
  52. odxtools/diaglayers/basevariantraw.py +18 -19
  53. odxtools/diaglayers/diaglayer.py +5 -4
  54. odxtools/diaglayers/diaglayerraw.py +39 -48
  55. odxtools/diaglayers/ecushareddata.py +6 -6
  56. odxtools/diaglayers/ecushareddataraw.py +11 -12
  57. odxtools/diaglayers/ecuvariant.py +5 -4
  58. odxtools/diaglayers/ecuvariantraw.py +17 -18
  59. odxtools/diaglayers/functionalgroup.py +5 -5
  60. odxtools/diaglayers/functionalgroupraw.py +13 -14
  61. odxtools/diaglayers/hierarchyelement.py +9 -9
  62. odxtools/diaglayers/hierarchyelementraw.py +8 -9
  63. odxtools/diaglayers/protocol.py +4 -4
  64. odxtools/diaglayers/protocolraw.py +10 -11
  65. odxtools/diagnostictroublecode.py +12 -14
  66. odxtools/diagservice.py +19 -18
  67. odxtools/diagvariable.py +19 -20
  68. odxtools/docrevision.py +14 -13
  69. odxtools/dopbase.py +10 -11
  70. odxtools/dtcconnector.py +6 -5
  71. odxtools/dtcdop.py +15 -15
  72. odxtools/dynamicendmarkerfield.py +6 -6
  73. odxtools/dynamiclengthfield.py +6 -6
  74. odxtools/dyndefinedspec.py +7 -7
  75. odxtools/dynenddopref.py +7 -7
  76. odxtools/dyniddefmodeinfo.py +17 -17
  77. odxtools/ecuvariantpattern.py +6 -7
  78. odxtools/element.py +12 -12
  79. odxtools/endofpdufield.py +6 -7
  80. odxtools/envdataconnector.py +6 -6
  81. odxtools/environmentdata.py +7 -8
  82. odxtools/environmentdatadescription.py +13 -12
  83. odxtools/externalaccessmethod.py +4 -5
  84. odxtools/externaldoc.py +4 -4
  85. odxtools/field.py +12 -11
  86. odxtools/functionalclass.py +7 -7
  87. odxtools/inputparam.py +9 -8
  88. odxtools/internalconstr.py +10 -10
  89. odxtools/leadinglengthinfotype.py +5 -6
  90. odxtools/library.py +7 -6
  91. odxtools/linkeddtcdop.py +7 -6
  92. odxtools/matchingbasevariantparameter.py +5 -5
  93. odxtools/matchingparameter.py +6 -6
  94. odxtools/message.py +1 -1
  95. odxtools/minmaxlengthtype.py +6 -7
  96. odxtools/modification.py +5 -4
  97. odxtools/multiplexer.py +48 -12
  98. odxtools/multiplexercase.py +10 -10
  99. odxtools/multiplexerdefaultcase.py +8 -7
  100. odxtools/multiplexerswitchkey.py +6 -6
  101. odxtools/nameditemlist.py +1 -1
  102. odxtools/negoutputparam.py +6 -6
  103. odxtools/odxcategory.py +12 -24
  104. odxtools/odxdoccontext.py +16 -0
  105. odxtools/odxlink.py +11 -12
  106. odxtools/odxtypes.py +3 -3
  107. odxtools/outputparam.py +7 -6
  108. odxtools/parameters/codedconstparameter.py +6 -6
  109. odxtools/parameters/createanyparameter.py +15 -15
  110. odxtools/parameters/dynamicparameter.py +4 -5
  111. odxtools/parameters/lengthkeyparameter.py +6 -6
  112. odxtools/parameters/matchingrequestparameter.py +4 -4
  113. odxtools/parameters/nrcconstparameter.py +8 -8
  114. odxtools/parameters/parameter.py +12 -13
  115. odxtools/parameters/parameterwithdop.py +9 -9
  116. odxtools/parameters/physicalconstantparameter.py +5 -4
  117. odxtools/parameters/reservedparameter.py +4 -5
  118. odxtools/parameters/systemparameter.py +4 -5
  119. odxtools/parameters/tableentryparameter.py +6 -6
  120. odxtools/parameters/tablekeyparameter.py +12 -12
  121. odxtools/parameters/tablestructparameter.py +9 -9
  122. odxtools/parameters/valueparameter.py +6 -6
  123. odxtools/paramlengthinfotype.py +6 -7
  124. odxtools/parentref.py +12 -10
  125. odxtools/physicaldimension.py +12 -12
  126. odxtools/physicaltype.py +5 -5
  127. odxtools/posresponsesuppressible.py +11 -11
  128. odxtools/preconditionstateref.py +8 -8
  129. odxtools/progcode.py +9 -8
  130. odxtools/protstack.py +8 -7
  131. odxtools/relateddiagcommref.py +5 -5
  132. odxtools/relateddoc.py +8 -7
  133. odxtools/request.py +12 -13
  134. odxtools/response.py +12 -13
  135. odxtools/scaleconstr.py +8 -8
  136. odxtools/singleecujob.py +14 -13
  137. odxtools/snrefcontext.py +1 -1
  138. odxtools/specialdata.py +6 -5
  139. odxtools/specialdatagroup.py +13 -13
  140. odxtools/specialdatagroupcaption.py +5 -4
  141. odxtools/standardlengthtype.py +5 -13
  142. odxtools/state.py +5 -4
  143. odxtools/statechart.py +10 -9
  144. odxtools/statemachine.py +2 -2
  145. odxtools/statetransition.py +7 -7
  146. odxtools/statetransitionref.py +11 -11
  147. odxtools/staticfield.py +5 -4
  148. odxtools/structure.py +5 -5
  149. odxtools/subcomponent.py +18 -16
  150. odxtools/subcomponentparamconnector.py +8 -7
  151. odxtools/subcomponentpattern.py +7 -7
  152. odxtools/swvariable.py +6 -6
  153. odxtools/table.py +20 -21
  154. odxtools/tablediagcommconnector.py +7 -6
  155. odxtools/tablerow.py +57 -36
  156. odxtools/tablerowconnector.py +6 -6
  157. odxtools/teammember.py +14 -13
  158. odxtools/templates/macros/printParentRef.xml.jinja2 +3 -1
  159. odxtools/text.py +4 -4
  160. odxtools/unit.py +9 -8
  161. odxtools/unitgroup.py +9 -8
  162. odxtools/unitspec.py +15 -16
  163. odxtools/variablegroup.py +4 -5
  164. odxtools/variantpattern.py +3 -4
  165. odxtools/version.py +2 -2
  166. odxtools/writepdxfile.py +0 -19
  167. odxtools/xdoc.py +11 -10
  168. {odxtools-10.0.0.dist-info → odxtools-10.1.0.dist-info}/METADATA +1 -1
  169. odxtools-10.1.0.dist-info/RECORD +265 -0
  170. {odxtools-10.0.0.dist-info → odxtools-10.1.0.dist-info}/WHEEL +1 -1
  171. odxtools-10.0.0.dist-info/RECORD +0 -264
  172. {odxtools-10.0.0.dist-info → odxtools-10.1.0.dist-info}/entry_points.txt +0 -0
  173. {odxtools-10.0.0.dist-info → odxtools-10.1.0.dist-info}/licenses/LICENSE +0 -0
  174. {odxtools-10.0.0.dist-info → odxtools-10.1.0.dist-info}/top_level.txt +0 -0
odxtools/library.py CHANGED
@@ -5,12 +5,13 @@ from xml.etree import ElementTree
5
5
 
6
6
  from .element import IdentifiableElement
7
7
  from .exceptions import odxraise, odxrequire
8
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
9
10
  from .snrefcontext import SnRefContext
10
11
  from .utils import dataclass_fields_asdict
11
12
 
12
13
 
13
- @dataclass
14
+ @dataclass(kw_only=True)
14
15
  class Library(IdentifiableElement):
15
16
  """
16
17
  A library defines a shared library used for single ECU jobs etc.
@@ -19,19 +20,19 @@ class Library(IdentifiableElement):
19
20
  """
20
21
 
21
22
  code_file: str
22
- encryption: str | None
23
+ encryption: str | None = None
23
24
  syntax: str
24
25
  revision: str
25
- entrypoint: str | None
26
+ entrypoint: str | None = None
26
27
 
27
28
  @property
28
29
  def code(self) -> bytes:
29
30
  return self._code
30
31
 
31
32
  @staticmethod
32
- def from_et(et_element: ElementTree.Element, doc_frags: list[OdxDocFragment]) -> "Library":
33
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Library":
33
34
 
34
- kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
35
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
35
36
 
36
37
  code_file = odxrequire(et_element.findtext("CODE-FILE"))
37
38
  encryption = et_element.findtext("ENCRYPTION")
odxtools/linkeddtcdop.py CHANGED
@@ -1,21 +1,22 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from typing import TYPE_CHECKING, Any
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .diagnostictroublecode import DiagnosticTroubleCode
7
7
  from .exceptions import odxrequire
8
8
  from .nameditemlist import NamedItemList
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 .snrefcontext import SnRefContext
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from .dtcdop import DtcDop
14
15
 
15
16
 
16
- @dataclass
17
+ @dataclass(kw_only=True)
17
18
  class LinkedDtcDop:
18
- not_inherited_dtc_snrefs: list[str]
19
+ not_inherited_dtc_snrefs: list[str] = field(default_factory=list)
19
20
  dtc_dop_ref: OdxLinkRef
20
21
 
21
22
  @property
@@ -31,14 +32,14 @@ class LinkedDtcDop:
31
32
  return self._dtc_dop.short_name
32
33
 
33
34
  @staticmethod
34
- def from_et(et_element: ElementTree.Element, doc_frags: list[OdxDocFragment]) -> "LinkedDtcDop":
35
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "LinkedDtcDop":
35
36
  not_inherited_dtc_snrefs = [
36
37
  odxrequire(el.get("SHORT-NAME"))
37
38
  for el in et_element.iterfind("NOT-INHERITED-DTC-SNREFS/"
38
39
  "NOT-INHERITED-DTC-SNREF")
39
40
  ]
40
41
 
41
- dtc_dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DTC-DOP-REF"), doc_frags))
42
+ dtc_dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DTC-DOP-REF"), context))
42
43
 
43
44
  return LinkedDtcDop(
44
45
  not_inherited_dtc_snrefs=not_inherited_dtc_snrefs, dtc_dop_ref=dtc_dop_ref)
@@ -3,12 +3,12 @@ from dataclasses import dataclass
3
3
  from xml.etree import ElementTree
4
4
 
5
5
  from .matchingparameter import MatchingParameter
6
- from .odxlink import OdxDocFragment
6
+ from .odxdoccontext import OdxDocContext
7
7
  from .odxtypes import odxstr_to_bool
8
8
  from .utils import dataclass_fields_asdict
9
9
 
10
10
 
11
- @dataclass
11
+ @dataclass(kw_only=True)
12
12
  class MatchingBaseVariantParameter(MatchingParameter):
13
13
  """A description of a parameter used for base variant matching.
14
14
 
@@ -17,7 +17,7 @@ class MatchingBaseVariantParameter(MatchingParameter):
17
17
  additional subtag `USE-PHYSICAL-ADDRESSING`.
18
18
  """
19
19
 
20
- use_physical_addressing_raw: bool | None
20
+ use_physical_addressing_raw: bool | None = None
21
21
 
22
22
  @property
23
23
  def use_physical_addressing(self) -> bool:
@@ -25,9 +25,9 @@ class MatchingBaseVariantParameter(MatchingParameter):
25
25
 
26
26
  @staticmethod
27
27
  def from_et(et_element: ElementTree.Element,
28
- doc_frags: list[OdxDocFragment]) -> "MatchingBaseVariantParameter":
28
+ context: OdxDocContext) -> "MatchingBaseVariantParameter":
29
29
 
30
- kwargs = dataclass_fields_asdict(MatchingParameter.from_et(et_element, doc_frags))
30
+ kwargs = dataclass_fields_asdict(MatchingParameter.from_et(et_element, context))
31
31
 
32
32
  use_physical_addressing_raw = odxstr_to_bool(et_element.findtext("USE-PHYSICAL-ADDRESSING"))
33
33
 
@@ -7,12 +7,13 @@ 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: str | None
38
- out_param_if_snpathref: str | None
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(
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
 
@@ -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: int | None
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:
@@ -77,7 +76,7 @@ class MinMaxLengthType(DiagCodedType):
77
76
  @override
78
77
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
79
78
 
80
- if not isinstance(internal_value, str | BytesTypes):
79
+ if not isinstance(internal_value, (str, BytesTypes)):
81
80
  odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
82
81
  EncodeError)
83
82
 
odxtools/modification.py CHANGED
@@ -4,17 +4,18 @@ 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: str | None
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
 
odxtools/multiplexer.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from typing import Any, cast
4
4
  from xml.etree import ElementTree
5
5
 
@@ -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: MultiplexerDefaultCase | None
34
- cases: NamedItemList[MultiplexerCase]
35
- is_visible_raw: bool | None
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
 
@@ -118,7 +119,7 @@ class Multiplexer(ComplexDop):
118
119
  orig_origin = encode_state.origin_byte_position
119
120
  encode_state.origin_byte_position = encode_state.cursor_byte_position
120
121
 
121
- if isinstance(physical_value, list | tuple) and len(physical_value) == 2:
122
+ if isinstance(physical_value, (list, tuple)) and len(physical_value) == 2:
122
123
  case_spec, case_value = physical_value
123
124
  elif isinstance(physical_value, dict) and len(physical_value) == 1:
124
125
  case_spec, case_value = next(iter(physical_value.items()))
@@ -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)
@@ -6,19 +6,20 @@ from xml.etree import ElementTree
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: OdxLinkRef | None
21
- structure_snref: str | None
21
+ structure_ref: OdxLinkRef | None = None
22
+ structure_snref: str | None = None
22
23
  lower_limit: Limit
23
24
  upper_limit: Limit
24
25
 
@@ -27,23 +28,22 @@ class MultiplexerCase(NamedElement):
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
 
@@ -5,17 +5,18 @@ 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: OdxLinkRef | None
18
- structure_snref: str | None
18
+ structure_ref: OdxLinkRef | None = None
19
+ structure_snref: str | None = None
19
20
 
20
21
  @property
21
22
  def structure(self) -> Structure | None:
@@ -23,11 +24,11 @@ class MultiplexerDefaultCase(NamedElement):
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"))
@@ -5,17 +5,18 @@ 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: int | None
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,
odxtools/nameditemlist.py CHANGED
@@ -142,7 +142,7 @@ class ItemAttributeList(list[T]):
142
142
  ...
143
143
 
144
144
  def __getitem__(self, key: SupportsIndex | str | slice) -> T | list[T]:
145
- if isinstance(key, SupportsIndex | slice):
145
+ if isinstance(key, (SupportsIndex, slice)):
146
146
  return super().__getitem__(key)
147
147
  else:
148
148
  return self._item_dict[key]
@@ -6,12 +6,13 @@ from xml.etree import ElementTree
6
6
  from .dopbase import DopBase
7
7
  from .element import NamedElement
8
8
  from .exceptions import odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
11
  from .snrefcontext import SnRefContext
11
12
  from .utils import dataclass_fields_asdict
12
13
 
13
14
 
14
- @dataclass
15
+ @dataclass(kw_only=True)
15
16
  class NegOutputParam(NamedElement):
16
17
  dop_base_ref: OdxLinkRef
17
18
 
@@ -21,11 +22,10 @@ class NegOutputParam(NamedElement):
21
22
  return self._dop
22
23
 
23
24
  @staticmethod
24
- def from_et(et_element: ElementTree.Element,
25
- doc_frags: list[OdxDocFragment]) -> "NegOutputParam":
25
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "NegOutputParam":
26
26
 
27
- kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
28
- dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
27
+ kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
28
+ dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), context))
29
29
 
30
30
  return NegOutputParam(dop_base_ref=dop_base_ref, **kwargs)
31
31
 
odxtools/odxcategory.py CHANGED
@@ -1,14 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from typing import TYPE_CHECKING, Any
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .admindata import AdminData
7
7
  from .companydata import CompanyData
8
8
  from .element import IdentifiableElement
9
- from .exceptions import odxrequire
10
9
  from .nameditemlist import NamedItemList
11
- from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
10
+ from .odxdoccontext import OdxDocContext
11
+ from .odxlink import OdxLinkDatabase, OdxLinkId
12
12
  from .snrefcontext import SnRefContext
13
13
  from .specialdatagroup import SpecialDataGroup
14
14
  from .utils import dataclass_fields_asdict
@@ -17,37 +17,25 @@ if TYPE_CHECKING:
17
17
  from .database import Database
18
18
 
19
19
 
20
- @dataclass
20
+ @dataclass(kw_only=True)
21
21
  class OdxCategory(IdentifiableElement):
22
22
  """This is the base class for all top-level container classes in ODX"""
23
23
 
24
- admin_data: AdminData | None
25
- company_datas: NamedItemList[CompanyData]
26
- sdgs: list[SpecialDataGroup]
24
+ admin_data: AdminData | None = None
25
+ company_datas: NamedItemList[CompanyData] = field(default_factory=NamedItemList)
26
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
27
27
 
28
28
  @staticmethod
29
- def from_et(et_element: ElementTree.Element, doc_frags: list[OdxDocFragment]) -> "OdxCategory":
30
- raise Exception("Calling `._from_et()` is not allowed for OdxCategory. "
31
- "Use `OdxCategory.category_from_et()`!")
29
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "OdxCategory":
32
30
 
33
- @staticmethod
34
- def category_from_et(et_element: ElementTree.Element, doc_frags: list[OdxDocFragment], *,
35
- doc_type: DocType) -> "OdxCategory":
36
-
37
- short_name = odxrequire(et_element.findtext("SHORT-NAME"))
38
- # create the current ODX "document fragment" (description of the
39
- # current document for references and IDs)
40
- doc_frags = [OdxDocFragment(short_name, doc_type)]
41
- kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
31
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
42
32
 
43
- admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
33
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), context)
44
34
  company_datas = NamedItemList([
45
- CompanyData.from_et(cde, doc_frags)
35
+ CompanyData.from_et(cde, context)
46
36
  for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
47
37
  ])
48
- sdgs = [
49
- SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
50
- ]
38
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
51
39
 
52
40
  return OdxCategory(admin_data=admin_data, company_datas=company_datas, sdgs=sdgs, **kwargs)
53
41
 
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from packaging.version import Version
5
+
6
+ if TYPE_CHECKING:
7
+ from odxtools.odxlink import OdxDocFragment
8
+
9
+
10
+ @dataclass(slots=True, frozen=True)
11
+ class OdxDocContext:
12
+ version: Version
13
+
14
+ # the doc_fragments are either tuple(doc_frag(category),)
15
+ # or tuple(doc_frag(category), doc_frag(diag_layer))
16
+ doc_fragments: tuple["OdxDocFragment"] | tuple["OdxDocFragment", "OdxDocFragment"]