odxtools 6.7.0__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 (213) hide show
  1. odxtools/__init__.py +6 -4
  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 -240
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +168 -134
  9. odxtools/cli/browse.py +111 -92
  10. odxtools/cli/compare.py +90 -71
  11. odxtools/cli/list.py +24 -15
  12. odxtools/cli/snoop.py +28 -5
  13. odxtools/codec.py +211 -0
  14. odxtools/commrelation.py +122 -0
  15. odxtools/companydata.py +5 -7
  16. odxtools/companydocinfo.py +7 -8
  17. odxtools/companyrevisioninfo.py +3 -5
  18. odxtools/companyspecificinfo.py +8 -9
  19. odxtools/comparam.py +4 -6
  20. odxtools/comparaminstance.py +7 -9
  21. odxtools/comparamspec.py +16 -54
  22. odxtools/comparamsubset.py +22 -62
  23. odxtools/complexcomparam.py +5 -7
  24. odxtools/compumethods/compucodecompumethod.py +63 -0
  25. odxtools/compumethods/compuconst.py +31 -0
  26. odxtools/compumethods/compudefaultvalue.py +27 -0
  27. odxtools/compumethods/compuinternaltophys.py +56 -0
  28. odxtools/compumethods/compuinversevalue.py +7 -0
  29. odxtools/compumethods/compumethod.py +93 -12
  30. odxtools/compumethods/compuphystointernal.py +56 -0
  31. odxtools/compumethods/compurationalcoeffs.py +20 -9
  32. odxtools/compumethods/compuscale.py +30 -35
  33. odxtools/compumethods/createanycompumethod.py +28 -161
  34. odxtools/compumethods/identicalcompumethod.py +31 -6
  35. odxtools/compumethods/linearcompumethod.py +69 -189
  36. odxtools/compumethods/linearsegment.py +190 -0
  37. odxtools/compumethods/ratfunccompumethod.py +106 -0
  38. odxtools/compumethods/ratfuncsegment.py +87 -0
  39. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  40. odxtools/compumethods/scaleratfunccompumethod.py +113 -0
  41. odxtools/compumethods/tabintpcompumethod.py +119 -99
  42. odxtools/compumethods/texttablecompumethod.py +107 -43
  43. odxtools/createanydiagcodedtype.py +10 -67
  44. odxtools/database.py +167 -87
  45. odxtools/dataobjectproperty.py +15 -25
  46. odxtools/decodestate.py +9 -15
  47. odxtools/description.py +47 -0
  48. odxtools/determinenumberofitems.py +4 -5
  49. odxtools/diagcodedtype.py +36 -106
  50. odxtools/diagcomm.py +24 -12
  51. odxtools/diagdatadictionaryspec.py +33 -34
  52. odxtools/diaglayercontainer.py +46 -54
  53. odxtools/diaglayers/basevariant.py +128 -0
  54. odxtools/diaglayers/basevariantraw.py +123 -0
  55. odxtools/diaglayers/diaglayer.py +432 -0
  56. odxtools/{diaglayerraw.py → diaglayers/diaglayerraw.py} +105 -120
  57. odxtools/diaglayers/ecushareddata.py +96 -0
  58. odxtools/diaglayers/ecushareddataraw.py +87 -0
  59. odxtools/diaglayers/ecuvariant.py +124 -0
  60. odxtools/diaglayers/ecuvariantraw.py +129 -0
  61. odxtools/diaglayers/functionalgroup.py +110 -0
  62. odxtools/diaglayers/functionalgroupraw.py +106 -0
  63. odxtools/{diaglayer.py → diaglayers/hierarchyelement.py} +209 -448
  64. odxtools/diaglayers/hierarchyelementraw.py +58 -0
  65. odxtools/diaglayers/protocol.py +64 -0
  66. odxtools/diaglayers/protocolraw.py +91 -0
  67. odxtools/diagnostictroublecode.py +8 -9
  68. odxtools/diagservice.py +56 -43
  69. odxtools/diagvariable.py +113 -0
  70. odxtools/docrevision.py +5 -7
  71. odxtools/dopbase.py +15 -17
  72. odxtools/dtcdop.py +168 -50
  73. odxtools/dynamicendmarkerfield.py +134 -0
  74. odxtools/dynamiclengthfield.py +41 -37
  75. odxtools/dyndefinedspec.py +177 -0
  76. odxtools/dynenddopref.py +38 -0
  77. odxtools/ecuvariantmatcher.py +6 -7
  78. odxtools/element.py +13 -15
  79. odxtools/encodestate.py +199 -22
  80. odxtools/endofpdufield.py +31 -18
  81. odxtools/environmentdata.py +8 -1
  82. odxtools/environmentdatadescription.py +198 -38
  83. odxtools/exceptions.py +11 -2
  84. odxtools/field.py +10 -10
  85. odxtools/functionalclass.py +3 -5
  86. odxtools/inputparam.py +3 -12
  87. odxtools/leadinglengthinfotype.py +37 -18
  88. odxtools/library.py +66 -0
  89. odxtools/loadfile.py +64 -0
  90. odxtools/matchingparameter.py +3 -3
  91. odxtools/message.py +0 -7
  92. odxtools/minmaxlengthtype.py +61 -33
  93. odxtools/modification.py +3 -5
  94. odxtools/multiplexer.py +128 -73
  95. odxtools/multiplexercase.py +13 -14
  96. odxtools/multiplexerdefaultcase.py +15 -12
  97. odxtools/multiplexerswitchkey.py +4 -5
  98. odxtools/nameditemlist.py +29 -5
  99. odxtools/negoutputparam.py +3 -5
  100. odxtools/odxcategory.py +83 -0
  101. odxtools/odxlink.py +60 -51
  102. odxtools/odxtypes.py +37 -5
  103. odxtools/outputparam.py +4 -15
  104. odxtools/parameterinfo.py +218 -67
  105. odxtools/parameters/codedconstparameter.py +16 -24
  106. odxtools/parameters/dynamicparameter.py +5 -4
  107. odxtools/parameters/lengthkeyparameter.py +60 -26
  108. odxtools/parameters/matchingrequestparameter.py +23 -11
  109. odxtools/parameters/nrcconstparameter.py +45 -46
  110. odxtools/parameters/parameter.py +54 -56
  111. odxtools/parameters/parameterwithdop.py +15 -25
  112. odxtools/parameters/physicalconstantparameter.py +15 -18
  113. odxtools/parameters/reservedparameter.py +6 -2
  114. odxtools/parameters/systemparameter.py +55 -11
  115. odxtools/parameters/tableentryparameter.py +3 -2
  116. odxtools/parameters/tablekeyparameter.py +103 -49
  117. odxtools/parameters/tablestructparameter.py +47 -48
  118. odxtools/parameters/valueparameter.py +16 -20
  119. odxtools/paramlengthinfotype.py +52 -32
  120. odxtools/parentref.py +16 -2
  121. odxtools/physicaldimension.py +3 -8
  122. odxtools/progcode.py +26 -11
  123. odxtools/protstack.py +3 -5
  124. odxtools/py.typed +0 -0
  125. odxtools/relateddoc.py +7 -9
  126. odxtools/request.py +120 -10
  127. odxtools/response.py +123 -23
  128. odxtools/scaleconstr.py +3 -3
  129. odxtools/servicebinner.py +1 -1
  130. odxtools/singleecujob.py +12 -10
  131. odxtools/snrefcontext.py +29 -0
  132. odxtools/specialdata.py +3 -5
  133. odxtools/specialdatagroup.py +7 -9
  134. odxtools/specialdatagroupcaption.py +3 -6
  135. odxtools/standardlengthtype.py +80 -14
  136. odxtools/state.py +3 -5
  137. odxtools/statechart.py +13 -19
  138. odxtools/statetransition.py +7 -17
  139. odxtools/staticfield.py +31 -25
  140. odxtools/subcomponent.py +288 -0
  141. odxtools/swvariable.py +21 -0
  142. odxtools/table.py +7 -8
  143. odxtools/tablerow.py +19 -11
  144. odxtools/teammember.py +3 -5
  145. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -24
  146. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +5 -26
  147. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +15 -31
  148. odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +1 -1
  149. odxtools/templates/macros/printAudience.xml.jinja2 +1 -1
  150. odxtools/templates/macros/printBaseVariant.xml.jinja2 +53 -0
  151. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -7
  152. odxtools/templates/macros/printComparam.xml.jinja2 +6 -4
  153. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  154. odxtools/templates/macros/printCompuMethod.xml.jinja2 +147 -0
  155. odxtools/templates/macros/printDOP.xml.jinja2 +27 -133
  156. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  157. odxtools/templates/macros/printDiagComm.xml.jinja2 +1 -1
  158. odxtools/templates/macros/printDiagLayer.xml.jinja2 +222 -0
  159. odxtools/templates/macros/printDiagVariable.xml.jinja2 +66 -0
  160. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +48 -0
  161. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  162. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +1 -1
  163. odxtools/templates/macros/printEcuSharedData.xml.jinja2 +30 -0
  164. odxtools/templates/macros/printEcuVariant.xml.jinja2 +53 -0
  165. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +1 -1
  166. odxtools/templates/macros/printElementId.xml.jinja2 +8 -3
  167. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  168. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  169. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +1 -1
  170. odxtools/templates/macros/printFunctionalGroup.xml.jinja2 +40 -0
  171. odxtools/templates/macros/printHierarchyElement.xml.jinja2 +24 -0
  172. odxtools/templates/macros/printLibrary.xml.jinja2 +21 -0
  173. odxtools/templates/macros/printMux.xml.jinja2 +4 -3
  174. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  175. odxtools/templates/macros/printParam.xml.jinja2 +11 -12
  176. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  177. odxtools/templates/macros/printProtocol.xml.jinja2 +30 -0
  178. odxtools/templates/macros/printRequest.xml.jinja2 +1 -1
  179. odxtools/templates/macros/printResponse.xml.jinja2 +1 -1
  180. odxtools/templates/macros/printService.xml.jinja2 +3 -2
  181. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +5 -26
  182. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  183. odxtools/templates/macros/printState.xml.jinja2 +1 -1
  184. odxtools/templates/macros/printStateChart.xml.jinja2 +1 -1
  185. odxtools/templates/macros/printStateTransition.xml.jinja2 +1 -1
  186. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  187. odxtools/templates/macros/printStructure.xml.jinja2 +1 -1
  188. odxtools/templates/macros/printSubComponent.xml.jinja2 +104 -0
  189. odxtools/templates/macros/printTable.xml.jinja2 +4 -5
  190. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -5
  191. odxtools/uds.py +2 -10
  192. odxtools/unit.py +4 -8
  193. odxtools/unitgroup.py +3 -5
  194. odxtools/unitspec.py +17 -17
  195. odxtools/utils.py +38 -20
  196. odxtools/variablegroup.py +32 -0
  197. odxtools/version.py +2 -2
  198. odxtools/{write_pdx_file.py → writepdxfile.py} +20 -10
  199. odxtools/xdoc.py +3 -5
  200. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/METADATA +20 -21
  201. odxtools-9.3.0.dist-info/RECORD +228 -0
  202. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/WHEEL +1 -1
  203. odxtools/createcompanydatas.py +0 -17
  204. odxtools/createsdgs.py +0 -19
  205. odxtools/load_file.py +0 -13
  206. odxtools/load_odx_d_file.py +0 -6
  207. odxtools/load_pdx_file.py +0 -8
  208. odxtools/templates/macros/printVariant.xml.jinja2 +0 -216
  209. odxtools-6.7.0.dist-info/RECORD +0 -182
  210. /odxtools/{diaglayertype.py → diaglayers/diaglayertype.py} +0 -0
  211. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/LICENSE +0 -0
  212. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/entry_points.txt +0 -0
  213. {odxtools-6.7.0.dist-info → odxtools-9.3.0.dist-info}/top_level.txt +0 -0
