odxtools 5.3.1__py3-none-any.whl → 6.0.1__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 (88) hide show
  1. odxtools/__init__.py +1 -1
  2. odxtools/basicstructure.py +76 -91
  3. odxtools/cli/_parser_utils.py +12 -9
  4. odxtools/cli/_print_utils.py +7 -7
  5. odxtools/cli/browse.py +94 -73
  6. odxtools/cli/find.py +42 -59
  7. odxtools/cli/list.py +21 -17
  8. odxtools/cli/snoop.py +19 -18
  9. odxtools/communicationparameterref.py +6 -3
  10. odxtools/companydocinfo.py +2 -2
  11. odxtools/companyrevisioninfo.py +1 -1
  12. odxtools/comparamsubset.py +6 -6
  13. odxtools/complexcomparam.py +1 -1
  14. odxtools/compumethods/compumethod.py +6 -9
  15. odxtools/compumethods/createanycompumethod.py +11 -9
  16. odxtools/compumethods/identicalcompumethod.py +5 -4
  17. odxtools/compumethods/limit.py +9 -9
  18. odxtools/compumethods/linearcompumethod.py +25 -17
  19. odxtools/compumethods/scalelinearcompumethod.py +6 -5
  20. odxtools/compumethods/tabintpcompumethod.py +30 -9
  21. odxtools/compumethods/texttablecompumethod.py +22 -24
  22. odxtools/database.py +5 -5
  23. odxtools/dataobjectproperty.py +10 -23
  24. odxtools/decodestate.py +1 -1
  25. odxtools/determinenumberofitems.py +37 -8
  26. odxtools/diagcodedtype.py +14 -9
  27. odxtools/diagdatadictionaryspec.py +60 -37
  28. odxtools/diaglayer.py +30 -21
  29. odxtools/diaglayercontainer.py +40 -40
  30. odxtools/diaglayerraw.py +92 -63
  31. odxtools/diagnostictroublecode.py +2 -2
  32. odxtools/diagservice.py +53 -35
  33. odxtools/docrevision.py +1 -1
  34. odxtools/dopbase.py +14 -3
  35. odxtools/dtcdop.py +15 -9
  36. odxtools/dynamiclengthfield.py +6 -4
  37. odxtools/endofpdufield.py +22 -23
  38. odxtools/environmentdata.py +2 -5
  39. odxtools/environmentdatadescription.py +6 -4
  40. odxtools/field.py +3 -8
  41. odxtools/isotp_state_machine.py +52 -38
  42. odxtools/leadinglengthinfotype.py +9 -7
  43. odxtools/load_file.py +2 -1
  44. odxtools/load_odx_d_file.py +2 -5
  45. odxtools/load_pdx_file.py +2 -6
  46. odxtools/message.py +11 -3
  47. odxtools/minmaxlengthtype.py +107 -78
  48. odxtools/modification.py +2 -2
  49. odxtools/multiplexer.py +23 -21
  50. odxtools/multiplexerswitchkey.py +37 -8
  51. odxtools/nameditemlist.py +59 -58
  52. odxtools/odxlink.py +4 -2
  53. odxtools/odxtypes.py +4 -3
  54. odxtools/parameterinfo.py +6 -6
  55. odxtools/parameters/codedconstparameter.py +15 -25
  56. odxtools/parameters/createanyparameter.py +1 -1
  57. odxtools/parameters/dynamicparameter.py +6 -5
  58. odxtools/parameters/lengthkeyparameter.py +2 -1
  59. odxtools/parameters/matchingrequestparameter.py +8 -11
  60. odxtools/parameters/nrcconstparameter.py +11 -21
  61. odxtools/parameters/parameter.py +4 -18
  62. odxtools/parameters/parameterwithdop.py +14 -29
  63. odxtools/parameters/physicalconstantparameter.py +7 -9
  64. odxtools/parameters/reservedparameter.py +17 -38
  65. odxtools/parameters/systemparameter.py +6 -5
  66. odxtools/parameters/tableentryparameter.py +6 -5
  67. odxtools/parameters/tablekeyparameter.py +8 -15
  68. odxtools/parameters/tablestructparameter.py +11 -12
  69. odxtools/parameters/valueparameter.py +9 -24
  70. odxtools/paramlengthinfotype.py +11 -9
  71. odxtools/physicaldimension.py +1 -1
  72. odxtools/physicaltype.py +2 -2
  73. odxtools/response.py +7 -3
  74. odxtools/singleecujob.py +48 -22
  75. odxtools/standardlengthtype.py +11 -6
  76. odxtools/uds.py +1 -1
  77. odxtools/unit.py +5 -5
  78. odxtools/unitgroup.py +1 -1
  79. odxtools/unitspec.py +2 -2
  80. odxtools/version.py +13 -3
  81. odxtools/write_pdx_file.py +7 -4
  82. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
  83. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
  84. odxtools/positioneddataobjectproperty.py +0 -74
  85. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
  86. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
  87. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
  88. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/top_level.txt +0 -0
