odxtools 7.1.1__py3-none-any.whl → 7.2.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 (130) hide show
  1. odxtools/__init__.py +6 -4
  2. odxtools/additionalaudience.py +3 -5
  3. odxtools/admindata.py +5 -7
  4. odxtools/audience.py +3 -5
  5. odxtools/basecomparam.py +3 -5
  6. odxtools/basicstructure.py +10 -17
  7. odxtools/cli/_parser_utils.py +1 -1
  8. odxtools/cli/_print_utils.py +3 -2
  9. odxtools/cli/compare.py +1 -1
  10. odxtools/companydata.py +5 -7
  11. odxtools/companydocinfo.py +7 -8
  12. odxtools/companyrevisioninfo.py +3 -5
  13. odxtools/companyspecificinfo.py +8 -9
  14. odxtools/comparam.py +4 -6
  15. odxtools/comparaminstance.py +6 -8
  16. odxtools/comparamspec.py +14 -13
  17. odxtools/comparamsubset.py +17 -16
  18. odxtools/complexcomparam.py +5 -7
  19. odxtools/compumethods/compuconst.py +31 -0
  20. odxtools/compumethods/compudefaultvalue.py +27 -0
  21. odxtools/compumethods/compuinternaltophys.py +39 -0
  22. odxtools/compumethods/compuinversevalue.py +7 -0
  23. odxtools/compumethods/compumethod.py +67 -12
  24. odxtools/compumethods/compuphystointernal.py +39 -0
  25. odxtools/compumethods/compuscale.py +15 -26
  26. odxtools/compumethods/createanycompumethod.py +14 -160
  27. odxtools/compumethods/identicalcompumethod.py +31 -6
  28. odxtools/compumethods/linearcompumethod.py +69 -189
  29. odxtools/compumethods/linearsegment.py +193 -0
  30. odxtools/compumethods/scalelinearcompumethod.py +132 -26
  31. odxtools/compumethods/tabintpcompumethod.py +119 -99
  32. odxtools/compumethods/texttablecompumethod.py +107 -43
  33. odxtools/createanydiagcodedtype.py +10 -67
  34. odxtools/database.py +68 -62
  35. odxtools/dataobjectproperty.py +10 -19
  36. odxtools/description.py +47 -0
  37. odxtools/determinenumberofitems.py +4 -5
  38. odxtools/diagcodedtype.py +29 -12
  39. odxtools/diagcomm.py +10 -6
  40. odxtools/diagdatadictionaryspec.py +20 -21
  41. odxtools/diaglayer.py +34 -5
  42. odxtools/diaglayercontainer.py +17 -11
  43. odxtools/diaglayerraw.py +20 -21
  44. odxtools/diagnostictroublecode.py +7 -8
  45. odxtools/diagservice.py +9 -7
  46. odxtools/docrevision.py +5 -7
  47. odxtools/dopbase.py +7 -8
  48. odxtools/dtcdop.py +5 -8
  49. odxtools/dynamicendmarkerfield.py +22 -9
  50. odxtools/dynamiclengthfield.py +5 -11
  51. odxtools/element.py +4 -3
  52. odxtools/endofpdufield.py +0 -2
  53. odxtools/environmentdatadescription.py +4 -6
  54. odxtools/exceptions.py +1 -1
  55. odxtools/field.py +9 -9
  56. odxtools/functionalclass.py +3 -5
  57. odxtools/inputparam.py +3 -5
  58. odxtools/leadinglengthinfotype.py +15 -2
  59. odxtools/loadfile.py +64 -0
  60. odxtools/minmaxlengthtype.py +20 -2
  61. odxtools/modification.py +3 -5
  62. odxtools/multiplexer.py +7 -14
  63. odxtools/multiplexercase.py +4 -6
  64. odxtools/multiplexerdefaultcase.py +4 -6
  65. odxtools/multiplexerswitchkey.py +4 -5
  66. odxtools/negoutputparam.py +3 -5
  67. odxtools/outputparam.py +3 -5
  68. odxtools/parameterinfo.py +3 -3
  69. odxtools/parameters/codedconstparameter.py +2 -14
  70. odxtools/parameters/lengthkeyparameter.py +3 -17
  71. odxtools/parameters/nrcconstparameter.py +2 -14
  72. odxtools/parameters/parameter.py +22 -22
  73. odxtools/parameters/parameterwithdop.py +6 -8
  74. odxtools/parameters/physicalconstantparameter.py +5 -8
  75. odxtools/parameters/reservedparameter.py +4 -3
  76. odxtools/parameters/tablekeyparameter.py +6 -9
  77. odxtools/parameters/tablestructparameter.py +6 -8
  78. odxtools/parameters/valueparameter.py +5 -8
  79. odxtools/paramlengthinfotype.py +19 -6
  80. odxtools/parentref.py +15 -1
  81. odxtools/physicaldimension.py +3 -5
  82. odxtools/progcode.py +18 -7
  83. odxtools/protstack.py +3 -5
  84. odxtools/relateddoc.py +7 -9
  85. odxtools/request.py +8 -0
  86. odxtools/response.py +8 -0
  87. odxtools/scaleconstr.py +3 -3
  88. odxtools/singleecujob.py +12 -10
  89. odxtools/snrefcontext.py +29 -0
  90. odxtools/specialdata.py +3 -5
  91. odxtools/specialdatagroup.py +5 -7
  92. odxtools/specialdatagroupcaption.py +3 -6
  93. odxtools/standardlengthtype.py +27 -2
  94. odxtools/state.py +3 -5
  95. odxtools/statechart.py +9 -11
  96. odxtools/statetransition.py +4 -9
  97. odxtools/staticfield.py +4 -8
  98. odxtools/table.py +7 -8
  99. odxtools/tablerow.py +7 -6
  100. odxtools/teammember.py +3 -5
  101. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +2 -5
  102. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +2 -5
  103. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -5
  104. odxtools/templates/macros/printComparamRef.xml.jinja2 +5 -12
  105. odxtools/templates/macros/printCompuMethod.xml.jinja2 +153 -0
  106. odxtools/templates/macros/printDOP.xml.jinja2 +10 -132
  107. odxtools/templates/macros/printDescription.xml.jinja2 +18 -0
  108. odxtools/templates/macros/printElementId.xml.jinja2 +3 -3
  109. odxtools/templates/macros/printMux.xml.jinja2 +3 -2
  110. odxtools/templates/macros/printTable.xml.jinja2 +2 -3
  111. odxtools/unit.py +3 -5
  112. odxtools/unitgroup.py +3 -5
  113. odxtools/unitspec.py +9 -10
  114. odxtools/utils.py +1 -26
  115. odxtools/version.py +2 -2
  116. odxtools/{write_pdx_file.py → writepdxfile.py} +19 -10
  117. odxtools/xdoc.py +3 -5
  118. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/METADATA +1 -1
  119. odxtools-7.2.0.dist-info/RECORD +192 -0
  120. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/WHEEL +1 -1
  121. odxtools/createcompanydatas.py +0 -17
  122. odxtools/createsdgs.py +0 -19
  123. odxtools/load_file.py +0 -13
  124. odxtools/load_odx_d_file.py +0 -6
  125. odxtools/load_pdx_file.py +0 -8
  126. odxtools-7.1.1.dist-info/RECORD +0 -186
  127. /odxtools/templates/{index.xml.xml.jinja2 → index.xml.jinja2} +0 -0
  128. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/LICENSE +0 -0
  129. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/entry_points.txt +0 -0
  130. {odxtools-7.1.1.dist-info → odxtools-7.2.0.dist-info}/top_level.txt +0 -0
