odxtools 6.6.1__py3-none-any.whl → 9.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. odxtools/__init__.py +7 -5
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +10 -13
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +55 -241
  7. odxtools/cli/_parser_utils.py +16 -1
  8. odxtools/cli/_print_utils.py +169 -134
  9. odxtools/cli/browse.py +127 -103
  10. odxtools/cli/compare.py +114 -87
  11. odxtools/cli/decode.py +2 -1
  12. odxtools/cli/dummy_sub_parser.py +3 -1
  13. odxtools/cli/find.py +2 -1
  14. odxtools/cli/list.py +26 -16
  15. odxtools/cli/main.py +1 -0
  16. odxtools/cli/snoop.py +32 -6
  17. odxtools/codec.py +211 -0
  18. odxtools/commrelation.py +122 -0
  19. odxtools/companydata.py +5 -7
  20. odxtools/companydocinfo.py +7 -8
  21. odxtools/companyrevisioninfo.py +3 -5
  22. odxtools/companyspecificinfo.py +8 -9
  23. odxtools/comparam.py +4 -6
  24. odxtools/comparaminstance.py +14 -14
  25. odxtools/comparamspec.py +16 -54
  26. odxtools/comparamsubset.py +22 -62
  27. odxtools/complexcomparam.py +5 -7
  28. odxtools/compumethods/compucodecompumethod.py +63 -0
  29. odxtools/compumethods/compuconst.py +31 -0
  30. odxtools/compumethods/compudefaultvalue.py +27 -0
  31. odxtools/compumethods/compuinternaltophys.py +56 -0
  32. odxtools/compumethods/compuinversevalue.py +7 -0
  33. odxtools/compumethods/compumethod.py +94 -15
  34. odxtools/compumethods/compuphystointernal.py +56 -0
  35. odxtools/compumethods/compurationalcoeffs.py +20 -9
  36. odxtools/compumethods/compuscale.py +67 -32
  37. odxtools/compumethods/createanycompumethod.py +31 -172
  38. odxtools/compumethods/identicalcompumethod.py +31 -6
  39. odxtools/compumethods/limit.py +70 -36
  40. odxtools/compumethods/linearcompumethod.py +70 -181
  41. odxtools/compumethods/linearsegment.py +190 -0
  42. odxtools/compumethods/ratfunccompumethod.py +106 -0
  43. odxtools/compumethods/ratfuncsegment.py +87 -0
  44. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  45. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  46. odxtools/compumethods/tabintpcompumethod.py +123 -92
  47. odxtools/compumethods/texttablecompumethod.py +117 -57
  48. odxtools/createanydiagcodedtype.py +10 -67
  49. odxtools/database.py +167 -87
  50. odxtools/dataobjectproperty.py +25 -32
  51. odxtools/decodestate.py +14 -17
  52. odxtools/description.py +47 -0
  53. odxtools/determinenumberofitems.py +4 -5
  54. odxtools/diagcodedtype.py +37 -106
  55. odxtools/diagcomm.py +24 -12
  56. odxtools/diagdatadictionaryspec.py +120 -96
  57. odxtools/diaglayercontainer.py +46 -54
  58. odxtools/diaglayers/basevariant.py +128 -0
  59. odxtools/diaglayers/basevariantraw.py +123 -0
  60. odxtools/diaglayers/diaglayer.py +432 -0
  61. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  62. odxtools/diaglayers/diaglayertype.py +42 -0
  63. odxtools/diaglayers/ecushareddata.py +96 -0
  64. odxtools/diaglayers/ecushareddataraw.py +87 -0
  65. odxtools/diaglayers/ecuvariant.py +124 -0
  66. odxtools/diaglayers/ecuvariantraw.py +129 -0
  67. odxtools/diaglayers/functionalgroup.py +110 -0
  68. odxtools/diaglayers/functionalgroupraw.py +106 -0
  69. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +273 -472
  70. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  71. odxtools/diaglayers/protocol.py +64 -0
  72. odxtools/diaglayers/protocolraw.py +91 -0
  73. odxtools/diagnostictroublecode.py +8 -9
  74. odxtools/diagservice.py +57 -44
  75. odxtools/diagvariable.py +113 -0
  76. odxtools/docrevision.py +5 -7
  77. odxtools/dopbase.py +15 -15
  78. odxtools/dtcdop.py +170 -50
  79. odxtools/dynamicendmarkerfield.py +134 -0
  80. odxtools/dynamiclengthfield.py +47 -42
  81. odxtools/dyndefinedspec.py +177 -0
  82. odxtools/dynenddopref.py +38 -0
  83. odxtools/ecuvariantmatcher.py +6 -7
  84. odxtools/element.py +13 -15
  85. odxtools/encodestate.py +199 -22
  86. odxtools/endofpdufield.py +31 -18
  87. odxtools/environmentdata.py +8 -1
  88. odxtools/environmentdatadescription.py +198 -36
  89. odxtools/exceptions.py +11 -2
  90. odxtools/field.py +10 -10
  91. odxtools/functionalclass.py +3 -5
  92. odxtools/inputparam.py +3 -12
  93. odxtools/internalconstr.py +14 -5
  94. odxtools/isotp_state_machine.py +14 -6
  95. odxtools/leadinglengthinfotype.py +37 -18
  96. odxtools/library.py +66 -0
  97. odxtools/loadfile.py +64 -0
  98. odxtools/matchingparameter.py +3 -3
  99. odxtools/message.py +0 -7
  100. odxtools/minmaxlengthtype.py +61 -33
  101. odxtools/modification.py +3 -5
  102. odxtools/multiplexer.py +135 -75
  103. odxtools/multiplexercase.py +39 -18
  104. odxtools/multiplexerdefaultcase.py +15 -12
  105. odxtools/multiplexerswitchkey.py +4 -5
  106. odxtools/nameditemlist.py +33 -8
  107. odxtools/negoutputparam.py +3 -5
  108. odxtools/odxcategory.py +83 -0
  109. odxtools/odxlink.py +62 -53
  110. odxtools/odxtypes.py +93 -8
  111. odxtools/outputparam.py +5 -16
  112. odxtools/parameterinfo.py +219 -61
  113. odxtools/parameters/codedconstparameter.py +45 -32
  114. odxtools/parameters/createanyparameter.py +19 -193
  115. odxtools/parameters/dynamicparameter.py +25 -4
  116. odxtools/parameters/lengthkeyparameter.py +83 -25
  117. odxtools/parameters/matchingrequestparameter.py +48 -18
  118. odxtools/parameters/nrcconstparameter.py +76 -54
  119. odxtools/parameters/parameter.py +97 -73
  120. odxtools/parameters/parameterwithdop.py +41 -38
  121. odxtools/parameters/physicalconstantparameter.py +41 -20
  122. odxtools/parameters/reservedparameter.py +36 -18
  123. odxtools/parameters/systemparameter.py +74 -7
  124. odxtools/parameters/tableentryparameter.py +47 -7
  125. odxtools/parameters/tablekeyparameter.py +142 -55
  126. odxtools/parameters/tablestructparameter.py +79 -58
  127. odxtools/parameters/valueparameter.py +39 -21
  128. odxtools/paramlengthinfotype.py +56 -33
  129. odxtools/parentref.py +20 -3
  130. odxtools/physicaldimension.py +3 -8
  131. odxtools/progcode.py +26 -11
  132. odxtools/protstack.py +3 -5
  133. odxtools/py.typed +0 -0
  134. odxtools/relateddoc.py +7 -9
  135. odxtools/request.py +120 -10
  136. odxtools/response.py +123 -23
  137. odxtools/scaleconstr.py +14 -8
  138. odxtools/servicebinner.py +1 -1
  139. odxtools/singleecujob.py +12 -10
  140. odxtools/snrefcontext.py +29 -0
  141. odxtools/specialdata.py +3 -5
  142. odxtools/specialdatagroup.py +7 -9
  143. odxtools/specialdatagroupcaption.py +3 -6
  144. odxtools/standardlengthtype.py +80 -14
  145. odxtools/state.py +3 -5
  146. odxtools/statechart.py +13 -19
  147. odxtools/statetransition.py +8 -18
  148. odxtools/staticfield.py +107 -0
  149. odxtools/subcomponent.py +288 -0
  150. odxtools/swvariable.py +21 -0
  151. odxtools/table.py +9 -9
  152. odxtools/tablerow.py +30 -15
  153. odxtools/teammember.py +3 -5
  154. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  155. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  156. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  157. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  158. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  159. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  160. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  161. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  162. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  163. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  164. odxtools/templates/macros/printDOP.xml.jinja2 +27 -137
  165. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  166. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  167. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  168. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  169. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  170. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  171. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  172. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  173. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  174. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  175. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  176. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  178. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  180. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  181. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  182. odxtools/templates/macros/printMux.xml.jinja2 +5 -3
  183. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  184. odxtools/templates/macros/printParam.xml.jinja2 +18 -19
  185. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  187. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  189. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  190. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  191. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  192. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  193. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  194. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  195. odxtools/templates/macros/printStaticField.xml.jinja2 +15 -0
  196. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  197. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  198. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  199. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  200. odxtools/uds.py +2 -10
  201. odxtools/unit.py +4 -8
  202. odxtools/unitgroup.py +3 -5
  203. odxtools/unitspec.py +17 -17
  204. odxtools/utils.py +38 -20
  205. odxtools/variablegroup.py +32 -0
  206. odxtools/version.py +2 -2
  207. odxtools/{write_pdx_file.py → writepdxfile.py} +22 -12
  208. odxtools/xdoc.py +3 -5
  209. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/METADATA +44 -33
  210. odxtools-9.3.0.dist-info/RECORD +228 -0
  211. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  212. odxtools/createcompanydatas.py +0 -17
  213. odxtools/createsdgs.py +0 -19
  214. odxtools/diaglayertype.py +0 -30
  215. odxtools/load_file.py +0 -13
  216. odxtools/load_odx_d_file.py +0 -6
  217. odxtools/load_pdx_file.py +0 -8
  218. odxtools/templates/macros/printVariant.xml.jinja2 +0 -208
  219. odxtools-6.6.1.dist-info/RECORD +0 -180
  220. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  221. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  222. {odxtools-6.6.1.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,42 +1,50 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
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
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
+ from .odxtypes import AtomicOdxType, DataType
11
+ from .snrefcontext import SnRefContext
12
+ from .structure import Structure
10
13
  from .utils import dataclass_fields_asdict
11
14
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
15
 
16
16
  @dataclass
17
17
  class MultiplexerCase(NamedElement):
18
- """This class represents a Case which represents multiple options in a Multiplexer."""
18
+ """This class represents a case which represents a range of keys of a multiplexer."""
19
19
 
20
20
  structure_ref: Optional[OdxLinkRef]
21
21
  structure_snref: Optional[str]
22
- lower_limit: str
23
- upper_limit: str
22
+ lower_limit: Limit
23
+ upper_limit: Limit
24
24
 
25
25
  def __post_init__(self) -> None:
26
- self._structure: BasicStructure
26
+ self._structure: Optional[Structure]
27
27
 
28
28
  @staticmethod
29
29
  def from_et(et_element: ElementTree.Element,
30
30
  doc_frags: List[OdxDocFragment]) -> "MultiplexerCase":
31
- """Reads a Case for a Multiplexer."""
31
+ """Reads a case for a Multiplexer."""
32
32
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
33
33
  structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
34
34
  structure_snref = None
35
35
  if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
36
36
  structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
37
37
 
38
- lower_limit = odxrequire(et_element.findtext("LOWER-LIMIT"))
39
- upper_limit = odxrequire(et_element.findtext("UPPER-LIMIT"))
38
+ lower_limit = Limit.limit_from_et(
39
+ odxrequire(et_element.find("LOWER-LIMIT")),
40
+ doc_frags,
41
+ value_type=None,
42
+ )
43
+ upper_limit = Limit.limit_from_et(
44
+ odxrequire(et_element.find("UPPER-LIMIT")),
45
+ doc_frags,
46
+ value_type=None,
47
+ )
40
48
 
41
49
  return MultiplexerCase(
42
50
  structure_ref=structure_ref,
@@ -49,14 +57,27 @@ class MultiplexerCase(NamedElement):
49
57
  return {}
50
58
 
51
59
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
60
+ raise RuntimeError("Calling MultiplexerCase._resolve_odxlinks() is not allowed. "
61
+ "Use ._mux_case_resolve_odxlinks().")
62
+
63
+ def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *,
64
+ key_physical_type: DataType) -> None:
65
+ self._structure = None
52
66
  if self.structure_ref:
53
- self._structure = odxlinks.resolve(self.structure_ref)
67
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
54
68
 
55
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
69
+ self.lower_limit.set_value_type(key_physical_type)
70
+ self.upper_limit.set_value_type(key_physical_type)
71
+
72
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
56
73
  if self.structure_snref:
57
- ddds = diag_layer.diag_data_dictionary_spec
58
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
74
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
75
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
76
+
77
+ def applies(self, value: AtomicOdxType) -> bool:
78
+ return self.lower_limit.complies_to_lower(value) \
79
+ and self.upper_limit.complies_to_upper(value)
59
80
 
60
81
  @property
61
- def structure(self) -> BasicStructure:
82
+ def structure(self) -> Optional[Structure]:
62
83
  return self._structure
@@ -1,17 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .basicstructure import BasicStructure
7
6
  from .element import NamedElement
8
7
  from .exceptions import odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
9
+ from .snrefcontext import SnRefContext
10
+ from .structure import Structure
10
11
  from .utils import dataclass_fields_asdict
11
12
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
13
 
16
14
  @dataclass
17
15
  class MultiplexerDefaultCase(NamedElement):
@@ -20,12 +18,12 @@ class MultiplexerDefaultCase(NamedElement):
20
18
  structure_snref: Optional[str]
21
19
 
22
20
  def __post_init__(self) -> None:
23
- self._structure: Optional[BasicStructure] = None
21
+ self._structure: Optional[Structure]
24
22
 
25
23
  @staticmethod
26
24
  def from_et(et_element: ElementTree.Element,
27
25
  doc_frags: List[OdxDocFragment]) -> "MultiplexerDefaultCase":
28
- """Reads a Default Case for a Multiplexer."""
26
+ """Reads a default case for a multiplexer."""
29
27
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
30
28
 
31
29
  structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
@@ -40,10 +38,15 @@ class MultiplexerDefaultCase(NamedElement):
40
38
  return {}
41
39
 
42
40
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
41
+ self._structure = None
43
42
  if self.structure_ref is not None:
44
- self._structure = odxlinks.resolve(self.structure_ref)
43
+ self._structure = odxlinks.resolve(self.structure_ref, Structure)
45
44
 
46
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
45
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
47
46
  if self.structure_snref:
48
- ddds = diag_layer.diag_data_dictionary_spec
49
- self._structure = odxrequire(ddds.structures.get(self.structure_snref))
47
+ ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
48
+ self._structure = resolve_snref(self.structure_snref, ddds.structures, Structure)
49
+
50
+ @property
51
+ def structure(self) -> Optional[Structure]:
52
+ return self._structure
@@ -1,13 +1,12 @@
1
+ # SPDX-License-Identifier: MIT
1
2
  from dataclasses import dataclass
2
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
3
4
  from xml.etree import ElementTree
4
5
 
5
6
  from .dataobjectproperty import DataObjectProperty
6
7
  from .exceptions import odxrequire
7
8
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
-
9
- if TYPE_CHECKING:
10
- from ..diaglayer import DiagLayer
9
+ from .snrefcontext import SnRefContext
11
10
 
12
11
 
13
12
  @dataclass
@@ -39,7 +38,7 @@ class MultiplexerSwitchKey:
39
38
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
40
39
  self._dop = odxlinks.resolve(self.dop_ref, DataObjectProperty)
41
40
 
42
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
41
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
43
42
  pass
44
43
 
45
44
  @property
odxtools/nameditemlist.py CHANGED
@@ -1,18 +1,20 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
+ import typing
4
+ from copy import deepcopy
3
5
  from keyword import iskeyword
4
- from typing import (Any, Collection, Dict, Iterable, List, Optional, Protocol, SupportsIndex, Tuple,
5
- TypeVar, Union, cast, overload, runtime_checkable)
6
+ from typing import (Any, Collection, Dict, Iterable, List, Optional, SupportsIndex, Tuple, TypeVar,
7
+ Union, cast, overload, runtime_checkable)
6
8
 
7
9
  from .exceptions import odxraise
8
10
 
9
11
 
10
12
  @runtime_checkable
11
- class OdxNamed(Protocol):
13
+ class OdxNamed(typing.Protocol):
12
14
 
13
15
  @property
14
16
  def short_name(self) -> str:
15
- pass
17
+ ...
16
18
 
17
19
 
18
20
  T = TypeVar("T")
@@ -127,7 +129,7 @@ class ItemAttributeList(List[T]):
127
129
  result.update(self._item_dict)
128
130
  return result
129
131
 
130
- @overload # type: ignore[override]
132
+ @overload
131
133
  def __getitem__(self, key: SupportsIndex) -> T:
132
134
  ...
133
135
 
@@ -174,10 +176,33 @@ class ItemAttributeList(List[T]):
174
176
  def __repr__(self) -> str:
175
177
  return f"{type(self).__name__}([{', '.join([repr(x) for x in self])}])"
176
178
 
179
+ def __copy__(self) -> Any:
180
+ return self.__class__(list(self))
181
+
182
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
183
+ cls = self.__class__
184
+ result = cls.__new__(cls)
185
+ memo[id(self)] = result
186
+ result._item_dict = {}
187
+ for x in self:
188
+ result.append(deepcopy(x, memo))
189
+
190
+ return result
191
+
192
+ def __reduce__(self) -> Tuple[Any, ...]:
193
+ """Support for Python's pickle protocol.
194
+ This method ensures that the object can be reconstructed with its current state,
195
+ using its class and the list of items it contains.
196
+ It returns a tuple containing the reconstruction function (the class)
197
+ and its arguments necessary to recreate the object.
198
+
199
+ """
200
+ return self.__class__, (list(self),)
201
+
177
202
 
178
203
  class NamedItemList(ItemAttributeList[T]):
179
204
 
180
- def _get_item_key(self, obj: T) -> str:
205
+ def _get_item_key(self, item: T) -> str:
181
206
  """Transform an object's `short_name` attribute into a valid
182
207
  python identifier
183
208
 
@@ -187,9 +212,9 @@ class NamedItemList(ItemAttributeList[T]):
187
212
  such short names.
188
213
 
189
214
  """
190
- if not isinstance(obj, OdxNamed):
215
+ if not isinstance(item, OdxNamed):
191
216
  odxraise()
192
- sn = obj.short_name
217
+ sn = item.short_name
193
218
  if not isinstance(sn, str):
194
219
  odxraise()
195
220
 
@@ -1,17 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .dopbase import DopBase
7
7
  from .element import NamedElement
8
8
  from .exceptions import odxrequire
9
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from .snrefcontext import SnRefContext
10
11
  from .utils import dataclass_fields_asdict
11
12
 
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
14
-
15
13
 
16
14
  @dataclass
17
15
  class NegOutputParam(NamedElement):
@@ -35,7 +33,7 @@ class NegOutputParam(NamedElement):
35
33
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
36
34
  self._dop = odxlinks.resolve(self.dop_base_ref)
37
35
 
38
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
36
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
39
37
  pass
40
38
 
41
39
  @property
@@ -0,0 +1,83 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from xml.etree import ElementTree
5
+
6
+ from .admindata import AdminData
7
+ from .companydata import CompanyData
8
+ from .element import IdentifiableElement
9
+ from .exceptions import odxrequire
10
+ from .nameditemlist import NamedItemList
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
12
+ from .snrefcontext import SnRefContext
13
+ from .specialdatagroup import SpecialDataGroup
14
+ from .utils import dataclass_fields_asdict
15
+
16
+ if TYPE_CHECKING:
17
+ from .database import Database
18
+
19
+
20
+ @dataclass
21
+ class OdxCategory(IdentifiableElement):
22
+ """This is the base class for all top-level container classes in ODX"""
23
+
24
+ admin_data: Optional[AdminData]
25
+ company_datas: NamedItemList[CompanyData]
26
+ sdgs: List[SpecialDataGroup]
27
+
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()`!")
32
+
33
+ @staticmethod
34
+ def category_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
35
+ doc_type: str) -> "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))
42
+
43
+ admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
44
+ company_datas = NamedItemList([
45
+ CompanyData.from_et(cde, doc_frags)
46
+ for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
47
+ ])
48
+ sdgs = [
49
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
50
+ ]
51
+
52
+ return OdxCategory(admin_data=admin_data, company_datas=company_datas, sdgs=sdgs, **kwargs)
53
+
54
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
55
+ result = {self.odx_id: self}
56
+
57
+ if self.admin_data is not None:
58
+ result.update(self.admin_data._build_odxlinks())
59
+ for cd in self.company_datas:
60
+ result.update(cd._build_odxlinks())
61
+ for sdg in self.sdgs:
62
+ result.update(sdg._build_odxlinks())
63
+
64
+ return result
65
+
66
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
67
+ if self.admin_data is not None:
68
+ self.admin_data._resolve_odxlinks(odxlinks)
69
+ for cd in self.company_datas:
70
+ cd._resolve_odxlinks(odxlinks)
71
+ for sdg in self.sdgs:
72
+ sdg._resolve_odxlinks(odxlinks)
73
+
74
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
75
+ pass
76
+
77
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
78
+ if self.admin_data is not None:
79
+ self.admin_data._resolve_snrefs(context)
80
+ for cd in self.company_datas:
81
+ cd._resolve_snrefs(context)
82
+ for sdg in self.sdgs:
83
+ sdg._resolve_snrefs(context)
odxtools/odxlink.py CHANGED
@@ -1,32 +1,17 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, Type, TypeVar, overload
4
+ from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, overload
5
5
  from xml.etree import ElementTree
6
6
 
7
- from .exceptions import OdxWarning, odxassert
7
+ from .exceptions import OdxWarning, odxassert, odxraise, odxrequire
8
+ from .nameditemlist import OdxNamed, TNamed
8
9
 
9
10
 
10
11
  @dataclass(frozen=True)
11
12
  class OdxDocFragment:
12
13
  doc_name: str
13
- doc_type: Optional[str]
14
-
15
- def __eq__(self, other: Any) -> bool:
16
- if other is None:
17
- # if the other document fragment is not specified, we
18
- # treat it as a wildcard...
19
- return True
20
-
21
- if not isinstance(other, OdxDocFragment):
22
- return False
23
-
24
- # the ODX spec says that the doctype can be ignored...
25
- return self.doc_name == other.doc_name
26
-
27
- def __hash__(self) -> int:
28
- # only the document name is relevant for the hash value
29
- return hash(self.doc_name) + hash(self.doc_type)
14
+ doc_type: str
30
15
 
31
16
 
32
17
  @dataclass(frozen=True)
@@ -58,10 +43,12 @@ class OdxLinkId:
58
43
  if not isinstance(other, OdxLinkId):
59
44
  return False
60
45
 
61
- # we do take the document fragment into consideration here, because
62
- # document fragments are handled using separate sub-databases,
63
- # i.e. the same OdxId object can be put into all of them.
64
- return self.local_id == other.local_id
46
+ # if the local ID is different, the whole id is different
47
+ if self.local_id != other.local_id:
48
+ return False
49
+
50
+ # the document fragments must be identical for the IDs to be identical
51
+ return self.doc_fragments == other.doc_fragments
65
52
 
66
53
  def __str__(self) -> str:
67
54
  return f"OdxLinkId('{self.local_id}')"
@@ -119,6 +106,7 @@ class OdxLinkRef:
119
106
 
120
107
  id_ref = et.attrib.get("ID-REF")
121
108
  if id_ref is None:
109
+ odxraise(f"Tag {et.tag} is not a ODXLINK reference")
122
110
  return None
123
111
 
124
112
  doc_ref = et.attrib.get("DOCREF")
@@ -132,7 +120,7 @@ class OdxLinkRef:
132
120
  # reference, use it, else use the document fragment containing
133
121
  # the reference.
134
122
  if doc_ref is not None:
135
- doc_frags = [OdxDocFragment(doc_ref, doc_type)]
123
+ doc_frags = [OdxDocFragment(doc_ref, odxrequire(doc_type))]
136
124
  else:
137
125
  doc_frags = source_doc_frags
138
126
 
@@ -146,32 +134,19 @@ class OdxLinkRef:
146
134
  def __str__(self) -> str:
147
135
  return f"OdxLinkRef('{self.ref_id}')"
148
136
 
149
- def __contains__(self, odx_id: OdxLinkId) -> bool:
150
- """
151
- Returns true iff a given OdxLinkId object is referenced.
152
- """
153
-
154
- # we must reference at to at least of the ID's document
155
- # fragments
156
- if not any(ref_doc in odx_id.doc_fragments for ref_doc in self.ref_docs):
157
- return False
158
-
159
- # the local ID of the reference and the object ID must match
160
- return odx_id.local_id == self.ref_id
161
-
162
137
 
163
138
  T = TypeVar("T")
164
139
 
165
140
 
166
141
  class OdxLinkDatabase:
167
142
  """
168
- A database holding all objects which ehibit OdxLinkIds
143
+ A database holding all objects which exhibit OdxLinkIds
169
144
 
170
- This can resolve references to such.
145
+ This can resolve ODXLINK references.
171
146
  """
172
147
 
173
148
  def __init__(self) -> None:
174
- self._db: Dict[OdxDocFragment, Dict[OdxLinkId, Any]] = {}
149
+ self._db: Dict[OdxDocFragment, Dict[str, Any]] = {}
175
150
 
176
151
  @overload
177
152
  def resolve(self, ref: OdxLinkRef, expected_type: None = None) -> Any:
@@ -181,20 +156,19 @@ class OdxLinkDatabase:
181
156
  def resolve(self, ref: OdxLinkRef, expected_type: Type[T]) -> T:
182
157
  ...
183
158
 
184
- def resolve(self, ref: OdxLinkRef, expected_type: Optional[Type[T]] = None) -> Any:
159
+ def resolve(self, ref: OdxLinkRef, expected_type: Optional[Any] = None) -> Any:
185
160
  """
186
161
  Resolve a reference to an object
187
162
 
188
163
  If the database does not contain any object which is referred to, a
189
164
  KeyError exception is raised.
190
165
  """
191
- odx_id = OdxLinkId(ref.ref_id, ref.ref_docs)
192
166
  for ref_frag in reversed(ref.ref_docs):
193
167
  doc_frag_db = self._db.get(ref_frag)
194
168
  if doc_frag_db is None:
195
169
  # No object featured by the database uses the document
196
170
  # fragment mentioned by the reference. This should not
197
- # happen for correct databases...
171
+ # happen for correct ODX databases...
198
172
  warnings.warn(
199
173
  f"Warning: Unknown document fragment {ref_frag} "
200
174
  f"when resolving reference {ref}",
@@ -203,15 +177,18 @@ class OdxLinkDatabase:
203
177
  )
204
178
  continue
205
179
 
206
- obj = doc_frag_db.get(odx_id)
207
- if obj is not None:
180
+ # locate an object exhibiting with the referenced local ID
181
+ # in the ID database for the document fragment
182
+ if (obj := doc_frag_db.get(ref.ref_id)) is not None:
208
183
  if expected_type is not None:
209
184
  odxassert(isinstance(obj, expected_type))
210
185
 
211
186
  return obj
212
187
 
213
- raise KeyError(f"ODXLINK reference {ref} could not be resolved for any "
214
- f"of the document fragments {ref.ref_docs}")
188
+ odxraise(
189
+ f"ODXLINK reference {ref} could not be resolved for any "
190
+ f"of the document fragments {ref.ref_docs}", KeyError)
191
+ return None
215
192
 
216
193
  @overload
217
194
  def resolve_lenient(self, ref: OdxLinkRef, expected_type: None = None) -> Any:
@@ -223,7 +200,7 @@ class OdxLinkDatabase:
223
200
 
224
201
  def resolve_lenient(self,
225
202
  ref: OdxLinkRef,
226
- expected_type: Optional[Type[T]] = None) -> Optional[Any]:
203
+ expected_type: Optional[Any] = None) -> Optional[Any]:
227
204
  """
228
205
  Resolve a reference to an object
229
206
 
@@ -231,7 +208,6 @@ class OdxLinkDatabase:
231
208
  is returned.
232
209
  """
233
210
 
234
- odx_id = OdxLinkId(ref.ref_id, ref.ref_docs)
235
211
  for ref_frag in reversed(ref.ref_docs):
236
212
  doc_frag_db = self._db.get(ref_frag)
237
213
  if doc_frag_db is None:
@@ -246,8 +222,7 @@ class OdxLinkDatabase:
246
222
  )
247
223
  continue
248
224
 
249
- obj = doc_frag_db.get(odx_id)
250
- if obj is not None:
225
+ if (obj := doc_frag_db.get(ref.ref_id)) is not None:
251
226
  if expected_type is not None:
252
227
  odxassert(isinstance(obj, expected_type))
253
228
 
@@ -255,7 +230,7 @@ class OdxLinkDatabase:
255
230
 
256
231
  return None
257
232
 
258
- def update(self, new_entries: Dict[OdxLinkId, Any]) -> None:
233
+ def update(self, new_entries: Dict[OdxLinkId, Any], *, overwrite: bool = True) -> None:
259
234
  """
260
235
  Add a bunch of new objects to the ODXLINK database.
261
236
 
@@ -269,4 +244,38 @@ class OdxLinkDatabase:
269
244
  if doc_frag not in self._db:
270
245
  self._db[doc_frag] = {}
271
246
 
272
- self._db[doc_frag][odx_id] = obj
247
+ if overwrite:
248
+ self._db[doc_frag][odx_id.local_id] = obj
249
+ else:
250
+ self._db[doc_frag].setdefault(odx_id.local_id, obj)
251
+
252
+
253
+ @overload
254
+ def resolve_snref(target_short_name: str,
255
+ items: Iterable[OdxNamed],
256
+ expected_type: None = None) -> Any:
257
+ """Resolve a short name reference given a sequence of candidate objects"""
258
+ ...
259
+
260
+
261
+ @overload
262
+ def resolve_snref(target_short_name: str, items: Iterable[OdxNamed],
263
+ expected_type: Type[TNamed]) -> TNamed:
264
+ ...
265
+
266
+
267
+ def resolve_snref(target_short_name: str,
268
+ items: Iterable[OdxNamed],
269
+ expected_type: Any = None) -> Any:
270
+ candidates = [x for x in items if x.short_name == target_short_name]
271
+
272
+ if not candidates:
273
+ odxraise(f"Cannot resolve short name reference to '{target_short_name}'")
274
+ return None
275
+ elif len(candidates) > 1:
276
+ odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'")
277
+ elif expected_type is not None and not isinstance(candidates[0], expected_type):
278
+ odxraise(f"Reference '{target_short_name}' points to a {type(candidates[0]).__name__}"
279
+ f"object while expecting {expected_type.__name__}")
280
+
281
+ return candidates[0]