@@ -15,9 +15,8 @@ from .element import IdentifiableElement
15
15
  from .encodestate import EncodeState
16
16
  from .exceptions import DecodeError, EncodeError, odxassert, odxrequire
17
17
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
18
- from .odxtypes import odxstr_to_bool
18
+ from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
19
19
  from .physicaltype import PhysicalType
20
- from .standardlengthtype import StandardLengthType
21
20
  from .unit import Unit
22
21
  from .utils import dataclass_fields_asdict
23
22
 
@@ -65,7 +64,7 @@ class DataObjectProperty(DopBase):
65
64
  sdgs=sdgs,
66
65
  **kwargs)
67
66
  else:
68
- dtclist: List[Union[DiagnosticTroubleCode, OdxLinkRef]] = list()
67
+ dtclist: List[Union[DiagnosticTroubleCode, OdxLinkRef]] = []
69
68
  if (dtcs_elem := et_element.find("DTCS")) is not None:
70
69
  for dtc_proxy_elem in dtcs_elem:
71
70
  if dtc_proxy_elem.tag == "DTC":
@@ -100,7 +99,7 @@ class DataObjectProperty(DopBase):
100
99
  result.update(self.diag_coded_type._build_odxlinks())
101
100
  return result
102
101
 
103
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
102
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
104
103
  """Resolves the reference to the unit"""
105
104
  super()._resolve_odxlinks(odxlinks)
106
105
 
@@ -119,14 +118,8 @@ class DataObjectProperty(DopBase):
119
118
  def unit(self) -> Optional[Unit]:
120
119
  return self._unit
121
120
 
122
- @property
123
- def bit_length(self):
124
- # TODO: The DiagCodedTypes except StandardLengthType don't have a bit length.
125
- # Should we remove this bit_length property from DOP or return None?
126
- if isinstance(self.diag_coded_type, StandardLengthType):
127
- return self.diag_coded_type.bit_length
128
- else:
129
- return None
121
+ def get_static_bit_length(self) -> Optional[int]:
122
+ return self.diag_coded_type.get_static_bit_length()
130
123
 
131
124
  def convert_physical_to_internal(self, physical_value: Any) -> Any:
132
125
  """
@@ -145,10 +138,7 @@ class DataObjectProperty(DopBase):
145
138
  """
146
139
  if not self.is_valid_physical_value(physical_value):
147
140
  raise EncodeError(f"The value {repr(physical_value)} of type {type(physical_value)}"
148
- f" is not a valid." +
149
- (f" Valid values are {self.compu_method.get_valid_physical_values()}"
150
- if self.compu_method.get_valid_physical_values(
151
- ) else f" Expected type {self.physical_type.base_data_type.value}."))
141
+ f" is not a valid.")
152
142
 
153
143
  internal_val = self.convert_physical_to_internal(physical_value)