odxtools/database.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from itertools import chain
3
3
  from pathlib import Path
4
- from typing import List, Optional
4
+ from typing import IO, List, Optional, OrderedDict
5
5
  from xml.etree import ElementTree
6
6
  from zipfile import ZipFile
7
7
 
@@ -26,69 +26,75 @@ class Database:
26
26
  *,
27
27
  pdx_zip: Optional[ZipFile] = None,
28
28
  odx_d_file_name: Optional[str] = None) -> None:
29
- self.model_version = None
30
-
31
- if pdx_zip is None and odx_d_file_name is None:
32
- # create an empty database object
33
- self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
34
- self._comparam_subsets = NamedItemList[ComparamSubset]()
35
- self._comparam_specs = NamedItemList[ComparamSpec]()
36
- return
37
-
38
- if pdx_zip is not None and odx_d_file_name is not None:
39
- raise TypeError("The 'pdx_zip' and 'odx_d_file_name' parameters are mutually exclusive")
40
-
41
- documents: List[ElementTree.Element] = []
42
- if pdx_zip is not None:
43
- names = list(pdx_zip.namelist())
44
- names.sort()
45
- for zip_member in names:
46
- # The name of ODX files can end with .odx, .odx-d,
47
- # .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
48
- # .odx-v . We could test for all that, or just make
49
- # sure that the file's suffix starts with .odx
50
- if Path(zip_member).suffix.startswith(".odx"):
51
- d = pdx_zip.read(zip_member)
52
- root = ElementTree.fromstring(d)
53
- documents.append(root)
54
-
55
- elif odx_d_file_name is not None:
56
- documents.append(ElementTree.parse(odx_d_file_name).getroot())
57
-
29
+ self.model_version: Optional[Version] = None
30
+ self.auxiliary_files: OrderedDict[str, IO[bytes]] = OrderedDict()
31
+
32
+ # create an empty database object
33
+ self._diag_layer_containers = NamedItemList[DiagLayerContainer]()
34
+ self._comparam_subsets = NamedItemList[ComparamSubset]()
35
+ self._comparam_specs = NamedItemList[ComparamSpec]()
36
+
37
+ def add_pdx_file(self, pdx_file_name: str) -> None:
38
+ pdx_zip = ZipFile(pdx_file_name)
39
+
40
+ for zip_member in pdx_zip.namelist():
41
+ # The name of ODX files can end with .odx, .odx-d,
42
+ # .odx-c, .odx-cs, .odx-e, .odx-f, .odx-fd, .odx-m,
43
+ # .odx-v . We could test for all that, or just make
44
+ # sure that the file's suffix starts with .odx
45
+ p = Path(zip_member)
46
+ if p.suffix.lower().startswith(".odx"):
47
+ root = ElementTree.parse(pdx_zip.open(zip_member)).getroot()
48
+ self._process_xml_tree(root)
49
+ elif p.name.lower() != "index.xml":
50
+ self.add_auxiliary_file(zip_member, pdx_zip.open(zip_member))
51
+
52
+ def add_odx_file(self, odx_file_name: str) -> None:
53
+ self._process_xml_tree(ElementTree.parse(odx_file_name).getroot())
54
+
55
+ def add_auxiliary_file(self,
56
+ aux_file_name: str,
57
+ aux_file_obj: Optional[IO[bytes]] = None) -> None:
58
+ if aux_file_obj is None:
59
+ aux_file_obj = open(aux_file_name, "rb")
60
+
61
+ self.auxiliary_files[aux_file_name] = aux_file_obj
62
+
63
+ def _process_xml_tree(self, root: ElementTree.Element) -> None:
58
64
  dlcs: List[DiagLayerContainer] = []
59
65
  comparam_subsets: List[ComparamSubset] = []
60
66
  comparam_specs: List[ComparamSpec] = []
61
- for root in documents:
62
- # ODX spec version
63
- model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
64
- if self.model_version is not None and self.model_version != model_version:
65
- odxraise(f"Different ODX versions used in the same file (ODX {model_version} "
66
- f"and ODX {self.model_version}")
67
- self.model_version = model_version
68
- dlc = root.find("DIAG-LAYER-CONTAINER")
69
- if dlc is not None:
70
- dlcs.append(DiagLayerContainer.from_et(dlc, []))
71
-
72
- # In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
73
- # content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
74
- # and COMPARAM-SPEC became a container for PROT-STACKS and
75
- # a PROT-STACK references a list of COMPARAM-SUBSET
76
- cp_subset = root.find("COMPARAM-SUBSET")
77
- if cp_subset is not None:
78
- comparam_subsets.append(ComparamSubset.from_et(cp_subset, []))
79
-
80
- cp_spec = root.find("COMPARAM-SPEC")
81
- if cp_spec is not None:
82
- if model_version < Version("2.2"):
83
- comparam_subsets.append(ComparamSubset.from_et(cp_spec, []))
84
- else: # odx >= 2.2
85
- comparam_specs.append(ComparamSpec.from_et(cp_spec, []))
86
-
87
- self._diag_layer_containers = NamedItemList(dlcs)
88
- self._comparam_subsets = NamedItemList(comparam_subsets)
89
- self._comparam_specs = NamedItemList(comparam_specs)
90
-
91
- self.refresh()
67
+
68
+ # ODX spec version
69
+ model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
70
+ if self.model_version is not None and self.model_version != model_version:
71
+ odxraise(f"Different ODX versions used for the same database (ODX {model_version} "
72
+ f"and ODX {self.model_version}")
73
+
74
+ self.model_version = model_version
75
+
76
+ dlc = root.find("DIAG-LAYER-CONTAINER")
77
+ if dlc is not None:
78
+ dlcs.append(DiagLayerContainer.from_et(dlc, []))
79
+
80
+ # In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
81
+ # content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
82
+ # and COMPARAM-SPEC became a container for PROT-STACKS and
83
+ # a PROT-STACK references a list of COMPARAM-SUBSET
84
+ cp_subset = root.find("COMPARAM-SUBSET")
85
+ if cp_subset is not None:
86
+ comparam_subsets.append(ComparamSubset.from_et(cp_subset, []))
87
+
88
+ cp_spec = root.find("COMPARAM-SPEC")
89
+ if cp_spec is not None:
90
+ if model_version < Version("2.2"):
91
+ comparam_subsets.append(ComparamSubset.from_et(cp_spec, []))
92
+ else: # odx >= 2.2
93
+ comparam_specs.append(ComparamSpec.from_et(cp_spec, []))
94
+
95
+ self._diag_layer_containers.extend(dlcs)
96
+ self._comparam_subsets.extend(comparam_subsets)
97
+ self._comparam_specs.extend(comparam_specs)
92
98
 
93
99
  def refresh(self) -> None:
94
100
  # Create wrapper objects
@@ -125,7 +131,7 @@ class Database:
125
131
  # let the diaglayers sort out the inherited objects and the
126
132
  # short name references
127
133
  for dlc in self.diag_layer_containers:
128
- dlc._finalize_init(self._odxlinks)
134
+ dlc._finalize_init(self, self._odxlinks)
129
135
 
130
136
  @property
131
137
  def odxlinks(self) -> OdxLinkDatabase:
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
3
+ from typing import Any, Dict, List, Optional, cast
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .compumethods.compumethod import CompuMethod
@@ -10,17 +10,15 @@ from .decodestate import DecodeState
10
10
  from .diagcodedtype import DiagCodedType
11
11
  from .dopbase import DopBase
12
12
  from .encodestate import EncodeState
13
- from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
13
+ from .exceptions import DecodeError, EncodeError, odxraise, odxrequire
14
14
  from .internalconstr import InternalConstr
15
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
16
16
  from .odxtypes import AtomicOdxType, ParameterValue
17
17
  from .physicaltype import PhysicalType
18
+ from .snrefcontext import SnRefContext
18
19
  from .unit import Unit
19
20
  from .utils import dataclass_fields_asdict
20
21
 
21
- if TYPE_CHECKING:
22
- from .diaglayer import DiagLayer
23
-
24
22
 
25
23
  @dataclass
26
24
  class DataObjectProperty(DopBase):
@@ -98,10 +96,10 @@ class DataObjectProperty(DopBase):
98
96
  if self.unit_ref:
99
97
  self._unit = odxlinks.resolve(self.unit_ref, Unit)
100
98
 
101
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
102
- super()._resolve_snrefs(diag_layer)
99
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
100
+ super()._resolve_snrefs(context)
103
101
 
104
- self.diag_coded_type._resolve_snrefs(diag_layer)
102
+ self.diag_coded_type._resolve_snrefs(context)
105
103
 
106
104
  @property
107
105
  def unit(self) -> Optional[Unit]:
@@ -110,16 +108,6 @@ class DataObjectProperty(DopBase):
110
108
  def get_static_bit_length(self) -> Optional[int]:
111
109
  return self.diag_coded_type.get_static_bit_length()