odxtools/nameditemlist.py CHANGED
@@ -1,14 +1,16 @@
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:
@@ -139,8 +141,7 @@ class ItemAttributeList(List[T]):
139
141
  def __getitem__(self, key: slice) -> List[T]:
140
142
  ...
141
143
 
142
- def __getitem__( # type: ignore
143
- self, key: Union[SupportsIndex, str, slice]) -> Union[T, List[T]]:
144
+ def __getitem__(self, key: Union[SupportsIndex, str, slice]) -> Union[T, List[T]]:
144
145
  if isinstance(key, (SupportsIndex, slice)):
145
146
  return super().__getitem__(key)
146
147
  else:
@@ -175,6 +176,29 @@ class ItemAttributeList(List[T]):
175
176
  def __repr__(self) -> str:
176
177
  return f"{type(self).__name__}([{', '.join([repr(x) for x in self])}])"
177
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
+
178
202
 
179
203
  class NamedItemList(ItemAttributeList[T]):
180
204
 
@@ -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:
@@ -188,13 +163,12 @@ class OdxLinkDatabase:
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:
@@ -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]
odxtools/odxtypes.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from enum import Enum
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union, overload
3
+ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Optional, Tuple, Type, Union,
4
+ overload)
4
5
  from xml.etree import ElementTree
5
6
 
6
7
  from .exceptions import odxassert, odxraise, odxrequire
@@ -28,7 +29,7 @@ ParameterDict = Dict[str, Union["Parameter", "ParameterDict"]]
28
29
  # multiple items, so this can be a list of objects.
29
30
  TableStructParameterValue = Tuple[str, "ParameterValue"]
30
31
  ParameterValue = Union[AtomicOdxType, "ParameterValueDict", TableStructParameterValue,
31
- List["ParameterValue"], "DiagnosticTroubleCode"]
32
+ Iterable["ParameterValue"], "DiagnosticTroubleCode"]
32
33
  ParameterValueDict = Dict[str, ParameterValue]
33
34
 
34
35
 
@@ -63,8 +64,8 @@ def bool_to_odxstr(bool_val: bool) -> str:
63
64
 
64
65
  def parse_int(value: str) -> int:
65
66
  try:
66
- return int(value)
67
- except ValueError:
67
+ return int(value, 0)
68
+ except (ValueError, TypeError):
68
69
  try:
69
70
  v = float(value)
70
71
  except Exception as e:
@@ -154,6 +155,19 @@ def compare_odx_values(a: AtomicOdxType, b: AtomicOdxType) -> int:
154
155
  f"and {type(b).__name__}")
155
156
 