154
144
  return self.diag_coded_type.convert_internal_to_bytes(
@@ -164,19 +154,16 @@ class DataObjectProperty(DopBase):
164
154
  """
165
155
  odxassert(0 <= bit_position and bit_position < 8)
166
156
 
167
- internal, next_byte_position = self.diag_coded_type.convert_bytes_to_internal(
157
+ internal, cursor_position = self.diag_coded_type.convert_bytes_to_internal(
168
158
  decode_state, bit_position=bit_position)
169
159
 
170
160
  if self.compu_method.is_valid_internal_value(internal):
171
- return self.compu_method.convert_internal_to_physical(internal), next_byte_position
161
+ return self.compu_method.convert_internal_to_physical(internal), cursor_position
172
162
  else:
173
163
  # TODO: How to prevent this?
174
164
  raise DecodeError(
175
165
  f"DOP {self.short_name} could not convert the coded value "
176
166
  f" {repr(internal)} to physical type {self.physical_type.base_data_type}.")
177
167
 
178
- def is_valid_physical_value(self, physical_value):
179
- return self.compu_method.is_valid_physical_value(physical_value)
180
-
181
- def get_valid_physical_values(self):
182
- return self.compu_method.get_valid_physical_values()
168
+ def is_valid_physical_value(self, physical_value: ParameterValue) -> bool:
169
+ return self.compu_method.is_valid_physical_value(cast(AtomicOdxType, physical_value))
odxtools/decodestate.py CHANGED
@@ -15,4 +15,4 @@ class DecodeState:
15
15
  parameter_values: ParameterValueDict
16
16
 
17
17
  #: Position of the next parameter if its position is not specified in ODX
18
- next_byte_position: int
18
+ cursor_position: int
@@ -1,18 +1,47 @@
1
1
  from dataclasses import dataclass
2
- from typing import List
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
3
  from xml.etree import ElementTree
4
4
 
5
- from .odxlink import OdxDocFragment
6
- from .positioneddataobjectproperty import PositionedDataObjectProperty
7
- from .utils import dataclass_fields_asdict
5
+ from .dataobjectproperty import DataObjectProperty
6
+ from .exceptions import odxrequire
7
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
8
+
9
+ if TYPE_CHECKING:
10
+ from ..diaglayer import DiagLayer
8
11
 
9
12
 
10
13
  @dataclass
11
- class DetermineNumberOfItems(PositionedDataObjectProperty):
14
+ class DetermineNumberOfItems:
15
+ """
16
+ The object that determines the number of items of dynamic fields
17
+ """
18
+ byte_position: int
19
+ bit_position: Optional[int]
20
+ dop_ref: OdxLinkRef
12
21
 
13
22
  @staticmethod
14
23
  def from_et(et_element: ElementTree.Element,
15
24
  doc_frags: List[OdxDocFragment]) -> "DetermineNumberOfItems":
16
- kwargs = dataclass_fields_asdict(
17
- PositionedDataObjectProperty.from_et(et_element, doc_frags))
18
- return DetermineNumberOfItems(**kwargs)
25
+ byte_position = int(odxrequire(et_element.findtext("BYTE-POSITION")))
26
+ bit_position_str = et_element.findtext("BIT-POSITION")
27
+ bit_position = int(bit_position_str) if bit_position_str is not None else None
28
+ dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), doc_frags))
29
+
30
+ return DetermineNumberOfItems(
31
+ byte_position=byte_position,
32
+ bit_position=bit_position,
33
+ dop_ref=dop_ref,
34
+ )
35
+
36
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
37
+ return {}
38
+
39
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
40
+ self._dop = odxlinks.resolve(self.dop_ref, DataObjectProperty)
41
+
42
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
43
+ pass
44
+
45
+ @property
46
+ def dop(self) -> DataObjectProperty:
47
+ return self._dop
odxtools/diagcodedtype.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Union
5
5
 
6
6
  import bitstruct
7
7
 
@@ -9,12 +9,13 @@ from . import exceptions
9
9
  from .decodestate import DecodeState
10
10
  from .encodestate import EncodeState
11
11
  from .exceptions import DecodeError, EncodeError, odxassert, odxraise
12
- from .odxlink import OdxLinkDatabase
12
+ from .odxlink import OdxLinkDatabase, OdxLinkId
13
13
  from .odxtypes import AtomicOdxType, DataType
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from .diaglayer import DiagLayer
17
17
 
18
+ # format specifiers for the data type using the bitstruct module
18
19
  ODX_TYPE_TO_FORMAT_LETTER = {
19
20
  DataType.A_INT32: "s",
20
21
  DataType.A_UINT32: "u",
@@ -26,6 +27,7 @@ ODX_TYPE_TO_FORMAT_LETTER = {
26
27
  DataType.A_UTF8STRING: "t",
27
28
  }
28
29
 
30
+ # Allowed diag-coded types
29
31
  DctType = Literal[
30
32
  "LEADING-LENGTH-INFO-TYPE",
31
33
  "MIN-MAX-LENGTH-TYPE",
@@ -41,17 +43,20 @@ class DiagCodedType(abc.ABC):
41
43
  base_type_encoding: Optional[str]
42
44
  is_highlow_byte_order_raw: Optional[bool]
43
45
 
44
- def _build_odxlinks(self):
46
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: # noqa: B027
45
47
  return {}
46
48
 
47
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
49
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: # noqa: B027
48
50
  """Recursively resolve any odxlinks references"""
49
51
  pass
50
52
 
51
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
53
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: # noqa: B027
52
54
  """Recursively resolve any short-name references"""
53
55
  pass
54
56
 
57
+ def get_static_bit_length(self) -> Optional[int]:
58
+ return None
59
+
55
60
  @property
56
61
  @abc.abstractmethod
57
62
  def dct_type(self) -> DctType:
@@ -81,8 +86,8 @@ class DiagCodedType(abc.ABC):
81
86
  byte_length = (bit_length + bit_position + 7) // 8
82
87
  if byte_position + byte_length > len(coded_message):
83
88
  raise DecodeError(f"Expected a longer message.")
84
- next_byte_position = byte_position + byte_length
85
- extracted_bytes = coded_message[byte_position:next_byte_position]
89
+ cursor_position = byte_position + byte_length
90
+ extracted_bytes = coded_message[byte_position:cursor_position]
86
91
 
87
92
  # Apply byteorder
88
93
  if not is_highlow_byte_order and base_data_type not in [
@@ -120,7 +125,7 @@ class DiagCodedType(abc.ABC):
120
125
  else:
121
126
  internal_value = internal_value.decode("utf-16-le")
122
127
 
123
- return internal_value, next_byte_position
128
+ return internal_value, cursor_position
124
129
 
125
130
  def _to_bytes(
126
131
  self,
@@ -159,7 +164,7 @@ class DiagCodedType(abc.ABC):
159
164
  ] and base_data_type != 0):
160
165
  raise EncodeError(
161
166
  f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.")
162
- return bytes()
167
+ return b''
163
168
 
164
169
  char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
165
170
 
@@ -40,17 +40,6 @@ class DiagDataDictionarySpec:
40
40
  unit_spec: Optional[UnitSpec]
41
41
  sdgs: List[SpecialDataGroup]
42
42
 
43
- def __post_init__(self):
44
- self._all_data_object_properties = NamedItemList(
45
- chain(
46
- self.data_object_props,
47
- self.structures,
48
- self.end_of_pdu_fields,
49
- self.dynamic_length_fields,
50
- self.dtc_dops,
51
- self.tables,
52
- ),)
53
-
54
43
  @staticmethod
55
44
  def from_et(et_element: ElementTree.Element,
56
45
  doc_frags: List[OdxDocFragment]) -> "DiagDataDictionarySpec":
@@ -145,19 +134,26 @@ class DiagDataDictionarySpec:
145
134
  # note that DataDictionarySpec objects do not exhibit an ODXLINK id.
146
135
  odxlinks = {}
147
136
 
148
- for obj in chain(
149
- self.data_object_props,
150
- self.dtc_dops,
151
- self.env_data_descs,
152
- self.env_datas,
153
- self.muxs,
154
- self.sdgs,
155
- self.structures,
156
- self.end_of_pdu_fields,
157
- self.dynamic_length_fields,
158
- self.tables,
159
- ):
160
- odxlinks.update(obj._build_odxlinks())
137
+ for data_object_prop in self.data_object_props:
138
+ odxlinks.update(data_object_prop._build_odxlinks())
139
+ for dtc_dop in self.dtc_dops:
140
+ odxlinks.update(dtc_dop._build_odxlinks())
141
+ for env_data_desc in self.env_data_descs:
142
+ odxlinks.update(env_data_desc._build_odxlinks())
143
+ for env_data in self.env_datas:
144
+ odxlinks.update(env_data._build_odxlinks())
145
+ for mux in self.muxs:
146
+ odxlinks.update(mux._build_odxlinks())
147
+ for sdg in self.sdgs:
148
+ odxlinks.update(sdg._build_odxlinks())
149
+ for structure in self.structures:
150
+ odxlinks.update(structure._build_odxlinks())
151
+ for dynamic_length_field in self.dynamic_length_fields:
152
+ odxlinks.update(dynamic_length_field._build_odxlinks())
153
+ for end_of_pdu_field in self.end_of_pdu_fields:
154
+ odxlinks.update(end_of_pdu_field._build_odxlinks())
155
+ for table in self.tables:
156
+ odxlinks.update(table._build_odxlinks())
161
157
 
162
158
  if self.unit_spec is not None:
163
159
  odxlinks.update(self.unit_spec._build_odxlinks())
@@ -165,24 +161,51 @@ class DiagDataDictionarySpec:
165
161
  return odxlinks
166
162
 
167
163
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
168
-
169
- for obj in chain(self.data_object_props, self.dtc_dops, self.end_of_pdu_fields,
170
- self.dynamic_length_fields, self.env_data_descs, self.env_datas, self.muxs,
171
- self.sdgs, self.structures, self.tables):
172
- obj._resolve_odxlinks(odxlinks)
164
+ for data_object_prop in self.data_object_props:
165
+ data_object_prop._resolve_odxlinks(odxlinks)
166
+ for dtc_dop in self.dtc_dops:
167
+ dtc_dop._resolve_odxlinks(odxlinks)
168
+ for dynamic_length_field in self.dynamic_length_fields:
169
+ dynamic_length_field._resolve_odxlinks(odxlinks)
170
+ for end_of_pdu_field in self.end_of_pdu_fields:
171
+ end_of_pdu_field._resolve_odxlinks(odxlinks)
172
+ for env_data_desc in self.env_data_descs:
173
+ env_data_desc._resolve_odxlinks(odxlinks)
174
+ for env_data in self.env_datas:
175
+ env_data._resolve_odxlinks(odxlinks)
176
+ for mux in self.muxs:
177
+ mux._resolve_odxlinks(odxlinks)
178
+ for sdg in self.sdgs:
179
+ sdg._resolve_odxlinks(odxlinks)
180
+ for structure in self.structures:
181
+ structure._resolve_odxlinks(odxlinks)
182
+ for table in self.tables:
183
+ table._resolve_odxlinks(odxlinks)
173
184
 
174
185
  if self.unit_spec is not None:
175
186
  self.unit_spec._resolve_odxlinks(odxlinks)
176
187
 
177
188
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
178
- for obj in chain(self.data_object_props, self.dtc_dops, self.end_of_pdu_fields,
179
- self.dynamic_length_fields, self.env_data_descs, self.env_datas, self.muxs,
180
- self.sdgs, self.structures, self.tables):
181
- obj._resolve_snrefs(diag_layer)
189
+ for data_object_prop in self.data_object_props:
190
+ data_object_prop._resolve_snrefs(diag_layer)
191
+ for dtc_dop in self.dtc_dops:
192
+ dtc_dop._resolve_snrefs(diag_layer)
193
+ for dynamic_length_field in self.dynamic_length_fields:
194
+ dynamic_length_field._resolve_snrefs(diag_layer)
195
+ for end_of_pdu_field in self.end_of_pdu_fields:
196
+ end_of_pdu_field._resolve_snrefs(diag_layer)
197
+ for env_data_desc in self.env_data_descs:
198
+ env_data_desc._resolve_snrefs(diag_layer)
199
+ for env_data in self.env_datas:
200
+ env_data._resolve_snrefs(diag_layer)
201
+ for mux in self.muxs:
202
+ mux._resolve_snrefs(diag_layer)
203
+ for sdg in self.sdgs:
204
+ sdg._resolve_snrefs(diag_layer)
205
+ for structure in self.structures:
206
+ structure._resolve_snrefs(diag_layer)
207
+ for table in self.tables:
208
+ table._resolve_snrefs(diag_layer)
182
209
 
183
210
  if self.unit_spec is not None:
184
211
  self.unit_spec._resolve_snrefs(diag_layer)
185
-
186
- @property
187
- def all_data_object_properties(self):
188
- return self._all_data_object_properties
odxtools/diaglayer.py CHANGED
@@ -208,7 +208,8 @@ class DiagLayer:
208
208
  # scheme, cf the docstring of
209
209
  # _compute_available_commmunication_parameters().
210
210
  #####
211
- self._communication_parameters = self._compute_available_commmunication_parameters()
211
+ self._communication_parameters = NamedItemList(
212
+ self._compute_available_commmunication_parameters())
212
213
 
213
214
  #####
214
215
  # resolve all SNREFs. TODO: We allow SNREFS to objects that
@@ -349,7 +350,7 @@ class DiagLayer:
349
350
  #####
350
351
  # <value inheritance mechanism helpers>
351
352
  #####
352
- def _get_parent_refs_sorted_by_priority(self, reverse=False) -> Iterable[ParentRef]:
353
+ def _get_parent_refs_sorted_by_priority(self, reverse: bool = False) -> Iterable[ParentRef]:
353
354
  return sorted(
354
355
  self.diag_layer_raw.parent_refs,
355
356
  key=lambda pr: pr.layer.variant_type.inheritance_priority,
@@ -443,10 +444,10 @@ class DiagLayer:
443
444
  def _compute_available_diag_comms(self, odxlinks: OdxLinkDatabase
444
445
  ) -> Iterable[Union[DiagService, SingleEcuJob]]:
445
446
 
446
- def get_local_objects_fn(dl):
447
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[Union[DiagService, SingleEcuJob]]:
447
448
  return dl._get_local_diag_comms(odxlinks)
448
449
 
449
- def not_inherited_fn(parent_ref):
450
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
450
451
  return parent_ref.not_inherited_diag_comms
451
452
 
452
453
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
@@ -454,10 +455,10 @@ class DiagLayer:
454
455
  def _compute_available_global_neg_responses(self, odxlinks: OdxLinkDatabase) \
455
456
  -> Iterable[Response]:
456
457
 
457
- def get_local_objects_fn(dl):
458
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[Response]:
458
459
  return dl.diag_layer_raw.global_negative_responses
459
460
 
460
- def not_inherited_fn(parent_ref):
461
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
461
462
  return parent_ref.not_inherited_global_neg_responses
462
463
 
463
464
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
@@ -468,7 +469,7 @@ class DiagLayer:
468
469
  exclude: Callable[["ParentRef"], List[str]],
469
470
  ) -> NamedItemList[TNamed]:
470
471
 
471
- def get_local_objects_fn(dl: "DiagLayer"):
472
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[TNamed]:
472
473
  if dl.diag_layer_raw.diag_data_dictionary_spec is None:
473
474
  return []
474
475
  return include(dl.diag_layer_raw.diag_data_dictionary_spec)
@@ -478,40 +479,40 @@ class DiagLayer:
478
479
 
479
480
  def _compute_available_functional_classes(self) -> Iterable[FunctionalClass]:
480
481
 
481
- def get_local_objects_fn(dl):
482
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[FunctionalClass]:
482
483
  return dl.diag_layer_raw.functional_classes
483
484
 
484
- def not_inherited_fn(parent_ref):
485
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
485
486
  return []
486
487
 
487
488
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
488
489
 
489
490
  def _compute_available_additional_audiences(self) -> Iterable[AdditionalAudience]:
490
491
 
491
- def get_local_objects_fn(dl):
492
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[AdditionalAudience]:
492
493
  return dl.diag_layer_raw.additional_audiences
493
494
 
494
- def not_inherited_fn(parent_ref):
495
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
495
496
  return []
496
497
 
497
498
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
498
499
 
499
500
  def _compute_available_state_charts(self) -> Iterable[StateChart]:
500
501
 
501
- def get_local_objects_fn(dl):
502
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[StateChart]:
502
503
  return dl.diag_layer_raw.state_charts
503
504
 
504
- def not_inherited_fn(parent_ref):
505
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
505
506
  return []
506
507
 
507
508
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
508
509
 
509
510
  def _compute_available_unit_groups(self) -> Iterable[UnitGroup]:
510
511
 
511
- def get_local_objects_fn(dl):
512
+ def get_local_objects_fn(dl: DiagLayer) -> Iterable[UnitGroup]:
512
513
  return dl._get_local_unit_groups()
513
514
 
514
- def not_inherited_fn(parent_ref):
515
+ def not_inherited_fn(parent_ref: ParentRef) -> List[str]:
515
516
  return []
516
517
 
517
518
  return self._compute_available_objects(get_local_objects_fn, not_inherited_fn)
@@ -547,7 +548,7 @@ class DiagLayer:
547
548
  without a specified protocol are taken as fallbacks...
548
549
 
549
550
  """
550
- com_params_dict: Dict[Tuple[str, Optional[str]], CommunicationParameterRef] = dict()
551
+ com_params_dict: Dict[Tuple[str, Optional[str]], CommunicationParameterRef] = {}
551
552
 
552
553
  # Look in parent refs for inherited communication
553
554
  # parameters. First fetch the communication parameters from
@@ -563,7 +564,7 @@ class DiagLayer:
563
564
  return list(com_params_dict.values())
564
565
 
565
566
  @property
566
- def communication_parameters(self) -> List[CommunicationParameterRef]:
567
+ def communication_parameters(self) -> NamedItemList[CommunicationParameterRef]:
567
568
  """All communication parameters applicable to this DiagLayer
568
569
 
569
570
  Note that, although communication parameters use inheritance,
@@ -579,7 +580,7 @@ class DiagLayer:
579
580
  Note that protocols are *not* explicitly inherited objects,
580
581
  but the parent diagnostic layers of variant type "PROTOCOL".
581
582
  """
582
- result_dict: Dict[str, DiagLayer] = dict()
583
+ result_dict: Dict[str, DiagLayer] = {}
583
584
 
584
585
  for parent_ref in self._get_parent_refs_sorted_by_priority():
585
586
  for prot in parent_ref.layer.protocols:
@@ -613,6 +614,7 @@ class DiagLayer:
613
614
  f"Communication parameter `{cp_short_name}` specified more "
614
615
  f"than once. Using first occurence.",
615
616
  OdxWarning,
617
+ stacklevel=1,
616
618
  )