112
110
 
113
- def convert_physical_to_internal(self, physical_value: Any) -> Any:
114
- """
115
- Convert a physical representation of a parameter to its internal counterpart
116
- """
117
- odxassert(
118
- self.physical_type.base_data_type.isinstance(physical_value),
119
- f"Expected {self.physical_type.base_data_type.value}, got {type(physical_value)}")
120
-
121
- return self.compu_method.convert_physical_to_internal(physical_value)
122
-
123
111
  def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:
124
112
  """
125
113
  Convert a physical representation of a parameter to a string bytes that can be send over the wire
@@ -129,7 +117,10 @@ class DataObjectProperty(DopBase):
129
117
  f"The value {repr(physical_value)} of type {type(physical_value).__name__}"
130
118
  f" is not a valid.")
131
119
 
132
- internal_value = self.convert_physical_to_internal(physical_value)
120
+ if not isinstance(physical_value, (int, float, str, bytes, bytearray)):
121
+ odxraise(f"Invalid type '{type(physical_value).__name__}' for physical value. "
122
+ f"(Expect atomic type!)")
123
+ internal_value = self.compu_method.convert_physical_to_internal(physical_value)
133
124
  self.diag_coded_type.encode_into_pdu(internal_value, encode_state)
134
125
 
135
126
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+ from xml.etree import ElementTree
4
+
5
+ from .exceptions import odxrequire
6
+ from .odxlink import OdxDocFragment
7
+
8
+
9
+ @dataclass
10
+ class Description:
11
+ text: str
12
+ external_docs: List[str]
13
+ text_identifier: Optional[str]
14
+
15
+ @staticmethod
16
+ def from_et(et_element: Optional[ElementTree.Element],
17
+ doc_frags: List[OdxDocFragment]) -> Optional["Description"]:
18
+ if et_element is None:
19
+ return None
20
+
21
+ # Extract the contents of the tag as a XHTML string.
22
+ raw_string = et_element.text or ""
23
+ for e in et_element:
24
+ if e.tag == "EXTERNAL-DOCS":
25
+ break
26
+ raw_string += ElementTree.tostring(e, encoding="unicode")
27
+
28
+ # remove white spaces at the beginning and at the end of all
29
+ # extracted lines
30
+ stripped_lines = [x.strip() for x in raw_string.split("\n")]
31
+
32
+ text = "\n".join(stripped_lines).strip()
33
+
34
+ text_identifier = et_element.get("TI")
35
+
36
+ external_docs = \
37
+ [
38
+ odxrequire(ed.get("HREF")) for ed in et_element.iterfind("EXTERNAL-DOCS/EXTERNAL-DOC")
39
+ ]
40
+ return Description(text=text, text_identifier=text_identifier, external_docs=external_docs)
41
+
42
+ @staticmethod
43
+ def from_string(text: str) -> "Description":
44
+ return Description(text=text, external_docs=[], text_identifier=None)
45
+
46
+ def __str__(self) -> str:
47
+ return self.text
@@ -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 DetermineNumberOfItems:
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/diagcodedtype.py CHANGED
@@ -1,16 +1,14 @@
1
1
  # SPDX-License-Identifier: MIT
2
- import abc
3
2
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
3
+ from typing import Any, Dict, List, Literal, Optional, Union, cast
4
+ from xml.etree import ElementTree
5
5
 
6
6
  from .decodestate import DecodeState
7
7
  from .encodestate import EncodeState
8
- from .exceptions import odxassert, odxraise
9
- from .odxlink import OdxLinkDatabase, OdxLinkId
10
- from .odxtypes import AtomicOdxType, DataType
11
-
12
- if TYPE_CHECKING:
13
- from .diaglayer import DiagLayer
8
+ from .exceptions import odxassert, odxraise, odxrequire
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
10
+ from .odxtypes import AtomicOdxType, DataType, odxstr_to_bool
11
+ from .snrefcontext import SnRefContext
14
12
 
15
13
  # Allowed diag-coded types
16
14
  DctType = Literal[
@@ -22,12 +20,30 @@ DctType = Literal[
22
20
 
23
21
 
24
22
  @dataclass
25
- class DiagCodedType(abc.ABC):
23
+ class DiagCodedType:
26
24
 
27
25
  base_data_type: DataType
28
26
  base_type_encoding: Optional[str]
29
27
  is_highlow_byte_order_raw: Optional[bool]
30
28
 
29
+ @staticmethod
30
+ def from_et(et_element: ElementTree.Element,
31
+ doc_frags: List[OdxDocFragment]) -> "DiagCodedType":
32
+ base_data_type_str = odxrequire(et_element.get("BASE-DATA-TYPE"))
33
+ try:
34
+ base_data_type = DataType(base_data_type_str)
35
+ except ValueError:
36
+ odxraise(f"Unknown base data type {base_data_type_str}")
37
+ base_data_type = cast(DataType, None)
38
+
39
+ base_type_encoding = et_element.get("BASE-TYPE-ENCODING")
40
+ is_highlow_byte_order_raw = odxstr_to_bool(et_element.get("IS-HIGHLOW-BYTE-ORDER"))
41
+
42
+ return DiagCodedType(
43
+ base_data_type=base_data_type,
44
+ base_type_encoding=base_type_encoding,
45
+ is_highlow_byte_order_raw=is_highlow_byte_order_raw)
46
+
31
47
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: # noqa: B027
32
48
  return {}
33
49
 
@@ -35,7 +51,7 @@ class DiagCodedType(abc.ABC):
35
51
  """Recursively resolve any odxlinks references"""
36
52
  pass
37
53
 
38
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: # noqa: B027
54
+ def _resolve_snrefs(self, context: SnRefContext) -> None: # noqa: B027
39
55
  """Recursively resolve any short-name references"""
40
56
  pass
41
57
 
@@ -43,9 +59,10 @@ class DiagCodedType(abc.ABC):
43
59
  return None
44
60
 
45
61
  @property
46
- @abc.abstractmethod
47
62
  def dct_type(self) -> DctType:
48
- pass
63
+ odxraise(f"Class {type(self).__name__} does not override required method "
64
+ f"dct_type()", NotImplementedError)
65
+ return cast(DctType, None)
49
66
 
50
67
  @property
51
68
  def is_highlow_byte_order(self) -> bool:
odxtools/diagcomm.py CHANGED
@@ -6,13 +6,13 @@ from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
8
8
  from .audience import Audience
9
- from .createsdgs import create_sdgs_from_et
10
9
  from .element import IdentifiableElement
11
10
  from .exceptions import odxraise, odxrequire
12
11
  from .functionalclass import FunctionalClass
13
12
  from .nameditemlist import NamedItemList
14
13
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
15
14
  from .odxtypes import odxstr_to_bool
15
+ from .snrefcontext import SnRefContext
16
16
  from .specialdatagroup import SpecialDataGroup
17
17
  from .state import State
18
18
  from .statetransition import StateTransition
@@ -76,7 +76,9 @@ class DiagComm(IdentifiableElement):
76
76
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
77
77
 
78
78
  admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
79
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
79
+ sdgs = [
80
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
81
+ ]
80
82
 
81
83
  functional_class_refs = [
82
84
  odxrequire(OdxLinkRef.from_et(el, doc_frags))
@@ -201,22 +203,24 @@ class DiagComm(IdentifiableElement):
201
203
  self._state_transitions = NamedItemList(
202
204
  [odxlinks.resolve(stt_ref, StateTransition) for stt_ref in self.state_transition_refs])
203
205
 
204
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
206
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
205
207
  if self.admin_data:
206
- self.admin_data._resolve_snrefs(diag_layer)
208
+ self.admin_data._resolve_snrefs(context)
207
209
 
208
210
  if self.audience:
209
- self.audience._resolve_snrefs(diag_layer)
211
+ self.audience._resolve_snrefs(context)
210
212
 
211
213
  for sdg in self.sdgs:
212
- sdg._resolve_snrefs(diag_layer)
214
+ sdg._resolve_snrefs(context)
213
215
 
214
216
  if TYPE_CHECKING:
217
+ diag_layer = odxrequire(context.diag_layer)
215
218
  self._protocols = NamedItemList([
216
219
  resolve_snref(prot_snref, diag_layer.protocols, DiagLayer)
217
220
  for prot_snref in self.protocol_snrefs
218
221
  ])
219
222
  else:
223
+ diag_layer = odxrequire(context.diag_layer)
220
224
  self._protocols = NamedItemList([
221
225
  resolve_snref(prot_snref, diag_layer.protocols)
222
226
  for prot_snref in self.protocol_snrefs
@@ -1,12 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from itertools import chain
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+ from typing import Any, Dict, List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
8
8
  from .basicstructure import BasicStructure
9
- from .createsdgs import create_sdgs_from_et
10
9
  from .dataobjectproperty import DataObjectProperty
11
10
  from .dopbase import DopBase
12
11
  from .dtcdop import DtcDop
@@ -19,15 +18,13 @@ from .exceptions import odxraise
19
18
  from .multiplexer import Multiplexer
20
19
  from .nameditemlist import NamedItemList
21
20
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
21
+ from .snrefcontext import SnRefContext
22
22
  from .specialdatagroup import SpecialDataGroup
23
23
  from .staticfield import StaticField
24
24
  from .structure import Structure
25
25
  from .table import Table
26
26
  from .unitspec import UnitSpec
27
27
 
28
- if TYPE_CHECKING:
29
- from .diaglayer import DiagLayer
30
-
31
28
 
32
29
  @dataclass
33
30
  class DiagDataDictionarySpec:
@@ -135,7 +132,9 @@ class DiagDataDictionarySpec:
135
132
  for table_element in et_element.iterfind("TABLES/TABLE")
136
133
  ]
137
134
 
138
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
135
+ sdgs = [
136
+ SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
137
+ ]
139
138
 
140
139
  return DiagDataDictionarySpec(
141
140
  admin_data=admin_data,
@@ -219,35 +218,35 @@ class DiagDataDictionarySpec:
219
218
  for sdg in self.sdgs:
220
219
  sdg._resolve_odxlinks(odxlinks)
221
220
 
222
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
221
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
223
222
  if self.admin_data is not None:
224
- self.admin_data._resolve_snrefs(diag_layer)
223
+ self.admin_data._resolve_snrefs(context)
225
224
  for dtc_dop in self.dtc_dops:
226
- dtc_dop._resolve_snrefs(diag_layer)
225
+ dtc_dop._resolve_snrefs(context)
227
226
  for env_data_desc in self.env_data_descs:
228
- env_data_desc._resolve_snrefs(diag_layer)
227
+ env_data_desc._resolve_snrefs(context)
229
228
  for data_object_prop in self.data_object_props:
230
- data_object_prop._resolve_snrefs(diag_layer)
229
+ data_object_prop._resolve_snrefs(context)
231
230
  for structure in self.structures:
232
- structure._resolve_snrefs(diag_layer)
231
+ structure._resolve_snrefs(context)
233
232
  for static_field in self.static_fields:
234
- static_field._resolve_snrefs(diag_layer)
233
+ static_field._resolve_snrefs(context)
235
234
  for dynamic_length_field in self.dynamic_length_fields:
236
- dynamic_length_field._resolve_snrefs(diag_layer)
235
+ dynamic_length_field._resolve_snrefs(context)
237
236
  for dynamic_endmarker_field in self.dynamic_endmarker_fields:
238
- dynamic_endmarker_field._resolve_snrefs(diag_layer)
237
+ dynamic_endmarker_field._resolve_snrefs(context)
239
238
  for end_of_pdu_field in self.end_of_pdu_fields:
240
- end_of_pdu_field._resolve_snrefs(diag_layer)
239
+ end_of_pdu_field._resolve_snrefs(context)
241
240
  for mux in self.muxs:
242
- mux._resolve_snrefs(diag_layer)
241
+ mux._resolve_snrefs(context)
243
242
  for env_data in self.env_datas:
244
- env_data._resolve_snrefs(diag_layer)
243
+ env_data._resolve_snrefs(context)
245
244
  if self.unit_spec is not None:
246
- self.unit_spec._resolve_snrefs(diag_layer)
245
+ self.unit_spec._resolve_snrefs(context)
247
246
  for table in self.tables:
248
- table._resolve_snrefs(diag_layer)
247
+ table._resolve_snrefs(context)
249
248
  for sdg in self.sdgs:
250
- sdg._resolve_snrefs(diag_layer)
249
+ sdg._resolve_snrefs(context)
251
250
 
252
251
  @property
253
252
  def all_data_object_properties(self) -> NamedItemList[DopBase]:
odxtools/diaglayer.py CHANGED
@@ -1,11 +1,12 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import re
3
3
  import warnings
4
- from copy import copy
4
+ from copy import copy, deepcopy
5
5
  from dataclasses import dataclass
6
6
  from functools import cached_property
7
7
  from itertools import chain
8
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union, cast
8
+ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar,
9
+ Union, cast)
9
10
  from xml.etree import ElementTree
10
11
 
11
12
  from deprecation import deprecated
@@ -16,6 +17,7 @@ from .companydata import CompanyData
16
17
  from .comparaminstance import ComparamInstance
17
18
  from .comparamspec import ComparamSpec
18
19
  from .comparamsubset import ComparamSubset
20
+ from .description import Description
19
21
  from .diagcomm import DiagComm
20
22
  from .diagdatadictionaryspec import DiagDataDictionarySpec
21
23
  from .diaglayerraw import DiagLayerRaw
@@ -33,12 +35,16 @@ from .request import Request
33
35
  from .response import Response
34
36
  from .servicebinner import ServiceBinner
35
37
  from .singleecujob import SingleEcuJob
38
+ from .snrefcontext import SnRefContext
36
39
  from .specialdatagroup import SpecialDataGroup
37
40
  from .statechart import StateChart
38
41
  from .table import Table
39
42
  from .unitgroup import UnitGroup
40
43
  from .unitspec import UnitSpec
41
44
 
45
+ if TYPE_CHECKING:
46
+ from .database import Database
47
+
42
48
  TNamed = TypeVar("TNamed", bound=OdxNamed)
43
49
 
44
50
  PrefixTree = Dict[int, Union[List[DiagService], "PrefixTree"]]
@@ -124,7 +130,22 @@ class DiagLayer:
124
130
 
125
131
  self.diag_layer_raw._resolve_odxlinks(odxlinks)
126
132
 
127
- def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
133
+ def __deepcopy__(self, memo: Dict[int, Any]) -> Any:
134
+ """Create a deep copy of the diagnostic layer
135
+
136
+ Note that the copied diagnostic layer is not fully
137
+ initialized, so `_finalize_init()` should to be called on it
138
+ before it can be used normally.
139
+ """
140
+ cls = self.__class__
141
+ result = cls.__new__(cls)
142
+ memo[id(self)] = result
143
+
144
+ result.diag_layer_raw = deepcopy(self.diag_layer_raw, memo)
145
+
146
+ return result
147
+
148
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
128
149
  """This method deals with everything inheritance related and
