odxtools 8.3.4__py3-none-any.whl → 9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. odxtools/basicstructure.py +36 -207
  2. odxtools/cli/_print_utils.py +163 -131
  3. odxtools/cli/browse.py +94 -79
  4. odxtools/cli/compare.py +88 -69
  5. odxtools/cli/list.py +2 -3
  6. odxtools/codec.py +211 -0
  7. odxtools/comparamspec.py +16 -54
  8. odxtools/comparamsubset.py +20 -61
  9. odxtools/database.py +18 -2
  10. odxtools/diaglayercontainer.py +14 -40
  11. odxtools/diaglayers/hierarchyelement.py +0 -10
  12. odxtools/dopbase.py +5 -3
  13. odxtools/dtcdop.py +101 -14
  14. odxtools/inputparam.py +0 -7
  15. odxtools/leadinglengthinfotype.py +1 -8
  16. odxtools/message.py +0 -7
  17. odxtools/minmaxlengthtype.py +4 -4
  18. odxtools/odxcategory.py +83 -0
  19. odxtools/outputparam.py +0 -7
  20. odxtools/parameterinfo.py +12 -12
  21. odxtools/parameters/lengthkeyparameter.py +1 -2
  22. odxtools/parameters/parameter.py +6 -4
  23. odxtools/parameters/tablekeyparameter.py +9 -8
  24. odxtools/paramlengthinfotype.py +8 -9
  25. odxtools/request.py +109 -16
  26. odxtools/response.py +115 -15
  27. odxtools/specialdatagroup.py +1 -1
  28. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -21
  29. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +4 -22
  30. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -25
  31. odxtools/templates/macros/printDOP.xml.jinja2 +16 -0
  32. odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
  33. odxtools/uds.py +0 -8
  34. odxtools/version.py +2 -2
  35. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/METADATA +7 -8
  36. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/RECORD +40 -37
  37. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/WHEEL +1 -1
  38. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/LICENSE +0 -0
  39. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/entry_points.txt +0 -0
  40. {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/top_level.txt +0 -0
@@ -1,50 +1,41 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
- from .admindata import AdminData
7
- from .companydata import CompanyData
8
6
  from .comparam import Comparam
9
7
  from .complexcomparam import ComplexComparam
10
8
  from .dataobjectproperty import DataObjectProperty
11
- from .element import IdentifiableElement
12
- from .exceptions import odxrequire
13
9
  from .nameditemlist import NamedItemList
10
+ from .odxcategory import OdxCategory
14
11
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
15
12
  from .snrefcontext import SnRefContext
16
- from .specialdatagroup import SpecialDataGroup
17
13
  from .unitspec import UnitSpec
18
14
  from .utils import dataclass_fields_asdict
19
15
 
16
+ if TYPE_CHECKING:
17
+ from .database import Database
18
+
20
19
 
21
20
  @dataclass
22
- class ComparamSubset(IdentifiableElement):
23
- # mandatory in ODX 2.2, but non existent in ODX 2.0
21
+ class ComparamSubset(OdxCategory):
22
+ # mandatory in ODX 2.2, but non-existent in ODX 2.0
24
23
  category: Optional[str]
25
- data_object_props: NamedItemList[DataObjectProperty]
24
+
26
25
  comparams: NamedItemList[Comparam]
27
26
  complex_comparams: NamedItemList[ComplexComparam]
27
+ data_object_props: NamedItemList[DataObjectProperty]
28
28
  unit_spec: Optional[UnitSpec]
29
- admin_data: Optional[AdminData]
30
- company_datas: NamedItemList[CompanyData]
31
- sdgs: List[SpecialDataGroup]
32
29
 
33
30
  @staticmethod
34
31
  def from_et(et_element: ElementTree.Element,
35
32
  doc_frags: List[OdxDocFragment]) -> "ComparamSubset":
36
33
 
37
- category = et_element.get("CATEGORY")
38
-
39
- short_name = odxrequire(et_element.findtext("SHORT-NAME"))
40
- doc_frags = [OdxDocFragment(short_name, str(et_element.tag))]
41
- kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
34
+ cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="COMPARAM-SUBSET")
35
+ doc_frags = cat.odx_id.doc_fragments
36
+ kwargs = dataclass_fields_asdict(cat)
42
37
 
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
- ])
38
+ category = et_element.get("CATEGORY")
48
39
 
49
40
  data_object_props = NamedItemList([
50
41
  DataObjectProperty.from_et(el, doc_frags)
@@ -61,25 +52,16 @@ class ComparamSubset(IdentifiableElement):
61
52
  else:
62
53
  unit_spec = None
63
54
 
64
- sdgs = [
65
- SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
66
- ]
67
-
68
55
  return ComparamSubset(
69
56
  category=category,
70
- admin_data=admin_data,
71
- company_datas=company_datas,
72
57
  data_object_props=data_object_props,
73
58
  comparams=comparams,
74
59
  complex_comparams=complex_comparams,
75
60
  unit_spec=unit_spec,
76
- sdgs=sdgs,
77
61
  **kwargs)
78
62
 
79
63
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
80
- odxlinks: Dict[OdxLinkId, Any] = {}
81
- if self.odx_id is not None:
82
- odxlinks[self.odx_id] = self
64
+ odxlinks = super()._build_odxlinks()
83
65
 
84
66
  for dop in self.data_object_props:
85
67
  odxlinks[dop.odx_id] = dop
@@ -93,19 +75,11 @@ class ComparamSubset(IdentifiableElement):
93
75
  if self.unit_spec:
94
76
  odxlinks.update(self.unit_spec._build_odxlinks())
95
77
 
96
- if self.admin_data is not None:
97
- odxlinks.update(self.admin_data._build_odxlinks())
98
-
99
- if self.company_datas is not None:
100
- for cd in self.company_datas:
101
- odxlinks.update(cd._build_odxlinks())
102
-
103
- for sdg in self.sdgs:
104
- odxlinks.update(sdg._build_odxlinks())
105
-
106
78
  return odxlinks
107
79
 
108
80
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
81
+ super()._resolve_odxlinks(odxlinks)
82
+
109
83
  for dop in self.data_object_props:
110
84
  dop._resolve_odxlinks(odxlinks)
111
85
 
@@ -118,17 +92,12 @@ class ComparamSubset(IdentifiableElement):
118
92
  if self.unit_spec:
119
93
  self.unit_spec._resolve_odxlinks(odxlinks)
120
94
 
121
- if self.admin_data is not None:
122
- self.admin_data._resolve_odxlinks(odxlinks)
123
-
124
- if self.company_datas is not None:
125
- for cd in self.company_datas:
126
- cd._resolve_odxlinks(odxlinks)
127
-
128
- for sdg in self.sdgs:
129
- sdg._resolve_odxlinks(odxlinks)
95
+ def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
96
+ super()._finalize_init(database, odxlinks)
130
97
 
131
98
  def _resolve_snrefs(self, context: SnRefContext) -> None:
99
+ super()._resolve_snrefs(context)
100
+
132
101
  for dop in self.data_object_props:
133
102
  dop._resolve_snrefs(context)
134
103
 
@@ -140,13 +109,3 @@ class ComparamSubset(IdentifiableElement):
140
109
 
141
110
  if self.unit_spec:
142
111
  self.unit_spec._resolve_snrefs(context)
143
-
144
- if self.admin_data is not None:
145
- self.admin_data._resolve_snrefs(context)
146
-
147
- if self.company_datas is not None:
148
- for cd in self.company_datas:
149
- cd._resolve_snrefs(context)
150
-
151
- for sdg in self.sdgs:
152
- sdg._resolve_snrefs(context)
odxtools/database.py CHANGED
@@ -20,6 +20,7 @@ from .diaglayers.protocol import Protocol
20
20
  from .exceptions import odxraise, odxrequire
21
21
  from .nameditemlist import NamedItemList
22
22
  from .odxlink import OdxLinkDatabase, OdxLinkId
23
+ from .snrefcontext import SnRefContext
23
24
 
24
25
 
25
26
  class Database:
@@ -139,11 +140,26 @@ class Database:
139
140
  for dlc in self.diag_layer_containers:
140
141
  dlc._resolve_odxlinks(self._odxlinks)
141
142
 
142
- # let the diaglayers sort out the inherited objects and the
143
- # short name references
143
+ # resolve short name references for containers which do not do
144
+ # inheritance (we can call directly call _resolve_snrefs())
145
+ context = SnRefContext()
146
+ context.database = self
147
+
148
+ # let the diaglayers sort out the inherited objects
149
+ for subset in self.comparam_subsets:
150
+ subset._finalize_init(self, self._odxlinks)
151
+ for spec in self.comparam_specs:
152
+ spec._finalize_init(self, self._odxlinks)
144
153
  for dlc in self.diag_layer_containers:
145
154
  dlc._finalize_init(self, self._odxlinks)
146
155
 
156
+ for subset in self.comparam_subsets:
157
+ subset._resolve_snrefs(context)
158
+ for spec in self.comparam_specs:
159
+ spec._resolve_snrefs(context)
160
+ for dlc in self.diag_layer_containers:
161
+ dlc._resolve_snrefs(context)
162
+
147
163
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
148
164
  result: Dict[OdxLinkId, Any] = {}
149
165
 
@@ -1,22 +1,19 @@
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, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Union
5
5
  from xml.etree import ElementTree
6
6
 
7
- from .admindata import AdminData
8
- from .companydata import CompanyData
9
7
  from .diaglayers.basevariant import BaseVariant
10
8
  from .diaglayers.diaglayer import DiagLayer
11
9
  from .diaglayers.ecushareddata import EcuSharedData
12
10
  from .diaglayers.ecuvariant import EcuVariant
13
11
  from .diaglayers.functionalgroup import FunctionalGroup
14
12
  from .diaglayers.protocol import Protocol
15
- from .element import IdentifiableElement
16
- from .exceptions import odxrequire
17
13
  from .nameditemlist import NamedItemList
14
+ from .odxcategory import OdxCategory
18
15
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
19
- from .specialdatagroup import SpecialDataGroup
16
+ from .snrefcontext import SnRefContext
20
17
  from .utils import dataclass_fields_asdict
21
18
 
22
19
  if TYPE_CHECKING:
@@ -24,15 +21,12 @@ if TYPE_CHECKING:
24
21
 
25
22
 
26
23
  @dataclass
27
- class DiagLayerContainer(IdentifiableElement):
28
- admin_data: Optional[AdminData]
29
- company_datas: NamedItemList[CompanyData]
24
+ class DiagLayerContainer(OdxCategory):
30
25
  ecu_shared_datas: NamedItemList[EcuSharedData]
31
26
  protocols: NamedItemList[Protocol]
32
27
  functional_groups: NamedItemList[FunctionalGroup]
33
28
  base_variants: NamedItemList[BaseVariant]
34
29
  ecu_variants: NamedItemList[EcuVariant]
35
- sdgs: List[SpecialDataGroup]
36
30
 
37
31
  @property
38
32
  def ecus(self) -> NamedItemList[EcuVariant]:
@@ -54,17 +48,10 @@ class DiagLayerContainer(IdentifiableElement):
54
48
  def from_et(et_element: ElementTree.Element,
55
49
  doc_frags: List[OdxDocFragment]) -> "DiagLayerContainer":
56
50
 
57
- short_name = odxrequire(et_element.findtext("SHORT-NAME"))
58
- # create the current ODX "document fragment" (description of the
59
- # current document for references and IDs)
60
- doc_frags = [OdxDocFragment(short_name, "CONTAINER")]
61
- kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
51
+ cat = OdxCategory.category_from_et(et_element, doc_frags, doc_type="CONTAINER")
52
+ doc_frags = cat.odx_id.doc_fragments
53
+ kwargs = dataclass_fields_asdict(cat)
62
54
 
63
- admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
64
- company_datas = NamedItemList([
65
- CompanyData.from_et(cde, doc_frags)
66
- for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
67
- ])
68
55
  ecu_shared_datas = NamedItemList([
69
56
  EcuSharedData.from_et(dl_element, doc_frags)
70
57
  for dl_element in et_element.iterfind("ECU-SHARED-DATAS/ECU-SHARED-DATA")
@@ -85,30 +72,17 @@ class DiagLayerContainer(IdentifiableElement):
85
72
  EcuVariant.from_et(dl_element, doc_frags)
86
73
  for dl_element in et_element.iterfind("ECU-VARIANTS/ECU-VARIANT")
87
74
  ])
88
- sdgs = [
89
- SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
90
- ]
91
75
 
92
76
  return DiagLayerContainer(
93
- admin_data=admin_data,
94
- company_datas=company_datas,
95
77
  ecu_shared_datas=ecu_shared_datas,
96
78
  protocols=protocols,
97
79
  functional_groups=functional_groups,
98
80
  base_variants=base_variants,
99
81
  ecu_variants=ecu_variants,
100
- sdgs=sdgs,
101
82
  **kwargs)
102
83
 
103
84
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
104
- result = {self.odx_id: self}
105
-
106
- if self.admin_data is not None:
107
- result.update(self.admin_data._build_odxlinks())
108
- for cd in self.company_datas:
109
- result.update(cd._build_odxlinks())
110
- for sdg in self.sdgs:
111
- result.update(sdg._build_odxlinks())
85
+ result = super()._build_odxlinks()
112
86
 
113
87
  for ecu_shared_data in self.ecu_shared_datas:
114
88
  result.update(ecu_shared_data._build_odxlinks())
@@ -124,12 +98,7 @@ class DiagLayerContainer(IdentifiableElement):
124
98
  return result
125
99
 
126
100
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
127
- if self.admin_data is not None:
128
- self.admin_data._resolve_odxlinks(odxlinks)
129
- for cd in self.company_datas:
130
- cd._resolve_odxlinks(odxlinks)
131
- for sdg in self.sdgs:
132
- sdg._resolve_odxlinks(odxlinks)
101
+ super()._resolve_odxlinks(odxlinks)
133
102
 
134
103
  for ecu_shared_data in self.ecu_shared_datas:
135
104
  ecu_shared_data._resolve_odxlinks(odxlinks)
@@ -143,6 +112,8 @@ class DiagLayerContainer(IdentifiableElement):
143
112
  ecu_variant._resolve_odxlinks(odxlinks)
144
113
 
145
114
  def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
115
+ super()._finalize_init(database, odxlinks)
116
+
146
117
  for ecu_shared_data in self.ecu_shared_datas:
147
118
  ecu_shared_data._finalize_init(database, odxlinks)
148
119
  for protocol in self.protocols:
@@ -154,6 +125,9 @@ class DiagLayerContainer(IdentifiableElement):
154
125
  for ecu_variant in self.ecu_variants:
155
126
  ecu_variant._finalize_init(database, odxlinks)
156
127
 
128
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
129
+ super()._resolve_snrefs(context)
130
+
157
131
  @property
158
132
  def diag_layers(self) -> NamedItemList[DiagLayer]:
159
133
  return self._diag_layers
@@ -8,8 +8,6 @@ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional
8
8
  Union, cast)
9
9
  from xml.etree import ElementTree
10
10
 
11
- from deprecation import deprecated
12
-
13
11
  from ..additionalaudience import AdditionalAudience
14
12
  from ..admindata import AdminData
15
13
  from ..comparaminstance import ComparamInstance
@@ -724,10 +722,6 @@ class HierarchyElement(DiagLayer):
724
722
 
725
723
  return int(result)
726
724
 
727
- @deprecated(details="use get_can_receive_id()") # type: ignore[misc]
728
- def get_receive_id(self) -> Optional[int]:
729
- return self.get_can_receive_id()
730
-
731
725
  def get_can_send_id(self, protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
732
726
  """CAN ID to which the ECU sends replies to diagnostic messages"""
733
727
 
@@ -753,10 +747,6 @@ class HierarchyElement(DiagLayer):
753
747
 
754
748
  return int(result)
755
749
 
756
- @deprecated(details="use get_can_send_id()") # type: ignore[misc]
757
- def get_send_id(self) -> Optional[int]:
758
- return self.get_can_send_id()
759
-
760
750
  def get_can_func_req_id(self,
761
751
  protocol: Optional[Union[str, "Protocol"]] = None) -> Optional[int]:
762
752
  """CAN Functional Request Id."""
odxtools/dopbase.py CHANGED
@@ -16,10 +16,12 @@ from .utils import dataclass_fields_asdict
16
16
 
17
17
  @dataclass
18
18
  class DopBase(IdentifiableElement):
19
- """Base class for all DOPs.
19
+ """Base class for all (simple and complex) data object properties.
20
+
21
+ Any class that a parameter can reference via a DOP-REF (Simple
22
+ DOPs, structures, ...) inherits from this class. All DOPs objects
23
+ implement the `Codec` type protocol.
20
24
 
21
- Any class that a parameter can reference via a DOP-REF should
22
- inherit from this class.
23
25
  """
24
26
 
25
27
  admin_data: Optional[AdminData]
odxtools/dtcdop.py CHANGED
@@ -15,13 +15,59 @@ from .dopbase import DopBase
15
15
  from .encodestate import EncodeState
16
16
  from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
17
17
  from .nameditemlist import NamedItemList
18
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
19
19
  from .odxtypes import ParameterValue, odxstr_to_bool
20
20
  from .physicaltype import PhysicalType
21
21
  from .snrefcontext import SnRefContext
22
22
  from .utils import dataclass_fields_asdict
23
23
 
24
24
 
25
+ @dataclass
26
+ class LinkedDtcDop:
27
+ not_inherited_dtc_snrefs: List[str]
28
+ dtc_dop_ref: OdxLinkRef
29
+
30
+ @property
31
+ def dtc_dop(self) -> "DtcDop":
32
+ return self._dtc_dop
33
+
34
+ @property
35
+ def short_name(self) -> str:
36
+ return self._dtc_dop.short_name
37
+
38
+ @property
39
+ def not_inherited_dtcs(self) -> NamedItemList[DiagnosticTroubleCode]:
40
+ return self._not_inherited_dtcs
41
+
42
+ @staticmethod
43
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "LinkedDtcDop":
44
+ not_inherited_dtc_snrefs = [
45
+ odxrequire(el.get("SHORT-NAME"))
46
+ for el in et_element.iterfind("NOT-INHERITED-DTC-SNREFS/"
47
+ "NOT-INHERITED-DTC-SNREF")
48
+ ]
49
+
50
+ dtc_dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DTC-DOP-REF"), doc_frags))
51
+
52
+ return LinkedDtcDop(
53
+ not_inherited_dtc_snrefs=not_inherited_dtc_snrefs, dtc_dop_ref=dtc_dop_ref)
54
+
55
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
56
+ return {}
57
+
58
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
59
+ self._dtc_dop = odxlinks.resolve(self.dtc_dop_ref, DtcDop)
60
+
61
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
62
+ dtc_dop = self._dtc_dop
63
+ not_inherited_dtcs = [
64
+ resolve_snref(ni_snref, dtc_dop.dtcs, DiagnosticTroubleCode)
65
+ for ni_snref in self.not_inherited_dtc_snrefs
66
+ ]
67
+
68
+ self._not_inherited_dtcs = NamedItemList(not_inherited_dtcs)
69
+
70
+
25
71
  @dataclass
26
72
  class DtcDop(DopBase):
27
73
  """A DOP describing a diagnostic trouble code"""
@@ -30,9 +76,12 @@ class DtcDop(DopBase):
30
76
  physical_type: PhysicalType
31
77
  compu_method: CompuMethod
32
78
  dtcs_raw: List[Union[DiagnosticTroubleCode, OdxLinkRef]]
33
- linked_dtc_dop_refs: List[OdxLinkRef]
79
+ linked_dtc_dops_raw: List[LinkedDtcDop]
34
80
  is_visible_raw: Optional[bool]
35
81
 
82
+ def __post_init__(self) -> None:
83
+ self._init_finished = False
84
+
36
85
  @staticmethod
37
86
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "DtcDop":
38
87
  """Reads a DTC-DOP."""
@@ -56,12 +105,10 @@ class DtcDop(DopBase):
56
105
  elif dtc_proxy_elem.tag == "DTC-REF":
57
106
  dtcs_raw.append(OdxLinkRef.from_et(dtc_proxy_elem, doc_frags))
58
107
 
59
- # TODO: NOT-INHERITED-DTC-SNREFS
60
- linked_dtc_dop_refs = [
61
- OdxLinkRef.from_et(dtc_ref_elem, doc_frags)
108
+ linked_dtc_dops_raw = [
109
+ LinkedDtcDop.from_et(dtc_ref_elem, doc_frags)
62
110
  for dtc_ref_elem in et_element.iterfind("LINKED-DTC-DOPS/"
63
- "LINKED-DTC-DOP/"
64
- "DTC-DOP-REF")
111
+ "LINKED-DTC-DOP")
65
112
  ]
66
113
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
67
114
 
@@ -70,7 +117,7 @@ class DtcDop(DopBase):
70
117
  physical_type=physical_type,
71
118
  compu_method=compu_method,
72
119
  dtcs_raw=dtcs_raw,
73
- linked_dtc_dop_refs=linked_dtc_dop_refs,
120
+ linked_dtc_dops_raw=linked_dtc_dops_raw,
74
121
  is_visible_raw=is_visible_raw,
75
122
  **kwargs)
76
123
 
@@ -79,12 +126,12 @@ class DtcDop(DopBase):
79
126
  return self._dtcs
80
127
 
81
128
  @property
82
- def is_visible(self) -> bool:
83
- return self.is_visible_raw is True
129
+ def linked_dtc_dops(self) -> NamedItemList[LinkedDtcDop]:
130
+ return self._linked_dtc_dops
84
131
 
85
132
  @property
86
- def linked_dtc_dops(self) -> NamedItemList["DtcDop"]:
87
- return self._linked_dtc_dops
133
+ def is_visible(self) -> bool:
134
+ return self.is_visible_raw is True
88
135
 
89
136
  @override
90
137
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
@@ -185,6 +232,9 @@ class DtcDop(DopBase):
185
232
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
186
233
  odxlinks.update(dtc_proxy._build_odxlinks())
187
234
 
235
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
236
+ odxlinks.update(linked_dtc_dop._build_odxlinks())
237
+
188
238
  return odxlinks
189
239
 
190
240
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -201,10 +251,19 @@ class DtcDop(DopBase):
201
251
  dtc = odxlinks.resolve(dtc_proxy, DiagnosticTroubleCode)
202
252
  self._dtcs.append(dtc)
203
253
 
204
- linked_dtc_dops = [odxlinks.resolve(x, DtcDop) for x in self.linked_dtc_dop_refs]
205
- self._linked_dtc_dops = NamedItemList(linked_dtc_dops)
254
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
255
+ linked_dtc_dop._resolve_odxlinks(odxlinks)
206
256
 
207
257
  def _resolve_snrefs(self, context: SnRefContext) -> None:
258
+ # hack to avoid initializing the DtcDop object multiple
259
+ # times. This is required, because the linked DTC DOP feature
260
+ # requires the "parent" DTC DOPs to be fully initialized but
261
+ # the standard does not define a formal ordering of DTC DOPs.
262
+ if self._init_finished:
263
+ return
264
+
265
+ self._init_finished = True
266
+
208
267
  super()._resolve_snrefs(context)
209
268
 
210
269
  self.compu_method._resolve_snrefs(context)
@@ -212,3 +271,31 @@ class DtcDop(DopBase):
212
271
  for dtc_proxy in self.dtcs_raw:
213
272
  if isinstance(dtc_proxy, DiagnosticTroubleCode):
214
273
  dtc_proxy._resolve_snrefs(context)
274
+
275
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
276
+ linked_dtc_dop._resolve_snrefs(context)
277
+
278
+ # add the inherited DTCs from linked DTC DOPs. Note that this
279
+ # requires that there are no cycles in the "link-hierarchy"
280
+ dtc_short_names = {dtc.short_name for dtc in self._dtcs}
281
+ for linked_dtc_dop in self.linked_dtc_dops_raw:
282
+ linked_dtc_dop.dtc_dop._resolve_snrefs(context)
283
+
284
+ for dtc in linked_dtc_dop.dtc_dop.dtcs:
285
+ if dtc.short_name in dtc_short_names:
286
+ # we already have a DTC with that name. Since we
287
+ # are not supposed to overwrite the local DTCs, we
288
+ # skip processing this one. TODO: Are inheritance
289
+ # conflicts for DTCs allowed?
290
+ continue
291
+
292
+ if dtc.short_name in linked_dtc_dop.not_inherited_dtc_snrefs:
293
+ # DTC is explicitly not inherited
294
+ continue
295
+
296
+ self._dtcs.append(dtc)
297
+ dtc_short_names.add(dtc.short_name)
298
+
299
+ # at this place, the linked DTC DOPs exhibit .short_name, so
300
+ # we can create a NamedItemList...
301
+ self._linked_dtc_dops = NamedItemList(self.linked_dtc_dops_raw)
odxtools/inputparam.py CHANGED
@@ -3,8 +3,6 @@ from dataclasses import dataclass
3
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 NamedElement
10
8
  from .exceptions import odxrequire
@@ -49,8 +47,3 @@ class InputParam(NamedElement):
49
47
  def dop_base(self) -> DopBase:
50
48
  """The data object property describing this parameter."""
51
49
  return self._dop_base
52
-
53
- @property
54
- @deprecated(details="use .dop_base") # type: ignore[misc]
55
- def dop(self) -> DopBase:
56
- return self._dop_base
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from typing import List
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from typing_extensions import override
@@ -50,13 +50,6 @@ class LeadingLengthInfoType(DiagCodedType):
50
50
  def dct_type(self) -> DctType:
51
51
  return "LEADING-LENGTH-INFO-TYPE"
52
52
 
53
- def get_static_bit_length(self) -> Optional[int]:
54
- # note that self.bit_length is just the length of the length
55
- # specifier field. This is then followed by the same number of
56
- # bytes as the value of this field, i.e., the length of this
57
- # DCT is dynamic!
58
- return None
59
-
60
53
  @override
61
54
  def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
62
55
 
odxtools/message.py CHANGED
@@ -2,8 +2,6 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Union
4
4
 
5
- from deprecation import deprecated
6
-
7
5
  from .odxtypes import ParameterValue, ParameterValueDict
8
6
 
9
7
  if TYPE_CHECKING:
@@ -29,8 +27,3 @@ class Message:
29
27
 
30
28
  def __getitem__(self, key: str) -> ParameterValue:
31
29
  return self.param_dict[key]
32
-
33
- @property
34
- @deprecated("use .coding_object") # type: ignore[misc]
35
- def structure(self) -> Union["Request", "Response"]:
36
- return self.coding_object
@@ -20,6 +20,10 @@ class MinMaxLengthType(DiagCodedType):
20
20
  max_length: Optional[int]
21
21
  termination: str
22
22
 
23
+ @property
24
+ def dct_type(self) -> DctType:
25
+ return "MIN-MAX-LENGTH-TYPE"
26
+
23
27
  @staticmethod
24
28
  @override
25
29
  def from_et(et_element: ElementTree.Element,
@@ -50,10 +54,6 @@ class MinMaxLengthType(DiagCodedType):
50
54
  "END-OF-PDU",
51
55
  ], f"A min-max length type cannot have the termination {self.termination}")
52
56
 
53
- @property
54
- def dct_type(self) -> DctType:
55
- return "MIN-MAX-LENGTH-TYPE"
56
-
57
57
  def __termination_sequence(self) -> bytes:
58
58
  """Returns the termination byte sequence if it isn't defined."""
59
59
  # The termination sequence is actually not specified by ASAM