156
157
 
158
+ # format specifiers for the data type using the bitstruct module
159
+ _BITSTRUCT_FORMAT_LETTER_MAP__ = {
160
+ "A_INT32": "s",
161
+ "A_UINT32": "u",
162
+ "A_FLOAT32": "f",
163
+ "A_FLOAT64": "f",
164
+ "A_BYTEFIELD": "r",
165
+ "A_UNICODE2STRING": "r", # UTF-16 strings must be converted explicitly
166
+ "A_ASCIISTRING": "r",
167
+ "A_UTF8STRING": "r",
168
+ }
169
+
170
+
157
171
  class DataType(Enum):
158
172
  """Types for the physical and internal value.
159
173
 
@@ -181,6 +195,10 @@ class DataType(Enum):
181
195
  def python_type(self) -> Type[AtomicOdxType]:
182
196
  return _ODX_TYPE_TO_PYTHON_TYPE[self.value]
183
197
 
198
+ @property
199
+ def bitstruct_format_letter(self) -> str:
200
+ return _BITSTRUCT_FORMAT_LETTER_MAP__[self.value]
201
+
184
202
  def from_string(self, value: str) -> AtomicOdxType:
185
203
  return _PARSE_ODX_TYPE[self.value](value)
186
204
 
@@ -217,9 +235,23 @@ class DataType(Enum):
217
235
  expected_type = self.python_type
218
236
  if isinstance(value, expected_type):
219
237
  return True
220
- elif expected_type == float and isinstance(value, (int, float)):
238
+ elif expected_type is float and isinstance(value, (int, float)):
221
239
  return True
222
240
  elif self == DataType.A_BYTEFIELD and isinstance(value, (bytearray, bytes)):
223
241
  return True
224
242
  else:
225
243
  return False
244
+
245
+ def __str__(self) -> str:
246
+ if self == DataType.A_INT32:
247
+ return "int"
248
+ elif self == DataType.A_UINT32:
249
+ return "uint"
250
+ elif self in (DataType.A_FLOAT32, DataType.A_FLOAT64):
251
+ return "float"
252
+ elif self == DataType.A_BYTEFIELD:
253
+ return "bytefield"
254
+ elif self in (DataType.A_UNICODE2STRING, DataType.A_ASCIISTRING, DataType.A_UTF8STRING):
255
+ return "string"
256
+ else:
257
+ return f"<unknown type '{self.value}'>"
odxtools/outputparam.py CHANGED
@@ -1,24 +1,19 @@
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 deprecation import deprecated
7
-
8
6
  from .dopbase import DopBase
9
7
  from .element import IdentifiableElement
10
8
  from .exceptions import odxrequire
11
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
+ from .snrefcontext import SnRefContext
12
11
  from .utils import dataclass_fields_asdict
13
12
 
14
- if TYPE_CHECKING:
15
- from .diaglayer import DiagLayer
16
-
17
13
 
18
14
  @dataclass
19
15
  class OutputParam(IdentifiableElement):
20
16
  dop_base_ref: OdxLinkRef
21
- oid: Optional[str]
22
17
  semantic: Optional[str]
23
18
 
24
19
  @staticmethod
@@ -27,9 +22,8 @@ class OutputParam(IdentifiableElement):
27
22
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
28
23
  dop_base_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DOP-BASE-REF"), doc_frags))
29
24
  semantic = et_element.get("SEMANTIC")
30
- oid = et_element.get("OID")
31
25
 
32
- return OutputParam(dop_base_ref=dop_base_ref, semantic=semantic, oid=oid, **kwargs)
26
+ return OutputParam(dop_base_ref=dop_base_ref, semantic=semantic, **kwargs)
33
27
 
34
28
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
35
29
  return {}
@@ -37,15 +31,10 @@ class OutputParam(IdentifiableElement):
37
31
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
38
32
  self._dop_base = odxlinks.resolve(self.dop_base_ref, DopBase)
39
33
 
40
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
34
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
41
35
  pass
42
36
 
43
37
  @property
44
38
  def dop_base(self) -> DopBase:
45
39
  """The data object property describing this parameter."""
46
40
  return self._dop_base
47
-
48
- @property
49
- @deprecated(details="use .dop_base") # type: ignore[misc]
50
- def dop(self) -> DopBase:
51
- return self._dop_base