129
150
  -- after the final set of objects covered by the diagnostic
130
151
  layer is determined -- resolves any short name references in
@@ -140,6 +161,11 @@ class DiagLayer:
140
161
  excessive memory consumption for large databases...
141
162
  """
142
163
 
164
+ # this attribute may be removed later. it is currently
165
+ # required to properly deal with auxiliary files within the
166
+ # diagnostic layer.
167
+ self._database = database
168
+
143
169
  #####
144
170
  # fill in all applicable objects that use value inheritance
145
171
  #####
@@ -276,7 +302,10 @@ class DiagLayer:
276
302
  # by the spec (So far, I haven't found any definitive
277
303
  # statement...)
278
304
  #####
279
- self.diag_layer_raw._resolve_snrefs(self)
305
+ context = SnRefContext(database=database)
306
+ context.diag_layer = self
307
+ self.diag_layer_raw._resolve_snrefs(context)
308
+ context.diag_layer = None
280
309
 
281
310
  #####
282
311
  # <convenience functionality>
@@ -309,7 +338,7 @@ class DiagLayer:
309
338
  return self.diag_layer_raw.long_name
310
339
 
311
340
  @property
312
- def description(self) -> Optional[str]:
341
+ def description(self) -> Optional[Description]:
313
342
  return self.diag_layer_raw.description
314
343
 
315
344
  @property