617
619
  elif len(cps) == 0:
618
620
  return None
@@ -897,7 +899,9 @@ class DiagLayer:
897
899
  # global negative responses. (I.e., one for each
898
900
  # service. This can be avoided by specifying the
899
901
  # corresponding request for `decode_response()`.)
900
- request_prefix = s.request.coded_const_prefix()
902
+ request_prefix = b''
903
+ if s.request is not None:
904
+ request_prefix = s.request.coded_const_prefix()
901
905
  prefixes = [request_prefix]
902
906
  prefixes += [
903
907
  x.coded_const_prefix(request_prefix=request_prefix) for x in chain(
@@ -954,11 +958,16 @@ class DiagLayer:
954
958
  for gnr in self.global_negative_responses:
955
959
  try:
956
960
  decoded_gnr = gnr.decode(message)
961
+ if not isinstance(decoded_gnr, dict):
962
+ raise DecodeError(f"Expected the decoded value of a global "
963
+ f"negative response to be a dictionary, "
964
+ f"got {type(decoded_gnr)} for {self.short_name}")
965
+
957
966
  decoded_messages.append(
958
967
  Message(
959
968
  coded_message=message,
960
969
  service=service,
961
- structure=gnr,
970
+ coding_object=gnr,
962
971
  param_dict=decoded_gnr))
963
972
  except DecodeError:
964
973
  pass
@@ -974,7 +983,7 @@ class DiagLayer:
974
983
 
975
984
  return self._decode(message, candidate_services)
976
985
 
977
- def decode_response(self, response: bytes, request: Union[bytes, Message]) -> Iterable[Message]:
986
+ def decode_response(self, response: bytes, request: Union[bytes, Message]) -> List[Message]:
978
987
  if isinstance(request, Message):
979
988
  candidate_services = [request.service]
980
989
  else:
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from itertools import chain
4
- from typing import List, Optional, Union
4
+ from typing import Any, Dict, List, Optional, Union
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from .admindata import AdminData
@@ -12,7 +12,7 @@ from .diaglayer import DiagLayer
12
12
  from .element import IdentifiableElement
13
13
  from .exceptions import odxrequire
14
14
  from .nameditemlist import NamedItemList
15
- from .odxlink import OdxDocFragment, OdxLinkDatabase
15
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
16
16
  from .specialdatagroup import SpecialDataGroup
17
17
  from .utils import dataclass_fields_asdict
18
18
 
@@ -82,62 +82,62 @@ class DiagLayerContainer(IdentifiableElement):
82
82
  sdgs=sdgs,
83
83
  **kwargs)
84
84
 
85
- def _build_odxlinks(self):
85
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
86
86
  result = {self.odx_id: self}
87
87
 
88
88
  if self.admin_data is not None:
89
89
  result.update(self.admin_data._build_odxlinks())
90
-
91
- if self.company_datas is not None:
92
- for cd in self.company_datas:
93
- result.update(cd._build_odxlinks())
94
-
95
- for dl in chain(
96
- self.ecu_shared_datas,
97
- self.protocols,
98
- self.functional_groups,
99
- self.base_variants,
100
- self.ecu_variants,
101
- ):
102
- result.update(dl._build_odxlinks())
103
-
90
+ for cd in self.company_datas:
91
+ result.update(cd._build_odxlinks())
104
92
  for sdg in self.sdgs:
105
93
  result.update(sdg._build_odxlinks())
106
94
 
95
+ for ecu_shared_data in self.ecu_shared_datas:
96
+ result.update(ecu_shared_data._build_odxlinks())
97
+ for protocol in self.protocols:
98
+ result.update(protocol._build_odxlinks())
99
+ for functional_group in self.functional_groups:
100
+ result.update(functional_group._build_odxlinks())
101
+ for base_variant in self.base_variants:
102
+ result.update(base_variant._build_odxlinks())
103
+ for ecu_variant in self.ecu_variants:
104
+ result.update(ecu_variant._build_odxlinks())
105
+
107
106
  return result
108
107
 
109
108
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
110
109
  if self.admin_data is not None:
111
110
  self.admin_data._resolve_odxlinks(odxlinks)
112
-
113
- if self.company_datas is not None:
114
- for cd in self.company_datas:
115
- cd._resolve_odxlinks(odxlinks)
116
-
117
- for dl in chain(
118
- self.ecu_shared_datas,
119
- self.protocols,
120
- self.functional_groups,
121
- self.base_variants,
122
- self.ecu_variants,
123
- ):
124
- dl._resolve_odxlinks(odxlinks)
125
-
111
+ for cd in self.company_datas:
112
+ cd._resolve_odxlinks(odxlinks)
126
113
  for sdg in self.sdgs:
127
114
  sdg._resolve_odxlinks(odxlinks)
128
115
 
116
+ for ecu_shared_data in self.ecu_shared_datas:
117
+ ecu_shared_data._resolve_odxlinks(odxlinks)
118
+ for protocol in self.protocols:
119
+ protocol._resolve_odxlinks(odxlinks)
120
+ for functional_group in self.functional_groups:
121
+ functional_group._resolve_odxlinks(odxlinks)
122
+ for base_variant in self.base_variants:
123
+ base_variant._resolve_odxlinks(odxlinks)
124
+ for ecu_variant in self.ecu_variants:
125
+ ecu_variant._resolve_odxlinks(odxlinks)
126
+
129
127
  def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
130
- for dl in chain(
131
- self.ecu_shared_datas,
132
- self.protocols,
133
- self.functional_groups,
134
- self.base_variants,
135
- self.ecu_variants,
136
- ):
137
- dl._finalize_init(odxlinks)
128
+ for ecu_shared_data in self.ecu_shared_datas:
129
+ ecu_shared_data._finalize_init(odxlinks)
130
+ for protocol in self.protocols:
131
+ protocol._finalize_init(odxlinks)
132
+ for functional_group in self.functional_groups:
133
+ functional_group._finalize_init(odxlinks)
134
+ for base_variant in self.base_variants:
135
+ base_variant._finalize_init(odxlinks)
136
+ for ecu_variant in self.ecu_variants:
137
+ ecu_variant._finalize_init(odxlinks)
138
138
 
139
139
  @property
140
- def diag_layers(self):
140
+ def diag_layers(self) -> NamedItemList[DiagLayer]:
141
141
  return self._diag_layers
142
142
 
143
143
  def __getitem__(self, key: Union[int, str]) -> DiagLayer: